From d7f26038ee2b44f3d02fe2a7556bafb91a02f46e Mon Sep 17 00:00:00 2001 From: "Mr. Goferito" Date: Fri, 26 Dec 2025 23:16:31 +0100 Subject: [PATCH] keybinds: fix multikey binds breaking after scroll wheel events (#12638) --- hyprtester/src/tests/main/keybinds.cpp | 30 ++++++++++++++++++++++++++ src/managers/KeybindManager.cpp | 26 +++++++++++++--------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/hyprtester/src/tests/main/keybinds.cpp b/hyprtester/src/tests/main/keybinds.cpp index 23f17abf4..a87a8b07d 100644 --- a/hyprtester/src/tests/main/keybinds.cpp +++ b/hyprtester/src/tests/main/keybinds.cpp @@ -456,6 +456,35 @@ static void testSubmap() { Tests::killAllWindows(); } +static void testBindsAfterScroll() { + NLog::log("{}Testing binds after scroll", Colors::GREEN); + + clearFlag(); + OK(getFromSocket("/keyword binds Alt_R,w,exec,touch " + flagFile)); + + // press keybind before scroll + OK(getFromSocket("/dispatch plugin:test:keybind 1,0,108")); // Alt_R press + OK(getFromSocket("/dispatch plugin:test:keybind 1,4,25")); // w press + EXPECT(attemptCheckFlag(20, 50), true); + OK(getFromSocket("/dispatch plugin:test:keybind 0,4,25")); // w release + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,108")); // Alt_R release + + // scroll + OK(getFromSocket("/dispatch plugin:test:scroll 120")); + OK(getFromSocket("/dispatch plugin:test:scroll -120")); + OK(getFromSocket("/dispatch plugin:test:scroll 120")); + + // press keybind after scroll + OK(getFromSocket("/dispatch plugin:test:keybind 1,0,108")); // Alt_R press + OK(getFromSocket("/dispatch plugin:test:keybind 1,4,25")); // w press + EXPECT(attemptCheckFlag(20, 50), true); + OK(getFromSocket("/dispatch plugin:test:keybind 0,4,25")); // w release + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,108")); // Alt_R release + + clearFlag(); + OK(getFromSocket("/keyword unbind Alt_R,w")); +} + static void testSubmapUniversal() { NLog::log("{}Testing submap universal", Colors::GREEN); @@ -507,6 +536,7 @@ static bool test() { testShortcutRepeatKeyRelease(); testSubmap(); testSubmapUniversal(); + testBindsAfterScroll(); clearFlag(); return !ret; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index dbfa45589..2bfd2db6c 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -647,16 +647,22 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP bool found = false; SDispatchResult res; - if (pressed) { - if (keycodeToModifier(key.keycode)) - m_mkMods.insert(key.keysym); - else - m_mkKeys.insert(key.keysym); - } else { - if (keycodeToModifier(key.keycode)) - m_mkMods.erase(key.keysym); - else - m_mkKeys.erase(key.keysym); + // Skip keysym tracking for events with no keysym (e.g., scroll wheel events). + // Scroll events have keysym=0 and are always "pressed" (never released), + // so without this check, 0 gets inserted into m_mkKeys and never removed, + // breaking multi-key binds (binds flag 's'). See issue #8699. + if (key.keysym != 0) { + if (pressed) { + if (keycodeToModifier(key.keycode)) + m_mkMods.insert(key.keysym); + else + m_mkKeys.insert(key.keysym); + } else { + if (keycodeToModifier(key.keycode)) + m_mkMods.erase(key.keysym); + else + m_mkKeys.erase(key.keysym); + } } for (auto& k : m_keybinds) {