diff --git a/include/wayfire/config/types.hpp b/include/wayfire/config/types.hpp index 2e8f38d..0ae125e 100644 --- a/include/wayfire/config/types.hpp +++ b/include/wayfire/config/types.hpp @@ -134,7 +134,14 @@ std::string to_string(const color_t& value); /** * A list of valid modifiers. - * The enumerations values are the same as the ones in wlroots. + * The modifier values are the same as the ones in wlroots. + * + * The press words are not modifiers: they say which press of the key fires a key + * binding, and live in bits that wlroots never uses. and fire + * on the second and third press in quick succession, when the key is held. + * A compositor that dispatches them matches the binding against the key with the + * press bit added; elsewhere a binding with a press word never matches. At most one + * press word per binding, only with a key (not a button, not modifiers alone). */ enum keyboard_modifier_t { @@ -146,6 +153,11 @@ enum keyboard_modifier_t KEYBOARD_MODIFIER_ALT = 8, /* Windows/Mac logo modifier, */ KEYBOARD_MODIFIER_LOGO = 64, + /* Press words: , , */ + KEYBOARD_PRESS_DOUBLE = 1 << 16, + KEYBOARD_PRESS_TRIPLE = 1 << 17, + KEYBOARD_PRESS_LONG = 1 << 18, + KEYBOARD_PRESS_MASK = KEYBOARD_PRESS_DOUBLE | KEYBOARD_PRESS_TRIPLE | KEYBOARD_PRESS_LONG, }; /** diff --git a/src/types.cpp b/src/types.cpp index 473954b..4deab4b 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -286,6 +286,14 @@ static std::map modifier_names = {"super", wf::KEYBOARD_MODIFIER_LOGO}, }; +/* Press words: which press of the key fires the binding (see keyboard_modifier_t). */ +static std::map press_names = +{ + {"double", wf::KEYBOARD_PRESS_DOUBLE}, + {"triple", wf::KEYBOARD_PRESS_TRIPLE}, + {"long", wf::KEYBOARD_PRESS_LONG}, +}; + static std::string binding_to_string(general_binding_t binding) { std::string result = ""; @@ -297,6 +305,15 @@ static std::string binding_to_string(general_binding_t binding) } } + /* The press word follows the real modifiers: KEY_V */ + for (auto& pair : press_names) + { + if (binding.mods & pair.second) + { + result += "<" + pair.first + "> "; + } + } + if (binding.value > 0) { auto evdev_name = libevdev_event_code_get_name(EV_KEY, binding.value); @@ -356,6 +373,14 @@ static std::optional parse_binding( if (modifier_names.count(tokens[i])) { result.mods |= modifier_names[tokens[i]]; + } else if (press_names.count(tokens[i])) + { + if (result.mods & wf::KEYBOARD_PRESS_MASK) + { + return {}; // at most one press word + } + + result.mods |= press_names[tokens[i]]; } else { return {}; // invalid modifier @@ -379,6 +404,13 @@ static std::optional parse_binding( result.value = code; + /* A press word needs a key: modifiers alone (or a press word alone) cannot + * have one. Buttons are refused by the button parser. */ + if ((result.mods & wf::KEYBOARD_PRESS_MASK) && (code == 0)) + { + return {}; + } + /* Do one last check: if we remove whitespaces, and add a whitespace after * each > character, then the resulting string should be almost equal to the * minimal description, generated by binding_to_string(). @@ -496,6 +528,12 @@ std::optional wf::option_type::from_string( return {}; } + /* Press words are for keys only */ + if (parsed.mods & wf::KEYBOARD_PRESS_MASK) + { + return {}; + } + if (parsed.value == 0) { return {}; @@ -837,6 +875,16 @@ std::optional wf::option_type::from_string( if (!is_valid_binding) { + /* A press word marks a key binding: one that does not parse is a + * mistake, not an extension for some plugin. */ + for (auto& word : split_at(token, "<>" + whitespace_chars)) + { + if (press_names.count(word)) + { + return {}; + } + } + binding.priv->extensions.push_back(filter_trailing_leading(token)); } } diff --git a/test/types_test.cpp b/test/types_test.cpp index 71df743..c6e53c8 100644 --- a/test/types_test.cpp +++ b/test/types_test.cpp @@ -167,6 +167,34 @@ TEST_CASE("wf::keybinding_t") CHECK(from_string(to_string(binding2)).value() == binding2); } +TEST_CASE("wf::keybinding_t press words") +{ + using wk_t = wf::keybinding_t; + const uint32_t super_double = wf::KEYBOARD_MODIFIER_LOGO | wf::KEYBOARD_PRESS_DOUBLE; + + /* Parsed in any order, printed after the real modifiers */ + auto doubled = from_string(" KEY_V").value(); + CHECK(doubled.get_modifiers() == super_double); + CHECK(doubled.get_key() == KEY_V); + CHECK(from_string("KEY_V").value() == doubled); + CHECK(to_string(doubled) == " KEY_V"); + CHECK(to_string(from_string(" KEY_T").value()) == + " KEY_T"); + CHECK(to_string(from_string(" KEY_F11").value()) == " KEY_F11"); + + /* A press word is part of the match: a plain press never equals it */ + CHECK(!(doubled == wk_t{wf::KEYBOARD_MODIFIER_LOGO, KEY_V})); + CHECK(from_string(to_string(doubled)).value() == doubled); + + /* At most one press word, only with a key */ + CHECK(!from_string(" KEY_V")); + CHECK(!from_string(" KEY_V")); + CHECK(!from_string(" ")); + CHECK(!from_string("")); + CHECK(!from_string(" BTN_LEFT")); + CHECK(!from_string(" KEY_V")); +} + TEST_CASE("wf::buttonbinding_t") { /* Test simple constructor */ @@ -297,6 +325,26 @@ TEST_CASE("wf::hotspot_binding_t") CHECK(!from_string("hotspot top 110")); } +TEST_CASE("press words in button and activator bindings") +{ + using namespace wf; + CHECK(!from_string(" BTN_LEFT")); + CHECK(!from_string(" BTN_LEFT")); + + auto act = from_string(" KEY_K | KEY_SLASH").value(); + CHECK(act.has_match(keybinding_t{KEYBOARD_MODIFIER_LOGO, KEY_K})); + CHECK(act.has_match(keybinding_t{KEYBOARD_MODIFIER_LOGO | KEYBOARD_PRESS_DOUBLE, KEY_SLASH})); + CHECK(!act.has_match(keybinding_t{KEYBOARD_MODIFIER_LOGO, KEY_SLASH})); + CHECK(to_string(act) == " KEY_K | KEY_SLASH"); + + /* An alternative with a press word that does not parse makes the whole + * activator invalid instead of becoming an extension */ + CHECK(!from_string(" KEY_K | BTN_LEFT")); + CHECK(!from_string(" KEY_K | ")); + CHECK(!from_string(" KEY_V")); + CHECK(from_string(" KEY_K | some_extension").has_value()); +} + TEST_CASE("wf::activatorbinding_t") { using namespace wf;