Keychron K8 Pro custom firmware on Linux

Posted on 2022-10-14 in misc • 6 min read

One of the main reasons I wanted the Keycron K8 pro instead of any of the other Keychron options was the QMK firmware. Updating macros, and normal keys using VIA is handy, but doesn't let you do the more complicated things like MOD-TAP which I wanted for my CAPSLOCK key. So obviously you can customize all that with QMK, but it's not exactly obvious how to do that with the K8 pro, and in Linux, because everything I found specific to Keychron was using QMK Toolbox which isn't released for Linux (and it's all C# and .NET, so there is no way I am going to try to take the source and compile it).


I am specifically using the Keychron K8 Pro, RGB backlit version. If you happen upon this page and are using a different keyboard, you will have to modify accordingly. I have also only just figureed this out in the last 24 hours before writing this. So your milage may vary, and doing it wrong could blow everything up, so you are on your own.

Also, after doing this to get the mod-tap functionality, I discovered that in VIA, you can do the more complicated things by specifying an any key, and adding the specific QMK code. So while my original reason for doing this wasn't needed, it's still a simple exercise in figuring out the mechanics of getting a custom-compiled firmware built and loaded on the keyboard.

Official firmware

It is always possible that a custom firmware doesn't work at all. You should always be able to get back to the factory default by re-flashing with the official firmware from Keychron, so we'll just put that link here to be safe.

But obviously we want to do things that aren't in the default, so let's continue, shall we?


There are a few things we need to do in preparation. Which is basically clone the Keychron fork of QMK, and install the QMK CLI.

Keychron Repository

Clone the repo in a directory of your choosing, and change directories to the newly-cloned repo

git clone && cd qmk_firmware

In the next section, QMK will call out not having the official firmware as an upstream, so let's satisfy that request:

git remote add upstream

Likewise, qmk setup will tell you the submodules aren't initialized, so let's go ahead and do that too while we are here

make git-submodules

We are currently in the "master" branch of the repo, but the K8 pro stuff won't work in this branch (yet?). There is a branch called 'k8_pro', but that one doesn't get it all either. The 'bluetooth_playground' is the one we need. So let's switch to that branch

git switch bluetooth_playground


Make sure you switched to the bluetooth_playground branch. As of the time of this writing, the keyboard doesn't exist in master, and there are errors if you try to copile in the more-obvious-sounding k8_pro branch.

Ok, with that out of the way, we should be set for the QMK CLI setup


Most of the information on how to get this set up is available on QMK's setup guide. Basically we need to install the QMK CLI with pip

python3 -m pip install --user qmk

And then running:

qmk setup

If you followed along above, you probably don't have anything to fix; But if something does come up, you'll probably want to answer 'y' to anything that comes up and let it fix whatever it finds. At this point you are ready to actually compile a firmware.

Optional setup

Since you probably only have one keyboard, we can tell qmk to just always use that keyboard with qmk config user.keyboard=keychron/k8_pro/ansi/rgb if you don't specify that, you will need to pass -kb keychron/k8_pro/ansi/rgb to every qmk command.

Testing a build

There are two keymaps defined, one called default and one called via. The only difference between them is a in the via one that enables via support. So if you use default, via won't work. So with that, lets try to build the via keymap.

qmk compile -km via

If all goes well, you should see something like this

sample output of compile


Now we can create a custom keymap. The qmk cli has a qmk new-keymap, which will prompt you for a new keymap name, but you can also specify with -km mynewmap. That will essentially just create a new keymap based on default. Since we wanted to maintain via compatibility, we could just copy the from via into our new keymap directory, but since it is just copying anyway, I created a new keymap with:

cp -R keyboards/keychron/k8_pro/ansi/rgb/keymaps/via keyboards/keychron/k8_pro/ansi/rgb/keymaps/mynewmap

At this point you could try to compile your new one (which should be the same as the via one that we tested above) with

qmk compile -km mynewmap

But, we want to actually change something, so let's edit keyboards/keychron/k8_pro/ansi/rgb/keymaps/mynewmap/keymap.c and change "KC_CAPS" in the "WIN_BASE" layer to "LCTL_T(KC_ESC)", which is what gets escape on tap, control on hold for the caps lock key. So the full WIN_BASE layer looks like this now

[WIN_BASE] = LAYOUT_ansi_87(
     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_PSCR,   KC_CTANA, RGB_MOD,
     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_INS,    KC_HOME,  KC_PGUP,
     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_PGDN,
     LCTL_T(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_ENT,
     KC_LSFT,            KC_Z,     KC_X,     KC_C,     KC_V,     KC_B,     KC_N,     KC_M,     KC_COMM,  KC_DOT,   KC_SLSH,            KC_RSFT,             KC_UP,
     KC_LCTL,  KC_LGUI,  KC_LALT,                                KC_SPC,                                 KC_RALT,  KC_RGUI, MO(WIN_FN),KC_RCTL,  KC_LEFT,  KC_DOWN,  KC_RGHT),

Now with that change, we are going to compile for real this time:

qmk compile -km mynewmap

Assuming everything is good, we can move on to flashing


The previous step should have created a keychron_k8_pro_ansi_rgb_mynewmap.bin file. We are going to flash the keyboard with that file. We are again going to use the qmk CLI to do that. Run:

qmk flash keychron_k8_pro_ansi_rgb_mynewmap.bin

You should see:

Flashing binary firmware...
Please reset your keyboard into bootloader mode now!
Press Ctrl-C to exit.

Ok, so let's do what it tells us. If you follow the instructions on Keychron's site, in order to do this you have to do the following:

  1. move the keyboard switch to the 'cable'
  2. Pop off the spacebar keycap
  3. press and hold the reset button
  4. plug in the USB cable
  5. release the reset button

This works, and I think the point is that this works no matter what. I don't particularly want to pop off my keycap every time I want to flash a firmware, and it turns out there is a simpler way. The USB cable can already be plugged in for this method (probably could be for the previous one too, but their documentation specifically states to have it unplugged)

  1. move the keyboard switch to off
  2. plug in the USB cable (if not already connected)
  3. hold the escape key (upper left-hand button)
  4. move the keyboard switch to cable
  5. release the escape key.

The process is basically the same, with the major difference being you don't have to pop off any keycaps to do it.

If all goes well, you should see the qmk flash command begin to wipe the keyboard memory and apply the new firmware. Then we should be ready to go. Except, if you hit your new capslock-thats-supposed-to-be-an-escape, you'll still see caps lock behavior. WTF?

Well it turns out this is a via-compatibility thing. This is specifically covered in the FAQ. The solution is to basically do the thing we did above to put it in bootloader mode:

Hold the Bootmagic Lite key (usually top left/Escape) while plugging the board in, which will also place the board into bootloader mode; then unplug and replug the board.

Since the K8 pro has a physical switch, you can do the same thing with switching it from off->cable instead of the plugging/unplugging.