{"id":5659,"date":"2025-03-10T14:43:06","date_gmt":"2025-03-10T13:43:06","guid":{"rendered":"https:\/\/pmortensen.eu\/world2\/?p=5659"},"modified":"2026-06-22T14:40:49","modified_gmt":"2026-06-22T13:40:49","slug":"customising-the-zmk-based-keychron-b6-pro-keyboard-without-the-via-clone","status":"publish","type":"post","link":"https:\/\/pmortensen.eu\/world2\/2025\/03\/10\/customising-the-zmk-based-keychron-b6-pro-keyboard-without-the-via-clone\/","title":{"rendered":"Customising the ZMK-based Keychron B6 Pro keyboard without the Via clone"},"content":{"rendered":"<p>This was written as it happened (though not necessarily in the same order) when I was new to <a href=\"https:\/\/github.com\/zmkfirmware\/zmk\">ZMK<\/a>, on day 1 of getting the <a href=\"https:\/\/www.keychron.com\/products\/keychron-b6-pro-ultra-slim-wireless-keyboard\">Keychron B6 Pro<\/a>.<\/p>\n<p>Some pros:<\/p>\n<ul>\n<li>Independence of a web service.<\/li>\n<li>More permanent configuration of the keyboard (not requiring reloading from a saved configuration after every reset to factory defaults (the reloading also requires the web service))<\/li>\n<\/ul>\n<h2 id=\"source_code\">Source code locations<\/h2>\n<ol>\n<li> <a href=\"https:\/\/github.com\/Keychron\/zmk\/tree\/keychron_bpro\/app\/boards\/shields\/keychron\"><em>\/app\/boards\/<strong>shields<\/strong>\/keychron<\/em><\/a>. The specific keyboards are here, for example, the ISO variant of the B6 Pro: <a href=\"https:\/\/github.com\/Keychron\/zmk\/tree\/keychron_bpro\/app\/boards\/shields\/keychron\/b6\/uk\"><em>app\/boards\/shields\/keychron\/b6\/uk<\/em><\/a>. There is also the common file <a href=\"https:\/\/github.com\/Keychron\/zmk\/tree\/keychron_bpro\/app\/boards\/shields\/keychron\/keychron.zmk.yml\"><em>keychron.zmk.yml<\/em><\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Keychron\/zmk\/tree\/keychron_bpro\/app\/boards\/arm\/keychron\"><em>\/app\/boards\/<strong>arm<\/strong>\/keychron<\/em><\/a>. It seems to be common code, but it could also be an early experiment, for the B6 Pro &#8216;ANSI&#8217; variant. It also has what seems to be a stray file, <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/arm\/keychron\/keychron_z1-287f3d87.keymap\"><em>keychron_z1-287f3d87.keymap<\/em><\/a> (for a Keychron&nbsp;Z1?). File <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/arm\/keychron\/keychron_defconfig#L104\"><em>keychron_defconfig<\/em><\/a> probably has the two-hour sleep period: &#8220;CONFIG_ZMK_IDLE_SLEEP_TIMEOUT=7200000&#8221; (interpreted as 7200000&nbsp;ms = 7200&nbsp;secs = 120&nbsp;minutes = 2&nbsp;hours) <\/li>\n<li><a href=\"https:\/\/github.com\/Keychron\/zmk\/tree\/keychron_bpro\/app\/src\"><em>\/app\/src<\/em><\/a>. Despite the unspecific folder name, the meat of Keychron&#8217;s (custom) implementation. For example, file <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/src\/behaviors\/behavior_macro.c#L219\"><em>behavior_macro.c<\/em><\/a> seems to be related to support of dynamic macros (set in a GUI keyboard configuration tool, <em>seemingly<\/em> hardcoded tied to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Microsoft_Edge\">Edge<\/a> on Windows and <a href=\"https:\/\/en.wikipedia.org\/wiki\/Safari_%28web_browser%29\">Safari<\/a> on <a href=\"https:\/\/en.wikipedia.org\/wiki\/Mac_(computer)\">Mac<\/a>)<\/li>\n<\/ol>\n<p><!--\n  File 'version.h': USB-side version\n  <https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/src\/version.h#L13>\n\n  The actual matches (only because it is custom built???):\n\n    \"..., idVendor=3434, idProduct=0762, bcdDevice= 1.33\"\n\n  =========================================================\n\n  File 'app\/boards\/arm\/keychron\/README.md'\n  <https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/arm\/keychron\/README.md>\n    \"Keychron NRF52840 Experimental Support\"\n\n  Does it have per-key RGB???\n\n      2 x I2C CKLED2001 LED drivers\n\n      That is ***full*** per-key LEDs...\n--><\/p>\n<h2 id=\"ZMK_installation\">Installation of ZMK<\/h2>\n<p>I initially considered using the standard Docker image for ZMK, but the instructions for installation of Docker itself seemed way too confusing; it wasn&#8217;t at all clear how to actually perform the installation (if they could just show a <strong><em>concrete linear<\/em><\/strong> example for some popular platform, say, Ubuntu). It is an example of overgeneralised installation instructions (way too abstract).<\/p>\n<p>Instead, I decided to try to follow the official ZMK instructions for a native installation without Docker.<\/p>\n<p>Initially, the installation failed on <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ubuntu_version_history#Ubuntu_20.04_LTS_(Focal_Fossa)\">Ubuntu&nbsp;20.04<\/a> (yes, I know), as expected. Instead, an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linux_Mint#LMDE\">LMDE<\/a> 6 system was used.<\/p>\n<p>Below are the magic command lines (it is a linear thing (unlike the official instructions which makes &#8220;subroutine calls&#8221; to the Zephyr instructions (confusing)), and it should be possible to use them blindly, one by one, to get a ZMK environment for Keychron up and running).<\/p>\n<p>Note: First, the <strong><em>main ZMK project<\/em><\/strong> was set up and the keyboard firmware for a sample keyboard was compiled (to make sure compilation actually worked). Only after that checked out, the extra complexity due to the Keychron fork was dealt with, cloned to a new fresh folder. It is probably possible to find a procedure with fewer steps that doesn&#8217;t result in the extra consumed bandwidth and disk space.<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\n# &lt;https:\/\/zmk.dev\/docs\/development\/local-toolchain\/setup\/native&gt;\r\ngit clone https:\/\/github.com\/zmkfirmware\/zmk.git\r\ncd zmk\r\n\r\n\r\n# &lt;https:\/\/docs.zephyrproject.org\/3.5.0\/develop\/getting_started\/index.html#select-and-update-os&gt;\r\nsudo apt update\r\nsudo apt upgrade\r\n\r\n# Install the required dependencies:\r\nsudo apt install --no-install-recommends git cmake ninja-build gperf \\\r\n  ccache dfu-util device-tree-compiler wget \\\r\n  python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \\\r\n  make gcc gcc-multilib g++-multilib libsdl2-dev libmagic1\r\n\r\n# Install a virtual environment (in this case 'venv'),\r\n# 'pip', and Git.\r\n# 'venv': &lt;https:\/\/docs.python.org\/3\/library\/venv.html&gt;\r\n#\r\n# Notes:\r\n#\r\n#   1. A &quot;virtual environment&quot; in this context is\r\n#      a sandboxed Python installation. That is,\r\n#      isolated from the system Python\r\n#      installation (which the operating\r\n#      system heavily relies on; thus it\r\n#      should not be messed with)\r\n#\r\n#   2. 'venv' is part of Python's standard \r\n#      library (since version 3.2)\r\n\r\nsudo apt install python3-venv python3-pip git\r\n\r\n# Create a new virtual environment\r\npython3 -m venv $HOME\/.ZMK_environment\r\nsource $HOME\/.ZMK_environment\/bin\/activate\r\n\r\n# Inside the virtual environment:\r\n#\r\n# Install west\r\npip install west\r\n\r\n\r\n# Initialize the application and update to\r\n# fetch modules, including Zephyr:\r\nwest init -l app\/\r\nwest update\r\n\r\n\r\n# Export a Zephyr CMake package. This allows CMake to automatically\r\n# load boilerplate code required for building Zephyr applications.\r\nwest zephyr-export\r\n\r\n\r\n# Install the additional dependencies found\r\n# in Zephyr's requirements-base.txt\r\npip install -r zephyr\/scripts\/requirements-base.txt\r\n\r\n\r\n# &lt;https:\/\/docs.zephyrproject.org\/3.5.0\/develop\/getting_started\/index.html#install-zephyr-sdk&gt;:\r\n#\r\n# Install Zephyr SDK\r\n#\r\n# The Zephyr Software Development Kit (SDK) contains toolchains for\r\n# each of Zephyr's supported architectures, which include\r\n# a compiler, assembler, linker and other programs\r\n# required to build Zephyr applications.\r\n#\r\n#\r\n# Download and verify the Zephyr SDK bundle\r\n#\r\n# 1.2 GB... (download size. Compressed)\r\n#\r\ncd $HOME\r\nwget https:\/\/github.com\/zephyrproject-rtos\/sdk-ng\/releases\/download\/v0.16.3\/zephyr-sdk-0.16.3_linux-x86_64.tar.xz\r\nwget -O - https:\/\/github.com\/zephyrproject-rtos\/sdk-ng\/releases\/download\/v0.16.3\/sha256.sum | shasum --check --ignore-missing\r\n\r\n\r\n# Extract the Zephyr SDK bundle archive:\r\ntar xvf zephyr-sdk-0.16.3_linux-x86_64.tar.xz\r\n\r\n\r\n# Run the Zephyr SDK bundle setup script:\r\ncd zephyr-sdk-0.16.3\r\n.\/setup.sh\r\n\r\n\r\n#Not yet... For the B6 Pro, we are going to just copy\r\n#           the .uf2 file to the keyboard being in \r\n#           flash mode and mounted in the \r\n#           operating system.\r\n#\r\n# Install udev rules, which allow you to flash most Zephyr\r\n# boards as a regular user:\r\nsudo cp $HOME\/zephyr-sdk-0.16.3\/sysroots\/x86_64-pokysdk-linux\/usr\/share\/openocd\/contrib\/60-openocd.rules \/etc\/udev\/rules.d\r\nsudo udevadm control --reload\r\n\r\n# &lt;https:\/\/zmk.dev\/docs\/development\/local-toolchain\/build-flash&gt;\r\n#\r\n# Building and flashing\r\ncd $HOME\/zmk\/app\r\n\r\n# Build firmware for a sample keyboard\r\n#\r\n#   &lt;\/home\/embo\/zmk\/app\/boards\/arm\/planck\/planck_rev6.yaml&gt;\r\n#      Has:\r\n#\r\n#        identifier: planck_rev6\r\n#\r\n#   &lt;\/home\/embo\/zmk\/app\/boards\/arm\/planck\/planck_rev6.keymap&gt;\r\n#   &lt;\/home\/embo\/zmk\/app\/boards\/arm\/planck\/planck_rev6.zmk.yml&gt;\r\n#   &lt;\/home\/embo\/zmk\/app\/boards\/arm\/planck\/planck_rev6.dtsv\r\n#\r\n# A build folder:\r\n#\r\n#   &lt;\/home\/embo\/zmk\/app\/build\/zephyr\/boards\/boards\/arm\/planck_rev6&gt;\r\n#\r\n#\r\ncd $HOME\/zmk\/app\r\nwest build -b planck_rev6\r\nfind $HOME\/zmk\/app | grep elf | xargs ls -l\r\n\r\n\r\n# Keychron B6 Pro\r\n#\r\n# Note: The Git branch will be &quot;main&quot; after the clone...\r\n#       Keychron's Git branch is &quot;keychron_bpro&quot;\r\n#\r\n#       And we should clone Keychron's fork,\r\n#       not the main one...\r\n#\r\n# &lt;https:\/\/github.com\/Keychron\/zmk\/tree\/keychron_bpro\/app\/boards\/shields\/keychron\/b6&gt;\r\n#\r\n# &lt;https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/shields\/keychron\/b6\/uk\/Kconfig.shield&gt;\r\n#   Has: 'keychron_b6_uk'?\r\n#\r\n# Clone the source code repository\r\n#\r\ncd $HOME\r\ngit clone https:\/\/github.com\/Keychron\/zmk.git  zmk_KeychronFork_forReal\r\n\r\n# The action is in Git branch &quot;keychron_bpro&quot;\r\ncd $HOME\/zmk_KeychronFork_forReal\r\ngit switch keychron_bpro\r\n\r\n# Or in a single step:\r\n#\r\n#   cd $HOME\r\n#   git clone -b keychron_bpro https:\/\/github.com\/Keychron\/zmk.git  zmk_KeychronFork_forReal\r\n\r\n\r\n# Initialize the application and update to\r\n# fetch modules, including Zephyr.\r\n#\r\n# This will take some time...\r\n#\r\ncd $HOME\/zmk_KeychronFork_forReal\r\nwest init -l app\/\r\nwest update # Primary approx. 300 MB. 145496 &quot;objects&quot;.\r\n            # And several Git submodules, e.g.\r\n            #   'modules\/lib\/canopennode'\r\n            #   'modules\/hal\/stm32'\r\n            #\r\n            # Nearly 2000 lines in the screen output.\r\n\r\n\r\n# Export a Zephyr CMake package. This allows CMake to automatically\r\n# load boilerplate code required for building Zephyr applications.\r\nwest zephyr-export\r\n\r\n\r\n# Keyboard Planck in Keychron's fork of ZMK\r\ncd $HOME\/zmk_KeychronFork_forReal\/app\r\nwest build -b planck_rev6\r\n\r\n  # Failed:\r\n  #\r\n  #   _power.h: No such file or directory\r\n  #   18 | #include &lt;hal\/nrf_power.h&gt;\r\n  #      |          ^~~~~~~~~~~~~~~~~\r\n\r\n\r\n# --pristine due to the previous build of keyboard Planck\r\ncd $HOME\/zmk_KeychronFork_forReal\/app\r\nwest build --pristine  -b keychron -p  -- -DSHIELD=keychron_b6_uk\r\n<\/pre>\n<p>OK, this was probably predicated on Git already being installed, so the Git clone step should probably be moved down somewhat.<\/p>\n<h2>The blocking build error<\/h2>\n<p><a href=\"https:\/\/github.com\/Keychron\/qmk_firmware\/issues\/344#issuecomment-2652749875\">This was already reported<\/a>:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nFAILED: zephyr\/CMakeFiles\/zephyr.dir\/home\/embo\/zmk_KeychronFork_forReal\/app\/src\/dfu\/tdfu_prv.c.obj\r\nccache \/home\/embo\/zephyr-sdk-0.16.3\/arm-zephyr-eabi\/bin\/arm-zephyr-eabi-gcc -DKERNEL -DNRF52840_XXAA -D_FORTIFY_SOURCE=2 -D__PROGRAM_START -D__ZEPHYR__=1 -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/kernel\/include -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/arch\/arm\/include -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/include -I\/home\/embo\/zmk_KeychronFork_forReal\/app\/build\/zephyr\/include\/generated -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/soc\/arm\/nordic_nrf\/nrf52 -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/soc\/arm\/nordic_nrf\/common\/. -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/subsys\/bluetooth\/controller\/ll_sw\/nordic\/hal\/nrf5\/nrfx_glue -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/subsys\/bluetooth -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/subsys\/usb\/device -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/subsys\/settings\/include -I\/home\/embo\/zmk_KeychronFork_forReal\/modules\/hal\/cmsis\/CMSIS\/Core\/Include -I\/home\/embo\/zmk_KeychronFork_forReal\/modules\/hal\/nordic\/nrfx -I\/home\/embo\/zmk_KeychronFork_forReal\/modules\/hal\/nordic\/nrfx\/drivers\/include -I\/home\/embo\/zmk_KeychronFork_forReal\/modules\/hal\/nordic\/nrfx\/mdk -I\/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/modules\/hal_nordic\/nrfx\/. -I\/home\/embo\/zmk_KeychronFork_forReal\/modules\/crypto\/tinycrypt\/lib\/include -I\/home\/embo\/zmk_KeychronFork_forReal\/app\/module\/include -I\/home\/embo\/zmk_KeychronFork_forReal\/app\/module\/drivers\/sensor\/battery\/. -isystem \/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/lib\/libc\/minimal\/include -isystem \/home\/embo\/zephyr-sdk-0.16.3\/arm-zephyr-eabi\/bin\/..\/lib\/gcc\/arm-zephyr-eabi\/12.2.0\/include -isystem \/home\/embo\/zephyr-sdk-0.16.3\/arm-zephyr-eabi\/bin\/..\/lib\/gcc\/arm-zephyr-eabi\/12.2.0\/include-fixed -fno-strict-aliasing -Os -imacros \/home\/embo\/zmk_KeychronFork_forReal\/app\/build\/zephyr\/include\/generated\/autoconf.h -ffreestanding -fno-common -g -gdwarf-4 -fdiagnostics-color=always -mcpu=cortex-m4 -mthumb -mabi=aapcs -mfp16-format=ieee --sysroot=\/home\/embo\/zephyr-sdk-0.16.3\/arm-zephyr-eabi\/arm-zephyr-eabi -imacros \/home\/embo\/zmk_KeychronFork_forReal\/zephyr\/include\/zephyr\/toolchain\/zephyr_stdint.h -Wall -Wformat -Wformat-security -Wno-format-zero-length -Wno-main -Wno-pointer-sign -Wpointer-arith -Wexpansion-to-defined -Wno-unused-but-set-variable -Werror=implicit-int -fno-pic -fno-pie -fno-asynchronous-unwind-tables -fno-reorder-functions --param=min-pagesize=0 -fno-defer-pop -fmacro-prefix-map=\/home\/embo\/zmk_KeychronFork_forReal\/app=CMAKE_SOURCE_DIR -fmacro-prefix-map=\/home\/embo\/zmk_KeychronFork_forReal\/zephyr=ZEPHYR_BASE -fmacro-prefix-map=\/home\/embo\/zmk_KeychronFork_forReal=WEST_TOPDIR -ffunction-sections -fdata-sections -std=c99 -nostdinc -Wfatal-errors -MD -MT zephyr\/CMakeFiles\/zephyr.dir\/home\/embo\/zmk_KeychronFork_forReal\/app\/src\/dfu\/tdfu_prv.c.obj -MF zephyr\/CMakeFiles\/zephyr.dir\/home\/embo\/zmk_KeychronFork_forReal\/app\/src\/dfu\/tdfu_prv.c.obj.d -o zephyr\/CMakeFiles\/zephyr.dir\/home\/embo\/zmk_KeychronFork_forReal\/app\/src\/dfu\/tdfu_prv.c.obj -c \/home\/embo\/zmk_KeychronFork_forReal\/app\/src\/dfu\/tdfu_prv.c\r\nIn file included from \/home\/embo\/zmk_KeychronFork_forReal\/app\/src\/dfu\/tdfu_prv.c:1:\r\n\/home\/embo\/zmk_KeychronFork_forReal\/app\/src\/dfu\/tdfu_prv.h:3:10: fatal error: ..\\version.h: No such file or directory\r\n    3 | #include &quot;..\\version.h&quot;\/\/&lt;app_version.h&gt;\r\n      |          ^~~~~~~~~~~~~~\r\ncompilation terminated.\r\n<\/pre>\n<p>And I got the same error, using the final step for actually building for B6 Pro (using the instruction found <a href=\"https:\/\/github.com\/PeterMortensen\/zmk_fork_of_Keychron_s_fork\/blob\/Copy_editing_of_the_build_instructions\/README.md\">here<\/a>):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nsource $HOME\/.ZMK_environment\/bin\/activate\r\ncd $HOME\/zmk_KeychronFork_forReal\/app\r\nwest build --pristine  -b keychron -p  -- -DSHIELD=keychron_b6_uk\r\ndeactivate\r\n<\/pre>\n<p><a href=\"https:\/\/github.com\/Keychron\/qmk_firmware\/issues\/344#issuecomment-2709384769\">It was later overcome<\/a>. Here is <a href=\"https:\/\/pmortensen.eu\/temp2\/Transcript_of_building_B6_Pro_2025-03-09.txt\">a transcript<\/a>.<\/p>\n<h2>Simple keymappings, including modifier keys<\/h2>\n<p>Edit the keymap file, <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/shields\/keychron\/b6\/uk\/keychron_b6_uk.keymap#L233\">\/app\/boards\/shields\/keychron\/b6\/uk\/keychron_b6_uk.keymap<\/a>, for example,<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\ngeany  '$HOME\/zmk_KeychronFork_forReal\/app\/boards\/shields\/keychron\/b6\/uk\/keychron_b6_uk.keymap:233'\r\n<\/pre>\n<h2 id=\"Fn_J_Z_is_broken_for_self-compiled_Keychron_ZMK_firmware\">Fn + J + Z doesn&#8217;t work for the self-compiled firmware<\/h2>\n<p>Unlike Keychron&#8217;s official firmware, holding down <strong><em>Fn<\/em><\/strong> + <strong><em>J<\/em><\/strong> + <strong><em>Z<\/em><\/strong> doesn&#8217;t work for the self-compiled firmware (nothing happens). This is a similar situation to the <a href=\"https:\/\/docs.qmk.fm\/#\/faq_general?id=what-is-qmk\">QMK<\/a>-based Keychron keyboards where it doesn&#8217;t work either using the official source code.<\/p>\n<p>In the source code, by inspection, the time lock is expressed in file <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/shields\/keychron\/b6\/uk\/keychron_b6_uk.keymap#L64\">keychron_b6_uk.keymap<\/a>:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\ncombos1 \r\n{\r\n    compatible = &quot;zmk,combos&quot;;\r\n    combo_jz \r\n    {\r\n        timeout-ms = &lt;1000&gt;;\r\n        layers = &lt;1 3&gt;;\r\n        key-positions = &lt;69 81&gt;; \/\/ Fn + J + Z\r\n        bindings = &lt;&amp;long_press_recover 0 0&gt;;\r\n    };\r\n<\/pre>\n<p>For some of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Magic_number_(programming)\">magic numbers<\/a>, I added this (an extension to a custom Perl script to reformat the messy code, to automatically generate a symbolic representation of the keynumbers):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nenum key_numbers\r\n{\r\n    k_J                 =  69\r\n    k_Z                 =  81\r\n    k_Fn                = 102\r\n};\r\n<\/pre>\n<p>&#8220;long_press_recover&#8221; is <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/shields\/keychron\/b6\/uk\/keychron_b6_uk.keymap#L23\">in the same file<\/a>:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nlong_press_recover:l1\r\n{\r\n    compatible = &quot;zmk,behavior-hold-tap&quot;;\r\n    label = &quot;l1&quot;;\r\n    #binding-cells = &lt;2&gt;;\r\n    flavor = &quot;tap-preferred&quot;;\r\n    tapping-term-ms = &lt;3000&gt;;  \/\/ Delay amount in milliseconds\r\n    bindings = &lt;&amp;recover&gt;, &lt;&amp;none&gt;;\r\n};\r\n<\/pre>\n<p>Drawing an analogy from the similarly non-working holding <strong><em>Fn<\/em><\/strong> + <strong><em>&#8220;-&#8220;<\/em><\/strong> for 3 seconds to enter bootloader mode, using &#8220;&#038;recover&#8221; in a regular keymapping may be a workaround.<\/p>\n<h2 id=\"B6_Pro_key_numbers\">Appendix A: Key numbers<\/h2>\n<p>The source code file for the key map contains <strong><em>a lot<\/em><\/strong> of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Magic_number_(programming)\">magic numbers<\/a>, for example key numbers in the keymap. Here is the list of them for the B6 Pro, ISO variant:<\/p>\n<table id=\"someTable\">\n<tr>\n<th>Key number<\/th>\n<th>Key<\/th>\n<th>Notes<\/th>\n<\/tr>\n<p>    <!--\n    \n\n<tr> \n\n<td>1<\/td>\n\n \n\n<td class=\"rightAlign\">0.6<\/td>\n\n \n\n<td>FAT32<\/td>\n\n \n\n<td>\/boot\/efi<\/td>\n\n \n\n<td>boot<\/td>\n\n \n\n<td><\/td>\n\n \n\n<td>\/dev\/nvme0n1p1<\/td>\n\n <\/tr>\n\n\n    --><\/p>\n<tr>\n<td>0<\/td>\n<td>ESC<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>F1<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>F2<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>F3<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>4<\/td>\n<td>F4<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>5<\/td>\n<td>F5<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>6<\/td>\n<td>F6<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>7<\/td>\n<td>F7<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>8<\/td>\n<td>F8<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>9<\/td>\n<td>F9<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>10<\/td>\n<td>F10<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>11<\/td>\n<td>F11<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>12<\/td>\n<td>F12<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>13<\/td>\n<td>F13<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>14<\/td>\n<td>PSCRN<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>15<\/td>\n<td>SLCK<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>16<\/td>\n<td>PAUS<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>17<\/td>\n<td>C_AL_CALC<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>18<\/td>\n<td>&amp;uc LG(TAB)<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>19<\/td>\n<td>&amp;uc LG(LC(LEFT))<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>20<\/td>\n<td>&amp;uc LG(LC(RIGHT))<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>21<\/td>\n<td>GRAVE<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>22<\/td>\n<td>N1<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>23<\/td>\n<td>N2<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>24<\/td>\n<td>N3<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>25<\/td>\n<td>N4<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>26<\/td>\n<td>N5<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>27<\/td>\n<td>N6<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>28<\/td>\n<td>N7<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>29<\/td>\n<td>N8<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>30<\/td>\n<td>N9<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>31<\/td>\n<td>N0<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>32<\/td>\n<td>MINUS<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>33<\/td>\n<td>EQUAL<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>34<\/td>\n<td>BSPC<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>35<\/td>\n<td>INS<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>36<\/td>\n<td>HOME<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>37<\/td>\n<td>PG_UP<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>38<\/td>\n<td>KP_NLCK<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>39<\/td>\n<td>KP_SLASH<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>40<\/td>\n<td>KP_ASTERISK<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>41<\/td>\n<td>KP_MINUS<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>42<\/td>\n<td>TAB<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>43<\/td>\n<td>Q<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>44<\/td>\n<td>W<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>45<\/td>\n<td>E<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>46<\/td>\n<td>R<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>47<\/td>\n<td>T<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>48<\/td>\n<td>Y<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>49<\/td>\n<td>U<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>50<\/td>\n<td>I<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>51<\/td>\n<td>O<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>52<\/td>\n<td>P<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>53<\/td>\n<td>LBKT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>54<\/td>\n<td>RBKT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>55<\/td>\n<td>DEL<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>56<\/td>\n<td>END<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>57<\/td>\n<td>PG_DN<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>58<\/td>\n<td>KP_N7<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>59<\/td>\n<td>KP_N8<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>60<\/td>\n<td>KP_N9<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>61<\/td>\n<td>KP_PLUS<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>62<\/td>\n<td>CLCK<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>63<\/td>\n<td>A<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>64<\/td>\n<td>S<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>65<\/td>\n<td>D<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>66<\/td>\n<td>F<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>67<\/td>\n<td>G<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>68<\/td>\n<td>H<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>69<\/td>\n<td>J<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>70<\/td>\n<td>K<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>71<\/td>\n<td>L<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>72<\/td>\n<td>SEMI<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>73<\/td>\n<td>SQT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>74<\/td>\n<td>NUHS<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>75<\/td>\n<td>RET<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>76<\/td>\n<td>KP_N4<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>77<\/td>\n<td>KP_N5<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>78<\/td>\n<td>KP_N6<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>79<\/td>\n<td>LSHFT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>80<\/td>\n<td>NUBS<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>81<\/td>\n<td>Z<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>82<\/td>\n<td>X<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>83<\/td>\n<td>C<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>84<\/td>\n<td>V<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>85<\/td>\n<td>B<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>86<\/td>\n<td>N<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>87<\/td>\n<td>M<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>88<\/td>\n<td>COMMA<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>89<\/td>\n<td>DOT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>90<\/td>\n<td>FSLH<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>91<\/td>\n<td>RSHFT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>92<\/td>\n<td>UP<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>93<\/td>\n<td>KP_N1<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>94<\/td>\n<td>KP_N2<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>95<\/td>\n<td>KP_N3<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>96<\/td>\n<td>LCTRL<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>97<\/td>\n<td>LGUI<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>98<\/td>\n<td>LALT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>99<\/td>\n<td>SPACE<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>100<\/td>\n<td>RALT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>101<\/td>\n<td>RGUI<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>102<\/td>\n<td>&amp;mo 3<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>103<\/td>\n<td>RCTRL<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>104<\/td>\n<td>LEFT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>105<\/td>\n<td>DOWN<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>106<\/td>\n<td>RIGHT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>107<\/td>\n<td>KP_N0<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>108<\/td>\n<td>KP_DOT<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>109<\/td>\n<td>KP_ENTER<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>110<\/td>\n<td>&amp;none<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>111<\/td>\n<td>&amp;out OUT_BLE<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>112<\/td>\n<td>&amp;out OUT_24G<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>113<\/td>\n<td>&amp;out OUT_CHG<\/td>\n<td> <\/td>\n<\/tr>\n<tr>\n<td>114<\/td>\n<td>&amp;out OUT_CHGD<\/td>\n<td> <\/td>\n<\/tr>\n<\/table>\n<h2>References<\/h2>\n<ul>\n<li><a href=\"https:\/\/zmk.dev\/docs\/keymaps\/list-of-keycodes\">ZMK keycodes<\/a>. For example, RWIN for right <a href=\"https:\/\/en.wikipedia.org\/wiki\/Windows_key\">Windows key<\/a> and <a href=\"https:\/\/zmk.dev\/docs\/keymaps\/list-of-keycodes#navigation\">K_APP<\/a> for the context menu (the corresponding keycode in QMK is <a href=\"https:\/\/github.com\/qmk\/qmk_firmware\/blob\/master\/docs\/keycodes.md\">KC_APP<\/a>; they are probably both aliases)<\/li>\n<li><a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\">ZMK &#8216;behaviors&#8217;<\/a>. Includes an explanation for, for example, &#8220;&#038;kp&#8221; (<a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\/key-press\">key press<\/a>), &#8220;&#038;none&#8221;, &#8220;&#038;trans&#8221; (transparent), &#8220;&#038;mo&#8221; (<a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\/layers#momentary-layer\">momentary layer<\/a>; e.g., the <strong><em>Fn<\/em><\/strong> key), &#8220;&#038;out&#8221; (output selection), &#8220;&#038;to&#8221; (<a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\/layers#to-layer\">to layer<\/a>), &#8220;&#038;tog&#8221; (<a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\/layers#toggle-layer\">toggle layer<\/a>), &#8220;&#038;sl&#8221; (<a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\/sticky-layer\">sticky layer<\/a>), &#8220;&#038;lt&#8221; (<a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\/layers#layer-tap\">layer tap<\/a>), &#8220;&#038;sk&#8221; (<a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\/sticky-key\">sticky key<\/a>), &#8220;&#038;mt&#8221; (modifier tap), and &#8220;&#038;gresc&#8221; (<a href=\"https:\/\/zmk.dev\/docs\/keymaps\/behaviors\/mod-morph#behavior-binding\">grave escape<\/a>). But not &#8220;&#038;uc&#8221;&#8230; OK, &#8220;uc&#8221; is probably defined by <a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/shields\/keychron\/b6\/uk\/keychron_b6_uk.keymap#L199\">the line<\/a> in file &#8220;keychron_b6_uk.keymap&#8221; with &#8220;uc:user_custom&#8221;. But why is there &#038;uc in front of &#8220;LALT&#8221; and &#8220;LCMD&#8221;, but &#8220;&#038;kp&#8221; in front of &#8220;LCTRL&#8221;?<\/li>\n<li><a href=\"https:\/\/peccu.github.io\/zmk-cheat-sheet\/\">ZMK cheat sheet<\/a>. But it doesn&#8217;t have an explanation for &#8220;&#038;uc&#8221; either&#8230; Is it Keychron-specific? An alias of &#8220;user_custom&#8221; (whatever that is)?<\/li>\n<li><a href=\"https:\/\/zmk.dev\/docs\/troubleshooting\/connection-issues#building-a-reset-firmware\">Building a reset firmware<\/a> [sic]. Does it only apply for split keyboards?<\/li>\n<li><a href=\"https:\/\/zmk.dev\/docs\/development\/usb-logging\">USB logging<\/a> (in ZMK)<\/li>\n<li><a href=\"https:\/\/zmk.dev\/docs\/troubleshooting\/connection-issues#windows-connected-but-not-working\">Troubleshooting Bluetooth connection issues in Windows<\/a><\/li>\n<li><a href=\"https:\/\/zmk.dev\/docs\/config\/system#hid\">Enable NKRO in wireless mode<\/a> (ZMK), using <strong><em>CONFIG_ZMK_HID_REPORT_TYPE_NKRO<\/em><\/strong><\/li>\n<li><a href=\"https:\/\/github.com\/Keychron\/zmk\/blob\/keychron_bpro\/app\/boards\/shields\/keychron\/b6\/uk\/keychron_b6_uk.keymap#L236\">Keychron B6 Pro keymap<\/a> (ISO)<\/li>\n<li><a href=\"https:\/\/pipx.pypa.io\/stable\/comparisons\/\">Comparison of pipx to other tools<\/a> (Python)<\/li>\n<\/ul>\n<p><!- Marker for editing in WordPress: pppp --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This was written as it happened (though not necessarily in the same order) when I was new to ZMK, on day 1 of getting the Keychron B6 Pro. Some pros: Independence of a web service. More permanent configuration of the &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"more-link\" href=\"https:\/\/pmortensen.eu\/world2\/2025\/03\/10\/customising-the-zmk-based-keychron-b6-pro-keyboard-without-the-via-clone\/\"> <span class=\"screen-reader-text\">Customising the ZMK-based Keychron B6 Pro keyboard without the Via clone<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[44,4,28,26,40],"tags":[],"_links":{"self":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/5659"}],"collection":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/comments?post=5659"}],"version-history":[{"count":85,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/5659\/revisions"}],"predecessor-version":[{"id":6489,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/5659\/revisions\/6489"}],"wp:attachment":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/media?parent=5659"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/categories?post=5659"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/tags?post=5659"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}