Keyboard: check translated keysyms for mappings
If our current approch without xkbcommon translation does not match any mapping on a key event attempt to match the translated keysym as well. This makes e.g. the keypad number keys (e.g. KP_1) work intuitively as they may require translation with numlock active. The reason we stopped doing this in I7c02ebcbc was due to layout where e.g. Super+Shift+Space is translated as Space with the Shift modifier consumed, thereby conflicting with a separate mapping for Super+Space. This should not be a issue anymore though as we now only run a maximum of one mapping per key event and we attemt to match mappings without xkbcommon translation before attempting with translation.
This commit is contained in:
		@ -69,6 +69,7 @@ pub fn match(
 | 
				
			|||||||
    modifiers: wlr.Keyboard.ModifierMask,
 | 
					    modifiers: wlr.Keyboard.ModifierMask,
 | 
				
			||||||
    released: bool,
 | 
					    released: bool,
 | 
				
			||||||
    xkb_state: *xkb.State,
 | 
					    xkb_state: *xkb.State,
 | 
				
			||||||
 | 
					    method: enum { no_translate, translate },
 | 
				
			||||||
) bool {
 | 
					) bool {
 | 
				
			||||||
    if (released != self.options.release) return false;
 | 
					    if (released != self.options.release) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -79,6 +80,8 @@ pub fn match(
 | 
				
			|||||||
    // will fall back to the active layout if so.
 | 
					    // will fall back to the active layout if so.
 | 
				
			||||||
    const layout_index = self.options.layout_index orelse xkb_state.keyGetLayout(keycode);
 | 
					    const layout_index = self.options.layout_index orelse xkb_state.keyGetLayout(keycode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (method) {
 | 
				
			||||||
 | 
					        .no_translate => {
 | 
				
			||||||
            // Get keysyms from the base layer, as if modifiers didn't change keysyms.
 | 
					            // Get keysyms from the base layer, as if modifiers didn't change keysyms.
 | 
				
			||||||
            // E.g. pressing `Super+Shift 1` does not translate to `Super Exclam`.
 | 
					            // E.g. pressing `Super+Shift 1` does not translate to `Super Exclam`.
 | 
				
			||||||
            const keysyms = keymap.keyGetSymsByLevel(
 | 
					            const keysyms = keymap.keyGetSymsByLevel(
 | 
				
			||||||
@ -87,20 +90,36 @@ pub fn match(
 | 
				
			|||||||
                0,
 | 
					                0,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (std.meta.eql(modifiers, self.modifiers)) {
 | 
					            if (@as(u32, @bitCast(modifiers)) == @as(u32, @bitCast(self.modifiers))) {
 | 
				
			||||||
                for (keysyms) |sym| {
 | 
					                for (keysyms) |sym| {
 | 
				
			||||||
                    if (sym == self.keysym) {
 | 
					                    if (sym == self.keysym) {
 | 
				
			||||||
                        return true;
 | 
					                        return true;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        .translate => {
 | 
				
			||||||
 | 
					            // Keysyms and modifiers as translated by xkb.
 | 
				
			||||||
 | 
					            // Modifiers used to translate the key are consumed.
 | 
				
			||||||
 | 
					            // E.g. pressing `Super+Shift 1` translates to `Super Exclam`.
 | 
				
			||||||
 | 
					            const keysyms_translated = keymap.keyGetSymsByLevel(
 | 
				
			||||||
 | 
					                keycode,
 | 
				
			||||||
 | 
					                layout_index,
 | 
				
			||||||
 | 
					                xkb_state.keyGetLevel(keycode, layout_index),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // We deliberately choose not to translate keysyms and modifiers with xkb,
 | 
					            const consumed = xkb_state.keyGetConsumedMods2(keycode, .xkb);
 | 
				
			||||||
    // because of strange behavior that xkb shows for some layouts and keys.
 | 
					            const modifiers_translated = @as(u32, @bitCast(modifiers)) & ~consumed;
 | 
				
			||||||
    // When pressing `Shift Space` on some layouts (Swedish among others),
 | 
					
 | 
				
			||||||
    // xkb reports `Shift` as consumed. This leads to the case that we cannot
 | 
					            if (modifiers_translated == @as(u32, @bitCast(self.modifiers))) {
 | 
				
			||||||
    // distinguish between `Space` and `Shift Space` presses when doing a
 | 
					                for (keysyms_translated) |sym| {
 | 
				
			||||||
    // correct translation with xkb.
 | 
					                    if (sym == self.keysym) {
 | 
				
			||||||
 | 
					                        return true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -359,7 +359,7 @@ pub fn enterMode(self: *Self, mode_id: u32) void {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Handle any user-defined mapping for passed keycode, modifiers and keyboard state
 | 
					/// Handle any user-defined mapping for passed keycode, modifiers and keyboard state
 | 
				
			||||||
/// Returns true if at least one mapping was run
 | 
					/// Returns true if a mapping was run
 | 
				
			||||||
pub fn handleMapping(
 | 
					pub fn handleMapping(
 | 
				
			||||||
    self: *Self,
 | 
					    self: *Self,
 | 
				
			||||||
    keycode: xkb.Keycode,
 | 
					    keycode: xkb.Keycode,
 | 
				
			||||||
@ -368,16 +368,40 @@ pub fn handleMapping(
 | 
				
			|||||||
    xkb_state: *xkb.State,
 | 
					    xkb_state: *xkb.State,
 | 
				
			||||||
) bool {
 | 
					) bool {
 | 
				
			||||||
    const modes = &server.config.modes;
 | 
					    const modes = &server.config.modes;
 | 
				
			||||||
    // It is possible for more than one mapping to be matched due to the existence of
 | 
					
 | 
				
			||||||
    // layout-independent mappings. In this case run the first match and log a warning
 | 
					    // It is possible for more than one mapping to be matched due to the
 | 
				
			||||||
    // if there are other matches.
 | 
					    // existence of layout-independent mappings. It is also possible due to
 | 
				
			||||||
 | 
					    // translation by xkbcommon consuming modifiers. On the swedish layout
 | 
				
			||||||
 | 
					    // for example, translating Super+Shift+Space may consume the Shift
 | 
				
			||||||
 | 
					    // modifier and confict with a mapping for Super+Space. For this reason,
 | 
				
			||||||
 | 
					    // matching wihout xkbcommon translation is done first and after a match
 | 
				
			||||||
 | 
					    // has been found all further matches are ignored.
 | 
				
			||||||
    var found: ?*Mapping = null;
 | 
					    var found: ?*Mapping = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // First check for matches without translating keysyms with xkbcommon.
 | 
				
			||||||
 | 
					    // That is, if the physical keys Mod+Shift+1 are pressed on a US layout don't
 | 
				
			||||||
 | 
					    // translate the keysym 1 to an exclamation mark. This behavior is generally
 | 
				
			||||||
 | 
					    // what is desired.
 | 
				
			||||||
    for (modes.items[self.mode_id].mappings.items) |*mapping| {
 | 
					    for (modes.items[self.mode_id].mappings.items) |*mapping| {
 | 
				
			||||||
        if (mapping.match(keycode, modifiers, released, xkb_state)) {
 | 
					        if (mapping.match(keycode, modifiers, released, xkb_state, .no_translate)) {
 | 
				
			||||||
            if (found == null) {
 | 
					            if (found == null) {
 | 
				
			||||||
                found = mapping;
 | 
					                found = mapping;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                log.warn("already found a matching mapping, ignoring additional match", .{});
 | 
					                log.debug("already found a matching mapping, ignoring additional match", .{});
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // There are however some cases where it is necessary to translate keysyms
 | 
				
			||||||
 | 
					    // with xkbcommon for intuitive behavior. For example, layouts may require
 | 
				
			||||||
 | 
					    // translation with the numlock modifier to obtain keypad number keysyms
 | 
				
			||||||
 | 
					    // (e.g. KP_1).
 | 
				
			||||||
 | 
					    for (modes.items[self.mode_id].mappings.items) |*mapping| {
 | 
				
			||||||
 | 
					        if (mapping.match(keycode, modifiers, released, xkb_state, .translate)) {
 | 
				
			||||||
 | 
					            if (found == null) {
 | 
				
			||||||
 | 
					                found = mapping;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                log.debug("already found a matching mapping, ignoring additional match", .{});
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user