From 4ce6d44df7cac311e1891f110553938323f76875 Mon Sep 17 00:00:00 2001 From: jrthekidRSS Date: Wed, 20 May 2026 17:10:30 -0500 Subject: [PATCH 1/4] chore: update Hyprland dispatcher syntax to Lua --- multiwindow_unity.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/multiwindow_unity.cpp b/multiwindow_unity.cpp index 4925bce..4cd9fd6 100644 --- a/multiwindow_unity.cpp +++ b/multiwindow_unity.cpp @@ -815,11 +815,11 @@ void CustomWindow::updateThings() { hyprReady = true; hyprctl->setProp("initialtitle:" + std::to_string(customId), "no_focus", "on"); hyprctl->setProp("initialtitle:" + std::to_string(customId), "no_anim", "on"); - hyprctl->sendMessage("dispatch setfloating initialtitle:" + std::to_string(customId)); + hyprctl->sendMessage("dispatch hl.dsp.window.float({ window = 'initialtitle:" + std::to_string(customId) + "' })"); } hyprctl->moveWindow("initialtitle:" + std::to_string(customId), finalX, finalY); - hyprctl->sendMessage("dispatch resizewindowpixel exact " + std::to_string(finalWidth) + " " + - std::to_string(finalHeight) + ",initialtitle:" + std::to_string(customId)); + hyprctl->sendMessage("dispatch hl.dsp.window.resize({ x = " + std::to_string(finalWidth) + ", y = " + + std::to_string(finalHeight) + ", window = 'initialtitle:" + std::to_string(customId) + "'})"); if (finalDecorations != _lastDecorations) { this->_lastDecorations = finalDecorations; hyprctl->setProp("initialtitle:" + std::to_string(customId), "decorate", finalDecorations ? "on" : "off"); @@ -982,11 +982,11 @@ bool Hyprctl::sendMessageSync(std::string message) { } bool Hyprctl::setProp(std::string window, std::string effect, std::string argument) { - return sendMessageSync("dispatch setprop " + window + " " + effect + " " + argument); + return sendMessageSync("dispatch hl.dsp.window.set_prop({ prop = '" + effect + "', value = '" + argument + "', window = '" + window + "'})"); } void Hyprctl::moveWindow(std::string window, int x, int y) { - return sendMessage("dispatch movewindowpixel exact " + std::to_string(x) + " " + std::to_string(y) + "," + window); + return sendMessage("dispatch hl.dsp.window.move({ x = " + std::to_string(x) + ", y = " + std::to_string(y) + ", window = '" + window + "'})"); } // ---- End of Hyprctl ---- @@ -1521,7 +1521,7 @@ void arrangeWindowsHyprland(HWND* windows, int count) { if (hasChanged) { for (auto win : windowList) { - hyprctl->sendMessageSync("dispatch alterzorder top,initialtitle:" + std::to_string(win->customId)); + hyprctl->sendMessageSync("dispatch hl.dsp.window.alter_zorder({ mode = 'top', window = 'initialtitle:" + std::to_string(win->customId) + "'"); } } } From 94dc59beb412a9777c7236840e5eadba04846667 Mon Sep 17 00:00:00 2001 From: jrthekidRSS Date: Mon, 15 Jun 2026 19:22:26 -0500 Subject: [PATCH 2/4] fix lua syntax errors and refactor hyprctl calls to create window rules instead of 'set_prop' dispatcher --- multiwindow_unity.cpp | 110 +++++++++++++++++++++++++++++++++--------- multiwindow_unity.hpp | 4 +- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/multiwindow_unity.cpp b/multiwindow_unity.cpp index 6b3fd22..64c4750 100644 --- a/multiwindow_unity.cpp +++ b/multiwindow_unity.cpp @@ -449,6 +449,7 @@ void createApplication() { qputenv("QT_QPA_PLATFORM", "xcb"); } else if (waylandType == WaylandType::Hyprland) { hyprctl = new Hyprctl(); + hyprctl->initWindowRules(); } std::thread([] { @@ -816,22 +817,15 @@ void CustomWindow::updateThings() { } this->setWindowTitle(targetTitle + QString::fromStdString(encoded)); } else if (waylandType == WaylandType::Hyprland) { - if (!hyprReady) { - if (!hyprctl->setProp("initialtitle:" + std::to_string(customId), "no_blur", "on")) { - return; - } - hyprReady = true; - hyprctl->setProp("initialtitle:" + std::to_string(customId), "no_focus", "on"); - hyprctl->setProp("initialtitle:" + std::to_string(customId), "no_anim", "on"); - hyprctl->sendMessage("dispatch hl.dsp.window.float({ window = 'initialtitle:" + std::to_string(customId) + "' })"); - } - hyprctl->moveWindow("initialtitle:" + std::to_string(customId), finalX, finalY); - hyprctl->sendMessage("dispatch hl.dsp.window.resize({ x = " + std::to_string(finalWidth) + ", y = " + - std::to_string(finalHeight) + ", window = 'initialtitle:" + std::to_string(customId) + "'})"); + hyprctl->setWindowGeometry(customId, finalX, finalY, finalWidth, finalHeight); + if (finalDecorations != _lastDecorations) { this->_lastDecorations = finalDecorations; - hyprctl->setProp("initialtitle:" + std::to_string(customId), "decorate", finalDecorations ? "on" : "off"); + hyprctl->sendMessageSync((std::string) "dispatch hl.dsp.window.tag({ tag = '" + + (finalDecorations ? "+" : "-") + "_rd_window_dance_shown', window = 'initialtitle:" + + std::to_string(customId) + "' })"); } + this->setWindowTitle(targetTitle); } else { this->setFixedSize(finalWidth, finalHeight); @@ -989,12 +983,81 @@ bool Hyprctl::sendMessageSync(std::string message) { return true; } -bool Hyprctl::setProp(std::string window, std::string effect, std::string argument) { - return sendMessageSync("dispatch hl.dsp.window.set_prop({ prop = '" + effect + "', value = '" + argument + "', window = '" + window + "'})"); -} - -void Hyprctl::moveWindow(std::string window, int x, int y) { - return sendMessage("dispatch hl.dsp.window.move({ x = " + std::to_string(x) + ", y = " + std::to_string(y) + ", window = '" + window + "'})"); +// I LOVE LUA!1!!!1 YIPPIE +bool Hyprctl::initWindowRules() { + return hyprctl->sendMessageSync(R"""(eval -- Do not remove the leading + -- space after 'eval' + + -- Should be more efficient to use window rules instead of + -- repeatedly spamming Hyprland IPC + -- + -- Also, avoid using named window rules since anonymous ones + -- always take priority, even if the named one is defined + -- afterwards + if _rd_window_rules_active then + return + end + + _rd_window_rules_active = true + + hl.window_rule({ + match = { + initial_class = "Rhythm Doctor", + initial_title = "negative:Rhythm Doctor" + }, + + float = true, + no_focus = true, + no_anim = true, + no_blur = true, + suppress_event = "activatefocus", + opaque = true + --decorate = false + }) + + -- Showing the dancing windows when window dance is active + hl.window_rule({ + match = { + initial_class = "Rhythm Doctor", + tag = "_rd_window_dance_shown" + }, + + decorate = true, + }) + + -- Hiding the main window when window dance is active + hl.window_rule({ + match = { + initial_class = "Rhythm Doctor", + tag = "_rd_window_main_hidden" + }, + + opacity = 0.0, + no_blur = true + }) + + --hl.notification.create({ text = "rules ready", timeout = 3000, icon = 1 }) + )"""); +} + +// Merged window moving and resizing into one function to reduce calls +// to Hyprland IPC +void Hyprctl::setWindowGeometry(int id, int x, int y, int w, int h) { + return sendMessage(("eval " + + (std::string) "local window, new_x, new_y, new_w, new_h = hl.get_window('initialtitle:" + + // A negative id means the main window + // I hope sentinal values aren't bad practice in C++... + (id < 0 ? (std::string) "Rhythm Doctor" : std::to_string(id)) + "'), " + + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(w) + ", " + std::to_string(h) + "\n" + R"""( + if not window then + return + end + + hl.dispatch(hl.dsp.window.move({ x = new_x, y = new_y, window = window })) + hl.dispatch(hl.dsp.window.resize({ x = new_w, y = new_h, window = window })) + )""" + )); } // ---- End of Hyprctl ---- @@ -1034,11 +1097,10 @@ void setMainWindowGeometry(int x, int y, int w, int h) { #else if (waylandType == WaylandType::Hyprland) { if (invisible) { - hyprctl->setProp("initialtitle:Rhythm.Doctor", "opacity", "0"); - hyprctl->setProp("initialtitle:Rhythm.Doctor", "no_blur", "1"); + hyprctl->sendMessageSync("dispatch hl.dsp.window.tag({ tag = '+_rd_window_main_hidden', window = 'initialtitle:Rhythm Doctor' })"); } else { - hyprctl->setProp("initialtitle:Rhythm.Doctor", "opacity", "1"); - hyprctl->moveWindow("initialtitle:Rhythm.Doctor", x, y); + hyprctl->sendMessageSync("dispatch hl.dsp.window.tag({ tag = '-_rd_window_main_hidden', window = 'initialtitle:Rhythm Doctor' })"); + hyprctl->setWindowGeometry(-1, x, y, main_window_width, main_window_height); } } else { if (main_window_handle == 0) { @@ -1529,7 +1591,7 @@ void arrangeWindowsHyprland(HWND* windows, int count) { if (hasChanged) { for (auto win : windowList) { - hyprctl->sendMessageSync("dispatch hl.dsp.window.alter_zorder({ mode = 'top', window = 'initialtitle:" + std::to_string(win->customId) + "'"); + hyprctl->sendMessageSync("dispatch hl.dsp.window.alter_zorder({ mode = 'top', window = 'initialtitle:" + std::to_string(win->customId) + "' })"); } } } diff --git a/multiwindow_unity.hpp b/multiwindow_unity.hpp index 1d09ce8..a0c11a5 100644 --- a/multiwindow_unity.hpp +++ b/multiwindow_unity.hpp @@ -76,6 +76,6 @@ class Hyprctl { Hyprctl(); void sendMessage(std::string message); bool sendMessageSync(std::string message); - bool setProp(std::string window, std::string effect, std::string argument); - void moveWindow(std::string window, int x, int y); + bool initWindowRules(); + void setWindowGeometry(int id, int x, int y, int w, int h); }; From b8b6880ae95fb5b070f1276d3170bdb6d1c84d23 Mon Sep 17 00:00:00 2001 From: jrthekidRSS Date: Mon, 15 Jun 2026 21:30:13 -0500 Subject: [PATCH 3/4] fix Hyprland decorationless window --- multiwindow_unity.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/multiwindow_unity.cpp b/multiwindow_unity.cpp index 64c4750..3d2b904 100644 --- a/multiwindow_unity.cpp +++ b/multiwindow_unity.cpp @@ -822,11 +822,13 @@ void CustomWindow::updateThings() { if (finalDecorations != _lastDecorations) { this->_lastDecorations = finalDecorations; hyprctl->sendMessageSync((std::string) "dispatch hl.dsp.window.tag({ tag = '" + - (finalDecorations ? "+" : "-") + "_rd_window_dance_shown', window = 'initialtitle:" + + (finalDecorations ? "-" : "+") + "_rd_window_dance_frameless', window = 'initialtitle:" + std::to_string(customId) + "' })"); } - this->setWindowTitle(targetTitle); + // TODO: After the Lua update, Hyprland is consistently setting the + // wrong initial class so leave it disabled for now + //this->setWindowTitle(targetTitle); } else { this->setFixedSize(finalWidth, finalHeight); this->setGeometry(finalX, finalY, finalWidth, finalHeight); @@ -1012,17 +1014,16 @@ bool Hyprctl::initWindowRules() { no_blur = true, suppress_event = "activatefocus", opaque = true - --decorate = false }) - -- Showing the dancing windows when window dance is active + -- Hiding window decorations for dancing windows hl.window_rule({ match = { initial_class = "Rhythm Doctor", - tag = "_rd_window_dance_shown" + tag = "_rd_window_dance_frameless" }, - decorate = true, + decorate = false, }) -- Hiding the main window when window dance is active From 81385caa4cb6e088fc5de9cfea5a8367b11af6fa Mon Sep 17 00:00:00 2001 From: jrthekidRSS Date: Mon, 15 Jun 2026 22:46:24 -0500 Subject: [PATCH 4/4] move 'Hyprctl::setWindowGeomerty' logic to a lua function --- multiwindow_unity.cpp | 80 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/multiwindow_unity.cpp b/multiwindow_unity.cpp index 3d2b904..12231ce 100644 --- a/multiwindow_unity.cpp +++ b/multiwindow_unity.cpp @@ -996,11 +996,69 @@ bool Hyprctl::initWindowRules() { -- Also, avoid using named window rules since anonymous ones -- always take priority, even if the named one is defined -- afterwards - if _rd_window_rules_active then + if _rd then return end - _rd_window_rules_active = true + _rd = { + dancing_windows = {}, + main_window = nil + } + + -- Returns the window (or nil if not found) and a bool signifying if + -- it was a "dancing" window + local function get_window_by_id(id) + local rd = _rd + + if id then + local window = rd.dancing_windows[id] + + if not window then + window = hl.get_window("initialtitle:" .. id) + + if not window then + return nil, false + end + + rd.dancing_windows[id] = window + end + + return window, true + else + local window = rd.main_window + + if not window then + window = hl.get_window("initialtitle:Rhythm Doctor") + + if not window then + return nil, false + end + + rd.main_window = window + end + + return window, false + end + end + + function _rd:set_window_geometry(id, x, y, w, h) + local window = get_window_by_id(id) + if not window then return end + + if not (x == window.x and y == window.y) then + hl.dispatch(hl.dsp.window.move({ x = x, y = y, window = window })) + end + + if not (w == window.width and h == window.width) then + hl.dispatch(hl.dsp.window.resize({ x = w, y = h, window = window })) + end + end + + if _rd.window_rules_active then + return + end + + _rd.window_rules_active = true hl.window_rule({ match = { @@ -1045,19 +1103,12 @@ bool Hyprctl::initWindowRules() { // to Hyprland IPC void Hyprctl::setWindowGeometry(int id, int x, int y, int w, int h) { return sendMessage(("eval " + - (std::string) "local window, new_x, new_y, new_w, new_h = hl.get_window('initialtitle:" + + (std::string) "_rd:set_window_geometry(" + // A negative id means the main window // I hope sentinal values aren't bad practice in C++... - (id < 0 ? (std::string) "Rhythm Doctor" : std::to_string(id)) + "'), " + - std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(w) + ", " + std::to_string(h) + "\n" - R"""( - if not window then - return - end - - hl.dispatch(hl.dsp.window.move({ x = new_x, y = new_y, window = window })) - hl.dispatch(hl.dsp.window.resize({ x = new_w, y = new_h, window = window })) - )""" + (id < 0 ? (std::string) "nil" : std::to_string(id)) + ", " + + std::to_string(x) + ", " + std::to_string(y) + ", " + + std::to_string(w) + ", " + std::to_string(h) + ")" )); } @@ -1465,6 +1516,9 @@ extern "C" WINAPI void destroy_window(HWND window) { customWindowMutex.lock(); CustomWindow* customWindow = (CustomWindow*)window; customWindow->isClosing = true; + if (waylandType == WaylandType::Hyprland) { + hyprctl->sendMessage("eval _rd.dancing_windows[" + std::to_string(customWindow->customId) + "] = nil"); + } QMetaObject::invokeMethod( app, [customWindow]() {