jtroo / kanata

Improve keyboard comfort and usability with advanced customization
GNU Lesser General Public License v3.0
2.32k stars 114 forks source link

Handling multiple keyboards with different configurations #377

Closed noctuid closed 1 year ago

noctuid commented 1 year ago

My case is not like #357 since it's not just about what keys are available. I'd want my changes for the letter keys to be shared, but I work on laptops with drastically different bottom rows. I have one keyboard where the bottom row looks like fn, control, alt, muhenkan, space, henkan, etc., and that is very different from other laptops I would work on. I want to bind by position. The key to the left of the spacebar, for example, will be a different depending on the keyboard, so I need different keybindings depending on the keyboard.

It would be nice to have some builtin system for doing it whether it be conditionals or the ability to specify multiple non-overlapping defsrc/deflayers combined with imports/includes or any other method.

Please correct me if I'm wrong, but it looks like only one kanata instance can register a device, so running multiple kanata instances with different configurations (e.g. one for the letter keys that is shared between all computers and a separate instance for the bottom row) will not work.

jtroo commented 1 year ago

Please correct me if I'm wrong, but it looks like only one kanata instance can register a device, so running multiple kanata instances with different configurations (e.g. one for the letter keys that is shared between all computers and a separate instance for the bottom row) will not work.

This is correct. There is, however, a different workaround that might work (no guarantees). You can use the -s, --symlink-path flag to specify a device path for kanata. And you could run a second kanata instance on that symlink to do some more remappings.

Some sort of conditional configuration, as you suggested, would be a cleaner solution than the above though.

jtroo commented 1 year ago

Idea dump for something that came to mind:

A new defaliasenvcond configuration item that is parsed similarly to defalias, but takes an initial list parameter for an environment variable name and an expected value of that variable. The list parameter is chosen to easily differentiate from the atom-list pairs that would be used in the rest of the defaliasenvcond configuration.

Something like:

(defaliasenvcond (LAPTOP lp1)
  met ... something for lp1
)

(defaliasenvcond (LAPTOP lp2)
  met ... something for lp2
)

This won't enable the correct different visual configuration across all laptops like how a conditional import for the bottom row might, but it should still enable the use case since the visual aspect is purely cosmetic. One needs to make sure all of the keys that would be in use across all laptops are defined in defsrc and then have the correct alias according for the desired laptop.

noctuid commented 1 year ago

If the syntax had more lisp capabilities, it might be nice to be able to conditionally define variables for a list of keys to preserve the visual aspect and be able to somehow splice that into a deflayer (or alternate version that allows for that type of syntax, e.g. (deflistlayer main `(... ,@my-bottom-row))).

For my use case, I think allowing multiple non-overlapping defsrcs would be the cleanest looking way though. The import wouldn't have to be conditional to get most of the benefit. You could also for example have a base.kbd imported by the specific configuration like linux-lenovo.kbd or windows-dell.kbd or osx.kbd (assuming kanata gets OSX support at some point).

From a functional perspective, ignoring the visual aspect and having something like you propose would definitely be much preferable to fully replicating most of a configuration for multiple laptops, which is what I have been doing with kmonad. Some sort of conditional system like this would also be useful for someone who wanted to change key behavior based on laptop/OS rather than just handle keys being in different positions on different laptops.

jtroo commented 1 year ago

I implemented it in the way I proposed earlier in the linked PR. The reason I came up with the idea was that it seemed like the least amount of work (on my end haha) to be able to satisfy the use case of not having multiple copies of kanata for small variations in keyboard layout.

As an aside, having variations in defsrc would mean that deflayer would also need to vary in the same way. While this could keep the visual alignment intact, it seems like this could be more work on the configuration end to get it to work. I haven't thought about it too hard though. If it seems compelling enough to add in the future, the current implementation and a conditional defsrc/deflayer shouldn't conflict with each other.

noctuid commented 1 year ago

Thanks! Btw I switched to kanata on my personal laptop yesterday, and so far it is working very well. I was previously using kmonad on a Windows work laptop but was not using it on my personal laptop (except as a backup) due some missing functionality and small annoyances/issues. I was able to replace a bunch of other software (klfc/XKB + xmodmap + xcape + keyszer + sxhkd + hkd) with just kanata and sxhkd. Much simpler. Thanks for your work.

the current implementation and a conditional defsrc/deflayer shouldn't conflict with each other.

A conditional system based on environment variables also allows for more changes based on any arbitrary condition which multiple defsrcs wouldn't support. A defvar equivalent might also be useful to change the hold timeout on different systems, for example (I found the subjective experience of the same timeout value to be drastically different on different systems with kmonad).

As an aside, having variations in defsrc would mean that deflayer would also need to vary in the same way. While this could keep the visual alignment intact, it seems like this could be more work on the configuration end to get it to work.

I have no idea what the current implementation looks like, but I was imagining that multiple defsrcs would basically just be an append operation and validation would check that the length of each appended deflayer matched the length of the appended defsrcs. This would make it easy to combine files to support a bunch of different possible keyboards (e.g. base.kbd, lenovo-bottom-row.kbd, number-row.kbd, numpad.kbd, etc.). Though you can already include numpad keys, for example, and have the config work even for keyboards without a numpad, being able to split that off into a separate file/defsrc would look cleaner I think. An actual example is that I'm now using kanata to turn my trackpoint mouse buttons into thumb keys, and it would be slightly nicer to have a separate defsrc/file for that, but that's a very minor improvement.

Another way to support preserving the visual aspect for my original example would be to allow using aliases of simple keys in defsrc, so you could do something like this and not need to touch any deflayers

(defaliasenvcond (LAPTOP lenovo)
  spaceright1 henkan
  spaceright2 kana
  spaceright3 ralt)

Something else that might be worth considering at some point is permutations (e.g. like klfc supports). They allow applying a wide mod or angle-z mod to any layout. An example top row permutation for a wide mode would be y, u, i, o, p, [ to shift the letters right by one position. Permutations is how I'm currently handling bottom row keyboard differences (generating different configs from different permutations with klfc). They're very much not visually obvious though, and I don't actually need to use a permutation for a wide mod since I always use one.

jtroo commented 1 year ago

I've added some minimal functionality to achieve the use case, but there are some interesting ideas here so rather than closing I'll move this to a discussion. Maybe someone will get inspired and think of use cases that aren't possible without adding more functionality in this area.

Thoughts on defvar, I'm not sure how kmonad's implementation works, but kanata's millisecond tracking is based on a number of ticks according to a monotonic clock. So as long as the varying platforms' monotonic clocks are relatively accurate, there shouldn't be too much variation. Super-high system load would mess with this as well, but I don't think differing defvar would save you at that point haha.