Re-apply remap_modifiers to a reloaded xkb keymap state

On X11 glfw_xkb_compile_keymap() gets its state from
xkb_x11_state_new_from_device(), which seeds it with the server's physical
modifier mask. Every other path into that state goes through
glfw_xkb_update_modifiers(), where the permutation is applied, so after a
keymap reload the state held physical modifiers until the next XkbStateNotify
happened to arrive. Any key pressed in that window was encoded with the
modifier on the keycap: with ctrl held across a reload under
`remap_modifiers ctrl:super`, Ctrl+A reached the child as a literal 0x01.

Keymap reloads are routine - switching layout causes one, and so does any
client asking X for a keysym it must bind to a spare keycode.

Re-apply the permutation to the freshly loaded state, after
glfw_xkb_update_masks() has computed the modifier indices it is expressed in.
On Wayland the new state is empty, so this is a no-op there.
This commit is contained in:
bjohas 2026-08-12 13:50:42 +01:00
parent 48625a824d
commit bd0a39f495

38
glfw/xkb_glfw.c vendored
View file

@ -724,6 +724,39 @@ update_modifiers(_GLFWXKBData *xkb) {
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
}
#ifdef _GLFW_X11
static xkb_mod_mask_t remap_xkb_mask(_GLFWXKBData *xkb, xkb_mod_mask_t mask);
// A freshly loaded state is seeded from the server's live modifier state by
// xkb_x11_state_new_from_device(), which knows nothing about remap_modifiers.
// Every other way the state changes goes through glfw_xkb_update_modifiers(),
// where the permutation is applied, so without this the state holds physical
// modifiers from the reload until the next XkbStateNotify happens to arrive, and
// key events in that window report the modifier on the keycap rather than the
// one it was remapped to.
//
// X11 only: elsewhere xkb_glfw_load_state() is xkb_state_new(), which starts
// empty, so there is nothing to re-apply.
static void
reapply_modifier_remap_to_state(_GLFWXKBData *xkb) {
if (!_glfw.modifierRemapMask || !xkb->states.state) return;
const xkb_mod_mask_t depressed = xkb_state_serialize_mods(xkb->states.state, XKB_STATE_MODS_DEPRESSED);
const xkb_mod_mask_t latched = xkb_state_serialize_mods(xkb->states.state, XKB_STATE_MODS_LATCHED);
const xkb_mod_mask_t locked = xkb_state_serialize_mods(xkb->states.state, XKB_STATE_MODS_LOCKED);
const xkb_mod_mask_t remapped = remap_xkb_mask(xkb, depressed);
if (remapped == depressed && latched == remap_xkb_mask(xkb, latched) && locked == remap_xkb_mask(xkb, locked)) return;
debug("remap_modifier: re-applying to reloaded xkb state, depressed 0x%x -> 0x%x\n", depressed, remapped);
xkb_state_update_mask(
xkb->states.state,
remapped,
remap_xkb_mask(xkb, latched),
remap_xkb_mask(xkb, locked),
xkb_state_serialize_layout(xkb->states.state, XKB_STATE_LAYOUT_DEPRESSED),
xkb_state_serialize_layout(xkb->states.state, XKB_STATE_LAYOUT_LATCHED),
xkb_state_serialize_layout(xkb->states.state, XKB_STATE_LAYOUT_LOCKED));
}
#endif
bool
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
const char *err;
@ -751,6 +784,11 @@ glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
i != xkb->capsLockIdx && i != xkb->numLockIdx)
xkb->unknownModifiers[j++] = i;
}
#ifdef _GLFW_X11
// after glfw_xkb_update_masks(), which computes the modifier indices the
// remap is expressed in, and before update_modifiers() reads the state
reapply_modifier_remap_to_state(xkb);
#endif
xkb->states.modifiers = 0;
xkb->states.activeUnknownModifiers = 0;
update_modifiers(xkb);