import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Hyprland import qs.Commons import qs.Ui BarWidget { id: root moduleName: "joe.workspaces" // The Hyprland monitor this bar surface lives on. One bar is built per // screen, so each instance filters to its own monitor's workspaces. readonly property var screen: root.QsWindow.window ? root.QsWindow.window.screen : null readonly property var monitor: screen ? Hyprland.monitorFor(screen) : null // Green from the current theme's colors.toml. The shell's Color singleton // doesn't expose it, so read it here; `activeColor` in shell.json overrides. property color themeGreen: "#9ece6a" readonly property color activeColor: root.setting("activeColor", root.themeGreen) FileView { id: colorsFile path: Color.currentThemePath + "/colors.toml" printErrors: false onLoaded: { var match = text().match(/^\s*(?:green|color2)\s*=\s*["']?(#[0-9A-Fa-f]{6})/m) if (match) root.themeGreen = match[1] } } // Theme switches push new colors into Color; re-read ours alongside. Connections { target: Color function onAccentChanged() { colorsFile.reload() } } function workspaceById(id) { var values = Hyprland.workspaces.values for (var i = 0; i < values.length; i++) { if (values[i].id === id) return values[i] } return null } // Hyprland gives named workspaces negative ids, as it does special // (scratchpad) workspaces; only the latter have a "special:" name. function isNamed(ws) { return ws !== null && ws.id < 0 && String(ws.name).indexOf("special") !== 0 } // Numbered workspaces 1-10 in order, then named ones alphabetically. function workspaceIds() { var numbered = [] var named = [] var values = Hyprland.workspaces.values for (var i = 0; i < values.length; i++) { var ws = values[i] if (root.monitor && ws.monitor !== root.monitor) continue if (ws.id > 0 && ws.id <= 10) numbered.push(ws.id) else if (root.isNamed(ws)) named.push(ws) } numbered.sort(function(left, right) { return left - right }) named.sort(function(left, right) { return String(left.name).localeCompare(String(right.name)) }) return numbered.concat(named.map(function(ws) { return ws.id })) } function focusWorkspace(id) { if (!root.bar) return var ws = root.workspaceById(id) var target = root.isNamed(ws) ? "name:" + String(ws.name).replace(/\\/g, "\\\\").replace(/"/g, "\\\"") : String(id) root.bar.run("hyprctl dispatch " + Util.shellQuote("hl.dsp.focus({ workspace = \"" + target + "\" })")) } readonly property real trailingGap: root.vertical ? 0 : Style.spaceReal(1.5) implicitWidth: grid.implicitWidth + trailingGap implicitHeight: grid.implicitHeight GridLayout { id: grid anchors.fill: parent anchors.rightMargin: root.trailingGap columns: root.vertical ? 1 : Math.max(1, root.workspaceIds().length) columnSpacing: root.vertical ? 0 : Style.space(1) rowSpacing: root.vertical ? Style.space(2) : 0 Repeater { model: root.workspaceIds() WidgetButton { required property int modelData readonly property var workspace: root.workspaceById(modelData) readonly property bool occupied: workspace !== null && workspace.toplevels.values.length > 0 // The workspace shown on this bar's monitor (each monitor has one). readonly property bool focused: root.monitor !== null && root.monitor.activeWorkspace !== null && root.monitor.activeWorkspace.id === modelData id: button bar: root.bar readonly property bool named: root.isNamed(workspace) text: named ? String(workspace.name) : (modelData === 10 ? "0" : String(modelData)) // WidgetButton's own label can't be bolded, so draw our own instead. labelVisible: false opacity: occupied || focused ? 1 : 0.5 horizontalMargin: 6 verticalPadding: 6 // Numbers get a fixed slot; names get as much room as they need. fixedWidth: root.vertical ? root.barSize : (named ? Math.max(Style.space(20), label.implicitWidth + Style.space(12)) : Style.space(20)) fixedHeight: root.barSize onPressed: function() { root.focusWorkspace(modelData) } Text { id: label anchors.centerIn: parent textFormat: Text.PlainText text: button.text color: button.focused ? root.activeColor : button.foreground font.family: button.fontFamily font.pixelSize: button.fontSize font.bold: button.focused renderType: Text.NativeRendering horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } }