💾 Archived View for tilde.team › ~contrapunctus › gemlog › keyboard-machinations-kmonad.gmi captured on 2023-09-28 at 17:09:29. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-12-03)

-=-=-=-=-=-=-

↩ gemlog

→ Next: Literate Programming 2: Jumping from compiler errors to the literate program

← Previous: Emacs Sidebars

Keyboard machinations with Kmonad

(2021-07-22T20:25:53+0530)

Yesterday, as I was going through the xcape issue tracker trying to find a way to configure it such that Shift would emit parentheses and place the cursor between them when tapped, I learned about Kmonad.

Kmonad's project page on GitHub

I began looking through their documentation to see if it could do what I wanted. I was pleasantly surprised to see that they chose s-expressions for their configuration format. I couldn't find my answers in the documentation, but the small-yet-active community in the IRC channel confirmed that it had the constructs for what I wanted.

Time to install it and give it a shot.

Installation

It wasn't present in the Debian Testing repositories, but installation was far from the ordeal I was expecting - I downloaded the Linux binary from their releases, did the usual `chmod u+x <binary>`, and placed it in ~/bin/. It's not every day that life is so easy - that was all it took.

Configuration

Each configuration file requires a `defcfg` form -

(defcfg
  ;; ** For Linux **
  input  (device-file "/dev/input/by-id/usb-04d9_1203-event-kbd")
  output (uinput-sink "KMonad output")
  fallthrough true)

The easiest thing to do was to swap Caps Lock and Escape, like I do with `setxkbmap`.

(defsrc CapsLock Esc)
(deflayer default Esc CapsLock)

I tried invoking kmonad with the `-d` option to dry-run my configuration and report any errors. Once I had a configuration it was happy with, I tried running it without `-d`, and ran into the uinput permissions problem covered in their FAQ. With some hassle and more help from the IRC channel (lesson learned - udev rule files must have the extension `.rules`), I finally got it running.

Inserting parentheses with Shift

I had a few false starts here; first, I tried -

(defalias
  parens      (tap-macro \( \) Left :delay 5)
  left-shift  (tap-next @parens LeftShift)
  right-shift (tap-next @parens RightShift))

(defsrc
  LeftShift RightShift
  CapsLock Esc)

(deflayer default
  @left-shift @right-shift
  Esc CapsLock)

...only for it to complain about the `:delay` keyword. I was helpfully informed on the IRC channel that the `:delay` feature was not yet released.

Then, I tried -

(defalias
  parens      (tap-macro \( P5 \) P5 Left)
  left-shift  (tap-next @parens LeftShift)
  right-shift (tap-next @parens RightShift))

(defsrc
  LeftShift RightShift
  CapsLock Esc)

(deflayer default
  @left-shift @right-shift
  Esc CapsLock)

...which did result in `()` being inserted when I pressed Shift, but also in the cursor going backward indefinitely until I pressed something else.

With more input from the community over the IRC channel, I finally got it working -

(defalias
  parens      (tap-macro \( P5 \) P5 Left P5)
  left-shift  (tap-next @parens LeftShift)
  right-shift (tap-next @parens RightShift))

(defsrc
  LeftShift RightShift
  CapsLock Esc)

(deflayer default
  @left-shift @right-shift
  Esc CapsLock)

Using Space to send Ctrl

Lastly, I tried getting Space to trigger Ctrl when used as part of a key chord (or "held", in Kmonad parlance). The rationale was that Ctrl is used in nearly every program—the idea of using the strongest fingers to press it, and to do so without leaving the home row, was alluring. Within Emacs, I'm a heavy user of C-m for Enter, C-w for backward-kill-word, and C-h for backward-delete-char, along with many other Ctrl-based bindings (even in a modal editing setup).

This was what got me to finally learn, through trial and error, what all the different button-definition commands were for.

`tap-next` behaves similar (but not identical) to xcape -

I tested it in Emacs' `speed-type-top-1000`, and quickly ran into its downside - if you type quickly, Space may not release fast enough and you can unwittingly send Ctrl. Having to avoid it while typing slowed me down quite a lot.

With `tap-hold` -

This, too, is not suitable for fast typing.

`tap-hold-next` is a combination of the above two, and is (I think) the exact behaviour of xcape -

Also not suitable for fast typing. The use case is to prevent unintentional triggers of Ctrl (not very useful in this case, but imagine if it was Esc (tap) and Ctrl (hold) instead of Ctrl and Space, and an accidental Esc could close your chat window) - if you accidentally press Space, just hold it down and it will trigger Space instead of Ctrl.

`tap-next-release` seemed like the solution -

With this, I was able to type at my usual speed. The only downside [1] was that holding down the keychord did nothing (e.g. you can't hold down C-left to go back many words).

`tap-hold-next-release` removed that downside -

[1] A possible downside to setting tap/hold behaviour for Space in general is that you can't hold it down to insert many spaces. Not something I'm particularly concerned about. I also imagine it would be an issue if one were playing a game, such as an FPS.

My final configuration looks like this -

(defcfg
  ;; ** For Linux **
  ;; TVSe Gold Prime
  input  (device-file "/dev/input/by-id/usb-04d9_1203-event-kbd")
  output (uinput-sink "KMonad output")
  fallthrough true)

(defalias
  parens      (tap-macro \( P5 \) P5 Left P5)
  ;; tap-hold-next rather than tap-next, to prevent accidental
  ;; insertion of parentheses while typing capital letters
  left-shift  (tap-hold-next 700 @parens LeftShift)
  right-shift (tap-hold-next 700 @parens RightShift)
  space       (tap-hold-next-release 200 Space LeftCtrl))

(defsrc
  LeftShift RightShift
  Space
  CapsLock Esc)

(deflayer default
  @left-shift @right-shift
  @space
  Esc CapsLock)

Epilogue

I'm quite excited to get used to this new keyboard layout, even though I'm still accidentally hitting Caps Lock and exiting chats in Gajim at the moment. 😅

(stemming from the muscle memory of my previous configuration)

In the upcoming days I might try creating a layer for emojis, to get a uniform interface for the ones I use regularly. I also want to try binding C-h to Backspace and C-w to C-backspace, hopefully bringing these bindings out of terminals and my Emacs to all applications.

Drop me a line

Support me on Liberapay