From ccd2712982d7f89287def4135a8bab035506541c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lyas?= <67807483+Mestane@users.noreply.github.com> Date: Sun, 31 May 2026 16:48:33 +0300 Subject: [PATCH] fix: Lua dispatcher compat (#112) * fix: temporary Lua dispatcher compat for workspace dispatchers * fix(resizer): add Lua dispatcher compat for window resize/move/float/center * feat(theme): write current.lua for Hyprland Lua config, current.conf for hyprlang * fix: align gen_lua format with #111 * refactor address review feedback refactor(hypr,theme): address review feedback - cache is_lua_config result to avoid redundant socket calls - remove is_lua_config wrapper, rename _is_lua_config to is_lua_config - move hypr import to top of theme.py - use single line syntax in apply_hypr and apply_colours * restore original specialws behavior and some formatting --- src/caelestia/subcommands/resizer.py | 30 +++++++++++++++++++++++----- src/caelestia/utils/hypr.py | 30 ++++++++++++++++++++++++++++ src/caelestia/utils/theme.py | 12 +++++++++-- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/caelestia/subcommands/resizer.py b/src/caelestia/subcommands/resizer.py index c9d8fc0..fc12662 100644 --- a/src/caelestia/subcommands/resizer.py +++ b/src/caelestia/subcommands/resizer.py @@ -26,6 +26,26 @@ class Command: self.timeout_tracker: dict[str, float] = {} self.window_rules = self._load_window_rules() + def _make_resize_cmd(self, width: int | str, height: int | str, address: str) -> str: + if hypr.is_lua_config(): + return f'dispatch hl.dsp.window.resize({{x = {width}, y = {height}, exact = true, window = "address:{address}"}})' + return f"dispatch resizewindowpixel exact {width} {height},address:{address}" + + def _make_move_cmd(self, x: int, y: int, address: str) -> str: + if hypr.is_lua_config(): + return f'dispatch hl.dsp.window.move({{x = {x}, y = {y}, window = "address:{address}"}})' + return f"dispatch movewindowpixel exact {x} {y},address:{address}" + + def _make_float_cmd(self, address: str) -> str: + if hypr.is_lua_config(): + return f'dispatch hl.dsp.window.float({{action = "toggle", window = "address:{address}"}})' + return f"dispatch togglefloating address:{address}" + + def _make_center_cmd(self) -> str: + if hypr.is_lua_config(): + return "dispatch hl.dsp.window.center()" + return "dispatch centerwindow" + def _load_window_rules(self) -> list[WindowRule]: default_rules = [ WindowRule("(Bitwarden", "titleContains", "20%", "54%", ["float", "center"]), @@ -164,8 +184,8 @@ class Command: move_x = monitor_x + monitor_width - scaled_width - offset move_y = monitor_y + monitor_height - scaled_height - offset - command1 = f"dispatch resizewindowpixel exact {scaled_width} {scaled_height},address:{address}" - command2 = f"dispatch movewindowpixel exact {int(move_x)} {int(move_y)},address:{address}" + command1 = self._make_resize_cmd(scaled_width, scaled_height, address) + command2 = self._make_move_cmd(int(move_x), int(move_y), address) hypr.batch(command1, command2) log_message( @@ -181,16 +201,16 @@ class Command: if "float" in actions: window_info = self._get_window_info(window_id) if window_info and not window_info.get("floating", False): - dispatch_commands.append(f"dispatch togglefloating address:0x{window_id}") + dispatch_commands.append(self._make_float_cmd(f"0x{window_id}")) if "pip" in actions: self._apply_pip_action(window_id) return True - dispatch_commands.append(f"dispatch resizewindowpixel exact {width} {height},address:0x{window_id}") + dispatch_commands.append(self._make_resize_cmd(width, height, f"0x{window_id}")) if "center" in actions: - dispatch_commands.append("dispatch centerwindow") + dispatch_commands.append(self._make_center_cmd()) try: hypr.batch(*dispatch_commands) diff --git a/src/caelestia/utils/hypr.py b/src/caelestia/utils/hypr.py index d251b98..f1bd0ad 100644 --- a/src/caelestia/utils/hypr.py +++ b/src/caelestia/utils/hypr.py @@ -7,6 +7,7 @@ socket_base = f"{os.getenv('XDG_RUNTIME_DIR')}/hypr/{os.getenv('HYPRLAND_INSTANC socket_path = f"{socket_base}/.socket.sock" socket2_path = f"{socket_base}/.socket2.sock" +_lua_config_cache: bool | None = None def message(msg: str, is_json: bool = True) -> str | dict[str, Any]: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: @@ -26,7 +27,36 @@ def message(msg: str, is_json: bool = True) -> str | dict[str, Any]: return json.loads(resp) if is_json else resp +def is_lua_config() -> bool: + global _lua_config_cache + if _lua_config_cache is not None: + return _lua_config_cache + try: + result = message("systeminfo", is_json=False) + for line in result.splitlines(): + if "configProvider:" in line: + _lua_config_cache = "lua" in line.lower() + return _lua_config_cache + _lua_config_cache = False + return False + except Exception: + _lua_config_cache = False + return False + + +DISPATCHER_MAP_LUA = { + "togglespecialworkspace": lambda *a: f'hl.dsp.workspace.toggle_special("{a[0]}")' if a else 'hl.dsp.workspace.toggle_special()', + "movetoworkspacesilent": lambda *a: ( + f'hl.dsp.window.move({{window = "address:{a[0].split(",")[1].replace("address:", "")}", workspace = "{a[0].split(",")[0]}", follow = false}})' +), + "exec": lambda *a: 'hl.dsp.exec_cmd("' + ' '.join(a).replace('\\', '\\\\').replace('"', '\\"') + '")', +} + + def dispatch(dispatcher: str, *args: str) -> bool: + if is_lua_config() and dispatcher in DISPATCHER_MAP_LUA: + lua_dispatch = DISPATCHER_MAP_LUA[dispatcher](*args) + return message(f"dispatch {lua_dispatch}", is_json=False) == "ok" return message(f"dispatch {dispatcher} {' '.join(map(str, args))}".rstrip(), is_json=False) == "ok" diff --git a/src/caelestia/utils/theme.py b/src/caelestia/utils/theme.py index 9fc95f4..deec33a 100644 --- a/src/caelestia/utils/theme.py +++ b/src/caelestia/utils/theme.py @@ -19,6 +19,7 @@ from caelestia.utils.paths import ( user_templates_dir, ) from caelestia.utils.scheme import get_scheme +from caelestia.utils.hypr import is_lua_config def gen_conf(colours: dict[str, str]) -> str: @@ -27,6 +28,12 @@ def gen_conf(colours: dict[str, str]) -> str: conf += f"${name} = {colour}\n" return conf +def gen_lua(colours: dict[str, str]) -> str: + lua = "return {\n" + for name, colour in colours.items(): + lua += f' {name} = "{colour}",\n' + lua += "}" + return lua def gen_scss(colours: dict[str, str]) -> str: scss = "" @@ -144,7 +151,8 @@ def apply_terms(sequences: str) -> None: @log_exception def apply_hypr(conf: str) -> None: - write_file(config_dir / "hypr/scheme/current.conf", conf) + ext = "lua" if is_lua_config() else "conf" + write_file(config_dir / f"hypr/scheme/current.{ext}", conf) @log_exception @@ -428,7 +436,7 @@ def apply_colours(colours: dict[str, str], mode: str) -> None: if check("enableTerm"): apply_terms(gen_sequences(colours)) if check("enableHypr"): - apply_hypr(gen_conf(colours)) + apply_hypr(gen_lua(colours) if is_lua_config() else gen_conf(colours)) if check("enableDiscord"): apply_discord(gen_scss(colours)) if check("enableSpicetify"):