diff options
Diffstat (limited to 'quickshell/shell.qml')
| -rw-r--r-- | quickshell/shell.qml | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/quickshell/shell.qml b/quickshell/shell.qml new file mode 100644 index 0000000..1aa81e5 --- /dev/null +++ b/quickshell/shell.qml @@ -0,0 +1,443 @@ +import Quickshell +import Quickshell.Wayland +import Quickshell.Io +import Quickshell.Hyprland +import QtQuick +import QtQuick.Layouts + +ShellRoot { + id: root + + // Theme colors + property color colBg: "#1a1b26" + property color colFg: "#a9b1d6" + property color colMuted: "#444b6a" + property color colCyan: "#0db9d7" + property color colPurple: "#ad8ee6" + property color colRed: "#f7768e" + property color colYellow: "#e0af68" + property color colBlue: "#7aa2f7" + + // Font + property string fontFamily: "JetBrainsMono Nerd Font" + property int fontSize: 14 + + // System info properties + property string kernelVersion: "Linux" + property int cpuUsage: 0 + property int memUsage: 0 + property int diskUsage: 0 + property int volumeLevel: 0 + property string activeWindow: "Window" + property string currentLayout: "Tile" + property string cpuTemp: "0" + // CPU tracking + property var lastCpuIdle: 0 + property var lastCpuTotal: 0 + + // Kernel version + Process { + id: kernelProc + command: ["uname", "-r"] + stdout: SplitParser { + onRead: data => { + if (data) kernelVersion = data.trim() + } + } + Component.onCompleted: running = true + } + + // CPU usage + Process { + id: cpuProc + command: ["sh", "-c", "head -1 /proc/stat"] + stdout: SplitParser { + onRead: data => { + if (!data) return + var parts = data.trim().split(/\s+/) + var user = parseInt(parts[1]) || 0 + var nice = parseInt(parts[2]) || 0 + var system = parseInt(parts[3]) || 0 + var idle = parseInt(parts[4]) || 0 + var iowait = parseInt(parts[5]) || 0 + var irq = parseInt(parts[6]) || 0 + var softirq = parseInt(parts[7]) || 0 + + var total = user + nice + system + idle + iowait + irq + softirq + var idleTime = idle + iowait + + if (lastCpuTotal > 0) { + var totalDiff = total - lastCpuTotal + var idleDiff = idleTime - lastCpuIdle + if (totalDiff > 0) { + cpuUsage = Math.round(100 * (totalDiff - idleDiff) / totalDiff) + } + } + lastCpuTotal = total + lastCpuIdle = idleTime + } + } + Component.onCompleted: running = true + } + + // Memory usage + Process { + id: memProc + command: ["sh", "-c", "free | grep Mem"] + stdout: SplitParser { + onRead: data => { + if (!data) return + var parts = data.trim().split(/\s+/) + var total = parseInt(parts[1]) || 1 + var used = parseInt(parts[2]) || 0 + memUsage = Math.round(100 * used / total) + } + } + Component.onCompleted: running = true + } + + // Disk usage + Process { + id: diskProc + command: ["sh", "-c", "df / | tail -1"] + stdout: SplitParser { + onRead: data => { + if (!data) return + var parts = data.trim().split(/\s+/) + var percentStr = parts[4] || "0%" + diskUsage = parseInt(percentStr.replace('%', '')) || 0 + } + } + Component.onCompleted: running = true + } + + // Volume level (wpctl for PipeWire) + Process { + id: volProc + command: ["wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@"] + stdout: SplitParser { + onRead: data => { + if (!data) return + var match = data.match(/Volume:\s*([\d.]+)/) + if (match) { + volumeLevel = Math.round(parseFloat(match[1]) * 100) + } + } + } + Component.onCompleted: running = true + } + + // Active window title + Process { + id: windowProc + command: ["sh", "-c", "hyprctl activewindow -j | jq -r '.title // empty'"] + stdout: SplitParser { + onRead: data => { + if (data && data.trim()) { + activeWindow = data.trim() + } + } + } + Component.onCompleted: running = true + } + + // Current layout (Hyprland: dwindle/master/floating) + Process { + id: layoutProc + command: ["sh", "-c", "hyprctl activewindow -j | jq -r 'if .floating then \"Floating\" elif .fullscreen == 1 then \"Fullscreen\" else \"Tiled\" end'"] + stdout: SplitParser { + onRead: data => { + if (data && data.trim()) { + currentLayout = data.trim() + } + } + } + Component.onCompleted: running = true + } + Process { + id: cpuTempProc + command: ["sh", "-c", "sensors | awk '/Tctl:/ {print $2}'"] + stdout: SplitParser { + onRead: data => { + if (data && data.length > 0) { + cpuTemp = data.trim() + } + } + } + } + + + + // Slow timer for system stats + Timer { + interval: 2000 + running: true + repeat: true + onTriggered: { + cpuProc.running = true + memProc.running = true + diskProc.running = true + volProc.running = true + cpuTempProc.running = true + } + } + + // Event-based updates for window/layout (instant) + Connections { + target: Hyprland + function onRawEvent(event) { + windowProc.running = true + layoutProc.running = true + } + } + + // Backup timer for window/layout (catches edge cases) + Timer { + interval: 200 + running: true + repeat: true + onTriggered: { + windowProc.running = true + layoutProc.running = true + } + } + + Variants { + model: Quickshell.screens + + PanelWindow { + property var modelData + screen: modelData + + anchors { + top: true + left: true + right: true + } + + implicitHeight: 30 + color: root.colBg + + margins { + top: 0 + bottom: 0 + left: 0 + right: 0 + } + + Rectangle { + anchors.fill: parent + color: root.colBg + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { width: 8 } + + Rectangle { + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + color: "transparent" + + Image { + anchors.fill: parent + source: "file:///home/subh/.config/quickshell/icons/arch.png" + fillMode: Image.PreserveAspectFit + } + } + + Item { width: 8 } + + Repeater { + model: 9 + + Rectangle { + Layout.preferredWidth: 20 + Layout.preferredHeight: parent.height + color: "transparent" + + property var workspace: Hyprland.workspaces.values.find(ws => ws.id === index + 1) ?? null + property bool isActive: Hyprland.focusedWorkspace?.id === (index + 1) + property bool hasWindows: workspace !== null + + Text { + text: index + 1 + color: parent.isActive ? root.colCyan : (parent.hasWindows ? root.colCyan : root.colMuted) + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + anchors.centerIn: parent + } + + Rectangle { + width: 20 + height: 3 + color: parent.isActive ? root.colPurple : root.colBg + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + } + + MouseArea { + anchors.fill: parent + onClicked: Hyprland.dispatch("workspace " + (index + 1)) + } + } + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.preferredHeight: 16 + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 8 + Layout.rightMargin: 8 + color: root.colMuted + } + + Text { + text: currentLayout + color: root.colFg + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + Layout.leftMargin: 5 + Layout.rightMargin: 5 + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.preferredHeight: 16 + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 2 + Layout.rightMargin: 8 + color: root.colMuted + } + + Text { + text: activeWindow + color: root.colPurple + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + Layout.fillWidth: true + Layout.leftMargin: 8 + elide: Text.ElideRight + maximumLineCount: 1 + } + + Text { + text: " " + cpuTemp + color: root.colRed + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + Layout.rightMargin: 8 + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.preferredHeight: 16 + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 0 + Layout.rightMargin: 8 + color: root.colMuted + } + + + + Text { + text: " " + cpuUsage + "%" + color: root.colYellow + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + Layout.rightMargin: 8 + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.preferredHeight: 16 + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 0 + Layout.rightMargin: 8 + color: root.colMuted + } + + Text { + text: " " + memUsage + "%" + color: root.colCyan + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + Layout.rightMargin: 8 + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.preferredHeight: 16 + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 0 + Layout.rightMargin: 8 + color: root.colMuted + } + + Text { + text: " " + diskUsage + "%" + color: root.colBlue + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + Layout.rightMargin: 8 + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.preferredHeight: 16 + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 0 + Layout.rightMargin: 8 + color: root.colMuted + } + + Text { + text: " " + volumeLevel + "%" + color: root.colPurple + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + Layout.rightMargin: 8 + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.preferredHeight: 16 + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 0 + Layout.rightMargin: 8 + color: root.colMuted + } + + Text { + id: clockText + text: Qt.formatDateTime(new Date(), "ddd, MMM dd - HH:mm") + color: root.colCyan + font.pixelSize: root.fontSize + font.family: root.fontFamily + font.bold: false + Layout.rightMargin: 8 + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: clockText.text = Qt.formatDateTime(new Date(), "ddd, MMM dd - HH:mm") + } + } + + Item { width: 8 } + } + } + } + } +} + |
