diff options
Diffstat (limited to 'services')
| -rw-r--r-- | services/BeatDetector.qml | 8 | ||||
| -rw-r--r-- | services/Bluetooth.qml | 52 | ||||
| -rw-r--r-- | services/Brightness.qml | 25 | ||||
| -rw-r--r-- | services/Cava.qml | 2 | ||||
| -rw-r--r-- | services/Colours.qml | 25 | ||||
| -rw-r--r-- | services/Hyprland.qml | 15 | ||||
| -rw-r--r-- | services/Network.qml | 48 | ||||
| -rw-r--r-- | services/Notifs.qml | 4 | ||||
| -rw-r--r-- | services/SystemUsage.qml | 83 | ||||
| -rw-r--r-- | services/Wallpapers.qml | 19 | ||||
| -rw-r--r-- | services/Weather.qml | 28 |
11 files changed, 168 insertions, 141 deletions
diff --git a/services/BeatDetector.qml b/services/BeatDetector.qml index 66bf2d4..65dd88e 100644 --- a/services/BeatDetector.qml +++ b/services/BeatDetector.qml @@ -10,9 +10,13 @@ Singleton { Process { running: true - command: [`${Quickshell.shellRoot}/assets/realtime-beat-detector.py`] + command: ["/usr/lib/caelestia/beat_detector", "--no-log", "--no-stats", "--no-visual"] stdout: SplitParser { - onRead: data => root.bpm = parseFloat(data) + onRead: data => { + const match = data.match(/BPM: ([0-9]+\.[0-9])/); + if (match) + root.bpm = parseFloat(match[1]); + } } } } diff --git a/services/Bluetooth.qml b/services/Bluetooth.qml index 43f3636..f5e9de1 100644 --- a/services/Bluetooth.qml +++ b/services/Bluetooth.qml @@ -24,31 +24,47 @@ Singleton { Process { id: getInfo + running: true - command: ["sh", "-c", "bluetoothctl show | paste -s"] - stdout: SplitParser { - onRead: data => { - root.powered = data.includes("Powered: yes"); - root.discovering = data.includes("Discovering: yes"); + command: ["bluetoothctl", "show"] + stdout: StdioCollector { + onStreamFinished: { + root.powered = text.includes("Powered: yes"); + root.discovering = text.includes("Discovering: yes"); } } } Process { id: getDevices + running: true - command: ["fish", "-c", `for a in (bluetoothctl devices | cut -d ' ' -f 2); bluetoothctl info $a | jq -R 'reduce (inputs / ":") as [$key, $value] ({}; .[$key | ltrimstr("\t")] = ($value | ltrimstr(" ")))' | jq -c --arg addr $a '.Address = $addr'; end | jq -sc`] - stdout: SplitParser { - onRead: data => { - const devices = JSON.parse(data).filter(d => d.Name); + command: ["fish", "-c", ` + for a in (bluetoothctl devices) + if string match -q 'Device *' $a + bluetoothctl info $addr (string split ' ' $a)[2] + echo + end + end`] + stdout: StdioCollector { + onStreamFinished: { + const devices = text.trim().split("\n\n").map(d => ({ + name: d.match(/Name: (.*)/)[1], + alias: d.match(/Alias: (.*)/)[1], + address: d.match(/Device ([0-9A-Z:]{17})/)[1], + icon: d.match(/Icon: (.*)/)[1], + connected: d.includes("Connected: yes"), + paired: d.includes("Paired: yes"), + trusted: d.includes("Trusted: yes") + })); const rDevices = root.devices; - const destroyed = rDevices.filter(rd => !devices.find(d => d.Address === rd.address)); + const destroyed = rDevices.filter(rd => !devices.find(d => d.address === rd.address)); for (const device of destroyed) rDevices.splice(rDevices.indexOf(device), 1).forEach(d => d.destroy()); for (const device of devices) { - const match = rDevices.find(d => d.address === device.Address); + const match = rDevices.find(d => d.address === device.address); if (match) { match.lastIpcObject = device; } else { @@ -63,13 +79,13 @@ Singleton { component Device: QtObject { required property var lastIpcObject - readonly property string name: lastIpcObject.Name - readonly property string alias: lastIpcObject.Alias - readonly property string address: lastIpcObject.Address - readonly property string icon: lastIpcObject.Icon - readonly property bool connected: lastIpcObject.Connected === "yes" - readonly property bool paired: lastIpcObject.Paired === "yes" - readonly property bool trusted: lastIpcObject.Trusted === "yes" + readonly property string name: lastIpcObject.name + readonly property string alias: lastIpcObject.alias + readonly property string address: lastIpcObject.address + readonly property string icon: lastIpcObject.icon + readonly property bool connected: lastIpcObject.connected + readonly property bool paired: lastIpcObject.paired + readonly property bool trusted: lastIpcObject.trusted } Component { diff --git a/services/Brightness.qml b/services/Brightness.qml index 1687878..3715853 100644 --- a/services/Brightness.qml +++ b/services/Brightness.qml @@ -9,7 +9,7 @@ import QtQuick Singleton { id: root - property var ddcMonitors: [] + property list<var> ddcMonitors: [] readonly property list<Monitor> monitors: variants.instances function getMonitorForScreen(screen: ShellScreen): var { @@ -49,19 +49,12 @@ Singleton { id: ddcProc command: ["ddcutil", "detect", "--brief"] - stdout: SplitParser { - splitMarker: "\n\n" - onRead: data => { - if (data.startsWith("Display ")) { - const lines = data.split("\n").map(l => l.trim()); - root.ddcMonitors.push({ - model: lines.find(l => l.startsWith("Monitor:")).split(":")[2], - busNum: lines.find(l => l.startsWith("I2C bus:")).split("/dev/i2c-")[1] - }); - } - } + stdout: StdioCollector { + onStreamFinished: root.ddcMonitors = text.trim().split("\n\n").filter(d => d.startsWith("Display ")).map(d => ({ + model: d.match(/Monitor:.*:(.*):.*/)[1], + busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)[1] + })) } - onExited: root.ddcMonitorsChanged() } Process { @@ -87,9 +80,9 @@ Singleton { property real brightness readonly property Process initProc: Process { - stdout: SplitParser { - onRead: data => { - const [, , , current, max] = data.split(" "); + stdout: StdioCollector { + onStreamFinished: { + const [, , , current, max] = text.split(" "); monitor.brightness = parseInt(current) / parseInt(max); } } diff --git a/services/Cava.qml b/services/Cava.qml index eaa6e20..3fa5083 100644 --- a/services/Cava.qml +++ b/services/Cava.qml @@ -11,7 +11,7 @@ Singleton { Process { running: true - command: ["sh", "-c", `printf '[general]\nframerate=60\nbars=${DashboardConfig.visualiserBars}\n[output]\nchannels=mono\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nascii_max_range=100' | cava -p /dev/stdin`] + command: ["sh", "-c", `printf '[general]\nframerate=60\nbars=${Config.dashboard.visualiserBars}\n[output]\nchannels=mono\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nascii_max_range=100' | cava -p /dev/stdin`] stdout: SplitParser { onRead: data => root.values = data.slice(0, -1).split(";").map(v => parseInt(v, 10)) } diff --git a/services/Colours.qml b/services/Colours.qml index 4383972..ea3b02a 100644 --- a/services/Colours.qml +++ b/services/Colours.qml @@ -36,12 +36,14 @@ Singleton { function load(data: string, isPreview: bool): void { const colours = isPreview ? preview : current; - for (const line of data.trim().split("\n")) { - let [name, colour] = line.split(" "); - name = name.trim(); - name = colourNames.includes(name) ? name : `m3${name}`; - if (colours.hasOwnProperty(name)) - colours[name] = `#${colour.trim()}`; + const scheme = JSON.parse(data); + + light = scheme.mode === "light"; + + for (const [name, colour] of Object.entries(scheme.colours)) { + const propName = colourNames.includes(name) ? name : `m3${name}`; + if (colours.hasOwnProperty(propName)) + colours[propName] = `#${colour}`; } if (!isPreview || (isPreview && endPreviewOnNextChange)) { @@ -51,7 +53,7 @@ Singleton { } function setMode(mode: string): void { - setModeProc.command = ["caelestia", "scheme", "dynamic", "default", mode]; + setModeProc.command = ["caelestia", "scheme", "-m", mode]; setModeProc.startDetached(); } @@ -60,14 +62,7 @@ Singleton { } FileView { - path: `${Paths.state}/scheme/current-mode.txt` - watchChanges: true - onFileChanged: reload() - onLoaded: root.light = text() === "light" - } - - FileView { - path: `${Paths.state}/scheme/current.txt` + path: `${Paths.state}/scheme.json` watchChanges: true onFileChanged: reload() onLoaded: root.load(text(), false) diff --git a/services/Hyprland.qml b/services/Hyprland.qml index 610fba2..b8fa7c5 100644 --- a/services/Hyprland.qml +++ b/services/Hyprland.qml @@ -41,10 +41,10 @@ Singleton { Process { id: getClients - command: ["sh", "-c", "hyprctl -j clients | jq -c"] - stdout: SplitParser { - onRead: data => { - const clients = JSON.parse(data); + command: ["hyprctl", "-j", "clients"] + stdout: StdioCollector { + onStreamFinished: { + const clients = JSON.parse(text); const rClients = root.clients; const destroyed = rClients.filter(rc => !clients.find(c => c.address === rc.address)); @@ -68,10 +68,9 @@ Singleton { Process { id: getActiveClient command: ["hyprctl", "-j", "activewindow"] - stdout: SplitParser { - splitMarker: "" - onRead: data => { - const client = JSON.parse(data); + stdout: StdioCollector { + onStreamFinished: { + const client = JSON.parse(text); const rClient = root.activeClient; if (client.address) { if (rClient) diff --git a/services/Network.qml b/services/Network.qml index b4827c8..6a7f50e 100644 --- a/services/Network.qml +++ b/services/Network.qml @@ -23,31 +23,36 @@ Singleton { Process { id: getNetworks running: true - command: ["sh", "-c", `nmcli -g ACTIVE,SIGNAL,FREQ,SSID,BSSID d w | jq -ncR '[(inputs | split("(?<!\\\\\\\\):"; "g")) | select(.[3] | length >= 4)]'`] - stdout: SplitParser { - onRead: data => { - const networks = JSON.parse(data).map(n => [n[0] === "yes", parseInt(n[1]), parseInt(n[2]), n[3], n[4].replace(/\\/g, "")]); + command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID", "d", "w"] + stdout: StdioCollector { + onStreamFinished: { + const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED"; + const rep = new RegExp("\\\\:", "g"); + const rep2 = new RegExp(PLACEHOLDER, "g"); + + const networks = text.trim().split("\n").map(n => { + const net = n.replace(rep, PLACEHOLDER).split(":"); + return { + active: net[0] === "yes", + strength: parseInt(net[1]), + frequency: parseInt(net[2]), + ssid: net[3], + bssid: net[4].replace(rep2, ":") + }; + }); const rNetworks = root.networks; - const destroyed = rNetworks.filter(rn => !networks.find(n => n[2] === rn.frequency && n[3] === rn.ssid && n[4] === rn.bssid)); + const destroyed = rNetworks.filter(rn => !networks.find(n => n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid)); for (const network of destroyed) rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy()); for (const network of networks) { - const match = rNetworks.find(n => n.frequency === network[2] && n.ssid === network[3] && n.bssid === network[4]); + const match = rNetworks.find(n => n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid); if (match) { - match.active = network[0]; - match.strength = network[1]; - match.frequency = network[2]; - match.ssid = network[3]; - match.bssid = network[4]; + match.lastIpcObject = network; } else { rNetworks.push(apComp.createObject(root, { - active: network[0], - strength: network[1], - frequency: network[2], - ssid: network[3], - bssid: network[4] + lastIpcObject: network })); } } @@ -56,11 +61,12 @@ Singleton { } component AccessPoint: QtObject { - required property string ssid - required property string bssid - required property int strength - required property int frequency - required property bool active + required property var lastIpcObject + readonly property string ssid: lastIpcObject.ssid + readonly property string bssid: lastIpcObject.bssid + readonly property int strength: lastIpcObject.strength + readonly property int frequency: lastIpcObject.frequency + readonly property bool active: lastIpcObject.active } Component { diff --git a/services/Notifs.qml b/services/Notifs.qml index 4acd56f..73d98a2 100644 --- a/services/Notifs.qml +++ b/services/Notifs.qml @@ -80,9 +80,9 @@ Singleton { readonly property Timer timer: Timer { running: true - interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : NotifsConfig.defaultExpireTimeout + interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : Config.notifs.defaultExpireTimeout onTriggered: { - if (NotifsConfig.expire) + if (Config.notifs.expire) notif.popup = false; } } diff --git a/services/SystemUsage.qml b/services/SystemUsage.qml index ef953f1..9bf665a 100644 --- a/services/SystemUsage.qml +++ b/services/SystemUsage.qml @@ -98,38 +98,40 @@ Singleton { running: true command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $1, $3, $4}'"] - stdout: SplitParser { - splitMarker: "" - onRead: data => { - const deviceMap = new Map(); + stdout: StdioCollector { + onStreamFinished: { + const deviceMap = new Map(); - for (const line of data.trim().split("\n")) { - if (line.trim() === "") continue; + for (const line of text.trim().split("\n")) { + if (line.trim() === "") + continue; - const parts = line.trim().split(/\s+/); - if (parts.length >= 3) { - const device = parts[0]; - const used = parseInt(parts[1], 10) || 0; - const avail = parseInt(parts[2], 10) || 0; - - // only keep the entry with the largest total space for each device - if (!deviceMap.has(device) || - (used + avail) > (deviceMap.get(device).used + deviceMap.get(device).avail)) { - deviceMap.set(device, { used: used, avail: avail }); - } - } - } + const parts = line.trim().split(/\s+/); + if (parts.length >= 3) { + const device = parts[0]; + const used = parseInt(parts[1], 10) || 0; + const avail = parseInt(parts[2], 10) || 0; - let totalUsed = 0; - let totalAvail = 0; - - for (const [device, stats] of deviceMap) { - totalUsed += stats.used; - totalAvail += stats.avail; - } + // Only keep the entry with the largest total space for each device + if (!deviceMap.has(device) || (used + avail) > (deviceMap.get(device).used + deviceMap.get(device).avail)) { + deviceMap.set(device, { + used: used, + avail: avail + }); + } + } + } + + let totalUsed = 0; + let totalAvail = 0; - root.storageUsed = totalUsed; - root.storageTotal = totalUsed + totalAvail; + for (const [device, stats] of deviceMap) { + totalUsed += stats.used; + totalAvail += stats.avail; + } + + root.storageUsed = totalUsed; + root.storageTotal = totalUsed + totalAvail; } } } @@ -138,10 +140,10 @@ Singleton { id: cpuTemp running: true - command: ["fish", "-c", "cat /sys/class/thermal/thermal_zone*/temp | string join ' '"] - stdout: SplitParser { - onRead: data => { - const temps = data.trim().split(" "); + command: ["sh", "-c", "cat /sys/class/thermal/thermal_zone*/temp"] + stdout: StdioCollector { + onStreamFinished: { + const temps = text.trim().split(" "); const sum = temps.reduce((acc, d) => acc + parseInt(d, 10), 0); root.cpuTemp = sum / temps.length / 1000; } @@ -153,10 +155,9 @@ Singleton { running: true command: ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] - stdout: SplitParser { - splitMarker: "" - onRead: data => { - const percs = data.trim().split("\n"); + stdout: StdioCollector { + onStreamFinished: { + const percs = text.trim().split("\n"); const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0); root.gpuPerc = sum / percs.length / 100; } @@ -167,13 +168,14 @@ Singleton { id: gpuTemp running: true - command: ["sh", "-c", "sensors | jq -nRc '[inputs]'"] - stdout: SplitParser { - onRead: data => { + command: ["sensors"] + stdout: StdioCollector { + onStreamFinished: { let eligible = false; let sum = 0; let count = 0; - for (const line of JSON.parse(data)) { + + for (const line of text.trim().split("\n")) { if (line === "Adapter: PCI adapter") eligible = true; else if (line === "") @@ -186,6 +188,7 @@ Singleton { } } } + root.gpuTemp = count > 0 ? sum / count : 0; } } diff --git a/services/Wallpapers.qml b/services/Wallpapers.qml index 3f6bf15..a667017 100644 --- a/services/Wallpapers.qml +++ b/services/Wallpapers.qml @@ -9,8 +9,9 @@ import QtQuick Singleton { id: root - readonly property string currentNamePath: `${Paths.state}/wallpaper/last.txt`.slice(7) + readonly property string currentNamePath: `${Paths.state}/wallpaper/path.txt`.slice(7) readonly property string path: `${Paths.pictures}/Wallpapers`.slice(7) + readonly property list<string> extensions: ["jpg", "jpeg", "png", "webp", "tif", "tiff"] readonly property list<Wallpaper> list: wallpapers.instances property bool showPreview: false @@ -77,11 +78,10 @@ Singleton { Process { id: getPreviewColoursProc - command: ["caelestia", "scheme", "print", root.previewPath] - stdout: SplitParser { - splitMarker: "" - onRead: data => { - Colours.load(data, true); + command: ["caelestia", "wallpaper", "-p", root.previewPath] + stdout: StdioCollector { + onStreamFinished: { + Colours.load(text, true); Colours.showPreview = true; } } @@ -97,10 +97,9 @@ Singleton { Process { running: true - command: ["fd", ".", root.path, "-t", "f", "-e", "jpg", "-e", "jpeg", "-e", "png", "-e", "svg"] - stdout: SplitParser { - splitMarker: "" - onRead: data => wallpapers.model = data.trim().split("\n") + command: ["find", root.path, "-type", "d", "-path", '*/.*', "-prune", "-o", "-not", "-name", '.*', "-type", "f", "-print"] + stdout: StdioCollector { + onStreamFinished: wallpapers.model = text.trim().split("\n").filter(w => root.extensions.includes(w.slice(w.lastIndexOf(".") + 1))).sort() } } diff --git a/services/Weather.qml b/services/Weather.qml index 13503f9..4f53d0b 100644 --- a/services/Weather.qml +++ b/services/Weather.qml @@ -7,6 +7,7 @@ import Quickshell.Io Singleton { id: root + property string loc property string icon property string description property real temperature @@ -15,17 +16,28 @@ Singleton { wttrProc.running = true; } + onLocChanged: wttrProc.running = true + Process { - id: wttrProc + id: ipProc running: true - command: ["fish", "-c", `curl "https://wttr.in/$(curl ipinfo.io | jq -r '.city' | string replace -a ' ' '%20')?format=j1" | jq -c '.current_condition[0] | {code: .weatherCode, desc: .weatherDesc[0].value, temp: .temp_C}'`] - stdout: SplitParser { - onRead: data => { - const json = JSON.parse(data); - root.icon = Icons.getWeatherIcon(json.code); - root.description = json.desc; - root.temperature = parseFloat(json.temp); + command: ["curl", "ipinfo.io"] + stdout: StdioCollector { + onStreamFinished: root.loc = JSON.parse(text).loc + } + } + + Process { + id: wttrProc + + command: ["curl", `https://wttr.in/${root.loc}?format=j1`] + stdout: StdioCollector { + onStreamFinished: { + const json = JSON.parse(text).current_condition[0]; + root.icon = Icons.getWeatherIcon(json.weatherCode); + root.description = json.weatherDesc[0].value; + root.temperature = parseFloat(json.temp_C); } } } |