Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to emit on release only #80

Closed
oblitzitate opened this issue Jul 28, 2022 · 28 comments · Fixed by #92
Closed

Allow to emit on release only #80

oblitzitate opened this issue Jul 28, 2022 · 28 comments · Fixed by #92
Labels
enhancement New feature or request

Comments

@oblitzitate
Copy link

oblitzitate commented Jul 28, 2022

I'd like a functionality where you can emit a tap on-release only. The expression could be named on-release.

You can get a similar functionality in kmonad with tap-macro-release by ignoring the all the arguments except the last. (Note: To fully copy tap-macro-release, you'd probably just have to combine on-release with multi and macro)

I've found the on-release functionality useful for complex layer switching where I would keep/change a layer depending on which key I release last.

I've also found it useful for clearing modifiers (especially since kmonad has a press-only button that allows to keep mods activated).

@jtroo jtroo added enhancement New feature or request PRs welcome jtroo has no plans to work on this at present, but PRs are welcome labels Jul 28, 2022
@jtroo
Copy link
Owner

jtroo commented Jul 28, 2022

Currently macro is quite limited in functionality. It only allows basic typing actions (delays, keys and key-chords), so it probably wouldn't help in emulating tap-macro-release. The multi action might help, though it does tend to have some weird interactions.

Activating arbitrary actions on release is not well supported by the underlying key state machine at present. It does however support emitting a release event for Custom type actions. The Custom action type is what's used for mouse buttons, live reload, unicode, and command execution.

Given that this functionality seems quite specific, it may be sufficient to provide on-release for the few relevant use cases. E.g. just provide an on-release-layer-switch action.

@oblitzitate
Copy link
Author

Would I be able to listen to layer switches in order to run a cmd? Because in kmonad, I run a cmd along with the layer switch to change my key layer widget (in AwesomeWM) to display the current layer.

@jtroo
Copy link
Owner

jtroo commented Jul 28, 2022

Yea you could write your own code that connects to the kanata TCP server and runs the commands itself. Or another event could be added for on-release-cmd.

@oblitzitate
Copy link
Author

oblitzitate commented Jul 28, 2022

Okay then it seems on-release-layer-switch would be sufficient for me for now. The TCP server is probably the better solution to run the cmd, but I'm not gonna complain if you also added on-release-cmd

Do you think a general on-release would eventually possible in the future?

Because I've this concept I named Proxy Mod where, in the base layer, I hold a key (the proxy mod) -> it changes to a layer that lets you pick modifiers like ctrl (via press-only lctl) on press, then changes back to base layer on release so I can chord with them -> Once I release the proxy mod, it clears all modifiers. Would be nice to be able to do that.

@jtroo
Copy link
Owner

jtroo commented Jul 28, 2022

It's certainly possible if I or someone else can think of a design that fits well with keyberon's state machine.

An idea that popped into my head is that rather than have general-case press-only or on-release, have the ability to create fake keys that have arbitrary actions. And then there can be other actions to tap/press/release these fake keys that are disconnected from the keyboard inputs. A specific on-release case could act on these fake keys.

The concept of fake keys works better in keyberon's state machine than general on-release does.

@oblitzitate
Copy link
Author

oblitzitate commented Jul 29, 2022

Not sure I follow the whole fake keys concept.

Also, I just want to differentiate between on-press/on-release vs. press-only/release-only in case of confusion. The former refers to the user's physical press/release of a key. The latter refers to the emitted event.

I believe release-key and release-layer works like release-only.

@jtroo
Copy link
Owner

jtroo commented Jul 29, 2022

The fake keys idea would probably make more sense if you look at the design docs as well as the code.

The kanata code leaves the key state management to the keyberon library. The library doesn't have a good way to handle on-release. It also doesn't have a good way to handle press-only; all key states (not layer though) are tied to a physical key being pressed and the state goes away when the physical key is released.

Fake keys would be a way to use the keyberon library to decouple physical key presses and releases from key state.

@oblitzitate
Copy link
Author

Okay I think I understand. Basically, use fake keys as a middle-man (or filtering mechanism) to trick the keyberon's state management.

@jtroo jtroo removed the PRs welcome jtroo has no plans to work on this at present, but PRs are welcome label Aug 1, 2022
@jtroo
Copy link
Owner

jtroo commented Aug 3, 2022

I think #92 should be able to replicate all the functionality you want. It'll require a different setup compared to kmonad, but I think it'll work. See the kanata.kbd file changes for examples.

Please test at your leisure and let me know what you think.

@oblitzitate
Copy link
Author

oblitzitate commented Aug 3, 2022

Fake keys seem to work as intended. Thanks! I appreciate how quickly you added it in. I also noticed you can map (layer-switch layer_name) to a fake key which is cool.

Now my issues are pretty much API-related. (Note: I haven't really studied the source code as I'm not familiar with Rust as much so I'm looking at this from a more UX perspective. Forgive me for my lack of knowledge of the source code's limitations.)

Fake Keys API

For the average user, I think fake keys are probably better of as an implementation detail for some abstraction rather than a direct API, but here are my suggestions to improve the API anyway:

  1. Remove deffakekeys

From a UX perspective, it just seems redundant having to manually map a normal key to a fake key alias. Perhaps fake-key-op should just take a normal key as an argument, then when parsing it, it would automatically map the normal key to a generated fake key or something.

  1. Rename fake-key-op to on-press-fake-key-op

The added prefix makes it more clear. For instance, (fake-key-op ctl tap) might be confusing whereas with the added prefix, it's not.

A More Composable API

  1. Decouple the press-only/release-only/tap part from the on-press/on-release part and abstract them into their own general actions (while they use fake keys internally).

This would add a lot of composability. You can still keep the fake keys API if you want, but such composable abstractions would be nice. For instance, I could create a bunch of release-only aliases, combine them into a single alias, then pack this with layer-switch into on-release:

(defalias
  ;; GENERAL
  ;;; lock modifiers
  po-lalt (press-only lalt)
  po-lmeta (press-only lmet)
  po-lshift (press-only lsft)
  po-lctrl (press-only lctl)
  ;;; clear modifiers
  ro-lalt (release-only lalt)
  ro-lmeta (release-only lmet)
  ro-lshift (release-only lsft)
  ro-lctrl (release-only lctl)
  lclear (multi @ro-lalt @ro-lmeta @ro-lshift @ro-lctrl)
  ro-ralt (release-only ralt)
  ro-rmeta (release-only rmet)
  ro-rshift (release-only rsft)
  ro-rctrl (release-only rctl)
  rclear (multi @ro-ralt @ro-rmeta @ro-rshift @ro-rctrl)
  clear (multi @lclear @rclear)
  ;;; layers
  base (layer-switch base)
  extend (layer-switch extend)
  media (layer-switch media)
  navigation (layer-switch navigation)
  ;;; control/(escape+clear) key
  esc-from-ctrl (multi (release-only lctl) esc @clear)
  esc-from-ctrl-to-base (multi @esc-from-ctrl @base)
  ctrl_maybe-esc (multi lctl (tap-hold-press 200 200 @esc-from-ctrl XX))
  ctrl_maybe-esc-base (multi lctl (tap-hold-press 200 200 @esc-from-ctrl-to-base XX))
  cte @ctrl_maybe-esc ;; Map to Caps key for base layer
  ctb @ctrl_maybe-esc-base ;; Map to Caps key for other layers
  
  ;; BASE LAYER
  extend-base (multi (on-press @extend) (on-release (multi @base @clear))) ;; The example I was talking about
  alt-extend-base (multi lalt @extend-base)
  alt @alt-extend-base ;; Map to Alt key
  
  ;; EXTEND LAYER
  ;;; sub-layer keys
  ;;;; if you release this before releasing @alt, temporarily keep the layer until @alt is released
  ;;;; if you release this after releasing @alt, permanently keep the layer
  media-media (multi (on-press @media) (on-release @media))
  navigation-navigation (multi (on-press @navigation) (on-release @navigation))
  med @media-media ;; Map to Q key
  nav @navigation-navigation ;; Map to W key
  ;;; proxy mods
  ;;;; Lock a modifier until @alt is released. Go to base layer
  pxc (multi @po-lctrl @base) ;; Map to A key
  pxs (multi @po-lshift @base) ;; Map to S key
  pxa (multi @po-lalt @base) ;; Map to D key
  pxm (multi @po-lmeta @base) ;; Map to F key
)

Other Notes

  • For a general release-only action, I imagine you'd combine release-key, release-layer, and a fake key release into one, maybe using a match statement. Or maybe just use fake key release for all of it.

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

The fake keys API should be composable enough to achieve the effects of what you're describing. I think what you're going for is ergonomics instead, and I can see how the kmonad way of doing it could be more ergonomic than fake keys.

The fake keys API is the simplest possible API implementation that works for generic on-release, press-only, and release-only, since it maps very closely to the underlying state machine.

It may be possible to hide the fake keys functionality behind press/release-only + on-release APIs, but the backing code will need to become more complex to do the bookkeeping for which actions map to which fake keys. It may happen one day, but I'm not particularly motivated to do the work to make an already complex use case more ergonomic, particularly since I don't actually use the feature myself.

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

One thing to note is that fake key actions currently should also be able to refer to other previously defined fake key, and accept multi as well. This might help get closer to the ergonomics you're describing.

@oblitzitate
Copy link
Author

One thing to note is that fake key actions currently should also be able to refer to other previously defined fake key, and accept multi as well. This might help get closer to the ergonomics you're describing.

Ah, I did not test that, but that's good to know. I'll try to convert my kmonad config to kanata using the fake keys API and see how it goes.

In the mean time, what do you think about my suggestions on improving the fake keys API?

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

In the mean time, what do you think about my suggestions on improving the fake keys API?

To summarize my earlier comment, it's certainly possible but at this time I believe the current API and the suggested improvements have the same expressive power - just with different ergonomics - I'm not currently motivated to design and implement any changes.

@oblitzitate
Copy link
Author

oblitzitate commented Aug 4, 2022

I meant just the first 2 suggestions. I assume getting rid of deffakekeys would currently be a hassle, but what about the rename from fake-key-op to on-press-fake-key-op?

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

Ah I see. It's unclear how removing deffakekeys would actually be workable without going all-in into implementing the 3rd suggestion.

Renaming to on-press-fake-key-op seems fine to me.

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

I've force-pushed the PR branch to make the change to on-press-fake-key-op as well as added a more complex example to kanata.kbd.

@oblitzitate
Copy link
Author

oblitzitate commented Aug 4, 2022

Found a bug. It seems a multi containing on-release in deffakekeys doesn't mesh well. You can't refer to it in defalias.

(deffakekeys
  flctl lctl
  flsft lsft
  flmet lmet
  flalt lalt
  flclear (multi
            (on-release-fake-key-op flctl release)
            (on-release-fake-key-op flsft release)
            (on-release-fake-key-op flmet release)
            (on-release-fake-key-op flalt release)
          )
)

(defalias
  pct (on-press-fake-key-op flctl press)
  
  ;; DOESN'T WORK
  clr (on-release-fake-key-op flclear press)

  ;; DOESN'T WORK
  clr (on-release-fake-key-op flclear release)

  ;; DOESN'T WORK
  clr (on-press-fake-key-op flclear press)

  ;; DOESN'T WORK
  clr (on-press-fake-key-op flclear release)
  
  ;; WORKS
  clr (multi
        (on-release-fake-key-op flctl release)
        (on-release-fake-key-op flsft release)
        (on-release-fake-key-op flmet release)
        (on-release-fake-key-op flalt release)
      )
)

@oblitzitate
Copy link
Author

oblitzitate commented Aug 4, 2022

Ah I see. It's unclear how removing deffakekeys would actually be workable without going all-in into implementing the 3rd suggestion.

My suggestion is to use normal key (instead of fake key) as arguments in fake-key-op expressions. Then when parsing it, auto-generate/cache a fake key in place of the normal key.

So anytime you refer to a normal key inside a fake-key-op expression, it would point to the generated fake key.

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

Let me check my understanding. The suggestion is that if argument to fake-key-op happens to map to a real key label like lmet then the code should auto-generate a fake key for lmet without needing deffakekeys. This would be convenient, but eliminating deffakekeys entirely would lose its flexibility, so it doesn't seem worth doing.

@oblitzitate
Copy link
Author

Maybe I'm missing something, but I'm not sure what flexibility you would lose (at least from a user's perspective).

deffakekeys just feels verbose.

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

Regarding the bug, I can't reproduce it. Do note that on-release-fake-key-op doesn't actually activate unless the fake key was actually pressed in the first place. I tested with this:

(deffakekeys
  pal (multi
        (on-press-fake-key-op ctl press)
        (on-press-fake-key-op sft press)
        (on-press-fake-key-op met press)
        (on-press-fake-key-op alt press)
      )
  ral (multi
        (on-release-fake-key-op ctl release)
        (on-release-fake-key-op sft release)
        (on-release-fake-key-op met release)
        (on-release-fake-key-op alt release)
      )
)

(defalias
  pal (on-press-fake-key-op pal tap) ;; works
  ral (on-release-fake-key-op ral tap) ;; releases on press
  rl1 (on-release-fake-key-op ral press) ;; does nothing at first
  rl2 (on-release-fake-key-op ral release) ;; activates if activating @rl1 first then @rl2
)

@oblitzitate
Copy link
Author

I forgot to try tap. That works for me. Still, it's confusing having to refer to it that way. I think multis are probably better off staying in defalias instead of deffakekeys.

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

Maybe I'm missing something, but I'm not sure what flexibility you would lose (at least from a user's perspective).

I think we're missing some details in each other's understanding of how the feature should work.

The simple case which loses flexibility (in my view) is:

  • remove deffakekeys
  • fake-key-op only accepts key actions (e.g. lmet, lctl) and auto-generates a fake key for it

This loses flexibility since fake keys can no longer be arbitrary actions. But it might be that it's unnecessary, not sure.

The complicated case, which if implemented correctly may as well go all the way to suggestion 3 is:

  • remove deffakekeys
  • fake-key-op accepts arbitrary actions and generates fake keys for it

This is complicated because designing the code such that press/tap/release on various physical keys will map to the correct intended common fake keys is complex.

@oblitzitate
Copy link
Author

oblitzitate commented Aug 4, 2022

Oh I understand now. I was so fixated on key actions, I forgot some other arbitrary action like a layer-switch action would complicate things.

Maybe you can allow to auto-generate fake keys for normal key actions, but don't remove deffakekeys in case of other arbitrary actions? Or is that still too much?

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

It's possible and not so difficult, but to me doesn't seem worth it since the feature then becomes inconsistent, which may harm the user experience more than the inconvenience of always using deffakekeys.

@oblitzitate
Copy link
Author

oblitzitate commented Aug 4, 2022

Fair enough. Thanks for responding quickly.

I suppose my only suggestion left is probably to shorten fake-key-op expressions even more. Perhaps remove op suffix (I assume it means operation) since it's implied. Perhaps remove the - symbol in fake-key so that it's consistent with deffakekeys. So the final expressions would be on-press-fakekey and on-release-fakekey

@jtroo
Copy link
Owner

jtroo commented Aug 4, 2022

I'm good with shortening the action names. I'll push to the PR shortly.

@jtroo jtroo closed this as completed in #92 Aug 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants