summaryrefslogtreecommitdiff
path: root/quickshell/gruvbar/shell.qml
diff options
context:
space:
mode:
authorDavid Moc <personal@cdatgoose.org>2026-05-24 11:48:07 +0200
committerDavid Moc <personal@cdatgoose.org>2026-05-24 11:48:07 +0200
commit5fb19f10389b70ee2389755106092a9ef6c64598 (patch)
tree969eb0466de581cb14aa09f751a95138620fc720 /quickshell/gruvbar/shell.qml
Go
Diffstat (limited to 'quickshell/gruvbar/shell.qml')
-rw-r--r--quickshell/gruvbar/shell.qml631
1 files changed, 631 insertions, 0 deletions
diff --git a/quickshell/gruvbar/shell.qml b/quickshell/gruvbar/shell.qml
new file mode 100644
index 0000000..0d03f70
--- /dev/null
+++ b/quickshell/gruvbar/shell.qml
@@ -0,0 +1,631 @@
+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);
+
+ }
+ }
+
+ }
+
+}