Closed oblitzitate closed 2 years ago
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.
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.
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
.
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.
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.
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
.
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.
Okay I think I understand. Basically, use fake keys as a middle-man (or filtering mechanism) to trick the keyberon's state management.
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.
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.)
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:
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.
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.
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
)
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.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.
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.
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?
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.
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
?
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.
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
.
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)
)
)
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.
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.
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.
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
)
I forgot to try tap
. That works for me. Still, it's confusing having to refer to it that way. I think multi
s are probably better off staying in defalias
instead of deffakekeys
.
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:
deffakekeys
lmet
, lctl
) and auto-generates a fake key for itThis 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:
deffakekeys
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.
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?
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
.
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
I'm good with shortening the action names. I'll push to the PR shortly.
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 copytap-macro-release
, you'd probably just have to combineon-release
withmulti
andmacro
)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).