pragma Singleton // Transition-only device monitoring. Resting state is intentionally silent. import Quickshell import Quickshell.Bluetooth import Quickshell.Io import Quickshell.Services.Pipewire import QtQuick import qs.config Singleton { id: root property bool outputInitialized: false property bool bluetoothInitialized: false property bool kdeInitialized: false property bool powerInitialized: false property bool previousAcOnline: true property string previousOutput: "" property string previousBluetooth: "" property bool previousKdeReachable: false property string previousKdeName: "" PwObjectTracker { objects: [Pipewire.defaultAudioSink] } readonly property string outputName: { const sink = Pipewire.defaultAudioSink; return sink?.description || sink?.nickname || sink?.name || ""; } readonly property string bluetoothNames: { const devices = Bluetooth.devices?.values ?? []; return devices.filter(device => device.connected) .map(device => device.name || device.address) .sort() .join("\u001f"); } readonly property string kdeName: KdeConnect.preferredPhone?.name ?? "" onOutputNameChanged: { if (!root.outputInitialized) { root.previousOutput = root.outputName; return; } if (root.outputInitialized && root.outputName && root.outputName !== root.previousOutput) { StatusEvents.publish({ key: "device-output", glyph: "\u{F07E7}", title: root.outputName, detail: "Audio output selected", priority: StatusEvents.ambientPriority }); } root.previousOutput = root.outputName; } onBluetoothNamesChanged: { if (!root.bluetoothInitialized) { root.previousBluetooth = root.bluetoothNames; return; } if (root.bluetoothInitialized && root.bluetoothNames !== root.previousBluetooth) { const current = root.bluetoothNames ? root.bluetoothNames.split("\u001f") : []; const previous = root.previousBluetooth ? root.previousBluetooth.split("\u001f") : []; const connected = current.find(name => !previous.includes(name)); const disconnected = previous.find(name => !current.includes(name)); StatusEvents.publish({ key: "device-bluetooth", glyph: "\u{F00B1}", title: connected || disconnected || "Bluetooth device", detail: connected ? "Connected" : "Disconnected", tone: connected ? "accent" : "warn", priority: StatusEvents.ambientPriority }); } root.previousBluetooth = root.bluetoothNames; } Timer { id: discoverySettle interval: 1800 onTriggered: { root.previousOutput = root.outputName; root.previousBluetooth = root.bluetoothNames; root.previousKdeReachable = KdeConnect.phoneReachable; root.previousKdeName = root.kdeName; root.previousAcOnline = Battery.acOnline; root.outputInitialized = true; root.bluetoothInitialized = true; root.kdeInitialized = true; root.powerInitialized = true; } } // The charger. Announced the way every other device transition here is: // once, on the change, and never as a description of the resting state. // Ambient priority, so Do Not Disturb quiets it -- a charger is exactly // the kind of thing DND is for. // // Absent entirely on a desktop, because Battery.acChanged only fires where // there is a battery to be on. Connections { target: Battery function onAcChanged(online: bool): void { if (!root.powerInitialized) { root.previousAcOnline = online; return; } if (online === root.previousAcOnline) return; root.previousAcOnline = online; StatusEvents.publish({ key: "device-power", glyph: online ? "\u{F06A5}" : "\u{F0084}", title: online ? "Charging" : "On battery", detail: Battery.available ? Math.round(Battery.percent) + "%" : "", priority: StatusEvents.ambientPriority }); } } // The "Warn at" threshold from the Power page. A quiet mention, not an // interruption: Do Not Disturb may still silence it, and the bar glyph // turns with it. For a long time this threshold changed only the glyph's // color, which nobody watching their work ever saw -- the first actual // message arrived at the critical level. Connections { target: Battery function onLowChanged(): void { if (!root.powerInitialized || !Battery.low || Battery.critical) return; StatusEvents.publish({ key: "battery-low", glyph: "\u{F007A}", title: "Battery low", detail: Math.round(Battery.percent) + "% remaining", tone: "warning", priority: StatusEvents.importantPriority, actionId: "open-settings", actionData: "power" }); } } // Running out is not ambient. Published at a priority Do Not Disturb does // not silence, because the one notification you must not miss is the one // saying the machine is about to stop. // // And then, unless told otherwise, the machine is put to sleep: a suspend // at the urgent threshold preserves the session for days, a hard cut at // 0% preserves nothing. The grace timer is what makes the warning above // readable and the action cancelable -- plugging in during it stands // everything down. Timer { id: criticalSuspend interval: 15000 onTriggered: { if (Battery.critical && !Battery.acOnline && DesktopPreferences.get("batteryCriticalAction") === "suspend") Quickshell.execDetached(["systemctl", "suspend"]); } } Connections { target: Battery function onAcOnlineChanged(): void { if (Battery.acOnline) criticalSuspend.stop(); } } Connections { target: Battery function onCriticalChanged(): void { if (!root.powerInitialized || !Battery.critical) return; if (!Battery.acOnline && DesktopPreferences.get("batteryCriticalAction") === "suspend") criticalSuspend.restart(); StatusEvents.publish({ key: "battery-critical", glyph: "\u{F0083}", title: "Battery critically low", detail: Math.round(Battery.percent) + "% remaining", tone: "danger", priority: StatusEvents.criticalPriority, actionId: "open-settings", actionData: "power" }); } } Connections { target: KdeConnect function onPhoneReachableChanged(): void { if (!root.kdeInitialized) return; const reachable = KdeConnect.phoneReachable; if (reachable === root.previousKdeReachable) return; StatusEvents.publish({ key: "device-kdeconnect", glyph: "\u{F03F2}", title: root.kdeName || root.previousKdeName || "Phone", detail: reachable ? "KDE Connect available" : "KDE Connect disconnected", tone: reachable ? "accent" : "warn", priority: StatusEvents.ambientPriority }); root.previousKdeReachable = reachable; root.previousKdeName = root.kdeName || root.previousKdeName; } } Component.onCompleted: { root.previousOutput = root.outputName; root.previousBluetooth = root.bluetoothNames; root.previousKdeReachable = KdeConnect.phoneReachable; root.previousKdeName = root.kdeName; discoverySettle.start(); } }