Closed antler5 closed 6 months ago
Because of fall-through, sequence-position, and distinguishment, these keys can't be bound to
sldr
. A key-list can not containXX
, which is also not distinguishable. They can't bemacro sldr [a-z]
, because[a-z]
would fall-through (we want a no-op) and also are already bound (don't want existing keys to interact with the sequence).
Keys with sequence with fallthough triggering sldr
at the same time are quite possible with existing kanata features. E.g. autocorrect implementation:
(defcfg
process-unmapped-keys true
sequence-input-mode visible-backspaced
)
(defsrc)
(defvar seq-reset-keys (spc bspc enter))
(deflayermap (base)
___ (multi _ (fork XX sldr $seq-reset-keys))
)
(deftemplate seq (vk-name in out)
(defseq $vk-name $in)
(defvirtualkeys $vk-name $out)
)
;; autocorrect implementation
(template-expand seq seq001 (g u a g e) (macro g a u g e))
(template-expand seq seq002 (t h i e r) (macro t h e i r))
(template-expand seq seq003 (t u r e) (macro t r u e))
;; so on
Backspacing back up the trie should be doable, I don't know of any major blockers for it. I considered it before for my own uses but decided it wasn't worthwhile, I just tell myself not to make typing mistakes 😉
Regarding the no-op virtual keys, it seems Windows doesn't have any VK codes greater than 255, and Linux has a large range here that doesn't seem to have a key name:
OsCode::KEY_ONSCREEN_KEYBOARD => KeyCode::K632,
OsCode::KEY_633 => KeyCode::K633,
...
OsCode::KEY_703 => KeyCode::K703,
OsCode::BTN_TRIGGER_HAPPY1 => KeyCode::K704,
Perhaps we could special-case these and:
kbd_out.(release|press|write)_key
are called do nothing instead, just to make sure nothing strange happens.
visible-backspaced
doesn't treat these as backspace-requiredI haven't given any example sequences and was worried I hadn't explained what I was going for well enough, but we seem to be on the same page :p
@rszyma
That's beautiful, thank you! An excellent example, just still limited to real keys (can't have XX /in/ the sequence). I should learn how that fork
works.
@jtroo
- make sure visible-backspaced doesn't treat these as backspace-required
I've got a toy implementation, but am still, uh, learning Rust, and haven't fixed this specifically.
Edit: Added OsCodes::KEY_UNKNOWN
to the list of non-visual key-codes.
Ideally we don't need to bind them to real OsCode's. I've added a single key, second-to-last in enum KeyCode
*, and only code that touches sequences can differentiate specific instances of it (i need two) through some creative, non-production-quality data-wrangling. This will work for my use-case, but is heinous: I wonder if a more general and elegant solution exists as a super-set of the existing virtual-keys model. Code's been a pleasure to work with.
Edit: Now that I have it working how I wanted, I've collected my scattered "???" comments -- I don't necessarily need answers.
deflocalkeys
while trying to make modifiers cooperate-- I'm already binding these keys to OsCodes::KEY_UNKNOWN
, and wonder if I could put that into local-keys and get modifier-breakdown for ""free""...I wonder if a more general and elegant solution exists as a super-set of the existing virtual-keys model.
Extending the existing virtual keys mechanism doesn't seem promising to me. At the implementation level a virtual key is "merely" an input position in keyberon's layout and has no representation in the output keys that sequences operate on.
Certainly a new mechanism, e.g. virtual outputs could be created, but I don't imagine it would have any links in the implementation to virtual keys.
Extending the existing virtual keys mechanism [...] Certainly a new mechanism, e.g. virtual outputs could be created, but I don't imagine it would have any links in the implementation to virtual keys.
I've had a good think about it and understood what I'm doing (funny how we don't always know :p).
These Virtual Output Keys (vout
's?) are unique, non-fungible localkeys
with optional OsCode
bindings, so that's (architecturally) what they're an extension of. In configuration these unbound local-keys
could be in separate def
forms, be represented by splitting localkey
definitions from their OsCode
bindings forms, or be inferred from local-key
bindings which are omitted or given a sentinel value (expanded from XX
?). I currently resolve unbound localkeys
into OsCode::KEY_UNKNOWN
, and have maintained non-fungibility by adjusting parsing & Sequences to encode and interpret an additional <u16>
as a discriminating value.
Current the discriminator is always present (doubling the lengths of sequence paths), but it only needs to appear immediately after dedicated OsCodes
for unbound localkeys
(am not so sure about using OsCode::KEY_UNKNOWN
for this). This still being my only exposure to Rust, I'm curious about whether this constraint can be encoded in the type-system (on either end) in a way meaningfully contains the other: make a Vec<Either<KeyCode, VirtualOutputIdentity>>
or something and maybe the type-checker can lead me to anything other than Sequences that needs to tell them apart (eg. chords, modified keys are separate, and currently messier, but same theory. I expect it generalizes pretty broadly.).
Functional criteria for scratching-my-own-itch ends at parity with #956 (no multi-vout
chords or handling outside of Sequences), but I think the approach would be reasonable to merge if the above considerations are addressed and the type-system makes it prettier and/or is worth unwrapping everywhere else-- but i still don't have a great feel for whether that sounds solid (tests and docs aside). I definitely think that virtualoutputkeys
(which subsume localkeys
) and virtualinputkeys
(née fakekeys
) form a clear dichotomy through their names in a way that localkeys
, fakekeys
/ virtualkeys
, and my magic
keys don't, and that they may be easier to present to new users as two related, flexible concepts in juxtaposition rather than as two-three distinct ones.
The PR #961 implements a "worse-is-better"-style implementation that I think meets the use case
I question the value that virtualoutputkeys
-type feature can add, for it to be worth implementing and handling everywhere. The places I can think off the top of my head where it would be usable are:
switch
fork
But with the switch|fork
use case, I don't currently imagine that virtualoutputkeys
makes new use cases possible that aren't already possible with virtual input keys today.
Well, I suppose since virtual input keys aren't usable with fork
, it would help there. But switch
is a superset of all fork
functionality anyway - though it does have the quirk of not behaving the same as fork
does when called with rpt-any
.
Not digging in atm, but checking the docs, I see you're refering to the fork argument right-trigger-keys
and the per-case switch
argument keys check
. I haven't got a use for 'em there either, and this does make my config expressable. Look like they're just more call-site changes away though. What happens if we put one in a switch
anyway, just an errant KeyMacroN
emission? Should that be an error, and would that require refactoring the rest of the call-sites anyway? But however you wanna do it, sure; the prefix definitions make me chuckle.
I'd just highlight that accepting the accidental complexity of repurposing unused keycodes resolves the type complexity without necessarily ruling out unification with localkeys
. Require users to assign these ignored OsCodes
to localkey
atoms themselves and we preserve a minimal, customizable representation on the keymap, as well as making it clear how closely related they are. Again, talking without digging in to back this up, but as far as I can tell the only difference between localkeys
and Seq0-9 (as long as we're making the "selected keycodes" compromise) is whether the assigned OsCode
is one of those reserved no-ops. I'd leave the OpCodes unassigned by default, give them generic names (like, teach localkeys to expand NoOp0-NoOp9 into their #'s), and consider whether they behave appropriately when bound to localkeys and used in Sequences (and, perhaps incidentally, in switches) as-is. (I don't know that the prefix hack would as... straightforward, if that's what it is now :p)
Just sounds sleek to have unicode on the keymap and the ability to (re-)configure them like any other localkeys, that's the "vkey/vout" split right there, and I think you can still do that while dodging type changes by reserving OpCodes. But I could be missing something obv, and like I said, solves any use-case I can defend either way~
I'd leave the OpCodes unassigned by default, give them generic names (like, teach localkeys to expand NoOp0-NoOp9 into their #'s), and consider whether they behave appropriately when bound to localkeys and used in Sequences (and, perhaps incidentally, in switches)
Hm yea I think the nop0-nop9
naming (shortening to 4 chars to match other key lengths) is good. As you mentioned these are indeed usable in switch|fork
in addition to being useful in sequences. I personally prefer to leave these set by default though, so there's less for users to configure. In the code as it is today, it doesn't really matter if they are in use; if kanata has processed the key then the outputs will be suppressed.
There certainly could be features added in the future to manually configure which OsCode values are ignored and create one's own nopX
keys with custom numbers. But for now I'll defer to YAGNI - the PR seems good enough as-is (once names are changed).
The selection of opcodes is fine, I was only thinking about custom unicodes atoms-- but that's trivial to customize in src and you can get pretty close if aliases can include unicode anyway.
Thanks so much for hashin' it out with me c:
There are cases where I do (multi @anykey F13)
just to use F13 in a switch
later on. If I can do the same with noop1
in the future I would prefer that, but I'm non sure I understood the concept.
It matters most for Sequences with visible-backspace
enabled because (IIRC) otherwise the Sequence or Switch eats the captured input anyways, but yes, as of the above merge it should Just Work to swap F13
out for any of the explicitly-arbitrary nop0-9
values. I agree that this is preferable because it reflects intent more clearly, but maybe high-value function keys were the real Worse is Better all along.
non sure I understood the concept
While I'm happy with and support the PR as-is (it works! :p), nop
's could have been expressed as (deflocalkeys-linux nop0 XX [..])
. I argued above that this would make nop
examples double as additional supporting documentation for localkeys
, and would love to know if you think requiring users to write that binding explicitly would have been more helpful or confusing for your understanding.
I'm specifically curious whether your uncertainty about the feature stems from how, while nop0-9
are better names then seq0-9
, these keys -- called no-ops because they ""output"" suppressed keycodes -- aren't really "no-ops" from the user's perspective as soon as they're being used in a Seq. or Switch statement. Requiring users to define these bindings would not only make their conceptual nature as no-op localkeys
explicit (clarifying where they can be used once defined), but allow users to define whatever terms feel natural to them, and hence avoid introducing (in the long-term, supporting) these terms that are defined by their OS-facing (lack of) outputs rather than their role in user-facing configuration and the intent of their user-defined behavior. You can still write an alias that resolves to nop0-9
, so, do you have a term or unicode symbol in mind that you would use for such an alias if it were required? If so, perhaps it would have been clearer have users write (deflocalkeys-linux 🦆 XX [..])
( *quack* >u<) and not expose nop0-9
in the first place.
(Though now that I'm thinking about it again, a user might reasonably expect every XX
on their base later to be synonymous with such a localkey
-- not so, because multiple localkeys can't have the same binding and XX
would be a specially-handled exception that expends into nop0-9 keycodes sequentially, but I do realize there's a plot-hole of potential confusion in the solution I've fixated on)
There are cases where I do
(multi @anykey F13)
just to use F13 in aswitch
later on. If I can do the same withnoop1
in the future I would prefer that, but I'm non sure I understood the concept.
Yea the new nopX
keys should be able to fulfill the old case of high-value F-keys in a (hopefully) better way
maybe high-value function keys were the real Worse is Better all along.
This has been the recommended solution for a long time 😆. But there has been at least one report that I can recall where someone was actually using the f13-f24 keys for another purpose, so they needed a different solution. The solution then was to add more logic to switch
to allow processing on inputs.
Is your feature request related to a problem? Please describe.
There are a few compounding factors. I'd like:
sequence-input-mode visible-backspaced
Because of fall-through, sequence-position, and distinguishment, these keys can't be bound to
sldr
. A key-list can not containXX
, which is also not distinguishable. They can't bemacro sldr [a-z]
, because[a-z]
would fall-through (we want a no-op) and also are already bound (don't want existing keys to interact with the sequence).Describe the solution you'd like.
Ideally I would be able to bind multiple virtual-keys to (unique, if necessary) no-ops and use either the virtual-key names or the unique no-ops in sequence key-lists.
Describe alternatives you've considered.
If any substantial portion of
arbitrary-code
values are no-ops, it may be possible to bind to those and augment key-lists to be able to pick them up. I'm not sure how to figure out how they'll be interpreted though.Additional context
This is an itch I've scratched before by hacking on ZMK, and there's a group of alt-keyboard nerds working on a new implementation for QMK (see: qmk_sequence_transform). I was looking to KMonad for a software implementation, but just don't know where to begin with Haskell. I don't know any Rust either, but spent some time poking at Kanata by cloning existing CustomActions before stumbling into the trie-based Sequences which are already very close to the level of expressiveness I'm looking for. They're just missing the dedicated, distinguishable keys I've described, and the ability to back-space back up the trie, which idk that I want anyways (Traversal state is incremental / maintained until we reach a match, right?).
Bed now and idk when I'll be back, but I'd be happy in theory to learn enough Rust to hack something awful together, just for my own personal use, if you could spare a thought on what's feasible and how best to go about it -- though I expect there's some distance between the kind of patch I'd settle for and something worthy of mainline. I understand that the delegation to keyberon complicates this, hence my focus on no-ops -- it fits into my current mental model without too much risk of issues.
Any aid appreciated, and thank you for creating kanata as it is.