mooz / xkeysnail

Yet another keyboard remapping tool for X environment
891 stars 112 forks source link

Triggering two keybindings in sequence without fully releasing the first one #81

Closed thiagoa closed 3 years ago

thiagoa commented 4 years ago

I have the following shortcuts:

M-shift-RIGHT_BRACE
C-M-n

Let's say I press down the first keybinding.

To trigger the second keybinding immediately after the first one, I would preserve the modifiers of the first keybinding but release the Shift key and press down the Control key (while keeping Alt pressed down).

Unfortunately, that doesn't work. The only way to register the second keybinding is to fully release the modifiers of the first keybinding, and then press the second keybinding.

Is there a workaround to make this use case work?

Thank you for this awesome software. It was a long journey before discovering it, and surprisingly most of the Emacs config.py works so well for my needs, and it even remaps return to control correctly :smiley:

Lenbok commented 4 years ago

What version of xkeysnail are you running? As reported in #74, the version currently on master has problems with modifiers (#71 was merged before it was adequately tested, I think ideally @mooz would revert it so people running master don't have issues), so that may be causing the problem you are seeing. Try running with 123432bb and see if you still have the issue.

thiagoa commented 4 years ago

Thank you so much @Lenbok

Yes, that fixes it for me. It also fixed shortcuts like C-x C-s, which required two releases to trigger.

rbreaves commented 4 years ago

Yea, it would seem I could have done more testing of the change to the behavior on how the modifier keys are held down. I do have another pull request in the queue that fixes up a couple of behaviors that were off - one before my commit #74 (did not correctly identify which merge caused their problem - but I did fix it because my changes were significant enough for it to not matter who caused it) and another one actually caused by me, #80, that caused the shift key to not be picked up correctly in some circumstances. The latter was actually easy to fix compared to the former, but mostly because I fixed the former, so meh it worked out spending the time to fix it.

https://github.com/mooz/xkeysnail/pull/76

I really dislike the emitting behavior versus holding modifiers in conjunction with the original modifier being held, but I concede that for some people the emitting behavior may be preferred. Also I have been able to trigger multiple shortcut keys just fine in my own configs after making my changes.

The real answer to all this really may be to create an HK( ) function for held key behavior, which I believe I had proposed but I have not spent any time writing as of yet.

joshgoebel commented 2 years ago

I'm interesting in perhaps working on this larger issue but I'm trying to understand both cases... I'm starting with the issue mentioned here....

M-shift-RIGHT_BRACE
C-M-n

In just testing (with no mappings) the debug output is identical for 0.3 and 0.4... what mappings do I need to add to see it start to break?

rbreaves commented 2 years ago

emacs style hot keys, not sure of the specific example I had used, but anything with nested remaps will fail under my fork if I recall correctly.

74 @ncaq provided this example

https://github.com/ncaq/.xkeysnail/blob/master/config.py#L116

joshgoebel commented 2 years ago

I'm still trying to fully wrap my head around it but I wondering if both sides are just running into failures in the abstraction... it seems what we'd do (in a perfect world) is try and emulate the "real input" as close as possible (unless that wasn't desirable)... ie, in cases where we have one to one or one to many mappings the state events would follow the input exactly.

Right now we have two separate things and we're trying to glue them together... we have a "trigger" and a "output"... (or multiple outputs)... and these things are rather loosely connected...

It seems that the state machine needs to keep track of a "sticky mapping" after a combo is found... so for (a simple) example:

Super-F mapped to Ctrl-F

In this case it seems clear that after F is pressed and the transform engine locates the combo that it should realize the Super->Ctrl is a mapping, and bind those two together on the transform side (or perhaps communicate the binding info to the output side).

So the output would send:

And then nothing... until Super was released at which time a separate:

Would be sent thru to the output... well, actually not "nothing"... repeat events wouldn't continue to go thru if you kept holding the keys...

I think you could assume the continuity by default in many cases such that:

Super A 
   Super B => [Ctrl A, Ctrl B]

Such that when Super B is triggered Super is sticky mapped to Ctrl and then Ctrl is held on the output as long as Super is held.

It only gets weird when things get ambiguous:

Super A 
   Super B => [Alt A, Ctrl B]

What would Super be sticky mapped to in that case?


For cases where you NEEDED actual releases (though I'm not sure that's actually a big problem) you could specify them:

Super A 
   Super B => [Ctrl A, release(Ctrl), Ctrl B]

Such that this would output:


I think proper "sticky binding" (as I'm naming it) would solve all your alt-tabbing issues... the question is would that actually break any of the other keystrokes (if implemented properly).

joshgoebel commented 2 years ago

The problem with #74 (as I read it)... take the example given:

C-SPC → 
        C-e → 
              M-w.

The problem is that the engine itself was breaking this because if the M was hit before the C was released the C release would also kill the M (because it was trying to close/clean up/terminate the C-e combo or something and had no idea what to make of the extra M)... when really it should realize those things aren't related at all... a case like this should go "straight thru"... if the M is hit before the C is released, than that would go straight to the output "as is"...

rbreaves commented 2 years ago

Yea, I am not sure what all the intricacies are atm - all I know is that my fork largely works fine for my purposes and I have not been too awfully concerned about nested hotkeys - which users here are concerned about. A separate method for held keys I think is the easiest solution, but if there is one that be implemented different to reconcile the needs of both then that's fine too.

I also have this branch on my fork which allows for seeing the transformed hotkeys a little easier, not sure if it is fully in sync with my primary branch which is actual named "kinto" - it is not the master branch. When users install kinto.sh they are using the kinto branch of my xkeysnail fork.

https://github.com/rbreaves/xkeysnail/tree/debug

As far as I can recall though if I do a PR on my working branch I would need this issue reconciled, to avoid breakage with existing xkeysnail users, and hopefully it is the only one. The way I do my commits and changes though I can always split out other features and do PRs for them still.

The only thing I would like to resolve really is the Alt key issues under Electron apps - they are problematic to map or remap. May not be resolvable though via xkeysnail.

joshgoebel commented 2 years ago

all I know is that my fork largely works fine for my purposes and I have not been too awfully concerned about nested hotkeys

Does that mean you wouldn't be that interested in a more actively maintained fork of the project? ...

reconcile the needs of both then that's fine too.

Or that if it continued to "just work" (include for your existing patched use case) you'd be onboard? I'd say (guessing) you're currently one of the largest users of the project. I'd definitely be interested in solving your use case (sticky continuously held mappings) as it does indeed seem like a key feature for the kind of things you're trying to accomplish.

The only thing I would like to resolve really is the Alt key issues under Electron apps - they are problematic to map or remap.

I'm not familiar with that yet can you point me to an issue/discussion?

joshgoebel commented 2 years ago

@rbreaves Actually since no one else is complaining it might make more sense to treat your scenario as the edge case for now (might be easier to implement that way)... what about something like:

define_keymap(lambda wm_class: wm_class.casefold() not in remotes,{
   K("RC-Shift-Tab"):
      [
      STICKY({"RC": "M", "Shift": "Shift" }), 
      K("M-Shift-Tab")
      ]

The sticky status would be passed down into the output module and when it came time to release keys (at the end of a combo) if the key in question was stickily bound to a key that was still being held, then the release wouldn't happen. In this case we're binding RC -> M and Shift -> Shift... so if one released Shift then shift would come up on the output but as long as one still held RC then M would remain held on the output...

And of course simpler cases wouldn't work also, say where you just had RC -> M and no other binds...

So for those few cases where you need sticky behavior, you'd just specify it manually... and the engine would oblige.

If I make a working PR could you test for your use case?

joshgoebel commented 2 years ago

Edge cases.

rbreaves commented 2 years ago

Does that mean you wouldn't be that interested in a more actively maintained fork of the project? ...

Oh I’m totally on board - I was just a bit busy when I was making that post & I’m not entirely sure how often or frequent I’ll be able to help in the effort myself. Maybe 1 or 2 days a month, but I’d be in support of a more actively maintained & worked on xkeysnail.

As far as the STICKY suggestion that’d probably work, my suggestion in a thread somewhere was using HK (for held keys), but same difference.

Personally I want to see a fix for wayland as I feel that holds this project & projects like this back the most. There’s a thread somewhere that I opened that delves into it some. Am noticing a remap project or 2 has some sort of solution for gnome, & it will be per DE.

joshgoebel commented 2 years ago

Personally I want to see a fix for wayland

The issue is tracking which app has focus, yes? The other bits should just work since they are so low level, or am I mistaken?

HK (for held keys)

I don't think you gave a code example though... if you did I missed it... when I read that I was imagining something like:

define_keymap(lambda wm_class: wm_class.casefold() not in remotes,{
   K("RC-Shift-Tab"):
      HK("M-Shift-Tab")

And that doesn't have enough information about which keys are bound IMHO (without guessing)... I mean I guess you could create a "RC-Shift" -> "M-Shift" (all or nothing) mapping... but I think for completeness you need to know WHICH key is which... it's not obvious that the mapping is RC -> M.... the mapping could technically also be RC -> Shift and And Shift -> M... so when someone lifts the key we need to known which key to lift on the backend.

Perhaps I'm over thinking. :)

rbreaves commented 2 years ago

@joshgoebel you may be correct in thinking a more direct correlation btwn modifier transformations needs to be spelled out. And yea my code example for HK is about what you showed there. It didn’t show how to do the transform exactly but it makes sense to flesh it out otherwise when a modifier is released while others are still held it won’t know for sure what to release imo.

joshgoebel commented 2 years ago

The only thing I would like to resolve really is the Alt key issues under Electron apps -

Is it because they respond on the alt keypress itself (like Firefox mentioned in the README)? To fix that you'd have to "withold" keys from the output until you knew what the keystroke was going to be... I think for some setups that would require a LOT more manual passthru style stuff...

Right now we pass all keys straight thru then when we need to type a combo we "release" any extra REAL keys we pressed and then only send the artificial combo keys to the output... then after the combo is done we reexert the real keys on the output again...

This is important in cases where say someone had a WM that bound something to just pressing or holding a single key, like say holding down super to pullup some sort of info widget.

Not sure how much that matters in your personal use case...

rbreaves commented 2 years ago

Not aware of an alt issue in Firefox but vscode is a prime example - Alt shortcuts don’t remap because yea Alt activates focus in a weird way & the remap can’t switch to another modifier after that.

Not an issue if the remap for Alt hotkeys are done directly in vscode though.

joshgoebel commented 2 years ago

yea Alt activates focus in a weird way

Not sure I'd call it a weird way, but I see what you mean now. It's common to hide menu bars in linux, so you need a way to access them... and Alt is traditionally that way. :-)

What remaps in VS Code are affected by this issue - that you can't make work with xkeysnail? One or two examples would be fine.