Articles > Mapping chord key combos on Linux

Mapping chord key combos on Linux

30.03.2024 by Matic Utsumi Gačar

Recently I realized that lots of my key combinations require pretty unhealthy hand movements, so I added chording key combos to my life.

Chording combos refer to pressing multiple keys at the same time (or in very quick succession) to trigger some action rather that outputing the keys. The keys still function normally when not pressed in a chord.

While I'm not exactly sure if chording is the official name for it, I've been using this cool vim plugin that calls it that, so that's how I'm going to refer to it.

kana/vim-arpeggioVim plugin: Mappings for simultaneously pressed keys.

Since I developed my own software for Linux key remapping a while ago (I'll talk about it in length another time), adding the functionality there seemed like the obvious way to go. The project is called map2 and allows for blazingly fast key-remapping and even allows running arbirary python code to achieve even the craziest ideas.

Something similar might be doable on Windows using AutoHotkey as well.

shiro/map2Linux input remapping for your keyboard, mouse and more!

After setting the end-goal, I started writing some quick prototype code in Python. The idea is simple:

  • define some key-combos in an array
  • bind a custom python callback on all keys that appear as chord inputs
  • once a chord-key is hit, set a timeout which delays the event
  • if another key is pressed, clear the timeoout and, if it completes a chord, remap it

Of course I knew from the get-go that the above is just the ideal path, there's lots of edge cases to handle here.

Here's my initial prototype code that mostly works but doesn't cover all edge cases (it's supposed to work, not look good):



The Python prototype was a good first step and reassured me that using chords is the way to go - it just feels nice and reduces finger travel time by a lot.

I could have ended it there, but since this seemed like something lots of people could benefit from, I decided to do a native implementation and support it as part of the core map2 API. After a fun weekend, I managed to hack together someting I was satisfied with.

Since map2 has a pretty nice e2e test harness, it was also possible to test lots of edge cases systematically, getting me to a working version without ever running the script on my keyboard - this was one of those rare movements where test-driven-development actually worked well!
Here's a sample:

examples/tests/chords.rs
#[pyo3_asyncio::tokio::test] async fn simple_chord() -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> { let m = pytests::include_python!(); reader_send_all(py, m, "reader", &keys("{a down}{b down}{a up}{b up}")); sleep(py, 55); assert_eq!(writer_read_all(py, m, "writer"), keys("c"),); sleep(py, 55); assert_empty!(py, m, WRITER); Ok(()) })?; Ok(()) }

And here's a sample reamapping script with the finalized API:

example.py
import map2 # capture keyboard inputs with a reader reader = map2.Reader(patterns=["/dev/input/by-id/your-kbd"]) # re-map them using a chrod mapper mapper = map2.ChordMapper() # and write them to a virtual device writer = map2.Writer(clone_from="/dev/input/by-id/your-kbd") # setup the event pipeline map2.link([reader, mapper, writer]) # and map some chords! mapper.map(["a", "b"], "c") # and so on...

Curently we only support 2-key-chords, however I also decided to write the code in a way that allows for future multi-key support, so look forward to that inevitable pull request.

After using chords for a while, hitting multiple keys at once became natural quickly, almost like playing a piano. I can feel myself keeping my hands on the center of my keyboard and avoiding unhealthy finger movements to the point where I instincively feel like a specific movement is too much and I should add another chord.

While chords are probably not everyone's cup of tea, I highly recommend giving it a try. If you're on Linux, it has never been easier - simply run pip install map2 and off you go!
If you need some inspiration, here's some smooth chord suggestions:

chords.py
# ";" + letters on the left side to capital letters for ch in "asdfgqwertzxcv": mapper.map([";", ch], ch.upper()) # ";" + letters on the right side to capital letters for ch in "hjklyuiopbnm": mapper.map(["z", ch], ch.upper()) # special symbols mapper.map(["[", "q"], "[") # [ mapper.map(["[", "w"], "]") # ] mapper.map(["d", "f"], "{escape}") # esc mapper.map(["[", "e"], "{shift down}1{shift up}") # ! mapper.map(["[", "f"], "{shift down}{slash}{shift up}") # ? mapper.map(["[", "z"], "{shift down}\{{shift up}") # { mapper.map(["[", "x"], "{shift down}\}{shift up}") # } mapper.map(["[", "t"], "{shift down}2{shift up}") # @ mapper.map(["[", "r"], "{shift down}3{shift up}") # # mapper.map(["[", "g"], "{shift down}4{shift up}") # $ mapper.map(["[", "3"], "{shift down}5{shift up}") # % mapper.map(["[", "2"], "{shift down}6{shift up}") # ^ mapper.map(["[", "c"], "{shift down}7{shift up}") # & mapper.map(["[", "v"], "{shift down}8{shift up}") # * mapper.map(["[", "a"], "{shift down}9{shift up}") # ( mapper.map(["[", "s"], "{shift down}0{shift up}") # ) mapper.map(["x", "o"], "{shift down}{comma}{shift up}") # < mapper.map(["x", "p"], "{shift down}{dot}{shift up}") # > mapper.map(["x", "i"], "=") # = mapper.map(["x", "j"], "{shift down}-{shift up}") # _ # number keys mapper.map([",", "f"], "0") mapper.map([",", "d"], "1") mapper.map([",", "s"], "2") mapper.map([",", "a"], "3") mapper.map([",", "r"], "4") mapper.map([",", "e"], "5") mapper.map([",", "w"], "6") mapper.map([",", "c"], "7") mapper.map([",", "x"], "8") mapper.map([",", "z"], "9") mapper.map([",", "q"], "`")

No more difficult movements for capital letters and reaching for number keys feels amazing, happy mapping!

Check the code on Github