Building a RollerMouse Keyboard

After building an ergonomic hand-wired keyboard over several iterations, my next step was a mousing solution, particularly one that works without taking my hands off the keyboard. While touchpads or trackballs are viable options, I had a different approach in mind.

My rollerbar keyboard (keymouse) "kr04" is an ortholinear, column-staggered, keywell, 15° outward-angled, unibody split keyboard [db] with an integrated rollerbar mouse. As a prototype from multiple 3d-printed parts, it allows easy exchange of per-finger "key strips", e.g. to adjust column stagger. The keyboard portion is an upgrade of my kr02 iterative keyboard.

KeyMouse kr04 with roller mouse and two 6x6 keyboards, ortholinear, column-staggered, key well, 15° angle, unibody split
KeyMouse kr04 with roller mouse and two 6x6 keyboards, ortholinear, column-staggered, key well, 15° angle, unibody split

The "kr04" has no product polishing and no cable management; for that I gain the ability to change key positions quickly by printing a new keystrip for any given finger. Keyswitches sit inside "sleds" to simplify exchange, six per keystrip. As of 2025-11, I use it as my daily driver. Ok, so that was the short story, the longer one has chapters:

  1. Rollerbar mouse inspiration
  2. Palm rest redesign to integrate the roller bar
  3. Steel rods reinforcement to get rid of twisting
  4. Putting the kr04 together
  5. Layout with QMK on twice 6x6

Only read further if you are thinking about building a keyboard yourself, or are generally very curious, because the level of detail below might be a tad overwhelming otherwise.

1 Roller Bar Mouse

Fifteen years ago I tried a roller bar mouse [my guide] [vid] and was hooked, but it did not fit my curved keyboard. The roller mouse principle is an outer pipe sliding left/right and rolling up/down, sitting on a stationary inner bar or rod; effectively you have a cylindrical track ball. Now we are going to integrate that into an ergonomic keyboard.

Original RollerMouse by ContourDesign
Original RollerMouse, courtesy of ContourDesign

Purely due to availability, I chose lengths of 20cm for the moving outer round pipe (Rundrohr) and 30cm for the stationary inner round bar (Rundstab). Then I had to find some appropriate bearings (Kugellager). We can either go for bearings with comparatively massive 6mm inside, 19mm outside diameter [amazon] with fitting 19/22mm outer pipe [amazon] and 6mm inner bar [amazon]; or for bearings with more delicate 3mm inside, 7mm outside diameter [amazon] with fitting 7/9mm outer pipe [amazon] and 3mm inner bar [amazon].

I opted for the latter, and due to the wonders of industrial precision, the bearing outsides fit into the 7/9mm pipe on the first try, and the bearing insides fit onto the 3mm bar after a bit of hard pressing because the cuts at both ends make the bar a bit thicker there. For the optical sensor I took an ancient Logitech M-BJ69 (seriously, I am not making the name up) [Linux] and 3d printed custom mouse board holders around it, containing 3.1mm holes taking the same 3mm rods that also hold the cylinder [FCStd].

Roller mouse kr03, first prototype
Roller mouse kr03, first prototype

As always, tolerances between 50µm and 150µm are a good idea for fitting parts together. It took some tries and the 3mm rods were still hard to press through for some holes, and slightly loose for other holes. Vertical distance from the optical sensor needed to compensate for the missing mouse bottom plate, an additional 1mm or so. At first the pipe was too shiny, and mouse cursor movement was slight and somewhat erratic. Testing crepe tape around the cylinder for a more diffuse reflectance improved cursor movement, and spray paint [amazon] was a smoother solution, but the texture was not as good, so I ended up back at carefully applied crepe.

So the big question now is: How to integrate this mouse with my kr02 keyboard? Just laying it on the side reveals that the cylinder is not really nice to grab with the thumb.

Roller mouse kr03 and keyb kr02, side-by-side
Roller mouse kr03 and keyb kr02, side-by-side

The cylinder should go halfway into the palm rest, and also towards the back where it is currently blocked by the breadboard. Also, an angle of 15° or so would be nice, such that my hands can come a bit from the outside, instead of straight on. So we need to touch the keyboard design.

2 Keyboard or rather Palm Rest Redesign

Since my kr02 keyboard has a one-piece base plate with unsufficient attachment points, we need to redesign that base plate, now using 4 parts for faster prototyping, tacked together with dovetail joint pieces. A ground plate thickness of 1.5mm has sufficient stability. Dovetails of 6mm→4mm from outer to inner, with 4mm thickness, are also stable enough [my binder guide].

I decided to integrate the rollerbar into the palm rest, at an 15° angle to have a slight inward hand orientation. Two "palm rest pieces" instead of one long one allowed for faster prototyping, since now a lot of binder joints and a (sawtooth) bar receptacle were needed. The sawtooth is meant for adjusting the vertical position of the receptacle in 1.5mm steps, the distance between two teeth.

CAD for keyboard kr02 palm rest (right)
CAD for keyboard kr02 palm rest (right), as rollerbar receptacle; the bar comes out at the left.

To connect the mouse plate [FCStd], the palm rests and the rollerbar, I designed binder struts with horizontal and vertical connectors [FCStd] to fix against rotation. Again in two parts, a longer structural one directly at the palm rest, and a shorter one that does the fine-tuning at the mouse holder. This fine-tuning determines the roller bar positioning relative to the optical sensor.

Roller mouse kr03 (v2) between two palm rest pieces
Roller mouse kr03 (v2) between two palm rest pieces

However the assembly was not stable enough, even with both vertical and horizontal binders: Small rotations on large levers added up to big translations resulting in the roller bar being too low, particularly on uneven surfaces like cloth.

So the next step will be to re-introduce a second rod for stabilization, under the assumptation that steel rods are a lot more stable than a filament assembly. This also means touching the palm rest CAD once more.

3 Steel Rod Stabilizers

From experiments with both the rollermouse and my kr02 keyboard, just sticking 2-axis stabilized struts with dovetail binders together is not stable enough. The 300mm steel rods with 3mm diameter have a higher stability, so both the palm rest as well as the rest of the keyboard have to be designed around them.

The palm rests take 41mm on each end of the rod, for a total of 82mm; the mouse plater is another 80mm. This leaves 138mm in total, or 69mm for left/right strut, not accounting yet for the rod ends possibly not fitting completely into the palm rests, so 70mm should be good as "rod tube" strut length. Further up the keyboard on a corner plate, a secondary "rod tube" strut has to be proportionally shorter [FCStd].

CAD for mouse holder (v3), keyboard kr02 right pieces, and rod tube struts
CAD for mouse holder (v3), keyboard kr02 right pieces, and rod tube struts

Remember that in FreeCAD, mirroring bodies is in the "Part" workspace, using the YZ plane. Also note that while the three parts of the mouse holder could lock together both horizontally and vertically via dovetail joints (as before), in practice I only used the horizontal binders, since the rods provide all of the stabilization now.

Printed out, the mouse holder, keyboard palm rest + corner plate, and the rod tube struts in between them, fit well together. The stabilization effect of the 300mm steel rods is excellent; the roller bar alignment over the optical mouse sensor is now stable enough for the first time.

Roller mouse kr03 (v3) between two keyboard pieces
Roller mouse kr03 (v3) between two keyboard pieces

With the roller mouse finished, all we are missing now is extending the corner plates to arrive at a full split keyboard.

4 RollerMouse Keyboard kr04

Building upon my kr02 keyboard and the "kr03" rollermouse above, the rollermouse/keyboard combo "kr04" [FCStd] starts with 6 keystrips for 4 fingers, with the thumb reserved for the rollerbar. The keystrips are plugged into dedicated front/back strip holders, the former of which will attach to the palm rest.

KeyMouse kr04 finger strips and mounts
KeyMouse kr04 finger strips and mounts

As for the kr02, every per-finger keystrip takes 6 key sleds, each one containing, and stabilizing, a key switch.

Keyswitch sled and holder
Keyswitch sled and holder

The keystrip holders are mounted upon notched plates, same as the rollerbar ground plates. And because I cannot solder very well, the breadboard is also permanently embedded, together with a mini USB hub taking both the mouse cable and the Arduino cable.

KeyMouse kr04 plates and full-size palm rest
KeyMouse kr04 plates and full-size palm rest

I upgraded the palm rest to full width because the previous two-piece version had a noticable gap when touching. All parts on the right side are mirrored to the left, and the whole assembly stabilized by three 300mm long 3mm diameter steel rods, which is sorely needed for such a large device; suddenly I understand why most keyb builders on the internet go for split keyboards. Having both keyboard sides 15° angled for better ergonomics further increases the bulkiness.

Keycaps [guide] are again (or still) DSA 1u made from PBT [amazon], and key switches are still Cherry MX Blue [reichelt] because they are inexpensive and have built-in diodes for ghosting avoidance.

Cherry MX blue dimensions
Cherry MX blue dimensions, courtesy of Cherry

For wiring, I again used stranded wire 24 AWG (0.2mm², ø0.51mm) [amazon] which is flexible enough to survive some key sled replacements. Since I tended to melt some plastic with the soldering iron at the first and last keys, I designed two solder helpers (bridges) that take upside-down keyslides, connected to the next bridge by elongated dovetail joints in side notches. Also custom cable holder clip-ons, experimentally designed to hold the cable just firmly enough, with about 2 minutes print time for one.

Printed solder helper taking up upside-down keyslides
Printed solder helpers taking up upside-down keyslides

For the wire ends, I upgraded from the "kr02" keyboard by learning crimping [vid] which is so much better than soldering pieces of solid wire to the stranded ones. Breadboard pin distances are defined as JST-RE aka DuPont (2.54mm) [reddit], with the more common JST-EH (2.5mm) close enough. I bought PEBA crimping pliers [amazon] [pdf] [vid] for AWG 32 to 20, which fits my AWG 24 well. Plus, it came with some dozen DuPont connectors.

Crimping Pliers with breadboard-ready DuPont (JST-RE) pins
Crimping Pliers with breadboard-ready DuPont (JST-RE) pins

For crimping, I first take a look at the bare-metal target pin, which tells me how much stranded wire needs to be exposed. With the target pin in the half-closed plier, I insert the exposed and hand-drilled wire and press until click. For the rather long DuPont pins, I often had to press twice, once at the end and once a bit towards the middle of the pin.

The entire roller mouse and keyb combo is about as wide as as a traditional full-size keyboard, and a bit deeper due to the 15° inward angle making corners stick out more. The wires are rather untidy because they go into the breadboard and I didnt think of cable management earlier.

KeyMouse kr04 with roller mouse and two 6x6 keyboards
KeyMouse kr04 with roller mouse and two 6x6 keyboards

So that is the "kr04" in its full glory: Ortholinear, staggered column, keywell, with integrated mousing so you never need to take your hands away from the input device.

5 Keyboard Layout with QMK

This leaves only the tiny question of the key layout, where you can spent a lot of time. I wanted it to be similar to my "normal" German layout, and I wanted arrow keys with PgUp, PgDn, Home and End because I use them all the time. QMK [docs] allows for extra layers, think "my own Shift", and I tried having the arrow keys there much like tiny notebooks do, however it was not satisfying. So in the end it looks like this:

┌────┬────┬────┬────┬────┬────┐   ┌────┬────┬────┬────┬────┬────┐
|f12 │ f1 │ f2 │ f3 │ f4 │ f5 │   | f6 | f7 │ f8 │ f9 │f10 │f11 │
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│esc │ 1  │ 2  │ 3  │ 4  │ 5  │   │ 6  │ 7  │ 8  │ 9  │ 0  │pos1│
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│tab │ q  │ w  │ e  │ r  │ t  │   │ z  │ u  │ i  │ o  │ p  │pgup│
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│togg│ a  │ s  │ d  │ f  │ g  │   │ h  │ j  │ k  │ l  │ +  │pgdn│
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│shft│ y  │ x  │ c  │ v  │ b  |   │ n  │ m  │ ,  │ .  │uarr│end |
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│ctrl│back│del |alt │lmb │spac│   │entr│rmb |altg│larr|darr│rarr|
└────┴────┴────┴────┴────┴────┘   └────┴────┴────┴────┴────┴────┘

The eight arrow and position keys on the bottom right corner take a lot of place, and I also need at least left and right mouse button, because the ones from the rollerbar are deactivated. That means Space is only on the left side thumb, replaced by Enter on the right side thumb. Also Backspace and Delete are on the bottom row now, and there is no right Shift or Ctrl. The F keys are a bit weird because I wanted F1 directly above 1. In my generally German layout, I also remove ö on the home row and replace it by + which I need more often. So that means a lot of keys are missing, but luckily we can go to a custom second layer via holding togg (leftmost middle):

┌────┬────┬────┬────┬────┬────┐   ┌────┬────┬────┬────┬────┬────┐
│mute│vldn│vlup│brdn│brup│wake|   │pscr│paus│syrq│numl│caps│ins |
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│esc │ °  │ σ  | ε  │ π  │ Σ  │   │ Ä  │ Ö  │ Ü  │ ?  │ `  │ ↑  │
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│tab │ ^  │mul │mup │mur │whup|   │ ä  │ ö  │ ü  │ ß  │ '  │ ↓  │
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│togg│whl │ml  │mmb │mr  │whr |   │rec1│ {  │ }  │ #  │ '  │ply1│
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│shft│ |  │mdl │mdn │mdr │whdn|   │rec2│ <  │ >  │ -  │ _  │ply2│
├────┼────┼────┼────┼────┼────┤   ├────┼────┼────┼────┼────┼────┤
│ctrl│    |    │alt │lmb │spac│   │entr│rmb |altg│ ←  | \  │ →  |
└────┴────┴────┴────┴────┴────┘   └────┴────┴────┴────┴────┴────┘

Because QMK is wonderful, we can have mouse cursor movement on the left hand, as well as a bunch of unicode chars, and media keys for volume and brightness. And on the right side, both upper- and lowercase letters, together with macro recording and replay, and more unicode chars. Now all of that is defined in the keymap.c file.

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[0] = LAYOUT_ortho_12x6(
  LCS_T(KC_F12) , KC_F1, KC_F2, KC_F3, KC_F4, KC_F5,     KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11,
  KC_ESC        , KC_1 , KC_2 , KC_3 , KC_4 , KC_5,      KC_6 , KC_7 , KC_8 , KC_9  , KC_0  , LSA_T(KC_HOME),
  LWIN_T(KC_TAB), KC_Q , KC_W , KC_E , KC_R , KC_T,      KC_Y , KC_U , KC_I , KC_O  , KC_P  , RCS_T(KC_PGUP),
  MO(1)         , KC_A , KC_S , KC_D , KC_F , KC_G,      KC_H , KC_J , KC_K , KC_L, /*+*/ KC_RBRC, RCTL_T(KC_PGDN),
  KC_LSFT       , KC_Z , KC_X , KC_C , KC_V , KC_B,      KC_N , KC_M , KC_COMMA, KC_DOT, KC_UP, RSFT_T(KC_END),
  KC_LCTL, KC_BSPC, KC_DEL, KC_LALT, MS_BTN1, KC_SPC,    KC_ENT, MS_BTN2, KC_RALT, KC_LEFT, KC_DOWN, KC_RGHT
),
[1] = LAYOUT_ortho_12x6(
  KC_KB_MUTE, KC_KB_VOLUME_DOWN, KC_KB_VOLUME_UP, KC_BRID, KC_BRIU, KC_WAKE  ,     KC_PSCR, KC_PAUSE, KC_SYRQ, KC_NUM_LOCK, KC_CAPS_LOCK, KC_INS,
  KC_ESC    , LSFT(KC_GRAVE)     , UC(0x3c3), UC(0x3b5), UC(0x3c0), UC(0x3a3),     LSFT(KC_QUOTE), LSFT(KC_SCLN), LSFT(KC_LBRC) , LSFT(KC_MINUS), LSFT(KC_EQUAL), UC(0x2191),
  KC_NO     , /*°*/ KC_GRAVE     , MS_UPLE  , MS_UP    , MS_UPRI  , MS_WHLU  ,     /*ä*/ KC_QUOTE, /*ö*/ KC_SCLN, /*ü*/ KC_LBRC , /*ß*/ KC_MINUS, /*`*/ KC_EQUAL, UC(0x2193),
  KC_TRNS   , MS_WHLL            , MS_LEFT  , MS_BTN3  , MS_RGHT  , MS_WHLR  ,     DM_REC1       , /*{}*/ RALT(KC_7), RALT(KC_0), /*#*/ KC_NUHS , LSFT(KC_NUHS) , DM_PLY1,
  _______   , /*|*/ RALT(KC_NUBS), MS_DNLE  , MS_DOWN  , MS_DNRI  , MS_WHLD  ,     DM_REC2       , /*<>*/ KC_NUBS, RSFT(KC_NUBS), /*-*/ KC_SLASH, LSFT(KC_SLASH), DM_PLY2,
  _______   , KC_NO              , KC_NO    , _______  , _______  , _______  ,     _______       , _______      , _______       , UC(0x2190)    , RALT(KC_MINUS), UC(0x2192)
),
};

The first key [docs] already shows how nice QMK is. LCS_T(KC_F12) means that when held, the key acts as left Ctrl+Shift, and when pressed quickly, as key code F12. On the right side, you can see that I lied earlier when I said there would be no right Ctrl or Shift; here they are as RCTL_T and RSFT_T long-press options. Note that I had to move right Ctrl above Shift because the original location has KC_RGHT aka right arrow, which I often want to hold down for key repeat. LSA_T is left Shift+Alt, when I am too lazy to hold two keys at once.

Back on the left side, the windows key requires a long press on Tab. Directly below, the MO(1) switches to layer 1 as long as it is held. About eight lines below, the same spot on layer 1 is KC_TRNS which means "same as base layer", equivalent to _______ and unlike KC_NO which completely deactivates the key.

On the second layer, things get interesting and a bit weird. Since I did not use German KC defines and instead found out the corresponding English keys by installing a temporary US layout, we have things like KC_GRAVE for ° which would be absurd otherwise. Above it we have LSFT(KC_GRAVE), not to be confused with LSFT_T, which presses Shift+° for me. Unicode chars come by UC(0x123), but only under Linux. Mouse movements are MS_ instead of KC_ with wheel via MS_WHLx. On the right hand side, the only thing to add are a bunch of RALT modifiers for those hard-to-reach AltGr keys, and DM_REC for recording macros, replayed by DM_PLY.

For the mouse keys on the left hand, I left something out: Initially there is no diagonal mouse movement like MS_UPLE for up-left, which requires more tricks in keymap.c [guide]:

#include QMK_KEYBOARD_H

enum custom_keycodes {
  MS_UPRI = SAFE_RANGE, // up right
  MS_UPLE, // up left
  MS_DNRI, // down right
  MS_DNLE, // down left
};

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  switch (keycode) {
  case MS_UPRI:
    if (record->event.pressed) { // press
      register_code(MS_UP);
      register_code(MS_RGHT);
    } else { // release
      unregister_code(MS_RGHT);
      unregister_code(MS_UP);
    }
    break;
  case MS_UPLE:
    if (record->event.pressed) { // press
      register_code(MS_UP);
      register_code(MS_LEFT);
    } else { // release
      unregister_code(MS_LEFT);
      unregister_code(MS_UP);
    }
    break;
  case MS_DNRI:
    if (record->event.pressed) { // press
      register_code(MS_DOWN);
      register_code(MS_RGHT);
    } else { // release
      unregister_code(MS_RGHT);
      unregister_code(MS_DOWN);
    }
    break;
  case MS_DNLE:
    if (record->event.pressed) { // press
      register_code(MS_DOWN);
      register_code(MS_LEFT);
    } else { // release
      unregister_code(MS_LEFT);
      unregister_code(MS_DOWN);
    }
    break;
  }
  return true;
}

After a mandatory QMK_KEYBOARD_H include, the first order of the day is declaring an enum with four keycodes for diagonal mouse movement, and then overwriting specifically process_record_user to say what should happen when a user presses said keycode. Mostly, that is combinations of "register" for key down and "unregister" for key release.

After that, we have three config files of boiler-plate; I am sure one day the QMK devs will unify them to one. Starting with the shortest, the presumably soon deprecated rules.mk:

DYNAMIC_MACRO_ENABLE = yes
UNICODE_COMMON = yes
UNICODE_ENABLE = yes

Starting from the bottom, the last two entries enable Linux unicode [docs] [guide] up to character 0x7FFF, which is about everything except emojis; if you want those, there is more to configure. And the first entry allows for macro recording and replay [docs], which is super useful e.g. for cleaning up text files. Less useful is pressing a second "record macro" key when you are already recording, which can get very confusing. So in the second file config.h we forbid nested recordings; and also enable specify that we want Linux unicode.

#define DYNAMIC_MACRO_NO_NESTING
#define UNICODE_SELECTED_MODES UNICODE_MODE_LINUX

And because we love config files, the entire rest goes into the newer keyboard.json, which used to be called info.json [docs] when I wrote the QMK for the "kr02" keyboard.

{
  ...
  "keyboard_name": "kr04",
  "bootloader": "caterina",
  "processor": "atmega32u4",
  "diode_direction": "COL2ROW",
  "layouts": {
    "LAYOUT_ortho_12x6": {
      "layout": [
        {"matrix": [0,  0], "x":  0, "y": 0},
        ...
        {"matrix": [5, 11], "x": 11, "y": 5}
      ]
    }
  }
  "features": {
    "mousekey": true,
    "extrakey": true,
    "nkro": true,
    "bootmagic": false,
    "command": false,
    "console": true
  },
  "matrix_pins": {
    "cols": ["E6", "D7", "C6", "D4", "D0", "D1", "D2", "D3", "B0", "B3", "B1", "B2"],
    "rows": ["F0", "F1", "F4", "F5", "F6", "F7"]
  },
}

I left some boring parts out. Important are caterina for the ATMega32u4 on the Arduino, the diode direction COL2ROW, and the 12x6 keyboard (12 columns, 6 rows) layout where, yes, you really need to specify each key. Feature-wise [docs], I want mousekey [docs] since the rollerbar has none, and extrakey for audio control; nkro stands for n-key rollover [wiki] allowing more than six keys pressed at once, rarely needed but not hurting anyone either. Deactivated options include bootmagic [docs] to allow flashing without a physical Arduino reset button, command [docs] to change layouts without flashing anew, and console [docs] to allow shell access to the keyboard firmware.

The final keyboard.json section deals with the row wires and column wires and where to put them into the breadboard. For this, we consult the Arduino Micro pin-out:

Arduino Micro pin-out
Arduino Micro pin-out, courtesy of Arduino

While the red "D" (digital) and white "A" (analog) names refer to pins on the circuit board, the orange "P" names refer to the pins on the processor. QMK takes the processor pins, but omits the "P", which makes for maximum confusion. So if you read "E6" above in keyboard.json, this translate to processor pin "PE6", which on the breadboard is "D7".

And with that, we are all set! Configure with qmk config user.keyboard=kr04 and qmk config user.keymap=default, then qmk compile and qmk flash, the last of which will prompt you to press the white button in the middle of the Arduino. And immediately after, we can type, and mouse move, and everything is great.

EOF (Dec:2025)