EO6NBSIWQZ4MWHKHWLYLLRW3PIUWV33LSW5573EEAYSWONGELY3QC UBB7TTAXVPQQCOVHACKWXSPV2NPARSHREYJB6J3RSEDZZIXPFGOAC TWHOWFCDEEUW4XSDTNGHDMZR3IZ2QIPRT262AJ3IGPSAIU27VZDQC RTBMBSBABSGTRICJ4AWBKWO3JJHBRKV6FGOMYPDD7X6SS6X35ZIQC LNEFSFVU4YSTF7V36E24OWJN7ZLB4H2ZQS7K3NYIZ2D6H32CWCTAC 5G7WRBMWKG6DMCOHE6WQHTYZACUHO2UPBZRWN72CFH7P45NN5E7QC B25Y6ZESZM3UB3G7F2QCANOQN6FH5O4A5G7SFUJ5LLWWR7DZNHDQC CBGXBY2CSVY6SK5KLBIHTWKDVUKXZIB5EXJKULHP75QGOMH6G2WAC L6SN5PMKPZ2VPVJAH2G64EU3T6L5XOWVDH7DP7RU4EC2TU377RPAC LLRYXY25L67F5SIWZCZ3MXBC67T6LMQ3JBBJJR2ESKALWHXT4YGAC VTPA57TTA3C4RUCTOAO6HYOKGBKGBDSWMJMWA7CWSCFAAV7GMEJQC AA4SKOCEGK4EIT5WQ3JHENYTVXUQCPAY47P6JE4MRHFWEM5UNC3AC BDDKGGL7QNTFAX3ARMV4VK2XQCLOVJNFLXFCZB7IFCGEATCMVBIAC {config.flake.modules.nixos.quickshell ={ pkgs, inputs, ... }:{services.upower.enable = true;environment.systemPackages = [pkgs.kdePackages.qt5compatinputs.quickshell.packages.${pkgs.stdenv.hostPlatform.system}.quickshellinputs.qml-niri.packages.${pkgs.stdenv.hostPlatform.system}.qml-niri];};}
{"network": {"activeInterface": "wlo1","type": 1}}
import QtQuickimport QtQuick.Layoutsimport Quickshellimport Quickshell.Waylandimport "./modules/bar/"import "./modules/common/"import "./services/"ShellRoot{LazyLoader{active: truecomponent: Bar{position: Types.stringToPosition(Config.data.bar.position)size: Config.data.bar.sizecolor: Config.data.theme.colors.background}}}
pragma Singletonimport QtQuickimport Quickshellimport Quickshell.Ioimport qs.modules.commonSingleton {// RAM metrics in kibibytesproperty real total: 0.0property real used: 0.0property real shared: 0.0property real buffers: 0.0property real cached: 0.0property real sreclaimable: 0.0property real free: 0.0property real available: 0.0// Top processes, array of {pid: int, comm: string, mem: float}property var topProcesses: []signal statsUpdated()Process {id: psProccommand: ["sh", "-c", `ps -eo pid,comm,%mem --sort=-%mem --no-headers | head -${Config.data.ram.numTopProcesses+10}`]stdout: StdioCollector {onStreamFinished: {let lines = this.text.split('\n').filter(l => l.trim() !== '');let tp = [];for (let line of lines) {if (tp.length == Config.data.ram.numTopProcesses) break;let parts = line.trim().split(/\s+/);if (parts.length === 3) {let comm = parts[1];if (comm === 'ps') continue; // Skip ps itselflet pid = parseInt(parts[0]);let mem = parseFloat(parts[2]);tp.push({pid: pid, comm: comm, mem: mem});}}topProcesses = tp;}}}FileView {id: meminfoFilepath: "/proc/meminfo"onLoadFailed: function(error) {console.log("RAM Service: FileView load failed for /proc/meminfo:", error);}}Timer {interval: Config.data.ram.updateIntervalrunning: truerepeat: truetriggeredOnStart: trueonTriggered: {updateRamStats()psProc.running = true}}function updateRamStats() {meminfoFile.reload();let content = meminfoFile.text();if (content === "") return;let lines = content.split('\n');let stats = {};// Parse key linesfor (let line of lines) {let parts = line.split(/\s+/);if (parts.length >= 2) {let key = parts[0].slice(0, -1); // Remove colonlet value = parseFloat(parts[1]);stats[key] = value;}}total = stats.MemTotal || 0.0;free = stats.MemFree || 0.0;buffers = stats.Buffers || 0.0;cached = stats.Cached || 0.0;shared = stats.Shmem || 0.0;available = stats.MemAvailable || (free + buffers + cached);sreclaimable = stats.SReclaimable || 0.0;used = Math.max(0, total - available);statsUpdated()}}
pragma Singletonimport QtQuickimport Niri 0.1Niri {id: niriComponent.onCompleted: connect()onConnected: console.info("Connected to niri")onErrorOccurred: function(error) {console.error("Niri error:", error)}}
pragma Singletonimport QtQuickimport Quickshellimport Quickshell.Ioimport qs.modules.commonSingleton {id: root// Available interfaces: Map of Types.Network keys to arrays of interface namesproperty var interfaces: new Map()property string activeInterface: ""property int networkType: -1// Commonproperty real rateUp: 0.0 // Bytes/sproperty real rateDown: 0.0property var lanIPs: [] // List of IP stringsproperty string wanIP: ""// Wirelessproperty string ssid: ""property int frequency: 0property int signalStrength: 0 // dBmproperty real bitrateTx: 0.0 // Mbpsproperty real bitrateRx: 0.0 // Mbps// Wiredproperty int linkSpeed: 0 // Mbps// Internal state for rate calculationsproperty var tx: 0property var rx: 0// Fixed 1000ms timer for rates (not configurable, to ensure /s calc)Timer {id: rateTimerinterval: 1000running: truerepeat: truetriggeredOnStart: trueonTriggered: updateRates()}// Configurable timersTimer {id: infoTimerinterval: Config.data.network.infoUpdateInterval * 1000running: truerepeat: truetriggeredOnStart: trueonTriggered: updateInfo()}Timer {id: externalTimerinterval: Config.data.network.externalUpdateInterval * 1000running: truerepeat: truetriggeredOnStart: trueonTriggered: updateExternal()}FileView {id: txBytesViewpath: root.activeInterface ? "/sys/class/net/" + root.activeInterface + "/statistics/tx_bytes" : ""preload: falseonLoadFailed: (error) => console.log("Network Service: Failed to load tx_bytes:", error)}FileView {id: rxBytesViewpath: root.activeInterface ? "/sys/class/net/" + root.activeInterface + "/statistics/rx_bytes" : ""preload: falseonLoadFailed: (error) => console.log("Network Service: Failed to load rx_bytes:", error)}FileView {id: linkSpeedViewpath: root.activeInterface && root.networkType === Types.Network.Wired ? "/sys/class/net/" + root.activeInterface + "/speed" : ""preload: falseonLoadFailed: (error) => console.log("Network Service: Failed to load link speed:", error)}Process {id: listInterfacesProccommand: ["find", "/sys/class/net", "-mindepth", "1", "-maxdepth", "1", "-type", "l","-exec", "sh", "-c", `n=$(basename {}); [ -d {}/wireless ] || [ -L {}/phy80211 ] && echo "$n wireless" || { [ -d {}/device ] && echo "$n wired" || echo "$n virtual"; }`, `\;`]stdout: StdioCollector {onStreamFinished: {const lines = this.text.trim().split('\n');const result = new Map([[Types.Network.Wired, []],[Types.Network.Wireless, []],[Types.Network.Virtual, []],]);for (const line of lines) {const linet = line.trim();if (linet === '') continue;const [name, itypeStr] = linet.split(/\s+/);const itype = Types.stringToNetwork(itypeStr);result.get(itype).push(name);}result.forEach(function(val, key, map) {map[key] = val.sort();});interfaces = result;}}stderr: StdioCollector {onStreamFinished: {const stderr = this.text.trim();if (stderr) {throw new Error(`Failed running listInterfacesProc. Error: ${stderr}`);}}}onExited: function(exitCode, exitStatus) {if (exitCode !== 0) {throw new Error(`Failed running listInterfacesProc. Exit code: ${exitCode}, exit status: ${exitStatus}`);}autoSelectInterface();if (networkType === Types.Network.Wireless) {wirelessInfoProc.running = true;} else if (networkType === Types.Network.Wired){linkSpeedView.reload();linkSpeed = parseInt(linkSpeedView.text().trim()) || 0;}lanIPProc.running = true;}}Process {id: lanIPProccommand: ["ip", "-details", "-json", "address", "show", root.activeInterface]stdout: StdioCollector {onStreamFinished: {try {const data = JSON.parse(this.text);if (data && data.length > 0) {lanIPs = data[0].addr_info ? data[0].addr_info.map(info => info.local).filter(ip => ip) : [];}} catch (e) {console.error("Network Service: Failed to parse ip JSON:", e);lanIPs = [];}}}stderr: StdioCollector {onStreamFinished: {const stderr = this.text.trim();if (stderr) {throw new Error(`Failed running lanIPProc. Error: ${stderr}`);}}}onExited: function(exitCode, exitStatus) {if (exitCode !== 0) {throw new Error(`Failed running lanIPProc. Exit code: ${exitCode}, exit status: ${exitStatus}`);}}}Process {id: wirelessInfoProccommand: ["iw", "dev", root.activeInterface, "link"]stdout: StdioCollector {onStreamFinished: {const lines = this.text.split('\n');let ssidFound = "", freqFound = "", rxBitFound = "", txBitFound = "", signalFound = "";for (let line of lines) {if (line.includes("SSID:")) ssidFound = line.split("SSID:")[1].trim();if (line.includes("freq:")) freqFound = line.split("freq:")[1].trim();if (line.includes("signal:")) signalFound = line.split("signal:")[1].trim().split(' ')[0];if (line.includes("rx bitrate:")) rxBitFound = line.split("rx bitrate:")[1].trim();if (line.includes("tx bitrate:")) txBitFound = line.split("tx bitrate:")[1].trim();}ssid = ssidFound;frequency = freqFound ? parseInt(freqFound) : 0;signalStrength = signalFound ? parseInt(signalFound) : 0;bitrateRx = rxBitFound ? parseFloat(rxBitFound) : 0;bitrateTx = txBitFound ? parseFloat(txBitFound) : 0;}}stderr: StdioCollector {onStreamFinished: {const stderr = this.text.trim();if (stderr) {throw new Error(`Failed running wirelessInfoProc. Error: ${stderr}`);}}}onExited: function(exitCode, exitStatus) {if (exitCode !== 0) {throw new Error(`Failed running wirelessInfoProc. Exit code: ${exitCode}, exit status: ${exitStatus}`);}}}Process {id: wanIPProc// TODO: Use additional sources to improve robustness.command: ["dig", "+short", "@resolver2.opendns.com", "myip.opendns.com"]stdout: StdioCollector {onStreamFinished: wanIP = this.text.trim() || "N/A"}stderr: StdioCollector {onStreamFinished: {const stderr = this.text.trim();if (stderr) {throw new Error(`Failed running wanIPProc. Error: ${stderr}`);}}}onExited: function(exitCode, exitStatus) {if (exitCode !== 0) {throw new Error(`Failed running wanIPProc. Exit code: ${exitCode}, exit status: ${exitStatus}`);}}}// Public APIfunction setActiveInterface(interfaceName, networkType) {root.activeInterface = interfaceName;root.networkType = networkType;resetState();updateInfo();// Persist the selectionBarState.data.network.activeInterface = interfaceName;BarState.data.network.type = networkType;}// Internal: Auto-select the most appropriate interface, if none is already// selected. Priority order: persisted state, wireless, wired, loopback.function autoSelectInterface() {if (activeInterface) return;// Wait for the state file to be loadedBarState.view.waitForJob();// Try persisted state firstconst state = BarState.data.network;if (state.activeInterface && interfaces.get(state.type)?.includes(state.activeInterface)) {activeInterface = state.activeInterface;networkType = state.type;return;}let wireless = interfaces.get(Types.Network.Wireless),wired = interfaces.get(Types.Network.Wired),iface, ifaceType;if (wireless?.length) {iface = wireless[0];ifaceType = Types.Network.Wireless;} else if (wired?.length) {iface = wired[0];ifaceType = Types.Network.Wired;} else {iface = 'lo';ifaceType = Types.Network.Virtual;}if (iface) {activeInterface = ifacenetworkType = ifaceType}// Persist the selectionstate.activeInterface = iface;state.type = ifaceType;}function updateRates() {if (!activeInterface) return;let prevTx = tx, prevRx = rx;rxBytesView.reload();rx = parseInt(rxBytesView.text().trim()) || 0;txBytesView.reload();tx = parseInt(txBytesView.text().trim()) || 0;if (prevTx > 0 && prevRx > 0) { // Skip initialrateUp = Math.max(0, tx - prevTx); // Up = tx (bytes/s)rateDown = Math.max(0, rx - prevRx); // Down = rx}}function updateInfo() {listInterfacesProc.running = true;}function updateExternal() {wanIPProc.running = true;}function resetState() {tx = 0; rx = 0; rateUp = 0; rateDown = 0;lanIPs = []; ssid = ""; frequency = 0; signalStrength = 0;bitrateTx = 0; bitrateRx = 0; linkSpeed = 0;}}
pragma Singletonimport QtQuickimport Quickshellimport Quickshell.Ioimport qs.modules.commonSingleton {// Array of CPU core usage percentages (0.0 to 1.0), one per coreproperty var coreUsages: []// Overall CPU usage percentage (0.0 to 1.0), average across all coresproperty real overallUsage: 0.0// Previous /proc/stat values for delta calculationsproperty var prevStats: []// Top processes, array of {pid: int, comm: string, cpu: float}property var topProcesses: []// Load average values (1-min, 5-min, 15-min)property var loadAvg: []// Number of top processes to showproperty int numTopProcesses: Config.data.cpu.numTopProcesses || 5Process {id: psProccommand: ["sh", "-c", `ps -eo pid,comm,%cpu --sort=-%cpu --no-headers | head -${numTopProcesses+10}`]stdout: StdioCollector {onStreamFinished: {let lines = this.text.split('\n').filter(l => l.trim() !== '');let tp = [];for (let line of lines) {if (tp.length == numTopProcesses) break;let parts = line.trim().split(/\s+/);if (parts.length === 3) {let comm = parts[1];if (comm === 'ps') continue; // Skip ps itselflet pid = parseInt(parts[0]);let cpu = parseFloat(parts[2]);tp.push({pid: pid, comm: comm, cpu: cpu});}}topProcesses = tp;}}}Process {id: loadAvgProccommand: ["uptime"]stdout: StdioCollector {onStreamFinished: {let line = this.text.trim();let parts = line.split("load average:");if (parts.length > 1) {let loads = parts[1].trim().split(',').map(s => parseFloat(s.trim()));if (loads.length >= 3) {loadAvg = loads;}}}}}FileView {id: statFilepath: "/proc/stat"onLoadFailed: function(error) {console.log("CPU Service: FileView load failed for /proc/stat:", error);}}Timer {interval: Config.data.cpu.updateIntervalrunning: truerepeat: truetriggeredOnStart: trueonTriggered: {updateCpuUsage()psProc.running = trueloadAvgProc.running = true}}function updateCpuUsage() {statFile.reload();let content = statFile.text();if (content === "") {return;}let lines = content.split('\n');let currentStats = [];// Parse lines starting with "cpu" (cpu, cpu0, cpu1, ...)for (let i = 0; i < lines.length; ++i) {let line = lines[i].trim();if (line.startsWith('cpu')) {let parts = line.split(/\s+/);if (parts.length >= 8) {let user = parseInt(parts[1]);let nice = parseInt(parts[2]);let system = parseInt(parts[3]);let idle = parseInt(parts[4]);let iowait = parseInt(parts[5]);let irq = parseInt(parts[6]);let softirq = parseInt(parts[7]);// Total ticks: sum of all except idle for denominatorlet total = user + nice + system + idle + iowait + irq + softirq;currentStats.push({ total: total, idle: idle });}}}if (prevStats.length === currentStats.length && prevStats.length > 0) {let totalUsage = 0.0;let coreUsagesTemp = [];// Calculate for each core (skip overall "cpu")for (let j = 1; j < currentStats.length; ++j) {let current = currentStats[j];let prev = prevStats[j];let deltaTotal = current.total - prev.total;let deltaIdle = current.idle - prev.idle;let usage = (deltaTotal > 0) ? (deltaTotal - deltaIdle) / deltaTotal : 0.0;usage = Math.min(Math.max(usage, 0.0), 1.0);coreUsagesTemp.push(usage);}// Overall usagelet overallCurrent = currentStats[0];let overallPrev = prevStats[0];let overallDeltaTotal = overallCurrent.total - overallPrev.total;let overallDeltaIdle = overallCurrent.idle - overallPrev.idle;totalUsage = (overallDeltaTotal > 0) ? (overallDeltaTotal - overallDeltaIdle) / overallDeltaTotal : 0.0;totalUsage = Math.min(Math.max(totalUsage, 0.0), 1.0);coreUsages = coreUsagesTemp;overallUsage = totalUsage;} else if (prevStats.length === 0) {// First run: Store baselinesprevStats = currentStats;}prevStats = currentStats;}}
pragma Singletonimport Quickshellimport Quickshell.Services.UPowerimport QtQuickimport Quickshell.Ioimport qs.servicesimport qs.modules.commonSingleton {property bool available: UPower.displayDevice.isLaptopBatteryproperty var chargeState: UPower.displayDevice.stateproperty bool isCharging: chargeState == UPowerDeviceState.Chargingproperty bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingChargeproperty real percentage: UPower.displayDevice?.percentage ?? 1readonly property bool allowAutomaticSuspend: Config.data.battery.automaticSuspendproperty bool isLow: available && (percentage <= Config.data.battery.low / 100)property bool isCritical: available && (percentage <= Config.data.battery.critical / 100)property bool isSuspending: available && (percentage <= Config.data.battery.suspend / 100)property bool isLowAndNotCharging: isLow && !isChargingproperty bool isCriticalAndNotCharging: isCritical && !isChargingproperty bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isChargingproperty real energyRate: UPower.displayDevice.changeRateproperty real timeToEmpty: UPower.displayDevice.timeToEmptyproperty real timeToFull: UPower.displayDevice.timeToFullonIsLowAndNotChargingChanged: {if (available && isLowAndNotCharging) Quickshell.execDetached(["notify-send","Low battery","Consider plugging in your device","-u", "critical","-a", "Shell"])}onIsCriticalAndNotChargingChanged: {if (available && isCriticalAndNotCharging) Quickshell.execDetached(["notify-send","Critically low battery","Please charge!\nAutomatic suspend triggers at %1".arg(Config.data.power.battery.suspend),"-u", "critical","-a", "Shell"]);}onIsSuspendingAndNotChargingChanged: {if (available && isSuspendingAndNotCharging) {Quickshell.execDetached(["sh", "-c", `systemctl suspend || loginctl suspend`]);}}}
import QtQuickItem {id: root// Public propertiesproperty color color: "#888888"property real strokeSize: 1property real length: 100property real dashLength: 2property real angle: 0 // 0 = horizontal, 90 = vertical (clockwise)property string lineType: "solid" // solid, dotted, dashed, dotdashproperty real edgeRadius: 0 // Radius for line endings and dash capsproperty real spacing: 2 // Spacing multiplier for gaps between dots/dashesimplicitWidth: Math.abs(Math.cos(angle * Math.PI / 180)) * length +Math.abs(Math.sin(angle * Math.PI / 180)) * strokeSizeimplicitHeight: Math.abs(Math.sin(angle * Math.PI / 180)) * length +Math.abs(Math.cos(angle * Math.PI / 180)) * strokeSizeCanvas {id: canvasanchors.fill: parentonPaint: {const ctx = getContext("2d");ctx.reset();ctx.save();// Translate to center and rotatectx.translate(width / 2, height / 2);ctx.rotate(root.angle * Math.PI / 180);// Configure line stylectx.strokeStyle = root.color;ctx.lineWidth = root.strokeSize;ctx.lineCap = root.edgeRadius > 0 ? "round" : "butt";ctx.lineJoin = "round";// Set dash pattern based on line typeconst gap = root.spacing;// Dots: very short dash length to create dotsconst dotLength = root.dashLength / 10;// Offset to ensure elements aren't cutoff.let dashOffset = 0;switch(root.lineType) {case "dotted":ctx.setLineDash([dotLength, gap]);dashOffset = -root.length / 2 % (gap / 10 + gap);break;case "dashed":ctx.setLineDash([root.dashLength, gap]);dashOffset = -root.length / 2 % (dashLength + gap);break;case "dotdash":ctx.setLineDash([dotLength, gap, root.dashLength, gap]);dashOffset = -root.length / 2 % (gap / 10 + gap + dashLength + gap);break;default: // solidctx.setLineDash([]);}ctx.lineDashOffset = dashOffset;// Draw the linectx.beginPath();ctx.moveTo(-root.length / 2, 0);ctx.lineTo(root.length / 2, 0);ctx.stroke();// Restore context statectx.restore();}}}
import QtQuickimport QtQuick.Shapesimport QuickshellItem {id: rootproperty real scale: 1property color color: "white"width: scaleheight: scale// Icon from Phosphor by Phosphor Icons// https://github.com/phosphor-icons/core/blob/main/LICENSE// Slightly modified to add an additional DRAM chip, and make the chips filled out.Shape {anchors.centerIn: parentwidth: root.widthheight: root.heightpreferredRendererType: Shape.CurveRendererfillMode: Shape.PreserveAspectFittransformOrigin: Item.TopLeftShapePath {fillColor: root.colorstrokeColor: "transparent"PathSvg { path: "M 232,56 H 24 C 15.163444,56 8,63.163444 8,72 v 128 c 0,4.41828 3.581722,8 8,8 4.418278,0 8,-3.58172 8,-8 v -16 h 16 v 16 c 0,4.41828 3.581722,8 8,8 4.418278,0 8,-3.58172 8,-8 v -16 h 16 v 16 c 0,4.41828 3.581722,8 8,8 4.418278,0 8,-3.58172 8,-8 v -16 h 16 v 16 c 0,4.41828 3.58172,8 8,8 4.41828,0 8,-3.58172 8,-8 v -16 h 16 v 16 c 0,4.41828 3.58172,8 8,8 4.41828,0 8,-3.58172 8,-8 v -16 h 16 v 16 c 0,4.41828 3.58172,8 8,8 4.41828,0 8,-3.58172 8,-8 v -16 h 16 v 16 c 0,4.41828 3.58172,8 8,8 4.41828,0 8,-3.58172 8,-8 v -16 h 16 v 16 c 0,4.41828 3.58172,8 8,8 4.41828,0 8,-3.58172 8,-8 V 72 c 0,-8.836556 -7.16344,-16 -16,-16 M 24,72 h 208 v 96 H 24 Z m 56,80 c 4.41828,0 8,-3.58172 8,-8 V 96 c 0,-4.418278 -3.58172,-8 -8,-8 H 48 c -4.418278,0 -8,3.581722 -8,8 v 48 c 0,4.41828 3.581722,8 8,8 z m 64,0 c 4.41828,0 8,-3.58172 8,-8 V 96 c 0,-4.418278 -3.58172,-8 -8,-8 h -32 c -4.41828,0 -8,3.581722 -8,8 v 48 c 0,4.41828 3.58172,8 8,8 z m 64,0 c 4.41828,0 8,-3.58172 8,-8 V 96 c 0,-4.418278 -3.58172,-8 -8,-8 h -32 c -4.41828,0 -8,3.581722 -8,8 v 48 c 0,4.41828 3.58172,8 8,8 z" }}}}
import QtQuickimport QtQuick.Shapesimport qs.modules.common.utilsItem {id: rootproperty real scale: 1property color color: "white"// 0 = no bars, 1 = innermost bar only, 2 = two bars, 3 = all three barsproperty int bars: 3width: scaleheight: scale// Icon from Material Symbols by Google// https://github.com/google/material-design-icons/blob/master/LICENSE// SVG deconstructed to allow dynamically changing the amount of visible bars.Shape {anchors.centerIn: parentwidth: root.widthheight: root.heightpreferredRendererType: Shape.CurveRendererfillMode: Shape.PreserveAspectFittransformOrigin: Item.TopLeft// Innermost bar (opaque when bars >= 1)ShapePath {fillColor: root.bars >= 1 ? root.color : ColorUtils.transparentize(root.color, 0.75)strokeColor: "transparent"PathSvg {path: "M12 14.5q-.95 0-1.85.25t-1.7.75q-.45.275-.975.263t-.9-.388t-.35-.875t.45-.8q1.175-.825 2.525-1.263T12 12t2.8.438t2.525 1.262q.425.3.45.8t-.35.875t-.9.388t-.975-.263q-.8-.5-1.7-.75T12 14.5"}}// Middle bar (opaque when bars >= 2)ShapePath {fillColor: root.bars >= 2 ? root.color : ColorUtils.transparentize(root.color, 0.75)strokeColor: "transparent"PathSvg {path: "M12 10.5q-1.75 0-3.375.538T5.6 12.625q-.425.325-.937.313t-.888-.388q-.35-.375-.337-.875t.412-.825q1.75-1.4 3.838-2.125T12.025 8t4.325.725t3.825 2.1q.4.325.425.838t-.35.887t-.9.388t-.95-.313q-1.4-1.05-3.037-1.588T12 10.5"}}// Outermost bar (opaque when bars >= 3)ShapePath {fillColor: root.bars >= 3 ? root.color : ColorUtils.transparentize(root.color, 0.75)strokeColor: "transparent"PathSvg {path: "M12 6.5q-2.55 0-4.913.85T2.775 9.775q-.425.35-.937.338T.95 9.725t-.362-.887T1 7.975q2.325-1.95 5.15-2.962T12 4t5.85 1.013T23 7.975q.4.35.413.863t-.363.887t-.888.388t-.937-.338Q19.275 8.2 16.913 7.35T12 6.5"}}// Central element (always opaque)ShapePath {fillColor: root.colorstrokeColor: "transparent"PathSvg {path: "M12 20q-.825 0-1.412-.587T10 18t.588-1.412T12 16t1.413.588T14 18t-.587 1.413T12 20"}}}}
import QtQuickimport QtQuick.ShapesItem {id: rootproperty real scale: 1property color color: "white"width: scaleheight: scale// Icon from Material Design Icons by Pictogrammers// https://github.com/Templarian/MaterialDesign/blob/master/LICENSEShape {anchors.centerIn: parentwidth: root.widthheight: root.heightpreferredRendererType: Shape.CurveRendererfillMode: Shape.PreserveAspectFittransformOrigin: Item.TopLeftShapePath {fillColor: root.colorstrokeColor: "transparent"PathSvg { path: "M7 15h2v3h2v-3h2v3h2v-3h2v3h2V9h-4V6H9v3H5v9h2zM4.38 3h15.25A2.37 2.37 0 0 1 22 5.38v14.25A2.37 2.37 0 0 1 19.63 22H4.38A2.37 2.37 0 0 1 2 19.63V5.38C2 4.06 3.06 3 4.38 3" }}}}
import QtQuickimport QtQuick.ShapesItem {id: rootproperty real scale: 1property color color: "white"width: scaleheight: scale// Icon from Material Design Icons by Pictogrammers// https://github.com/Templarian/MaterialDesign/blob/master/LICENSEShape {anchors.centerIn: parentwidth: root.widthheight: root.heightpreferredRendererType: Shape.CurveRendererfillMode: Shape.PreserveAspectFittransformOrigin: Item.TopLeftShapePath {fillColor: root.colorstrokeColor: "transparent"PathSvg { path: "M15 20a1 1 0 0 0-1-1h-1v-2h4a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h4v2h-1a1 1 0 0 0-1 1H2v2h7a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1h7v-2zm-8-5V5h10v10zm3-9H8v8h2zm4 0h-3v8h2v-2h1a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2m0 4h-1V8h1z" }}}}
import QtQuickimport QtQuick.Shapesimport QuickshellItem {id: rootproperty real scale: 1property color color: "white"width: scaleheight: scale// Icon from HeroIcons by Refactoring UI Inc// https://github.com/tailwindlabs/heroicons/blob/master/LICENSEShape {anchors.centerIn: parentwidth: root.widthheight: root.heightpreferredRendererType: Shape.CurveRendererfillMode: Shape.PreserveAspectFittransformOrigin: Item.TopLeftShapePath {fillColor: root.colorstrokeColor: "transparent"PathSvg { path: "M14 6H6v8h8z" }}ShapePath {fillColor: root.colorstrokeColor: "transparent"PathSvg { path: "M9.25 3V1.75a.75.75 0 0 1 1.5 0V3h1.5V1.75a.75.75 0 0 1 1.5 0V3h.5A2.75 2.75 0 0 1 17 5.75v.5h1.25a.75.75 0 0 1 0 1.5H17v1.5h1.25a.75.75 0 0 1 0 1.5H17v1.5h1.25a.75.75 0 0 1 0 1.5H17v.5A2.75 2.75 0 0 1 14.25 17h-.5v1.25a.75.75 0 0 1-1.5 0V17h-1.5v1.25a.75.75 0 0 1-1.5 0V17h-1.5v1.25a.75.75 0 0 1-1.5 0V17h-.5A2.75 2.75 0 0 1 3 14.25v-.5H1.75a.75.75 0 0 1 0-1.5H3v-1.5H1.75a.75.75 0 0 1 0-1.5H3v-1.5H1.75a.75.75 0 0 1 0-1.5H3v-.5A2.75 2.75 0 0 1 5.75 3h.5V1.75a.75.75 0 0 1 1.5 0V3zM4.5 5.75c0-.69.56-1.25 1.25-1.25h8.5c.69 0 1.25.56 1.25 1.25v8.5c0 .69-.56 1.25-1.25 1.25h-8.5c-.69 0-1.25-.56-1.25-1.25z" }}}}
import QtQuickimport qs.modules.commonItem {id: rootproperty color iconColor: "white"property int orientation: Types.Orientation.Horizontalproperty real size: 1readonly property real borderWidth: size * 0.05readonly property real bodyWidth: body.widthreadonly property real bodyHeight: body.heightreadonly property real bodyRadius: body.radiusstate: Types.orientationToString(orientation)width: orientation === Types.Orientation.Horizontal ? size : size * 0.6height: orientation === Types.Orientation.Vertical ? size : size * 0.6Rectangle {id: bodycolor: "transparent"border.color: root.iconColorborder.width: root.borderWidthradius: root.size * 0.1}Rectangle {id: nubcolor: root.iconColorradius: 0}states: [State {name: "horizontal"AnchorChanges {target: bodyanchors.left: parent.leftanchors.right: nub.leftanchors.verticalCenter: parent.verticalCenter}AnchorChanges {target: nubanchors.right: parent.rightanchors.verticalCenter: parent.verticalCenter}PropertyChanges {target: bodywidth: root.width * 0.9height: root.height}PropertyChanges {target: nubwidth: root.size * 0.1height: root.size * 0.2topRightRadius: root.size * 0.1bottomRightRadius: root.size * 0.1}},State {name: "vertical"AnchorChanges {target: bodyanchors.left: parent.leftanchors.right: parent.rightanchors.top: nub.bottomanchors.bottom: parent.bottom}AnchorChanges {target: nubanchors.top: parent.topanchors.horizontalCenter: parent.horizontalCenter}PropertyChanges {target: bodywidth: root.widthheight: root.height * 0.9}PropertyChanges {target: nubwidth: root.size * 0.2height: root.size * 0.1topLeftRadius: root.size * 0.1topRightRadius: root.size * 0.1}}]}
import QtQuickItem {id: rootproperty real scale: 1property color color: "white"// Rotation angle (0-360 degrees)property real angle: 0width: scaleheight: scale// Icon from TDesign Icons by TDesign// https://github.com/Tencent/tdesign-icons/blob/main/LICENSE// Converted to 2D canvas by Grok Code Fast 1.// I'm not using the original SVG with Shape since I had issues positioning// the rotated icon.Canvas {anchors.centerIn: parentwidth: root.widthheight: root.heightonPaint: {const ctx = getContext("2d");ctx.clearRect(0, 0, width, height);ctx.antialiasing = false;// Center the canvas coordinate system at the middle of the icon's// bounding boxctx.save();ctx.translate(width / 2, height / 2);ctx.rotate(root.angle * Math.PI / 180);// Calculate scale to fit the path's implicit bounding box// (x:4.5-19.5 = width 15; y:2-22 = height 20) within the canvas// boundsconst pathWidth = 15; // Implicit bbox width from pathconst pathHeight = 20; // Implicit bbox height from pathconst s = Math.min(width / pathWidth, height / pathHeight);ctx.scale(s, s);// Translate to offset the path's center (original SVG center at// (12,12)) to (0,0) in the centered/rotated spacectx.translate(-12, -12);// Draw the pathctx.fillStyle = root.color;ctx.beginPath();ctx.moveTo(15, 12);ctx.lineTo(19.5, 12);ctx.lineTo(12, 2);ctx.lineTo(4.5, 12);ctx.lineTo(9, 12);ctx.lineTo(9, 22);ctx.lineTo(15, 22);ctx.closePath();ctx.fill();ctx.restore();}}}
import QtQuickimport QtQuick.Controlsimport qs.modules.commonimport qs.modules.common.utils/*** A progress bar with optional text centered inside it.* Partially based on https://github.com/end-4/dots-hyprland/blob/449df7f161e6435569bc7d9499b2e444dd8aa153/dots/.config/quickshell/ii/modules/common/widgets/ClippedProgressBar.qml*/ProgressBar {id: rootproperty int orientation: Types.Orientation.Horizontalproperty real valueBarWidth: 2property real valueBarHeight: 1property color highlightColor: "gray"property color trackColor: ColorUtils.transparentize(highlightColor, 0.7)property color textColor: "white"property string textproperty bool shimmer: falseproperty bool pulse: falsefont.weight: text.length > 2 ? Font.Medium : Font.DemiBoldbackground: Item {implicitHeight: valueBarHeightimplicitWidth: valueBarWidth}contentItem: Rectangle {id: contentItemanchors.fill: parentcolor: root.trackColorSequentialAnimation on color {running: root.pulseloops: Animation.InfiniteColorAnimation {from: root.trackColorto: {var c = Qt.color(root.trackColor);var boostedLight = Math.min(1.0, c.hslLightness + 0.2);return Qt.hsla(c.hslHue, c.hslSaturation, boostedLight, c.a);}duration: 1000easing.type: Easing.InOutQuad}ColorAnimation {from: {var c = Qt.color(root.trackColor);var boostedLight = Math.min(1.0, c.hslLightness + 0.2);return Qt.hsla(c.hslHue, c.hslSaturation, boostedLight, c.a);}to: root.trackColorduration: 1000easing.type: Easing.InOutQuad}}Rectangle {id: progressFillcolor: root.highlightColorclip: true // ensure the shimmer is only visible inside progressFillanchors {top: parent.topbottom: parent.bottomleft: parent.leftright: undefined}width: parent.width * root.visualPositionheight: parent.heightstates: State {name: "vertical"when: root.orientation === Types.Orientation.VerticalAnchorChanges {target: progressFillanchors {top: undefinedbottom: parent.bottomleft: parent.leftright: parent.right}}PropertyChanges {target: progressFillwidth: parent.widthheight: parent.height * root.visualPosition}}Rectangle {id: shimmerOverlayvisible: root.shimmerwidth: root.valueBarWidthheight: root.valueBarHeightproperty real shimmerWidth: root.orientation === Types.Orientation.Vertical? root.valueBarHeight * 0.2 : root.valueBarWidth * 0.2x: root.orientation === Types.Orientation.Vertical ? 0 : -progressFill.xy: root.orientation === Types.Orientation.Vertical ? -progressFill.y : 0opacity: 0.5gradient: Gradient {orientation: root.orientation === Types.Orientation.Vertical? Gradient.Vertical : Gradient.HorizontalGradientStop { position: -0.3; color: "transparent" }GradientStop {position: Math.max(-0.3,shimmerAnimation.position - shimmerOverlay.shimmerWidth/ (root.orientation === Types.Orientation.Vertical? root.valueBarHeight : root.valueBarWidth))color: "transparent"}GradientStop { position: shimmerAnimation.position; color: "white" }GradientStop {position: Math.min(1.3,shimmerAnimation.position + shimmerOverlay.shimmerWidth/ (root.orientation === Types.Orientation.Vertical? root.valueBarHeight : root.valueBarWidth))color: "transparent"}GradientStop { position: 1.3; color: "transparent" }}SequentialAnimation on x {id: shimmerAnimationproperty real position: 0.0running: root.shimmerloops: Animation.InfiniteNumberAnimation {target: shimmerAnimationproperty: "position"from: root.orientation === Types.Orientation.Vertical ? 1.2 : -0.2to: root.orientation === Types.Orientation.Vertical ? 0.8 - value : value + 0.2duration: 3000easing.type: Easing.InOutExpo}}}}}Text {id: overlayTextfont: root.fonttext: root.textcolor: textColoropacity: 0.5width: root.widthheight: root.heightverticalAlignment: Text.AlignVCenterhorizontalAlignment: Text.AlignHCenterstyle: Text.Outline}}
import QtQuickimport QtQuick.Layoutsimport QtQuick.Effectsimport Quickshellimport Quickshell.Waylandimport qs.modules.commonItem {id: rootproperty Item hoverTargetproperty int anchorPosition: Types.Position.Topproperty bool shouldShow: false// Generic content to display in the popupproperty Component contentComponent: nullwidth: hoverTarget ? hoverTarget.width : 0height: hoverTarget ? hoverTarget.height : 0MouseArea {id: mouseAreaanchors.fill: parenthoverEnabled: trueonEntered: root.onHoveredEntered()onExited: root.onHoveredExited()}Timer {id: popupTimerinterval: 500onTriggered: root.open()}function onHoveredEntered() {popupTimer.start()}function onHoveredExited() {popupTimer.stop()root.close()}function open() {shouldShow = true}function close() {shouldShow = false}LazyLoader {active: root.shouldShowcomponent: PanelWindow {id: popupWindowcolor: "transparent"anchors {left: trueright: truetop: root.anchorPosition === Types.Position.Topbottom: root.anchorPosition === Types.Position.Bottom}implicitWidth: contentRect.implicitWidthimplicitHeight: contentRect.implicitHeight + chevron.height * 2margins {left: {const mapped = root.QsWindow.mapFromItem(root,(root.width - contentRect.implicitWidth)/2, 0)return mapped.x}top: root.anchorPosition === Types.Position.Top ? Config.data.bar.size : 0bottom: root.anchorPosition === Types.Position.Bottom ? Config.data.bar.size : 0}exclusionMode: ExclusionMode.IgnoreexclusiveZone: 0WlrLayershell.namespace: "quickshell:hover-popup"WlrLayershell.layer: WlrLayer.TopRectangle {id: contentRectcolor: Config.data.theme.colors.backgroundradius: 8implicitWidth: contentLoader.implicitWidth + 24implicitHeight: contentLoader.implicitHeight + 24y: chevron.heightlayer.enabled: truelayer.effect: MultiEffect {shadowEnabled: trueshadowVerticalOffset: 5shadowHorizontalOffset: 0blurMax: 20shadowColor: "#60000000"}Loader {id: contentLoaderanchors.centerIn: parentsourceComponent: root.contentComponent}}// Chevron element pointing to the hovered componentCanvas {id: chevronwidth: 20height: 10anchors {horizontalCenter: contentRect.horizontalCenterbottom: root.anchorPosition === Types.Position.Top ? contentRect.top : undefinedtop: root.anchorPosition === Types.Position.Bottom ? contentRect.bottom : undefined}onPaint: {var ctx = getContext("2d")ctx.clearRect(0, 0, width, height)ctx.fillStyle = Config.data.theme.colors.backgroundctx.beginPath()if (root.anchorPosition === Types.Position.Top) {// Pointing upctx.moveTo(0, height)ctx.lineTo(width / 2, 0)ctx.lineTo(width, height)} else {// Pointing downctx.moveTo(0, 0)ctx.lineTo(width / 2, height)ctx.lineTo(width, 0)}ctx.closePath()ctx.fill()}}}}}
import QtQuickimport QtQuick.Controls// A menu that sets its width according to the size of its items, excluding MenuSeparator.// Source: https://martin.rpdev.net/2018/03/13/qt-quick-controls-2-automatically-set-the-width-of-menus.htmlMenu {implicitWidth: {let result = 0;let padding = 0;for (let i = 0; i < count; ++i) {let item = itemAt(i);if (item instanceof MenuItem && !(item instanceof MenuSeparator)) {result = Math.max(item.contentItem.implicitWidth, result);padding = Math.max(item.padding, padding);}}// Add a small margin factor for the padding.return result + padding * 3;}}
pragma Singletonimport QuickshellSingleton {id: root// Binary unit factors: 1 KiB = 1024 bytes, etc.readonly property int bytesPerKib: 1024readonly property int bytesPerMib: 1024 ** 2readonly property int bytesPerGib: 1024 ** 3readonly property int bytesPerTib: 1024 ** 4readonly property int bytesPerPib: 1024 ** 5readonly property int bytesPerEib: 1024 ** 6// Decimal unit factors: 1 KB = 1000 bytes, etc.readonly property int bytesPerKB: 1000readonly property int bytesPerMB: 1000 ** 2readonly property int bytesPerGB: 1000 ** 3readonly property int bytesPerTB: 1000 ** 4readonly property int bytesPerPB: 1000 ** 5readonly property int bytesPerEB: 1000 ** 6// Unit hierarchiesreadonly property var unitsIECByte: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]readonly property var unitsMetricByte: ["KB", "MB", "GB", "TB", "PB", "EB"]readonly property var unitsIECBit: ["Kib", "Mib", "Gib", "Tib", "Pib", "Eib"]readonly property var unitsMetricBit: ["Kb", "Mb", "Gb", "Tb", "Pb", "Eb"]readonly property var units: ({"B": 1,"KB": bytesPerKB,"KiB": bytesPerKib,"MB": bytesPerMB,"MiB": bytesPerMib,"GB": bytesPerGB,"GiB": bytesPerGib,"TB": bytesPerTB,"TiB": bytesPerTib,"PB": bytesPerPB,"PiB": bytesPerPib,"EB": bytesPerEB,"EiB": bytesPerEib,// Bit units (e.g. 1 Kb = 1000 bits = bytesPerKB / 8 bytes)"b": 1 / 8.0,"Kb": bytesPerKB / 8.0,"Kib": bytesPerKib / 8.0,"Mb": bytesPerMB / 8.0,"Mib": bytesPerMib / 8.0,"Gb": bytesPerGB / 8.0,"Gib": bytesPerGib / 8.0,"Tb": bytesPerTB / 8.0,"Tib": bytesPerTib / 8.0,"Pb": bytesPerPB / 8.0,"Pib": bytesPerPib / 8.0,"Eb": bytesPerEB / 8.0,"Eib": bytesPerEib / 8.0})/*** Converts a value from one file size unit to another.** @param {number} value - The numeric value to convert.* @param {string} fromUnit - The unit of the input value (e.g., "KB", "MB").* @param {string} toUnit - The unit to convert to (e.g., "MB", "GB").* @returns {number} The converted value.*/function convertSizeUnit(value, fromUnit, toUnit) {if (!units.hasOwnProperty(fromUnit) || !units.hasOwnProperty(toUnit)) {throw new Error("Invalid unit. Supported units: " + Object.keys(units).join(", "));}if (fromUnit === toUnit) {return value;}var bytes = value * units[fromUnit];return bytes / units[toUnit];}// Determine unit type and return ordered hierarchy.function getUnitHierarchy(unit) {switch (true) {case unit.endsWith('iB'):return unitsIECByte;case unit.endsWith('B'):return unitsMetricByte;case unit.endsWith('ib'):return unitsIECBit;case unit.endsWith('b'):return unitsMetricBit;default:throw new Error(`Invalid unit: ${unit}. Supported units: ` + Object.keys(units).join(", "));}}}
pragma Singletonimport Quickshell/*Copied from https://github.com/end-4/dots-hyprland/blob/449df7f161e6435569bc7d9499b2e444dd8aa153/dots/.config/quickshell/ii/modules/common/functions/ColorUtils.qml*/Singleton {id: root/*** Returns a color with the hue of color2 and the saturation, value, and alpha of color1.** @param {string} color1 - The base color (any Qt.color-compatible string).* @param {string} color2 - The color to take hue from.* @returns {Qt.rgba} The resulting color.*/function withHueOf(color1, color2) {var c1 = Qt.color(color1);var c2 = Qt.color(color2);// Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1var hue = c2.hsvHue;var sat = c1.hsvSaturation;var val = c1.hsvValue;var alpha = c1.a;return Qt.hsva(hue, sat, val, alpha);}/*** Returns a color with the saturation of color2 and the hue/value/alpha of color1.** @param {string} color1 - The base color (any Qt.color-compatible string).* @param {string} color2 - The color to take saturation from.* @returns {Qt.rgba} The resulting color.*/function withSaturationOf(color1, color2) {var c1 = Qt.color(color1);var c2 = Qt.color(color2);var hue = c1.hsvHue;var sat = c2.hsvSaturation;var val = c1.hsvValue;var alpha = c1.a;return Qt.hsva(hue, sat, val, alpha);}/*** Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).** @param {string} color - The base color (any Qt.color-compatible string).* @param {number} lightness - The lightness value to use (0-1).* @returns {Qt.rgba} The resulting color.*/function withLightness(color, lightness) {var c = Qt.color(color);return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);}/*** Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).** @param {string} color1 - The base color (any Qt.color-compatible string).* @param {string} color2 - The color to take lightness from.* @returns {Qt.rgba} The resulting color.*/function withLightnessOf(color1, color2) {var c2 = Qt.color(color2);return colorWithLightness(color1, c2.hslLightness);}/*** Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.** @param {string} color1 - The base color (any Qt.color-compatible string).* @param {string} color2 - The accent color.* @returns {Qt.rgba} The resulting color.*/function adaptToAccent(color1, color2) {var c1 = Qt.color(color1);var c2 = Qt.color(color2);var hue = c2.hslHue;var sat = c2.hslSaturation;var light = c1.hslLightness;var alpha = c1.a;return Qt.hsla(hue, sat, light, alpha);}/*** Mixes two colors by a given percentage.** @param {string} color1 - The first color (any Qt.color-compatible string).* @param {string} color2 - The second color.* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.* @returns {Qt.rgba} The resulting mixed color.*/function mix(color1, color2, percentage = 0.5) {var c1 = Qt.color(color1);var c2 = Qt.color(color2);return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);}/*** Transparentizes a color by a given percentage.** @param {string} color - The color (any Qt.color-compatible string).* @param {number} percentage - The amount to transparentize (0-1).* @returns {Qt.rgba} The resulting color.*/function transparentize(color, percentage = 1) {var c = Qt.color(color);return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));}/*** Sets the alpha channel of a color.** @param {string} color - The base color (any Qt.color-compatible string).* @param {number} alpha - The desired alpha (0-1).* @returns {Qt.rgba} The resulting color with applied alpha.*/function applyAlpha(color, alpha) {var c = Qt.color(color);var a = Math.max(0, Math.min(1, alpha));return Qt.rgba(c.r, c.g, c.b, a);}}
pragma Singletonimport QtQuickQtObject {enum Orientation {Horizontal,Vertical}enum Position {Top,Bottom}enum Network {Wired,Wireless,Virtual}/*** I would prefer to use stdlib enum conversion functions:* https://doc.qt.io/qt-6/qtqml-typesystem-enumerations.html* But these aren't defined in Quickshell v0.2.1, for some reason, even* though it does use Qt 6.10...*/function stringToOrientation(str) {const normalized = str.toLowerCase();switch (normalized) {case "horizontal":return Types.Orientation.Horizontal;case "vertical":return Types.Orientation.Vertical;default:console.error("Error: invalid Orientation value:", str)return -1;}}function orientationToString(value) {switch (value) {case Types.Orientation.Horizontal:return "horizontal"case Types.Orientation.Vertical:return "vertical"default:console.error("Error: invalid Orientation value:", value)return "";}}function stringToPosition(str) {const normalized = str.toLowerCase();switch (normalized) {case "top":return Types.Position.Top;case "bottom":return Types.Position.Bottom;default:console.error("Error: invalid Position value:", str)return -1;}}function positionToString(value) {switch (value) {case Types.Position.Top:return "top"case Types.Position.Bottom:return "bottom"default:console.error("Error: invalid Position value:", value)return "";}}function stringToNetwork(str) {const normalized = str.toLowerCase();switch (normalized) {case "wired":return Types.Network.Wired;case "wireless":return Types.Network.Wireless;case "virtual":return Types.Network.Virtual;default:console.error("Error: invalid Network value:", str)return -1;}}function networkToString(value) {switch (value) {case Types.Network.Wired:return "wired"case Types.Network.Wireless:return "wireless"case Types.Network.Virtual:return "virtual";default:console.error("Error: invalid Network value:", value)return "";}}}
pragma Singletonimport Quickshellimport Quickshell.Ioimport qs.modules.commonSingleton {property var data: adapterFileView {path: Quickshell.shellPath("config.json")watchChanges: trueonFileChanged: reload()onAdapterUpdated: writeAdapter()blockLoading: true// For some reason, this is needed to read workspaces.maxCount from the// config.json.preload: falseJsonAdapter {id: adapter// Global theme. Source of default and base values for all components.property JsonObject theme: JsonObject {property JsonObject colors: JsonObject {property string text: "#999999"property string textMuted: "#777777"property string foreground: "#999999"property string foreground2: "#777777"property string background: "#222222"property string background2: "#666666"property string ok: "#1A7F39"property string error: "#E5002E"property string warning: "#E5BF00"}// Proportional fontproperty JsonObject font: JsonObject {property string family: "Lexend"// Size in pixels of all proportional fonts. The actual size// of fonts in individual components will be proportional to// this value.property real size: 14}// Monospace fontproperty JsonObject fontMono: JsonObject {property string family: "Maple Mono NF"// Size in pixels of all monospace fonts. The actual size of// fonts in individual components will be proportional to// this value.property real size: 14}property JsonObject widget: JsonObject {// Size in pixels of all widgets. The actual size of// individual widgets will be proportial to this value.property real size: 24}}// Defines the widgets that should be shown in each section and their order.property JsonObject layout: JsonObject {property JsonObject left: JsonObject {property list<string> widgets: ["workspaces", "focusedWindow"]property bool separator: trueproperty int spacing: 6}property JsonObject center: JsonObject {property list<string> widgets: []property bool separator: trueproperty int spacing: 6}property JsonObject right: JsonObject {property list<string> widgets: ["cpu", "ram", "network", "battery", "clock"]property bool separator: trueproperty int spacing: 6}}property JsonObject bar: JsonObject {property string position: Types.positionToString(Types.Position.Top)property int size: 30}property JsonObject focusedWindow: JsonObject {property JsonObject icon: JsonObject {property bool enabled: trueproperty real scale: 0.9}property JsonObject title: JsonObject {property bool enabled: true}property JsonObject font: JsonObject {property string familyproperty real scale: 1.2property int weight: 600}}property JsonObject cpu: JsonObject {property real scale: 1property real updateInterval: 1000 // Millisecondsproperty int numTopProcesses: 5property JsonObject icon: JsonObject {property bool enabled: trueproperty real scale: 0.85property string color: Config.data.theme.colors.foreground2}property JsonObject graph: JsonObject {property bool enabled: trueproperty real history: 30 // Secondsproperty string lineColor: Config.data.theme.colors.foregroundproperty string lowUsageColor: "#802D3154" // Cool blueproperty string highUsageColor: "#80FF4500" // Bright orange/red}}property JsonObject ram: JsonObject {property real scale: 1property real updateInterval: 1000 // Millisecondsproperty string sizeUnit: "GiB"property int numTopProcesses: 5property JsonObject icon: JsonObject {property bool enabled: trueproperty real scale: 1property string color: Config.data.theme.colors.foreground2}property JsonObject colors: JsonObject {property string used: "#2E86C1" // Blueproperty string shared: "#004880" // Dark blueproperty string buffersCached: "#7D3C98" // Purpleproperty string free: "#666666" // Gray}property JsonObject graph: JsonObject {property bool enabled: true}}property JsonObject network: JsonObject {property real scale: 1// For up/down rates and graph updatesproperty real rateUpdateInterval: 1000 // Milliseconds// For interface information (link speed, SSID, LAN IPs, etc.)property real infoUpdateInterval: 5 // Seconds// For external information (WAN IP)property real externalUpdateInterval: 600 // Secondsproperty JsonObject rates: JsonObject {property bool enabled: trueproperty string baseUnit: "KiB"}property JsonObject graph: JsonObject {property bool enabled: trueproperty real history: 30 // Seconds}property JsonObject icon: JsonObject {property bool enabled: trueproperty real scale: 1property string color: Config.data.theme.colors.foreground2}property JsonObject colors: JsonObject {property string rx: "#1F77B4" // Blueproperty string tx: "#FF7F0E" // Orange}}property JsonObject battery: JsonObject {property real scale: 1.5property int low: 20property int critical: 10property int suspend: 5property bool automaticSuspend: trueproperty bool showPercentage: trueproperty string orientation: Types.orientationToString(Types.Orientation.Horizontal)}property JsonObject clock: JsonObject {property real scale: 1property JsonObject time: JsonObject {property bool enabled: trueproperty string format: "hh:mm"}property JsonObject date: JsonObject {property bool enabled: trueproperty string format: "yyyy-MM-dd"}property JsonObject font: JsonObject {property string familyproperty real scale: 1property int weight: 400}}property JsonObject workspaces: JsonObject {property int maxCount: 10property JsonObject icon: JsonObject {property real scale: 0.6property real radius: 1}property JsonObject colors: JsonObject {property string active: "#000000"property string inactive: "#333333"}}}}}
pragma Singletonimport Quickshellimport Quickshell.IoSingleton {readonly property var data: adapterreadonly property var view: viewFileView {id: viewpath: Quickshell.shellPath("state.json")watchChanges: trueonFileChanged: reload()onAdapterUpdated: writeAdapter()blockLoading: trueJsonAdapter {id: adapterproperty JsonObject network: JsonObject {property string activeInterface: ""property int type: -1}}}}
import QtQuickimport QtQuick.Layoutsimport Quickshellimport qs.modules.commonimport qs.servicesRectangle {id: rootimplicitWidth: row.width + 20implicitHeight: Config.data.bar.size - Config.data.bar.size * 0.25color: Config.data.theme.colors.background2radius: 50Component.onCompleted: {Niri.workspaces.maxCount = Config.data.workspaces.maxCount;}Row {id: rowanchors.centerIn: parentspacing: 8Repeater {model: Niri.workspacesItem {implicitWidth: 10implicitHeight: 10Rectangle {anchors.fill: parentradius: 5color: (model.isActive || model.activeWindowId > 0)? Config.data.workspaces.colors.active: Config.data.workspaces.colors.inactive;opacity: model.isActive ? 1.0 : 0.5scale: model.isActive ? 1.25 : 1.0MouseArea {anchors.fill: parentcursorShape: Qt.PointingHandCursoronClicked: Niri.focusWorkspaceById(model.id)}Behavior on scale {PropertyAnimation {duration: 150easing.type: Easing.InOutQuad}}Behavior on opacity {NumberAnimation { duration: 150 }}}}}}}
import QtQuickimport QtQuick.Layoutsimport Quickshellimport qs.modules.commonimport qs.modules.common.widgetsimport qs.modules.common.utilsimport qs.modules.iconsimport qs.servicesItem {id: rootproperty real scale: Config.data.ram.scale || 1.0implicitWidth: (icon.visible ? icon.width : 0)+ (graph.visible ? graph.width : 0) + 4implicitHeight: Config.data.bar.size - Config.data.bar.size * 0.2RAMIcon {id: iconvisible: Config.data.ram.icon.enabledcolor: Config.data.ram.icon.colorscale: Config.data.ram.icon.scale * root.heightanchors.verticalCenter: parent.verticalCenter}Canvas {id: graphvisible: Config.data.ram.graph.enabledanchors {top: parent.topbottom: parent.bottomright: parent.right}width: 100 * root.scaleonPaint: {if (RAM.total === 0) return;let ctx = getContext("2d");ctx.clearRect(0, 0, width, height);let usedRatio = RAM.used / RAM.total;let sharedRatio = RAM.shared / RAM.total;let buffersCachedRatio = (RAM.buffers + RAM.cached + RAM.sreclaimable) / RAM.total;let freeRatio = RAM.free / RAM.total;// Clamp small ratios to minimum visible widthlet minVisible = 1 / width;usedRatio = Math.max(usedRatio, minVisible);sharedRatio = Math.max(sharedRatio, minVisible);buffersCachedRatio = Math.max(buffersCachedRatio, minVisible);freeRatio = Math.max(freeRatio, minVisible);let cumulativeX = 0;// Draw segments from left to rightif (usedRatio > 0) {ctx.fillStyle = Config.data.ram.colors.used;ctx.fillRect(cumulativeX, 0, usedRatio * width, height);cumulativeX += usedRatio * width;}if (sharedRatio > 0) {ctx.fillStyle = Config.data.ram.colors.shared;ctx.fillRect(cumulativeX, 0, sharedRatio * width, height);cumulativeX += sharedRatio * width;}if (buffersCachedRatio > 0) {ctx.fillStyle = Config.data.ram.colors.buffersCached;ctx.fillRect(cumulativeX, 0, buffersCachedRatio * width, height);cumulativeX += buffersCachedRatio * width;}if (freeRatio > 0) {ctx.fillStyle = Config.data.ram.colors.free;ctx.fillRect(cumulativeX, 0, freeRatio * width, height);}}}Connections {target: RAMfunction onStatsUpdated() {graph.requestPaint()}}HoverPopup {anchors.centerIn: roothoverTarget: rootanchorPosition: Types.stringToPosition(Config.data.bar.position)contentComponent: Component {ColumnLayout {id: contentColumnspacing: 2Text {Layout.alignment: Qt.AlignHCentertext: "RAM Usage"font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizefont.bold: truecolor: Config.data.theme.colors.foreground}Text {Layout.alignment: Qt.AlignHCenterfont.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizecolor: Config.data.theme.colors.foregroundtextFormat: Text.RichTexttext: {let rows = [{label: "Total",source: ['total'],color: Config.data.theme.colors.foreground,},{label: "Used",source: ['used'],color: Config.data.ram.colors.used,},{label: "Shared",source: ['shared'],color: Config.data.ram.colors.shared,},{label: "Buff/Cache",source: ['buffers', 'cached', 'sreclaimable'],color: Config.data.ram.colors.buffersCached,},{label: "Free",source: ['free'],color: Config.data.ram.colors.free,},{label: "Available",source: ['available'],color: Config.data.theme.colors.foreground,},];let total = FileUtils.convertSizeUnit(RAM.total, "KiB", Config.data.ram.sizeUnit);for (let i = 0; i < rows.length; ++i) {let val = 0.0;for (let j = 0; j < rows[i].source.length; ++j) {val += RAM[rows[i].source[j]];}if (rows[i].label === 'Total') {rows[i].value = total;} else {rows[i].value = FileUtils.convertSizeUnit(val, "KiB", Config.data.ram.sizeUnit);rows[i].pct = (rows[i].value / total)*100;}}let createRow = function(rowData) {return `<tr><td align="left" width="60"><span style="color: ${rowData.color}">${rowData.label}</span>:</td><td align="right" width="70"><span style="font-family: '${Config.data.theme.fontMono.family}'; font-size: ${Config.data.theme.fontMono.size}px;">${rowData.value.toFixed(2)}${Config.data.ram.sizeUnit}</span></td><td align="right" width="60"><span style="font-family: '${Config.data.theme.fontMono.family}'; font-size: ${Config.data.theme.fontMono.size}px;">${typeof rowData.pct !== 'undefined' ? rowData.pct.toFixed(2) + "%" : ""}</span></td></tr>`;};return `<table>${rows.map(r => createRow(r)).join("")}</table>`;}}Text {Layout.alignment: Qt.AlignHCentertext: "Top Processes"font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizefont.bold: truecolor: Config.data.theme.colors.foregroundLayout.topMargin: 6}Text {Layout.alignment: Qt.AlignHCenterfont.family: Config.data.theme.fontMono.familyfont.pixelSize: Config.data.theme.fontMono.sizecolor: Config.data.theme.colors.foregroundtext: {let out = [];for (let p of RAM.topProcesses) {let pidStr = p.pid.toString().padStart(6);let commStr = p.comm.slice(0, 15).padEnd(15);let memStr = `${p.mem.toFixed(1)}%`;out.push(`${pidStr} ${commStr}\t${memStr.padStart(6)}`);}return out.join("\n");}}}}}}
import QtQuickimport QtQuick.Controlsimport QtQuick.Layoutsimport qs.modules.commonimport qs.modules.common.utilsimport qs.modules.common.widgetsimport qs.modules.iconsimport qs.servicesItem {id: rootproperty real scale: Config.data.network.scaleproperty real history: Config.data.network.graph.historyproperty var points: [] // Graph points: [{x: time, up: bytes/s, down: bytes/s}]readonly property var unitHierarchy: FileUtils.getUnitHierarchy(Config.data.network.rates.baseUnit)implicitWidth: (graph.visible ? graph.width : 0)+ (iconLoader.visible ? iconLoader.width : 0)+ (rates.visible ? rates.width : 0) + 4implicitHeight: Config.data.bar.size - Config.data.bar.size * 0.2Component.onCompleted: points.push({x: Date.now(), up: 0, down: 0});Timer {interval: Config.data.network.rateUpdateIntervalrunning: truerepeat: trueonTriggered: {const x = Date.now(), up = Network.rateUp, down = Network.rateDown;points.push({x, up, down});const cutoff = x - history * 1000;while (points.length && points[0].x < cutoff) points.shift();graph.requestPaint();}}Loader {id: iconLoadervisible: Config.data.network.icon.enabledwidth: sourceComponent.widthheight: sourceComponent.heightanchors.verticalCenter: parent.verticalCentersourceComponent: Network.networkType === Types.Network.Wireless ?wirelessIcon :(Network.networkType === Types.Network.Wired ? wiredIcon : virtualIcon)}Component {id: wiredIconNetworkWiredIcon {color: Config.data.network.icon.colorscale: Config.data.network.icon.scale * root.heightanchors.verticalCenter: parent.verticalCenter}}Component {id: wirelessIconNetworkWirelessIcon {color: Config.data.network.icon.colorscale: Config.data.network.icon.scale * root.heightanchors.verticalCenter: parent.verticalCenterbars: {const thresholds = [-60, -70, -80, -90];let numBars = 3;for (let i=0; i < thresholds.length; ++i) {if (Network.signalStrength > thresholds[i]) break;numBars--;}return Math.max(numBars, 0);}}}Component {id: virtualIconNetworkVirtualIcon {color: Config.data.network.icon.colorscale: Config.data.network.icon.scale * root.heightanchors.verticalCenter: parent.verticalCenter}}Canvas {id: graphvisible: Config.data.network.graph.enabledanchors {top: parent.topbottom: parent.bottomleft: iconLoader.rightleftMargin: iconLoader.visible ? 2 : 0}implicitWidth: visible ? 100 * root.scale : 0onPaint: {const ctx = getContext("2d");ctx.clearRect(0, 0, width, height);if (points.length < 2) return;const range = history * 1000, currentTime = Date.now(), minTime = currentTime - range;const visiblePoints = points.filter(p => p.x >= minTime);const margin = 2, marginHeight = height - 2 * margin;const drawLine = function(prop, color) {ctx.strokeStyle = color;ctx.beginPath();for (let i = 0; i < visiblePoints.length; ++i) {const px = ((visiblePoints[i].x - minTime) / range) * width;const py = margin + (1 - visiblePoints[i][prop] / maxRate) * marginHeight;if (i === 0) ctx.moveTo(px, py);ctx.lineTo(px, py);}ctx.stroke();}ctx.lineWidth = 2;drawLine('up', Config.data.network.colors.tx);drawLine('down', Config.data.network.colors.rx)}property real maxRate: 1 // Computed: max of all up/down points, updated on paintonPainted: { maxRate = Math.max(...points.map(p => Math.max(p.up, p.down)), 1); }}// Return the rate value using the most appropriate unit within the given// hierarchy, starting with the given base unit.// This ensures that the rate component never displays more than 5 digits,// so that it can have a predictable max width.function getDisplayRate(rateBytes, fromUnit, baseUnit, hierarchy) {const startIndex = Math.max(0, hierarchy.indexOf(baseUnit));for (let i = startIndex; i < hierarchy.length; i++) {const unit = hierarchy[i];const rate = FileUtils.convertSizeUnit(rateBytes, fromUnit, unit);// Use this unit if rate fits OR it's the last available unitif (rate < 1000 || i === hierarchy.length - 1) {return {unit, rate};}}// Fallback to largest unit. Shouldn't happen...const lastUnit = hierarchy[hierarchy.length - 1];return {unit: lastUnit,rate: FileUtils.convertSizeUnit(rateBytes, fromUnit, lastUnit)};}ColumnLayout {id: ratesvisible: Config.data.network.rates.enabledspacing: -4anchors {left: graph.rightleftMargin: 4verticalCenter: parent.verticalCenter}// Calculate fixed width based on maximum possible textTextMetrics {id: rateTextMetrics// The "M" in "MiB", "MB", etc. is usually the widest font glyph.text: "999.99 " + root.unitHierarchy[1] + "/s"font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.size * 0.9}RowLayout {spacing: 2ArrowIcon {id: txArrowIconscale: root.height / 2angle: 0color: Config.data.network.colors.tx}Text {text: {const display = getDisplayRate(Network.rateUp, 'B', Config.data.network.rates.baseUnit, root.unitHierarchy);return `${display.rate.toFixed(2)} ${display.unit}/s`;}font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.size * 0.9color: Config.data.network.colors.txLayout.preferredWidth: rateTextMetrics.widthhorizontalAlignment: Text.AlignRight}}RowLayout {spacing: 2ArrowIcon {id: rxArrowIconscale: root.height / 2angle: 180color: Config.data.network.colors.rx}Text {text: {const display = getDisplayRate(Network.rateDown, 'B', Config.data.network.rates.baseUnit, root.unitHierarchy);return `${display.rate.toFixed(2)} ${display.unit}/s`;}font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.size * 0.9color: Config.data.network.colors.rxLayout.preferredWidth: rateTextMetrics.widthhorizontalAlignment: Text.AlignRight}}}MouseArea {anchors.fill: parentacceptedButtons: Qt.RightButton | Qt.LeftButtononClicked: (mouse) => (mouse.button === Qt.RightButton) && menu.popup()}AutoSizingMenu {id: menupopupType: Popup.Nativetitle: "Available Interfaces"closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside | Popup.CloseOnPressOutsideParentAutoSizingMenu {title: "Select interface"// Group 1: WirelessRepeater {model: Network.interfaces.get(Types.Network.Wireless)MenuItem {contentItem: Row {spacing: 4anchors.verticalCenter: parent.verticalCenterLoader {active: truesourceComponent: wirelessIcononLoaded: {item.color = ColorUtils.withLightness(palette.windowText, 0.4);item.scale = 16;item.bars = 3;}}Text {text: modelDatacolor: palette.windowTextfont.pixelSize: Config.data.theme.font.size * 0.9}}onTriggered: {Network.setActiveInterface(modelData, Types.Network.Wireless);points = [];}}}// Separator after wireless group (only if wireless AND wired OR// virtual groups are not empty).// A Repeater must be used for the separator to be placed dynamically.Repeater {model: Network.interfaces.get(Types.Network.Wireless)?.length > 0&& (Network.interfaces.get(Types.Network.Wired)?.length > 0|| Network.interfaces.get(Types.Network.Virtual)?.length > 0)? [1] : []delegate: MenuSeparator {}}// Group 2: WiredRepeater {model: Network.interfaces.get(Types.Network.Wired)MenuItem {contentItem: Row {spacing: 4anchors.verticalCenter: parent.verticalCenterLoader {active: truesourceComponent: wiredIcononLoaded: {item.color = ColorUtils.withLightness(palette.windowText, 0.4);item.scale = 16;}}Text {text: modelDatacolor: palette.windowTextfont.pixelSize: Config.data.theme.font.size * 0.9}}onTriggered: {Network.setActiveInterface(modelData, Types.Network.Wired);points = [];}}}// Separator after wired group (only if wired AND virtual groups are// not empty).// A Repeater must be used for the separator to be placed dynamically.Repeater {model: Network.interfaces.get(Types.Network.Wired)?.length > 0&& Network.interfaces.get(Types.Network.Virtual)?.length > 0? [1] : []delegate: MenuSeparator {}}// Group 3: VirtualRepeater {model: Network.interfaces.get(Types.Network.Virtual)MenuItem {contentItem: Row {spacing: 4anchors.verticalCenter: parent.verticalCenterLoader {active: truesourceComponent: virtualIcononLoaded: {item.color = ColorUtils.withLightness(palette.windowText, 0.4);item.scale = 16;}}Text {text: modelDatacolor: palette.windowTextfont.pixelSize: Config.data.theme.font.size * 0.9}}onTriggered: {Network.setActiveInterface(modelData, Types.Network.Virtual);points = [];}}}}}HoverPopup {anchors.centerIn: roothoverTarget: rootanchorPosition: Types.stringToPosition(Config.data.bar.position)contentComponent: Component {ColumnLayout {id: contentColumnspacing: 2Text {Layout.alignment: Qt.AlignHCentertext: `Interface: ${Network.activeInterface} (${Types.networkToString(Network.networkType)})`font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizefont.bold: truecolor: Config.data.theme.colors.foreground}Text {Layout.alignment: Qt.AlignHCenterfont.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizecolor: Config.data.theme.colors.foregroundtextFormat: Text.RichTexttext: {let rows = [{label: "LAN IPs",value: Network.lanIPs.length ? Network.lanIPs.join("<br>") : "N/A",font: Config.data.theme.fontMono.family,fontSize: Config.data.theme.fontMono.size,},{label: "WAN IP",value: Network.wanIP || "N/A",font: Config.data.theme.fontMono.family,fontSize: Config.data.theme.fontMono.size,},];switch (Network.networkType) {case Types.Network.Wireless:rows = [{label: "SSID",value: Network.ssid || "N/A",font: Config.data.theme.font.family,fontSize: Config.data.theme.font.size,},{label: "Signal",value: Network.signalStrength < 0 ? Network.signalStrength + " dBm" : "N/A",font: Config.data.theme.font.family,fontSize: Config.data.theme.font.size,},{label: "Bitrate",value: function() {let out = [];if (Network.bitrateRx > 0) {let rx = getDisplayRate(Network.bitrateRx, 'Mb', 'Mb', FileUtils.unitsMetricBit);out.push(`↓ ${rx.rate} ${rx.unit}/s`);}if (Network.bitrateTx > 0) {let tx = getDisplayRate(Network.bitrateTx, 'Mb', 'Mb', FileUtils.unitsMetricBit);out.push(`↑ ${tx.rate} ${tx.unit}/s`);}if (out.length) {return out.join("<br>");}return "N/A";}(),font: Config.data.theme.font.family,fontSize: Config.data.theme.font.size,},].concat(rows);break;case Types.Network.Wired:rows = [{label: "Link speed",value: function() {if (Network.linkSpeed > 0) {let dr = getDisplayRate(Network.linkSpeed, 'Mb', 'Mb', FileUtils.unitsMetricBit);return `${dr.rate} ${dr.unit}/s`;}return "N/A";}(),font: Config.data.theme.font.family,fontSize: Config.data.theme.font.size,},].concat(rows);break;}let createRow = function(rowData) {return `<tr><td align="left" width="60">${rowData.label}:</td><td align="left" width="150"><span style="font-family: '${rowData.font}'; font-size: ${rowData.fontSize}px;">${rowData.value}</span></td></tr>`;};return `<table>${rows.map(r => createRow(r)).join("")}</table>`;}}}}}}
import QtQuickimport QtQuick.Layoutsimport Quickshellimport qs.modules.commonRowLayout {id: rootrequired property string sectionproperty var widgetComponentsspacing: Config.data.layout[section]?.spacing || 0readonly property var widgetModel: {let model = [];let hasPreviousWidget = false;const widgets = Config.data.layout[section]?.widgets || [];const useSeparator = Config.data.layout[section]?.separator || false;for (let i = 0; i < widgets.length; i++) {const widget = widgetComponents[widgets[i]];if (!widget) {console.error(`invalid widget: ${widgets[i]}`);continue;}if (useSeparator && hasPreviousWidget) {model.push(widgetComponents["separator"]);}model.push(widget);hasPreviousWidget = true;}return model;}Repeater {model: root.widgetModeldelegate: Loader {active: truesourceComponent: modelData}}}
import QtQuickimport qs.modules.commonimport qs.servicesRow {spacing: 4Image {anchors.verticalCenter: parent.verticalCentersource: Niri.focusedWindow?.iconPath ? "file://" + Niri.focusedWindow?.iconPath : ""sourceSize.width: Config.data.focusedWindow.icon.scale * Config.data.theme.widget.sizesourceSize.height: Config.data.focusedWindow.icon.scale * Config.data.theme.widget.sizevisible: Config.data.focusedWindow.icon.enabled && Niri.focusedWindow?.iconPath !== ""smooth: true}// Fallback for missing iconsRectangle {anchors.verticalCenter: parent.verticalCenterwidth: Config.data.focusedWindow.icon.scale * Config.data.theme.widget.sizeheight: Config.data.focusedWindow.icon.scale * Config.data.theme.widget.sizecolor: "#CCC"visible: Config.data.focusedWindow.icon.enabled && Niri.focusedWindow?.iconPath === ""radius: 12}Text {// FIXME: Make the width proportional to the width of the container.width: 1000elide: Text.ElideRightanchors.verticalCenter: parent.verticalCentertext: Niri.focusedWindow?.title ?? ""font.family: Config.data.focusedWindow.font.family || Config.data.theme.font.familyfont.pixelSize: Config.data.focusedWindow.font.scale * Config.data.theme.font.sizefont.weight: Config.data.focusedWindow.font.weightcolor: Config.data.theme.colors.textvisible: Config.data.focusedWindow.title.enabled}}
import QtQuickimport QtQuick.Layoutsimport Quickshellimport qs.modules.common// Clock component that displays time and date in a vertically stacked// layout, with both blocks horizontally centered along the same axis. The width// and height of the blocks is dynamically determined to avoid layout shifting// with proportional fonts as digits change, and to make the shorter block// slightly larger to compensate.Item {id: rootproperty real size: 1SystemClock {id: clockprecision: SystemClock.Seconds}implicitWidth: contentItem.implicitWidthimplicitHeight: size// Fixed-width blocks used as width references for the actual time and date blocks.// The reference isn't perfect, but should look good in most cases.TextMetrics {id: timeMetricsfont: timeBlock.fonttext: Qt.formatDateTime(new Date(2000, 12, 30, 23, 59, 59),Config.data.clock.time.format || "hh:mm")}TextMetrics {id: dateMetricsfont: dateBlock.fonttext: Qt.formatDateTime(new Date(2000, 12, 30),Config.data.clock.date.format || "yyyy-MM-dd")}ColumnLayout {id: contentItemanchors.centerIn: parentwidth: Math.max(timeBlock.Layout.preferredWidth, dateBlock.Layout.preferredWidth)height: parent.heightspacing: gapSizeText {id: timeBlockLayout.alignment: Qt.AlignHCenterLayout.preferredHeight: (root.size - gapSize) / 2Layout.preferredWidth: timeMetrics.advanceWidthtext: Qt.formatDateTime(clock.date, Config.data.clock.time.format || "hh:mm")font.family: Config.data.clock.font.family || Config.data.theme.font.familyfont.pixelSize: baseFontSize * timeScalefont.weight: Config.data.clock.font.weightcolor: Config.data.theme.colors.textMutedvisible: Config.data.clock.time.enabled !== falseverticalAlignment: Text.AlignVCenter}Text {id: dateBlockLayout.alignment: Qt.AlignHCenterLayout.preferredHeight: (root.size - gapSize) / 2Layout.preferredWidth: dateMetrics.advanceWidthtext: Qt.formatDateTime(clock.date, Config.data.clock.date.format || "yyyy-MM-dd")font.family: Config.data.clock.font.family || Config.data.theme.font.familyfont.pixelSize: baseFontSize * dateScalefont.weight: Config.data.clock.font.weightcolor: Config.data.theme.colors.textMutedvisible: Config.data.clock.date.enabled !== falseverticalAlignment: Text.AlignVCenter}}readonly property real baseFontSize: {(timeBlock.visible && dateBlock.visible ? 0.5 : 1)* size * Config.data.clock.font.scale}readonly property real maxBlockHeight: size - gapSizereadonly property real gapSize: size * 0.1property real timeScale: 1property real dateScale: 1Component.onCompleted: {if (!timeBlock.visible || !dateBlock.visible) return;const timeWidth = timeBlock.contentWidth;const dateWidth = dateBlock.contentWidth;// Give narrower block a boostlet targetTimeScale = dateWidth > timeWidth ? 1.3 : 1;let targetDateScale = timeWidth > dateWidth ? 1.3 : 1;timeScale = targetTimeScale;dateScale = targetDateScale;// Scale down if either block is too tallif (timeBlock.contentHeight > maxBlockHeight) {timeScale *= maxBlockHeight / timeBlock.contentHeight;}if (dateBlock.contentHeight > maxBlockHeight) {dateScale *= maxBlockHeight / dateBlock.contentHeight;}// Ensure scaled block doesn't exceed the widest block's widthconst maxWidth = Math.max(timeWidth, dateWidth);if (timeBlock.contentWidth > maxWidth) {timeScale *= maxWidth / timeBlock.contentWidth;}if (dateBlock.contentWidth > maxWidth) {dateScale *= maxWidth / dateBlock.contentWidth;}}}
import QtQuickimport QtQuick.Layoutsimport Quickshellimport qs.modules.commonimport qs.modules.common.utilsimport qs.modules.common.widgetsimport qs.modules.iconsimport qs.servicesItem {id: rootproperty real scale: Config.data.cpu.scale || 1.0property real history: Config.data.cpu.graph.history || 60 // Secondsproperty real updateInterval: Config.data.cpu.updateInterval || 1000 // Millisecondsproperty color lineColor: Config.data.cpu.graph.lineColorproperty color lowUsageColor: Config.data.cpu.graph.lowUsageColorproperty color highUsageColor: Config.data.cpu.graph.highUsageColorreadonly property color mixedUsageColor: ColorUtils.mix(lowUsageColor, highUsageColor)implicitWidth: (icon.visible ? icon.width : 0)+ (graph.visible ? graph.width : 0) + 4implicitHeight: Config.data.bar.size - Config.data.bar.size * 0.2// Buffer for graph points (rolling window)property var points: []// Initial dummy point to avoid empty graphComponent.onCompleted: {if (!graph.visible) return;points.push({ x: Date.now(), y: 0.0 });graph.requestPaint();}Timer {interval: updateIntervalrunning: Config.data.cpu.graph.enabledrepeat: trueonTriggered: updateGraph()}function updateGraph() {// Add new pointlet x = Date.now();let y = CPU.overallUsage;points.push({ x: x, y: y });// Clean old points beyond historylet cutoff = Date.now() - history * 1000;while (points.length > 0 && points[0].x < cutoff) {points.splice(0, 1);}graph.requestPaint();}CPUIcon {id: iconvisible: Config.data.cpu.icon.enabledcolor: Config.data.cpu.icon.colorscale: Config.data.cpu.icon.scale * root.heightanchors.verticalCenter: parent.verticalCenter}Canvas {id: graphvisible: Config.data.cpu.graph.enabledanchors {top: parent.topbottom: parent.bottomright: parent.right}width: 100 * root.scaleonPaint: {let ctx = getContext("2d");ctx.clearRect(0, 0, width, height);let range = history * 1000;let currentTime = Date.now();let minTime = currentTime - range;let visiblePoints = points.filter(function(p) { return p.x >= minTime; });if (visiblePoints.length < 2) return;// Margin to avoid graph cutoff at top/bottomlet margin = 2;let effectiveHeight = height - 2 * margin;ctx.strokeStyle = lineColor;ctx.lineWidth = 2;let gradient = ctx.createLinearGradient(0, height, 0, 0); // Vertical from bottom to topgradient.addColorStop(0, lowUsageColor);gradient.addColorStop(0.5, mixedUsageColor);gradient.addColorStop(0.9, highUsageColor);ctx.fillStyle = gradient;// Draw fill under the line first (closed path)ctx.beginPath();ctx.moveTo(0, height+10); // Start bottom-leftfor (let i = 0; i < visiblePoints.length; ++i) {let px = ((visiblePoints[i].x - minTime) / range) * width;let py = margin + (1 - visiblePoints[i].y) * effectiveHeight;if (i === 0) ctx.lineTo(px, py);ctx.lineTo(px, py);}ctx.lineTo(width, height); // Bottom-rightctx.closePath();ctx.fill();// Draw stroke on top of the line (separate path)ctx.beginPath();for (let i = 0; i < visiblePoints.length; ++i) {let px = ((visiblePoints[i].x - minTime) / range) * width;let py = margin + (1 - visiblePoints[i].y) * effectiveHeight;if (i === 0) ctx.moveTo(px, py);ctx.lineTo(px, py);}ctx.stroke();}}HoverPopup {anchors.centerIn: roothoverTarget: rootanchorPosition: Types.stringToPosition(Config.data.bar.position)contentComponent: Component {ColumnLayout {id: contentColumnspacing: 2Text {Layout.alignment: Qt.AlignHCentertext: "Load Average"font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizefont.bold: truecolor: Config.data.theme.colors.foreground}Text {Layout.alignment: Qt.AlignHCenterfont.family: Config.data.theme.fontMono.familyfont.pixelSize: Config.data.theme.fontMono.sizecolor: Config.data.theme.colors.foregroundtext: {let names = ['1m', '5m', '15m'];let out = [];for (let i = 0; i < CPU.loadAvg.length; ++i) {let name = `${names[i]}:`.padStart(5).padEnd(6);let val = CPU.loadAvg[i].toFixed(2);out.push(`${name}${val}`);}return out.join("\n")}}Text {Layout.alignment: Qt.AlignHCentertext: "CPU Core Usage"font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizefont.bold: truecolor: Config.data.theme.colors.foregroundLayout.topMargin: 6}Text {Layout.alignment: Qt.AlignHCenterfont.family: Config.data.theme.fontMono.familyfont.pixelSize: Config.data.theme.fontMono.sizecolor: Config.data.theme.colors.foregroundtext: {let out = [];for (let i = 0; i < CPU.coreUsages.length; ++i) {let coreNum = `Core ${i+1}:`.padEnd(10);let coreUsage = CPU.coreUsages[i].toFixed(2);out.push(`${coreNum}${coreUsage}`);}return out.join("\n");}}Text {Layout.alignment: Qt.AlignHCentertext: "Top Processes"font.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizefont.bold: truecolor: Config.data.theme.colors.foregroundLayout.topMargin: 6}Text {Layout.alignment: Qt.AlignHCenterfont.family: Config.data.theme.fontMono.familyfont.pixelSize: Config.data.theme.fontMono.sizecolor: Config.data.theme.colors.foregroundtext: {let out = [];for (let p of CPU.topProcesses) {let pidStr = p.pid.toString().padStart(6);let commStr = p.comm.slice(0, 15).padEnd(15);let cpuStr = `${p.cpu.toFixed(1)}%`;out.push(`${pidStr} ${commStr}\t${cpuStr.padStart(6)}`);}return out.join("\n");}}}}}}
import QtQuickimport QtQuick.Layoutsimport Qt5Compat.GraphicalEffectsimport Quickshellimport Quickshell.Services.UPowerimport qs.modules.commonimport qs.modules.common.widgetsimport qs.modules.iconsimport qs.servicesItem {id: rootproperty int orientation: Types.Orientation.Horizontalreadonly property var chargeState: Battery.chargeStatereadonly property bool isCharging: Battery.isChargingreadonly property bool isPluggedIn: Battery.isPluggedInreadonly property real percentage: Battery.percentagereadonly property bool isLow: percentage <= Config.data.battery.low / 100readonly property bool isCritical: percentage <= Config.data.battery.critical / 100implicitWidth: icon.widthimplicitHeight: icon.heightProgressBarText {id: batteryProgressanchors {left: icon.leftbottom: icon.bottom}valueBarWidth: icon.bodyWidthvalueBarHeight: icon.bodyHeightvalue: percentagetext: Config.data.battery.showPercentage ? Math.round(value * 100) : ""orientation: root.orientationshimmer: isChargingpulse: isCharginghighlightColor: (() => {if (isCritical && !isCharging) {return Config.data.theme.colors.error;}if (isLow && !isCharging) {return Config.data.theme.colors.warning;}return Config.data.theme.colors.ok;})()font.family: "Noto Sans"font.bold: truefont.pixelSize: Config.data.theme.font.size * Config.data.battery.scale * 0.7textColor: Config.data.theme.colors.foreground// Clip the progress bar within the borders of the battery icon bodylayer.enabled: truelayer.effect: OpacityMask {maskSource: Item {width: batteryProgress.widthheight: batteryProgress.heightRectangle {x: icon.borderWidthy: icon.borderWidthwidth: icon.bodyWidth - icon.borderWidth * 2height: icon.bodyHeight - icon.borderWidth * 2radius: icon.bodyRadius / 2}}}}BatteryIcon {id: iconanchors.centerIn: parentsize: Math.min(Config.data.battery.scale * Config.data.theme.widget.size,Config.data.bar.size)iconColor: Config.data.theme.colors.foregroundorientation: root.orientation}HoverPopup {anchors.centerIn: iconhoverTarget: iconanchorPosition: Types.stringToPosition(Config.data.bar.position)contentComponent: Component {ColumnLayout {id: contentColumnspacing: 4Text {text: {if (Battery.energyRate > 0) {const status = (() => {if (Battery.isCharging) return "Charging";if (Battery.chargeState == UPowerDeviceState.Discharging) return "Discharging";return "Unknown";})();return status + ": " + Battery.energyRate.toFixed(1) + "W";} else {const state = Battery.chargeState;if (state == UPowerDeviceState.FullyCharged) return "Fully charged";if (state == UPowerDeviceState.PendingCharge) return "Plugged in (not charging)";if (state == UPowerDeviceState.Discharging) return "On battery";return "Unknown";}}color: Config.data.theme.colors.foregroundfont.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.size}Text {text: {if (Battery.isCharging) {const hours = Math.floor(Battery.timeToFull / 3600);const minutes = Math.floor((Battery.timeToFull % 3600) / 60);return "Time to full: " + hours + "h " + minutes + "m";} else {const hours = Math.floor(Battery.timeToEmpty / 3600);const minutes = Math.floor((Battery.timeToEmpty % 3600) / 60);return "Time remaining: " + hours + "h " + minutes + "m";}}color: Config.data.theme.colors.foregroundfont.family: Config.data.theme.font.familyfont.pixelSize: Config.data.theme.font.sizevisible: Battery.energyRate > 0 && ((Battery.isCharging && Battery.timeToFull > 0) || (!Battery.isCharging && Battery.timeToEmpty > 0))}}}}}
import QtQuickimport QtQuick.Effectsimport QtQuick.Layoutsimport Quickshellimport Quickshell.Waylandimport qs.modules.commonimport qs.modules.common.utilsimport qs.modules.iconsItem {id: rootproperty int position: Types.Position.Topproperty string color: "gray"property int size: 30WlrLayershell {id: barShadowimplicitHeight: bar.height + 100color: "transparent"layer: WlrLayer.BottomexclusionMode: ExclusionMode.Ignoreanchors: bar.anchorsRectangle {color: barContent.coloranchors {top: root.position === Types.Position.Top ? parent.top : undefinedbottom: root.position === Types.Position.Bottom ? parent.bottom : undefined}height: barContent.height// The +40 here and the -20 shadowHorizontalOffset are to have the// shadow extend all the way along the edge. Otherwise it would be// slightly cut off at the left and right corners.width: parent.width + 40layer.enabled: truelayer.effect: MultiEffect {shadowEnabled: true// The vertical offset makes the shadow slightly more prominentshadowVerticalOffset: root.position === Types.Position.Top ? 5 : -5shadowHorizontalOffset: -20shadowBlur: 1blurMultiplier: 1shadowColor: "#D0000000"}}}PanelWindow {id: barimplicitHeight: root.sizecolor: "transparent"anchors {top: root.position === Types.Position.Topbottom: root.position === Types.Position.Bottomleft: trueright: true}// Widget component definitionsComponent {id: separatorComponentSeparatorIcon {color: ColorUtils.transparentize(Config.data.theme.colors.foreground2, 0.5)angle: 90length: bar.height - bar.height * 0.4strokeSize: 4spacing: 1.5lineType: "dotted"dashLength: 1edgeRadius: 4Layout.alignment: Qt.AlignVCenter}}Component { id: workspacesComponent; Workspaces {} }Component { id: focusedWindowComponent; FocusedWindow {} }Component { id: cpuComponent; CPU {} }Component { id: ramComponent; RAM {} }Component { id: networkComponent; Network {} }Component {id: batteryComponentBattery {orientation: Types.stringToOrientation(Config.data.battery.orientation)}}Component {id: clockComponentClock {size: Math.min(root.size * Config.data.clock.scale - root.size * 0.2,root.size,)}}readonly property var widgetComponents: {"battery": batteryComponent,"clock": clockComponent,"cpu": cpuComponent,"focusedWindow": focusedWindowComponent,"network": networkComponent,"ram": ramComponent,"separator": separatorComponent,"workspaces": workspacesComponent,}Rectangle {id: barContentanchors.fill: parentcolor: root.colorLayoutSection {section: "left"widgetComponents: bar.widgetComponentsanchors {verticalCenter: parent.verticalCenterleft: parent.leftleftMargin: 6}}LayoutSection {section: "center"widgetComponents: bar.widgetComponentsanchors {horizontalCenter: parent.horizontalCenterverticalCenter: parent.verticalCenter}}LayoutSection {section: "right"widgetComponents: bar.widgetComponentsanchors {verticalCenter: parent.verticalCenterright: parent.rightrightMargin: 6}}}}}
"flake-parts": {"inputs": {"nixpkgs-lib": "nixpkgs-lib"},"locked": {"lastModified": 1762040540,"narHash": "sha256-z5PlZ47j50VNF3R+IMS9LmzI5fYRGY/Z5O5tol1c9I4=","owner": "hercules-ci","repo": "flake-parts","rev": "0010412d62a25d959151790968765a70c436598b","type": "github"},"original": {"owner": "hercules-ci","repo": "flake-parts","type": "github"}},
"type": "github"}},"nixpkgs": {"locked": {"lastModified": 1761236834,"narHash": "sha256-+pthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE+fV2Q=","owner": "nixos","repo": "nixpkgs","rev": "d5faa84122bc0a1fd5d378492efce4e289f8eac1","type": "github"},"original": {"owner": "nixos","ref": "nixpkgs-unstable","repo": "nixpkgs",
"nixpkgs-lib": {"locked": {"lastModified": 1761765539,"narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=","owner": "nix-community","repo": "nixpkgs.lib","rev": "719359f4562934ae99f5443f20aa06c2ffff91fc","type": "github"},"original": {"owner": "nix-community","repo": "nixpkgs.lib","type": "github"}},
"qml-niri": {"inputs": {"flake-parts": "flake-parts","nixpkgs": ["os"],"quickshell": ["quickshell"],"treefmt-nix": "treefmt-nix_2"},"locked": {"lastModified": 1766224085,"narHash": "sha256-hZbaqww++6dh2324Us3h868BZWDAzZViiB4c6/i/BDo=","owner": "imiric","repo": "qml-niri","rev": "39e9c83d908318032ccd5330b1ad77764b498eb8","type": "github"},"original": {"owner": "imiric","ref": "main","repo": "qml-niri","type": "github"}},"quickshell": {"inputs": {"nixpkgs": ["os"]},"locked": {"lastModified": 1768549203,"narHash": "sha256-DxN7v8g8DO8gGJmgBJMo3fsSR3HEs+DFCXeKeHq61zA=","ref": "refs/heads/master","rev": "d03c59768c680f052dff6e7a7918bbf990b0f743","revCount": 727,"type": "git","url": "https://git.outfoxxed.me/outfoxxed/quickshell"},"original": {"type": "git","url": "https://git.outfoxxed.me/outfoxxed/quickshell"}},
"type": "github"},"original": {"owner": "numtide","repo": "treefmt-nix","type": "github"}},"treefmt-nix_2": {"inputs": {"nixpkgs": "nixpkgs"},"locked": {"lastModified": 1762410071,"narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=","owner": "numtide","repo": "treefmt-nix","rev": "97a30861b13c3731a84e09405414398fbf3e109f",