pragma Singleton // Pointer size and text scale. // // These are the two settings that must agree across three consumers that do not // share a configuration system: the compositor draws the cursor, GTK // applications read gsettings, and the shell renders its own text. Panama's // store is the source of truth, and this pushes the value out to the other two // so they cannot disagree. // // pointer size -> gsettings (GTK) + `hyprctl setcursor` (compositor) // text scale -> gsettings (GTK) // // The shell's own font size is not scaled here. Theme.qml's sizes are part of // the design rather than a user preference, and scaling them at runtime would // reflow every panel against layouts that were tuned at the design size. Text // scale therefore affects applications, which is where it matters, and the // page says so rather than implying it does more. import Quickshell import Quickshell.Io import QtQuick import qs.config Singleton { id: root property string cursorTheme: "" property string lastError: "" readonly property bool busy: themeQuery.running || runner.running || root.pending.length > 0 readonly property int cursorSize: DesktopPreferences.get("cursorSize") readonly property real textScale: DesktopPreferences.get("textScale") Process { id: themeQuery command: ["gsettings", "get", "org.gnome.desktop.interface", "cursor-theme"] stdout: StdioCollector { onStreamFinished: { // gsettings quotes strings: 'oreo_blue_cursors' root.cursorTheme = this.text.trim().replace(/^'|'$/g, ""); } } } // A short queue, because applying one setting takes several commands and // Process runs one at a time. property var pending: [] Process { id: runner onExited: (exitCode, exitStatus) => { if (exitCode !== 0) root.lastError = "That accessibility setting could not be applied."; root.drain(); } } function drain(): void { if (runner.running || root.pending.length === 0) return; const next = root.pending[0]; root.pending = root.pending.slice(1); runner.exec(next); } function enqueue(commands: var): void { root.pending = root.pending.concat(commands); root.drain(); } Component.onCompleted: { themeQuery.running = true; settle.restart(); } // Push the stored values outward once at startup, so a value changed in a // previous session is in effect in this one even though gsettings and the // compositor do not read Panama's store. Timer { id: settle interval: 1200 onTriggered: root.applyAll() } Connections { target: DesktopPreferences function onRevisionChanged(): void { coalesce.restart(); } } Timer { id: coalesce interval: 250 onTriggered: root.applyAll() } function applyAll(): void { root.lastError = ""; const size = String(root.cursorSize); const commands = [ ["gsettings", "set", "org.gnome.desktop.interface", "cursor-size", size], ["gsettings", "set", "org.gnome.desktop.interface", "text-scaling-factor", String(root.textScale)] ]; // setcursor needs a theme name; skip it rather than guess if gsettings // has not answered yet. The next change will catch up. if (root.cursorTheme !== "") commands.push(["hyprctl", "setcursor", root.cursorTheme, size]); root.enqueue(commands); } }