init: working version

This commit is contained in:
Kiana Sheibani 2025-10-07 19:43:46 -04:00
commit 7d8d7dacae
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
109 changed files with 15066 additions and 0 deletions

85
services/Audio.qml Normal file
View file

@ -0,0 +1,85 @@
pragma Singleton
import qs.config
import qs.custom
import Quickshell
import Quickshell.Services.Pipewire
Singleton {
id: root
readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property PwNode source: Pipewire.defaultAudioSource
PwObjectTracker {
objects: [root.sink, root.source]
}
readonly property bool muted: !!sink?.audio?.muted
readonly property real volume: sink?.audio?.volume ?? 0
readonly property bool sourceMuted: !!source?.audio?.muted
readonly property real sourceVolume: source?.audio?.volume ?? 0
function setVolume(newVolume: real, s: PwNode): void {
if (!s) s = sink;
if (s?.ready && s?.audio) {
s.audio.volume = Math.max(0, Math.min(1, newVolume));
}
}
function increaseVolume(amount: real, s: PwNode): void {
setVolume(volume + (amount || Config.osd.volumeIncrement), s);
}
function decreaseVolume(amount: real, s: PwNode): void {
setVolume(volume - (amount || Config.osd.volumeIncrement), s);
}
CustomShortcut {
name: "volumeUp"
description: "Increase volume"
onPressed: root.increaseVolume()
}
CustomShortcut {
name: "volumeDown"
description: "Decrease volume"
onPressed: root.decreaseVolume()
}
CustomShortcut {
name: "mute"
description: "Toggle muting of output (Pipewire default audio sink)"
onPressed: root.sink.audio.muted = !root.muted
}
CustomShortcut {
name: "muteMic"
description: "Toggle muting of input (Pipewire default audio source)"
onPressed: root.source.audio.muted = !root.sourceMuted
}
function setSourceVolume(newVolume: real, s: PwNode): void {
if (!s) s = source;
if (s?.ready && s?.audio) {
s.audio.volume = Math.max(0, Math.min(1, newVolume));
}
}
function incrementSourceVolume(amount: real, s: PwNode): void {
setSourceVolume(sourceVolume + (amount || Config.osd.micIncrement), s);
}
function decrementSourceVolume(amount: real, s: PwNode): void {
setSourceVolume(sourceVolume - (amount || Config.osd.micIncrement), s);
}
function setAudioSink(newSink: PwNode): void {
Pipewire.preferredDefaultAudioSink = newSink;
}
function setAudioSource(newSource: PwNode): void {
Pipewire.preferredDefaultAudioSource = newSource;
}
}

104
services/Bluetooth.qml Normal file
View file

@ -0,0 +1,104 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
property bool powered
property bool discovering
readonly property list<Device> devices: []
Process {
running: true
command: ["bluetoothctl"]
stdout: SplitParser {
onRead: {
getInfo.running = true;
getDevices.running = true;
}
}
}
Process {
id: getInfo
running: true
command: ["bluetoothctl", "show"]
environment: ({
LANG: "C",
LC_ALL: "C"
})
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)
if string match -q 'Device *' $a
bluetoothctl info $addr (string split ' ' $a)[2]
echo
end
end`]
environment: ({
LANG: "C",
LC_ALL: "C"
})
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));
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);
if (match) {
match.lastIpcObject = device;
} else {
rDevices.push(deviceComp.createObject(root, {
lastIpcObject: device
}));
}
}
}
}
}
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
readonly property bool paired: lastIpcObject.paired
readonly property bool trusted: lastIpcObject.trusted
}
Component {
id: deviceComp
Device {}
}
}

153
services/Brightness.qml Normal file
View file

@ -0,0 +1,153 @@
pragma Singleton
pragma ComponentBehavior: Bound
import qs.custom
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
reloadableId: "brightness"
property list<var> ddcMonitors: []
readonly property list<Monitor> monitors: variants.instances
property bool appleDisplayPresent: false
function getMonitorForScreen(screen: ShellScreen): var {
return monitors.find(m => m.modelData === screen);
}
function increaseBrightness(): void {
const focusedName = Hypr.focusedMonitor.name;
const monitor = monitors.find(m => focusedName === m.modelData.name);
if (monitor)
monitor.setBrightness(monitor.brightness + Config.osd.brightnessIncrement);
}
function decreaseBrightness(): void {
const focusedName = Hypr.focusedMonitor.name;
const monitor = monitors.find(m => focusedName === m.modelData.name);
if (monitor)
monitor.setBrightness(monitor.brightness - Config.osd.brightnessIncrement);
}
onMonitorsChanged: {
ddcMonitors = [];
ddcProc.running = true;
}
Variants {
id: variants
model: Quickshell.screens
Monitor {}
}
Process {
running: true
command: ["sh", "-c", "asdbctl get"] // To avoid warnings if asdbctl is not installed
stdout: StdioCollector {
onStreamFinished: root.appleDisplayPresent = text.trim().length > 0
}
}
Process {
id: ddcProc
command: ["ddcutil", "detect", "--brief"]
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]
}))
}
}
CustomShortcut {
name: "brightnessUp"
description: "Increase brightness"
onPressed: root.increaseBrightness()
}
CustomShortcut {
name: "brightnessDown"
description: "Decrease brightness"
onPressed: root.decreaseBrightness()
}
component Monitor: QtObject {
id: monitor
required property ShellScreen modelData
readonly property bool isDdc: root.ddcMonitors.some(m => m.model === modelData.model)
readonly property string busNum: root.ddcMonitors.find(m => m.model === modelData.model)?.busNum ?? ""
readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay")
property real brightness
property real queuedBrightness: NaN
readonly property Process initProc: Process {
stdout: StdioCollector {
onStreamFinished: {
if (monitor.isAppleDisplay) {
const val = parseInt(text.trim());
monitor.brightness = val / 101;
} else {
const [, , , cur, max] = text.split(" ");
monitor.brightness = parseInt(cur) / parseInt(max);
}
}
}
}
readonly property Timer timer: Timer {
interval: 500
onTriggered: {
if (!isNaN(monitor.queuedBrightness)) {
monitor.setBrightness(monitor.queuedBrightness);
monitor.queuedBrightness = NaN;
}
}
}
function setBrightness(value: real): void {
value = Math.max(0, Math.min(1, value));
const rounded = Math.round(value * 100);
if (Math.round(brightness * 100) === rounded)
return;
if (isDdc && timer.running) {
queuedBrightness = value;
return;
}
brightness = value;
if (isAppleDisplay)
Quickshell.execDetached(["asdbctl", "set", rounded]);
else if (isDdc)
Quickshell.execDetached(["ddcutil", "-b", busNum, "setvcp", "10", rounded]);
else
Quickshell.execDetached(["brightnessctl", "s", `${rounded}%`]);
if (isDdc)
timer.restart();
}
function initBrightness(): void {
if (isAppleDisplay)
initProc.command = ["asdbctl", "get"];
else if (isDdc)
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"];
else
initProc.command = ["sh", "-c", "echo a b c $(brightnessctl g) $(brightnessctl m)"];
initProc.running = true;
}
onBusNumChanged: initBrightness()
Component.onCompleted: initBrightness()
}
}

86
services/Hypr.qml Normal file
View file

@ -0,0 +1,86 @@
pragma Singleton
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import QtQuick
Singleton {
id: root
readonly property var toplevels: Hyprland.toplevels
readonly property var workspaces: Hyprland.workspaces
readonly property var monitors: Hyprland.monitors
property HyprlandToplevel activeToplevel: null
readonly property HyprlandWorkspace focusedWorkspace: Hyprland.focusedWorkspace
readonly property HyprlandMonitor focusedMonitor: Hyprland.focusedMonitor
property string kbLayout: "?"
readonly property int arbitraryRaceConditionDelay: 50
function dispatch(request: string): void {
Hyprland.dispatch(request);
}
function monitorFor(screen: ShellScreen): HyprlandMonitor {
return Hyprland.monitorFor(screen);
}
Connections {
target: Hyprland
function onRawEvent(event: HyprlandEvent): void {
const n = event.name;
if (n.endsWith("v2"))
return;
if (n === "activelayout") {
root.kbLayout = event.parse(2)[1].slice(0, 2).toLowerCase();
} else if (["workspace", "moveworkspace", "activespecial", "focusedmon"].includes(n)) {
Hyprland.refreshWorkspaces();
Hyprland.refreshMonitors();
} else if (["openwindow", "closewindow", "movewindow"].includes(n)) {
Hyprland.refreshToplevels();
Hyprland.refreshWorkspaces();
} else if (n.includes("mon")) {
Hyprland.refreshMonitors();
} else if (n.includes("workspace")) {
Hyprland.refreshWorkspaces();
} else if (n.includes("window") || n.includes("group") || ["pin", "fullscreen", "changefloatingmode", "minimize"].includes(n)) {
Hyprland.refreshToplevels();
}
}
function onActiveToplevelChanged() {
toplevelTimer.start();
}
}
onFocusedWorkspaceChanged: toplevelTimer.start()
// Delay update to account for Hyprland's processing delay
// (Prevent false null reports)
Timer {
id: toplevelTimer
interval: root.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
const toplevel = Hyprland.activeToplevel;
// Invalidate active toplevel if in different workspace
if (toplevel && toplevel?.workspace === focusedWorkspace) {
root.activeToplevel = toplevel;
} else {
root.activeToplevel = null;
}
}
}
Process {
running: true
command: ["hyprctl", "-j", "devices"]
stdout: StdioCollector {
onStreamFinished: root.kbLayout = JSON.parse(text).keyboards.find(k => k.main).active_keymap.slice(0, 2).toLowerCase()
}
}
}

103
services/Hyprsunset.qml Normal file
View file

@ -0,0 +1,103 @@
pragma Singleton
import qs.config
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property var manualActive
property string from: Config.services.sunsetFrom
property string to: Config.services.sunsetTo
property bool automatic: from !== to
property bool shouldBeOn
property bool firstEvaluation: true
property bool active: false
property int fromHour: Number(from.split(":")[0])
property int fromMinute: Number(from.split(":")[1])
property int toHour: Number(to.split(":")[0])
property int toMinute: Number(to.split(":")[1])
property int clockHour: Time.hours
property int clockMinute: Time.minutes
onClockMinuteChanged: reEvaluate()
onAutomaticChanged: {
root.manualActive = undefined;
root.firstEvaluation = true;
reEvaluate();
}
function reEvaluate() {
const t = clockHour * 60 + clockMinute;
const from = fromHour * 60 + fromMinute;
const to = toHour * 60 + toMinute;
if (from < to) {
root.shouldBeOn = t >= from && t <= to;
} else {
// Wrapped around midnight
root.shouldBeOn = t >= from || t <= to;
}
if (firstEvaluation) {
firstEvaluation = false;
root.ensureState();
}
}
onShouldBeOnChanged: ensureState()
function ensureState() {
if (!root.automatic || root.manualActive !== undefined)
return;
if (root.shouldBeOn) {
root.enable();
} else {
root.disable();
}
}
function load() { } // Dummy to force init
function enable() {
root.active = true;
Quickshell.execDetached(["hyprsunset", "--temperature", Config.services.sunsetTemperature]);
}
function disable() {
root.active = false;
Quickshell.execDetached(["pkill", "hyprsunset"]);
}
function fetchState() {
fetchProc.running = true;
}
Process {
id: fetchProc
running: true
command: ["hyprctl", "hyprsunset", "temperature"]
stdout: StdioCollector {
id: stateCollector
onStreamFinished: {
const output = stateCollector.text.trim();
if (output.length == 0 || output.startsWith("Couldn't"))
root.active = false;
else
root.active = (output != "6500");
}
}
}
function toggle() {
if (root.manualActive === undefined)
root.manualActive = root.active;
root.manualActive = !root.manualActive;
if (root.manualActive) {
root.enable();
} else {
root.disable();
}
}
}

47
services/Idle.qml Normal file
View file

@ -0,0 +1,47 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property alias inhibit: inhibitor.running
property bool inhibitPipewire
function toggleInhibitPipewire() {
root.inhibitPipewire = !root.inhibitPipewire;
pipewireInhibitor.running = true;
}
// Idle Inhibitor
Process {
id: inhibitor
command: ["wayland-idle-inhibitor"]
}
// Idle Inhibit on Pipewire
readonly property string pipewireInhibitorService: "wayland-pipewire-idle-inhibit.service"
Timer {
id: pipewireInhibitorTimer
running: true
repeat: true
triggeredOnStart: true
onTriggered: pipewireInhibitorCheck.running = true
}
Process {
id: pipewireInhibitorCheck
command: ["systemctl", "status", "--user", root.pipewireInhibitorService]
onExited: (code, _) => root.inhibitPipewire = (code === 0)
}
Process {
id: pipewireInhibitor
command: ["systemctl", root.inhibitPipewire ? "start" : "stop", "--user", root.pipewireInhibitorService]
}
}

192
services/Network.qml Normal file
View file

@ -0,0 +1,192 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
readonly property list<AccessPoint> networks: []
readonly property AccessPoint active: networks.find(n => n.active) ?? null
property bool wifiEnabled: true
readonly property bool scanning: rescanProc.running
reloadableId: "network"
function enableWifi(enabled: bool): void {
const cmd = enabled ? "on" : "off";
enableWifiProc.exec(["nmcli", "radio", "wifi", cmd]);
}
function toggleWifi(): void {
const cmd = wifiEnabled ? "off" : "on";
enableWifiProc.exec(["nmcli", "radio", "wifi", cmd]);
}
function rescanWifi(): void {
rescanProc.running = true;
}
function connectToNetwork(ssid: string, password: string): void {
// TODO: Implement password
connectProc.exec(["nmcli", "conn", "up", ssid]);
}
function disconnectFromNetwork(): void {
if (active) {
disconnectProc.exec(["nmcli", "connection", "down", active.ssid]);
}
}
function getWifiStatus(): void {
wifiStatusProc.running = true;
}
Process {
running: true
command: ["nmcli", "m"]
stdout: SplitParser {
onRead: getNetworks.running = true
}
}
Process {
id: wifiStatusProc
running: true
command: ["nmcli", "radio", "wifi"]
environment: ({
LANG: "C",
LC_ALL: "C"
})
stdout: StdioCollector {
onStreamFinished: {
root.wifiEnabled = text.trim() === "enabled";
}
}
}
Process {
id: enableWifiProc
onExited: {
root.getWifiStatus();
getNetworks.running = true;
}
}
Process {
id: rescanProc
command: ["nmcli", "dev", "wifi", "list", "--rescan", "yes"]
onExited: {
getNetworks.running = true;
}
}
Process {
id: connectProc
stdout: SplitParser {
onRead: getNetworks.running = true
}
stderr: StdioCollector {
onStreamFinished: console.warn("Network connection error:", text)
}
}
Process {
id: disconnectProc
stdout: SplitParser {
onRead: getNetworks.running = true
}
}
Process {
id: getNetworks
running: true
command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID,SECURITY", "d", "w"]
environment: ({
LANG: "C",
LC_ALL: "C"
})
stdout: StdioCollector {
onStreamFinished: {
const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED";
const rep = new RegExp("\\\\:", "g");
const rep2 = new RegExp(PLACEHOLDER, "g");
const allNetworks = 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, ":") ?? "",
security: net[5] || ""
};
}).filter(n => n.ssid && n.ssid.length > 0);
// Group networks by SSID and prioritize connected ones
const networkMap = new Map();
for (const network of allNetworks) {
const existing = networkMap.get(network.ssid);
if (!existing) {
networkMap.set(network.ssid, network);
} else {
// Prioritize active/connected networks
if (network.active && !existing.active) {
networkMap.set(network.ssid, network);
} else if (!network.active && !existing.active) {
// If both are inactive, keep the one with better signal
if (network.strength > existing.strength) {
networkMap.set(network.ssid, network);
}
}
// If existing is active and new is not, keep existing
}
}
const networks = Array.from(networkMap.values());
const rNetworks = root.networks;
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.frequency && n.ssid === network.ssid && n.bssid === network.bssid);
if (match) {
match.lastIpcObject = network;
} else {
rNetworks.push(apComp.createObject(root, {
lastIpcObject: network
}));
}
}
}
}
}
component AccessPoint: QtObject {
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
readonly property string security: lastIpcObject.security
readonly property bool isSecure: security.length > 0
}
Component {
id: apComp
AccessPoint {}
}
}

60
services/NixOS.qml Normal file
View file

@ -0,0 +1,60 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
readonly property string nixVersion: nixVersionProc.nixVersion
readonly property list<Generation> generations: nixosGenerationsProc.generations
readonly property Generation currentGen: generations.find(g => g.current) ?? null
Timer {
running: true
repeat: true
triggeredOnStart: true
interval: 60000
onTriggered: {
nixVersionProc.running = true;
nixosGenerationsProc.running = true;
}
}
Process {
id: nixVersionProc
command: ["nix", "--version"]
property string nixVersion: ""
stdout: StdioCollector {
onStreamFinished: nixVersionProc.nixVersion = this.text.split(" ")[2].trim()
}
}
Process {
id: nixosGenerationsProc
command: ["nixos-rebuild", "list-generations", "--json"]
property list<Generation> generations: []
stdout: StdioCollector {
onStreamFinished: {
const json = JSON.parse(this.text);
nixosGenerationsProc.generations = json.map(o => genComp.createObject(root, { ipcObject: o }));
}
}
}
component Generation: QtObject {
required property var ipcObject
readonly property int id: ipcObject.generation
readonly property date date: ipcObject.date
readonly property string nixosVersion: ipcObject.nixosVersion
readonly property string kernelVersion: ipcObject.kernelVersion
readonly property string revision: ipcObject.configurationRevision
readonly property bool current: ipcObject.current
}
Component {
id: genComp
Generation {}
}
}

132
services/Notifs.qml Normal file
View file

@ -0,0 +1,132 @@
pragma Singleton
pragma ComponentBehavior: Bound
import qs.config
import qs.custom
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Quickshell.Services.Notifications
import QtQuick
Singleton {
id: root
readonly property list<Notif> list: []
property bool dnd: false
function toggleDnd(): void {
dnd = !dnd
}
NotificationServer {
id: server
keepOnReload: false
actionsSupported: true
bodyHyperlinksSupported: true
bodyImagesSupported: true
bodyMarkupSupported: true
imageSupported: true
persistenceSupported: true
onNotification: notif => {
notif.tracked = true;
root.list.push(notifComp.createObject(root, {
popup: root.dnd ? null : Hypr.focusedMonitor,
notification: notif
}));
}
}
CustomShortcut {
name: "clearNotifs"
description: "Clear all notifications"
onPressed: {
for (const notif of root.list)
notif.popup = null;
}
}
CustomShortcut {
name: "dismissNotifs"
description: "Dismiss all notifications"
onPressed: {
for (const notif of root.list)
notif.popup = null;
}
}
IpcHandler {
target: "notifs"
function clear(): void {
for (const notif of root.list)
notif.popup = null;
}
}
component Notif: QtObject {
id: notif
// Monitor to display popup in (typically the focused monitor when received)
property HyprlandMonitor popup: null
readonly property date time: new Date()
readonly property string timeStr: Qt.formatTime(time, "hh:mm:ss")
readonly property string timeSince: {
const diff = Time.date.getTime() - time.getTime();
const m = Math.floor(diff / 60000);
const h = Math.floor(m / 60);
const d = Math.floor(h / 24);
if (m < 1)
return "now";
if (h < 1)
return `${m}m`;
if (d < 1)
return `${h}h`;
return `${d}d`;
}
required property Notification notification
readonly property string summary: notification.summary
readonly property string appIcon: notification.appIcon
readonly property string appName: notification.appName
readonly property string image: notification.image
readonly property int urgency: notification.urgency
readonly property list<NotificationAction> actions: notification.actions
// Split body text into lines parseable by StyledText format
readonly property string body: notification.body.replace("\n", "<br/>")
// One-line version (for non-expanded notifications)
readonly property string bodyOneLine: notification.body.replace("\n", " ")
readonly property Timer timer: Timer {
running: true
interval: notif.notification.expireTimeout > 0 ? notif.notification.expireTimeout : Config.notifs.defaultExpireTimeout
onTriggered: {
if (Config.notifs.expire)
notif.popup = null;
}
}
readonly property Connections conn: Connections {
target: notif.notification.Retainable
function onDropped(): void {
root.list.splice(root.list.indexOf(notif), 1);
}
function onAboutToDestroy(): void {
notif.destroy();
}
}
}
Component {
id: notifComp
Notif {}
}
}

123
services/Players.qml Normal file
View file

@ -0,0 +1,123 @@
pragma Singleton
import qs.config
import qs.custom
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
Singleton {
id: root
readonly property list<MprisPlayer> list: Mpris.players.values
readonly property MprisPlayer active: manualActive ?? list.find(p => getIdentity(p) === Config.services.defaultPlayer) ?? list[0] ?? null
property MprisPlayer manualActive
// Alias IDs (for looking up icons)
readonly property list<var> idAliases: [
{
"from": "Mozilla firefox",
"to": "Firefox"
}
]
// Alias names (for displaying)
readonly property list<var> nameAliases: [
{
"from": "com.github.th_ch.youtube_music",
"to": "YT Music"
}
]
function getIdentity(player: MprisPlayer): string {
const alias = idAliases.find(a => a.from === player.identity);
return alias?.to ?? player.identity;
}
function getName(player: MprisPlayer): string {
const alias = nameAliases.find(a => a.from === player.identity);
return alias?.to ?? getIdentity(player);
}
CustomShortcut {
name: "mediaToggle"
description: "Toggle media playback"
onPressed: {
const active = root.active;
if (active && active.canTogglePlaying)
active.togglePlaying();
}
}
CustomShortcut {
name: "mediaPrev"
description: "Previous track"
onPressed: {
const active = root.active;
if (active && active.canGoPrevious)
active.previous();
}
}
CustomShortcut {
name: "mediaNext"
description: "Next track"
onPressed: {
const active = root.active;
if (active && active.canGoNext)
active.next();
}
}
CustomShortcut {
name: "mediaStop"
description: "Stop media playback"
onPressed: root.active?.stop()
}
IpcHandler {
target: "mpris"
function getActive(prop: string): string {
const active = root.active;
return active ? active[prop] ?? "Invalid property" : "No active player";
}
function list(): string {
return root.list.map(p => root.get(p)).join("\n");
}
function play(): void {
const active = root.active;
if (active?.canPlay)
active.play();
}
function pause(): void {
const active = root.active;
if (active?.canPause)
active.pause();
}
function playPause(): void {
const active = root.active;
if (active?.canTogglePlaying)
active.togglePlaying();
}
function previous(): void {
const active = root.active;
if (active?.canGoPrevious)
active.previous();
}
function next(): void {
const active = root.active;
if (active?.canGoNext)
active.next();
}
function stop(): void {
root.active?.stop();
}
}
}

36
services/Requests.qml Normal file
View file

@ -0,0 +1,36 @@
pragma Singleton
import qs.config
import qs.util
import Quickshell
Singleton {
id: root
function get(url: string, callback: var): void {
const xhr = new XMLHttpRequest();
const cleanup = () => {
xhr.abort();
xhr.onreadystatechange = null;
xhr.onerror = null;
};
xhr.open("GET", url, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200)
callback(xhr.responseText);
else
console.warn(`[REQUESTS] GET request to ${url} failed with status ${xhr.status}`);
cleanup();
}
};
xhr.onerror = () => {
console.warn(`[REQUESTS] GET request to ${url} failed`);
cleanup();
};
xhr.send();
}
}

222
services/SystemUsage.qml Normal file
View file

@ -0,0 +1,222 @@
pragma Singleton
import qs.config
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
property real cpuPerc
property real cpuTemp
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property string autoGpuType: "NONE"
property real gpuPerc
property real gpuTemp
property real memUsed
property real memTotal
readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
property real storageUsed
property real storageTotal
property real storagePerc: storageTotal > 0 ? storageUsed / storageTotal : 0
property real lastCpuIdle
property real lastCpuTotal
property int refCount
function formatKib(kib: real): var {
const mib = 1024;
const gib = 1024 ** 2;
const tib = 1024 ** 3;
if (kib >= tib)
return {
value: kib / tib,
unit: "TiB"
};
if (kib >= gib)
return {
value: kib / gib,
unit: "GiB"
};
if (kib >= mib)
return {
value: kib / mib,
unit: "MiB"
};
return {
value: kib,
unit: "KiB"
};
}
Timer {
running: root.refCount > 0
interval: 3000
repeat: true
triggeredOnStart: true
onTriggered: {
stat.reload();
meminfo.reload();
storage.running = true;
gpuUsage.running = true;
sensors.running = true;
}
}
FileView {
id: stat
path: "/proc/stat"
onLoaded: {
const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
if (data) {
const stats = data.slice(1).map(n => parseInt(n, 10));
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3] + (stats[4] ?? 0);
const totalDiff = total - root.lastCpuTotal;
const idleDiff = idle - root.lastCpuIdle;
root.cpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
root.lastCpuTotal = total;
root.lastCpuIdle = idle;
}
}
}
FileView {
id: meminfo
path: "/proc/meminfo"
onLoaded: {
const data = text();
root.memTotal = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
root.memUsed = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
}
}
Process {
id: storage
command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $1, $3, $4}'"]
stdout: StdioCollector {
onStreamFinished: {
const deviceMap = new Map();
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
});
}
}
}
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;
}
}
}
Process {
id: gpuTypeCheck
running: !Config.services.gpuType
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
Process {
id: gpuUsage
command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : root.gpuType === "NVIDIA" ? ["nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu", "--format=csv,noheader,nounits"] : ["echo"]
stdout: StdioCollector {
onStreamFinished: {
if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
root.gpuPerc = sum / percs.length / 100;
} else if (root.gpuType === "NVIDIA") {
const [usage, temp] = text.trim().split(",");
root.gpuPerc = parseInt(usage, 10) / 100;
root.gpuTemp = parseInt(temp, 10);
} else {
root.gpuPerc = 0;
root.gpuTemp = 0;
}
}
}
}
Process {
id: sensors
command: ["sensors"]
environment: ({
LANG: "C.UTF-8",
LC_ALL: "C.UTF-8"
})
stdout: StdioCollector {
onStreamFinished: {
let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
if (!cpuTemp)
// If AMD Tdie pattern failed, try fallback on Tctl
cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
if (cpuTemp)
root.cpuTemp = parseFloat(cpuTemp[1]);
if (root.gpuType !== "GENERIC")
return;
let eligible = false;
let sum = 0;
let count = 0;
for (const line of text.trim().split("\n")) {
if (line === "Adapter: PCI adapter")
eligible = true;
else if (line === "")
eligible = false;
else if (eligible) {
let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (!match)
// Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
if (match) {
sum += parseFloat(match[2]);
count++;
}
}
}
root.gpuTemp = count > 0 ? sum / count : 0;
}
}
}
}

52
services/Time.qml Normal file
View file

@ -0,0 +1,52 @@
pragma Singleton
import Quickshell
Singleton {
property alias enabled: clock.enabled
readonly property date date: clock.date
readonly property int hours: clock.hours
readonly property int minutes: clock.minutes
readonly property int seconds: clock.seconds
SystemClock {
id: clock
precision: SystemClock.Seconds
}
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
}
function formatSeconds(s: int, includeSecs = false): string {
let min = Math.floor(s / 60);
let hr = Math.floor(min / 60);
let day = Math.floor(hr / 24);
let week = Math.floor(day / 7);
let year = Math.floor(day / 365);
s = s % 60;
min = min % 60;
hr = hr % 24;
day = day % 7;
week = week % 52;
let comps = [];
if (year > 0)
comps.push(`${year}y`);
if (week > 0)
comps.push(`${week}w`);
if (day > 0)
comps.push(`${day}d`);
if (hr > 0)
comps.push(`${hr}h`);
if (min > 0)
comps.push(`${min}m`);
if (includeSecs && s > 0)
comps.push(`${s}s`);
if (comps.length === 0)
return "";
else
return comps.join(" ");
}
}

36
services/User.qml Normal file
View file

@ -0,0 +1,36 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
readonly property string user: userProcess.user
readonly property string uptime: fileUptime.uptime
Timer {
running: true
repeat: true
interval: 15000
triggeredOnStart: true
onTriggered: fileUptime.reload()
}
FileView {
id: fileUptime
property string uptime: ""
path: "/proc/uptime"
onLoaded: uptime = Time.formatSeconds(parseInt(text().split(" ")[0] ?? 0));
}
Process {
id: userProcess
property string user: ""
running: true
command: ["whoami"]
stdout: StdioCollector {
onStreamFinished: userProcess.user = this.text.trim()
}
}
}

42
services/Weather.qml Normal file
View file

@ -0,0 +1,42 @@
pragma Singleton
import qs.config
import qs.util
import Quickshell
import QtQuick
Singleton {
id: root
property string city
property var cc
property var forecast
readonly property bool available: !!cc
readonly property string icon: available ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert"
readonly property color iconColor: available ? Icons.weatherIconColors[icon] : Config.colors.inactive
readonly property string description: cc?.weatherDesc[0].value ?? qsTr("No weather")
readonly property string temp: Config.services.useFahrenheit ? `${cc?.temp_F ?? 0}°F` : `${cc?.temp_C ?? 0}°C`
readonly property string feelsLike: Config.services.useFahrenheit ? `${cc?.FeelsLikeF ?? 0}°F` : `${cc?.FeelsLikeC ?? 0}°C`
readonly property real humidity: (cc?.humidity ?? 0) / 100
readonly property string humidityIcon: available ? Icons.getHumidityIcon(humidity) : "humidity_low"
function reload(): void {
if (Config.services.weatherLocation)
city = Config.services.weatherLocation;
else if (!city || timer.elapsed() > 900)
Requests.get("https://ipinfo.io/json", text => {
city = JSON.parse(text).city ?? "";
timer.restart();
});
}
onCityChanged: Requests.get(`https://wttr.in/${city}?format=j1`, text => {
const json = JSON.parse(text);
cc = json.current_condition[0];
forecast = json.weather;
})
ElapsedTimer {
id: timer
}
}