One of the first things people notice when they see my setup (whether at cyber competitions or in pictures) is my keyboard, the SP-111. It’s essentially the opposite of what one normally expects. The numpad is on the left side, it has two (three?) spacebars, a variety of non-standard keys, and it runs on customized QMK Firmware. Not to mention the elephant in the room: it’s split in half.
I won’t lie, it took a long time to finish and it was expensive. I invested a lot into customizing this keyboard, but I also learned a lot. The end result is a silent, reliable, and perfectly-tailored input device that I intend to use for the rest of my life. Many would find it hard to justify spending so much time and money on a keyboard, but a keyboard is more than just the sum of its parts when you work in cybersecurity. This post is an overview of anything someone might want to know about it; it’s just as much for my reference as it is anyone else’s.
Keyboards are a hacker’s primary mechanism for enacting their will in cyberspace. With the stroke of a key, motion is converted into sequences of electrical impulses that encode the user’s intent. A series of complex and seemingly magical events take place in the blink of an eye, and then a character appears on the display. The input was processed by your computer and drawn on a screen faster than you could even begin to consider the engineering that allowed it to occur. Perhaps you’re only logging into your PC, or maybe you’ve just discovered a bombshell zero-day vulnerability and you’re racing to write a patch… or an exploit.
Your keyboard doesn’t know which is the case. It doesn’t care. It’s a tool—akin to a soldier’s weapon. But sometimes a keyboard can be much more effective than a firearm, all things considered. Analysts, engineers, programmers, military operators, and cybercriminals alike all use this essential and often-overlooked piece of technology to control their computers. Regardless of which side of cyberwar you stand on, the world is at your fingertips if you have the know-how. In this sense, keyboards are the primary interface between you and your goals. It’s the main device that your thoughts and commands are filtered through, and it will take care of you if you take care of it.
Background
Although this is what one would call a custom board, I didn’t create the entire thing from scratch. I purchased a kit that included the PCBs, cases, and plates. Everything else (e.g., switches, stabilizers, keycaps, etc.) is the responsibility of the user that assembles the kit. If you’re unfamiliar with the world of custom keyboards, designers will often post interest checks in specialized forums to gauge whether the community is interested in their work. A kit usually becomes available for purchase later as part of a group buy if feedback is positive.
I discovered the SP-111 while I was taking technical drafting and engineering design courses in high school. We started in year 1 with old-fashioned paper and pencil drafting (which I still love), and later moved on to computer software like AutoCAD and SolidWorks in years 2, 3, and 4.
Anyone with AutoCAD experience will tell you how useful the numpad is, so it’s no surprise that I quickly began searching for a keyboard with a left-oriented numpad. That’s (arguably) where it belongs, anyways. I usually use the numpad with a mouse, and it feels much more natural to be able to keep my right hand where it is while I use the numpad with my left hand. This avoids the awkward motion of either removing your right hand from the mouse (assuming you’re right-handed) or moving your left hand all the way to the other side of your keyboard just to insert some numbers.
Although I currently don’t use it this way, a left-oriented numpad can also serve as a convenient built-in macropad if you’re willing to put in some configuration work.
Materials and Build Process
My kit was delivered in November 2024 after nearly two years of waiting—I had placed my order in February 2023 when the designer launched a group buy for a revised SP-111. That kind of delay is typical for custom keyboards. If anything, it was actually quite fast compared to some other group buys that have gotten stuck in development hell. While I was waiting, I worked on acquiring everything else I would need to make a finished keyboard. A list of materials and prices is visible below for anyone looking at doing a similar build.
Because this isn’t a tutorial for building custom keyboards, my assembly steps have been simplified:
- Test PCBs and flash latest default firmware
- Assemble and install stabilizers
- Test stabilizers and tune accordingly
- Install and solder switches
- Assemble keyboard and test functionality
- Create keymap
- Write and flash custom firmware
I originally assembled my SP-111 with an aluminum plate and Boba U4T switches, but later swapped to a carbon fiber plate with case foam and Boba U4 silent tactile switches. This was mainly to reduce noise and avoid being “that guy” at the office (nobody wants to hear your bucking spring IBM all day). Honestly, I much prefer the silent sound now that I’ve made the change.
Unfortunately, I discovered that my spare right PCB has five diodes that are just missing when I went to install the new switches. My original plan was to remove the existing PCBs that had the old switches installed and desolder them later. In the meantime, the new silent switches would get soldered to my spare PCBs and installed in the keyboard. I ended up having to desolder my working right PCB to install the new switches.
I haven’t had time to try fixing the missing diodes yet, but I might end up needing to take my PCB to a repair shop. The diodes are extremely small and I don’t have the proper equipment or technique to handle microsoldering jobs right now.
| Item | Cost (USD) |
|---|---|
| SP-111 kit (includes carbon fiber plate and spare PCBs) | $606.54 |
| ePBT 6085 keycap set (includes base, numpad, spacebars, novelties, and 40s) | $164.49 |
| Gazzew Boba U4T switches x200 (68g) | $132.50 |
| Gazzew Boba U4 silent tactile switches x120 (v2, 68g) | $88.30 |
| TX AP clip-in stabilizers (1.6mm) | $71.06 |
| Case and plate foam | $34.50 |
| Split wrist rest | $28.83 |
| Replacement diodes | $12.12 |
| Grand Total | $1,138.34 |
I know what you’re probably thinking. "$1,100 for a keyboard?! That’s insane." You might be right (giving it a typing test might change your mind), but if you think this is bad you should take a look at keycult. Around those parts it isn’t uncommon to enter a lottery just for a chance at buying a $700+ kit, and keep in mind, you’re still responsible for purchasing the remaining components like switches and kepcaps. High-end custom keyboards are a rich man’s hobby, so it’s one and done for me.
Layouts
It’s helpful to understand what valid SP-111 layouts can look like before discussing keymaps. Refer to the diagram below:
The SP-111 has a highly versatile layout. This is one of the main reasons that I selected the kit over other contenders. The potential for customization is almost limitless when you add QMK to the mix, assuming you have keycaps that match your underlying layout and keymap. You could always get around this by using blank keycaps, but I prefer mine to have legends.
Speaking of keycaps, I selected the ePBT 6085 set which happened to have excellent compatibility for what I planned to do. This set was based off of the Docutech keyboard that was intended for use with the gorgeously-retro Xerox computer system pictured below. For the unfamiliar, the computer desktop as we know it today essentially originated at Xerox. Expect a post about this at some (undetermined) point in the future!
My Keymap
My keymap contains three layers:
- Command layer (0)
- Normal layer (1)
- Function layer (2)
The command layer is the default layer. Its purpose is to allow one to execute commands with Super + <key> input combinations that my devices are configured to watch for. A held spacebar is treated as a Windows/Super modifier in this layer, but a space is still inserted like normal when the key is tapped to allow the user to type.
A byproduct of using this setup is that it becomes easy to make mistakes at high WPM. This happens if the user begins their next keystroke before lifting their finger completely from the spacebar after they attempted to type a Space character. The firmware recognizes this as holding the spacebar and translates it into a Windows/Super modifier when the user actually only meant to insert a Space. The result is that commands can sometimes fire mistakenly during typing.
An easy solution is to create another layer where the spacebar acts only as a normal spacebar—this is why the normal layer exists. The intention is for users to switch to normal mode in situations where significant amounts of typing are required, otherwise, the command layer is perfectly serviceable for short bursts as long as you’re mindful about avoiding accidental commands.
The last layer is the function layer. This layer can only be accessed by holding down the momentary activation key or the caps lock key (which is remapped to Escape). The layer effectively functions as a modifier when held; many keys are automatically prepended with Shift or Ctrl, and h / j / k / l turns into Left / Down / Up / Right, among other changes.
Custom Firmware
The keymap works great, but there’s one major problem: there’s no visual way to tell which layer is currently active. Without some kind of indicator, the user just has to remember which layer they activated last—very inconvenient. Fixing this requires reprogramming the device with custom firmware as VIA doesn’t give users granular control over LEDs from the web interface.
My modified firmware changes the color of the keyboard’s first LED to indicate the active layer: red for command, and green for normal. Here’s the expected behavior:
- LED 1 is red while in the command layer
- LED 1 is green while in the normal layer
- Use
Lockkey (right shift) to toggle layers - LED state updates immediately
- Momentary activation of the function layer does not change LED state
- LEDs 2 and 3 indicate caps lock and num lock state
I made sure that the colors matched my shell’s mode string as a bonus.
I’ve found that the easiest way to modify the SP-111’s firmware is to use the default keymap as a starting point. The first thing I needed to do was prepare a QMK environment. I ran the install script (but not before examining it, of course):
curl -fsSL https://install.qmk.fm | sh
After that it’s just a matter of running qmk setup and following the prompts to finish getting ready. I compiled firmware using the default SP-111 keymap to confirm that everything works:
qmk compile -kb viktus/sp111_v2 -km default
This generated a viktus_sp111_v2_default.hex file in my qmk_firmware folder. I transferred the hex file to my Windows laptop and flashed my spare PCBs with QMK Toolbox. This also worked as expected, so I continued working on my firmware modifications.
The SP-111 has VIA support, so I used the configurator to make my desired keymap and exported it as a json file. Next, I made a copy of the default keymap and began inserting keycodes over from the json into my keymaps/custom/keymap.c file. This is what the final keymap looks like:
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
/*
* ┌──┐┌──┐┌──┐┌──┐ ┌──┐ ┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐ ┌──┐┌──┐ ┌──┐┌──┐┌──┐┌──┐ ┌──┐ ┌──┐┌──┐
* │0A││0B││0C││0D│ │0E│ │0F││0G││0H││0I│ │0J││0K│ │6A││6B│ │6D││6E││6F││6G│ │6H│ │6I││6J│
* └──┘└──┘└──┘└──┘ └──┘ └──┘└──┘└──┘└──┘ └──┘└──┘ └──┘└──┘ └──┘└──┘└──┘└──┘ └──┘ └──┘└──┘
*
* ┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐
* │1A││1B││1C││1D│ │1E││1F││1G││1H││1I││1J││1K│ │7A││7B││7C││7D││7E││7F││7G││7H│ │7I││7J│
* └──┘└──┘└──┘└──┘ └──┘└──┘└──┘└──┘└──┘└──┘└──┘ └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘ └──┘└──┘
* ┌──┐┌──┐┌──┐┌──┐ ┌────┐┌──┐┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌────┐ ┌──┐┌──┐
* │2A││2B││2C││2D│ │2E ││2F││2G││2H││2I││2J│ │8A││8B││8C││8D││8E││8F││8G││8H │ │8I││8J│
* └──┘└──┘└──┘└──┘ └────┘└──┘└──┘└──┘└──┘└──┘ └──┘└──┘└──┘└──┘└──┘└──┘└──┘└────┘ └──┘└──┘
* ┌──┐┌──┐┌──┐┌──┐ ┌─────┐┌──┐┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌───┐ ┌──┐┌──┐
* │3A││3B││3C││3D│ │3E ││3F││3G││3H││3I││3J│ │9A││9B││9C││9D││9E││9F││9G││9H │ │9I││9J│
* └──┘└──┘└──┘└──┘ └─────┘└──┘└──┘└──┘└──┘└──┘ └──┘└──┘└──┘└──┘└──┘└──┘└──┘└───┘ └──┘└──┘
* ┌──┐┌──┐┌──┐┌──┐ ┌───┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐ ┌──┐┌──┐┌──┐┌──┐┌──┐┌─────┐┌──┐
* │4A││4B││4C││4D│ │4E ││4F││4G││4H││4I││4J││4K│ │AB││AC││AD││AE││AF││AG ││AH│ ┌──┐
* └──┘└──┘└──┘└──┘ └───┘└──┘└──┘└──┘└──┘└──┘└──┘ └──┘└──┘└──┘└──┘└──┘└─────┘└──┘ │AI│
* ┌──┐┌──┐┌──┐┌──┐ ┌───┐┌───┐┌───┐┌──┐┌───────┐ ┌───────┐┌──┐┌───┐┌───┐┌───┐ └──┘
* │5A││5B││5C││5D│ │5E ││5F ││5H ││5I││5J │ │BC ││BD││BE ││BF ││BG │ ┌──┐┌──┐┌──┐
* └──┘└──┘└──┘└──┘ └───┘└───┘└───┘└──┘└───────┘ └───────┘└──┘└───┘└───┘└───┘ │BH││BI││BJ│
* └──┘└──┘└──┘
*/
[_BASE] = LAYOUT(
KC_PSCR, KC_SCRL, KC_DEL, KC_PAUS, KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_F13, KC_STOP, MACRO_1,
KC_NUM, KC_PSLS, KC_PAST, KC_BSPC, KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_BSPC, KC_INS, KC_HOME,
KC_P7, KC_P8, KC_P9, KC_PMNS, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_DEL, KC_END,
KC_P4, KC_P5, KC_P6, KC_PPLS, LT(_FN, KC_ESC), KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_NUHS, KC_ENT, KC_PGDN, KC_PGUP,
KC_P1, KC_P2, KC_P3, KC_PENT, KC_LSFT, KC_NUBS, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, TG(_L1), MO(_FN), KC_UP,
KC_P0, MACRO_0, KC_PDOT, KC_PENT, KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_BSPC, MT(MOD_LGUI | MOD_RGUI, KC_SPC), KC_APP, KC_RALT, KC_NO, KC_0, KC_LEFT, KC_DOWN, KC_RIGHT),
[_L1] = LAYOUT(
KC_PSCR, KC_SCRL, KC_DEL, KC_PAUS, KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_F13, KC_STOP, MACRO_1,
KC_NUM, KC_PSLS, KC_PAST, KC_BSPC, KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_TRNS, KC_BSPC, KC_INS, KC_HOME,
KC_P7, KC_P8, KC_P9, KC_PMNS, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_DEL, KC_END,
KC_P4, KC_P5, KC_P6, KC_PPLS, LT(_FN, KC_ESC), KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_TRNS, KC_ENT, KC_PGDN, KC_PGUP,
KC_P1, KC_P2, KC_P3, KC_TRNS, KC_LSFT, KC_TRNS, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_NO, MO(_FN), KC_UP,
KC_P0, MACRO_0, KC_PDOT, KC_PENT, KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_BSPC, KC_SPC, KC_TRNS, KC_RALT, KC_NO, KC_1, KC_LEFT, KC_DOWN, KC_RGHT),
[_FN] = LAYOUT(
_______, _______, _______, _______, KC_CAPS, KC_F13, KC_F14, KC_F15, KC_F16, KC_F17, KC_F18, KC_F19, KC_F20, KC_F21, KC_F22, KC_F23, KC_F24, _______, _______, QK_BOOT,
_______, _______, _______, _______, S(KC_GRV), S(KC_1), S(KC_2), S(KC_3), S(KC_4), S(KC_5), S(KC_6), S(KC_7), S(KC_8), S(KC_9), S(KC_0), S(KC_MINS), S(KC_EQL), _______, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, C(KC_Y), KC_HOME, KC_END, _______, _______, S(KC_LBRC), S(KC_RBRC), S(KC_BSLS), _______, _______,
_______, _______, _______, _______, _______, C(KC_A), _______, _______, C(KC_F), _______, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, S(KC_SCLN), S(KC_QUOT), _______, _______, MS_BTN1, MS_BTN2,
_______, _______, _______, _______, _______, _______, C(KC_Z), C(KC_X), C(KC_C), C(KC_V), _______, KC_PGDN, KC_PGUP, S(KC_COMM), S(KC_DOT), S(KC_SLSH), TO(_BASE), _______, MS_UP,
KC_MPRV, KC_MNXT, KC_MPLY, _______, _______, _______, _______, _______, KC_DEL, _______, _______, _______, _______, KC_2, MS_LEFT, MS_DOWN, MS_RGHT)
};
The main modification needs to be inserted after the keycodes. This code changes the behavior of the onboard LEDs by using LED 1 as a layer indicator. LEDs 2 and 3 are used to indicate the caps lock and num lock states.
#ifdef RGB_MATRIX_ENABLE
bool rgb_matrix_indicators_user(void) {
led_t led_state = host_keyboard_led_state();
uint8_t LED1 = 0; // power/layer indicator
uint8_t LED2 = 1; // capslock
uint8_t LED3 = 2; // numlock
uint8_t base_layer = get_highest_layer(layer_state & ~(1UL << _FN));
if (base_layer == _L1) {
rgb_matrix_set_color(LED1, 0, 255, 0);
} else {
rgb_matrix_set_color(LED1, 255, 0, 0);
}
if (led_state.caps_lock) {
rgb_matrix_set_color(LED2, 255, 255, 255);
} else {
rgb_matrix_set_color(LED2, 0, 0, 0);
}
if (led_state.num_lock) {
rgb_matrix_set_color(LED3, 255, 255, 255);
} else {
rgb_matrix_set_color(LED3, 0, 0, 0);
}
return true;
}
#endif
With the keymap file done, all that’s left is compiling and flashing the custom firmware.
qmk compile -kb viktus/sp111_v2 -km custom
I flashed the firmware according to the following (paraphrased) instructions that were included with the board:
In order to program the board with new firmware, you will need to be able to program both halves separately. Always make sure the same firmware version is programmed on both halves or there may be unexpected errors/issues. When programming the PCBs do not have the interconnect cable plugged in, and only plug into the PCBs via the USB port labeled “COMPUTER”.
The left half of the SP-111 can be programmed while fully assembled via the USB port that you use to connect to your computer already. If disassembled, use the USB port labeled “COMPUTER” on the PCB for programming only. The right half of the SP-111 must be taken apart in order to access a secondary, internal USB connector, labeled as “COMPUTER” on the PCB for programming.
- Make sure you have the latest release (not a beta) of QMK Toolbox downloaded
- Launch QMK Toolbox and click the
Openbutton located near the top right - Navigate to your firmware hex file
<FILENAME>.hexand open it - Ensure the dropdown next to the open button has the
ATmega32U4option selected - Enable the
Auto-Flashfeature - Plug in either the left or right PCB via the USB port labeled
COMPUTER - Push the small button labeled
RESETon the bottom of the PCB near the MCU/controller once and let go - You should now see text scrolling that shows the firmware flashing progress
Links
- geekhack Interest Check
- geekhack Group Buy
- SP-111 v2 QMK Firmware
- Hakko FX-888D Soldering Station
- Hakko FR-301 Desoldering Tool
- Manual Desoldering Pump
- Replacement PCB Diodes
- Boba U4T Tactile Switches
- Boba U4 Silent Tactile Switches
- TX AP Clip-In Stabilizers
- ePBT 6085 Keycaps
- Case/Plate Foam
- Stabilizer Tuning Tutorial