|
| 1 | +-- Workspace modes for omarchy-hyprland-workspace-layout-toggle. |
| 2 | +-- |
| 3 | +-- Hyprland has dwindle and scrolling as tiled layouts, but nothing that floats a |
| 4 | +-- whole workspace: it reports a workspace's `tiledLayout` and takes an unknown |
| 5 | +-- layout name in silence, falling back to dwindle. So floating is applied to the |
| 6 | +-- windows themselves. |
| 7 | +-- |
| 8 | +-- Ending the mode has to tile the windows the mode floated and only those, so |
| 9 | +-- each one is tagged. The tag lives on the window: it dies with it, follows it |
| 10 | +-- between workspaces, and survives the config reload that a monitor being |
| 11 | +-- plugged in performs, none of which a table on this side would do. |
| 12 | + |
| 13 | +require("default.hypr.helpers") |
| 14 | + |
| 15 | +local FLOATED_TAG = "omarchy-mode-floated" |
| 16 | + |
| 17 | +local modes = {} |
| 18 | +local watching = false |
| 19 | + |
| 20 | +local function set_floating(window, floating) |
| 21 | + hl.dispatch(hl.dsp.window.float({ action = floating and "on" or "off", window = window })) |
| 22 | +end |
| 23 | + |
| 24 | +local function set_claimed(window, claimed) |
| 25 | + hl.dispatch(hl.dsp.window.tag({ tag = (claimed and "+" or "-") .. FLOATED_TAG, window = window })) |
| 26 | +end |
| 27 | + |
| 28 | +local function claimed() |
| 29 | + local addresses = {} |
| 30 | + |
| 31 | + for _, window in ipairs(hl.get_windows({ tag = FLOATED_TAG })) do |
| 32 | + addresses[window.address] = true |
| 33 | + end |
| 34 | + |
| 35 | + return addresses |
| 36 | +end |
| 37 | + |
| 38 | +-- A window already floating on its own account -- an app with a float rule of |
| 39 | +-- its own, a dialog, one popped out with SUPER + T -- is left unclaimed, so it |
| 40 | +-- is still floating when the mode ends. |
| 41 | +local function float_for_mode(window) |
| 42 | + if window.floating then |
| 43 | + return |
| 44 | + end |
| 45 | + |
| 46 | + set_claimed(window, true) |
| 47 | + set_floating(window, true) |
| 48 | +end |
| 49 | + |
| 50 | +-- A pinned window is one the user asked to keep above everything, and tiling it |
| 51 | +-- would unpin it as well, so the mode leaves it be. |
| 52 | +local function tile_for_mode(window, mine) |
| 53 | + if not mine[window.address] or window.pinned then |
| 54 | + return |
| 55 | + end |
| 56 | + |
| 57 | + set_claimed(window, false) |
| 58 | + set_floating(window, false) |
| 59 | +end |
| 60 | + |
| 61 | +-- Windows arrive by opening and by being carried in, and a window rule only ever |
| 62 | +-- fires for the first. One carried back out gives up the floating the mode gave |
| 63 | +-- it rather than staying floating somewhere that tiles. |
| 64 | +local function watch() |
| 65 | + if watching then |
| 66 | + return |
| 67 | + end |
| 68 | + |
| 69 | + watching = true |
| 70 | + |
| 71 | + hl.on("window.open", function(window) |
| 72 | + if modes[tostring(window.workspace.id)] == "floating" then |
| 73 | + float_for_mode(window) |
| 74 | + end |
| 75 | + end) |
| 76 | + |
| 77 | + hl.on("window.move_to_workspace", function(window, workspace) |
| 78 | + if modes[tostring(workspace.id)] == "floating" then |
| 79 | + float_for_mode(window) |
| 80 | + else |
| 81 | + tile_for_mode(window, claimed()) |
| 82 | + end |
| 83 | + end) |
| 84 | +end |
| 85 | + |
| 86 | +function o.workspace_mode(spec) |
| 87 | + local workspace = tostring(spec.workspace) |
| 88 | + local floating = spec.mode == "floating" |
| 89 | + |
| 90 | + watch() |
| 91 | + |
| 92 | + -- Floating keeps whatever tiled layout the workspace had, so nothing has to be |
| 93 | + -- chosen for it on the way back out. |
| 94 | + if not floating then |
| 95 | + hl.workspace_rule({ workspace = workspace, layout = spec.mode }) |
| 96 | + end |
| 97 | + |
| 98 | + modes[workspace] = spec.mode |
| 99 | + |
| 100 | + local windows = hl.get_workspace_windows(workspace) |
| 101 | + |
| 102 | + if floating then |
| 103 | + local claiming = {} |
| 104 | + |
| 105 | + -- Claim every tiled window before floating any of them: floating one member |
| 106 | + -- of a group floats the rest, and a single pass would then find those |
| 107 | + -- already floating and leave them to be stranded when the mode ends. |
| 108 | + for _, window in ipairs(windows) do |
| 109 | + if not window.floating then |
| 110 | + set_claimed(window, true) |
| 111 | + table.insert(claiming, window) |
| 112 | + end |
| 113 | + end |
| 114 | + |
| 115 | + for _, window in ipairs(claiming) do |
| 116 | + set_floating(window, true) |
| 117 | + end |
| 118 | + else |
| 119 | + local mine = claimed() |
| 120 | + |
| 121 | + for _, window in ipairs(windows) do |
| 122 | + tile_for_mode(window, mine) |
| 123 | + end |
| 124 | + end |
| 125 | +end |
0 commit comments