joshgoebel / keyszer

a smart, flexible keymapper for X11 (a fork/reboot of xkeysnail )
Other
69 stars 15 forks source link

Blocking a modifier from emitting alone? #151

Open RedBearAK opened 1 year ago

RedBearAK commented 1 year ago

@joshgoebel

I'm not sure if this question will even make much sense, but do you think it would be possible to block a specific modifier key from doing anything if (and only if) it is not being combined with anything?

I am thinking specifically of the Super/Win/Meta key, which has come to be used to open application menus or quick launchers in different Linux desktop environments, including GNOME, KDE, Cinnamon, and Xfce (at least on Xubuntu). Each DE seems to approach the use of this modifier key, to perform an action by itself, quite differently. All of the example DEs fail to expose the "shortcut" for the Meta key in their normal control panel for shortcuts. I assume this is because the usual control panels were never designed to allow a modifier key to perform an action without a "normal" key involved, just like xkeysnail/keyszer. Xubuntu even has to run a special separate background app just to enable this Meta key action.

So for each DE, a separate solution has to be implemented to disable the Meta key action. If there were a way to simply keep a modifier key by itself from being emitted from the virtual keyboard, maybe none of that would be necessary.

I've tried to define a generic keymap even before the modmaps, but although it doesn't cause any errors, it doesn't work. The modmaps are always triggered. I suppose that by their nature the modmaps must be evaluated first, so that they affect all the shortcuts in the keymaps.

keymap("Block Meta key by itself", {
    Key.LEFT_META: None,
    Key.RIGHT_META: None,
}, when = lambda ctx: True is True)

Remapping onto a single modifier key on the output side turned out to be fine, as we discovered in the past. But the keymapper seems to just completely ignore having a single modifier on the input side of a keymap.

Obviously, trying to do it with a modmap would simply result in the key never doing anything, even in shortcuts. That would not be the right solution.

Of course, I'm not entirely sure what kind of side effects would come from blocking the press-release of a particular modifier key from doing anything at all (if it is alone). But I can't really think of anything bad that would happen. Unless it would also break being able to "hold" the modifier. That could be unhelpful.

But if it could be done safely, it sure would simplify some things.

joshgoebel commented 1 year ago

Isn't this really just asking for per modifier suspend timers?

RedBearAK commented 1 year ago

Isn't this really just asking for per modifier suspend timers?

Kind of, I think? But there is the problem that I still have to have my suspend set to zero due to the touchpad issue.

So also, kind of not. It's really more of "block this mod press entirely if it's alone, but not if it's part of a combo, and not if held".

Oh, maybe I should be looking at a multi-modmap. Block when tapped, but not when held? Would that keep it working in combos, when it's not just a "tap"?

joshgoebel commented 1 year ago

It's really more of "block this mod press entirely if it's alone, but not if it's part of a combo, and not if held".

That's almost exactly what suspend does....

The problem is it's impossible to detect "alone" when their are other devices that could be contributing to the input such as a mouse or trackpad...

RedBearAK commented 1 year ago

That's almost exactly what suspend does.... And exactly what it does with a longer delay.

Then maybe a per-mod suspend would be the closest option.

The problem is it's impossible to detect "alone" when their are other devices that could be contributing to the input such as a mouse or trackpad...

That is the kicker. But isn't it possible to tell the difference between a tap that's really too short to be something that's reasonably getting paired with a click, and a hold that's much more likely to be paired with some other input? The way the multipurpose modmap does?

Unfortunately the multipurpose doesn't like None.

joshgoebel commented 1 year ago

a tap that's really too short to be something that's reasonably getting paired with a click

But ALSO not part of a combo? Why is someone hitting the key at all then?

RedBearAK commented 1 year ago

a tap that's really too short to be something that's reasonably getting paired with a click

But ALSO not part of a combo? Why is someone hitting the key at all then?

That's not really the point. It's just that if you do happen to hit it without using it in a combo, it performs an action on its own. In the context of something like Kinto mimicking macOS, that's unwanted behavior.

Sometimes you might just stop halfway through deciding to use a combo, and release the modifier. I've done this many times in macOS, and since modifier keys don't do anything on their own, it was never a problem.

What takes precedence, regular modmaps or multipurpose? I'm seeing some odd behavior with a multi-modmap on the first "hold" usage.

joshgoebel commented 1 year ago

What takes precedence, regular modmaps or multipurpose?

They are sequential.

https://github.com/joshgoebel/keyszer/blob/main/src/keyszer/transform.py#L352

I'm seeing some odd behavior

Multi-purpose is ugly and likely buggy.

Sometimes you might just stop halfway through deciding to use a combo

Maybe, but that's not ALSO guaranteed to be "too short"... someone might hold the key a little too long and then get the unexpected behavior... I'm not seeing how we can have our cake here and eat it also without a long suspend.

RedBearAK commented 1 year ago

Nevermind, I think the multipurpose modmaps I'm trying end up getting caught up in the Cmd+Tab bind when I try to pair them with Tab. The first time they'll act like Ctrl+Tab (switch tabs) and then they'll start acting like Cmd+Tab (switch applications) after more repititions. If I don't try to use Tab they work more like you'd expect.

Maybe, but that's not ALSO guaranteed to be "too short"... someone might hold the key a little too long and then get the unexpected behavior... I'm not seeing how we can have our cake here and eat it also without a long suspend.

True. Probably not an easily solvable problem. Oh well.

RedBearAK commented 1 year ago

In large part, this actually seems to do what I want:

multipurpose_modmap("Block Meta when alone", {
    Key.LEFT_META:              [Key.RESERVED, Key.RIGHT_META]
})

It causes the left Ctrl key (modmapped to LSuper) to fail to bring up the GNOME overview screen with a tap after I reactivate the overlay-key shortcut, but the key can still be used in a shortcut like Super+A, which brings up the GNOME applications screen. But, when I hit Ctrl+Tab, I just get a Tab character the first time, then if I keep holding the key it starts to act like Super+Tab from then on, until I release the modifier. I can see that it's invoking the Super+Tab combo from the log.

It's like it's behaving how I would expect, except for that very first Super+Tab outcome, where it always just types a Tab into an app like GNOME Text Editor.

So close. If I can figure out why the different behavior is happening on the first try, I think this is the solution.

joshgoebel commented 1 year ago

There are some assumptions about using NON-modifiers for multipurpose modmaps, not actual modifiers... see on_key where the modifier path fires before the multi-purpose... which means you could wind up with the multi-purpose timeout not getting applied at all for modifiers... hence with a short normal suspend delay turning your CMD into a RESERVED when it [immediately] un-suspends as RESERVED...

Though in that case I'm not sure why it starts working later...

joshgoebel commented 1 year ago

Nevermind, that might be all wrong... actually if it wakes up right away (but after THINKING it was suspended) then it counts as a hold, hence the non-momentary key is what is pressed, so in this case RIGHT_META... but all this should be pretty clear in the log I'd guess.

RedBearAK commented 1 year ago

This is probably one of those situations where I will have to toss out print commands like 4th of July parade candy and just keep driving around the block until I figure out what I’m looking at. You know how it goes.

RedBearAK commented 1 year ago

I think the basic thing that is going wrong is that the "tapped" version of the key is "leaking" through and coming out before the Tab key the first time around, rather than the Meta key press. So since I'm using Key.RESERVED (key code zero) as the tapped key, I just get a Tab in the text editor. But, why this happens, no idea yet.

This is the log of using Ctrl+Tab twice, with Left Ctrl modmapped to Left Meta by the standard Kinto config setup. You can see that the modmap is applied, then the multi_modmap is applied, and out comes RESERVED right away, which should only come out for a "tap". I don't have any understanding yet how the multi_modmap decides which one to apply.

You can see that the combo the second time ends up being RSuper/RIGHT_META, which confirms that the "held" part of the multi_modmap is working at that point, since as you can see above I decided to remap the Left Meta to Right Meta for a clearer indication of which modmap is taking effect.

(II) in LEFT_CTRL (press)
##  ##  ## Entering apply_modmap()...
(DD) modmap: LEFT_CTRL => LEFT_META [Cond modmap - GUI - Win kbd]
### ### ### Entering apply_multi_modmap()...
##  ##  ##  Just about to do on_key(ks, context)...
(DD) on_key RESERVED press
(DD) suspending keys []

(II) in TAB (press)
##  ##  ## Entering apply_modmap()...
### ### ### Entering apply_multi_modmap()...
##  ##  ##  Just about to do on_key(ks, context)...
(DD) on_key TAB press
(DD) resuming keys: [<Key.RESERVED: 0>]
(OO) press RIGHT_META 1680850396.334326
(OO) press TAB 1680850396.3344471

(II) in TAB (release)
##  ##  ## Entering apply_modmap()...
### ### ### Entering apply_multi_modmap()...
##  ##  ##  Just about to do on_key(ks, context)...
(DD) on_key TAB release
(OO) release TAB 1680850396.4072049

(II) in TAB (press)
##  ##  ## Entering apply_modmap()...
### ### ### Entering apply_multi_modmap()...
##  ##  ##  Just about to do on_key(ks, context)...
(DD) on_key TAB press

(DD) WM_CLASS: 'gnome-text-editor' | WM_NAME: 'Cmd CapsLock ÿ 12345 (Draft) - Text Editor'
(DD) DEVICE: 'AT Translated Set 2 keyboard' | CAPS_LOCK: 'False' | NUM_LOCK: 'False'
(DD) ACTIVE KEYMAPS:
     'OptSpecialChars - US', 'User hardware keys', 'Wordwise - not vscode',
     'Cmd+Dot not in terminals', 'GenGUI overrides: not Chromebook', 'GenGUI
      … overrides: Fedora', 'GenGUI overrides: GNOME', 'General GUI'
(DD) COMBO: RSuper-TAB => [<ComboHint.BIND: 1>, LCtrl-TAB] in KMAP: 'GenGUI overrides: not Chromebook'
(DD) KEY_CODE: 15 | MODIFIER_CODES: [126]
(DD) spent modifiers []
(DD) BIND: {<Key.RIGHT_META: 126>: <Key.LEFT_CTRL: 29>}
(OO) press LEFT_CTRL 1680850396.903851
(OO) release RIGHT_META 1680850396.904009
(OO) press TAB 1680850396.9055352
(OO) release TAB 1680850396.905797
(OO) press RIGHT_META 1680850396.9080622

(II) in TAB (release)
##  ##  ## Entering apply_modmap()...
### ### ### Entering apply_multi_modmap()...
##  ##  ##  Just about to do on_key(ks, context)...
(DD) on_key TAB release

(II) in LEFT_CTRL (release)
##  ##  ##  Just about to do on_key(ks, context)...
(DD) on_key RIGHT_META release
(DD) lift of BIND RIGHT_META => LEFT_CTRL
(OO) release LEFT_CTRL 1680850397.2076383
(OO) release RIGHT_META 1680850397.2077942
joshgoebel commented 1 year ago
(DD) on_key TAB press
(DD) resuming keys: [<Key.RESERVED: 0>]
(OO) press RIGHT_META 1680850396.334326
(OO) press TAB 1680850396.3344471

The resume happens too late... and the key isn't "resolved" until it "wakes"... which means at that time it's still RESERVED and reserved-tab means nothing special.

RedBearAK commented 1 year ago

Yeaaah... But the actual output lines with the "(OO)" seem to show that Meta then Tab should be coming out of the virtual keyboard, in that order.

Here, maybe this is a major clue. If I just hold the key long enough to let it start repeating, it starts repeating as the momentary key for a while, then becomes the "held" key, and continues repeating. So I have to hold it for about twice as long as the normal 1 second suspend timeout, before it actually becomes the "held" key. Any attempt to use it in a shortcut before that gets me the "momentary" version of the key. But again, only on the first try.

So what controls the timing of that, when my suspend timer is set to zero?

I can do two combo tries as fast as possible, in a much shorter span of time than holding to watch the repeats, and the second try after NOT releasing the key is ALWAYS the "held" key, where the first try is ALWAYS the "momentary" key. (Unless, as in the paragraph above, it was first held for a relatively long span of time.)

joshgoebel commented 1 year ago

But the actual output lines with the "(OO)" seem to show that Meta then Tab should be coming out of the virtual keyboard, in that order.

Right, after it's resolved it's a right meta key. Multi-map has it's own delay timer, but as I already mentioned all this stuff is quite buggy... probably won't get fixed until I have a linux box I use regularly again... unless you want to hack at it very slowly...

What I'd like long-term is to see if all these different features could be untangled a lot more so they stand on their own and are easier to debug/fix, etc.

RedBearAK commented 1 year ago

@joshgoebel

Got a pretty weird phenomena between left and right mod keys with multipurpose modmap. Any idea why apps and the shell react normally when I remap on to left Alt or left Meta, but fail to respond when I remap onto right Alt or right Meta?

multipurpose_modmap("Block Mod when alone", {
    Key.LEFT_META:              [Key.RESERVED, Key.LEFT_META],
    Key.RIGHT_META:             [Key.RESERVED, Key.RIGHT_META],
    Key.LEFT_ALT:               [Key.RESERVED, Key.LEFT_ALT],
    Key.RIGHT_ALT:              [Key.RESERVED, Key.RIGHT_ALT],
})

I absolutely thought the blocking was working at some points, but then I realized it was just that the GNOME overview wouldn't show up if I remapped to RIGHT_META, and Firefox wouldn't activate the menu bar if I remapped to RIGHT_ALT. On the other hand, both will respond to the left version.

Also, VSCode won't let me do multi-cursor stuff with RIGHT_ALT, but that also seems to happen when the keymapper is disabled, it ignores the Alt key on the right side. But with the GNOME overview and Firefox, of course they react to both Alt or Meta keys when the keymapper is disabled. So this difference between left and right mods has me pretty confused.

I can reverse the left and right in the modmap above, and then the other physical key (with the "left" version) will trigger what you would expect it to, while the ones with the "right" variant will act like you didn't tap them, as far as the app or shell is concerned.

RedBearAK commented 1 year ago

This left/right issue extends to combos, not just the usage of the key alone. If I remap to LEFT_META and then to Meta+A, I see that the combo hits the existing remap in the Kinto config for "emacs style":

    # emacs style
    C("Super-a"):               C("Home"),                      # Beginning of Line
    C("Super-e"):               C("End"),                       # End of Line
    C("Super-b"):               C("Left"),
    C("Super-f"):               C("Right"),
    C("Super-n"):               C("Down"),
    C("Super-p"):               C("Up"),
    C("Super-k"):              [C("Shift-End"), C("Backspace")],
    C("Super-d"):               C("Delete"),

And that action of "Home" is activated, but the shell also recognizes it as the shortcut for the applications view (Super+A).

On the other hand, if I remap to RIGHT_META instead, it still triggers the same emacs remap, but the shell doesn't respond to the shortcut anymore.

I can't recall ever encountering anything like this outside the context of a multipurpose modmap, but I'm still not sure if it's actually the multipurpose logic or something in resume_keys() that isn't working well with the multipurpose stuff. But something seems to be breaking the normally agnostic nature of left/right mod keys on output.

Will continue investigating this when I have some time.