Hyprland plugin that assigns each workspace a window mode in the spirit
of yabai's layout on macOS. Every workspace picks one of three modes:
- tile - the default dwindle layout
- float - all windows open floating
- stack - all windows join a single Hyprland group (tabbed container)
Modes are declared per-workspace in hyprland.conf and can be toggled
at runtime via a dispatcher. Mode changes are broadcast on socket2 so
bar widgets update without polling.
The plugin applies modes in the window.openEarly hook, before layout
placement, so there is no visible tile-then-adjust flash.
Built against Hyprland v0.55.4.
{
inputs = {
hyprland.url = "git+https://github.com/hyprwm/Hyprland?submodules=1";
hyprwsmode = {
url = "github:SubiqT/hyprwsmode";
inputs.hyprland.follows = "hyprland";
};
};
}Load via the Hyprland home-manager module:
wayland.windowManager.hyprland = {
enable = true;
plugins = [ hyprwsmode.packages.${system}.default ];
};Or point Hyprland at the plugin .so directly:
plugin = ${hyprwsmode.packages.x86_64-linux.default}/lib/libhyprwsmode.so
Requires Hyprland's development headers on the system (pkg-config hyprland must succeed):
cmake -DCMAKE_BUILD_TYPE=Release -B build
cmake --build build
hyprctl plugin load "$PWD/build/libhyprwsmode.so"Two axes: whether a workspace is managed vs unmanaged (unmanaged = float), and when managed, tile vs stack.
Global defaults:
plugin:wsmode:default_managed = managed # "managed" or "float"
plugin:wsmode:default_managed_type = tile # "tile" or "stack"
Per-workspace overrides for workspaces 1..9. Empty string inherits the global:
plugin:wsmode:default_managed_3 = float # ws 3: float by default
plugin:wsmode:default_managed_type_3 = stack # if toggled out of float, restore to stack
plugin:wsmode:default_managed_type_5 = stack # ws 5: managed (inherit), stacked
Workspaces outside 1..9 (including negative-id special workspaces) fall through to the global defaults with no per-workspace override option.
Config changes take effect on workspace creation. hyprctl reload does
not retroactively change the mode of a workspace that already exists.
To apply a new default to an existing workspace without waiting for it
to be recreated, dispatch wsmode reseed on that workspace; it
re-reads the current config and applies the mode.
Three ways to invoke the plugin's actions:
-
Classic keybind syntax in
hyprland.conf:bind = SUPER, m, dispatcher, wsmode, toggle bind = SUPER SHIFT, m, dispatcher, wsmode, toggle_float -
Lua config in
hyprland.lua, using thehl.plugin.wsmode.*namespace:local wsmode = hl.plugin.wsmode hl.bind("SUPER + m", function() return wsmode.toggle() end) hl.bind("SUPER + SHIFT + m", function() return wsmode.toggle_float() end) hl.bind("SUPER + CTRL + m", function() return wsmode.reseed() end)
The
returnis required:hl.bind's callback must yield a dispatcher table for the keypress to fire. Each action thunk performs its side effect and returnshl.dsp.no_op()sohl.bindhas something valid to call on keypress. -
hyprctlfrom the shell, either through the classic dispatch:hyprctl dispatch wsmode toggle
or through Lua evaluation on Hyprland 0.55+:
hyprctl dispatch 'hl.plugin.wsmode.toggle()'Query the current mode via:
hyprctl dispatch 'print(hl.plugin.wsmode.current())'currentreturns the mode as a Lua string rather than a dispatcher, so wrap it inprint(...)for CLI use. Do not call it from anhl.bindcallback; use the action thunks for keybinds.
Subcommands:
| Subcommand | Behaviour |
|---|---|
wsmode toggle |
On the active workspace, flip tile ↔ stack. |
wsmode toggle_float |
On the active workspace, flip managed ↔ float. |
wsmode set tile |
Set the active workspace to tile. |
wsmode set stack |
Set the active workspace to stack. |
wsmode set float |
Set the active workspace to float. |
wsmode current |
Print the active workspace's mode. |
wsmode reseed |
Recompute the active workspace's mode from current config, as if the workspace had just been created. Use after editing defaults in hyprland.conf or Nix and running hyprctl reload. |
wsmode broadcast |
Re-emit wsmode>>N,<mode> on socket2 for every workspace this plugin tracks, without changing any state. Used by bar widgets started after Hyprland to fetch an initial snapshot. |
Existing windows on the target workspace are left in place, except when
transitioning to stack: existing non-floating windows are grouped
into the workspace's stack group so the workspace ends up with the
one-window-visible-at-a-time invariant that stack implies.
wsmode toggle on a workspace that is currently float does not leave
float. It flips the remembered "last managed type" so the next
wsmode toggle_float restores the opposite side (tile ↔ stack). This
keeps the mental model "toggle picks the managed type, toggle_float
picks whether we're managed at all" consistent even when unmanaged.
Every mode change emits a wsmode event on Hyprland's socket2:
wsmode>>3,stack
wsmode>>3,float
Payload is <workspace-id>,<mode> where mode is one of tile,
stack, float. The event fires on:
- Workspace creation (once per workspace, with the seed mode).
- Every dispatcher call that changes the mode.
hyprctl reload, once per known workspace, with the current effective mode.- An explicit
wsmode broadcastdispatcher call, once per known workspace, with the current effective mode.
Consume it like any other socket2 event:
socat - "UNIX-CONNECT:$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" \
| grep '^wsmode>>'Assumes Hyprland's built-in tiling layout (dwindle or master). Running alongside a layout plugin like hy3 or hyprscroller is unsupported:
floatstill works because floating windows bypass the tiled layout.tiledelegates to whichever layout is active, so "tile" means "whatever the loaded layout does".stackcreates a HyprlandCGroupwhich layout plugins do not manage. Behaviour is undefined.
If you use hy3, prefer hy3:makegroup, tab for tabbed containers and
treat hyprwsmode as tile/float only, or bind only wsmode toggle_float.
- Adding a new layout algorithm.
stackuses Hyprland groups. - Replacing dwindle.
tilemeans "let dwindle do its thing". - Cross-compositor support. Hyprland only.
- Persistence of runtime toggles across full Hyprland restarts.
MIT.