From 787f312cbe8577b1acb4af15a0d6d8b8f52716ff Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Fri, 8 May 2026 18:00:31 -0700 Subject: [PATCH] hyprland: dispatch bind callbacks explicitly --- dotfiles/config/hypr/hyprland.lua | 114 +++++++++--------- .../checks/hyprland-config-syntax/default.nix | 5 + .../hyprland-config-syntax/smoke-test.lua | 31 +++-- 3 files changed, 87 insertions(+), 63 deletions(-) diff --git a/dotfiles/config/hypr/hyprland.lua b/dotfiles/config/hypr/hyprland.lua index 986f7420..95c170b4 100644 --- a/dotfiles/config/hypr/hyprland.lua +++ b/dotfiles/config/hypr/hyprland.lua @@ -116,6 +116,10 @@ local function exec(command) return hl.dsp.exec_cmd(command) end +local function dispatch(dispatcher) + return hl.dispatch(dispatcher) +end + local function shell_quote(value) return "'" .. tostring(value):gsub("'", "'\\''") .. "'" end @@ -652,7 +656,7 @@ local function update_nstack_count() end stack_count = math.max(stack_count, 2) - hl.dsp.layout("setstackcount " .. tostring(stack_count))() + dispatch(hl.dsp.layout("setstackcount " .. tostring(stack_count))) end local function schedule_nstack_count_update() @@ -788,24 +792,24 @@ end local function monocle_next() local window = hl.get_active_window() if window and window.group and window.group.size and window.group.size > 1 then - hl.dsp.group.next({ window = window_selector(window) })() + dispatch(hl.dsp.group.next({ window = window_selector(window) })) elseif current_layout == monocle_layout then - hl.dsp.layout("cyclenext")() + dispatch(hl.dsp.layout("cyclenext")) update_monocle_notice() else - hl.dsp.window.cycle_next({ next = true, tiled = true, floating = false })() + dispatch(hl.dsp.window.cycle_next({ next = true, tiled = true, floating = false })) end end local function monocle_prev() local window = hl.get_active_window() if window and window.group and window.group.size and window.group.size > 1 then - hl.dsp.group.prev({ window = window_selector(window) })() + dispatch(hl.dsp.group.prev({ window = window_selector(window) })) elseif current_layout == monocle_layout then - hl.dsp.layout("cycleprev")() + dispatch(hl.dsp.layout("cycleprev")) update_monocle_notice() else - hl.dsp.window.cycle_next({ next = false, tiled = true, floating = false })() + dispatch(hl.dsp.window.cycle_next({ next = false, tiled = true, floating = false })) end end @@ -820,30 +824,30 @@ local function focus_direction(direction) return end - hl.dsp.focus({ direction = direction })() + dispatch(hl.dsp.focus({ direction = direction })) end local function swap_direction(direction) if enable_nstack and is_nstack_layout(current_layout) and active_group_size() <= 1 then - hl.dsp.layout("swapdirection " .. direction)() + dispatch(hl.dsp.layout("swapdirection " .. direction)) return end - hl.dsp.window.swap({ direction = direction })() + dispatch(hl.dsp.window.swap({ direction = direction })) end local function focus_workspace(workspace_id) - hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true })() + dispatch(hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true })) end local function move_window_to_workspace(workspace_id, follow, window) local target_window = window or hl.get_active_window() local target_selector = window_selector(target_window) - hl.dsp.window.move({ workspace = tostring(workspace_id), follow = false, window = target_selector })() + dispatch(hl.dsp.window.move({ workspace = tostring(workspace_id), follow = false, window = target_selector })) if follow then focus_workspace(workspace_id) if target_selector then - hl.dsp.focus({ window = target_selector })() + dispatch(hl.dsp.focus({ window = target_selector })) end end end @@ -871,8 +875,8 @@ local function move_window_into_group(window, anchor) end for _, direction in ipairs(grouping_directions(window, anchor)) do - hl.dsp.focus({ window = selector })() - hl.dsp.window.move({ into_group = direction, window = selector })() + dispatch(hl.dsp.focus({ window = selector })) + dispatch(hl.dsp.window.move({ into_group = direction, window = selector })) local active = hl.get_active_window() if active and active.group and active.group.size and active.group.size > 1 then @@ -960,12 +964,12 @@ local function restore_workspace_tabbed_group() return end - hl.dsp.focus({ window = anchor_selector })() - hl.dsp.group.toggle({ window = anchor_selector })() + dispatch(hl.dsp.focus({ window = anchor_selector })) + dispatch(hl.dsp.group.toggle({ window = anchor_selector })) tabbed_workspace_groups[key] = nil set_layout(columns_layout) restore_tabbed_group_window_order(state, target_workspace_id) - hl.dsp.focus({ window = anchor_selector })() + dispatch(hl.dsp.focus({ window = anchor_selector })) schedule_nstack_count_update() end @@ -1014,8 +1018,8 @@ local function gather_workspace_into_tabbed_group() set_layout(columns_layout) - hl.dsp.focus({ window = anchor_selector })() - hl.dsp.group.toggle({ window = anchor_selector })() + dispatch(hl.dsp.focus({ window = anchor_selector })) + dispatch(hl.dsp.group.toggle({ window = anchor_selector })) local group_windows = {} for _, window in ipairs(candidates) do @@ -1037,8 +1041,8 @@ local function gather_workspace_into_tabbed_group() end if grouped_count <= 1 then - hl.dsp.focus({ window = anchor_selector })() - hl.dsp.group.toggle({ window = anchor_selector })() + dispatch(hl.dsp.focus({ window = anchor_selector })) + dispatch(hl.dsp.group.toggle({ window = anchor_selector })) notify_tabbed_group("Unable to group tiled windows") return elseif grouped_count < #candidates then @@ -1050,7 +1054,7 @@ local function gather_workspace_into_tabbed_group() order = original_order, windows = candidate_addresses, } - hl.dsp.focus({ window = anchor_selector })() + dispatch(hl.dsp.focus({ window = anchor_selector })) end local function force_columns_layout() @@ -1114,7 +1118,7 @@ local function enter_workspace_swap_mode() color = "rgba(edb443ff)", font_size = 13, }) - hl.dsp.submap("swap-workspace")() + dispatch(hl.dsp.submap("swap-workspace")) end local function focus_next_empty_workspace() @@ -1149,10 +1153,10 @@ local function move_window_to_monitor(direction, follow) end local original_monitor = hl.get_active_monitor() - hl.dsp.window.move({ monitor = direction, follow = follow, window = window_selector(window) })() + dispatch(hl.dsp.window.move({ monitor = direction, follow = follow, window = window_selector(window) })) if not follow and original_monitor then - hl.dsp.focus({ monitor = original_monitor })() + dispatch(hl.dsp.focus({ monitor = original_monitor })) end end @@ -1170,9 +1174,9 @@ local function move_window_to_empty_workspace_on_monitor(direction) return end - hl.dsp.focus({ monitor = target_monitor })() + dispatch(hl.dsp.focus({ monitor = target_monitor })) focus_workspace(workspace_id) - hl.dsp.focus({ monitor = original_monitor })() + dispatch(hl.dsp.focus({ monitor = original_monitor })) move_window_to_workspace(workspace_id, false, window) end @@ -1443,35 +1447,35 @@ local function apply_scratchpad_geometry(name, window, target_monitor) end local selector = window_selector(window) - hl.dsp.window.float({ action = "enable", window = selector })() - hl.dsp.window.tag({ tag = "+scratchpad", window = selector })() - hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = selector })() - hl.dsp.window.resize({ x = width, y = height, relative = false, window = selector })() - hl.dsp.window.move({ x = x, y = y, relative = false, window = selector })() + dispatch(hl.dsp.window.float({ action = "enable", window = selector })) + dispatch(hl.dsp.window.tag({ tag = "+scratchpad", window = selector })) + dispatch(hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = selector })) + dispatch(hl.dsp.window.resize({ x = width, y = height, relative = false, window = selector })) + dispatch(hl.dsp.window.move({ x = x, y = y, relative = false, window = selector })) if def.dropdown then - hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = selector })() - hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = selector })() + dispatch(hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = selector })) + dispatch(hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = selector })) end end local function float_active_window_preserving_tiled_geometry() local geometry = tiled_window_geometry(hl.get_active_window()) - hl.dsp.window.float({ action = "enable", window = geometry and geometry.selector or nil })() + dispatch(hl.dsp.window.float({ action = "enable", window = geometry and geometry.selector or nil })) if geometry then - hl.dsp.window.resize({ x = geometry.width, y = geometry.height, relative = false, window = geometry.selector })() - hl.dsp.window.move({ x = geometry.x, y = geometry.y, relative = false, window = geometry.selector })() + dispatch(hl.dsp.window.resize({ x = geometry.width, y = geometry.height, relative = false, window = geometry.selector })) + dispatch(hl.dsp.window.move({ x = geometry.x, y = geometry.y, relative = false, window = geometry.selector })) end return geometry end local function float_and_drag_active_window() float_active_window_preserving_tiled_geometry() - hl.dsp.window.drag()() + dispatch(hl.dsp.window.drag()) end local function float_and_resize_active_window() float_active_window_preserving_tiled_geometry() - hl.dsp.window.resize()() + dispatch(hl.dsp.window.resize()) end local function schedule_scratchpad_geometry(name, window, target_monitor) @@ -1493,7 +1497,7 @@ local function show_scratchpad_window(name, window, workspace, target_monitor) remove_minimized_window(window) move_window_to_workspace(workspace.id, false, window) - hl.dsp.focus({ window = window_selector(window) })() + dispatch(hl.dsp.focus({ window = window_selector(window) })) schedule_scratchpad_geometry(name, window, target_monitor or hl.get_active_monitor()) end @@ -1610,36 +1614,36 @@ local function activate_window_picker_candidate(index) local mode = window_picker_mode window_picker_mode = nil window_picker_candidates = {} - hl.dsp.submap("reset")() + dispatch(hl.dsp.submap("reset")) if not window then return end if mode == "go" then - hl.dsp.focus({ window = window_selector(window) })() + dispatch(hl.dsp.focus({ window = window_selector(window) })) return end local workspace = active_workspace() if mode == "bring" and workspace then move_window_to_workspace(workspace.id, false, window) - hl.dsp.focus({ window = window_selector(window) })() + dispatch(hl.dsp.focus({ window = window_selector(window) })) return end if mode == "minimized" and workspace then remove_minimized_window(window) restore_minimized_window(window, workspace) - hl.dsp.focus({ window = window_selector(window) })() + dispatch(hl.dsp.focus({ window = window_selector(window) })) return end if mode == "replace" then local focused = hl.get_active_window() if focused and focused ~= window then - hl.dsp.window.swap({ target = window_selector(window), window = window_selector(focused) })() - hl.dsp.focus({ window = window_selector(window) })() + dispatch(hl.dsp.window.swap({ target = window_selector(window), window = window_selector(focused) })) + dispatch(hl.dsp.focus({ window = window_selector(window) })) end end end @@ -1677,7 +1681,7 @@ local function enter_window_picker(mode) color = "rgba(edb443ff)", font_size = 11, }) - hl.dsp.submap("window-picker")() + dispatch(hl.dsp.submap("window-picker")) end local function gather_focused_class() @@ -1707,7 +1711,7 @@ end local function focus_next_class() local focused = hl.get_active_window() if not focused or not focused.class or focused.class == "" then - hl.dsp.window.cycle_next({ next = true, tiled = true, floating = false })() + dispatch(hl.dsp.window.cycle_next({ next = true, tiled = true, floating = false })) return end @@ -1736,7 +1740,7 @@ local function focus_next_class() local next_class = classes[(current_index % #classes) + 1] local target = first_by_class[next_class] if target then - hl.dsp.focus({ window = window_selector(target) })() + dispatch(hl.dsp.focus({ window = window_selector(target) })) end end @@ -1775,7 +1779,7 @@ local function raise_or_spawn(class_fragment, command) local fragment = string.lower(class_fragment) for _, window in ipairs(hl.get_windows()) do if is_normal_window(window) and window.class and string.find(string.lower(window.class), fragment, 1, true) then - hl.dsp.focus({ window = window_selector(window) })() + dispatch(hl.dsp.focus({ window = window_selector(window) })) return end end @@ -1805,7 +1809,7 @@ local function restore_last_minimized() local window = table.remove(minimized_windows) if window and window.address and is_minimized_window(window) then restore_minimized_window(window, workspace) - hl.dsp.focus({ window = window_selector(window) })() + dispatch(hl.dsp.focus({ window = window_selector(window) })) return end end @@ -2180,7 +2184,7 @@ hl.define_submap("swap-workspace", function() local workspace_id = i bind(tostring(i), function() swap_current_workspace_with(workspace_id) - hl.dsp.submap("reset")() + dispatch(hl.dsp.submap("reset")) end) end @@ -2282,8 +2286,8 @@ for i = 1, 9 do bind(main_mod .. " + " .. workspace, hl.dsp.focus({ workspace = workspace, on_current_monitor = true })) bind(main_mod .. " + SHIFT + " .. workspace, hl.dsp.window.move({ workspace = workspace, follow = false })) bind(main_mod .. " + CTRL + " .. workspace, function() - hl.dsp.window.move({ workspace = workspace, follow = false })() - hl.dsp.focus({ workspace = workspace, on_current_monitor = true })() + dispatch(hl.dsp.window.move({ workspace = workspace, follow = false })) + dispatch(hl.dsp.focus({ workspace = workspace, on_current_monitor = true })) end) end diff --git a/nixos/checks/hyprland-config-syntax/default.nix b/nixos/checks/hyprland-config-syntax/default.nix index 63467e0a..c5e1dadf 100644 --- a/nixos/checks/hyprland-config-syntax/default.nix +++ b/nixos/checks/hyprland-config-syntax/default.nix @@ -13,6 +13,11 @@ pkgs.runCommand "hyprland-config-syntax" { exit 1 fi + if grep -nE 'hl[.]dsp.*[)][(][)]' hyprland.lua; then + echo "hyprland.lua should use hl.dispatch(...) instead of calling dispatcher objects directly" >&2 + exit 1 + fi + lua ${./smoke-test.lua} ./hyprland.lua touch "$out" '' diff --git a/nixos/checks/hyprland-config-syntax/smoke-test.lua b/nixos/checks/hyprland-config-syntax/smoke-test.lua index f476d83b..724fc0d0 100644 --- a/nixos/checks/hyprland-config-syntax/smoke-test.lua +++ b/nixos/checks/hyprland-config-syntax/smoke-test.lua @@ -3,14 +3,18 @@ local callbacks = {} local function noop() end -local function dispatcher_proxy() - local proxy = {} - return setmetatable(proxy, { +local dispatcher = setmetatable({}, { + __call = function() + error("dispatcher objects cannot be called directly; use hl.dispatch(dispatcher)", 2) + end, +}) + +local function dispatcher_namespace() + return setmetatable({}, { __index = function() - return dispatcher_proxy() - end, - __call = function() - return noop + return function() + return dispatcher + end end, }) end @@ -48,6 +52,7 @@ hl = { bind = noop, config = noop, curve = noop, + dispatch = noop, env = noop, exec_cmd = noop, define_submap = function(_, reset_or_callback, callback) @@ -59,7 +64,17 @@ hl = { monitor = noop, workspace_rule = noop, window_rule = noop, - dsp = dispatcher_proxy(), + dsp = setmetatable({ + group = dispatcher_namespace(), + window = dispatcher_namespace(), + workspace = dispatcher_namespace(), + }, { + __index = function() + return function() + return dispatcher + end + end, + }), notification = { create = function() return notification