Skip to content

fix(hyprland): order workspaces by id - #3875

Merged
ItsLemmy merged 2 commits into
noctalia-dev:mainfrom
TheBinaryLoop:fix/hyprland-workspace-order
Aug 15, 2026
Merged

fix(hyprland): order workspaces by id#3875
ItsLemmy merged 2 commits into
noctalia-dev:mainfrom
TheBinaryLoop:fix/hyprland-workspace-order

Conversation

@TheBinaryLoop

@TheBinaryLoop TheBinaryLoop commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

HyprlandWorkspaceBackend::workspaceOrderLess() now sorts purely by workspace id. It used to read a negative id as "this workspace is named", push those workspaces behind every numbered one, and sort them alphabetically among themselves. Hyprland orders workspaces by ascending id, negatives included, so the bar disagreed with the compositor about what the next workspace is.

The workspaces widget in label_source = "id" mode also falls back to the workspace name when a workspace has no numeric id, instead of falling through to its position in the bar.

Motivation

Reported in #3871: with persistent named workspaces, the bar order does not match the order Hyprland uses for relative dispatches.

With the reporter's rules:

workspace = 8, monitor:<output>, persistent:true
workspace = name:0, monitor:<output>, persistent:true
workspace = name:Grave, monitor:<output>, persistent:true

Hyprland assigned Grave = -1338 and 0 = -1337. Repeatedly dispatching workspace m+1 visits Grave, 0, 8, and m~1 ("first workspace on the monitor") selects Grave. The bar showed 8, 0, Grave, so a next/previous keybind appeared to jump around the bar and m~1 highlighted the rightmost pill.

The label fallback matters for the same setup: Grave rendered as 3 in id mode, a number that was neither its id nor its name, and that changed as soon as the ordering changed. Rendering the negative id instead would be worse than useless, since the allocation is not stable across restarts: the same two rules produced -1339/-1338 for the reporter and -1337/-1338 here.

The fallback only fires when there is no numeric identity at all, so no workspace that previously showed a number shows something else now. Backends that set index (niri, kwin, dwl, mango, triad) never reach it. Named sway and ext-workspace workspaces get the same improvement as Hyprland.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Build / packaging

Related Issue

Closes #3871

Testing

  • just test debug — 80/80, including the new tests/hyprland_workspace_backend_test.cpp. It drives the backend against a stub hyprctl request socket serving the workspace set above plus a special workspace, and asserts ascending-id order from all() and forOutput().
  • Confirmed the new test is not vacuous by temporarily restoring the old comparator: it fails with got: 8(8), 0(-1337), Grave(-1338) and passes with the new one.
  • just format — no changes (clang-format 22.1.8).
  • clang-tidy with -warnings-as-errors=* over both modified files — clean.
  • Manual: nested Hyprland instance with the rules above, running a debug build. Pills read Grave, 0, 8; stepping workspace m+1 highlights them left to right; m~1 selects the leftmost. Labels come out as Grave 0 8 in id mode and G 0 8 in name mode with max_label_chars = 1.

Manual Coverage

  • Tested on Niri
  • Tested on Hyprland
  • Tested on Sway
  • Tested on another compositor:
  • Tested with different bar positions and density settings
  • Tested at different interface scaling values
  • Tested with multiple monitors

Hyprland coverage was a nested instance with a single output, not a full session. The unchecked boxes are untested: no layout, geometry, or scaling code is touched, so pill order and label text should be independent of bar position, density, and scale. Multi-monitor is worth a look from someone with the hardware, since forOutput() is one of the two call sites that changed.

Screenshots / Videos

Bar contents in the nested instance, active workspace in brackets:

before, label_source = id     [8]  2   3         (pills 2 and 3 are "0" and "Grave")
after,  label_source = id      Grave  0  [8]
after,  label_source = name    G  0  [8]

I have PNGs of all three and can attach them on request.

Checklist

  • This PR is ready for review, or it is marked as Draft.
  • I read and followed the relevant guidance in CONTRIBUTING.md.
  • I ran just format with clang-format v22+ installed, or this PR has no code changes.
  • I ran the relevant build or test commands, or explained why they were not run.
  • I self-reviewed the changes.
  • I checked for new warnings or errors.
  • I will update end-user documentation after merge, or this PR does not change user-facing configuration or behavior.
  • I added or updated assets/translations/en.json, or this PR adds no new user-facing strings.
  • I did not edit non-English translation files unless this PR is explicitly for translation tooling, an import/export sync, or a maintainer-requested locale change.
  • I used the existing canonical names for config keys, IPC names, paths, and identifiers.

Additional Notes

I left the documentation box unchecked deliberately. No config key, IPC name, or identifier changes, but observable behavior does change for anyone running named workspaces, and I do not know whether the docs describe workspace ordering or what id mode shows for a named workspace. Point me at the right page and I will send a note.

Two limitations worth knowing:

Order among named workspaces follows Hyprland's id allocation, not the order the rules are declared in. Above, name:0 is declared first but Grave sorts first. That is inherent in matching the compositor's own traversal.

max_label_chars is only exposed for label_source = "name", so a long workspace name is not truncated in id mode. Widening that setting's visibility to both modes is a settings-schema change I kept out of this PR. Happy to add it if you want it.

Named workspaces get negative ids from Hyprland, and the bar's comparator treated
a negative id as "named": it pushed those workspaces behind every numbered one and
sorted them alphabetically. Hyprland orders workspaces by ascending id, so with
`workspace = name:0` / `name:Grave` rules the bar read 8, 0, Grave while
`workspace m+1` walked Grave, 0, 8, and `m~1` landed on the last pill in the bar
instead of the first.

Sort by id and drop the special case. Labels in id mode now fall back to the
workspace name instead of its position in the bar, since a named workspace has no
numeric id to show; a negative id is an allocation artifact that changes between
restarts, so it is not worth rendering.
@TheBinaryLoop
TheBinaryLoop force-pushed the fix/hyprland-workspace-order branch from cb1ac0c to c50c18e Compare August 11, 2026 15:19
@ItsLemmy
ItsLemmy merged commit e719738 into noctalia-dev:main Aug 15, 2026
1 of 2 checks passed
@ItsLemmy

Copy link
Copy Markdown
Collaborator

THanks

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.

[BUG] negative hyprland workspace IDs mess up workspace ordering in the bar

2 participants