"""Side-effecting helpers shared by menu_tree.py's leaf actions. Everything here fires-and-forgets via Gio.Subprocess (lib.proc.fire) so a click never blocks the GTK main loop or the menu's close animation. Per-window ops use two different mechanisms on purpose: * focus / bring-here go through `hyprctl dispatch "hl.dsp...."` — hyprlua's own Lua-eval dispatch layer, the same one astal-menu/ui/taskbar.py already uses in production for exactly this (see its _focus/_pull). * close / force-kill go through plain native Hyprland dispatchers (`closewindow address:...`) and a bare `kill -9 `, which work regardless of the Lua layer and don't require guessing whether hl.dsp.window exposes a close-by- address form. """ from __future__ import annotations from lib.proc import fire def launch(cmd: str) -> None: """Run cmd via Hyprland's exec dispatcher — cmd may carry a `[tag ...]` / `[workspace ...]` window-rule-on-launch prefix, same as binds.lua.""" fire(["hyprctl", "dispatch", "exec", cmd]) def hyprshutdown(post_cmd: str | None = None) -> None: if post_cmd: fire(["hyprshutdown", "-p", post_cmd]) else: fire(["hyprshutdown"]) def _dispatch_lua(lua: str) -> None: fire(["hyprctl", "dispatch", lua]) def focus_window(address: str) -> None: _dispatch_lua(f'hl.dsp.focus({{ window = "address:{address}" }})') def bring_here(address: str, active_ws: int) -> None: _dispatch_lua(f'hl.dsp.window.move({{ window = "address:{address}", ' f'workspace = {active_ws} }})') def send_to_workspace(address: str, workspace_id: int) -> None: _dispatch_lua(f'hl.dsp.window.move({{ window = "address:{address}", ' f'workspace = {workspace_id}, silent = true }})') def close_window(address: str) -> None: fire(["hyprctl", "dispatch", "closewindow", f"address:{address}"]) def force_kill(pid: int) -> None: fire(["kill", "-9", str(pid)]) def report_pid(pid: int, title: str) -> None: fire(["wl-copy", str(pid)]) fire(["notify-send", "-a", "orbit-menu", f"PID {pid}", f"{title} — copied to clipboard"])