yqrashawn / GokuRakuJoudo

config karabiner with ease
GNU General Public License v3.0
1.17k stars 120 forks source link

Old/legacy layer suggestions #158

Open fabiomcosta opened 2 years ago

fabiomcosta commented 2 years ago

Hi!

I've been playing around with Goku and I'm truly impressed about how it's changing my workflow.

One thing I tried to do was to make my :quote key act as a :left_command when held. For that I used the following code:

[:##quote :right_command nil {:alone :quote}]

This worked quite well for a while, until I started to notice that while typing quickly I would sometimes hit command related shortcuts, like command+m (when typing "I'm", for example), which would minimize my current application and was quite annoying.

What I really wanted was to trigger quote when pressing the key alone and command when holding it, and also when command is triggered I didn't want the quote to be triggered, so it would be one or the other. This behavior turned out to be quite tricky to implement in a reliable way. I tried different combinations of :alone, :delay/:cancel and :hold with no success.

Thinking further, I wanted to have a similar behavior to the existing :layer functionality, but without the current issue that is has that won't let you type quickly with the key that activates the layer.

I then thought that I could allow quick typing by adding simultaneous events between the key that activates the layer and all the other keyboard keys, and then setup the normal layer functionality. When I tried that it actually worked quite well, so I wanted to share the current implementation, which doesn't look very nice but maybe could be incorporated into Goku.

For this code sample, I'll be using an example of a navigation layer

It looks like this:

    {:des "navigation mode, fluid typing protection"
      :rules [
        [{:sim [:d :q] :simo {:dorder :strict}} [:d :q]]
        [{:sim [:d :w] :simo {:dorder :strict}} [:d :w]]
        ; ... And all the other keys on your keyboard :)
    ]}
    {:des "toggle navigation mode"
      :rules [
        [:d ["navigation_mode" 1] nil {:alone :d :afterup ["navigation_mode" 0]}]
    ]}
    {:des "navigation mode"
      :rules [:navigation_mode
        [:##j :down_arrow]
        [:##k :up_arrow]
        [:##h :left_arrow {:held :!Oleft_arrow}]
        [:##l :right_arrow {:held :!Oright_arrow}]
        ; etc...
    ]}

You can see the complete example on my own karabiner.edn file: https://github.com/fabiomcosta/dotfiles/blob/e61aff95c3c9434abb43ab5bc159c7751e8a8c28/home/.config/karabiner.edn#L224-L323

There I'm using the "d" key as my navigation layer key. When I'm typing really fast with "d", the sim rule is picked up instead of the one in the layer rules, which allows you to type quickly. The trade-off in this case is that when you want to use this layer you have to press "d" wait for the sim timeout, and then press the keys that you defined on your layer rules like hjkl in this case.

This type of behavior is perfect for home row mods, which is not currently well supported out-of-the-box by Karabiner (not in an intuitive and easy way at least).

One cool thing about this is that if you feel like you are using some of your rule keys very quickly right after you press your layer key you can just remove them from your "fluid typing protection" rules and that will allow you to have those responde to your layer rules immediately. If you look at my karabiner.edn you'll actually see that I did exactly that for the hjkl keys but not for the rest of the keyboard. There is obviously a trade-off that now if I type quickly between the layer key and those keys they will respond to the layer rules which might not be intended.

Let me know if there is anything that I can clarify, but I guess the best way to easily understand this is to try it out yourself and see how this differs from the legacy/old layer behavior.

Thank you very much for this project, it truly changed how I interact with my keyboard and quickly became an important tool for me to improve my workflow. I didn't realize how powerful this would be and always thought that the Karabiner interface would be good enough and I could always edit the karabiner.json if I needed more advanced features, but it truly is quite impossible to make real changes on that file. And Goku made it amazingly easy and, most importantly, maintainable!

franciscolourenco commented 2 years ago

I'm too, looking for a better implementation of layers in Karabiner Elements. Back when Karabiner was deprecated, and Karabiner Elements didn't yet support complex key modifications, I've implemented my own layer modes in Swift.

The solution I came up with didn't rely on timing, and didn't have accidental activations. As an example, let's imagine the space key activated the layer, and when the layer is active, pressing the letter i is supposed to send key_up. This is would be the sequence:

  1. When space is pressed, enter layer mode.
  2. Pressing i down, saves the key in a queue, but doesn't emit any keystrokes.
  3. Either one of the following:
    1. Releasing i up, sends up_arrow, flushing the queue.
    2. Keeping i pressed results in key repeat, which flushes the queue and sending multiple up_arrow keystrokes.
    3. If space is released before i is released, means the user is typing, so send space, and then i.

Is there a way to implement this in karabiner?

Another way to explain would be:

  1. After entering the layer mode, the first key-to-key keystroke is only sent after keyUp, because we are not sure if the user is typing or using the laywer
  2. After the first keystroke, the following keystrokes are sent normally on keyDown, because we know that we are in the layer mode.
  3. However if the layer mode is exited before the first keystroke is emitted, the user is probably typing, so we ignore the layer altogether.
gabriel-gardner commented 2 years ago

I believe I figured out a cleaner and improved version of @fabiomcosta's behavior. It is similar to QMK "modtap" functionality, and does not require you to define all of simultaneous event rules. See the two example edn files in this repo. It's not exactly the same setup, but I think it looks a bit simpler and let's you easily mod the home row keys to be modifiers and still type quickly.

I definitely agree something like this could be beneficial to add to goku, perhaps instead of the simlayer system, because I feel like simlayers trigger too often during regular typing. It seems to me better to set up layers that activate after a certain delay, and allow typing as fast as you like, instead ones that are dependent upon quick successive key presses.

franciscolourenco commented 2 years ago

@gabriel-gardner is it still possible to get accidental keystrokes with that setup? I'm back to using Octopus for the homerow mode

gabriel-gardner commented 2 years ago

@franciscolourenco not in any way I would define an "accidental keystroke". if you release any modifier keys before to_if_held_down_threshold_milliseconds, it will output the appropriate character on key up. If you hold a modifier over the limit, it will modify any keystrokes while held with the rules you defined, but if you release the modifier without typing anything it won't output anything (like how shift/command/etc act). This mimics the behavior I've seen from home row mods with QMK, although there may be some difference in specific keystroke sequencing edge cases.

I've been using this setup for more than 6 months and it has worked very smoothly for me. You can tweak the threshold values to match however long you tend to hold keys down when typing, and it should work quite well! The only "accidental" moments are if you hold a modifier down past the defined threshold, but that's how all home row mods I've seen have worked. I haven't tried Octopus, so not sure how that one handles it.

It should be pretty easy to test out one of the example edn files I put in the repo, so give it a go and let me know if something is off!

bangedorrunt commented 2 years ago

@gabriel-gardner I think you should make a new pr to tutorial/example section. This will help many, I believe. I've been using your implementation for a week and it's working so great, much better than I expected! Thank you.

gabriel-gardner commented 2 years ago

@babygau Thanks for giving it a try and I am glad to hear it is working well! Good call -- when I have some free time I will definitely write-up a good tutorial and pr the examples section.

Thank you for the nudge and I am happy my setup can be of service to you 🙂