2025-01-20 08:00:00
I’ve been with Veronica for over a decade now and I think I’m starting to know her fairly well. Yet she still manages to surprise me. For instance, a couple of weeks ago she came and asked me about email security:
I worry that my email password is too weak. Can you help me change email address and make it secure?
It was completely unexpected—but I’m all for it.
All heroic journeys needs a plan; here’s mine:
.com
surname was available).
If you ever want (or need) to change email providers it’s very nice to have your own domain.
For instance, Veronica has a hotmail.com
address but she can’t bring that with her if she moves to Fastmail.
Worse, what if she gets locked out of her Outlook account for some reason? It might happen if you forget your password, someone breaks into your account, or even by accident.
In almost all cases, your email is your key to the rest of your digital life. The email address is your username and to reset your password you use your email. If you lose access to your email you lose everything.
When you control your domain, you can point the domain to a new email provider and continue with your life.
One of the first things Veronica told me when I proposed that she’d change providers was that she didn’t want to pay. It’s a common sentiment online that email must be cheap (or even free).
I don’t think that email is the area where cost should be the most significant factor. As I argued for in why you should own your email’s domain, your email is your most important digital asset. If email is so important, why try to be cheap about it? You should spend your money on the important things and shouldn’t spend money on the unimportant things.
Paying for email gives you a couple of nice things:
Human support.
It’s all too easy to get shafted by algorithms where you might get banned because you triggered some edge case (such as resetting your password outside your usual IP address).
Ability to use your own domain.
Having a custom domain is a paid feature at most email providers.
A long-term viable business.
How do you run an email company if you don’t charge for it?
(You sell out your users or you close your business.)
The best thing you can do security wise is to adopt a password manager. Then you don’t have to try to remember dozens of passwords (leading to easy-to-remember and duplicate passwords) and can focus on remembering a single (stronger) password, confident that the password manager will remember all the rest.
“Putting all your passwords in one basket” is a concern of course but I think the pros outweigh the cons.
To take digital security to the next level you should use two-factor authentication (2FA). 2FA is an extra “thing” in addition to your password you need to be able to login. It could be a code sent to your phone over SMS (insecure), to your email (slightly better), a code from a 2FA app on your phone such as Aegis Authenticator (good), or from a hardware token (most secure).
It’s easy to think that I went with a YubiKey because it’s the most secure option; but the biggest reason is that a YubiKey is more convenient than a 2FA app.
With a 2FA app you have to whip out your phone, open the 2FA app, locate the correct site, and then copy the TOTP code into the website (quickly, before the code changes). It’s honestly not that convenient, even for someone like me who’s used this setup for years.
With a YubiKey you plug it into a USB port and press it when it flashes. Or on the phone you can use NFC. NFC is slightly more annoying compared to plugging it in as you need to move/hold it in a specific spot, yet it’s still preferable to having to jump between apps on the phone.
There are hardware keys other than YubiKey of course. I’ve used YubiKey for years and have a good experience. Don’t fix what isn’t broken.
Here’s a few quick notes on how I setup her new accounts:
The first thing we did was setup Bitwarden as the password manager for her. I chose the family plan so I can handle the billing.
To give her access I installed Bitwarden as:
I gave her a YubiKey and registered it with Bitwarden for additional security. As a backup I also registered my own YubiKeys on her account; if she loses her key we still have others she can use.
Although it was a bit confusing for her I think she appreciates not having to remember a dozen different passwords and can simply remember one (stronger) password. We can also share passwords easily via Bitwarden (for news papers, Spotify, etc).
The YubiKey itself is very user friendly and she hasn’t run into any usability issues.
With the core security up and running the next step was to change her email:
Gave her an email address on Fastmail with her own domain (<firstname>@<lastname>.com
).
She has a basic account that I manage (there’s a Duo plan that I couldn’t migrate to at this time).
I secured the account with our YubiKeys and a generated password stored in Bitwarden.
We bolstered the security of her old Hotmail account by generating a new password and registering our YubiKeys.
Forward all email from her old Hotmail address to her new address.
With this done she has a secure email account with an email address that she owns.
As is proper she’s been changing her contact information and changing email address in her other services. It’s a slow process but I can’t be too critical—I still have a few services that use my old Gmail address even though I migrated to my own domain more than a decade ago.
It’s great to worry about weak phishing, weak passwords, and getting hacked. But for most people the much bigger risk is to forget your password or lose your second factor auth, and get locked out that way.
To reduce the risk of losing access to her accounts we have:
Some go further than we’ve done here, others do less, and I think that’s fine. It’s important to not compare yourself with others too much; even small security measures makes a big difference in practice.
Not doing anything at all because you feel overwhelmed is worse than doing something, even something simple as making sure you’re using a strong password for your email account.
2025-01-06 08:00:00
There are two conflicting forces in play in setting up your computer environment:
It’s common to find people get stuck at the extreme ends of the spectrum; some programmers refuse to configure or learn their tools at all, while others get stuck re-configuring their setups constantly without any productivity gains to show for it.
Finding a balance can be tricky. With regards to terminals I’ve been using alacritty for many years. It gets the job done but I don’t know if I’m missing out on anything? I’ve been meaning to look at alternatives like wezterm and kitty but I never got far enough to try them out.
On one hand it’s just a terminal, what difference could it make?
On the other hand, I spend countless of hours every day inside a terminal so even a small improvement should pay off in the long run.
Enter Ghostty, a terminal so hyped up it made me drop any useful things I was working on and see what the fuzz was about. I don’t quite get why people hype up a terminal of all things but here we are.
Ghostty didn’t revolutionize my setup or anything but I admit that Ghostty is quite nice and it has replaced alacritty as my terminal.
One of the big selling points of Ghostty is its native platform integration. It’s supposed to integrate well with your window manager so it looks the same and gives you some extra functionality… But I don’t know why I should care—I just want a big square without decorations of any kind.
You’re supposed to to be able to simply turn off any window decorations:
window-decoration = false
At the moment there’s a bug that requires you set some weird GTK settings to fully remove the borders:
gtk-titlebar = false
gtk-adwaita = false
It’s unfortunate as I haven’t done any GKT configuration on my machine (I use XMonad as my window manager and I don’t have any window decorations anywhere).
cursor-invert-fg-bg = true
In alacritty I’ve had the cursor invert the background and foreground and you can do that in Ghostty too.
I ran into an issue where it interferes with indent-blankline.nvim making the cursor very hard to spot in indents (taking the color of the indent guides, which is by design low contrast with the background).
Annoying but it gave me the shove I needed to try out different plugins to see if the problem persisted. I ended up with (an even nicer) setup using snacks.nvim that doesn’t hide the cursor:
Unreadable ls
output is a staple of the excellent Linux UX.
It might look like this:
Super annoying.
You can of course configure the ls
output colors but that’s just for one program and it won’t automatically follow when you ssh
to another server.
Ghostty’s minimum-contrast
option ensures that the text and background always has enough contrast to be visible:
minimum-contrast = 1.05
Most excellent.
mouse-hide-while-typing = true
A small quality-of-life feature is the ability to hide the cursor when typing. I didn’t know I needed this in my life.
With alacritty I have an annoying problem where I need to use a very different font size on my laptop and my desktop (8
and 12
).
This wasn’t always the case and I think something may have changed in alacritty but I’m not sure.
Ghostty doesn’t have this problem and I can now use the same font settings across my machines (
font-size = 16
).
The issue for adding ligatures to alacritty was closed eight years ago and even though I wanted to try ligatures I couldn’t be bothered to “run a low quality fork”.
Ghostty seems like the opposite of “low quality” and it renders Iosevka’s ligatures very well:
Overall I feel that the font rendering in Ghostty is a little better than in alacritty, although that might be recency bias. I’m still undecided on ligatures but I love that I don’t have to feel limited by the terminal.
I use a custom Iosevka build with these Ghostty settings:
font-family = IosevkaTreeLig Nerd Font
font-style = Medium
font-style-bold = Bold
font-style-italic = Medium Italic
font-style-bold-italic = Bold Italic
font-size = 16
While Ghostty has an absolutely excellent theme selector with a bunch of included themes (ghostty +list-themes
) melange-nvim wasn’t included, so I had to configure the colorscheme myself.
It was fairly straightforward even though the palette = 0=
syntax was a bit surprising:
# The dark variant of melange
background = #292522
foreground = #ECE1D7
palette = 0=#867462
palette = 1=#D47766
palette = 2=#85B695
palette = 3=#EBC06D
palette = 4=#A3A9CE
palette = 5=#CF9BC2
palette = 6=#89B3B6
palette = 7=#ECE1D7
palette = 8=#34302C
palette = 9=#BD8183
palette = 10=#78997A
palette = 11=#E49B5D
palette = 12=#7F91B2
palette = 13=#B380B0
palette = 14=#7B9695
palette = 15=#C1A78E
# I think it's nice to colorize the selection too
selection-background = #403a36
selection-foreground = #c1a78e
In the end Ghostty has improved my setup and I’m happy I took time to try it out. It took a little more time than “just launch it” but it absolutely wasn’t a big deal. The reward was a few pleasant improvements that have improved my life a little.
And perhaps most important of all: I’m now an alpha Nerd that uses a terminal written in Zig.
2025-01-05 08:00:00
It’s time for my 15th yearly review.
I read a lot of fantasy books this year!
My favorite new series were The Kingkiller Chronicle, Gentlemen Bastards series, and The Stormlight Archive.
Customizing Neovim was fun and rewarding.
It’s amazing I got anything productive done this year…
I really enjoy working with Rust in my own hobby projects.
Types are coming to Elixir and I’m loving it.
(I recently migrated some small projects to v1.18 and found a bunch of errors.)
The Gleam programming language shows a lot of promise.
My one gripe is the pain of manually encoding/decoding JSON (even with the various libraries).
Compared to for example Elixir dynamic encoding or Rust’s
it just feels so bad that I’ve avoided Gleam for some projects.
Shame on me?
CSS is alive and better than ever.
I quit my job and started my own company.
At the moment I’m focusing on consulting but maybe something else can grow from it one day?
I wrote 26 blog posts—it was quite a productive blogging year for me.
I built a custom keyboard together with a custom keyboard layout.
Made the eBook for Why Cryptocurrencies? freely available and finally finished the How I wrote ‘Why Cryptocurrencies?’ series.
I wrote a Tree-sitter grammar for Djot.
Finished the blog series about building my first 3D printer.
I’m up to over 2100 printing hours with the machine so it’s safe to say I’ve been using it, not only playing around with.
Rewrote my lighting home automation from Python to Elixir.
I realized that meta-blogging is a great way to get virtual points on Hacker News.
For some reason the idea of writing a fantasy novel got stuck in my head.
I had a stint where I listened to dozens of hours of advice for aspiring writers and started planning a series. The excitement tapered off a bit during the Christmas holidays and I don’t know if this was just a temporary sidetrack or if it’s something I’ll actually end up doing.
Design a one-handed keyboard layout.
Again, this was just something my brain got stuck thinking about and I’m not sure if it’s just a fleeting idea or something I need to do so I can stop thinking about it. (Sometimes just a little planning plus solving the most difficult problems are enough—I don’t have to finish all the crazy/dumb ideas I get for my mind to consider them “done”.)
Complete my second 3D printer.
There’s no point in having a single printer; what if I break it and I can no longer print replacement parts for it?
Develop my home automation system more.
I’ve got a ton of things I’d like to improve (or play around with). For instance, yesterday I received the Home Assistant Voice Preview Edition that I hope works as well as advertised.
2024-12-30 08:00:00
Although I’m a big Elixir fan, the lack of static typing has always been my biggest annoyance (and why I think Gleam is so cool). I think static typing helps catch bugs earlier and in an automated way, leading to less buggy software and saves time in the long run.
To my great joy Elixir is working on a new type system that will hopefully give us the early type checking errors I’ve been craving for. The system has been rolled out in steps since v1.17 and when I migrated to v1.18 I found my first type checking warning that I wanted to highlight.
This is the offending code with the corresponding warning:
time = DateTime.to_time(now)
next_i = Enum.find_index(events, fn -> time < event_time end) || 0
warning: comparison with structs found:
time
given types:
dynamic(%Time{})
where "event_time" was given the type:
# type: dynamic()
# from: lib/haex/sun.ex
{_, event_time}
where "time" was given the type:
# type: dynamic(%Time{})
# from: lib/haex/sun.ex:88:10
time = DateTime.to_time(now)
Comparison operators (>, =, Comparing with a struct won't give meaningful results.
Structs that can be compared typically define a compare/2 function within their modules that
can be used for semantic comparison.
typing violation found at:
│
│ next_i = Enum.find_index(events, fn {_, event_time} -> time │ ~
(The type checker cannot yet resolve event_time
to the
Time
struct, leaving it as dynamic()
in the text above.)
The issue here as that <
isn’t overloaded for the
Time
struct (like it would be in for instance Rust) and will instead perform structural comparison.
You should use
Time.before?
instead of <
(and
DateTime.before
for DateTime
etc).
My hope for the future
I’ve always disliked matching against atoms in Elixir as it’s so easy to make a mistake, for example like this:
case Supervisor.start_child(supervisor, child_spec) do
->
Logger.info()
(There’s a missing r
in
:already_stated
.)
At the moment this doesn’t produce an error but I really hope we’ll reach this point sooner rather than later as I make these kinds of mistakes all the time.
I think I catch most of these with tests but I’m sure some slip through.
I hope this isn’t that far away as the v1.18 type checker manages to catch a simpler case like this:
case num do
1 -> :one
2 -> :two
_ -> :many
end
end
case num_to_descr(num) do
:zero -> IO.puts()
x -> IO.puts()
end
end
warning: the following clause will never match:
:zero
because it attempts to match on the result of:
num_to_descr(num)
which has type:
dynamic(:many or :one or :two)
typing violation found at:
│
41 │ :zero -> IO.puts("zero")
│ ~~~~~~~~~~~~~~~~~~~~~~~~
2024-11-26 08:00:00
This is the keyboard layout I’m using for my custom keyboard that I generated, printed, and hand-wired. It’s a minimalistic keyboard of 35 keys and features an integrated trackball on the right-hand side.
The keyboard layout started out as a direct copy of the T-34 keyboard layout, with some small modifications from the 34-key keyboard T-34 was designed for:
While the layout has diverged since then, the design philosophy from original T-34 post still holds true and I recommend it as it may explain why the layout looks like it does.
I use quite a number of special features for the keys and I’ve tried to color code according to the above legend.
Layers are super important for smaller keyboards and I use them a ton.
Z
and Q
, together with a bunch of other keys, are on combos.
F2
, F12
and FUN
are just extras and aren’t in a comfortable enough position to warrant anything more common.
When I want to write Swedish I activate this layer that replaces ()_
with åäö
, or I use combos from any layer.
I typically use combos to output symbols (following the same layout pattern as the symbols layer).
The symbols layer is mostly used to roll symbol pairs like {}
or #[
.
Some common symbol sequences (like ->
, !=
, or ```
) exists as combos and others as long press.
While I can activate the number layer persistently (using leader sequences) I typically use combos for single digits (like 0
), or NUMWORD for larger numbers (like 1984
).
NUMWORD makes the number layer smart, so it will deactivate when certain keys are pressed.
It’s used to type numbers in text or code and for relative movement in Neovim, where 17J
would move 17 lines down and then turn off the number layer.
Jumping directly to a line in Neovim with 12G
is also made convenient.
If I want to enter the layer without it turning off I can either use leader sequences to activate it persistently or hold the NUMWORD combo (hold both thumbs).
The layer won’t release until both thumb keys are released, so Space
can be tapped with the left thumb without leaving the number layer.
@u
is there to easily activate macros in Neovim. For example 7@u
in the number layer would run the u
macro 7 times and then turn off NUMWORD.
DPI
can be lowered and raised at runtime.
Keys with Gui
are used in xmonad.
Gui
+ W
, Gui
+ E
and Gui
+ R
are used to switch between monitor.
Gui
+ J
/Gui
+ K
to switch windows.
Gui
+ ,
/Gui
+ .
to add/remove windows from the “master” area.
This layer is commonly used in conjunction with the trackball, giving access to mouse keys.
For instance, Shift
+ Left Mouse
can be used to drag, Ctrl
+ A
to select, and Ctrl
+ C
to copy.
Tabbing is for switching browser tabs and the Back
/ Fwd
mouse buttons goes backwards and forwards in history.
Ctrl
+ arrow is used to switch windows in Neovim.
Ctrl
+ D
and Ctrl
+ U
are used in Neovim to scroll half a screen (compared to PgUp
/PgDn
that scrolls an entire screen).
The workspace layer is triggered by first holding Space
, and then holding the right thumb key (WNAV
).
This layer exists for the rare occasions I want to navigate using only the left hand. The keys on the right side is there just because and are rarely, if ever, used.
This is used for all window and workspace management in xmonad. Some common operations are also on the navigation layer.
Auto shift works and can used to send a window to another workspace (Gui
+ Shift
+ 2
).
This is purely to enable window switching using Alt-Tab
and Ctrl-Alt-Tab
, without releasing Alt
.
I generally prefer to use the navigation layer, where the mouse buttons are on the left side, but in some cases I’d like to only use my right hand.
The dead keys add diacritic to any letter. For example, to get é
you can use the dead key ´
then e
, and the operating system will merge them together.
(É
also exists as a combo.)
I typically use long press for shift and combos for other modifiers, this layer is a fallback for when those aren’t enough (the layer is mostly used for Right Alt
).
I’m not a heavy gamer by any means but this layer allows me to play Core Keeper with my kid. By necessity this layer disables auto shift.
Combos is another fantastic tool that I (ab)use a lot. Simply put it allows you to press multiple keys at once and acts as another key—very useful for smaller layouts.
These combos are made by keys next to each other, either horizontally (pressed with two fingers) or vertically (pressed with one finger in the middle of two keys).
Escape
activates the symbols layer, allowing me to output []
easily.
vsplit
splits a window vertically in Neovim and hsplit
splits it horizontally, and Close Window
closes a window in Neovim (<C-w>q
).
Clear
resets all states; sets the base layer, releases modifiers, stops CAPSWORD and NUMWORD, and clears other persistent states.
Ctrl
+ Shift
+ M
is the shortcut to mute/unmute in Teams.
SWE
activates the Swedish layer, and if prefixed with ()_
it will replace that with åäö
and vice versa. So for example if I typed hall(
I would press SWE
to get hallå
, with the Swedish layer activated.
Ctrl W
is used to close tabs in Firefox.
Save Neovim
is a 4-key combo that saves the buffer in Neovim.
These split combos uses the ring and index finger.
T
+ A
once activates CAPSWORD, tapping again makes it persistent (CAPS LOCK
), and a third tap to deactivate CAPS LOCK
.
Space
+ E
activates NUMWORD and tapping them again activates the number layer persistently.
The repeat key works with the above, making them easier to double-tap.
_
and -
.
I have a bunch of 2-key thumb
+ key
combos:
The logic here is that same-side thumb
+ key
= symbol
and opposite-side thumb
+ key
= digit
, following the placements of the numbers, symbols and swedish layers. They’re used if I want to just type a single character, without having to activate a layer first.
I have similar combos for the function keys.
The keycode QMK_BOOT
enters boot mode for the microcontroller connected via USB, making it easy to update the keymap on the keyboard.
These two 5-key combos (one for each half) are almost impossible to trigger accidentally while being easily accessible.
While layers and combos are the two main features I use, QMK has a lot of other nifty features (and you roll your own implementation of them too).
Most keys have a different behaviour when tapped compared to a long press. Most commonly I use this to produce shifted keys (called auto shift).
So tapping the A
key will output a
as normal and if it it A
will appear instead.
There are a bunch of special cases as well (many on top of combos):
Tap | Long press |
---|---|
_ < > / \ #
|
Double, e.g __
|
" ' = ` 0 .
|
Triple, e.g """
|
| & =
|
Double with spaces, e.g ||
|
! |
!= (with spaces) |
? |
{:?} |
# |
{:#?} |
% |
%{} |
( [ {
|
Close and move cursor between |
@ |
@u (paired with qu combo for Neovim macro execution) |
I use the combo l
+ )
as the leader key.
This will wait for a sequence of key presses (in contrast to combos where keys must be pressed at the same time).
I use this with mnemonics for rarely used outputs:
Leader sequence | Action |
---|---|
l + ) , c
|
Caps lock |
l + ) , s
|
Swedish input in Linux (mapped in xmonad) |
l + ) , t , n
|
Toggle Number layer |
l + ) , t , s
|
Toggle Symbols layer |
l + ) , t , f
|
Toggle Function layer |
l + ) , t , c
|
Toggle Caps lock escape swap |
l + ) , Esc
|
Ctrl Shift Escape |
CAPSWORD is a “smart caps lock”. It works like a regular caps lock, except it automatically turns off after certain keys are typed (most commonly space).
It will not turn off on letters, numbers, _
-
Backspace
and the Repeat
keys.
NUMWORD is a “smart layer”. It’s similar to CAPSWORD, except it activates and then turns off the numbers layer instead of caps lock.
It will not turn off on these keys: 0-9
%
/
+
*
-
_
.
,
:
=
x
Backspace
Enter
and the Repeat
keys.
The repeat key simply repeats the previous key. So to type fall
I can type f
a
l
Repeat
, using four different fingers instead of pressing l
twice. It can also repeat things like Ctrl-c
or Delete
, and unlike regular keys that use auto shift the Repeat
key can be held.
The trackball is normally configured to move the mouse as a regular trackball.
There are different modes that alters the behavior of the trackball:
Space
or _
are held (the mouse moves slower when the navigation layer or the mouse layer are active).
MOD
combo is held (the mouse moves faster).
SYM
combo is held.
Read the T-34 series for the design process and motivations of my other keyboard layout (it’s the same layout with minor refinements and additions).
See the post Building my ultimate keyboard for how I designed and built the keyboard I’m using this layout with.
For implementation details and the most up-to-date reference check out the layout’s QMK source code.
Copied the T-34 layout and adapted it for the new keyboard by adding a mouse layer, remove the shortcut layer, and changed the activation of the specials layer.
Moved -
to an angled combo, moving the WIN
key to the top row,
and move %
to the home-row and !
to the bottom row.
Reworked the mouse layer and use a more advanced triggering mechanism to be more explicit about when the layer is turned on and off.
-
back to it’s original position and placed %
on the angled combo.
-
and _
for the languages that use kebab-case
.
Reworked the navigation layer to keep the original positions for PgUp
, PgDn
, and Tab
s.
To allow this I moved the mouse click to index finger and demoted the up
/down
to the top row.
Reworked the navigation layer to be more focused on two hands on the board.
The idea is to use better placed keys on the right-hand side instead of cramming in too much on the left.
Moved PgUp
and Home
to the right side and it’s enough to use the arrow keys on the right. (The secondary navigation layer exists to enable arrow keys on the left side.)
Added mouse button combos on the right-hand side so in a pinch I can operate the mouse with one hand.
Removed mouse button combos from the right-side and place them on a separate layer instead (to keep the shift combo in the regular place).
Add Gui
+ ,
/.
for xmonad master window management to the navigation layer, moving Ctrl
+ A
to the home-row once again.
Added the gaming layer.
2024-11-26 08:00:00
What comes to mind when you see the description “the ultimate keyboard”?
There are many keyboards in this world; here are some that might fit the “ultimate” moniker:
Some even have “ultimate” in their name, although I’ll assert that they’re far from ultimate.
Any man who must say, “I am the King”, is no true king.
I’ll go one step further to say that no keyboard is universally the ultimate because it’s impossible to agree on how to rank different keyboards. For example, while I personally prefer a split keyboard, you might not. Some people have very long fingers and some have very short fingers, making some layouts more preferable. Others may not even have 10 fingers (or both hands), requiring more drastic modifications.
If an ultimate keyboard exists, it differs from person to person. This is my attempt to build my ultimate keyboard.
To me, the ultimate keyboard should have these features:
Should be split to support a more natural typing position.
Really the biggest ergonomical leap in my opinion.
Customized for my own fingers and typing eccentricities.
Column stagger, curvatures and tenting are features I think I want but they need to be tuned, probably by trial-and-error. The position of the thumb keys is another sticking point that the other keyboards I’ve tried have failed to get just right.
Have an integrated trackball or trackpad.
This way I don’t have to move my hand so far and I can free up some valuable desk space. It shouldn’t be operated with my thumb due to my RSI.
Contain the keys I need but no more.
I like smaller keyboards and I’ve been very happy and with my custom keyboard layout that only has 34 keys. Some modifications are fine of course but for the most part I want to be able to use the same layout on both the Ferris and my new keyboard.
Having looked around, I probably want something similar to a Dactyl / Dactyl Manuform (many variants exists). They’re keyboards you generate from parameters (such as number of rows and columns and the amount of curvature). I’ve always wanted to try one and now with a 3D printer, I can.
When looking for a generator I stumbled upon the Cosmos keyboard configurator and I want to gush about it a little because it’s excellent.
It’s excellent because it allows a clueless sod like me to configure a keyboard the way I want to and it has an impressive feature list:
Expert
mode that allows you to customize anything via JavaScript.
.stl
for easy printing or .step
you can import to CAD.
Here’s a small snippet from how the code in Expert
mode might look like:
const =
curvatureOfColumn: ,
curvatureOfRow: ,
spacingOfRows: , // 18x19 Choc spacing
spacingOfColumns: ,
arc: ,
};
/**
* Useful for setting a different curvature
* for the pinky keys.
*/
const =
...curvature,
curvatureOfColumn: ,
};
/**
* The plane used to position the upper keys.
* It's rotated by the tenting and x rotation
* then translated by the z offset.
*/
const = new
// `20` specifies the tenting angle.
.
.
.;
The entire state of the keyboard is also stored in the url, so I can easily share my config by including a link: Cosmos reference of the final keyboard configuration. (Barring any breaking changes in the tool of course…)
Even with a keyboard configurator I needed a way to start. I already have a layout that I really like so I wasn’t starting from nothing. These were the important parts going into the design process:
A 3x5 grid with 1-2 thumb keys (in practice one thumb key is enough).
If you question why I want to build such a small keyboard I’ll redirect you to the discussion in The T-34 keyboard layout post.
Integrated trackball on the right-hand side.
Choc switches.
One of the major decisions with a keyboard is what kind of switch to use. While MX-style switches are the most common I personally really love Choc switches for a couple of reasons:
While a low profile switch is more important for a flat keyboard, not a tented and curved one like I’m building now, the flatter keycaps and the switches being closer together is crucial for pressing two keys with one finger:
The low-actuation force is also more comfortable to me as it helps reduce the strain on my fingers, and makes combos (pressing several switches at once) generally more pleasant.
It’s not enough with just a 3D printer, to build a working keyboard you need a bunch of hardware:
Two microcontrollers.
I got the Liatris microcontroller as it has enough pins to connect a trackball sensor and it supports QMK.
Switches
What kind of Choc switch should I use?
Linear, tactile, or clicky?
Exactly how heavy should they be?
Should they be silent?
I wasn’t sure so I ordered a sampling of different switches to try.
For the final keyboard I used the Ambients silent Noctural (linear / 20gf) switches, where the deciding factor was getting as light switches as possible. (I’ve previously used modded 15gf switches, which were even better, but I couldn’t find a way to buy them.)
Keycaps
Keycaps aren’t only for looking cool. A convex keycap for the thumb button instead of the standard concave one makes it much more comfortable:
I also got keycaps for the index row with these small homing notches to help my fingers more easily find the home row.
A pair of TRRS connectors and a TRRS cable.
A Trackball with a matching sensor.
I decided to pick up this PMW3389 sensor because it was recommended in the keyboard configurator and a red 34mm trackball from Amazon.
Filament for the 3D printed pieces.
I ended up settling on the PolyTerra PLA Army Purple for the case but I used a bunch of different filament during the prototype phase.
Diodes, screws, heatset inserts, and cable to do the wiring.
When you’re trying to design something like a custom keyboard I think you need to go through a bunch of trial-and-error until you find something that fits.
Here’s a short rundown of some of the significant revisions I went through, mostly to illustrate that it’s very much an iterative process.
For my first print I mostly wanted to print it out and test how a keyboard with a standard curvature felt. I also wanted to try to place a trackball somewhere.
I ended up removing a regular thumb key (I’ve used two thumb keys with my keyboard layout) to make it fit and I added a “mouse thumb key” that I plan to use as a left mouse button
when I’m operating the trackball.
It was tricky to place the trackball as I wanted to operate it with my index + middle finger, not my thumb.
Another tweak I made was to reduce the spacing between the keys to be closer to the Choc spacing. Choc spacing seems to be 18.6 x 17.6 mm, but I used 19 x 18 mm spacing—the attraction to round numbers is real.
Most of the keys on the keyboard felt fine but I had one major annoyance: I have a habit of using the ring finger to press the top right key instead of the pinky but with the curvature on the keyboard this just wasn’t possible anymore.
You might wonder, why don’t I just create a new habit and use the pinky as you’re supposed to? The simple answer is that I hate it. To my fingers that feels beyond terrible and I’d rather remove the key and only have two keys in the outermost column. As it happens, pressing the key with my ring finger (on a flat keyboard) feels good so I’d rather adjust the key than remove it.
I also added an extra mouse thumb key and lowered the pinky column a bit.
Pressing p
with my ring finger feels great.
Pressing the thumb normal thumb key feels awful because the mouse thumb keys are in the way when I relax my hand.
Adjustments made:
Although I said I wanted to have a 3x5 grid, the generator included an easy option to include a small bottom row with 2 extra keys (for the ring and middle finger) that I wanted to try out for the left side. They’re… Okay I guess. Not crazy uncomfortable but not quite comfortable enough that I want to have common keys there.
At this point the Beta V3 of configurator is out and in it there’s several improvements, most notably:
Both halves can be configured at the same time.
Can go between the Advanced and Expert tabs! WOW!
I had to manually keep track of the JavaScript changes I made, and update them manually if I wanted to make a change in the UI… But no more!
I had to redo most of the configuration and I think I made some minor changes that I didn’t keep track of, but I made two larger ones:
When I started this project Cosmos only supported a single type of trackball mount: roller bearings. They worked quite poorly for me as the ball was spinning well in one direction but poorly in others.
Luckily new options were added and as I’m writing this there’s 4 different ways you can mount the trackball:
Because I was burned with the bad experience (and I didn’t want to rebuild the keyboard yet again) I made small prototypes of the three different options:
The BTUs had the least friction and it felt really easy to spin the ball but they were also distressingly loud. The static ball bearings had more friction than the BTUs and less than the roller bearings while being completely silent, so I chose to go with the ball bearings.
While they don’t feel nearly as good as the Kensington SlimBlade they’re decent enough. I try not to use the mouse that much and having the trackball so much closer is worth it compared to having a separate trackball unit besides the keyboard.
After having used the keyboard for real I realized that the three keys dedicated to mouse buttons would have to go. There were two major issues with them:
So I had them removed and I rewired the right half for the 3rd time. Sigh.
I think the lesson is that it’s not enough to print a prototype and press switches pretending to type, you have to build and use the keyboard a bunch before you can evaluate some of the design decisions.
While the case is the biggest and most important part of this kind of keyboard, there are a few other parts I had to print to complete the keyboard.
The wrist rests didn’t come with any sort of attachment to the case, so they just always drifted away. I tried to combat this by gluing magnets inside the case and outside the wrist rest, making them stick together just enough to stay together during normal use, while being easily removable.
Despite my efforts, I haven’t been using the printed rests as I reverted to the ”squishy” ones I’ve used before:
The printed felt too uncomfortable and I couldn’t find an angle I liked more than the gel rests. Oh well.
There’s a holder to fasten the microcontroller to the case that I use.
I had to manually make a hole to make the Boot
button accessible, which was easily accomplished when slicing the model.
One problem with the Ferris was that it would sometimes slip on the table. I counteracted this by using an old Netrunner playmat but I wanted another solution.
The keyboard is generated with a bottom plate that’s used to hide and protect the internals. I printed it in TPU, a flexible and rubbery material, that gives enough grip to stay relatively still when I’m typing.
One of the first things you need to do when wiring up a custom keyboard is to plan out a matrix. I guess you could directly wire every switch directly to the controller too, but that’s not feasible if you have a larger amount of keys, so the usual thing is to use a matrix.
What a matrix means is you should wire together all keys in a row and connect that to a pin on the controller, and to the same with the columns.
It might look something like this:
You should also use diodes in the matrix (for either rows or columns, I chose the rows). Pay attention to the diode direction.
The wiring is horrible, I know.
I only lost one microcontroller due to a short… With my wiring prowess I consider that a success!
Controller pin | Connection |
---|---|
1 | Handedness (VCC on the left keyboard and GND on the right) |
2 | TRRS data |
3, 4, 5, 6, 7 | Matrix columns |
20, 22, 26, 27 | Matrix rows |
13 (CS1) | Trackball SS |
14 (SCK1) | Trackball SCK |
15 (TX1) | Trackball MOSI |
16 (RX1) | Trackball MISO |
The QMK cli has the qmk new-keyboard
command that helps you get started.
I couldn’t get the generated template to work for me, so I copied settings from an existing keybord with rp2042
support.
I’ll try to hit on the most important parts of the config, take a look at the source code for all details.
The folder structure for the keyboard looks like this:
cybershard
├── keyboard.json
├── rules.mk
├── halconf.h
├── mcuconf.h
└── keymaps
└── default
├── config.h
├── keymap.c
├── rules.mk
└── ...
(Cybershard is the name I eventually settled on for the keyboard.)
The most important part is keyboard.json
that defines (almost) everything we need for a new keyboard in QMK.
First you need to set the processor
, bootloader
, and usb
values.
The Liatris microcontroller uses the RP2040 MCU, and I just picked some vendor- and product identifiers:
},
}
Then we need to define the matrix (with the pins we soldered) and the layout (how we’ll configure the keymap in keymap.c
):
// We need to use a `GP` prefix for the pins.
},
// First physical row
,
,
,
,
,
// Second row
,
,
,
,
,
// etc...
]
}
}
}
Note that we can pick whatever physical pins we want as we can move around and configure them in software.
The LAYOUT
macro is what we use in keymap.c
to define our keymap.
When defining it we can choose to skip certain keys and reorganize it to be easier to define; for example, there’s no switch at 0,0
in my keyboard so I skip that.
The above LAYOUT
can then be used like this:
SE_J, SE_C, SE_Y, SE_F, SE_P,
SE_R, SE_S, SE_T, SE_H, SE_K,
SE_COMM, SE_V, SE_G, SE_D, SE_B,
SE_A, SE_B,
// Thumb keys
FUN_CLR, MT_SPC,
,
With the above setup we should be able to flash the keyboard by first entering the boot loader and running:
qmk flash -kb cybershard -km default
Now the process of updating the firmware is quite nice and unless I screw up I don’t need to connect another keyboard to do it.
qmk flash
(it will wait until it finds a flashable target).
QK_BOOT
combo (the keyboard becomes unresponsive).
To get the split keyboard feature to work I had to set the SERIAL_DRIVER
option in rules.mk
:
SERIAL_DRIVER = vendor
And add the split
configuration to keyboard.json
and modify the LAYOUT
macro:
// The pin that signals if the current controller is the left (high)
// or right (low) controller.
},
// The TRRS data pin.
// We can override the pins for the right controller.
// Note that GP26 and GP27 are swapped compared to the left side
// due to a mistake I made when soldering.
}
},
// We need to sync the matrix state to allow combos, mods, and
// other stuff to work.
}
}
},
// The rows 0 to 3 specifies rows on the left side and
// 4 to 7 the rows on the right side.
// These 5 keys are the first row on the left side.
,
,
,
,
,
// These 5 keys are the first row on the right side.
,
,
,
,
,
// etc..
]
}
}
}
The LAYOUT
macro is just a function with many arguments but with the right order it can be formatted
to look similar to the physical keyboard.
For example, this is how the base layer of my keyboard could look like:
// Left side // Right side
SE_J, SE_C, SE_Y, SE_F, SE_P, SE_X, SE_W, SE_O, SE_U, SE_DOT,
SE_R, SE_S, SE_T, SE_H, SE_K, SE_M, SE_N, SE_A, SE_I, REPEAT,
SE_COMM, SE_V, SE_G, SE_D, SE_B, SE_SLSH, SE_L, SE_LPRN, SE_RPRN, SE_UNDS,
// The extra two keys on the left side
SE_MINS, SE_PLUS,
// Left thumb keys // Right thumb key
FUN_CLR, MT_SPC, SE_E
,
It took a long time for me to get the trackball working (admittedly, mostly because I soldered the pins wrong). There’s quite a lot of documentation for QMK but curiously enough I didn’t find anything that covered the whole setup. I arrived here by trial and error, trying to piece together parts from other keyboards into a setup that worked for me.
First we need to create the files halconf.h
and mcuconf.h
(they go in the same folder as keyboard.json
) to enable the SPI driver:
And enable the pointing device with the pmw3389
device driver in rules.mk
POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = pmw3389
Now we need to add the sensor pins to config.h
:
// SPI1, matching mcuconf.h
// The pin connections from the pmw3389 sensor
This should be enough to get the sensor going, but because we have a split keyboard we need to set that up too:
// The trackball is on the right
There are some additional tweaks that I had to play with to make the trackball work well:
// The trackball is quite sensitive to how
// large the liftoff distance should be.
// Sets the mouse resolution, up to 16000.
// The directions where messed up, this fixes it.
With that I got the trackball moves the mouse as expected.
As I struggled to get the trackball working I tried to use the debug output. I’ll include it here for completeness sake:
Enable the console in rules.mk
:
CONSOLE_ENABLE = yes
Enable pointing device debugging in config.h
:
Turn on debugging in keymap.c
:
void
debug_enable = true;
debug_mouse = true;
And then run qmk console
from the command line.
No.
This keyboard is certainly the most comfortable keyboard I’ve used but it’s not close to being an “ultimate” keyboard. Here’s a few things that might improve the keyboard:
The trackball still isn’t nearly as comfortable as the Kensington SlimBlade.
Maybe a keyboard with a larger trackball would be better?
The extra keys on the left side are barely useful.
It’s not a big deal, maybe I can find some usage for them, but to me having barely useful keys feels wrong.
There are more extra features I feel an ultimate keyboard should have.
The keyboard I’ve built is nice… But it’s still just a normal keyboard with a trackball. Maybe a vibration sensor, a display, or even some LEDs? A smart knob with software-configurable endstops and detents would really add some weight to the moniker of an ultimate keyboard.
It’s hard to know how good the keyboard is before I’ve put it through extensive use, and to do that I need to settle on a keyboard layout for the keyboard. I’ve already designed a layout for a 34-key keyboard that should be fairly straightforward to adapt but I still need to figure out how to add mouse keys and what to do with the “extra” keys on the left-hand side.
Check out The current Cybershard layout for how the keyboard layout is coming along.