refactor(quickshell): remove unused system monitor
This commit is contained in:
parent
06d2d365a0
commit
71a8ee189f
10 changed files with 4 additions and 427 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue