From c330d4334f0f14ea46fdcafc164faeec09ea82e9 Mon Sep 17 00:00:00 2001 From: usering-around <226918848+usering-around@users.noreply.github.com> Date: Tue, 11 Nov 2025 22:42:53 +0200 Subject: [PATCH 1/3] renderer: fix noscreenshare layerrule popups (#12260) --- src/protocols/Screencopy.cpp | 40 +++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index 687c4045a..1b47fb66e 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -211,6 +211,24 @@ void CScreencopyFrame::renderMon() { g_pHyprOpenGL->setRenderModifEnabled(true); g_pHyprOpenGL->popMonitorTransformEnabled(); + auto hidePopups = [&](Vector2D popupBaseOffset) { + return [&, popupBaseOffset](WP popup, void*) { + if (!popup->m_wlSurface || !popup->m_wlSurface->resource() || !popup->m_mapped) + return; + + const auto popRel = popup->coordsRelativeToParent(); + popup->m_wlSurface->resource()->breadthfirst( + [&](SP surf, const Vector2D& localOff, void*) { + const auto size = surf->m_current.size; + const auto surfBox = CBox{popupBaseOffset + popRel + localOff, size}.translate(m_monitor->m_position).scale(m_monitor->m_scale).translate(-m_box.pos()); + + if LIKELY (surfBox.w > 0 && surfBox.h > 0) + g_pHyprOpenGL->renderRect(surfBox, Colors::BLACK, {}); + }, + nullptr); + }; + }; + for (auto const& l : g_pCompositor->m_layers) { if (!l->m_noScreenShare) continue; @@ -225,6 +243,10 @@ void CScreencopyFrame::renderMon() { CBox{REALPOS.x, REALPOS.y, std::max(REALSIZE.x, 5.0), std::max(REALSIZE.y, 5.0)}.translate(-m_monitor->m_position).scale(m_monitor->m_scale).translate(-m_box.pos()); g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {}); + + const auto geom = l->m_geometry; + const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; + l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); } for (auto const& w : g_pCompositor->m_windows) { @@ -261,23 +283,7 @@ void CScreencopyFrame::renderMon() { const auto geom = w->m_xdgSurface->m_current.geometry; const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; - w->m_popupHead->breadthfirst( - [&](WP popup, void*) { - if (!popup->m_wlSurface || !popup->m_wlSurface->resource() || !popup->m_mapped) - return; - - const auto popRel = popup->coordsRelativeToParent(); - popup->m_wlSurface->resource()->breadthfirst( - [&](SP surf, const Vector2D& localOff, void*) { - const auto size = surf->m_current.size; - const auto surfBox = CBox{popupBaseOffset + popRel + localOff, size}.translate(-m_monitor->m_position).scale(m_monitor->m_scale).translate(-m_box.pos()); - - if LIKELY (surfBox.w > 0 && surfBox.h > 0) - g_pHyprOpenGL->renderRect(surfBox, Colors::BLACK, {}); - }, - nullptr); - }, - nullptr); + w->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); } if (m_overlayCursor) From ee2168c665c9c01991cd2a0e73633e4e7d833259 Mon Sep 17 00:00:00 2001 From: bea4dev <34712108+bea4dev@users.noreply.github.com> Date: Wed, 12 Nov 2025 05:43:43 +0900 Subject: [PATCH 2/3] renderer/ime: fix fcitx5 popup artifacts (#12263) --- src/managers/input/InputMethodPopup.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/managers/input/InputMethodPopup.cpp b/src/managers/input/InputMethodPopup.cpp index 27acd08c5..3c4731b53 100644 --- a/src/managers/input/InputMethodPopup.cpp +++ b/src/managers/input/InputMethodPopup.cpp @@ -120,11 +120,18 @@ void CInputPopup::updateBox() { CBox cursorBoxLocal({-popupOffset.x, -popupOffset.y}, cursorBoxParent.size()); m_popup->sendInputRectangle(cursorBoxLocal); - CBox popupBoxParent(cursorBoxParent.pos() + popupOffset, currentPopupSize); - if (popupBoxParent != m_lastBoxLocal) { + CBox popupBoxParent(cursorBoxParent.pos() + popupOffset, currentPopupSize); + const bool boxChanged = popupBoxParent != m_lastBoxLocal; + if (boxChanged) + damageEntire(); // damage the old location before updating + + m_lastBoxLocal = popupBoxParent; + + // Since a redraw request is not always sent when only the position is updated, + // a manual redraw may be required in some cases. + if (boxChanged) damageEntire(); - m_lastBoxLocal = popupBoxParent; - } + damageSurface(); if (const auto PM = g_pCompositor->getMonitorFromCursor(); PM && PM->m_id != m_lastMonitor) { From 308226a4fc2c9b63fa19894d5f85e79e05d75e03 Mon Sep 17 00:00:00 2001 From: Luke Barkess <57995669+Brumus14@users.noreply.github.com> Date: Tue, 11 Nov 2025 22:59:21 +0000 Subject: [PATCH 3/3] config/keybinds: add a submap universal keybind flag (#12100) --- hyprtester/src/tests/main/keybinds.cpp | 41 ++++++++++++- src/config/ConfigManager.cpp | 83 ++++++++++++-------------- src/debug/HyprCtl.cpp | 6 +- src/managers/KeybindManager.cpp | 2 +- src/managers/KeybindManager.hpp | 47 ++++++++------- 5 files changed, 107 insertions(+), 72 deletions(-) diff --git a/hyprtester/src/tests/main/keybinds.cpp b/hyprtester/src/tests/main/keybinds.cpp index 4e2247499..a2fe2f37a 100644 --- a/hyprtester/src/tests/main/keybinds.cpp +++ b/hyprtester/src/tests/main/keybinds.cpp @@ -34,6 +34,17 @@ static bool checkFlag() { return exists; } +static bool attemptCheckFlag(int attempts, int intervalMs) { + for (int i = 0; i < attempts; i++) { + if (checkFlag()) + return true; + + std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs)); + } + + return false; +} + static std::string readKittyOutput() { std::string output = Tests::execAndGet("kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all"); // chop off shell prompt @@ -443,6 +454,34 @@ static void testSubmap() { Tests::killAllWindows(); } +static void testSubmapUniversal() { + NLog::log("{}Testing submap universal", Colors::GREEN); + + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bindu SUPER,Y,exec,touch " + flagFile), "ok"); + EXPECT_CONTAINS(getFromSocket("/submap"), "default"); + + // keybind works on default submap + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29")); + EXPECT(attemptCheckFlag(30, 5), true); + + // keybind works on submap1 + getFromSocket("/dispatch plugin:test:keybind 1,7,30"); + getFromSocket("/dispatch plugin:test:keybind 0,7,30"); + EXPECT_CONTAINS(getFromSocket("/submap"), "submap1"); + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29")); + EXPECT(attemptCheckFlag(30, 5), true); + + // reset to default submap + getFromSocket("/dispatch plugin:test:keybind 1,0,33"); + getFromSocket("/dispatch plugin:test:keybind 0,0,33"); + EXPECT_CONTAINS(getFromSocket("/submap"), "default"); + + EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); +} + static bool test() { NLog::log("{}Testing keybinds", Colors::GREEN); @@ -462,8 +501,8 @@ static bool test() { testShortcutLongPressKeyRelease(); testShortcutRepeat(); testShortcutRepeatKeyRelease(); - testSubmap(); + testSubmapUniversal(); clearFlag(); return !ret; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index d03ad83e9..a3c60d2bb 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -2539,52 +2539,45 @@ std::optional CConfigManager::handleBind(const std::string& command // bind[fl]=SUPER,G,exec,dmenu_run // flags - bool locked = false; - bool release = false; - bool repeat = false; - bool mouse = false; - bool nonConsuming = false; - bool transparent = false; - bool ignoreMods = false; - bool multiKey = false; - bool longPress = false; - bool hasDescription = false; - bool dontInhibit = false; - bool click = false; - bool drag = false; - const auto BINDARGS = command.substr(4); + bool locked = false; + bool release = false; + bool repeat = false; + bool mouse = false; + bool nonConsuming = false; + bool transparent = false; + bool ignoreMods = false; + bool multiKey = false; + bool longPress = false; + bool hasDescription = false; + bool dontInhibit = false; + bool click = false; + bool drag = false; + bool submapUniversal = false; + const auto BINDARGS = command.substr(4); for (auto const& arg : BINDARGS) { - if (arg == 'l') { - locked = true; - } else if (arg == 'r') { - release = true; - } else if (arg == 'e') { - repeat = true; - } else if (arg == 'm') { - mouse = true; - } else if (arg == 'n') { - nonConsuming = true; - } else if (arg == 't') { - transparent = true; - } else if (arg == 'i') { - ignoreMods = true; - } else if (arg == 's') { - multiKey = true; - } else if (arg == 'o') { - longPress = true; - } else if (arg == 'd') { - hasDescription = true; - } else if (arg == 'p') { - dontInhibit = true; - } else if (arg == 'c') { - click = true; - release = true; - } else if (arg == 'g') { - drag = true; - release = true; - } else { - return "bind: invalid flag"; + switch (arg) { + case 'l': locked = true; break; + case 'r': release = true; break; + case 'e': repeat = true; break; + case 'm': mouse = true; break; + case 'n': nonConsuming = true; break; + case 't': transparent = true; break; + case 'i': ignoreMods = true; break; + case 's': multiKey = true; break; + case 'o': longPress = true; break; + case 'd': hasDescription = true; break; + case 'p': dontInhibit = true; break; + case 'c': + click = true; + release = true; + break; + case 'g': + drag = true; + release = true; + break; + case 'u': submapUniversal = true; break; + default: return "bind: invalid flag"; } } @@ -2657,7 +2650,7 @@ std::optional CConfigManager::handleBind(const std::string& command g_pKeybindManager->addKeybind(SKeybind{parsedKey.key, KEYSYMS, parsedKey.keycode, parsedKey.catchAll, MOD, MODS, HANDLER, COMMAND, locked, m_currentSubmap, DESCRIPTION, release, repeat, longPress, mouse, nonConsuming, transparent, ignoreMods, multiKey, hasDescription, dontInhibit, - click, drag}); + click, drag, submapUniversal}); } return {}; diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index a0ccddd08..b5de65030 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -1025,6 +1025,7 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request "has_description": {}, "modmask": {}, "submap": "{}", + "submap_universal": "{}", "key": "{}", "keycode": {}, "catch_all": {}, @@ -1033,8 +1034,9 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request "arg": "{}" }},)#", kb->locked ? "true" : "false", kb->mouse ? "true" : "false", kb->release ? "true" : "false", kb->repeat ? "true" : "false", kb->longPress ? "true" : "false", - kb->nonConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap.name), escapeJSONStrings(kb->key), - kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler), escapeJSONStrings(kb->arg)); + kb->nonConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap.name), kb->submapUniversal, + escapeJSONStrings(kb->key), kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler), + escapeJSONStrings(kb->arg)); } trimTrailingComma(ret); ret += "]"; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 68750ad71..8194f7394 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -680,7 +680,7 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP if (!k->locked && g_pSessionLockManager->isSessionLocked()) continue; - if (!IGNORECONDITIONS && ((modmask != k->modmask && !k->ignoreMods) || k->submap != m_currentSelectedSubmap || k->shadowed)) + if (!IGNORECONDITIONS && ((modmask != k->modmask && !k->ignoreMods) || (k->submap != m_currentSelectedSubmap && !k->submapUniversal) || k->shadowed)) continue; if (k->multiKey) { diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index 592588b54..e3433a100 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -26,29 +26,30 @@ struct SSubmap { }; struct SKeybind { - std::string key = ""; - std::set sMkKeys = {}; - uint32_t keycode = 0; - bool catchAll = false; - uint32_t modmask = 0; - std::set sMkMods = {}; - std::string handler = ""; - std::string arg = ""; - bool locked = false; - SSubmap submap = {}; - std::string description = ""; - bool release = false; - bool repeat = false; - bool longPress = false; - bool mouse = false; - bool nonConsuming = false; - bool transparent = false; - bool ignoreMods = false; - bool multiKey = false; - bool hasDescription = false; - bool dontInhibit = false; - bool click = false; - bool drag = false; + std::string key = ""; + std::set sMkKeys = {}; + uint32_t keycode = 0; + bool catchAll = false; + uint32_t modmask = 0; + std::set sMkMods = {}; + std::string handler = ""; + std::string arg = ""; + bool locked = false; + SSubmap submap = {}; + std::string description = ""; + bool release = false; + bool repeat = false; + bool longPress = false; + bool mouse = false; + bool nonConsuming = false; + bool transparent = false; + bool ignoreMods = false; + bool multiKey = false; + bool hasDescription = false; + bool dontInhibit = false; + bool click = false; + bool drag = false; + bool submapUniversal = false; // DO NOT INITIALIZE bool shadowed = false;