internal: use stdiocollector

Removes dependency on jq
Also fix beatdetector
This commit is contained in:
2 * r + 2 * t 2025-06-15 15:43:50 +10:00
parent 4c475548af
commit 05e9561fd6
10 changed files with 150 additions and 123 deletions

View file

@ -69,8 +69,8 @@ Row {
running: true
command: ["uptime", "-p"]
stdout: SplitParser {
onRead: data => uptimeProc.uptime = data
stdout: StdioCollector {
onStreamFinished: uptimeProc.uptime = text
}
}
}

View file

@ -10,7 +10,7 @@ Singleton {
Process {
running: true
command: [`${Quickshell.shellRoot}/assets/realtime-beat-detector.py`]
command: [`${Quickshell.configDir}/assets/realtime-beat-detector.py`]
stdout: SplitParser {
onRead: data => root.bpm = parseFloat(data)
}

View file

@ -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 {

View file

@ -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);
}
}

View file

@ -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)

View file

@ -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 {

View file

@ -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
});
}
}
}
root.storageUsed = totalUsed;
root.storageTotal = totalUsed + totalAvail;
let totalUsed = 0;
let totalAvail = 0;
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;
}
}

View file

@ -78,10 +78,9 @@ Singleton {
id: getPreviewColoursProc
command: ["caelestia", "wallpaper", "-p", root.previewPath]
stdout: SplitParser {
splitMarker: ""
onRead: data => {
Colours.load(data, true);
stdout: StdioCollector {
onStreamFinished: {
Colours.load(text, true);
Colours.showPreview = true;
}
}
@ -98,9 +97,8 @@ Singleton {
Process {
running: true
command: ["fd", ".", root.path, "-t", "f", "-e", "jpg", "-e", "jpeg", "-e", "png", "-e", "webp", "-e", "tif", "-e", "tiff"]
stdout: SplitParser {
splitMarker: ""
onRead: data => wallpapers.model = data.trim().split("\n")
stdout: StdioCollector {
onStreamFinished: wallpapers.model = text.trim().split("\n")
}
}

View file

@ -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: ipProc
running: true
command: ["curl", "ipinfo.io"]
stdout: StdioCollector {
onStreamFinished: root.loc = JSON.parse(text).loc
}
}
Process {
id: wttrProc
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", `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);
}
}
}

View file

@ -43,8 +43,8 @@ Image {
property string path
command: ["sha256sum", path]
stdout: SplitParser {
onRead: data => root.hash = data.split(" ")[0]
stdout: StdioCollector {
onStreamFinished: root.hash = text.split(" ")[0]
}
}
}