I implemented something along the lines of what this old discussion suggested, but in Lua:
local function swapactiveworkspacewith(target)
local source = hl.get_active_workspace()
if target == nil or source.id == target then
return
end
local source_id = source.id
hl.dispatch(hl.dsp.workspace.change_id({ workspace = source, id = 42 })) -- arbitrary temporary ID
hl.notification.create({ text = "sent source to 42", timeout = 5000 }) -- debugging
hl.dispatch(hl.dsp.workspace.change_id({ workspace = target, id = source_id }))
hl.notification.create({ text = "sent target to source", timeout = 5000 })
hl.dispatch(hl.dsp.workspace.change_id({ workspace = 42, id = target }))
hl.notification.create({ text = "sent source to 42", timeout = 5000 })
end
Side note: I’m not sure whether this is particularly idiomatic, but I couldn’t find a way to iterate over all windows in a specific workspace.
I’d like to avoid having to move every window individually with SUPER+SHIFT+<number>, as suggested in another forum reply that I can’t find now for some reason…
The function itself works correctly. hyprctl activeworkspace reports the expected workspace ID after the swap. However, my status bar’s workspace switcher does not update.
Both my custom Quickshell configuration and the stock ashell exhibit the same behaviour; they continue to report the previous workspace ID even though Hyprland reports the new one.
Here’s a short video demonstrating the issue: https://streamable.com/t5ka9f
The sequence of events displayed in the video is:
- I launch OBS on workspace 1.
- I switch to workspace 3.
- I open a terminal on workspace 3.
- I run
hyprctl activeworkspace, which correctly reports workspace 3. - I invoke my
swapactiveworkspacewith(1)key bind. The notifications in the top-right confirm that the function executes. - I run
hyprctl activeworkspaceagain, which correctly reports workspace 1. - However, the status bar still reports that I am on workspace 3. It should now report workspace 1.
- I switch to workspace 5, which is empty.
- I switch to workspace 3, which now contains OBS.
- I briefly switch to workspace 1, which now contains the terminal.
- I switch back to workspace 3.
- I stop the recording.
The same behaviour occurs with ashell.
While writing this post, I found that adding the following to the end of my function causes the status bar to update:
--- original 2026-08-08 03:07:26.565765620 +0200
+++ new 2026-08-08 03:07:34.153765170 +0200
@@ -13,4 +13,7 @@
hl.dispatch(hl.dsp.workspace.change_id({ workspace = 42, id = target }))
hl.notification.create({ text = "sent source to 42", timeout = 5000 })
+ hl.dispatch(hl.dsp.focus({ workspace = source_id }))
+ hl.dispatch(hl.dsp.focus({ workspace = target }))
end
However, this causes visible flickering because the focus is changed twice. Alas, my questions:
- Why does changing the workspace IDs with
change_idnot cause the active workspace event used by status bars to update? - Is there a way to notify the status bar that the active workspace has changed without changing focus twice?
- Or is there a more idiomatic way to swap the windows of two workspaces
Thanks in advance!