From dc6fa4fe3efcc618bdf54d465dbc5ad78fbc7925 Mon Sep 17 00:00:00 2001 From: fortime Date: Wed, 5 Mar 2025 14:56:34 +0800 Subject: [PATCH 1/3] feat(waylandim): Send modifiers to the Wayland server when a modifier key event needs to be forwarded. Signed-off-by: fortime --- src/frontend/waylandim/virtualinputcontext.h | 31 ++++++++ src/frontend/waylandim/waylandimserver.cpp | 59 +++++++++++++++ src/frontend/waylandim/waylandimserver.h | 17 +++-- src/frontend/waylandim/waylandimserverv2.cpp | 77 ++++++++++++++++++-- src/frontend/waylandim/waylandimserverv2.h | 1 + 5 files changed, 174 insertions(+), 11 deletions(-) diff --git a/src/frontend/waylandim/virtualinputcontext.h b/src/frontend/waylandim/virtualinputcontext.h index 89f7961b9..44ca1c15e 100644 --- a/src/frontend/waylandim/virtualinputcontext.h +++ b/src/frontend/waylandim/virtualinputcontext.h @@ -17,6 +17,23 @@ #include "fcitx/inputcontext.h" #include "appmonitor.h" +#ifdef __linux__ +#include +#elif __FreeBSD__ +#include +#else +#define KEY_LEFTCTRL 29 +#define KEY_LEFTSHIFT 42 +#define KEY_RIGHTSHIFT 54 +#define KEY_LEFTALT 56 +#define KEY_CAPSLOCK 58 +#define KEY_NUMLOCK 69 +#define KEY_RIGHTCTRL 97 +#define KEY_RIGHTALT 100 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#endif + namespace fcitx { class VirtualInputContextManager; @@ -52,6 +69,20 @@ class VirtualInputContextGlue : public InputContext { void updateSurroundingTextWrapper(); void setCapabilityFlagsWrapper(CapabilityFlags flags); + static bool isModifier(const int keycode) { + int code = keycode - 8; + return code == KEY_LEFTCTRL || + code == KEY_LEFTSHIFT || + code == KEY_RIGHTSHIFT || + code == KEY_LEFTALT || + code == KEY_CAPSLOCK || + code == KEY_NUMLOCK || + code == KEY_RIGHTCTRL || + code == KEY_RIGHTALT || + code == KEY_LEFTMETA || + code == KEY_RIGHTMETA; + } + private: void commitStringImpl(const std::string &text) override { commitStringDelegate(this, text); diff --git a/src/frontend/waylandim/waylandimserver.cpp b/src/frontend/waylandim/waylandimserver.cpp index 86ac29fd3..0bddb025d 100644 --- a/src/frontend/waylandim/waylandimserver.cpp +++ b/src/frontend/waylandim/waylandimserver.cpp @@ -603,6 +603,65 @@ void WaylandIMInputContextV1::sendKeyToVK(uint32_t time, const Key &key, } } +void WaylandIMInputContextV1::sendModifiers(const int keycode, + uint32_t state) const { + if (!ic_ || !server_->state_) { + return; + } + + if (!isModifier(keycode)) { + return; + } + + xkb_mod_mask_t modsDepressed, modsLatched, modsLocked, mask = 0; + xkb_layout_index_t group; + + modsDepressed = xkb_state_serialize_mods(server_->state_.get(), + XKB_STATE_MODS_DEPRESSED); + modsLatched = xkb_state_serialize_mods(server_->state_.get(), + XKB_STATE_MODS_LATCHED); + modsLocked = xkb_state_serialize_mods(server_->state_.get(), + XKB_STATE_MODS_LATCHED); + group = xkb_state_serialize_layout(server_->state_.get(), + XKB_STATE_LAYOUT_LOCKED); + + int code = keycode - 8; + + bool isLock = false; + if (code == KEY_LEFTCTRL || code == KEY_RIGHTCTRL) { + mask = server_->stateMask_.control_mask; + } else if (code == KEY_LEFTSHIFT || code == KEY_LEFTSHIFT) { + mask = server_->stateMask_.shift_mask; + } else if (code == KEY_LEFTALT || code == KEY_RIGHTALT) { + mask = server_->stateMask_.mod1_mask; + } else if (code == KEY_LEFTMETA || code == KEY_RIGHTMETA) { + mask = server_->stateMask_.mod4_mask; + } else if (code == KEY_CAPSLOCK) { + mask = server_->stateMask_.lock_mask; + isLock = true; + } else if (code == KEY_NUMLOCK) { + mask = server_->stateMask_.mod2_mask; + isLock = true; + } + + if (isLock) { + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { + modsLocked ^= mask; + } else { + // only update lock modifiers after released. + return; + } + } else { + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { + modsDepressed &= ~mask; + } else { + modsDepressed |= mask; + } + } + + ic_->modifiers(serial_, modsDepressed, modsLatched, modsLocked, group); +} + void WaylandIMInputContextV1::updatePreeditDelegate(InputContext *ic) const { if (!ic_) { return; diff --git a/src/frontend/waylandim/waylandimserver.h b/src/frontend/waylandim/waylandimserver.h index 634f13aff..7b89c6155 100644 --- a/src/frontend/waylandim/waylandimserver.h +++ b/src/frontend/waylandim/waylandimserver.h @@ -104,12 +104,18 @@ class WaylandIMInputContextV1 : public VirtualInputContextGlue { return; } if (key.rawKey().code() && key.rawKey().states() == KeyState::NoState) { - sendKeyToVK(time_, key.rawKey(), - key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED - : WL_KEYBOARD_KEY_STATE_PRESSED); - if (!key.isRelease()) { + if (isModifier(key.rawKey().code())) { + sendModifiers(key.rawKey().code(), + key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED + : WL_KEYBOARD_KEY_STATE_PRESSED); + } else { sendKeyToVK(time_, key.rawKey(), - WL_KEYBOARD_KEY_STATE_RELEASED); + key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED + : WL_KEYBOARD_KEY_STATE_PRESSED); + if (!key.isRelease()) { + sendKeyToVK(time_, key.rawKey(), + WL_KEYBOARD_KEY_STATE_RELEASED); + } } } else { sendKey(time_, key.rawKey().sym(), @@ -146,6 +152,7 @@ class WaylandIMInputContextV1 : public VirtualInputContextGlue { void sendKey(uint32_t time, uint32_t sym, uint32_t state, KeyStates states) const; void sendKeyToVK(uint32_t time, const Key &key, uint32_t state) const; + void sendModifiers(const int keycode, uint32_t state) const; static uint32_t toModifiers(KeyStates states) { uint32_t modifiers = 0; diff --git a/src/frontend/waylandim/waylandimserverv2.cpp b/src/frontend/waylandim/waylandimserverv2.cpp index a5efadb72..445211737 100644 --- a/src/frontend/waylandim/waylandimserverv2.cpp +++ b/src/frontend/waylandim/waylandimserverv2.cpp @@ -598,6 +598,65 @@ void WaylandIMInputContextV2::sendKeyToVK(uint32_t time, const Key &key, vk_->key(time, code, state); } +void WaylandIMInputContextV2::sendModifiers(const int keycode, + uint32_t state) const { + if (!vkReady_ || !server_->state_) { + return; + } + + if (!isModifier(keycode)) { + return; + } + + xkb_mod_mask_t modsDepressed, modsLatched, modsLocked, mask = 0; + xkb_layout_index_t group; + + modsDepressed = xkb_state_serialize_mods(server_->state_.get(), + XKB_STATE_MODS_DEPRESSED); + modsLatched = xkb_state_serialize_mods(server_->state_.get(), + XKB_STATE_MODS_LATCHED); + modsLocked = xkb_state_serialize_mods(server_->state_.get(), + XKB_STATE_MODS_LATCHED); + group = xkb_state_serialize_layout(server_->state_.get(), + XKB_STATE_LAYOUT_LOCKED); + + int code = keycode - 8; + + bool isLock = false; + if (code == KEY_LEFTCTRL || code == KEY_RIGHTCTRL) { + mask = server_->stateMask_.control_mask; + } else if (code == KEY_LEFTSHIFT || code == KEY_LEFTSHIFT) { + mask = server_->stateMask_.shift_mask; + } else if (code == KEY_LEFTALT || code == KEY_RIGHTALT) { + mask = server_->stateMask_.mod1_mask; + } else if (code == KEY_LEFTMETA || code == KEY_RIGHTMETA) { + mask = server_->stateMask_.mod4_mask; + } else if (code == KEY_CAPSLOCK) { + mask = server_->stateMask_.lock_mask; + isLock = true; + } else if (code == KEY_NUMLOCK) { + mask = server_->stateMask_.mod2_mask; + isLock = true; + } + + if (isLock) { + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { + modsLocked ^= mask; + } else { + // only update lock modifiers after released. + return; + } + } else { + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { + modsDepressed &= ~mask; + } else { + modsDepressed |= mask; + } + } + + vk_->modifiers(modsDepressed, modsLatched, modsLocked, group); +} + void WaylandIMInputContextV2::forwardKeyDelegate( InputContext * /*ic*/, const ForwardKeyEvent &key) const { uint32_t code = 0; @@ -616,13 +675,19 @@ void WaylandIMInputContextV2::forwardKeyDelegate( } } - Key keyWithCode(key.rawKey().sym(), key.rawKey().states(), code); + if (code && isModifier(code)) { + sendModifiers(key.rawKey().code(), + key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED + : WL_KEYBOARD_KEY_STATE_PRESSED); + } else { + Key keyWithCode(key.rawKey().sym(), key.rawKey().states(), code); - sendKeyToVK(time_, keyWithCode, - key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED - : WL_KEYBOARD_KEY_STATE_PRESSED); - if (!key.isRelease()) { - sendKeyToVK(time_, keyWithCode, WL_KEYBOARD_KEY_STATE_RELEASED); + sendKeyToVK(time_, keyWithCode, + key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED + : WL_KEYBOARD_KEY_STATE_PRESSED); + if (!key.isRelease()) { + sendKeyToVK(time_, keyWithCode, WL_KEYBOARD_KEY_STATE_RELEASED); + } } } diff --git a/src/frontend/waylandim/waylandimserverv2.h b/src/frontend/waylandim/waylandimserverv2.h index b4f9ffb06..35c883aac 100644 --- a/src/frontend/waylandim/waylandimserverv2.h +++ b/src/frontend/waylandim/waylandimserverv2.h @@ -129,6 +129,7 @@ class WaylandIMInputContextV2 : public VirtualInputContextGlue { uint32_t group); void repeatInfoCallback(int32_t rate, int32_t delay); void sendKeyToVK(uint32_t time, const Key &key, uint32_t state) const; + void sendModifiers(const int keycode, uint32_t state) const; int32_t repeatRate() const; int32_t repeatDelay() const; From 477f77358b113d78d39deb2206dcdd396c4ca701 Mon Sep 17 00:00:00 2001 From: fortime Date: Mon, 17 Mar 2025 17:21:35 +0800 Subject: [PATCH 2/3] feat(waylandim): use `xkb_keymap_key_get_mods_for_level`, `xkb_state_update_mask` and `xkb_state_update_key` to find all keycodes which changes modifiers. Signed-off-by: fortime --- src/frontend/waylandim/virtualinputcontext.h | 31 -- src/frontend/waylandim/waylandimserver.cpp | 61 +--- src/frontend/waylandim/waylandimserver.h | 24 +- .../waylandim/waylandimserverbase.cpp | 298 ++++++++++++++++++ src/frontend/waylandim/waylandimserverbase.h | 24 ++ src/frontend/waylandim/waylandimserverv2.cpp | 87 ++--- src/frontend/waylandim/waylandimserverv2.h | 3 +- 7 files changed, 367 insertions(+), 161 deletions(-) diff --git a/src/frontend/waylandim/virtualinputcontext.h b/src/frontend/waylandim/virtualinputcontext.h index 44ca1c15e..89f7961b9 100644 --- a/src/frontend/waylandim/virtualinputcontext.h +++ b/src/frontend/waylandim/virtualinputcontext.h @@ -17,23 +17,6 @@ #include "fcitx/inputcontext.h" #include "appmonitor.h" -#ifdef __linux__ -#include -#elif __FreeBSD__ -#include -#else -#define KEY_LEFTCTRL 29 -#define KEY_LEFTSHIFT 42 -#define KEY_RIGHTSHIFT 54 -#define KEY_LEFTALT 56 -#define KEY_CAPSLOCK 58 -#define KEY_NUMLOCK 69 -#define KEY_RIGHTCTRL 97 -#define KEY_RIGHTALT 100 -#define KEY_LEFTMETA 125 -#define KEY_RIGHTMETA 126 -#endif - namespace fcitx { class VirtualInputContextManager; @@ -69,20 +52,6 @@ class VirtualInputContextGlue : public InputContext { void updateSurroundingTextWrapper(); void setCapabilityFlagsWrapper(CapabilityFlags flags); - static bool isModifier(const int keycode) { - int code = keycode - 8; - return code == KEY_LEFTCTRL || - code == KEY_LEFTSHIFT || - code == KEY_RIGHTSHIFT || - code == KEY_LEFTALT || - code == KEY_CAPSLOCK || - code == KEY_NUMLOCK || - code == KEY_RIGHTCTRL || - code == KEY_RIGHTALT || - code == KEY_LEFTMETA || - code == KEY_RIGHTMETA; - } - private: void commitStringImpl(const std::string &text) override { commitStringDelegate(this, text); diff --git a/src/frontend/waylandim/waylandimserver.cpp b/src/frontend/waylandim/waylandimserver.cpp index 0bddb025d..2baebc594 100644 --- a/src/frontend/waylandim/waylandimserver.cpp +++ b/src/frontend/waylandim/waylandimserver.cpp @@ -462,6 +462,8 @@ void WaylandIMInputContextV1::keymapCallback(uint32_t format, int32_t fd, server_->stateMask_.meta_mask = 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Meta"); + server_->updateModMasksMappings(); + server_->parent_->wayland()->call(); } @@ -603,62 +605,15 @@ void WaylandIMInputContextV1::sendKeyToVK(uint32_t time, const Key &key, } } -void WaylandIMInputContextV1::sendModifiers(const int keycode, - uint32_t state) const { +void WaylandIMInputContextV1::sendModifiers( + const WlModifiersParams ¶ms) const { if (!ic_ || !server_->state_) { return; } - - if (!isModifier(keycode)) { - return; - } - - xkb_mod_mask_t modsDepressed, modsLatched, modsLocked, mask = 0; - xkb_layout_index_t group; - - modsDepressed = xkb_state_serialize_mods(server_->state_.get(), - XKB_STATE_MODS_DEPRESSED); - modsLatched = xkb_state_serialize_mods(server_->state_.get(), - XKB_STATE_MODS_LATCHED); - modsLocked = xkb_state_serialize_mods(server_->state_.get(), - XKB_STATE_MODS_LATCHED); - group = xkb_state_serialize_layout(server_->state_.get(), - XKB_STATE_LAYOUT_LOCKED); - - int code = keycode - 8; - - bool isLock = false; - if (code == KEY_LEFTCTRL || code == KEY_RIGHTCTRL) { - mask = server_->stateMask_.control_mask; - } else if (code == KEY_LEFTSHIFT || code == KEY_LEFTSHIFT) { - mask = server_->stateMask_.shift_mask; - } else if (code == KEY_LEFTALT || code == KEY_RIGHTALT) { - mask = server_->stateMask_.mod1_mask; - } else if (code == KEY_LEFTMETA || code == KEY_RIGHTMETA) { - mask = server_->stateMask_.mod4_mask; - } else if (code == KEY_CAPSLOCK) { - mask = server_->stateMask_.lock_mask; - isLock = true; - } else if (code == KEY_NUMLOCK) { - mask = server_->stateMask_.mod2_mask; - isLock = true; - } - - if (isLock) { - if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { - modsLocked ^= mask; - } else { - // only update lock modifiers after released. - return; - } - } else { - if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { - modsDepressed &= ~mask; - } else { - modsDepressed |= mask; - } - } - + const auto modsDepressed = std::get<0>(params); + const auto modsLatched = std::get<1>(params); + const auto modsLocked = std::get<2>(params); + const auto group = std::get<3>(params); ic_->modifiers(serial_, modsDepressed, modsLatched, modsLocked, group); } diff --git a/src/frontend/waylandim/waylandimserver.h b/src/frontend/waylandim/waylandimserver.h index 7b89c6155..18d2e6185 100644 --- a/src/frontend/waylandim/waylandimserver.h +++ b/src/frontend/waylandim/waylandimserver.h @@ -103,25 +103,24 @@ class WaylandIMInputContextV1 : public VirtualInputContextGlue { if (!ic_) { return; } + + auto state = key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED + : WL_KEYBOARD_KEY_STATE_PRESSED; + if (key.rawKey().code() && key.rawKey().states() == KeyState::NoState) { - if (isModifier(key.rawKey().code())) { - sendModifiers(key.rawKey().code(), - key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED - : WL_KEYBOARD_KEY_STATE_PRESSED); + auto params = server_->mayChangeModifiers(key.rawKey().code(), + state); + if (params) { + sendModifiers(params.value()); } else { - sendKeyToVK(time_, key.rawKey(), - key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED - : WL_KEYBOARD_KEY_STATE_PRESSED); + sendKeyToVK(time_, key.rawKey(), state); if (!key.isRelease()) { sendKeyToVK(time_, key.rawKey(), WL_KEYBOARD_KEY_STATE_RELEASED); } } } else { - sendKey(time_, key.rawKey().sym(), - key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED - : WL_KEYBOARD_KEY_STATE_PRESSED, - key.rawKey().states()); + sendKey(time_, key.rawKey().sym(), state, key.rawKey().states()); if (!key.isRelease()) { sendKey(time_, key.rawKey().sym(), WL_KEYBOARD_KEY_STATE_RELEASED, key.rawKey().states()); @@ -152,7 +151,8 @@ class WaylandIMInputContextV1 : public VirtualInputContextGlue { void sendKey(uint32_t time, uint32_t sym, uint32_t state, KeyStates states) const; void sendKeyToVK(uint32_t time, const Key &key, uint32_t state) const; - void sendModifiers(const int keycode, uint32_t state) const; + + void sendModifiers(const WlModifiersParams ¶ms) const; static uint32_t toModifiers(KeyStates states) { uint32_t modifiers = 0; diff --git a/src/frontend/waylandim/waylandimserverbase.cpp b/src/frontend/waylandim/waylandimserverbase.cpp index 716234f68..e85db3d1f 100644 --- a/src/frontend/waylandim/waylandimserverbase.cpp +++ b/src/frontend/waylandim/waylandimserverbase.cpp @@ -22,6 +22,28 @@ #include "waylandim.h" #include "wl_seat.h" +void updateModMasksMappingsForKey( + struct xkb_keymap *keymap, + struct xkb_state *state, + xkb_keycode_t key, + xkb_layout_index_t layout, + xkb_level_index_t level, + std::unordered_map>> + &keycodeToNormalModMasks, + std::unordered_map>> + &keycodeToLockModMasks); + +std::vector getKeyModMasks( + struct xkb_keymap *keymap, + xkb_keycode_t key, + xkb_layout_index_t layout, + xkb_level_index_t level); + namespace fcitx { WaylandIMServerBase::WaylandIMServerBase(wl_display *display, FocusGroup *group, @@ -85,4 +107,280 @@ int32_t WaylandIMServerBase::repeatDelay( return 600; } +void WaylandIMServerBase::updateModMasksMappings() { + if (!keymap_) { + return; + } + + std::unordered_map>> + keycodeToNormalModMasks; + + std::unordered_map>> + keycodeToLockModMasks; + + // used for calculating modifier masks. + struct xkb_state* state = xkb_state_new(keymap_.get()); + + xkb_keycode_t min = xkb_keymap_min_keycode(keymap_.get()); + xkb_keycode_t max = xkb_keymap_max_keycode(keymap_.get()); + + for (auto key = min; key <= max; ++key) { + xkb_layout_index_t layouts = xkb_keymap_num_layouts_for_key( + keymap_.get(), key); + for (xkb_layout_index_t layout = 0; layout < layouts; ++layout) { + xkb_level_index_t levels = xkb_keymap_num_levels_for_key( + keymap_.get(), key, layout); + for (xkb_level_index_t level = 0; level < levels; ++level) { + updateModMasksMappingsForKey( + keymap_.get(), + state, + key, + layout, + level, + keycodeToNormalModMasks, + keycodeToLockModMasks); + } + } + } + + xkb_state_unref(state); + + keycodeToNormalModMasks_ = keycodeToNormalModMasks; + keycodeToLockModMasks_ = keycodeToLockModMasks; +} + +std::optional WaylandIMServerBase::mayChangeModifiers( + const xkb_keycode_t key, + const uint32_t state) const { + if (!keymap_ || !state_) { + return std::nullopt; + } + + xkb_mod_mask_t modsDepressed, modsLatched, modsLocked, modsEffective; + xkb_layout_index_t layout; + modsDepressed = xkb_state_serialize_mods(state_.get(), + XKB_STATE_MODS_DEPRESSED); + modsLatched = xkb_state_serialize_mods(state_.get(), + XKB_STATE_MODS_LATCHED); + modsLocked = xkb_state_serialize_mods(state_.get(), XKB_STATE_MODS_LOCKED); + modsEffective = xkb_state_serialize_mods(state_.get(), + XKB_STATE_MODS_EFFECTIVE); + layout = xkb_state_serialize_layout(state_.get(), XKB_STATE_LAYOUT_LOCKED); + + if (auto it = keycodeToNormalModMasks_.find(key); + it != keycodeToNormalModMasks_.end()) { + for (auto const &modMasks : it->second) { + if ((modsEffective & std::get<0>(modMasks)) == 0) { + continue; + } + const WlModifiersParams ¶ms = std::get<1>(modMasks); + if (layout != std::get<3>(params)) { + continue; + } + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + return std::optional({ + modsDepressed | (std::get<0>(params)), + modsLatched | (std::get<1>(params)), + modsLocked | (std::get<2>(params)), + layout}); + } else { + return std::optional({ + modsDepressed & (~std::get<0>(params)), + modsLatched & (~std::get<1>(params)), + modsLocked & (~std::get<2>(params)), + layout}); + } + } + } + + if (auto it = keycodeToLockModMasks_.find(key); + it != keycodeToLockModMasks_.end()) { + for (auto const &modMasks : it->second) { + if ((modsEffective & std::get<0>(modMasks)) == 0) { + continue; + } + + const WlModifiersParams &pressedParams = std::get<1>(modMasks); + if (layout != std::get<3>(pressedParams)) { + continue; + } + const xkb_mod_mask_t downModsDepressed = std::get<0>(pressedParams); + const xkb_mod_mask_t downModsLatched = std::get<1>(pressedParams); + const xkb_mod_mask_t downModsLocked = std::get<2>(pressedParams); + + const WlModifiersParams &releasedParams = std::get<2>(modMasks); + const xkb_mod_mask_t upModsDepressed = std::get<0>(releasedParams); + const xkb_mod_mask_t upModsLatched = std::get<1>(releasedParams); + const xkb_mod_mask_t upModsLocked = std::get<2>(releasedParams); + + if (((upModsDepressed & modsDepressed) == upModsDepressed) && + ((upModsLatched & modsLatched) == upModsLatched) && + ((upModsLocked & modsLocked) == upModsLocked)) { + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + // the second time pressing the key. + // the modifiers is the same as the first pressing. + return std::optional({ + modsDepressed | downModsDepressed, + modsLatched | downModsLatched, + modsLocked | downModsLocked, + layout}); + } else { + // releasing in the first time pressing. + return std::optional({ + (modsDepressed & (~downModsDepressed)) | + upModsDepressed, + (modsLatched & (~downModsLatched)) | upModsLatched, + (modsLocked & (~downModsLocked)) | upModsLocked, + layout}); + } + } else { + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + // the first time pressing the key. + return std::optional({ + modsDepressed | downModsDepressed, + modsLatched | downModsLatched, + modsLocked | downModsLocked, + layout}); + } else { + // releasing in the second time pressing. + // reset all related masks. + return std::optional({ + modsDepressed & (~downModsDepressed), + modsLatched & (~downModsLatched), + modsLocked & (~downModsLocked), + layout}); + } + } + } + } + + return std::nullopt; +} + } // namespace fcitx + +void updateModMasksMappingsForKey( + struct xkb_keymap *keymap, + struct xkb_state *state, + xkb_keycode_t key, + xkb_layout_index_t layout, + xkb_level_index_t level, + std::unordered_map>> + &keycodeToNormalModMasks, + std::unordered_map>> + &keycodeToLockModMasks) { + + auto modMasks = getKeyModMasks(keymap, key, layout, level); + if (!keycodeToNormalModMasks.contains(key)) { + keycodeToNormalModMasks[key] = std::vector>(); + } + if (!keycodeToLockModMasks.contains(key)) { + keycodeToLockModMasks[key] = std::vector>(); + } + + xkb_mod_mask_t modsDepressed, modsLatched, modsLocked; + xkb_mod_mask_t downModsDepressed, downModsLatched, downModsLocked; + xkb_mod_mask_t upModsDepressed, upModsLatched, upModsLocked; + + for (auto modMask : modMasks) { + // check if pressing this key will modify the state of modifiers under + // the certain state of modifiers. + xkb_state_update_mask(state, modMask, 0, 0, 0, 0, layout); + modsDepressed = xkb_state_serialize_mods(state, + XKB_STATE_MODS_DEPRESSED); + modsLatched = xkb_state_serialize_mods(state, XKB_STATE_MODS_LATCHED); + modsLocked = xkb_state_serialize_mods(state, XKB_STATE_MODS_LOCKED); + + enum xkb_state_component downComponentMask = xkb_state_update_key( + state, key, XKB_KEY_DOWN); + downModsDepressed = xkb_state_serialize_mods(state, + XKB_STATE_MODS_DEPRESSED); + downModsLatched = xkb_state_serialize_mods(state, + XKB_STATE_MODS_LATCHED); + downModsLocked = xkb_state_serialize_mods(state, XKB_STATE_MODS_LOCKED); + + enum xkb_state_component upComponentMask = xkb_state_update_key( + state, key, XKB_KEY_UP); + upModsDepressed = xkb_state_serialize_mods(state, + XKB_STATE_MODS_DEPRESSED); + upModsLatched = xkb_state_serialize_mods(state, XKB_STATE_MODS_LATCHED); + upModsLocked = xkb_state_serialize_mods(state, XKB_STATE_MODS_LOCKED); + + if (downComponentMask != 0 || upComponentMask != 0) { + if (upModsDepressed == modsDepressed && + upModsLatched == modsLatched && + upModsLocked == modsLocked) { + keycodeToNormalModMasks[key].push_back({ + modMask, + { + downModsDepressed ^ modsDepressed, + downModsLatched ^ modsLatched, + downModsLocked ^ modsLocked, + layout}}); + } else { + keycodeToLockModMasks[key].push_back({ + modMask, + { + downModsDepressed ^ modsDepressed, + downModsLatched ^ modsLatched, + downModsLocked ^ modsLocked, + layout}, + { + upModsDepressed ^ modsDepressed, + upModsLatched ^ modsLatched, + upModsLocked ^ modsLocked, + layout}}); + } + } + } +} + +std::vector getKeyModMasks( + struct xkb_keymap *keymap, + xkb_keycode_t key, + xkb_layout_index_t layout, + xkb_level_index_t level) { + + std::vector out; + size_t masks_size = 4; + xkb_mod_index_t* masks = NULL; + while (true) { + if (*masks) { + delete[] masks; + } + masks = new xkb_mod_index_t[masks_size]; + size_t num = xkb_keymap_key_get_mods_for_level( + keymap, key, layout, level, masks, masks_size); + if (num == masks_size) { + // if num equals to masks_size, it may contain more items. + masks_size <<= 1; + continue; + } + masks_size = num; + break; + } + + if (masks) { + for (size_t i = 0; i < masks_size; ++i) { + out.push_back(masks[i]); + } + delete[] masks; + } + + return out; +} diff --git a/src/frontend/waylandim/waylandimserverbase.h b/src/frontend/waylandim/waylandimserverbase.h index ec610f6c5..0bcff6823 100644 --- a/src/frontend/waylandim/waylandimserverbase.h +++ b/src/frontend/waylandim/waylandimserverbase.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include "fcitx-utils/key.h" @@ -23,6 +24,12 @@ namespace fcitx { +typedef std::tuple +WlModifiersParams; + class WaylandIMServerBase { public: WaylandIMServerBase(wl_display *display, FocusGroup *group, @@ -35,6 +42,10 @@ class WaylandIMServerBase { std::optional mayCommitAsText(const Key &key, uint32_t state) const; + std::optional mayChangeModifiers( + const xkb_keycode_t key, + const uint32_t state) const; + int32_t repeatRate( const std::shared_ptr &seat, const std::optional> &defaultValue) const; @@ -43,6 +54,8 @@ class WaylandIMServerBase { const std::optional> &defaultValue) const; protected: + void updateModMasksMappings(); + FocusGroup *group_; std::string name_; WaylandIMModule *parent_; @@ -54,6 +67,17 @@ class WaylandIMServerBase { KeyStates modifiers_; + std::unordered_map>> + keycodeToNormalModMasks_; + + std::unordered_map>> + keycodeToLockModMasks_; + private: std::optional> repeatInfo( const std::shared_ptr &seat, diff --git a/src/frontend/waylandim/waylandimserverv2.cpp b/src/frontend/waylandim/waylandimserverv2.cpp index 445211737..b8231a741 100644 --- a/src/frontend/waylandim/waylandimserverv2.cpp +++ b/src/frontend/waylandim/waylandimserverv2.cpp @@ -23,6 +23,7 @@ #include "fcitx-utils/eventloopinterface.h" #include "fcitx-utils/key.h" #include "fcitx-utils/keysym.h" +#include #include "fcitx-utils/macros.h" #include "fcitx-utils/textformatflags.h" #include "fcitx-utils/unixfd.h" @@ -462,6 +463,8 @@ void WaylandIMInputContextV2::keymapCallback(uint32_t format, int32_t fd, } } + server_->updateModMasksMappings(); + server_->parent_->wayland()->call(); } @@ -598,62 +601,15 @@ void WaylandIMInputContextV2::sendKeyToVK(uint32_t time, const Key &key, vk_->key(time, code, state); } -void WaylandIMInputContextV2::sendModifiers(const int keycode, - uint32_t state) const { +void WaylandIMInputContextV2::sendModifiers( + const WlModifiersParams ¶ms) const { if (!vkReady_ || !server_->state_) { return; } - - if (!isModifier(keycode)) { - return; - } - - xkb_mod_mask_t modsDepressed, modsLatched, modsLocked, mask = 0; - xkb_layout_index_t group; - - modsDepressed = xkb_state_serialize_mods(server_->state_.get(), - XKB_STATE_MODS_DEPRESSED); - modsLatched = xkb_state_serialize_mods(server_->state_.get(), - XKB_STATE_MODS_LATCHED); - modsLocked = xkb_state_serialize_mods(server_->state_.get(), - XKB_STATE_MODS_LATCHED); - group = xkb_state_serialize_layout(server_->state_.get(), - XKB_STATE_LAYOUT_LOCKED); - - int code = keycode - 8; - - bool isLock = false; - if (code == KEY_LEFTCTRL || code == KEY_RIGHTCTRL) { - mask = server_->stateMask_.control_mask; - } else if (code == KEY_LEFTSHIFT || code == KEY_LEFTSHIFT) { - mask = server_->stateMask_.shift_mask; - } else if (code == KEY_LEFTALT || code == KEY_RIGHTALT) { - mask = server_->stateMask_.mod1_mask; - } else if (code == KEY_LEFTMETA || code == KEY_RIGHTMETA) { - mask = server_->stateMask_.mod4_mask; - } else if (code == KEY_CAPSLOCK) { - mask = server_->stateMask_.lock_mask; - isLock = true; - } else if (code == KEY_NUMLOCK) { - mask = server_->stateMask_.mod2_mask; - isLock = true; - } - - if (isLock) { - if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { - modsLocked ^= mask; - } else { - // only update lock modifiers after released. - return; - } - } else { - if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { - modsDepressed &= ~mask; - } else { - modsDepressed |= mask; - } - } - + const auto modsDepressed = std::get<0>(params); + const auto modsLatched = std::get<1>(params); + const auto modsLocked = std::get<2>(params); + const auto group = std::get<3>(params); vk_->modifiers(modsDepressed, modsLatched, modsLocked, group); } @@ -675,20 +631,23 @@ void WaylandIMInputContextV2::forwardKeyDelegate( } } - if (code && isModifier(code)) { - sendModifiers(key.rawKey().code(), - key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED - : WL_KEYBOARD_KEY_STATE_PRESSED); - } else { - Key keyWithCode(key.rawKey().sym(), key.rawKey().states(), code); + auto state = key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED + : WL_KEYBOARD_KEY_STATE_PRESSED; - sendKeyToVK(time_, keyWithCode, - key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED - : WL_KEYBOARD_KEY_STATE_PRESSED); - if (!key.isRelease()) { - sendKeyToVK(time_, keyWithCode, WL_KEYBOARD_KEY_STATE_RELEASED); + if (code) { + auto params = server_->mayChangeModifiers(code, state); + if (params) { + sendModifiers(params.value()); + return; } } + + Key keyWithCode(key.rawKey().sym(), key.rawKey().states(), code); + + sendKeyToVK(time_, keyWithCode, state); + if (!key.isRelease()) { + sendKeyToVK(time_, keyWithCode, WL_KEYBOARD_KEY_STATE_RELEASED); + } } void WaylandIMInputContextV2::updatePreeditDelegate(InputContext *ic) const { diff --git a/src/frontend/waylandim/waylandimserverv2.h b/src/frontend/waylandim/waylandimserverv2.h index 35c883aac..a146b6361 100644 --- a/src/frontend/waylandim/waylandimserverv2.h +++ b/src/frontend/waylandim/waylandimserverv2.h @@ -129,7 +129,8 @@ class WaylandIMInputContextV2 : public VirtualInputContextGlue { uint32_t group); void repeatInfoCallback(int32_t rate, int32_t delay); void sendKeyToVK(uint32_t time, const Key &key, uint32_t state) const; - void sendModifiers(const int keycode, uint32_t state) const; + + void sendModifiers(const WlModifiersParams ¶ms) const; int32_t repeatRate() const; int32_t repeatDelay() const; From 0e10c3b8d6a9fae740ef5a297e91a0e4b306e1ae Mon Sep 17 00:00:00 2001 From: fortime Date: Tue, 15 Apr 2025 17:52:00 +0800 Subject: [PATCH 3/3] fix(waylandim): dereference null pointer and incorrect mask comparison. Signed-off-by: fortime --- src/frontend/waylandim/waylandimserverbase.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/waylandim/waylandimserverbase.cpp b/src/frontend/waylandim/waylandimserverbase.cpp index e85db3d1f..e5488d3f8 100644 --- a/src/frontend/waylandim/waylandimserverbase.cpp +++ b/src/frontend/waylandim/waylandimserverbase.cpp @@ -175,7 +175,7 @@ std::optional WaylandIMServerBase::mayChangeModifiers( if (auto it = keycodeToNormalModMasks_.find(key); it != keycodeToNormalModMasks_.end()) { for (auto const &modMasks : it->second) { - if ((modsEffective & std::get<0>(modMasks)) == 0) { + if ((modsEffective & std::get<0>(modMasks)) != std::get<0>(modMasks)) { continue; } const WlModifiersParams ¶ms = std::get<1>(modMasks); @@ -360,7 +360,7 @@ std::vector getKeyModMasks( size_t masks_size = 4; xkb_mod_index_t* masks = NULL; while (true) { - if (*masks) { + if (masks) { delete[] masks; } masks = new xkb_mod_index_t[masks_size];