dot

vizie's NixOS desktop: Hyprland, CachyOS kernel and gaming tweaks

188 lines7656 bytesDiff
1diff --color -ruN a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp
2--- a/include/modules/hyprland/backend.hpp 2026-10-04 00:13:00.535259516 +0200
3+++ b/include/modules/hyprland/backend.hpp 2026-10-04 00:13:24.130275037 +0200
4@@ -3,6 +3,7 @@
5 #include <filesystem>
6 #include <list>
7 #include <mutex>
8+#include <optional>
9 #include <string>
10 #include <thread>
11 #include <utility>
12@@ -34,9 +35,27 @@
13 Json::Value getSocket1JsonReply(const std::string& rq);
14 static std::filesystem::path getSocketFolder(const char* instanceSig);
15
16+ /// Dispatch a Hyprland command. Automatically uses the correct protocol
17+ /// (legacy text or Lua-based) depending on the running Hyprland version.
18+ static std::string dispatch(const std::string& dispatcher, const std::string& arg);
19+
20+ /// Build a Lua-format dispatch command string.
21+ static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg);
22+
23 protected:
24 static std::filesystem::path socketFolder_;
25
26+ /// Detect whether the running Hyprland uses the Lua-based IPC protocol.
27+ /// Resolved once from the config manager it reports, then cached.
28+ static bool isLuaProtocol();
29+
30+ /// Whether a "systeminfo" reply reports the Lua config manager. An absent
31+ /// "configProvider:" field means the instance predates the Lua config
32+ /// manager (< 0.55) and therefore speaks the legacy protocol.
33+ static bool isLuaConfigProvider(const std::string& systemInfo);
34+
35+ static std::optional<bool> s_luaProtocolDetected_; // cached detection result
36+
37 private:
38 void socketListener();
39 void parseIPC(const std::string&);
40diff --color -ruN a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp
41--- a/src/modules/hyprland/backend.cpp 2026-10-04 00:13:00.537259511 +0200
42+++ b/src/modules/hyprland/backend.cpp 2026-10-04 00:13:24.138620589 +0200
43@@ -10,11 +10,16 @@
44 #include <unistd.h>
45
46 #include <filesystem>
47+#include <optional>
48 #include <string>
49+#include <string_view>
50+
51+#include "util/string.hpp"
52
53 namespace waybar::modules::hyprland {
54
55 std::filesystem::path IPC::socketFolder_;
56+std::optional<bool> IPC::s_luaProtocolDetected_;
57
58 std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
59 static std::mutex folderMutex;
60@@ -243,4 +248,98 @@
61 return parser_.parse(reply);
62 }
63
64+bool IPC::isLuaConfigProvider(const std::string& systemInfo) {
65+ // Hyprland reports which config manager it actually loaded in "systeminfo",
66+ // as a "configProvider: lua" / "configProvider: hyprlang" line. That is the
67+ // authoritative signal: the version alone is not enough, because Hyprland
68+ // only uses the Lua manager when the config file name ends in ".lua", so a
69+ // >= 0.54 instance started with a traditional hyprland.conf still speaks the
70+ // legacy dispatch protocol. The field landed together with the Lua config
71+ // manager in 0.55, so its absence means the instance predates Lua support
72+ // entirely and necessarily speaks the legacy protocol.
73+ static constexpr std::string_view key = "configProvider:";
74+
75+ const size_t keyPos = systemInfo.find(key);
76+ if (keyPos == std::string::npos) {
77+ return false;
78+ }
79+
80+ const size_t valuePos = keyPos + key.size();
81+ const size_t lineEnd = systemInfo.find('\n', valuePos);
82+ const std::string value = lineEnd == std::string::npos
83+ ? systemInfo.substr(valuePos)
84+ : systemInfo.substr(valuePos, lineEnd - valuePos);
85+
86+ return trim(value) == "lua";
87+}
88+
89+bool IPC::isLuaProtocol() {
90+ if (s_luaProtocolDetected_.has_value()) {
91+ return *s_luaProtocolDetected_;
92+ }
93+
94+ // The query is read-only, so detection has none of the side effects of an
95+ // actual dispatch probe.
96+ bool luaProto = false;
97+ try {
98+ luaProto = isLuaConfigProvider(getSocket1Reply("systeminfo"));
99+ } catch (const std::exception& e) {
100+ spdlog::warn("Hyprland IPC: could not read systeminfo ({}), assuming legacy protocol",
101+ e.what());
102+ }
103+
104+ if (luaProto) {
105+ spdlog::info("Hyprland IPC: detected Lua-based dispatch protocol");
106+ } else {
107+ spdlog::info("Hyprland IPC: detected legacy dispatch protocol");
108+ }
109+
110+ s_luaProtocolDetected_ = luaProto;
111+ return luaProto;
112+}
113+
114+std::string IPC::buildLuaDispatch(const std::string& dispatcher, const std::string& arg) {
115+ // Map old-style dispatchers to the new Lua hl.dsp API.
116+ //
117+ // Old format: dispatch workspace 1
118+ // New format: /dispatch hl.dsp.focus({ workspace = "1" })
119+ //
120+ // Old format: dispatch focusworkspaceoncurrentmonitor 2
121+ // New format: /dispatch hl.dsp.focus({ workspace = "2", on_current_monitor = true })
122+ //
123+ // Old format: dispatch togglespecialworkspace name
124+ // New format: /dispatch hl.dsp.workspace.toggle_special("name")
125+
126+ if (dispatcher == "workspace") {
127+ return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\" })";
128+ }
129+ if (dispatcher == "focusworkspaceoncurrentmonitor") {
130+ return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\", on_current_monitor = true })";
131+ }
132+ if (dispatcher == "togglespecialworkspace") {
133+ if (arg.empty()) {
134+ return "/dispatch hl.dsp.workspace.toggle_special()";
135+ }
136+ return "/dispatch hl.dsp.workspace.toggle_special(\"" + arg + "\")";
137+ }
138+
139+ // Fallback for any other dispatcher: try the old format wrapped in dispatch().
140+ // This may not work for all dispatchers, but it's a reasonable default.
141+ spdlog::warn("Hyprland IPC: unknown dispatcher '{}' in Lua mode, attempting generic format",
142+ dispatcher);
143+ return "/dispatch hl.dsp." + dispatcher + "(\"" + arg + "\")";
144+}
145+
146+std::string IPC::dispatch(const std::string& dispatcher, const std::string& arg) {
147+ if (isLuaProtocol()) {
148+ return getSocket1Reply(buildLuaDispatch(dispatcher, arg));
149+ }
150+ // Legacy format: "dispatch <dispatcher> <arg>"
151+ std::string cmd = "dispatch " + dispatcher;
152+ if (!arg.empty()) {
153+ cmd += " " + arg;
154+ }
155+ return getSocket1Reply(cmd);
156+}
157+
158 } // namespace waybar::modules::hyprland
159diff --color -ruN a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp
160--- a/src/modules/hyprland/workspace.cpp 2026-10-04 00:13:00.538259508 +0200
161+++ b/src/modules/hyprland/workspace.cpp 2026-10-04 00:13:24.138815299 +0200
162@@ -71,20 +71,20 @@
163 try {
164 if (id() > 0) { // normal
165 if (m_workspaceManager.moveToMonitor()) {
166- m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
167+ IPC::dispatch("focusworkspaceoncurrentmonitor", std::to_string(id()));
168 } else {
169- m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
170+ IPC::dispatch("workspace", std::to_string(id()));
171 }
172 } else if (!isSpecial()) { // named (this includes persistent)
173 if (m_workspaceManager.moveToMonitor()) {
174- m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
175+ IPC::dispatch("focusworkspaceoncurrentmonitor", "name:" + name());
176 } else {
177- m_ipc.getSocket1Reply("dispatch workspace name:" + name());
178+ IPC::dispatch("workspace", "name:" + name());
179 }
180 } else if (id() != -99) { // named special
181- m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
182+ IPC::dispatch("togglespecialworkspace", name());
183 } else { // special
184- m_ipc.getSocket1Reply("dispatch togglespecialworkspace");
185+ IPC::dispatch("togglespecialworkspace", "");
186 }
187 return true;
188 } catch (const std::exception& e) {