Bug 92818

Summary: xkb_state_key_get_consumed_mods() returns odd values for F1..F12 keycodes
Product: libxkbcommon Reporter: Carlos Garnacho Parro <carlosg>
Component: GeneralAssignee: Daniel Stone <daniel>
Status: RESOLVED MOVED QA Contact: Ran Benita <ran234>
Severity: normal    
Priority: medium CC: tiagomatos
Version: unspecified   
Hardware: Other   
OS: All   
Whiteboard:
i915 platform: i915 features:

Description Carlos Garnacho Parro 2015-11-04 14:03:30 UTC
Using american keyboard/layout here. I'm tracing problems in gtk+wayland where our gdk_keymap_translate_keyboard_state() implementation calls that in order to know the modifiers that would be consumed by the translation from hw_keycode+state to a keyval (to be precise, we currently figure out from the older xkb_state_mod_mask_remove_consumed(), but all xkb_state*consumed* api has the same issue).

When pressing any function key these functions are telling me the translation will consume (xkb_mod_mask_t) 0x8d, which AFAICT is shift|ctrl|mod1|mod5. Given these keycodes produce the same sym regardless of those modifiers, I find that behavior somewhat unexpected.

I tried to trace the issue but didn't get too far. I appreciated though that the function keys get the "CTRL+ALT" type as specified in /usr/share/X11/xkb/types/pc, which strikes me as odd.
Comment 1 Daniel Stone 2015-11-04 15:18:36 UTC
It's correct per the type, AFAICT:
    type "CTRL+ALT" {
        modifiers = Control+Alt+Shift+LevelThree;
        map[None] = Level1;
        map[Shift] = Level2;
        map[LevelThree] = Level3;
        map[Shift+LevelThree] = Level4;
        map[Control+Alt] = Level5;
        preserve[Shift] = Shift;
        preserve[Shift+LevelThree] = Shift;
        level_name[Level1] = "Base";
        level_name[Level2] = "Shift";
        level_name[Level3] = "Alt Base";
        level_name[Level4] = "Shift Alt";
        level_name[Level5] = "Ctrl+Alt";
    };

The type - if you check the key definitions - is used to implement VT switching, as Ctrl+Alt+F1 will generate XF86_Switch_VT_1. So that accounts for all of them: Mod1 being Alt, and Mod5 being LevelThree/AltGr.

What seems incorrect about it ... ?
Comment 2 Carlos Garnacho Parro 2015-11-04 16:35:48 UTC
(In reply to Daniel Stone from comment #1)
> It's correct per the type, AFAICT:
>     type "CTRL+ALT" {
>         modifiers = Control+Alt+Shift+LevelThree;
>         map[None] = Level1;
>         map[Shift] = Level2;
>         map[LevelThree] = Level3;
>         map[Shift+LevelThree] = Level4;
>         map[Control+Alt] = Level5;
>         preserve[Shift] = Shift;
>         preserve[Shift+LevelThree] = Shift;
>         level_name[Level1] = "Base";
>         level_name[Level2] = "Shift";
>         level_name[Level3] = "Alt Base";
>         level_name[Level4] = "Shift Alt";
>         level_name[Level5] = "Ctrl+Alt";
>     };
> 
> The type - if you check the key definitions - is used to implement VT
> switching, as Ctrl+Alt+F1 will generate XF86_Switch_VT_1. So that accounts
> for all of them: Mod1 being Alt, and Mod5 being LevelThree/AltGr.

Hmm, right, VT switching...

> 
> What seems incorrect about it ... ?

I guess only my expectancies about xkb_state_key_get_consumed_mods() being behaviorally compatible with gdk_keymap_translate_keyboard_state() "consumed_modifiers" argument :).

There, we only mark the modifiers that would end up producing a different keysym if toggled. However ctrl+f1 alone doesn't produce a different keysym, nor alt+f1 (and ctrl+alt+f1 is unseen by clients). But we've got all those bits in the mask that end up ignored, and we see <any-of-these-mods>+f1 seen as f1.

I guess the odd thing is that here you need 2 modifiers enabled to get a different keysym, whereas in eg. ALPHABETIC you only need 1 bit, I can't tell if that's also the case anywhere else though.

All in all, feel free to disregard this as NOTABUG, I see some sense with the "vt switching" piece of the puzzle I was missing, even though it's not the behavior I was expecting from these functions. I might also mimick the "enable/disable modifier bits and see if the resulting keysym is different" business we do on x11.
Comment 3 Daniel Stone 2015-11-13 11:35:48 UTC
(In reply to Carlos Garnacho Parro from comment #2)
> I guess only my expectancies about xkb_state_key_get_consumed_mods() being
> behaviorally compatible with gdk_keymap_translate_keyboard_state()
> "consumed_modifiers" argument :).
> 
> There, we only mark the modifiers that would end up producing a different
> keysym if toggled. However ctrl+f1 alone doesn't produce a different keysym,
> nor alt+f1 (and ctrl+alt+f1 is unseen by clients). But we've got all those
> bits in the mask that end up ignored, and we see <any-of-these-mods>+f1 seen
> as f1.

Oh, hmm ... interesting.

> I guess the odd thing is that here you need 2 modifiers enabled to get a
> different keysym, whereas in eg. ALPHABETIC you only need 1 bit, I can't
> tell if that's also the case anywhere else though.
> 
> All in all, feel free to disregard this as NOTABUG, I see some sense with
> the "vt switching" piece of the puzzle I was missing, even though it's not
> the behavior I was expecting from these functions. I might also mimick the
> "enable/disable modifier bits and see if the resulting keysym is different"
> business we do on x11.

I hadn't realised GTK+ behaved that way. Ran, any thoughts?
Comment 4 Carlos Garnacho Parro 2015-11-18 11:15:03 UTC
(In reply to Daniel Stone from comment #3)
> (In reply to Carlos Garnacho Parro from comment #2)
> > I guess only my expectancies about xkb_state_key_get_consumed_mods() being
> > behaviorally compatible with gdk_keymap_translate_keyboard_state()
> > "consumed_modifiers" argument :).
> > 
> > There, we only mark the modifiers that would end up producing a different
> > keysym if toggled. However ctrl+f1 alone doesn't produce a different keysym,
> > nor alt+f1 (and ctrl+alt+f1 is unseen by clients). But we've got all those
> > bits in the mask that end up ignored, and we see <any-of-these-mods>+f1 seen
> > as f1.
> 
> Oh, hmm ... interesting.

FWIW, the place where this happens is:
https://git.gnome.org/browse/gtk+/tree/gtk/gtkkeyhash.c#n442

This is the code that looks up actions associated to keybindings (eg. Ctrl-+, Ctrl-F1). There we have these checks:

  (state & ~consumed_modifiers ...) == (modifiers & ~consumed_modifiers ...)

that effectively disable all the consumed modifier bits from the checks (the example in gtk+ docs being Ctrl-+ being actually Ctrl-Shift-= in american keyboards, so Shift must be ignored for matching). 

consumed_modifiers comes more or less straight from our gdk_keymap_translate_keyboard_state() function (goes through the _gtk_translate_keyboard_accel_state() wrapper, but it doesn't do much on top that affects here).
Comment 5 Ran Benita 2015-11-21 17:31:10 UTC
The short story is: our current behaviour is what the XKB standard requires, and what Xlib implements, and what the existing keymaps expect; hence I don't think we should change it (unless the above statements are not correct in some way). The standard itself does not really expand on the issue, but it does say that the modifiers from the type's mask are consumed even if they would not affect the result:

http://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Key_Types

There is some semi-related discussion in another bug (a bit long):

https://github.com/xkbcommon/libxkbcommon/issues/17

And a commit it links to:

https://github.com/xkbcommon/libxkbcommon/commit/5417440970f15673fbc128108245ad0697fcadc0

GTK X11 has its own custom interpretation of "consumed", which I'm sure took a lot of time to get to play well with the implementation of keyboard shortcuts. See for example here:

https://github.com/GNOME/gtk/blob/cdd3e05fba8992746bcd80b286137d3bfc258d7c/gdk/x11/gdkkeys-x11.c#L1128

In order for the wayland backend to behave the same, you will almost certainly have to bend what xkbcommon gives you to your will.

If testing against a scratch xkb_state with different modifier masks works, then I don't see a problem with that (but there surely are edge cases...). What you can do though, is only iterate over the modifiers which are in the consumed mask returned by xkbcommon (or maybe all subsets of them?); this way you don't test unrelated modifiers, and automatically respect the "preserve" clauses of the type.
Comment 6 Ran Benita 2016-10-22 19:05:08 UTC
Closing in favor of https://github.com/xkbcommon/libxkbcommon/issues/17.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.