import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Services.Mpris import Quickshell.Services.Pipewire import Quickshell.Services.SystemTray import Quickshell.Wayland import Quickshell.Widgets Item { id: bar property date currentTime: new Date() property var controlCenter property var settings property var notificationCenter property var mediaPopup property var toggleMedia: null property var workspaces: [] property string windowTitle: "Desktop" property string language: "EN" property string weather: "N/A" property string gpu: "GPU N/A" property string gpuTemperature: "--" property string mail: "" property int cpuUsage: 0 property int memoryUsage: 0 property string diskUsage: "--" property string temperature: "--" property var audioSink: Pipewire.defaultAudioSink property var mediaPlayer: Mpris.players.values.find(player => player.playbackState === MprisPlaybackState.Playing) || Mpris.players.values[0] function start(process) { if (!process.running) process.running = true; } function focusWorkspace(index) { Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", String(index)]); } function parseJson(collector, fallback) { try { const value = JSON.parse(collector.text); return value === null ? fallback : value; } catch (error) { return fallback; } } PanelWindow { id: barWindow anchors { top: true left: true right: true } implicitHeight: 40 exclusiveZone: 40 color: "transparent" WlrLayershell.layer: WlrLayer.Top WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.namespace: "quickshell-bar" Rectangle { anchors.fill: parent anchors.topMargin: 4 anchors.leftMargin: 4 anchors.rightMargin: 4 anchors.bottomMargin: 0 radius: 14 color: "#1e1e2e" border.color: "#313244" border.width: 1 RowLayout { anchors.fill: parent anchors.leftMargin: 6 anchors.rightMargin: 6 spacing: 6 RowLayout { id: workspaceRow spacing: 3 Repeater { model: bar.workspaces delegate: Rectangle { required property var modelData implicitWidth: workspaceLabel.implicitWidth + 16 implicitHeight: 26 radius: 10 color: modelData.is_focused ? "#89b4fa" : (modelData.is_active ? "#45475a" : "#313244") Label { id: workspaceLabel anchors.centerIn: parent text: modelData.name || String(modelData.idx) color: modelData.is_focused ? "#1e1e2e" : "#cdd6f4" font.pixelSize: 15 font.bold: true } MouseArea { anchors.fill: parent onClicked: bar.focusWorkspace(modelData.idx) } } } } Item { Layout.fillWidth: true } Label { text: bar.windowTitle color: "#cdd6f4" font.pixelSize: 15 elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter Layout.maximumWidth: 420 Layout.fillWidth: true } Item { Layout.fillWidth: true } RowLayout { spacing: 4 Label { text: bar.weather color: "#89dceb" font.pixelSize: 15 ToolTip.visible: weatherMouse.containsMouse ToolTip.text: "Weather" MouseArea { id: weatherMouse anchors.fill: parent } } Label { visible: bar.mail.length > 0 text: bar.mail color: "#b4befe" font.pixelSize: 15 leftPadding: 19 BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: mailMouse.containsMouse ToolTip.text: "Unread mail" MouseArea { id: mailMouse anchors.fill: parent onClicked: Quickshell.execDetached(["thunderbird"]) } } Label { visible: bar.mediaPlayer !== null text: bar.mediaPlayer ? (bar.mediaPlayer.trackTitle || bar.mediaPlayer.identity) : "" color: "#f5c2e7" leftPadding: 19 elide: Text.ElideRight Layout.maximumWidth: 190 BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: mediaMouse.containsMouse ToolTip.text: "Open media controls" MouseArea { id: mediaMouse anchors.fill: parent onClicked: { if (bar.toggleMedia) bar.toggleMedia(); } } } Label { visible: !!bar.notificationCenter && bar.notificationCenter.count > 0 text: bar.notificationCenter ? String(bar.notificationCenter.count) : "" color: "#f38ba8" leftPadding: 19 BarIcon { name: "mail"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: notificationMouse.containsMouse ToolTip.text: "Open notifications" MouseArea { id: notificationMouse anchors.fill: parent onClicked: { if (bar.notificationCenter) bar.notificationCenter.open = !bar.notificationCenter.open; } } } Label { text: bar.gpu color: "#a6e3a1" font.pixelSize: 15 leftPadding: 19 BarIcon { name: "gpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: gpuMouse.containsMouse ToolTip.text: "GPU usage" MouseArea { id: gpuMouse anchors.fill: parent } } Label { text: bar.cpuUsage + "%" color: "#a6e3a1" font.pixelSize: 15 leftPadding: 19 BarIcon { name: "cpu"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: cpuMouse.containsMouse ToolTip.text: "CPU usage" MouseArea { id: cpuMouse; anchors.fill: parent } } Label { text: bar.memoryUsage + "%" color: "#cba6f7" font.pixelSize: 15 leftPadding: 19 BarIcon { name: "memory"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: memoryMouse.containsMouse ToolTip.text: "Memory usage" MouseArea { id: memoryMouse; anchors.fill: parent } } Label { text: bar.diskUsage color: "#f38ba8" font.pixelSize: 15 leftPadding: 19 BarIcon { name: "disk"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: diskMouse.containsMouse ToolTip.text: "Root filesystem" MouseArea { id: diskMouse; anchors.fill: parent } } Label { text: bar.language color: "#94e2d5" font.pixelSize: 15 leftPadding: 19 BarIcon { name: "language"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: languageMouse.containsMouse ToolTip.text: "Keyboard layout" MouseArea { id: languageMouse; anchors.fill: parent } } Label { text: bar.audioSink && bar.audioSink.audio ? Math.round(bar.audioSink.audio.volume * 100) + "%" : "--" color: "#f9e2af" font.pixelSize: 15 leftPadding: 19 BarIcon { name: "audio"; anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter } ToolTip.visible: audioMouse.containsMouse ToolTip.text: "Audio output" MouseArea { id: audioMouse anchors.fill: parent onClicked: { if (bar.audioSink && bar.audioSink.audio) bar.audioSink.audio.muted = !bar.audioSink.audio.muted; } } } Label { text: Qt.formatDateTime(bar.currentTime, "dd.MM HH:mm") color: "#cdd6f4" font.pixelSize: 15 ToolTip.visible: clockMouse.containsMouse ToolTip.text: "Open calendar" MouseArea { id: clockMouse anchors.fill: parent onClicked: Quickshell.execDetached(["sh", "-c", "alacritty --title khal-popup -e bash -c 'vdirsyncer sync 2>/dev/null; ikhal'"]) } } Label { text: "" color: "#89b4fa" Layout.preferredWidth: 18 Layout.minimumWidth: 18 Layout.maximumWidth: 18 BarIcon { name: "gear"; size: 18; anchors.centerIn: parent } ToolTip.visible: settingsMouse.containsMouse ToolTip.text: "Quick settings" MouseArea { id: settingsMouse anchors.fill: parent onClicked: { if (bar.controlCenter) bar.controlCenter.togglePanel(); } } } Repeater { model: SystemTray.items delegate: Item { required property var modelData property bool hiddenTrayItem: !modelData || /wayland to x11 video bridge|screen sharing|screen share/i.test(String(modelData.title || "")) || /video-display|screen-share|screen-sharing/i.test(String(modelData.icon || "")) visible: !hiddenTrayItem implicitWidth: hiddenTrayItem ? 0 : 20 implicitHeight: 22 Loader { id: trayIcon anchors.fill: parent active: !hiddenTrayItem && modelData sourceComponent: Component { IconImage { anchors.fill: parent implicitSize: 18 source: modelData && modelData.icon && modelData.icon.startsWith("image://") ? modelData.icon : (modelData ? Quickshell.iconPath(modelData.icon) : "") } } } BarIcon { visible: !hiddenTrayItem && !trayIcon.active anchors.fill: parent enabled: !hiddenTrayItem name: "video" size: 15 anchors.margins: 2 } MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: function(mouse) { if (!modelData) return; if (mouse.button === Qt.RightButton && modelData.hasMenu) { modelData.display(barWindow, mouse.x, mouse.y + 20); } else { modelData.activate(); } } } } } } } } } PwObjectTracker { objects: [bar.audioSink] } Process { id: workspaceReader command: ["niri", "msg", "-j", "workspaces"] stdout: StdioCollector { onStreamFinished: { const value = bar.parseJson(this, []); if (Array.isArray(value)) { value.sort((left, right) => left.idx - right.idx); bar.workspaces = value; } } } } Process { id: windowReader command: ["niri", "msg", "-j", "focused-window"] stdout: StdioCollector { onStreamFinished: { const value = bar.parseJson(this, {}); bar.windowTitle = value.title || "Desktop"; } } } Process { id: languageReader command: ["niri", "msg", "-j", "keyboard-layouts"] stdout: StdioCollector { onStreamFinished: { const value = bar.parseJson(this, {}); const index = Number(value.current_idx); const name = Array.isArray(value.names) && index >= 0 ? String(value.names[index] || "") : ""; const normalized = name.toLowerCase(); if (normalized.includes("russian") || normalized.includes("рус")) bar.language = "RU"; else if (normalized.includes("english") || normalized.includes("англ")) bar.language = "EN"; else if (name.length > 0) bar.language = name.slice(0, 2).toUpperCase(); } } } Process { id: cpuReader command: ["sh", "-c", "awk '/^cpu / { idle=$5; total=0; for (i=2; i<=NF; i++) total += $i; print int((total-idle)*100/total) }' /proc/stat"] stdout: StdioCollector { onStreamFinished: bar.cpuUsage = parseInt(text.trim()) || 0 } } Process { id: memoryReader command: ["sh", "-c", "free | awk '/^Mem:/ { print int($3*100/$2) }'"] stdout: StdioCollector { onStreamFinished: bar.memoryUsage = parseInt(text.trim()) || 0 } } Process { id: diskReader command: ["sh", "-c", "df -P / | awk 'NR==2 { print $5 }'"] stdout: StdioCollector { onStreamFinished: bar.diskUsage = text.trim() || "--" } } Process { id: temperatureReader command: ["sh", "-c", "max=0; for file in /sys/class/thermal/thermal_zone*/temp; do value=$(cat $file 2>/dev/null); [ $value -gt $max ] 2>/dev/null && max=$value; done; if [ $max -gt 0 ]; then echo $((max / 1000))C; else echo --; fi"] stdout: StdioCollector { onStreamFinished: bar.temperature = text.trim() || "--" } } Process { id: gpuReader command: ["sh", "-c", "nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1"] stdout: StdioCollector { onStreamFinished: { const parts = text.trim().split(","); const usage = parseInt((parts[0] || "").trim()); const temperature = parseInt((parts[1] || "").trim()); bar.gpu = isNaN(usage) ? "N/A" : usage + "%"; bar.gpuTemperature = isNaN(temperature) ? "--" : temperature + "C"; } } } Process { id: weatherReader command: ["bash", Quickshell.shellPath("../scripts/weather.sh")] stdout: StdioCollector { onStreamFinished: { const value = bar.parseJson(this, {}); bar.weather = value.text || "N/A"; } } } Process { id: mailReader command: ["sh", "-c", "if [ -x $HOME/.local/bin/mail-counter ]; then $HOME/.local/bin/mail-counter; else printf '{}'; fi"] stdout: StdioCollector { onStreamFinished: { const value = bar.parseJson(this, {}); bar.mail = value.text || ""; } } } Timer { id: refreshTimer interval: bar.settings ? bar.settings.temperatureInterval : 2000 running: true repeat: true onTriggered: { bar.currentTime = new Date(); bar.start(windowReader); bar.start(cpuReader); bar.start(memoryReader); bar.start(diskReader); bar.start(temperatureReader); bar.start(gpuReader); bar.start(mailReader); } } Timer { id: weatherTimer interval: 900000 running: true repeat: true onTriggered: bar.start(weatherReader) } Timer { id: languageTimer interval: 200 running: true repeat: true onTriggered: bar.start(languageReader) } Timer { id: workspaceTimer interval: 100 running: true repeat: true onTriggered: bar.start(workspaceReader) } Component.onCompleted: { bar.start(workspaceReader); bar.start(windowReader); bar.start(languageReader); bar.start(cpuReader); bar.start(memoryReader); bar.start(diskReader); bar.start(temperatureReader); bar.start(gpuReader); bar.start(weatherReader); bar.start(mailReader); } }