Skip to content

Workspaces.qml: Add global workspace sync for multi-monitor setups - #10199

Open
amacieli wants to merge 1 commit into
omacom:quattrofrom
amacieli:feature/global-workspaces-widget
Open

Workspaces.qml: Add global workspace sync for multi-monitor setups#10199
amacieli wants to merge 1 commit into
omacom:quattrofrom
amacieli:feature/global-workspaces-widget

Conversation

@amacieli

@amacieli amacieli commented Sep 4, 2026

Copy link
Copy Markdown
  • Introduces dual-mode operation: global mode (all monitors switch together) and local mode (stock per-monitor behavior, fallback)
  • Global mode is opt-in via toggle file ~/.local/state/omarchy/toggles/hypr/workspace-global.lua
  • When global mode is active:
    • Bar shows stable 5 slots (AW1-AW5) instead of raw Hyprland WS IDs
    • Clicking a slot calls omarchy-hyprland-workspace-global-switch (handles workspace theft and fullscreen=2 blocking)
    • All three monitor bars agree on focused/occupied state
  • When global mode is absent:
    • Falls back to stock omarchy behavior (per-monitor focus, raw WS IDs)
    • No breaking changes to existing installs
  • Each monitor owns exclusive workspace range (offset = monitor_id * 10):
    • Monitor 0: WS 1-10
    • Monitor 1: WS 11-20
    • Monitor 2: WS 21-30

Related to discussion #8412 (linked workspaces for multi-monitor setups)

- Introduces dual-mode operation: global mode (all monitors switch together) and
  local mode (stock per-monitor behavior, fallback)
- Global mode is opt-in via toggle file ~/.local/state/omarchy/toggles/hypr/workspace-global.lua
- When global mode is active:
  - Bar shows stable 5 slots (AW1-AW5) instead of raw Hyprland WS IDs
  - Clicking a slot calls omarchy-hyprland-workspace-global-switch (handles
    workspace theft and fullscreen=2 blocking)
  - All three monitor bars agree on focused/occupied state
- When global mode is absent:
  - Falls back to stock omarchy behavior (per-monitor focus, raw WS IDs)
  - No breaking changes to existing installs
- Each monitor owns exclusive workspace range (offset = monitor_id * 10):
  - Monitor 0: WS 1-10
  - Monitor 1: WS 11-20
  - Monitor 2: WS 21-30

Related to discussion omacom#8412 (linked workspaces for multi-monitor setups)
@isaac30503

Copy link
Copy Markdown

Three things from having built essentially this same design independently before finding this PR — thanks for the AW/WS split, it's the right model. Two are things I ran into building it, one is a bug I only found by running the equivalent of this for a while.

1. monitorId * 10 isn't stable across hotplug. Hyprland doesn't reuse monitor IDs — with (0,1,2), unplugging id 1 and replugging gives (0,2,3) (hyprwm/Hyprland#2601). So after one unplug/replug an external monitor silently moves to a fresh, unbounded-growing range, and its already-populated workspaces are orphaned. Related: awIsFocused() treats monitor 0 as authoritative, which breaks when the internal panel is off (clamshell/docked) or simply isn't id 0.

Allocating bases against a stable identity (monitor.description, falling back to name) on first sight, persisted across restarts, avoids both — this is what shezdy/hyprsplit's monitor_priority() does.

2. The QML side may not need the offset formula duplicated at all. Quickshell already exposes the mapping: Hyprland.monitorFor(screen) → this bar's monitor, HyprlandWorkspace.monitor → which monitor owns a workspace, HyprlandMonitor.activeWorkspace → that monitor's own active workspace (not the globally focused one). Deriving each bar's own logical numbering from Hyprland.workspaces.values filtered by w.monitor.id === myMonitor.id is agnostic to whatever base scheme the Lua side picks, so there's nothing to keep in sync between the two languages.

3. A real bug, worth flagging since this PR is probably affected too: monitor.activeWorkspace (and workspace.active, tried both) only updates for whichever monitor last produced a Hyprland IPC event Quickshell happened to be listening for. A monitor that changed workspace without taking real keyboard focus — exactly the "every other monitor" case in a synchronized multi-monitor switch — can sit stale in the bar until something incidental touches it (I confirmed even just hovering the cursor over that monitor "fixes" it). Content/window placement is unaffected (that's read straight off Hyprland.workspaces.values, which stays live), only the highlighted slot.

The fix that worked for me: add an IpcHandler to the widget that calls Hyprland.refreshMonitors() + Hyprland.refreshWorkspaces(), and invoke it via omarchy-shell -q <target> refresh right after every synchronized switch, backgrounded so it doesn't add latency to the dispatch itself. Since this fires from the same Lua entry point both the keybind and the bar click go through, it covers both paths with one call site.

One more small thing found while building the keybind side, unrelated to this widget but easy to trip over in the same feature: iterating monitors with ipairs() inside hyprland.lua hangs SUPER+K forever, because omarchy-menu-keybindings re-evaluates the config under a stub hl whose __index returns a truthy sentinel for every key, so ipairs() never terminates under it (omacom/omarchy#7025). hyprsplit's own init.lua still uses ipairs() and hits this today. A numeric for i = 1, #mons do sidesteps it, since # doesn't consult __index.

Happy to send any of this as a follow-up PR if useful.


Assisted by Claude Code (Claude Sonnet 5).

@Chessing234 Chessing234 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

global sync looks fine, but awisfocused shouldn't hardcode monitor 0 — if that output is gone every bar shows no focused workspace while switches still work. derive the slot from any connected monitor's active workspace minus its offset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants