From d599513d4a72d66ac62ffdedc41d6653fa81b39e Mon Sep 17 00:00:00 2001 From: ItsOhen Date: Sat, 11 Oct 2025 02:40:18 +0200 Subject: [PATCH] config: add automatic closing to submaps (#11760) * Allow submaps to auto reset to parent. * Really should be a stack instead. If hyprlang would allow for { } i would be so happy. * Fixed: Somewhat better way to do it.. Lets you define what submap you want to go to instead. * squash! Fixed: Somewhat better way to do it.. * God i hate cf.. * Force clang-format on the whole thing.. * Removed {}. * Added tests Tests and reset fix. --- hyprtester/plugin/src/main.cpp | 6 +-- hyprtester/src/tests/main/keybinds.cpp | 52 +++++++++++++++++++++++++- hyprtester/test.conf | 17 +++++++++ src/config/ConfigManager.cpp | 12 +++--- src/config/ConfigManager.hpp | 3 +- src/debug/HyprCtl.cpp | 8 ++-- src/managers/KeybindManager.cpp | 14 ++++--- src/managers/KeybindManager.hpp | 16 ++++++-- 8 files changed, 102 insertions(+), 26 deletions(-) diff --git a/hyprtester/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp index 07f90e9f6..1d0b68dcc 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -205,9 +205,9 @@ static SDispatchResult vkb(std::string in) { } static SDispatchResult scroll(std::string in) { - int by; + double by; try { - by = std::stoi(in); + by = std::stod(in); } catch (...) { return SDispatchResult{.success = false, .error = "invalid input"}; } Debug::log(LOG, "tester: scrolling by {}", by); @@ -272,4 +272,4 @@ APICALL EXPORT void PLUGIN_EXIT() { g_mouse.reset(); g_keyboard->destroy(); g_keyboard.reset(); -} \ No newline at end of file +} diff --git a/hyprtester/src/tests/main/keybinds.cpp b/hyprtester/src/tests/main/keybinds.cpp index 79f20ce05..ca212fc56 100644 --- a/hyprtester/src/tests/main/keybinds.cpp +++ b/hyprtester/src/tests/main/keybinds.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "../../shared.hpp" #include "../../hyprctlCompat.hpp" @@ -11,7 +12,19 @@ using namespace Hyprutils::Memory; static int ret = 0; static std::string flagFile = "/tmp/hyprtester-keybinds.txt"; -static void clearFlag() { +// Because i don't feel like changing someone elses code. +enum eKeyboardModifierIndex : uint8_t { + MOD_SHIFT = 1, + MOD_CAPS, + MOD_CTRL, + MOD_ALT, + MOD_MOD2, + MOD_MOD3, + MOD_META, + MOD_MOD5 +}; + +static void clearFlag() { std::filesystem::remove(flagFile); } @@ -394,6 +407,41 @@ static void testShortcutRepeatKeyRelease() { Tests::killAllWindows(); } +static void testSubmap() { + const auto press = [](const uint32_t key, const uint32_t mod = 0) { + // +8 because udev -> XKB keycode. + getFromSocket("/dispatch plugin:test:keybind 1," + std::to_string(mod) + "," + std::to_string(key + 8)); + getFromSocket("/dispatch plugin:test:keybind 0," + std::to_string(mod) + "," + std::to_string(key + 8)); + }; + + NLog::log("{}Testing submaps", Colors::GREEN); + // submap 1 no resets + press(KEY_U, MOD_META); + EXPECT_CONTAINS(getFromSocket("/submap"), "submap1"); + press(KEY_O); + Tests::waitUntilWindowsN(1); + EXPECT_CONTAINS(getFromSocket("/submap"), "submap1"); + // submap 2 resets to submap 1 + press(KEY_U); + EXPECT_CONTAINS(getFromSocket("/submap"), "submap2"); + press(KEY_O); + Tests::waitUntilWindowsN(2); + EXPECT_CONTAINS(getFromSocket("/submap"), "submap1"); + // submap 3 resets to default + press(KEY_I); + EXPECT_CONTAINS(getFromSocket("/submap"), "submap3"); + press(KEY_O); + Tests::waitUntilWindowsN(3); + EXPECT_CONTAINS(getFromSocket("/submap"), "default"); + // submap 1 reset via keybind + press(KEY_U, MOD_META); + EXPECT_CONTAINS(getFromSocket("/submap"), "submap1"); + press(KEY_P); + EXPECT_CONTAINS(getFromSocket("/submap"), "default"); + + Tests::killAllWindows(); +} + static bool test() { NLog::log("{}Testing keybinds", Colors::GREEN); @@ -414,6 +462,8 @@ static bool test() { testShortcutRepeat(); testShortcutRepeatKeyRelease(); + testSubmap(); + clearFlag(); return !ret; } diff --git a/hyprtester/test.conf b/hyprtester/test.conf index ab5987190..e6d8cee3e 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -297,6 +297,23 @@ bindl = , XF86AudioPause, exec, playerctl play-pause bindl = , XF86AudioPlay, exec, playerctl play-pause bindl = , XF86AudioPrev, exec, playerctl previous +bind = $mainMod, u, submap, submap1 + +submap = submap1 +bind = , u, submap, submap2 +bind = , i, submap, submap3 +bind = , o, exec, $terminal +bind = , p, submap, reset + +submap = submap2, submap1 +bind = , o, exec, $terminal + +submap = submap3, reset +bind = , o, exec, $terminal + +submap = reset + + ############################## ### WINDOWS AND WORKSPACES ### ############################## diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 936ec383e..47ac4730c 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -2630,7 +2630,7 @@ std::optional CConfigManager::handleBind(const std::string& command if ((!KEY.empty()) || multiKey) { SParsedKey parsedKey = parseKey(KEY); - if (parsedKey.catchAll && m_currentSubmap.empty()) { + if (parsedKey.catchAll && m_currentSubmap.name.empty()) { Debug::log(ERR, "Catchall not allowed outside of submap!"); return "Invalid catchall, catchall keybinds are only allowed in submaps."; } @@ -3011,12 +3011,10 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin return {}; } -std::optional CConfigManager::handleSubmap(const std::string& command, const std::string& submap) { - if (submap == "reset") - m_currentSubmap = ""; - else - m_currentSubmap = submap; - +std::optional CConfigManager::handleSubmap(const std::string&, const std::string& submap) { + const auto SUBMAP = CConstVarList(submap); + m_currentSubmap.name = (SUBMAP[0] == "reset") ? "" : SUBMAP[0]; + m_currentSubmap.reset = SUBMAP[1]; return {}; } diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index cff146f34..7f32be419 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -22,6 +22,7 @@ #include "../helpers/memory/Memory.hpp" #include "../desktop/WindowRule.hpp" #include "../managers/XWaylandManager.hpp" +#include "../managers/KeybindManager.hpp" #include @@ -307,7 +308,7 @@ class CConfigManager { Hyprutils::Animation::CAnimationConfigTree m_animationTree; - std::string m_currentSubmap = ""; // For storing the current keybind submap + SSubmap m_currentSubmap; std::vector m_execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index 116ddac97..ebf7acd68 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -1000,7 +1000,7 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request ret += "d"; ret += std::format("\n\tmodmask: {}\n\tsubmap: {}\n\tkey: {}\n\tkeycode: {}\n\tcatchall: {}\n\tdescription: {}\n\tdispatcher: {}\n\targ: {}\n\n", kb->modmask, - kb->submap, kb->key, kb->keycode, kb->catchAll, kb->description, kb->handler, kb->arg); + kb->submap.name, kb->key, kb->keycode, kb->catchAll, kb->description, kb->handler, kb->arg); } } else { // json @@ -1026,8 +1026,8 @@ 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), 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), escapeJSONStrings(kb->key), + kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler), escapeJSONStrings(kb->arg)); } trimTrailingComma(ret); ret += "]"; @@ -1962,7 +1962,7 @@ static std::string getDescriptions(eHyprCtlOutputFormat format, std::string requ } static std::string submapRequest(eHyprCtlOutputFormat format, std::string request) { - std::string submap = g_pKeybindManager->getCurrentSubmap(); + std::string submap = g_pKeybindManager->getCurrentSubmap().name; if (submap.empty()) submap = "default"; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 81c88d63d..c9008c2f7 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -643,7 +643,7 @@ eMultiKeyCase CKeybindManager::mkBindMatches(const SP keybind) { return mkKeysymSetMatches(keybind->sMkKeys, m_mkKeys); } -std::string CKeybindManager::getCurrentSubmap() { +SSubmap CKeybindManager::getCurrentSubmap() { return m_currentSelectedSubmap; } @@ -795,6 +795,8 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP found = true; // don't process keybinds on submap change. break; } + if (k->handler != "submap" && !k->submap.reset.empty()) + setSubmap(k->submap.reset); } if (pressed && k->repeat) { @@ -2390,19 +2392,19 @@ SDispatchResult CKeybindManager::toggleSwallow(std::string args) { SDispatchResult CKeybindManager::setSubmap(std::string submap) { if (submap == "reset" || submap.empty()) { - m_currentSelectedSubmap = ""; + m_currentSelectedSubmap.name = ""; Debug::log(LOG, "Reset active submap to the default one."); g_pEventManager->postEvent(SHyprIPCEvent{"submap", ""}); - EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap); + EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap.name); return {}; } for (const auto& k : g_pKeybindManager->m_keybinds) { - if (k->submap == submap) { - m_currentSelectedSubmap = submap; + if (k->submap.name == submap) { + m_currentSelectedSubmap.name = submap; Debug::log(LOG, "Changed keybind submap to {}", submap); g_pEventManager->postEvent(SHyprIPCEvent{"submap", submap}); - EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap); + EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap.name); return {}; } } diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index 2272aa8f8..45742e785 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -17,6 +17,14 @@ class IKeyboard; enum eMouseBindMode : int8_t; +struct SSubmap { + std::string name = ""; + std::string reset = ""; + bool operator==(const SSubmap& other) const { + return name == other.name; + } +}; + struct SKeybind { std::string key = ""; std::set sMkKeys = {}; @@ -27,7 +35,7 @@ struct SKeybind { std::string handler = ""; std::string arg = ""; bool locked = false; - std::string submap = ""; + SSubmap submap = {}; std::string description = ""; bool release = false; bool repeat = false; @@ -63,7 +71,7 @@ struct SPressedKeyWithMods { uint32_t keycode = 0; uint32_t modmaskAtPressTime = 0; bool sent = false; - std::string submapAtPress = ""; + SSubmap submapAtPress = {}; Vector2D mousePosAtPress = {}; }; @@ -98,7 +106,7 @@ class CKeybindManager { uint32_t keycodeToModifier(xkb_keycode_t); void clearKeybinds(); void shadowKeybinds(const xkb_keysym_t& doesntHave = 0, const uint32_t doesntHaveCode = 0); - std::string getCurrentSubmap(); + SSubmap getCurrentSubmap(); std::unordered_map> m_dispatchers; @@ -117,7 +125,7 @@ class CKeybindManager { private: std::vector m_pressedKeys; - inline static std::string m_currentSelectedSubmap = ""; + inline static SSubmap m_currentSelectedSubmap = {}; std::vector> m_activeKeybinds; WP m_lastLongPressKeybind;