refactor(quickshell): remove unused system monitor

This commit is contained in:
fy59 2026-10-05 13:02:41 +02:00
parent 06d2d365a0
commit 71a8ee189f
10 changed files with 4 additions and 427 deletions

View file

@ -7,7 +7,6 @@ import "windows"
import "datecenter"
import "notifications"
import "status"
import "systemmonitor"
import "theme"
PanelWindow {
@ -44,7 +43,6 @@ PanelWindow {
function toggleDateCenter() {
const opening = !dateCenter.visible && !dateCenter.opening;
controlCenter.visible = false;
systemMonitor.visible = false;
windowStrip.closeMenu();
if (opening) dateCenter.requestOpen();
else dateCenter.requestClose();
@ -76,7 +74,6 @@ PanelWindow {
maxWidth: Math.max(0, leftArea.width - x - 8)
onMenuOpened: {
controlCenter.visible = false;
systemMonitor.visible = false;
dateCenter.requestClose();
}
}
@ -110,29 +107,19 @@ PanelWindow {
wifiDevice: controlCenter.wifiDevice
adapter: controlCenter.adapter
controlCenterOpen: controlCenter.visible
systemMonitorOpen: systemMonitor.visible
onOpenPageRequested: page => {
systemMonitor.visible = false;
dateCenter.requestClose();
windowStrip.closeMenu();
controlCenter.currentPage = page;
controlCenter.visible = true;
}
onToggleControlCenterRequested: {
systemMonitor.visible = false;
const opening = !controlCenter.visible;
dateCenter.requestClose();
windowStrip.closeMenu();
controlCenter.currentPage = 0;
controlCenter.visible = opening;
}
onToggleSystemMonitorRequested: {
const opening = !systemMonitor.visible;
controlCenter.visible = false;
dateCenter.requestClose();
windowStrip.closeMenu();
systemMonitor.visible = opening;
}
}
}
@ -142,11 +129,6 @@ PanelWindow {
onThresholdApplied: rightStatusArea.refreshBatteryThreshold()
}
SystemMonitor {
id: systemMonitor
barWindow: bar
}
DateCenter {
id: dateCenter
barWindow: bar
@ -156,7 +138,5 @@ PanelWindow {
NotificationToastArea {
barWindow: bar
service: bar.notificationService
monitorOpen: systemMonitor.visible
monitorWidth: systemMonitor.implicitWidth
}
}

View file

@ -18,7 +18,6 @@ Bar
│ ├── Battery
│ ├── Updates
│ ├── Tray
│ ├── System Monitor
│ └── Control Center
└── Control Center
├── Wi-Fi
@ -47,9 +46,8 @@ est 60 % ; le helper root est documenté dans `bin/root/README.md`.
Dépendances essentielles : `quickshell`, SwayFX 0.6, `scenefx`, une Nerd Font,
NetworkManager, BlueZ, PipeWire, TLP/`tlpctl`, `checkupdates`, `yay`, `df`,
`python3`, `swaylock` et le helper batterie pour le réglage du seuil. Le
backlight utilise le noeud machine `amdgpu_bl1` dans `BrightnessSlider.qml` ;
le System Monitor résout dynamiquement les noeuds DRM AMD et hwmon
`k10temp`/`amdgpu` par identité, noms et labels. Les scans Wi-Fi et Bluetooth
backlight utilise le noeud machine `amdgpu_bl1` dans `BrightnessSlider.qml`.
Les scans Wi-Fi et Bluetooth
ne sont possédés que par les pages ouvertes.
Le gestionnaire de fonds d'écran local est décrit dans

View file

@ -1,53 +0,0 @@
#!/usr/bin/python3
"""Résout les capteurs de cette session sans figer les numéros sysfs."""
from pathlib import Path
import re
DRM = Path("/sys/class/drm")
HWMON = Path("/sys/class/hwmon")
def read(path):
try:
return path.read_text(encoding="ascii").strip()
except (OSError, UnicodeError):
return ""
def gpu_busy():
# INVARIANT: seul un noeud cardN dont l'identité PCI est AMD est retenu.
for card in sorted(DRM.glob("card[0-9]*")):
if not re.fullmatch(r"card\d+", card.name):
continue
device = card / "device"
candidate = device / "gpu_busy_percent"
if read(device / "vendor").lower() == "0x1002" and candidate.is_file():
return candidate
return None
def temperature(name, label):
for monitor in sorted(HWMON.glob("hwmon[0-9]*")):
if read(monitor / "name") != name:
continue
# CONTRACT: préférer le libellé physique ; temp1 sert aux pilotes sans labels.
inputs = sorted(monitor.glob("temp*_input"))
for candidate in inputs:
if read(candidate.with_name(candidate.name.replace("_input", "_label"))).lower() == label:
return candidate
fallback = monitor / "temp1_input"
if fallback.is_file():
return fallback
return None
def main():
# Format fixe sur trois lignes, contrôlé côté QML. Aucun chemin utilisateur.
for path in (gpu_busy(), temperature("k10temp", "tctl"), temperature("amdgpu", "edge")):
print(path or "")
if __name__ == "__main__":
main()

View file

@ -5,12 +5,8 @@ PopupWindow {
id: area
required property var barWindow
required property var service
property bool monitorOpen: false
property int monitorWidth: 400
anchor.window: barWindow
// Laisser les notifications visibles sans recouvrir le moniteur ouvert.
anchor.rect.x: monitorOpen ? barWindow.width - monitorWidth - implicitWidth - 16
: barWindow.width - implicitWidth - 8
anchor.rect.x: barWindow.width - implicitWidth - 8
anchor.rect.y: barWindow.height + 8
anchor.edges: Edges.Top | Edges.Left
anchor.gravity: Edges.Bottom | Edges.Right

View file

@ -1,6 +1,5 @@
import QtQuick
import "../controlcenter"
import "../systemmonitor"
Row {
id: area
@ -8,10 +7,8 @@ Row {
required property var wifiDevice
required property var adapter
required property bool controlCenterOpen
required property bool systemMonitorOpen
signal openPageRequested(int page)
signal toggleControlCenterRequested()
signal toggleSystemMonitorRequested()
function refreshBatteryThreshold() { batteryIndicator.refreshThreshold() }
spacing: 5
height: 26
@ -21,9 +18,5 @@ Row {
BatteryIndicator { id: batteryIndicator; onActivated: area.openPageRequested(5) }
UpdatesIndicator { }
Tray { barWindow: area.barWindow }
SystemMonitorButton {
open: area.systemMonitorOpen
onToggled: area.toggleSystemMonitorRequested()
}
ControlCenterButton { open: area.controlCenterOpen; onToggled: area.toggleControlCenterRequested() }
}

View file

@ -1,31 +0,0 @@
import QtQuick
import "../theme"
Column {
id: row
required property string label
required property string value
required property real percentage
property color accent: Theme.mauve
width: parent.width
spacing: 6
Row {
width: parent.width
Text { id: labelText; text: row.label; color: Theme.foreground; font.pixelSize: 12 }
Item { width: Math.max(0, parent.width - valueText.width - labelText.width); height: 1 }
Text { id: valueText; text: row.value; color: Theme.subtext1; font.pixelSize: 12 }
}
Rectangle {
width: parent.width
height: 5
radius: 3
color: Theme.buttonBackground
Rectangle {
width: parent.width * Math.max(0, Math.min(100, row.percentage)) / 100
height: parent.height
radius: 3
color: row.accent
}
}
}

View file

@ -1,177 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Io
Item {
id: metrics
property bool active: false
property real cpuPercent: -1
property real ramPercent: -1
property real ramUsedGiB: -1
property real ramTotalGiB: -1
property real gpuPercent: -1
property real cpuTemp: -1
property real gpuTemp: -1
property real diskPercent: -1
property real diskUsedGiB: -1
property real diskTotalGiB: -1
property string loadAverage: ""
property string uptimeText: ""
property var previousCpu: null
property string gpuPath: ""
property string cpuTempPath: ""
property string gpuTempPath: ""
function numberFromFile(view, divisor) {
const raw = view.text().trim();
if (!/^[0-9]+$/.test(raw)) return -1;
const value = Number(raw) / divisor;
return Number.isFinite(value) ? value : -1;
}
// CONTRACT: la première lecture CPU constitue seulement la référence ; seul un delta positif produit un pourcentage.
function updateCpu() {
const line = cpuFile.text().split("\n")[0].trim().split(/\s+/);
if (line[0] !== "cpu" || line.length < 6) return;
const values = line.slice(1).map(Number);
if (values.some(value => !Number.isFinite(value))) return;
const total = values.reduce((sum, value) => sum + value, 0);
const idle = values[3] + values[4]; // idle + iowait
if (previousCpu) {
const totalDelta = total - previousCpu.total;
const idleDelta = idle - previousCpu.idle;
if (totalDelta > 0 && idleDelta >= 0)
cpuPercent = Math.max(0, Math.min(100, 100 * (1 - idleDelta / totalDelta)));
}
previousCpu = { total: total, idle: idle };
}
function updateMemory() {
const entries = {};
for (const line of memoryFile.text().split("\n")) {
const match = line.match(/^(MemTotal|MemAvailable):\s+([0-9]+) kB$/);
if (match) entries[match[1]] = Number(match[2]);
}
if (!(entries.MemTotal > 0) || !(entries.MemAvailable >= 0)) return;
const used = Math.max(0, entries.MemTotal - entries.MemAvailable);
ramPercent = Math.min(100, used * 100 / entries.MemTotal);
ramUsedGiB = used / 1048576;
ramTotalGiB = entries.MemTotal / 1048576;
}
function updateLoad() {
const fields = loadFile.text().trim().split(/\s+/);
if (fields.length >= 3 && fields.slice(0, 3).every(value => /^[0-9]+\.[0-9]+$/.test(value)))
loadAverage = fields.slice(0, 3).join(" ");
}
function updateUptime() {
const seconds = Number(uptimeFile.text().trim().split(/\s+/)[0]);
if (!Number.isFinite(seconds) || seconds < 0) return;
const minutes = Math.floor(seconds / 60);
const days = Math.floor(minutes / 1440);
const hours = Math.floor((minutes % 1440) / 60);
uptimeText = days > 0 ? days + " j " + hours + " h" : hours + " h " + minutes % 60 + " min";
}
function sample() {
if (!active) return;
cpuFile.reload();
memoryFile.reload();
gpuFile.reload();
cpuTempFile.reload();
gpuTempFile.reload();
loadFile.reload();
uptimeFile.reload();
}
function refreshDisk() {
if (active && !diskReader.running) diskReader.running = true;
}
onActiveChanged: {
if (active) {
previousCpu = null;
cpuPercent = -1;
gpuPercent = -1;
cpuTemp = -1;
gpuTemp = -1;
gpuPath = "";
cpuTempPath = "";
gpuTempPath = "";
// WHY: les numéros cardN et hwmonN changent au redémarrage.
if (!hardwareResolver.running) hardwareResolver.running = true;
sample();
refreshDisk();
} else {
previousCpu = null;
diskReader.running = false;
}
}
// INVARIANT: aucun échantillonnage ni Process df périodique quand le panneau est fermé.
Timer { interval: 1000; repeat: true; running: metrics.active; onTriggered: metrics.sample() }
Timer { interval: 15000; repeat: true; running: metrics.active; onTriggered: metrics.refreshDisk() }
FileView { id: cpuFile; path: "/proc/stat"; onLoaded: metrics.updateCpu() }
FileView { id: memoryFile; path: "/proc/meminfo"; onLoaded: metrics.updateMemory() }
FileView {
id: gpuFile
path: metrics.gpuPath
printErrors: false
onLoaded: metrics.gpuPercent = metrics.numberFromFile(gpuFile, 1)
}
FileView {
id: cpuTempFile
path: metrics.cpuTempPath
printErrors: false
onLoaded: metrics.cpuTemp = metrics.numberFromFile(cpuTempFile, 1000)
}
FileView {
id: gpuTempFile
path: metrics.gpuTempPath
printErrors: false
onLoaded: metrics.gpuTemp = metrics.numberFromFile(gpuTempFile, 1000)
}
FileView { id: loadFile; path: "/proc/loadavg"; onLoaded: metrics.updateLoad() }
FileView { id: uptimeFile; path: "/proc/uptime"; onLoaded: metrics.updateUptime() }
Process {
id: hardwareResolver
command: ["/usr/bin/python3", Quickshell.shellPath("bin/resolve-hardware.py")]
stdout: StdioCollector { id: hardwareOutput; waitForEnd: true }
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0 || exitStatus !== 0) return;
const paths = hardwareOutput.text.trimEnd().split("\n");
if (paths.length !== 3) return;
// CONTRACT: seuls les chemins sysfs résolus par le helper sont chargés.
if (paths[0].startsWith("/sys/class/drm/")) metrics.gpuPath = paths[0];
if (paths[1].startsWith("/sys/class/hwmon/")) metrics.cpuTempPath = paths[1];
if (paths[2].startsWith("/sys/class/hwmon/")) metrics.gpuTempPath = paths[2];
if (metrics.active) {
gpuFile.reload();
cpuTempFile.reload();
gpuTempFile.reload();
}
}
}
Process {
id: diskReader
command: ["df", "-B1", "--output=size,used,pcent,target", "/"]
stdout: StdioCollector { id: diskOutput; waitForEnd: true }
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0 || exitStatus !== 0) return;
const lines = diskOutput.text.trim().split("\n");
if (lines.length < 2) return;
const fields = lines[lines.length - 1].trim().split(/\s+/);
if (fields.length !== 4 || fields[3] !== "/") return;
const total = Number(fields[0]);
const used = Number(fields[1]);
if (!(total > 0) || !(used >= 0)) return;
metrics.diskTotalGiB = total / 1073741824;
metrics.diskUsedGiB = used / 1073741824;
metrics.diskPercent = Math.min(100, used * 100 / total);
}
}
}

View file

@ -1,93 +0,0 @@
import QtQuick
import Quickshell
import "../components"
import "../theme"
PopupWindow {
id: popup
required property var barWindow
anchor.window: barWindow
anchor.rect.x: barWindow.width - implicitWidth - 8
anchor.rect.y: barWindow.height + 6
anchor.edges: Edges.Top | Edges.Left
anchor.gravity: Edges.Bottom | Edges.Right
implicitWidth: 400
implicitHeight: content.implicitHeight + 32
visible: false
// Le panneau est informatif ; laisser les boutons de barre recevoir les clics XOR.
grabFocus: false
color: "transparent"
SystemMetrics { id: metrics; active: popup.visible }
Rectangle {
anchors.fill: parent
radius: 4
color: Theme.popupBackground
border.color: Theme.outline
Column {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 16
spacing: 16
Row {
spacing: 8
NerdIcon { text: "󰍛"; font.pixelSize: 18 }
Text { text: "Moniteur système"; color: Theme.foreground; font.pixelSize: 16; font.bold: true }
}
MetricRow {
label: "CPU"
percentage: metrics.cpuPercent
value: (metrics.cpuPercent >= 0 ? Math.round(metrics.cpuPercent) + " %" : "—")
+ (metrics.cpuTemp >= 0 ? " " + Math.round(metrics.cpuTemp) + " °C" : "")
accent: metrics.cpuPercent >= 90 ? Theme.danger : metrics.cpuPercent >= 70 ? Theme.urgent : Theme.mauve
}
MetricRow {
label: "RAM"
percentage: metrics.ramPercent
value: metrics.ramPercent >= 0
? metrics.ramUsedGiB.toFixed(1) + " / " + metrics.ramTotalGiB.toFixed(1)
+ " GiB " + Math.round(metrics.ramPercent) + " %" : "—"
accent: metrics.ramPercent >= 90 ? Theme.danger : metrics.ramPercent >= 75 ? Theme.urgent : Theme.mauve
}
MetricRow {
visible: metrics.gpuPercent >= 0 || metrics.gpuTemp >= 0
label: "GPU"
percentage: metrics.gpuPercent
value: (metrics.gpuPercent >= 0 ? Math.round(metrics.gpuPercent) + " %" : "")
+ (metrics.gpuTemp >= 0 ? (metrics.gpuPercent >= 0 ? " " : "")
+ Math.round(metrics.gpuTemp) + " °C" : "")
}
MetricRow {
label: "Disque /"
percentage: metrics.diskPercent
value: metrics.diskPercent >= 0
? metrics.diskUsedGiB.toFixed(0) + " / " + metrics.diskTotalGiB.toFixed(0)
+ " GiB " + Math.round(metrics.diskPercent) + " %" : "—"
}
Row {
width: parent.width
Text { id: loadLabel; text: "Charge (1/5/15 min)"; color: Theme.foreground; font.pixelSize: 12 }
Item { width: Math.max(0, parent.width - loadValue.width - loadLabel.width); height: 1 }
Text { id: loadValue; text: metrics.loadAverage || "—"; color: Theme.subtext1; font.pixelSize: 12 }
}
Row {
width: parent.width
Text { id: uptimeLabel; text: "En marche"; color: Theme.foreground; font.pixelSize: 12 }
Item { width: Math.max(0, parent.width - uptimeValue.width - uptimeLabel.width); height: 1 }
Text { id: uptimeValue; text: metrics.uptimeText || "—"; color: Theme.subtext1; font.pixelSize: 12 }
}
}
}
}

View file

@ -1,35 +0,0 @@
import QtQuick
import "../components"
import "../status"
import "../theme"
Rectangle {
id: button
required property bool open
signal toggled()
width: 26
height: 26
radius: 4
color: open ? Theme.accent : pointer.containsMouse ? Theme.border : Theme.buttonBackground
NerdIcon {
anchors.centerIn: parent
text: "󰍛"
// WHY: compenser la surface plus faible du glyphe sans agrandir le bouton.
font.pixelSize: 20
color: button.open ? Theme.onAccent : Theme.foreground
}
MouseArea {
id: pointer
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: button.toggled()
}
StatusTooltip {
target: button
hovered: pointer.containsMouse && !button.open
message: "Moniteur système"
}
}

View file

@ -41,8 +41,7 @@ n'atteint pas 4,5:1 avec une couleur Catppuccin ; le noir atteint environ
Les 34 usages historiques de Mauve ont été classés par rôle : sélection,
focus, indicateur ou contrôle utilisent maintenant l'accent principal
Lavender. Les séries CPU/RAM du System Monitor restent intentionnellement
Mauve. Ce choix rend les contrôles historiques Mauve légèrement plus bleus
Lavender. Ce choix rend les contrôles historiques Mauve légèrement plus bleus
qu'avant. Les trois anciens Lavender restent l'accent principal.
L'API de thème est `AppearanceController.setManualFlavor(flavor)` et