From 1f16383e9bb7811f728878230ee2a047b23eaeb6 Mon Sep 17 00:00:00 2001 From: RichardLeite <63970085+RichardLeite@users.noreply.github.com> Date: Tue, 16 Jun 2026 23:54:41 -0300 Subject: [PATCH] fix(hyprland): use [[BATCH]] format for IPC dispatch commands Hyprland's IPC socket rejects plain dispatch commands with 'Bad workspace' or 'Invalid dispatcher'. Wrap them in the [[BATCH]] format that hyprctl uses internally, which is required for dispatch commands. Also improve dispatch() error detection to catch 'Bad workspace', 'Invalid dispatcher', and other known error responses. Fix version detection to use hyprctl binary instead of IPC socket (which does not support j/version). Add automatic fallback: if hl.dsp.* Lua dispatcher is rejected, fall back to legacy dispatcher. --- pkg/ipc/hyprland/client.go | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/pkg/ipc/hyprland/client.go b/pkg/ipc/hyprland/client.go index a2f2873..e3a9ed3 100644 --- a/pkg/ipc/hyprland/client.go +++ b/pkg/ipc/hyprland/client.go @@ -7,6 +7,7 @@ import ( "io" "net" "os" + "os/exec" "strconv" "strings" "sync" @@ -36,6 +37,14 @@ func (h *Hyprland) getSocketPath(socketName string) string { return fmt.Sprintf("%s/hypr/%s/%s", runtimeDir, h.signature, socketName) } +var dispatchErrPrefixes = []string{ + "error:", + "unknown request", + "Bad ", + "Invalid dispatcher", + "Parse error", +} + func (h *Hyprland) dispatch(cmd string) (string, error) { h.mu.Lock() defer h.mu.Unlock() @@ -56,8 +65,10 @@ func (h *Hyprland) dispatch(cmd string) (string, error) { } resp := string(response) trimmed := strings.TrimSpace(resp) - if strings.HasPrefix(trimmed, "error:") || trimmed == "unknown request" { - return resp, fmt.Errorf("hyprland rejected request: %s", trimmed) + for _, prefix := range dispatchErrPrefixes { + if strings.HasPrefix(trimmed, prefix) { + return resp, fmt.Errorf("hyprland rejected request: %s", trimmed) + } } return resp, nil } @@ -71,29 +82,39 @@ func (h *Hyprland) supportsLuaDispatchers() bool { } h.versionMu.Unlock() - resp, err := h.dispatch("j/version") + // Use hyprctl binary instead of IPC socket to query version, + // since the socket doesn't support the version command directly. + out, err := exec.Command("hyprctl", "version").Output() if err != nil { return false } - version, err := parseHyprlandVersion(resp) + version, err := parseHyprlandVersion(string(out)) if err != nil { return false } useLuaDispatch := isHyprlandVersionAtLeast(version, 0, 55) h.versionMu.Lock() - h.useLuaDispatch = useLuaDispatch h.versionKnown = true + h.useLuaDispatch = useLuaDispatch h.versionMu.Unlock() return useLuaDispatch } func (h *Hyprland) dispatchVersioned(legacy, lua string) (string, error) { if h.supportsLuaDispatchers() { - return h.dispatch("dispatch " + lua) + resp, err := h.dispatch("[[BATCH]]dispatch " + lua) + if err == nil { + return resp, nil + } + // Lua dispatcher failed (e.g. "Invalid dispatcher" on mainline), + // fall back to legacy dispatcher. + h.versionMu.Lock() + h.useLuaDispatch = false + h.versionMu.Unlock() } - return h.dispatch("dispatch " + legacy) + return h.dispatch("[[BATCH]]dispatch " + legacy) } func parseHyprlandVersion(resp string) (string, error) {