Firmware & goodies for making a KEYER (one-handed chorded keyboard).
JavaScript
400
126 commits
updated Mar 11, 2026
Firmware & goodies for making a Keyer (one-handed version of a chorded keyboard).
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Features:
Typing video youtube.com/watch?v=Ijwo7SQQ73Q (from Typeware).
(Send me your links on Bluesky bsky.app/profile/mrogalski.eu so that I can add them here!)
3d-printed keyers:
Commercial products:
Memes:
Welcome to the bottom of the ergonomic mechanical keyboard rabbit hole.
Let's start with some shopping.
Total: $34.40 (+shipping)
With all the materials and tools in hand, the first step is to form a metal scaffolding which will hold the switches in convenient positions. Traditional electronics devices tends to have "exoskeletons" - they're supported by an external case that surrounds them and protects them from your greasy fingers. This device is built around an endoskeleton of copper wire. We'll cover this endoskeleton with modelling clay in a moment. I hope you bought the thickest wire you could (while still fitting through the holes on the board) because in this device it's structural.
We'll start with a "GND loop". Cut a section of wire - about 20 or 30cm. Strip its insulation (all of it) & insert it into one of the GND holes on the board. Solder it in place - it should be firmly attached to the board. Insert the battery and take the board in your hand. Position it like you'd like it to stay in your hand and start bending the wire into a loop that goes through all the places where key switches bases are going to be placed. For some extra rigidity (long wire behaves a little like a spring) lead the other end of the wire back into another GND hole on the board. You can take the switches with keycaps and place them so that one of their contact points touch the wire. This will give you a better idea of how the keyer is going to end up looking. Don't worry about it being wobbly - we'll use this property to model it a little in a moment. First complete the loop by soldering the other end of the GND loop to the board. If your GND loop happens to pass near other GND holes, you can insert short sections of wire and solder them to the main GND loop as additional supports. This will increase the rigidity of the construction.
Once GND loop is complete, take your key switches and attach them to the GND loop so that one of their contact points makes an electrical contact. You can solder them directly but it's a good idea to start with some hot glue to hold them in place. In my version I also bent the contacts on the key switches to make them smaller (DIY low profile) and to take less space.
As you're going through the process the keyer is going to become more "complete" and you will be able to bend the wire a little to improve key positioning. Remember that hot glue and solder don't form particularly strong bonds so be careful about bending and ideally use pliers to do that precisely.
One word of advice about key positioning is that I've noticed that the keys are "nicest" to press when the axis of pressing goes straight into the palm of your hand. Motions that go parallel to palm of the hand, motions that extend fingers and motions that move fingers to the side are pretty awkward and uncomfortable. I guess our hands evolved to hold things, rather than poke or flick at them. Some keyboard manufacturers might disagree. Their keyboards look undeniably awesome, but this is your keyer and it should be comfortable to use - so make sure the keys are pressed in the same direction that you'd hold something.
Once you attached all of the keys, it's time to add even more rigidity into our construction. We'll do this by connecting the remaining contact points on the switches to the GPIO holes on the board. They're marked on the board with text that says "IO##". It doesn't matter which IO port you choose, but write down which key goes to which IO port - it's something that will have to be entered in the firmware. Take a short cut of the wire, strip it at both ends. Bend it (with pliers) so that it fits in the hole and goes straight to the switch. Then solder it in place at both ends. It's important that the wires going to the IO ports don't touch the GND loop. Insulation should help with that.
After this step, the keyer should be fairly rigid. Mount the keycaps and see how it feels. It's obviously a little "spiky" but you'll have to endure the pain for the moment. Right now bend the wires to put all the key switches in their right positions.
At this point you can go to the "Flashing Firmware" section and check out how your keyer works! It's good to see if you didn't mess anything up so far. The hardest part is over!
Now is the time to open up the modelling clay and use it to cover our keyer. Before you begin, remove the keycaps, as they'll only get in the way. Take a small amount of clay and start shaping it in your hand. Squeeze it and fold in half. Repeat this about twenty times. Modelling clay must be mixed a little to prevent it from crumbling. You'll have to do this with every bit of clay that you're adding to the sculpture.
Once you have your warm and soft piece of clay, slap it on top of the keyer - wherever you want to cover something. It's important to cover the bottom parts of the switches - that's the part that may prick your fingers. Everything else is optional. I decided to keep my development board mostly visible and only covered the wires.
As you're sticking pieces of clay, one after another, you may find the resulting shape a little bit ugly. Turns out modelling stuff out of clay is hard! I've found a couple of tricks that may help you:
You can try to take the keyer in your hand at this point but be careful. The clay is very pliable and may deform under the pressure of your hand.
One useful thing at this point is to try to put on the keycaps and to see whether they can be pressed all the way in. If they cannot - then either the clay (or the keycap) has to be trimmed. At this point the clay is still soft so it's easy to correct it.
Once you're done with modelling (it can take a couple of hours) heat up an oven to 110Β°C and put your keyer inside. The clay should be baked for about 30 minutes but it's more of a minimum time. Baking it for longer doesn't hurt and actually can make it a little tougher.
Oh, I hope you removed the battery before putting the keyer in the oven. If you didn't then you'll have to get a new one (oven). And call the fire department.
Assuming you removed the battery beforehand, after baking, the clay should be fairly tough - roughly as hard as high quality plastic.
# Clone this repository
$ git clone https://github.com/mafik/Keyer.git
# Enter the cloned directory
$ cd Keyer
# Build project
$ pio run
# Upload firmware
$ pio run --target upload
ChordKeyboard.cpp. Change the kButtonPin array to the IO ports that you used for connecting the switches. You can also rename the keyer by replacing "πππ" with your name. Bluetooth names may be truncated to 16 bytes so you don't have a lot of emojis to work with. Feel free to explore this file and experiment.kDebug to true and running the program with pio run --target upload --target monitor. This will let you see what the board is doing while you're fiddling with the code and pressing the keys.Alright, that's it! You may wonder - how it is possible that we've finished the work for today in like an hour? Well - it may not be an hour on your system. Technology has a bad habit of surprising us in the most unpleasant ways. It's possible that you will have issues installing PlatformIO. Or that your PC won't have a BLE receiver. Or one of a thousand other reasons. That's why whenever you're doing something on a PC, book 10x the time. If it all goes according to plan - then it's a nice surprise. It's much better to be surprised in positive ways than in negative ones. So set your expectations low and don't get disappointed if some technical issues pop up. And - if you've been lucky and everything worked smoothly, enjoy the rest of the day! You earned it.
Everybody has different typing patterns and what works best for me will likely not be the best one for you. This is especially important for a keyer. Do you intend to use your keyer to play games? Or chat with AI? Blast terminal commands? Write notes? Emails? A custom keyboard layout will help you be more efficient at doing the things you're doing. The default layout was optimized to minimize the text entry time for a mix of English, Polish, C++ and Python code. That's probably not exactly what you'll be using your keyer for. That's why it's worth taking a moment to do this properly - and make a custom layout just for yourself.
If you want to squeeze out the maximum efficiency then you might be interested in the ability to write complete words by pressing a single chord. Yep, that's right - you can program a chord to enter Β―\\_(γ)_/Β―. Or your credit card number. Oryour 20-character super secret password of mixed letters, digits and that single punctuation character. It's your keyer - you can trust it with those things.
Multi-character chords are the secret (not so secret any more, heh) trick of stenographers. They just mash a bunch of letters, and the keyboard arranges them into a proper word.
If you think about this, figuring out how to arrange the set of keys on a keyer is actually pretty complicated. Chords can overlap and interact - that's one thing that can be exploited. We could minimize finger movement. Additionally different keys can be pressed with different effort. A thumb is stronger and more dextrous than a little finger. If you think about the number of possible input options (exactly 586 possible shortcuts) and the fact that we like to fill those chords with useful key sequences (what key sequences would be best? single keys? syllables? common words?), things are getting pretty complex. The total number of potential layouts is... Let's just say it's beyond astronomical. It's one of those crazy numbers where the number of digits needed to write it down is already hard to write. Here is a subreddit dedicated solely to exploring this rabbit hole: r/KeyboardLayouts. Be careful before entering. Few of those who enter, return. And we would rather use the keyer to type, rather than figure out how to type.
This is the reason for the layout_generator/ directory. It contains all the tools you need to make a custom layout - all in a single day. Assuming you have a reasonably fast PC. That's right - you're going to do some serious science on your PC. Essentially it's going to scan this whole "beyond astronomical" space of all potential layouts to find the best one. And that will take some processing power. And a couple clever algorithms. But we'll get to that once we start the optimization process. It'll run for a couple of hours anyway.
Let's start by creating a corpus. Take some of your writing (as text files) - and drop it in layout_generator/corpus. You can look on your PC for some of your notes. Or copy your chat history. Or grab some online docs. Or that half-finished book you started writing years ago. Grab as many files as you can. My corpus amounted to 2847715 bytes - that's almost 3MB. This will probably take you some time so come back when you're done. Just make sure to store everything in text files, rather than PDFs.
Alright - the next step is to tweak the parameters for finger motion and key press effort. They may be different if your keyer has a different shape than mine. You can change them in keyer_simulator.cpp. Don't worry too much about being super precise - you can just "feel" how nice each key is to press - and assign it a "cost" in milliseconds. Once you're done, ask some AI chatbot to help you recompile it - it should guide you through the process. Every PC is a little different and AI chatbots know about everything that can go wrong during this process. They eat StackOveflow questions for breakfast.
Another file that you might want to tweak is the planner.py. Look at the main() function - in there you'll find some code that removes some of the hard-to-type chords from the optimization process. And also some "forced_assignments". You can tweak these to assign some keys to nice, memorable chords. As you can see, some of the chords have been assigned to unused characters (capital letters) that are actually placeholders for common shortcuts. You can leave them as is or change them to your liking. The optimizer was configured to optimize 4-chord sequences and to assume that shift is placed on the pinky finger. Some of the Ctrl+C / V / X / Z combinations have also been assigned so that they are similar to their Ctrl-free versions.
As you're starting out, you may also tweak layouts_per_generation (currently 240) to a smaller value - like the number of cores in your CPU. This will allow you to see the optimization results more quickly - and if you're happy with how they look, you'll bump it up back to 240 and let it optimize properly overnight.
If you're going to use some non-english letters, then you should also look at qwerty_analysis.py - it's a file that converts the characters from your corpus into sequences of qwerty-equivalent key presses. Depending on your language you may have to implement this mapping yourself.
Anyway, with all of the adjustments done, run the planner.py script. While the script is running we have some time to explain what it does.
https://github.com/user-attachments/assets/4c7ad9f6-67f0-450c-9985-fa53694d1321
I used the word "beyond astronomical" before. And space (cosmos) is actually a good metaphor to imagine the search process. Let's say we're looking for gravity wells. The bigger the better. Ideally - the supermassive black holes in the centers of galaxies. And not just any supermassive black holes. We want to find the biggest one in this whole universe. That's the optimal layout that we're looking for. During our search we have one major limitation - we can only check this space one point at a time. The score_layout function from keyer_simulator.cpp can take any point in this space (any layout), and measure how deep is the gravity well at this point (how fast it is to type with it). It's a rather expensive process. That's why this bit is implemented in C++ and optimized quite a bit.
You may have heard of fancy machine learning algorithms based on gradients - but hold your horses. We're dealing with a combinatorial problem here. We can swap a pair of keys to improve or lower the score - but it's not a continuous space. There is some smoothness here - that's true. We can imagine our optimal layout somewhere out there. The layouts that are very similar to the optimal one (one key swap away) are probably also decent. And the layouts one step further are probably also quite acceptable. But this kind of smoothness can't be used to create a "direction" that we could optimize in.
Most of the points that we can randomly pick in this space will contain total garbage layouts. Keys that should be close together have very little chance of actually being placed reasonably. That roughly matches the space analogy. If you pick a random point anywhere in the universe, you'll likely end up somewhere far from any galaxies. The fancy term for this search strategy is random search. Using this approach would give us pretty awful results though. The space is really large. The chance that we'll hit the deepest gravity well in the universe by pure luck are pretty slim.
Another approach is to focus our search near some good points. Like picking mushrooms in a forest. You find a mushroom πβπ«? That means there sholud be more nearby. So we can check the neighbourhood of good mushrooms (ekhm... I mean points) more dilligently. We start picking random points throughout the universe and then we progressively start focusing on the nice locations. The fancy term for this approach is simulated annealing. Doing this very slowly, mathematically speaking should let us find the optimal point. Unfortunately mathematicial proofs don't translate well into practical algorithms. This is one of those situations. The "very slowly" in mathematics translates to "forever" in practice.
For practical algorithms we'll have to learn from the force of nature that defines what it even means to be practical - from the life itself.
This algorithm is the staple of optimization techniques - an evolutionary algorithm π§¬. In this one we have to treat different points in the space that we're searching like living creatures. First we scatter a bunch of points throughout the universe randomly. Then we measure them. Then we kill half of them. Thanos style π€. The remaining points have virtual baby points that are a mixture of their parents plus some random mutations. In each iteration of evolution there is some death and there is some life. And some random mutations. Most of the mutations are cancer. But some of them are good. The unlucky cancer babies will die. But the lucky ones will live long enough to exchange genes. This cool trick (scientifically called meiosis, and colloqually - sex) is why we humans continue to exist and can survive a competition against bacterias that have reproductive cycle measured in hours and vastly greater populations. It's crazy to think what would happen if a germ like COVID-19 figured out sexual reproduction...
Anyway, from our layout search perspective evolutionary algorithm is actually quite decent. It would probably help us find a supermassive black hole. But it's not as good as the next one.
The last approach, and the one that is running in your terminal right now is based on ant behavior π. See, ants have a very nice way of exchanging information - they do this by depositing pheromones. They do this once they find food. When another ant finds a trail of pheromone, it starts to follow it. It can randomly lose it, but only rarely. Once that other ant finds more food (presumably the original piece of food that lured the first one to your kitchen) it deposits even more pheromone. Other ants follow it more and more. Initially the ants explore many pheromone paths, but after some time it turns out that some of them are more efficient than the others - and those paths start to snowball. Eventually the ants form a path that leads them straight under your fridge. The cool property is that the pheromone is a much more potent way of exchanging information. It encodes uncertainty. Also all ants in the colony deposit the pheromone together. Turns out that for combinatorial problems, this kind of optimization is even more efficient than evolutionary algorithms.
This is what you can see in your terminal. It's a pheromone matrix. It's a measure of how much pheromone the virtual ants deposited by on a given key/chord assignment. As the ants follow the pheromone trail they start to focus on good layouts. The pheromone matrix will start to look more and more sparse. Eventually all of the ants will follow the same key/chord assignment. That's our perfect layout.
![]() | ![]() |
On my PC it took 21303 generations (it ran overnight). In each generation 240 ants went out to search food (the best layout) and only the victorious ant was allowed to deposit the pheromone. The pheromone evaporates a little, so that eventually the quantity of pheromone starts to stabilize. It's a fairly slow process - but only because our ants are searching through mind boggingly large space. Given the complexity of the problem, I think they're doing quite well!
You will find the resulting layout in best_layout.txt . I do recommend to take a look at it and see how you'd type some of common words that you might enter frequently. I found some of the key combos to be quite interesting (like shifting from . or ; to Enter).
Unfortunately the ants can't optimize multi-key chords - you'll have to place those using your own intuition.
Edit the chord assignments in ChordKeyboard.cpp and pio run --target upload it to the device. Now you're ready to start using it!
This section is for the people who decided to keep designing their layout rather than use it. It's better if you stay away from these ideas.
qwerty_analyzer to assign a chord to the Shift key. By default Shift is just assumed to be on the pinky. This way the optimization can focus on the four main fingers.Within the layout_tutor/ you will find a small utility that will help you memorize the chording sequences. A version that can teach you the default layout can be found at: mafik.github.io/keyer/. I hope that you've already made your own layout though! If you did, then put all the chord sequences in the layout_tutor/layout.js file. You can also swap out the dictionary (an English dictionary is included in layout_tutor/words_dictionary.js by default). The state.js file contains some variables like targetWPM or targetAccuracy which are used by the script to automatically advance you through the exercises - you may tweak them to your liking. When all is fine, just open up index.html in a browser and start typing.
The most important element of proper typing on a keyer is minimizing key presses. The chords are optimized to reuse the finger positions so most often you will only have to press one extra finger. That's the main thing you have to keep in mind when going through the exercises.
Each step in the learning process will teach you how to combine two letters together. Then, when you can type them quickly, it will swap to some dictionary words. Your goal is to maintain 15+ WPM (Words Per Minute) and accuracy above 90%. Once you can do that for 30 subsequent key presses, then the exercise will auto-advance to the next step. And there are a lot of steps! Each pair of letters essentially has to be learned like that. It's a rather enjoyable process in fact. Once you're done - you should be able to write with a decent speed. 15 WPM is maybe well below typical QWERTY speeds but it's nothing to sneeze at! After you finish you can start the next round at 20 WPM, then 25, then 30.
One thing about building a muscle memory is that it requires long training sessions and you actually don't see much improvement during those sessions - but it does show up after some time. Typically the next day.
So that's all from me. I'll let you get some practice now!
# Tweak FreeRTOS configuration
$ pio run --target menuconfig
# Clean build files
$ pio run --target clean
layout_generator/ - a set of Python scripts for generating an optimized chord layout
corpus/ - directory for text files that will be used for evaluating the layoutplanner.py - main entry point for doing the optimizationqwerty_analysis.py - converts the text files into a sequence of equivalent IBM PC keyboard keyskeyer_simulator.cpp - simulates text entry on the keyerbeam_optimizer.py - optional utility to double-check whether the generated layout is (locally) optimalsrc/ - code that runs on the ESP32sdkconfig.ChordKeyboard - configuration for the ESP-IDF firmwarelayout_tutor/ - the home of "Keyer Flight School" - a webapp for learning to type with chordsThe src/ directory contains a prototype code for a wearable SSH Terminal that connects and displays its output through connected smart glasses.
The SSH code is not particularly polished so don't expect it to work out of the box.
117 followers Β· starred Oct 2025
505 followers Β· starred Oct 2025
253 followers Β· starred Oct 2025
868 followers Β· starred Oct 2025
Firmware & goodies for making a KEYER (one-handed chorded keyboard).
JavaScript
400
126 commits
updated Mar 11, 2026
Firmware & goodies for making a Keyer (one-handed version of a chorded keyboard).
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Features:
Typing video youtube.com/watch?v=Ijwo7SQQ73Q (from Typeware).
(Send me your links on Bluesky bsky.app/profile/mrogalski.eu so that I can add them here!)
3d-printed keyers:
Commercial products:
Memes:
Welcome to the bottom of the ergonomic mechanical keyboard rabbit hole.
Let's start with some shopping.
Total: $34.40 (+shipping)
With all the materials and tools in hand, the first step is to form a metal scaffolding which will hold the switches in convenient positions. Traditional electronics devices tends to have "exoskeletons" - they're supported by an external case that surrounds them and protects them from your greasy fingers. This device is built around an endoskeleton of copper wire. We'll cover this endoskeleton with modelling clay in a moment. I hope you bought the thickest wire you could (while still fitting through the holes on the board) because in this device it's structural.
We'll start with a "GND loop". Cut a section of wire - about 20 or 30cm. Strip its insulation (all of it) & insert it into one of the GND holes on the board. Solder it in place - it should be firmly attached to the board. Insert the battery and take the board in your hand. Position it like you'd like it to stay in your hand and start bending the wire into a loop that goes through all the places where key switches bases are going to be placed. For some extra rigidity (long wire behaves a little like a spring) lead the other end of the wire back into another GND hole on the board. You can take the switches with keycaps and place them so that one of their contact points touch the wire. This will give you a better idea of how the keyer is going to end up looking. Don't worry about it being wobbly - we'll use this property to model it a little in a moment. First complete the loop by soldering the other end of the GND loop to the board. If your GND loop happens to pass near other GND holes, you can insert short sections of wire and solder them to the main GND loop as additional supports. This will increase the rigidity of the construction.
Once GND loop is complete, take your key switches and attach them to the GND loop so that one of their contact points makes an electrical contact. You can solder them directly but it's a good idea to start with some hot glue to hold them in place. In my version I also bent the contacts on the key switches to make them smaller (DIY low profile) and to take less space.
As you're going through the process the keyer is going to become more "complete" and you will be able to bend the wire a little to improve key positioning. Remember that hot glue and solder don't form particularly strong bonds so be careful about bending and ideally use pliers to do that precisely.
One word of advice about key positioning is that I've noticed that the keys are "nicest" to press when the axis of pressing goes straight into the palm of your hand. Motions that go parallel to palm of the hand, motions that extend fingers and motions that move fingers to the side are pretty awkward and uncomfortable. I guess our hands evolved to hold things, rather than poke or flick at them. Some keyboard manufacturers might disagree. Their keyboards look undeniably awesome, but this is your keyer and it should be comfortable to use - so make sure the keys are pressed in the same direction that you'd hold something.
Once you attached all of the keys, it's time to add even more rigidity into our construction. We'll do this by connecting the remaining contact points on the switches to the GPIO holes on the board. They're marked on the board with text that says "IO##". It doesn't matter which IO port you choose, but write down which key goes to which IO port - it's something that will have to be entered in the firmware. Take a short cut of the wire, strip it at both ends. Bend it (with pliers) so that it fits in the hole and goes straight to the switch. Then solder it in place at both ends. It's important that the wires going to the IO ports don't touch the GND loop. Insulation should help with that.
After this step, the keyer should be fairly rigid. Mount the keycaps and see how it feels. It's obviously a little "spiky" but you'll have to endure the pain for the moment. Right now bend the wires to put all the key switches in their right positions.
At this point you can go to the "Flashing Firmware" section and check out how your keyer works! It's good to see if you didn't mess anything up so far. The hardest part is over!
Now is the time to open up the modelling clay and use it to cover our keyer. Before you begin, remove the keycaps, as they'll only get in the way. Take a small amount of clay and start shaping it in your hand. Squeeze it and fold in half. Repeat this about twenty times. Modelling clay must be mixed a little to prevent it from crumbling. You'll have to do this with every bit of clay that you're adding to the sculpture.
Once you have your warm and soft piece of clay, slap it on top of the keyer - wherever you want to cover something. It's important to cover the bottom parts of the switches - that's the part that may prick your fingers. Everything else is optional. I decided to keep my development board mostly visible and only covered the wires.
As you're sticking pieces of clay, one after another, you may find the resulting shape a little bit ugly. Turns out modelling stuff out of clay is hard! I've found a couple of tricks that may help you:
You can try to take the keyer in your hand at this point but be careful. The clay is very pliable and may deform under the pressure of your hand.
One useful thing at this point is to try to put on the keycaps and to see whether they can be pressed all the way in. If they cannot - then either the clay (or the keycap) has to be trimmed. At this point the clay is still soft so it's easy to correct it.
Once you're done with modelling (it can take a couple of hours) heat up an oven to 110Β°C and put your keyer inside. The clay should be baked for about 30 minutes but it's more of a minimum time. Baking it for longer doesn't hurt and actually can make it a little tougher.
Oh, I hope you removed the battery before putting the keyer in the oven. If you didn't then you'll have to get a new one (oven). And call the fire department.
Assuming you removed the battery beforehand, after baking, the clay should be fairly tough - roughly as hard as high quality plastic.
# Clone this repository
$ git clone https://github.com/mafik/Keyer.git
# Enter the cloned directory
$ cd Keyer
# Build project
$ pio run
# Upload firmware
$ pio run --target upload
ChordKeyboard.cpp. Change the kButtonPin array to the IO ports that you used for connecting the switches. You can also rename the keyer by replacing "πππ" with your name. Bluetooth names may be truncated to 16 bytes so you don't have a lot of emojis to work with. Feel free to explore this file and experiment.kDebug to true and running the program with pio run --target upload --target monitor. This will let you see what the board is doing while you're fiddling with the code and pressing the keys.Alright, that's it! You may wonder - how it is possible that we've finished the work for today in like an hour? Well - it may not be an hour on your system. Technology has a bad habit of surprising us in the most unpleasant ways. It's possible that you will have issues installing PlatformIO. Or that your PC won't have a BLE receiver. Or one of a thousand other reasons. That's why whenever you're doing something on a PC, book 10x the time. If it all goes according to plan - then it's a nice surprise. It's much better to be surprised in positive ways than in negative ones. So set your expectations low and don't get disappointed if some technical issues pop up. And - if you've been lucky and everything worked smoothly, enjoy the rest of the day! You earned it.
Everybody has different typing patterns and what works best for me will likely not be the best one for you. This is especially important for a keyer. Do you intend to use your keyer to play games? Or chat with AI? Blast terminal commands? Write notes? Emails? A custom keyboard layout will help you be more efficient at doing the things you're doing. The default layout was optimized to minimize the text entry time for a mix of English, Polish, C++ and Python code. That's probably not exactly what you'll be using your keyer for. That's why it's worth taking a moment to do this properly - and make a custom layout just for yourself.
If you want to squeeze out the maximum efficiency then you might be interested in the ability to write complete words by pressing a single chord. Yep, that's right - you can program a chord to enter Β―\\_(γ)_/Β―. Or your credit card number. Oryour 20-character super secret password of mixed letters, digits and that single punctuation character. It's your keyer - you can trust it with those things.
Multi-character chords are the secret (not so secret any more, heh) trick of stenographers. They just mash a bunch of letters, and the keyboard arranges them into a proper word.
If you think about this, figuring out how to arrange the set of keys on a keyer is actually pretty complicated. Chords can overlap and interact - that's one thing that can be exploited. We could minimize finger movement. Additionally different keys can be pressed with different effort. A thumb is stronger and more dextrous than a little finger. If you think about the number of possible input options (exactly 586 possible shortcuts) and the fact that we like to fill those chords with useful key sequences (what key sequences would be best? single keys? syllables? common words?), things are getting pretty complex. The total number of potential layouts is... Let's just say it's beyond astronomical. It's one of those crazy numbers where the number of digits needed to write it down is already hard to write. Here is a subreddit dedicated solely to exploring this rabbit hole: r/KeyboardLayouts. Be careful before entering. Few of those who enter, return. And we would rather use the keyer to type, rather than figure out how to type.
This is the reason for the layout_generator/ directory. It contains all the tools you need to make a custom layout - all in a single day. Assuming you have a reasonably fast PC. That's right - you're going to do some serious science on your PC. Essentially it's going to scan this whole "beyond astronomical" space of all potential layouts to find the best one. And that will take some processing power. And a couple clever algorithms. But we'll get to that once we start the optimization process. It'll run for a couple of hours anyway.
Let's start by creating a corpus. Take some of your writing (as text files) - and drop it in layout_generator/corpus. You can look on your PC for some of your notes. Or copy your chat history. Or grab some online docs. Or that half-finished book you started writing years ago. Grab as many files as you can. My corpus amounted to 2847715 bytes - that's almost 3MB. This will probably take you some time so come back when you're done. Just make sure to store everything in text files, rather than PDFs.
Alright - the next step is to tweak the parameters for finger motion and key press effort. They may be different if your keyer has a different shape than mine. You can change them in keyer_simulator.cpp. Don't worry too much about being super precise - you can just "feel" how nice each key is to press - and assign it a "cost" in milliseconds. Once you're done, ask some AI chatbot to help you recompile it - it should guide you through the process. Every PC is a little different and AI chatbots know about everything that can go wrong during this process. They eat StackOveflow questions for breakfast.
Another file that you might want to tweak is the planner.py. Look at the main() function - in there you'll find some code that removes some of the hard-to-type chords from the optimization process. And also some "forced_assignments". You can tweak these to assign some keys to nice, memorable chords. As you can see, some of the chords have been assigned to unused characters (capital letters) that are actually placeholders for common shortcuts. You can leave them as is or change them to your liking. The optimizer was configured to optimize 4-chord sequences and to assume that shift is placed on the pinky finger. Some of the Ctrl+C / V / X / Z combinations have also been assigned so that they are similar to their Ctrl-free versions.
As you're starting out, you may also tweak layouts_per_generation (currently 240) to a smaller value - like the number of cores in your CPU. This will allow you to see the optimization results more quickly - and if you're happy with how they look, you'll bump it up back to 240 and let it optimize properly overnight.
If you're going to use some non-english letters, then you should also look at qwerty_analysis.py - it's a file that converts the characters from your corpus into sequences of qwerty-equivalent key presses. Depending on your language you may have to implement this mapping yourself.
Anyway, with all of the adjustments done, run the planner.py script. While the script is running we have some time to explain what it does.
https://github.com/user-attachments/assets/4c7ad9f6-67f0-450c-9985-fa53694d1321
I used the word "beyond astronomical" before. And space (cosmos) is actually a good metaphor to imagine the search process. Let's say we're looking for gravity wells. The bigger the better. Ideally - the supermassive black holes in the centers of galaxies. And not just any supermassive black holes. We want to find the biggest one in this whole universe. That's the optimal layout that we're looking for. During our search we have one major limitation - we can only check this space one point at a time. The score_layout function from keyer_simulator.cpp can take any point in this space (any layout), and measure how deep is the gravity well at this point (how fast it is to type with it). It's a rather expensive process. That's why this bit is implemented in C++ and optimized quite a bit.
You may have heard of fancy machine learning algorithms based on gradients - but hold your horses. We're dealing with a combinatorial problem here. We can swap a pair of keys to improve or lower the score - but it's not a continuous space. There is some smoothness here - that's true. We can imagine our optimal layout somewhere out there. The layouts that are very similar to the optimal one (one key swap away) are probably also decent. And the layouts one step further are probably also quite acceptable. But this kind of smoothness can't be used to create a "direction" that we could optimize in.
Most of the points that we can randomly pick in this space will contain total garbage layouts. Keys that should be close together have very little chance of actually being placed reasonably. That roughly matches the space analogy. If you pick a random point anywhere in the universe, you'll likely end up somewhere far from any galaxies. The fancy term for this search strategy is random search. Using this approach would give us pretty awful results though. The space is really large. The chance that we'll hit the deepest gravity well in the universe by pure luck are pretty slim.
Another approach is to focus our search near some good points. Like picking mushrooms in a forest. You find a mushroom πβπ«? That means there sholud be more nearby. So we can check the neighbourhood of good mushrooms (ekhm... I mean points) more dilligently. We start picking random points throughout the universe and then we progressively start focusing on the nice locations. The fancy term for this approach is simulated annealing. Doing this very slowly, mathematically speaking should let us find the optimal point. Unfortunately mathematicial proofs don't translate well into practical algorithms. This is one of those situations. The "very slowly" in mathematics translates to "forever" in practice.
For practical algorithms we'll have to learn from the force of nature that defines what it even means to be practical - from the life itself.
This algorithm is the staple of optimization techniques - an evolutionary algorithm π§¬. In this one we have to treat different points in the space that we're searching like living creatures. First we scatter a bunch of points throughout the universe randomly. Then we measure them. Then we kill half of them. Thanos style π€. The remaining points have virtual baby points that are a mixture of their parents plus some random mutations. In each iteration of evolution there is some death and there is some life. And some random mutations. Most of the mutations are cancer. But some of them are good. The unlucky cancer babies will die. But the lucky ones will live long enough to exchange genes. This cool trick (scientifically called meiosis, and colloqually - sex) is why we humans continue to exist and can survive a competition against bacterias that have reproductive cycle measured in hours and vastly greater populations. It's crazy to think what would happen if a germ like COVID-19 figured out sexual reproduction...
Anyway, from our layout search perspective evolutionary algorithm is actually quite decent. It would probably help us find a supermassive black hole. But it's not as good as the next one.
The last approach, and the one that is running in your terminal right now is based on ant behavior π. See, ants have a very nice way of exchanging information - they do this by depositing pheromones. They do this once they find food. When another ant finds a trail of pheromone, it starts to follow it. It can randomly lose it, but only rarely. Once that other ant finds more food (presumably the original piece of food that lured the first one to your kitchen) it deposits even more pheromone. Other ants follow it more and more. Initially the ants explore many pheromone paths, but after some time it turns out that some of them are more efficient than the others - and those paths start to snowball. Eventually the ants form a path that leads them straight under your fridge. The cool property is that the pheromone is a much more potent way of exchanging information. It encodes uncertainty. Also all ants in the colony deposit the pheromone together. Turns out that for combinatorial problems, this kind of optimization is even more efficient than evolutionary algorithms.
This is what you can see in your terminal. It's a pheromone matrix. It's a measure of how much pheromone the virtual ants deposited by on a given key/chord assignment. As the ants follow the pheromone trail they start to focus on good layouts. The pheromone matrix will start to look more and more sparse. Eventually all of the ants will follow the same key/chord assignment. That's our perfect layout.
![]() | ![]() |
On my PC it took 21303 generations (it ran overnight). In each generation 240 ants went out to search food (the best layout) and only the victorious ant was allowed to deposit the pheromone. The pheromone evaporates a little, so that eventually the quantity of pheromone starts to stabilize. It's a fairly slow process - but only because our ants are searching through mind boggingly large space. Given the complexity of the problem, I think they're doing quite well!
You will find the resulting layout in best_layout.txt . I do recommend to take a look at it and see how you'd type some of common words that you might enter frequently. I found some of the key combos to be quite interesting (like shifting from . or ; to Enter).
Unfortunately the ants can't optimize multi-key chords - you'll have to place those using your own intuition.
Edit the chord assignments in ChordKeyboard.cpp and pio run --target upload it to the device. Now you're ready to start using it!
This section is for the people who decided to keep designing their layout rather than use it. It's better if you stay away from these ideas.
qwerty_analyzer to assign a chord to the Shift key. By default Shift is just assumed to be on the pinky. This way the optimization can focus on the four main fingers.Within the layout_tutor/ you will find a small utility that will help you memorize the chording sequences. A version that can teach you the default layout can be found at: mafik.github.io/keyer/. I hope that you've already made your own layout though! If you did, then put all the chord sequences in the layout_tutor/layout.js file. You can also swap out the dictionary (an English dictionary is included in layout_tutor/words_dictionary.js by default). The state.js file contains some variables like targetWPM or targetAccuracy which are used by the script to automatically advance you through the exercises - you may tweak them to your liking. When all is fine, just open up index.html in a browser and start typing.
The most important element of proper typing on a keyer is minimizing key presses. The chords are optimized to reuse the finger positions so most often you will only have to press one extra finger. That's the main thing you have to keep in mind when going through the exercises.
Each step in the learning process will teach you how to combine two letters together. Then, when you can type them quickly, it will swap to some dictionary words. Your goal is to maintain 15+ WPM (Words Per Minute) and accuracy above 90%. Once you can do that for 30 subsequent key presses, then the exercise will auto-advance to the next step. And there are a lot of steps! Each pair of letters essentially has to be learned like that. It's a rather enjoyable process in fact. Once you're done - you should be able to write with a decent speed. 15 WPM is maybe well below typical QWERTY speeds but it's nothing to sneeze at! After you finish you can start the next round at 20 WPM, then 25, then 30.
One thing about building a muscle memory is that it requires long training sessions and you actually don't see much improvement during those sessions - but it does show up after some time. Typically the next day.
So that's all from me. I'll let you get some practice now!
# Tweak FreeRTOS configuration
$ pio run --target menuconfig
# Clean build files
$ pio run --target clean
layout_generator/ - a set of Python scripts for generating an optimized chord layout
corpus/ - directory for text files that will be used for evaluating the layoutplanner.py - main entry point for doing the optimizationqwerty_analysis.py - converts the text files into a sequence of equivalent IBM PC keyboard keyskeyer_simulator.cpp - simulates text entry on the keyerbeam_optimizer.py - optional utility to double-check whether the generated layout is (locally) optimalsrc/ - code that runs on the ESP32sdkconfig.ChordKeyboard - configuration for the ESP-IDF firmwarelayout_tutor/ - the home of "Keyer Flight School" - a webapp for learning to type with chordsThe src/ directory contains a prototype code for a wearable SSH Terminal that connects and displays its output through connected smart glasses.
The SSH code is not particularly polished so don't expect it to work out of the box.
117 followers Β· starred Oct 2025
505 followers Β· starred Oct 2025
253 followers Β· starred Oct 2025
868 followers Β· starred Oct 2025