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.
This commit is contained in:
ItsOhen 2025-10-11 02:40:18 +02:00 committed by GitHub
parent 6a01c399a9
commit d599513d4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 102 additions and 26 deletions

View file

@ -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();
}
}

View file

@ -1,4 +1,5 @@
#include <filesystem>
#include <linux/input-event-codes.h>
#include <thread>
#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;
}

View file

@ -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 ###
##############################

View file

@ -2630,7 +2630,7 @@ std::optional<std::string> 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<std::string> CConfigManager::handleWorkspaceRules(const std::strin
return {};
}
std::optional<std::string> CConfigManager::handleSubmap(const std::string& command, const std::string& submap) {
if (submap == "reset")
m_currentSubmap = "";
else
m_currentSubmap = submap;
std::optional<std::string> 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 {};
}

View file

@ -22,6 +22,7 @@
#include "../helpers/memory/Memory.hpp"
#include "../desktop/WindowRule.hpp"
#include "../managers/XWaylandManager.hpp"
#include "../managers/KeybindManager.hpp"
#include <hyprlang.hpp>
@ -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<SExecRequestedRule> m_execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty

View file

@ -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";

View file

@ -643,7 +643,7 @@ eMultiKeyCase CKeybindManager::mkBindMatches(const SP<SKeybind> 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 {};
}
}

View file

@ -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<xkb_keysym_t> 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<std::string, std::function<SDispatchResult(std::string)>> m_dispatchers;
@ -117,7 +125,7 @@ class CKeybindManager {
private:
std::vector<SPressedKeyWithMods> m_pressedKeys;
inline static std::string m_currentSelectedSubmap = "";
inline static SSubmap m_currentSelectedSubmap = {};
std::vector<WP<SKeybind>> m_activeKeybinds;
WP<SKeybind> m_lastLongPressKeybind;