Package: waybar
Version: 0.15.0-1
Severity: normal
Tags: patch
X-Debbugs-Cc: [email protected]

Dear Maintainer,

Hyprland 0.55 has been released with lua support[1], and will soon be uploaded 
into Debian
after the hyprutils migration[2][3] is complete.

When hyprland 0.55 starts up with a lua config file, it stops responding
to old-style `hyprctl dispatch` commands on its IPC socket, which
waybar's `hyprland/workspaces` module relies on for workspace switching,
so clicking on a workspace to switch to it will no longer work.

This has been fixed upstream in PR #5013[4], specifically in commit
e17c0d9f0a73acc370df60ec8c532b1ed2385c73.

I've backported the commit onto version 0.15.0-1 and attached it here.
I've also test-built a waybar package locally and can confirm that it
fixes the issue with hyprland 0.55.1 running in lua mode.

[1] https://hypr.land/news/26_lua/
[2] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1136342
[3] https://release.debian.org/transitions/html/auto-hyprutils.html
[4] https://github.com/Alexays/Waybar/pull/5013


-- System Information:
Debian Release: forky/sid
  APT prefers questing-updates
  APT policy: (500, 'questing-updates'), (500, 'questing-security'), (500, 
'questing'), (100, 'questing-backports')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 6.17.0-23-generic (SMP w/14 CPU threads; PREEMPT)
Kernel taint flags: TAINT_CPU_OUT_OF_SPEC, TAINT_OOT_MODULE, 
TAINT_UNSIGNED_MODULE
Locale: LANG=en_SG.UTF-8, LC_CTYPE=en_SG.UTF-8 (charmap=UTF-8), 
LANGUAGE=en_SG:en
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages waybar depends on:
ii  init-system-helpers                  1.68
ii  libatkmm-1.6-1v5                     2.28.4-1build4
ii  libc6                                2.42-0ubuntu3.1
ii  libcairomm-1.0-1v5                   1.14.5-2build1
ii  libdbusmenu-gtk3-4                   18.10.20180917~bzr492+repack1-4
ii  libevdev2                            1.13.4+dfsg-1
ii  libfmt10                             10.1.1+ds1-4
ii  libgcc-s1                            15.2.0-4ubuntu4
ii  libglib2.0-0t64                      2.86.0-2ubuntu0.3
ii  libglibmm-2.4-1t64                   2.66.8-1
ii  libgps30t64                          3.25-5ubuntu1.25.10.1
ii  libgtk-3-0t64                        3.24.50-1ubuntu2
ii  libgtk-layer-shell0                  0.9.2-2
ii  libgtkmm-3.0-1t64                    3.24.10-1
ii  libinput10                           1.28.1-1ubuntu0.3
ii  libjack-jackd2-0 [libjack-0.125]     1.9.22~dfsg-5
ii  libjsoncpp26                         1.9.6-3
ii  libmpdclient2t64                     2.22-1.1build1
ii  libnl-3-200                          3.7.0-2build1
ii  libnl-genl-3-200                     3.7.0-2build1
ii  libpipewire-0.3-0t64                 1.4.7-3ubuntu2
ii  libplayerctl2                        2.4.1-3
ii  libpulse0                            1:17.0+dfsg1-2ubuntu3
ii  libsigc++-2.0-0v5                    2.12.1-4
ii  libsndio7.0                          1.10.0-0.2
ii  libspdlog1.15 [libspdlog1.15-fmt10]  1:1.15.3+ds-1
ii  libstdc++6                           15.2.0-4ubuntu4
ii  libudev1                             257.9-0ubuntu2.4
ii  libupower-glib3                      1.90.9-5
ii  libwayland-client0                   1.24.0-1build1
ii  libwireplumber-0.5-0                 0.5.10-3ubuntu1
ii  libxkbregistry0                      1.12.3-1

waybar recommends no packages.

Versions of packages waybar suggests:
ii  fonts-font-awesome                              5.0.10+really4.7.0~dfsg-4.1
pn  gir1.2-playerctl-2.0                            <none>
ii  libayatana-appindicator3-1 [libappindicator3-1  0.5.94-1
    ]
ii  python3                                         3.13.7-1
pn  sway                                            <none>

-- no debconf information
From: Higor Prado <[email protected]>
Date: Wed, 29 Apr 2026 15:53:09 -0300
X-Dgit-Generated: 0.15.0-1 b007f6d817595d2aecb46cddbc2f6223d51ac1bb
Subject: [PATCH] fix(hyprland/workspaces): adapt dispatch commands for Lua IPC protocol

Hyprland 0.54 replaced the text-based dispatch socket protocol with a
Lua-based one. Commands like "dispatch workspace 1" are now interpreted
as invalid Lua (return hl.dispatch(workspace 1)), breaking workspace
clicks and scroll navigation.

Add IPC::dispatch() that probes the running Hyprland on first call and
routes commands through the new hl.dsp Lua API when the Lua protocol is
detected, falling back to the old text format otherwise.

Origin: https://github.com/Alexays/Waybar/commit/e17c0d9f0a73acc370df60ec8c532b1ed2385c73
Bug: https://github.com/Alexays/Waybar/pull/5013
Applied-Upstream: https://github.com/Alexays/Waybar/commit/e17c0d9f0a73acc370df60ec8c532b1ed2385c73

---

diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp
index 2e0ef65..84e056f 100644
--- a/include/modules/hyprland/backend.hpp
+++ b/include/modules/hyprland/backend.hpp
@@ -3,6 +3,7 @@
 #include <filesystem>
 #include <list>
 #include <mutex>
+#include <optional>
 #include <string>
 #include <thread>
 #include <utility>
@@ -34,6 +35,10 @@ class IPC {
   Json::Value getSocket1JsonReply(const std::string& rq);
   static std::filesystem::path getSocketFolder(const char* instanceSig);
 
+  /// Dispatch a Hyprland command. Automatically uses the correct protocol
+  /// (legacy text or Lua-based) depending on the running Hyprland version.
+  static std::string dispatch(const std::string& dispatcher, const std::string& arg);
+
  protected:
   static std::filesystem::path socketFolder_;
 
@@ -41,6 +46,15 @@ class IPC {
   void socketListener();
   void parseIPC(const std::string&);
 
+  /// Detect whether the running Hyprland uses the Lua-based IPC protocol.
+  /// Returns true for Hyprland >= 0.54 (Lua config), false for older versions.
+  static bool isLuaProtocol();
+
+  /// Build a Lua-format dispatch command string.
+  static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg);
+
+  static std::optional<bool> s_luaProtocolDetected_;  // cached detection result
+
   std::thread ipcThread_;
   std::mutex callbackMutex_;
   util::JsonParser parser_;
diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp
index 7060d30..1f772ec 100644
--- a/src/modules/hyprland/backend.cpp
+++ b/src/modules/hyprland/backend.cpp
@@ -10,11 +10,13 @@
 #include <unistd.h>
 
 #include <filesystem>
+#include <optional>
 #include <string>
 
 namespace waybar::modules::hyprland {
 
 std::filesystem::path IPC::socketFolder_;
+std::optional<bool> IPC::s_luaProtocolDetected_;
 
 std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
   static std::mutex folderMutex;
@@ -243,4 +245,69 @@ Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
   return parser_.parse(reply);
 }
 
+bool IPC::isLuaProtocol() {
+  if (s_luaProtocolDetected_.has_value()) {
+    return *s_luaProtocolDetected_;
+  }
+
+  // Probe: send a harmless old-style dispatch and check the error.
+  // In Lua-based Hyprland (>= 0.54) the error contains "hl.dispatch".
+  // In older versions it returns "ok" or a different error.
+  auto reply = getSocket1Reply("dispatch workspace __waybar_probe__");
+  bool luaProto = reply.find("hl.dispatch") != std::string::npos;
+
+  if (luaProto) {
+    spdlog::info("Hyprland IPC: detected Lua-based dispatch protocol (Hyprland >= 0.54)");
+  } else {
+    spdlog::info("Hyprland IPC: detected legacy dispatch protocol");
+  }
+
+  s_luaProtocolDetected_ = luaProto;
+  return luaProto;
+}
+
+std::string IPC::buildLuaDispatch(const std::string& dispatcher, const std::string& arg) {
+  // Map old-style dispatchers to the new Lua hl.dsp API.
+  //
+  // Old format:  dispatch workspace 1
+  // New format:  /dispatch hl.dsp.focus({ workspace = "1" })
+  //
+  // Old format:  dispatch focusworkspaceoncurrentmonitor 2
+  // New format:  /dispatch hl.dsp.focus({ workspace = "2", monitor = "current" })
+  //
+  // Old format:  dispatch togglespecialworkspace name
+  // New format:  /dispatch hl.dsp.workspace.toggle_special("name")
+
+  if (dispatcher == "workspace") {
+    return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\" })";
+  }
+  if (dispatcher == "focusworkspaceoncurrentmonitor") {
+    return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\", monitor = \"current\" })";
+  }
+  if (dispatcher == "togglespecialworkspace") {
+    if (arg.empty()) {
+      return "/dispatch hl.dsp.workspace.toggle_special()";
+    }
+    return "/dispatch hl.dsp.workspace.toggle_special(\"" + arg + "\")";
+  }
+
+  // Fallback for any other dispatcher: try the old format wrapped in dispatch().
+  // This may not work for all dispatchers, but it's a reasonable default.
+  spdlog::warn("Hyprland IPC: unknown dispatcher '{}' in Lua mode, attempting generic format",
+               dispatcher);
+  return "/dispatch hl.dsp." + dispatcher + "(\"" + arg + "\")";
+}
+
+std::string IPC::dispatch(const std::string& dispatcher, const std::string& arg) {
+  if (isLuaProtocol()) {
+    return getSocket1Reply(buildLuaDispatch(dispatcher, arg));
+  }
+  // Legacy format: "dispatch <dispatcher> <arg>"
+  std::string cmd = "dispatch " + dispatcher;
+  if (!arg.empty()) {
+    cmd += " " + arg;
+  }
+  return getSocket1Reply(cmd);
+}
+
 }  // namespace waybar::modules::hyprland
diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp
index 4cdd891..b8090bd 100644
--- a/src/modules/hyprland/workspace.cpp
+++ b/src/modules/hyprland/workspace.cpp
@@ -71,20 +71,20 @@ bool Workspace::handleClicked(GdkEventButton* bt) const {
     try {
       if (id() > 0) {  // normal
         if (m_workspaceManager.moveToMonitor()) {
-          m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
+          IPC::dispatch("focusworkspaceoncurrentmonitor", std::to_string(id()));
         } else {
-          m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
+          IPC::dispatch("workspace", std::to_string(id()));
         }
       } else if (!isSpecial()) {  // named (this includes persistent)
         if (m_workspaceManager.moveToMonitor()) {
-          m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
+          IPC::dispatch("focusworkspaceoncurrentmonitor", "name:" + name());
         } else {
-          m_ipc.getSocket1Reply("dispatch workspace name:" + name());
+          IPC::dispatch("workspace", "name:" + name());
         }
       } else if (id() != -99) {  // named special
-        m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
+        IPC::dispatch("togglespecialworkspace", name());
       } else {  // special
-        m_ipc.getSocket1Reply("dispatch togglespecialworkspace");
+        IPC::dispatch("togglespecialworkspace", "");
       }
       return true;
     } catch (const std::exception& e) {

Attachment: signature.asc
Description: PGP signature

Reply via email to