import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.I3 import Quickshell.Io ShellRoot { id: root property bool expanded: false property int pendingWorkspace: 0 property int barHeight: 38 property int drawerHeight: 118 property string scriptDir: Quickshell.shellPath("scripts") property string fontFamily: "FiraCode Nerd Font" property string bg: "#282828" property string bg1: "#3c3836" property string bg2: "#504945" property string bg3: "#665c54" property string fg: "#ebdbb2" property string fg1: "#d5c4a1" property string red: "#cc241d" property string green: "#98971a" property string yellow: "#d79921" property string orange: "#d65d0e" property string purple: "#b16286" property string aqua: "#689d6a" property string gray: "#928374" property string clockText: Qt.formatDateTime(new Date(), "hh:mm dd MMM") signal refreshRequested(string actionName) function statusCommand(name) { return [root.scriptDir + "/status.sh", name]; } function requestStatusRefresh(actionName) { root.refreshRequested(actionName); quickStatusRefresh.actionName = actionName; settleStatusRefresh.actionName = actionName; lateStatusRefresh.actionName = actionName; quickStatusRefresh.restart(); settleStatusRefresh.restart(); lateStatusRefresh.restart(); } function runAction(name, arg) { var cmd = [root.scriptDir + "/action.sh", name]; if (arg !== undefined && arg !== "") cmd.push(arg); Quickshell.execDetached(cmd); root.requestStatusRefresh(name); } function switchWorkspace(workspaceNumber) { root.pendingWorkspace = workspaceNumber; pendingWorkspaceReset.restart(); I3.dispatch("workspace number " + workspaceNumber); } function textColor(value, fallback) { if (value === "n/a") return root.gray; if (value.indexOf("down") === 0 || value.indexOf("muted") === 0) return root.red; if (value.indexOf("dnd") === 0 || value.indexOf("night") === 0) return root.orange; if (value === "on" || value.indexOf("up") === 0 || value === "day") return root.green; return fallback; } Timer { interval: 1000 running: true repeat: true onTriggered: root.clockText = Qt.formatDateTime(new Date(), "hh:mm dd MMM") } Timer { id: quickStatusRefresh property string actionName: "" interval: 120 onTriggered: root.refreshRequested(actionName) } Timer { id: settleStatusRefresh property string actionName: "" interval: 500 onTriggered: root.refreshRequested(actionName) } Timer { id: lateStatusRefresh property string actionName: "" interval: 1600 onTriggered: root.refreshRequested(actionName) } Timer { id: pendingWorkspaceReset interval: 700 onTriggered: root.pendingWorkspace = 0 } ListModel { id: workspaceModel ListElement { ws: 1 } ListElement { ws: 2 } ListElement { ws: 3 } ListElement { ws: 4 } ListElement { ws: 5 } ListElement { ws: 6 } ListElement { ws: 7 } ListElement { ws: 8 } ListElement { ws: 9 } ListElement { ws: 10 } } Variants { model: Quickshell.screens PanelWindow { id: bar required property var modelData readonly property bool canExpand: modelData.name === Quickshell.screens[0].name screen: modelData implicitHeight: root.barHeight exclusiveZone: root.barHeight aboveWindows: true anchors { top: true left: true right: true } Rectangle { anchors.fill: parent color: root.bg Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom height: 1 color: root.bg2 } Row { id: taskStrip anchors.centerIn: parent height: 30 spacing: 6 ToggleButton { visible: bar.canExpand } Repeater { model: workspaceModel delegate: WorkspaceButton { required property int ws workspaceNumber: ws } } } Row { id: tray anchors.right: parent.right anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter height: 28 spacing: 5 StatusPill { title: "ntf" command: root.statusCommand("dunst") actionName: "dunst" accent: root.yellow } StatusPill { title: "wake" command: root.statusCommand("awake") actionName: "awake" accent: root.orange } StatusPill { title: "wg" command: root.statusCommand("wg") actionName: "wg" accent: root.aqua } StatusPill { title: "vol" command: root.statusCommand("volume") actionName: "volume" actionArg: "mute" wheelAction: "volume" wheelUpArg: "up" wheelDownArg: "down" accent: root.green } StatusPill { title: "kbd" command: root.statusCommand("keyboard") actionName: "keyboard" accent: root.purple } ClockPill { } } } PopupWindow { id: drawer visible: root.expanded && bar.canExpand grabFocus: false anchor.window: bar anchor.rect.x: Math.max(8, Math.round((bar.width - implicitWidth) / 2)) anchor.rect.y: root.barHeight + 6 implicitWidth: Math.max(320, Math.min(760, bar.width - 24)) implicitHeight: root.drawerHeight color: "transparent" Rectangle { anchors.fill: parent radius: 7 color: root.bg1 border.width: 1 border.color: root.bg3 RowLayout { anchors.fill: parent anchors.margins: 10 spacing: 10 Flow { Layout.fillWidth: true Layout.fillHeight: true spacing: 7 StatusPill { title: "net" command: root.statusCommand("net") accent: root.green } StatusPill { title: "spd" command: root.statusCommand("netspeed") accent: root.green } StatusPill { title: "tmp" command: root.statusCommand("temp") accent: root.orange } StatusPill { title: "cpu" command: root.statusCommand("cpu") accent: root.orange } StatusPill { title: "ram" command: root.statusCommand("mem") accent: root.purple } StatusPill { title: "disk" command: root.statusCommand("disk") accent: root.aqua } StatusPill { title: "upd" command: root.statusCommand("updates") actionName: "updates" interval: 900000 accent: root.yellow } StatusPill { title: "disp" command: root.statusCommand("display") actionName: "display" wheelAction: "display-brightness" wheelUpArg: "up" wheelDownArg: "down" accent: root.orange } StatusPill { title: "mic" command: root.statusCommand("mic") actionName: "mic" accent: root.purple } StatusPill { title: "mus" command: root.statusCommand("music") actionName: "music" interval: 2000 accent: root.green } StatusPill { title: "scratch" command: root.statusCommand("scratch") actionName: "scratch" accent: root.yellow } } ColumnLayout { Layout.preferredWidth: 84 Layout.fillHeight: true spacing: 7 ActionButton { label: "shot" actionName: "screenshot" accent: root.aqua } ActionButton { label: "clip" actionName: "clipboard" accent: root.purple } ActionButton { label: "power" actionName: "power" accent: root.red } } } } } } } component WorkspaceButton: Rectangle { id: wsButton required property int workspaceNumber readonly property bool focused: I3.focusedWorkspace !== null && I3.focusedWorkspace.number === workspaceNumber readonly property bool active: focused || root.pendingWorkspace === workspaceNumber Layout.preferredWidth: 30 Layout.preferredHeight: 28 width: 30 height: 28 radius: 4 color: active ? root.yellow : (mouse.containsMouse ? root.bg2 : root.bg1) border.width: 1 border.color: active ? root.yellow : root.bg2 Text { anchors.centerIn: parent text: String(wsButton.workspaceNumber) color: wsButton.active ? root.bg : root.fg1 font.family: root.fontFamily font.pixelSize: 13 font.bold: wsButton.active } MouseArea { id: mouse anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onClicked: root.switchWorkspace(wsButton.workspaceNumber) onWheel: wheel.angleDelta.y > 0 ? I3.dispatch("workspace next") : I3.dispatch("workspace prev") } } component ToggleButton: Rectangle { Layout.preferredHeight: 28 Layout.preferredWidth: 68 width: 68 height: 28 radius: 5 color: root.expanded ? root.bg2 : root.bg1 border.width: 1 border.color: root.yellow Text { anchors.centerIn: parent text: root.expanded ? "close" : "start" color: root.yellow font.family: root.fontFamily font.pixelSize: 12 font.bold: true } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: root.expanded = !root.expanded } } component ClockPill: Rectangle { width: 122 height: 28 radius: 5 color: root.bg1 border.width: 1 border.color: root.bg2 Text { anchors.centerIn: parent text: root.clockText color: root.fg font.family: root.fontFamily font.pixelSize: 12 font.bold: true } } component ActionButton: Rectangle { id: button property string label: "" property string actionName: "" property string actionArg: "" property string accent: root.fg1 Layout.preferredWidth: 76 Layout.preferredHeight: 28 width: 76 height: 28 radius: 5 color: mouse.pressed ? root.bg3 : (mouse.containsMouse ? root.bg2 : root.bg) border.width: 1 border.color: accent Text { id: labelText anchors.centerIn: parent text: button.label color: button.accent font.family: root.fontFamily font.pixelSize: 12 font.bold: true } MouseArea { id: mouse anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: root.runAction(button.actionName, button.actionArg) } } component StatusPill: Rectangle { id: pill property string title: "" property var command: [] property int interval: 2000 property string value: "..." property string actionName: "" property string actionArg: "" property string wheelAction: "" property string wheelUpArg: "" property string wheelDownArg: "" property string accent: root.fg1 function refresh() { if (pill.command.length > 0) proc.exec(pill.command); } function matchesRefresh(actionName) { return actionName === "" || actionName === pill.actionName || actionName === pill.wheelAction; } Layout.preferredHeight: 28 Layout.preferredWidth: Math.max(56, label.implicitWidth + 18) width: Math.max(56, label.implicitWidth + 18) height: 28 radius: 5 color: mouse.pressed ? root.bg3 : (mouse.containsMouse ? root.bg2 : root.bg) border.width: 1 border.color: root.bg2 Text { id: label anchors.centerIn: parent text: pill.title + " " + pill.value color: root.textColor(pill.value, pill.accent) font.family: root.fontFamily font.pixelSize: 12 } Process { id: proc command: pill.command running: true stdout: StdioCollector { onStreamFinished: pill.value = this.text.trim() } } Connections { function onRefreshRequested(actionName) { if (pill.matchesRefresh(actionName)) pill.refresh(); } target: root } Timer { interval: pill.interval running: true repeat: true onTriggered: pill.refresh() } MouseArea { id: mouse anchors.fill: parent hoverEnabled: true cursorShape: pill.actionName !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor onClicked: { if (pill.actionName !== "") root.runAction(pill.actionName, pill.actionArg); } onWheel: { if (pill.wheelAction !== "") root.runAction(pill.wheelAction, wheel.angleDelta.y > 0 ? pill.wheelUpArg : pill.wheelDownArg); } } } }