import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import Quickshell.Io import qs.Commons import qs.Ui BarWidget { id: root moduleName: "omarchy.workspaces" 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 } function workspaceIds() { var ids = [1, 2, 3, 4, 5] var values = Hyprland.workspaces.values for (var i = 0; i < values.length; i++) { var id = values[i].id if (id > 0 && id <= 10 && ids.indexOf(id) === -1) ids.push(id) } ids.sort(function(left, right) { return left - right }) return ids } function focusWorkspace(id) { if (!root.bar) return root.bar.run("hyprctl dispatch " + Util.shellQuote("hl.dsp.focus({ workspace = \"" + id + "\" })")) } // The focused workspace shows its layout: a square tiles, a circle scrolls, // and a triangle floats. Clicking it cycles to the next one, as Super + L does. property string focusedMode: "dwindle" property bool modeStale: false function modeGlyph(mode) { if (mode === "scrolling") return "\uDB81\uDF65" if (mode === "floating") return "\uDB81\uDD36" return "\uDB85\uDCFB" } // A layout only changes through the layout commands, which ask for a refresh, // or a config reload, so each workspace's mode is read once and cached rather // than running the script on every workspace switch. property var modeCache: ({}) property int modeGeneration: 0 property int modeProcWorkspace: -1 property int modeProcGeneration: 0 function focusedWorkspaceId() { return Hyprland.focusedWorkspace ? Hyprland.focusedWorkspace.id : -1 } function showFocusedMode() { var mode = root.modeCache[root.focusedWorkspaceId()] if (mode !== undefined) root.focusedMode = mode else readMode() } function readMode() { if (modeProc.running) { root.modeStale = true return } var id = root.focusedWorkspaceId() if (id === -1) return root.modeProcWorkspace = id root.modeProcGeneration = root.modeGeneration modeProc.command = ["omarchy-hyprland-workspace-layout-current", String(id)] modeProc.running = true } function refresh() { root.modeCache = ({}) root.modeGeneration++ readMode() } function cycleLayout() { if (root.bar) root.bar.run("omarchy-hyprland-workspace-layout-toggle") } Component.onCompleted: refresh() Connections { target: Hyprland function onFocusedWorkspaceChanged() { root.showFocusedMode() } function onRawEvent(event) { if (event && String(event.name) === "configreloaded") root.refresh() } } Process { id: modeProc stdout: StdioCollector { onStreamFinished: { var mode = text.trim() // A read that started before a refresh may predate the change. if (!mode || root.modeProcGeneration !== root.modeGeneration) return root.modeCache[root.modeProcWorkspace] = mode if (root.modeProcWorkspace === root.focusedWorkspaceId()) root.focusedMode = mode } } onExited: { if (!root.modeStale) return root.modeStale = false // Deferred: the process still reports running while it signals exit. Qt.callLater(root.showFocusedMode) } } // Super + L and the default layout change the mode without a focus change, // so they ask every bar to read it again. ShellIpc { target: "omarchy.workspaces" function refresh(): void { root.broadcast("refresh") } } 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 : 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 readonly property bool focused: Hyprland.focusedWorkspace !== null && Hyprland.focusedWorkspace.id === modelData bar: root.bar text: focused ? root.modeGlyph(root.focusedMode) : (modelData === 10 ? "0" : String(modelData)) opacity: occupied || focused ? 1 : 0.5 horizontalMargin: 6 verticalPadding: 6 fixedWidth: root.vertical ? root.barSize : Style.space(20) fixedHeight: root.barSize onPressed: function() { if (focused) root.cycleLayout() else root.focusWorkspace(modelData) } } } } }