diff --git a/builddefs/build_test.mk b/builddefs/build_test.mk
index ccdad1b4e798..4a26a5da0cbe 100644
--- a/builddefs/build_test.mk
+++ b/builddefs/build_test.mk
@@ -68,6 +68,7 @@ include $(QUANTUM_PATH)/encoder/tests/rules.mk
include $(QUANTUM_PATH)/os_detection/tests/rules.mk
include $(QUANTUM_PATH)/sequencer/tests/rules.mk
include $(QUANTUM_PATH)/wear_leveling/tests/rules.mk
+include keyboards/keychron/common/tests/rules.mk
include $(QUANTUM_PATH)/logging/print.mk
include $(PLATFORM_PATH)/test/rules.mk
ifneq ($(filter $(FULL_TESTS),$(TEST)),)
diff --git a/builddefs/testlist.mk b/builddefs/testlist.mk
index 2e81fe576bfc..c66b56a164e8 100644
--- a/builddefs/testlist.mk
+++ b/builddefs/testlist.mk
@@ -7,6 +7,7 @@ include $(QUANTUM_PATH)/encoder/tests/testlist.mk
include $(QUANTUM_PATH)/os_detection/tests/testlist.mk
include $(QUANTUM_PATH)/sequencer/tests/testlist.mk
include $(QUANTUM_PATH)/wear_leveling/tests/testlist.mk
+include keyboards/keychron/common/tests/testlist.mk
include $(PLATFORM_PATH)/test/testlist.mk
define VALIDATE_TEST_LIST
diff --git a/data/mappings/keyboard_aliases.hjson b/data/mappings/keyboard_aliases.hjson
index 3978235e20c9..9ff3af3af5c5 100644
--- a/data/mappings/keyboard_aliases.hjson
+++ b/data/mappings/keyboard_aliases.hjson
@@ -6,1062 +6,9 @@
//
/* This list of aliases is for testing purposes -- ensures "linked list" recursive traversal works correctly. */
- "_test_a": { "target": "_test_b" },
- "_test_b": { "target": "_test_c" },
- "_test_c": { "target": "planck/rev6" },
/* The main list of aliases for moved keyboards within QMK. */
- "2_milk": {
- "target": "spaceman/2_milk"
- },
- "absinthe": {
- "target": "keyhive/absinthe"
- },
- "aeboards/constellation": {
- "target": "aeboards/constellation/rev1"
- },
- "aeboards/ext65": {
- "target": "aeboards/ext65/rev1"
- },
- "ai03/equinox": {
- "target": "ai03/equinox/rev1"
- },
- "alice": {
- "target": "tgr/alice"
- },
- "amj40": {
- "target": "amjkeyboard/amj40"
- },
- "amj60": {
- "target": "amjkeyboard/amj60"
- },
- "amj96": {
- "target": "amjkeyboard/amj96"
- },
- "amjpad": {
- "target": "amjkeyboard/amjpad"
- },
- "angel64": {
- "target": "kakunpc/angel64/alpha"
- },
- "ashpil/modelm_usbc": {
- "target": "ibm/model_m/ashpil_usbc"
- },
- "at101_blackheart": {
- "target": "viktus/at101_bh"
- },
- "at101_bh": {
- "target": "viktus/at101_bh"
- },
- "atom47/rev2": {
- "target": "evyd13/atom47/rev2"
- },
- "atom47/rev3": {
- "target": "evyd13/atom47/rev3"
- },
- "bakeneko60": {
- "target": "kkatano/bakeneko60"
- },
- "bakeneko65": {
- "target": "kkatano/bakeneko65/rev2"
- },
- "bakeneko80": {
- "target": "kkatano/bakeneko80"
- },
- "bastardkb/charybdis/3x5/v2/elitec": {
- "target": "bastardkb/charybdis/3x5/elitec"
- },
- "bastardkb/charybdis/3x5/v2/splinky_2": {
- "target": "bastardkb/charybdis/3x5/elitec"
- },
- "bastardkb/charybdis/3x5/v2/splinky_3": {
- "target": "bastardkb/charybdis/3x5/elitec"
- },
- "bastardkb/charybdis/3x5/v2/stemcell": {
- "target": "bastardkb/charybdis/3x5/elitec"
- },
- "bastardkb/charybdis/3x6/v2/elitec": {
- "target": "bastardkb/charybdis/3x6/elitec"
- },
- "bastardkb/charybdis/3x6/v2/splinky_2": {
- "target": "bastardkb/charybdis/3x6/elitec"
- },
- "bastardkb/charybdis/3x6/v2/splinky_3": {
- "target": "bastardkb/charybdis/3x6/elitec"
- },
- "bastardkb/charybdis/3x6/v2/stemcell": {
- "target": "bastardkb/charybdis/3x6/elitec"
- },
- "bastardkb/charybdis/4x6/v2/elitec": {
- "target": "bastardkb/charybdis/4x6/elitec"
- },
- "bastardkb/charybdis/4x6/v2/splinky_2": {
- "target": "bastardkb/charybdis/4x6/elitec"
- },
- "bastardkb/charybdis/4x6/v2/splinky_3": {
- "target": "bastardkb/charybdis/4x6/elitec"
- },
- "bastardkb/charybdis/4x6/v2/stemcell": {
- "target": "bastardkb/charybdis/4x6/elitec"
- },
- "bastardkb/dilemma/3x5_2/splinky": {
- "target": "bastardkb/dilemma/3x5_2/promicro"
- },
- "bastardkb/scylla/v2/elitec": {
- "target": "bastardkb/scylla/promicro"
- },
- "bastardkb/scylla/v2/splinky_2": {
- "target": "bastardkb/scylla/promicro"
- },
- "bastardkb/scylla/v2/splinky_3": {
- "target": "bastardkb/scylla/promicro"
- },
- "bastardkb/scylla/v2/stemcell": {
- "target": "bastardkb/scylla/promicro"
- },
- "bastardkb/skeletyl/v2/elitec": {
- "target": "bastardkb/skeletyl/promicro"
- },
- "bastardkb/skeletyl/v2/splinky_2": {
- "target": "bastardkb/skeletyl/promicro"
- },
- "bastardkb/skeletyl/v2/splinky_3": {
- "target": "bastardkb/skeletyl/promicro"
- },
- "bastardkb/skeletyl/v2/stemcell": {
- "target": "bastardkb/skeletyl/promicro"
- },
- "bastardkb/tbkmini/v2/elitec": {
- "target": "bastardkb/tbkmini/promicro"
- },
- "bastardkb/tbkmini/v2/splinky_2": {
- "target": "bastardkb/tbkmini/promicro"
- },
- "bastardkb/tbkmini/v2/splinky_3": {
- "target": "bastardkb/tbkmini/promicro"
- },
- "bastardkb/tbkmini/v2/stemcell": {
- "target": "bastardkb/tbkmini/promicro"
- },
- "bear_face": {
- "target": "bear_face/v1"
- },
- "bm16a": {
- "target": "kprepublic/bm16a/v1"
- },
- "bm16s": {
- "target": "kprepublic/bm16s"
- },
- "bm40hsrgb": {
- "target": "kprepublic/bm40hsrgb"
- },
- "bm43a": {
- "target": "kprepublic/bm43a"
- },
- "bm60poker": {
- "target": "kprepublic/bm60hsrgb_poker/rev1"
- },
- "bm60rgb": {
- "target": "kprepublic/bm60hsrgb/rev1"
- },
- "bm60rgb_iso": {
- "target": "kprepublic/bm60hsrgb_iso/rev1"
- },
- "bm68rgb": {
- "target": "kprepublic/bm68hsrgb/rev1"
- },
- "bpiphany/pegasushoof": {
- "target": "bpiphany/pegasushoof/2013"
- },
- "brick": {
- "target": "pauperboards/brick"
- },
- "chavdai40": {
- "target": "chavdai40/rev1"
- },
- "candybar/lefty": {
- "target": "tkc/candybar/lefty"
- },
- "candybar/righty": {
- "target": "tkc/candybar/righty"
- },
- "canoe": {
- "target": "percent/canoe"
- },
- "clawsome/gamebuddy": {
- "target": "clawsome/gamebuddy/v1_0"
- },
- "cmm_studio/saka68": {
- "target": "cmm_studio/saka68/solder"
- },
- "converter/modelm101": {
- "target": "ibm/model_m/teensypp"
- },
- "converter/modelm101_teensy2": {
- "target": "ibm/model_m/teensy2"
- },
- "converter/modelm_ssk": {
- "target": "ibm/model_m_ssk/teensypp_ssk"
- },
- "cospad": {
- "target": "kprepublic/cospad"
- },
- "crkbd/rev1/legacy": {
- "target": "crkbd/rev1"
- },
- "crkbd/rev1/common": {
- "target": "crkbd/rev1"
- },
- "custommk/genesis": {
- "target": "custommk/genesis/rev1"
- },
- "cxt_studio":{
- "target":"cxt_studio/12e4"
- },
- "daisy": {
- "target": "ktec/daisy"
- },
- "deemen17/de60": {
- "target": "deemen17/de60/r1"
- },
- "dp3000": {
- "target": "dp3000/rev1"
- },
- "drakon": {
- "target": "jagdpietr/drakon"
- },
- "durgod/k320": {
- "target": "durgod/k320/base"
- },
- "durgod/k3x0/k320": {
- "target": "durgod/k320/base"
- },
- "durgod/hades": {
- "target": "durgod/dgk6x/hades_ansi"
- },
- "durgod/hades_ansi": {
- "target": "durgod/dgk6x/hades_ansi"
- },
- "durgod/hades_iso": {
- "target": "durgod/dgk6x/hades_iso"
- },
- "dztech/dz60rgb": {
- "target": "dztech/dz60rgb/v1"
- },
- "dztech/dz60rgb_ansi": {
- "target": "dztech/dz60rgb_ansi/v1"
- },
- "dztech/dz60rgb_wkl": {
- "target": "dztech/dz60rgb_wkl/v1"
- },
- "dztech/dz65rgb": {
- "target": "dztech/dz65rgb/v1"
- },
- "dztech/volcano660": {
- "target": "ilumkb/volcano660"
- },
- "dztech/og60": {
- "target": "dztech/tofu60"
- },
- "eek": {
- "target": "eek/silk_down"
- },
- "epoch80": {
- "target": "kbdfans/epoch80"
- },
- "era/klein": {
- "target": "era/sirind/klein_sd"
- },
- "ergodone": {
- "target": "ktec/ergodone"
- },
- "ergodox_stm32": {
- "target": "handwired/ergodox_stm32"
- },
- "ergoinu": {
- "target": "dm9records/ergoinu"
- },
- "ergosaurus": {
- "target": "keyhive/ergosaurus"
- },
- "exclusive/e85": {
- "target": "exclusive/e85/hotswap"
- },
- "gh60": {
- "target": "gh60/revc"
- },
- "gmmk/pro": {
- "target": "gmmk/pro/rev1/ansi"
- },
- "gmmk/pro/ansi": {
- "target": "gmmk/pro/rev1/ansi"
- },
- "gmmk/pro/iso": {
- "target": "gmmk/pro/rev1/iso"
- },
- "handwired/dactyl_manuform/3x5_3": {
- "target": "handwired/dactyl_minidox"
- },
- "handwired/dactyl_manuform/6x6_kinesis": {
- "target": "handwired/dactyl_kinesis"
- },
- "handwired/dactyl_manuform/dmote/62key": {
- "target": "handwired/dmote"
- },
- "handwired/ferris": {
- "target": "ferris/0_1"
- },
- "handwired/ibm122m": {
- "target": "ibm/model_m_122/ibm122m"
- },
- "handwired/p1800fl": {
- "target": "team0110/p1800fl"
- },
- "handwired/jscotto/scotto9": {
- "target": "handwired/scottokeebs/scotto9"
- },
- "handwired/jscotto/scotto36": {
- "target": "handwired/scottokeebs/scotto36"
- },
- "handwired/jscotto/scotto40": {
- "target": "handwired/scottokeebs/scotto40"
- },
- "handwired/jscotto/scottocmd": {
- "target": "handwired/scottokeebs/scottocmd"
- },
- "handwired/jscotto/scottostarter": {
- "target": "handwired/scottokeebs/scottostarter"
- },
- "helix": {
- "target": "helix/beta"
- },
- "helix/rev2": {
- "target": "helix/beta"
- },
- "honeycomb": {
- "target": "keyhive/honeycomb"
- },
- "hub16": {
- "target": "joshajohnson/hub16"
- },
- "hub20": {
- "target": "joshajohnson/hub20"
- },
- "idb_60": {
- "target": "idb/idb_60"
- },
- "idobo": {
- "target": "idobao/id75/v1"
- },
- "jacky_studio/piggy60": {
- "target": "jacky_studio/piggy60/rev1"
- },
- "jj40": {
- "target": "kprepublic/jj40/rev1"
- },
- "jj4x4": {
- "target": "kprepublic/jj4x4"
- },
- "jj50": {
- "target": "kprepublic/jj50/rev1"
- },
- "jm60": {
- "target": "kbdfans/jm60"
- },
- "jones": {
- "target": "jones/v03_1"
- },
- "kamigakushi": {
- "target": "jaykeeb/kamigakushi"
- },
- "katana60": {
- "target": "rominronin/katana60/rev1"
- },
- "kbdfans/kbd67mkiirgb": {
- "target": "kbdfans/kbd67/mkiirgb"
- },
- "kbdfans/kbd67/mkiirgb": {
- "target": "kbdfans/kbd67/mkiirgb/v1"
- },
- "keebio/chocopad": {
- "target": "keebio/chocopad/rev1"
- },
- "keebio/dsp40": {
- "target": "keebio/dsp40/rev1"
- },
- "keycapsss/plaid_pad": {
- "target": "keycapsss/plaid_pad/rev1"
- },
- "keyten/kt60hs_t": {
- "target": "keyten/kt60hs_t/v1"
- },
- "kira75": {
- "target": "kira/kira75"
- },
- "kira80": {
- "target": "kira/kira80"
- },
- "kudox": {
- "target": "kumaokobo/kudox/rev1"
- },
- "kudox/columner": {
- "target": "kumaokobo/kudox/columner"
- },
- "kudox/rev1": {
- "target": "kumaokobo/kudox/rev1"
- },
- "kudox/rev2": {
- "target": "kumaokobo/kudox/rev2"
- },
- "kudox/rev3": {
- "target": "kumaokobo/kudox/rev3"
- },
- "kudox_full": {
- "target": "kumaokobo/kudox_full/rev1"
- },
- "kudox_full/rev1": {
- "target": "kumaokobo/kudox_full/rev1"
- },
- "kudox_game": {
- "target": "kumaokobo/kudox_game/rev1"
- },
- "kudox_game/rev1": {
- "target": "kumaokobo/kudox_game/rev1"
- },
- "kudox_game/rev2": {
- "target": "kumaokobo/kudox_game/rev2"
- },
- "kyria": {
- "target": "splitkb/kyria"
- },
- "laser_ninja/pumpkin_pad": {
- "target": "laser_ninja/pumpkinpad"
- },
- "lattice60": {
- "target": "keyhive/lattice60"
- },
- "lazydesigners/the60": {
- "target": "lazydesigners/the60/rev1"
- },
- "lfkeyboards/lfk78": {
- "target": "lfkeyboards/lfk78/revj"
- },
- "lfkeyboards/smk65": {
- "target": "lfkeyboards/smk65/revb"
- },
- "ll3macorn/bongopad": {
- "target": "ll3ma/bongopad"
- },
- "m3v3van": {
- "target": "matthewdias/m3n3van"
- },
- "maartenwut/atom47/rev2": {
- "target": "evyd13/atom47/rev2"
- },
- "maartenwut/atom47/rev3": {
- "target": "evyd13/atom47/rev3"
- },
- "maartenwut/eon40": {
- "target": "evyd13/eon40"
- },
- "maartenwut/eon65": {
- "target": "evyd13/eon65"
- },
- "maartenwut/eon75": {
- "target": "evyd13/eon75"
- },
- "maartenwut/eon87": {
- "target": "evyd13/eon87"
- },
- "maartenwut/eon95": {
- "target": "evyd13/eon95"
- },
- "maartenwut/gh80_1800": {
- "target": "evyd13/gh80_1800"
- },
- "maartenwut/gh80_3700": {
- "target": "evyd13/gh80_3700"
- },
- "maartenwut/minitomic": {
- "target": "evyd13/minitomic"
- },
- "maartenwut/mx5160": {
- "target": "evyd13/mx5160"
- },
- "maartenwut/nt660": {
- "target": "evyd13/nt660"
- },
- "maartenwut/omrontkl": {
- "target": "evyd13/omrontkl"
- },
- "maartenwut/plain60": {
- "target": "evyd13/plain60"
- },
- "maartenwut/pockettype": {
- "target": "evyd13/pockettype"
- },
- "maartenwut/quackfire": {
- "target": "evyd13/quackfire"
- },
- "maartenwut/solheim68": {
- "target": "evyd13/solheim68"
- },
- "maartenwut/ta65": {
- "target": "evyd13/ta65"
- },
- "maartenwut/wasdat": {
- "target": "evyd13/wasdat"
- },
- "maartenwut/wasdat_code": {
- "target": "evyd13/wasdat_code"
- },
- "maartenwut/wonderland": {
- "target": "evyd13/wonderland"
- },
- "matchstickworks/southpad": {
- "target": "matchstickworks/southpad/rev2/"
- },
- "matrix/m12og": {
- "target": "matrix/m12og/rev1"
- },
- "mechlovin/hannah910": {
- "target": "mechlovin/hannah910/rev1"
- },
- "mechlovin/adelais/rgb_led": {
- "target": "mechlovin/adelais/rgb_led/rev1"
- },
- "mechlovin/adelais/standard_led": {
- "target": "mechlovin/adelais/standard_led/arm/rev2"
- },
- "mechlovin/delphine": {
- "target": "mechlovin/delphine/mono_led"
- },
- "mechlovin/hannah60rgb": {
- "target": "mechlovin/hannah60rgb/rev1"
- },
- "mechlovin/hannah65/mechlovin9": {
- "target": "mechlovin/mechlovin9/rev1"
- },
- "mechlovin/hex4b": {
- "target": "mechlovin/hex4b/rev1"
- },
- "melgeek/z70ultra": {
- "target": "melgeek/z70ultra/rev1"
- },
- "mechlovin/hannah65": {
- "target": "mechlovin/hannah65/rev1"
- },
- "minim": {
- "target": "matthewdias/minim"
- },
- "mnk1800s": {
- "target": "monokei/mnk1800s"
- },
- "mnk50": {
- "target": "monokei/mnk50"
- },
- "mnk75": {
- "target": "monokei/mnk75"
- },
- "model01": {
- "target": "keyboardio/model01"
- },
- "model_v": {
- "target": "matthewdias/model_v"
- },
- "m0lly": {
- "target": "tkc/m0lly"
- },
- "montsinger/rebound": {
- "target": "montsinger/rebound/rev1"
- },
- "moonlander": {
- "target": "zsa/moonlander"
- },
- "mschwingen/modelm": {
- "target": "ibm/model_m/mschwingen"
- },
- "oddball": {
- "target": "oddball/v1"
- },
- "omnikey_blackheart": {
- "target": "viktus/omnikey_bh"
- },
- "omnikey_bh": {
- "target": "viktus/omnikey_bh"
- },
- "opus": {
- "target": "keyhive/opus"
- },
- "pabile/p20": {
- "target": "pabile/p20/ver1"
- },
- "pancake/feather": {
- "target": "spaceman/pancake/rev1/feather"
- },
- "pancake/promicro": {
- "target": "spaceman/pancake/rev1/promicro"
- },
- "peiorisboards/ixora": {
- "target": "coarse/ixora"
- },
- "pico": {
- "target": "kumaokobo/pico/65keys"
- },
- "pico/65keys": {
- "target": "kumaokobo/pico/65keys"
- },
- "pico/70keys": {
- "target": "kumaokobo/pico/70keys"
- },
- "plaid": {
- "target": "dm9records/plaid"
- },
- "plain60": {
- "target": "evyd13/plain60"
- },
- "planck/ez": {
- "target": "zsa/planck_ez/base"
- },
- "planck/ez/base": {
- "target": "zsa/planck_ez/base"
- },
- "planck/ez/glow": {
- "target": "zsa/planck_ez/glow"
- },
- "ploopyco/trackball": {
- "target": "ploopyco/trackball/rev1_005"
- },
- "plywrks/ply8x": {
- "target": "plywrks/ply8x/solder"
- },
- "polilla": {
- "target": "polilla/rev1"
- },
- "primekb/prime_l": {
- "target": "primekb/prime_l/v1"
- },
- "primekb/prime_l_v2": {
- "target": "primekb/prime_l/v2"
- },
- "projectkb/alice": {
- "target": "projectkb/alice/rev1"
- },
- "rama/koyu": {
- "target": "wilba_tech/rama_works_koyu"
- },
- "rama/m6_a": {
- "target": "wilba_tech/rama_works_m6_a"
- },
- "rama/m6_b": {
- "target": "wilba_tech/rama_works_m6_b"
- },
- "rama/m10_b": {
- "target": "wilba_tech/rama_works_m10_b"
- },
- "rama/m60_a": {
- "target": "wilba_tech/rama_works_m60_a"
- },
- "rama/u80_a": {
- "target": "wilba_tech/rama_works_u80_a"
- },
- "ramonimbao/herringbone": {
- "target": "rmi_kb/herringbone/v1"
- },
- "ramonimbao/mona": {
- "target": "rmi_kb/mona/v1"
- },
- "redox_w": {
- "target": "redox/wireless"
- },
- "rgbkb/pan": {
- "target": "rgbkb/pan/rev1/32a"
- },
- "rgbkb/pan/rev1": {
- "target": "rgbkb/pan/rev1/32a"
- },
- "romac": {
- "target": "kingly_keys/romac"
- },
- "ropro": {
- "target": "kingly_keys/ropro"
- },
- "satan": {
- "target": "gh60/satan"
- },
- "skog": {
- "target": "percent/skog"
- },
- "smallice": {
- "target": "keyhive/smallice"
- },
- "southpole": {
- "target": "keyhive/southpole"
- },
- "speedo": {
- "target": "cozykeys/speedo/v2"
- },
- "staryu": {
- "target": "ktec/staryu"
- },
- "stoutgat": {
- "target": "tkw/stoutgat/v1"
- },
- "suihankey": {
- "target": "kakunpc/suihankey/split/alpha"
- },
- "ta65": {
- "target": "evyd13/ta65"
- },
- "tartan": {
- "target": "dm9records/tartan"
- },
- "tkc1800": {
- "target": "tkc/tkc1800"
- },
- "tkw/stoutgat/v2": {
- "target": "tkw/stoutgat/v2/f411"
- },
- "tokyo60": {
- "target": "tokyokeyboard/tokyo60"
- },
- "txuu": {
- "target": "matthewdias/txuu"
- },
- "underscore33": {
- "target": "tominabox1/underscore33/rev1"
- },
- "vinta": {
- "target": "coarse/vinta"
- },
- "wasdat": {
- "target": "evyd13/wasdat"
- },
- "westfoxtrot/cypher": {
- "target": "westfoxtrot/cypher/rev1"
- },
- "whale/sk": {
- "target": "whale/sk/v3"
- },
- "xd002": {
- "target": "xiudi/xd002"
- },
- "xd004": {
- "target": "xiudi/xd004/v1"
- },
- "xd60": {
- "target": "xiudi/xd60/rev2"
- },
- "xd68": {
- "target": "xiudi/xd68"
- },
- "xd75": {
- "target": "xiudi/xd75"
- },
- "xd84": {
- "target": "xiudi/xd84"
- },
- "xd84pro": {
- "target": "xiudi/xd84pro"
- },
- "xd87": {
- "target": "xiudi/xd87"
- },
- "xd96": {
- "target": "xiudi/xd96"
- },
- "xelus/dawn60": {
- "target": "xelus/dawn60/rev1"
- },
- "xelus/valor": {
- "target": "xelus/valor/rev1"
- },
- "z150_blackheart": {
- "target": "viktus/z150_bh"
- },
- "z150_bh":{
- "target": "viktus/z150_bh"
- },
- "zeal60": {
- "target": "wilba_tech/zeal60"
- },
- "zeal65": {
- "target": "wilba_tech/zeal65"
- },
- // Moved during 2022 Q1 cycle
- "6ball": {
- "target": "maple_computing/6ball"
- },
- "7skb": {
- "target": "salicylic_acid3/7skb"
- },
- "7splus": {
- "target": "salicylic_acid3/7splus"
- },
- "acr60": {
- "target": "mechkeys/acr60"
- },
- "adalyn": {
- "target": "tominabox1/adalyn"
- },
- "ajisai74": {
- "target": "salicylic_acid3/ajisai74"
- },
- "aleth42": {
- "target": "25keys/aleth42"
- },
- "alicia_cook": {
- "target": "ibnuda/alicia_cook"
- },
- "allison": {
- "target": "prototypist/allison"
- },
- "allison_numpad": {
- "target": "prototypist/allison_numpad"
- },
- "alu84": {
- "target": "mechkeys/alu84"
- },
- "angel17": {
- "target": "kakunpc/angel17"
- },
- "angel64/alpha": {
- "target": "kakunpc/angel64/alpha"
- },
- "angel64/rev1": {
- "target": "kakunpc/angel64/rev1"
- },
- "arch_36": {
- "target": "obosob/arch_36"
- },
- "bakeneko65/rev2": {
- "target": "kkatano/bakeneko65/rev2"
- },
- "bakeneko65/rev3": {
- "target": "kkatano/bakeneko65/rev3"
- },
- "barleycorn": {
- "target": "yiancardesigns/barleycorn"
- },
- "bat43/rev1": {
- "target": "dailycraft/bat43/rev1"
- },
- "bat43/rev2": {
- "target": "dailycraft/bat43/rev2"
- },
- "bigseries/1key": {
- "target": "woodkeys/bigseries/1key"
- },
- "bigseries/2key": {
- "target": "woodkeys/bigseries/2key"
- },
- "bigseries/3key": {
- "target": "woodkeys/bigseries/3key"
- },
- "bigseries/4key": {
- "target": "woodkeys/bigseries/4key"
- },
- "bkf": {
- "target": "drhigsby/bkf"
- },
- "business_card/alpha": {
- "target": "kakunpc/business_card/alpha"
- },
- "business_card/beta": {
- "target": "kakunpc/business_card/beta"
- },
- "butterstick": {
- "target": "gboards/butterstick"
- },
- "c39": {
- "target": "maple_computing/c39"
- },
- "cassette42": {
- "target": "25keys/cassette42"
- },
- "chidori": {
- "target": "kagizaraya/chidori"
- },
- "chili": {
- "target": "ydkb/chili"
- },
- "chimera_ergo": {
- "target": "glenpickle/chimera_ergo"
- },
- "chimera_ls": {
- "target": "glenpickle/chimera_ls"
- },
- "chimera_ortho": {
- "target": "glenpickle/chimera_ortho"
- },
- "chimera_ortho_plus": {
- "target": "glenpickle/chimera_ortho_plus"
- },
- "choc_taro": {
- "target": "kakunpc/choc_taro"
- },
- "choco60": {
- "target": "recompile_keys/choco60"
- },
- "christmas_tree": {
- "target": "maple_computing/christmas_tree"
- },
- "claw44/rev1": {
- "target": "dailycraft/claw44/rev1"
- },
- "cocoa40": {
- "target": "recompile_keys/cocoa40"
- },
- "comet46": {
- "target": "satt/comet46"
- },
- "cu24": {
- "target": "capsunlocked/cu24"
- },
- "cu75": {
- "target": "capsunlocked/cu75"
- },
- "cu80": {
- "target": "capsunlocked/cu80/v1"
- },
- "delilah": {
- "target": "rainkeebs/delilah"
- },
- "diverge3": {
- "target": "unikeyboard/diverge3"
- },
- "divergetm2": {
- "target": "unikeyboard/divergetm2"
- },
- "dozen0": {
- "target": "yynmt/dozen0"
- },
- "dubba175": {
- "target": "drhigsby/dubba175"
- },
- "eggman": {
- "target": "qpockets/eggman"
- },
- "enter67": {
- "target": "kezewa/enter67"
- },
- "enter80": {
- "target": "kezewa/enter80"
- },
- "ergo42": {
- "target": "biacco42/ergo42"
- },
- "ergoarrows": {
- "target": "salicylic_acid3/ergoarrows"
- },
- "ergodash/mini": {
- "target": "omkbd/ergodash/mini"
- },
- "ergodash/rev1": {
- "target": "omkbd/ergodash/rev1"
- },
- "ergodox_infinity": {
- "target": "input_club/ergodox_infinity"
- },
- "ergotaco": {
- "target": "gboards/ergotaco"
- },
- "espectro": {
- "target": "mechkeys/espectro"
- },
- "eu_isolation": {
- "target": "p3d/eu_isolation"
- },
- "felix": {
- "target": "unikeyboard/felix"
- },
- "flygone60/rev3": {
- "target": "shandoncodes/flygone60/rev3"
- },
- "four_banger": {
- "target": "bpiphany/four_banger"
- },
- "freyr": {
- "target": "hnahkb/freyr"
- },
- "geminate60": {
- "target": "weirdo/geminate60"
- },
- "gentleman65": {
- "target": "jkeys_design/gentleman65"
- },
- "georgi": {
- "target": "gboards/georgi"
- },
- "gergo": {
- "target": "gboards/gergo"
- },
- "getta25": {
- "target": "salicylic_acid3/getta25"
- },
- "gingham": {
- "target": "yiancardesigns/gingham"
- },
- "gurindam": {
- "target": "ibnuda/gurindam"
- },
- "halberd": {
- "target": "kagizaraya/halberd"
- },
- "handwired/hillside/0_1": {
- "target": "hillside/48/0_1"
- },
- "hecomi/alpha": {
- "target": "takashiski/hecomi/alpha"
- },
- "hfdkb/keyboard_sw/k83":{
- "target": "inland/kb83"
- },
- "hid_liber": {
- "target": "bpiphany/hid_liber"
- },
- "id67/default_rgb": {
- "target": "idobao/id67"
- },
- "id67/rgb": {
- "target": "idobao/id67"
- },
- "id80": {
- "target": "idobao/id80/v2/ansi"
- },
- "idobao/id80/v1/ansi": {
- "target": "idobao/id80/v2/ansi"
- },
- "idobao/id80/v1/iso": {
- "target": "idobao/id80/v2/iso"
- },
- "id87": {
- "target": "idobao/id87/v1"
- },
- "infinity60": {
- "target": "input_club/infinity60"
- },
- "ivy/rev1": {
- "target": "maple_computing/ivy/rev1"
- },
- "jisplit89": {
- "target": "salicylic_acid3/jisplit89"
- },
- "jnao": {
- "target": "maple_computing/jnao"
- },
- "just60": {
- "target": "ydkb/just60"
- },
- "k_type": {
- "target": "input_club/k_type"
- },
- "kagamidget": {
- "target": "yynmt/kagamidget"
- },
- "kelowna/rgb64": {
- "target": "weirdo/kelowna/rgb64"
- },
+ // Moved during 2022 Q1 cycle
"keychron/q0": {
"target": "keychron/q0/base"
},
@@ -1069,1303 +16,10 @@
"target": "keychron/q1v1/ansi"
}
"keychron/q4": {
- "target": "keychron/q4/ansi/v1"
- }
- "kmac": {
- "target": "kbdmania/kmac"
- }
- "kmac_pad": {
- "target": "kbdmania/kmac_pad"
- }
- "kprepublic/bm40hsrgb": {
- "target": "kprepublic/bm40hsrgb/rev1"
- },
- "kprepublic/bm65hsrgb_iso": {
- "target": "kprepublic/bm65hsrgb_iso/rev1"
- },
- "kprepublic/bm68hsrgb": {
- "target": "kprepublic/bm68hsrgb/rev1"
- },
- "late9/rev1": {
- "target": "ivndbt/late9/rev1"
- },
- "latin17rgb": {
- "target": "latincompass/latin17rgb"
- },
- "latin47ble": {
- "target": "latincompass/latin47ble"
- },
- "latin60rgb": {
- "target": "latincompass/latin60rgb"
- },
- "latin64ble": {
- "target": "latincompass/latin64ble"
- },
- "latin6rgb": {
- "target": "latincompass/latin6rgb"
- },
- "latinpad": {
- "target": "latincompass/latinpad"
- },
- "latinpadble": {
- "target": "latincompass/latinpadble"
- },
- "launchpad/rev1": {
- "target": "maple_computing/launchpad/rev1"
- },
- "lefty": {
- "target": "smoll/lefty/rev2"
- },
- "lefty/rev1": {
- "target": "smoll/lefty/rev1"
- },
- "lck75": {
- "target": "lyso1/lck75"
- },
- "le_chiffre": {
- "target": "tominabox1/le_chiffre"
- },
- "lefishe": {
- "target": "lyso1/lefishe"
- },
- "lets_split_eh/eh": {
- "target": "maple_computing/lets_split_eh"
- },
- "ls_60": {
- "target": "weirdo/ls_60"
- },
- "lpad": {
- "target": "laneware/lpad"
- },
- "lw67": {
- "target": "laneware/lw67"
- },
- "lw75": {
- "target": "laneware/lw75"
- },
- "m3n3van": {
- "target": "matthewdias/m3n3van"
- },
- "macro1": {
- "target": "laneware/macro1"
- },
- "maple_computing/lets_split_eh/eh": {
- "target": "maple_computing/lets_split_eh"
- },
- "massdrop/thekey": {
- "target": "drop/thekey/v1"
- },
- "massdrop/thekey_v2": {
- "target": "drop/thekey/v2"
- },
- "mechmini/v1": {
- "target": "mechkeys/mechmini/v1"
- },
- "mechmini/v2": {
- "target": "mechkeys/mechmini/v2"
- },
- "meira": {
- "target": "woodkeys/meira"
- },
- "meishi": {
- "target": "biacco42/meishi"
- },
- "meishi2": {
- "target": "biacco42/meishi2"
- },
- "melody96": {
- "target": "ymdk/melody96"
- },
- "miniaxe": {
- "target": "kagizaraya/miniaxe"
- },
- "minidox/rev1": {
- "target": "maple_computing/minidox/rev1"
- },
- "mino/hotswap": {
- "target": "shandoncodes/mino/hotswap"
- },
- "mino_plus/hotswap": {
- "target": "shandoncodes/mino_plus/hotswap"
- },
- "mino_plus/soldered": {
- "target": "shandoncodes/mino_plus/soldered"
- },
- "mio": {
- "target": "recompile_keys/mio"
- },
- "montex": {
- "target": "idobao/montex/v1"
- },
- "mt40": {
- "target": "mt/mt40"
- },
- "mt64rgb": {
- "target": "mt/mt64rgb"
- },
- "mt84": {
- "target": "mt/mt84"
- },
- "mt980": {
- "target": "mt/mt980"
- },
- "mt/ncr80/hotswap": {
- "target": "mt/ncr80/r2/hotswap"
- },
- "mt/ncr80/solder": {
- "target": "mt/ncr80/r2/solder"
- },
- "nafuda": {
- "target": "salicylic_acid3/nafuda"
- },
- "naiping/np64": {
- "target": "weirdo/naiping/np64"
- },
- "naiping/nphhkb": {
- "target": "weirdo/naiping/nphhkb"
- },
- "naiping/npminila": {
- "target": "weirdo/naiping/npminila"
- },
- "naked48": {
- "target": "salicylic_acid3/naked48"
- },
- "naked60": {
- "target": "salicylic_acid3/naked60"
- },
- "naked64": {
- "target": "salicylic_acid3/naked64"
- },
- "namecard2x4": {
- "target": "takashiski/namecard2x4"
- },
- "navi10": {
- "target": "keyhive/navi10"
- },
- "nebula12": {
- "target": "spaceholdings/nebula12"
- },
- "nebula68": {
- "target": "spaceholdings/nebula68"
- },
- "nebula68b": {
- "target": "spaceholdings/nebula68b"
- },
- "neopad/rev1": {
- "target": "ivndbt/neopad/rev1"
- },
- "niu_mini": {
- "target": "kbdfans/niu_mini"
- },
- "nk1": {
- "target": "novelkeys/nk1"
- },
- "nk65": {
- "target": "novelkeys/nk65"
- },
- "nk87": {
- "target": "novelkeys/nk87"
- },
- "nknl7en": {
- "target": "salicylic_acid3/nknl7en"
- },
- "nknl7jp": {
- "target": "salicylic_acid3/nknl7jp"
- },
- "nomu30": {
- "target": "recompile_keys/nomu30"
- },
- "novelpad": {
- "target": "novelkeys/novelpad"
- },
- "ogurec": {
- "target": "drhigsby/ogurec"
- },
- "otaku_split/rev0": {
- "target": "takashiski/otaku_split/rev0"
- },
- "otaku_split/rev1": {
- "target": "takashiski/otaku_split/rev1"
- },
- "owl8": {
- "target": "dailycraft/owl8"
- },
- "packrat": {
- "target": "drhigsby/packrat"
- },
- "pistachio": {
- "target": "rate/pistachio"
- },
- "pistachio_mp": {
- "target": "rate/pistachio_mp"
- },
- "pistachio_pro": {
- "target": "rate/pistachio_pro"
- },
- "plexus75": {
- "target": "checkerboards/plexus75"
- },
- "pursuit40": {
- "target": "checkerboards/pursuit40"
- },
- "pw88": {
- "target": "smoll/pw88"
- },
- "q4z": {
- "target": "p3d/q4z"
- },
- "qaz": {
- "target": "tominabox1/qaz"
- },
- "quark": {
- "target": "checkerboards/quark"
- },
- "rabbit_capture_plan": {
- "target": "kakunpc/rabbit_capture_plan"
- },
- "raindrop": {
- "target": "laneware/raindrop"
- },
- "ramonimbao/aelith": {
- "target": "rmi_kb/aelith"
- },
- "ramonimbao/chevron": {
- "target": "rmi_kb/chevron"
- },
- "ramonimbao/herringbone/pro": {
- "target": "rmi_kb/herringbone/pro"
- },
- "ramonimbao/herringbone/v1": {
- "target": "rmi_kb/herringbone/v1"
- },
- "ramonimbao/mona/v1": {
- "target": "rmi_kb/mona/v1"
- },
- "ramonimbao/mona/v1_1": {
- "target": "rmi_kb/mona/v1_1"
- },
- "ramonimbao/mona/v32a": {
- "target": "rmi_kb/mona/v32a"
- },
- "ramonimbao/squishy65": {
- "target": "rmi_kb/squishy65"
- },
- "ramonimbao/squishyfrl": {
- "target": "rmi_kb/squishyfrl"
- },
- "ramonimbao/squishytkl": {
- "target": "rmi_kb/squishytkl"
- },
- "ramonimbao/tkl_ff/v1": {
- "target": "rmi_kb/tkl_ff/v1"
- },
- "ramonimbao/tkl_ff/v2": {
- "target": "rmi_kb/tkl_ff/v2"
- },
- "ramonimbao/wete/v1": {
- "target": "rmi_kb/wete/v1"
- },
- "ramonimbao/wete/v2": {
- "target": "rmi_kb/wete/v2"
- },
- "rainkeeb": {
- "target": "rainkeebs/rainkeeb"
- },
- "reviung33": {
- "target": "reviung/reviung33"
- },
- "reviung34": {
- "target": "reviung/reviung34"
- },
- "reviung39": {
- "target": "reviung/reviung39"
- },
- "reviung41": {
- "target": "reviung/reviung41"
- },
- "reviung5": {
- "target": "reviung/reviung5"
- },
- "reviung53": {
- "target": "reviung/reviung53"
- },
- "reviung61": {
- "target": "reviung/reviung61"
- },
- "riot_pad": {
- "target": "shandoncodes/riot_pad"
- },
- "runner3680/3x6": {
- "target": "omkbd/runner3680/3x6"
- },
- "runner3680/3x7": {
- "target": "omkbd/runner3680/3x7"
- },
- "runner3680/3x8": {
- "target": "omkbd/runner3680/3x8"
- },
- "runner3680/4x6": {
- "target": "omkbd/runner3680/4x6"
- },
- "runner3680/4x7": {
- "target": "omkbd/runner3680/4x7"
- },
- "runner3680/4x8": {
- "target": "omkbd/runner3680/4x8"
- },
- "runner3680/5x6": {
- "target": "omkbd/runner3680/5x6"
- },
- "runner3680/5x6_5x8": {
- "target": "omkbd/runner3680/5x6_5x8"
- },
- "runner3680/5x7": {
- "target": "omkbd/runner3680/5x7"
- },
- "runner3680/5x8": {
- "target": "omkbd/runner3680/5x8"
- },
- "saevus/cor": {
- "target": "concreteflowers/cor"
- },
- "saevus/cor_tkl": {
- "target": "concreteflowers/cor_tkl"
- },
- "scarletbandana": {
- "target": "woodkeys/scarletbandana"
- },
- "scythe": {
- "target": "kagizaraya/scythe"
- },
- "seigaiha": {
- "target": "yiancardesigns/seigaiha"
- },
- "setta21": {
- "target": "salicylic_acid3/setta21"
- },
- "soda/mango": {
- "target": "magic_force/mf17"
- },
- "soda/pocket": {
- "target": "magic_force/mf34"
- },
- "space_space/rev1": {
- "target": "qpockets/space_space/rev1"
- },
- "space_space/rev2": {
- "target": "qpockets/space_space/rev2"
- },
- "spacey": {
- "target": "p3d/spacey"
- },
- "spiderisland/winry25tc": {
- "target": "winry/winry25tc"
- },
- "splitreus62": {
- "target": "nacly/splitreus62"
- },
- "squiggle/rev1": {
- "target": "ibnuda/squiggle/rev1"
- },
- "standaside": {
- "target": "edi/standaside"
- },
- "steal_this_keyboard": {
- "target": "obosob/steal_this_keyboard"
- },
- "stella": {
- "target": "hnahkb/stella"
- },
- "studiokestra/line_tkl": {
- "target": "studiokestra/line_friends_tkl"
- },
- "suihankey/alpha": {
- "target": "kakunpc/suihankey/alpha"
- },
- "suihankey/rev1": {
- "target": "kakunpc/suihankey/rev1"
- },
- "suihankey/split": {
- "target": "kakunpc/suihankey/split"
- },
- "synapse": {
- "target": "p3d/synapse"
- },
- "the_ruler": {
- "target": "maple_computing/the_ruler"
- },
- "thedogkeyboard": {
- "target": "kakunpc/thedogkeyboard"
- },
- "tiger910": {
- "target": "weirdo/tiger910"
- },
- "treadstone32": {
- "target": "marksard/treadstone32"
- },
- "treadstone48/rev1": {
- "target": "marksard/treadstone48/rev1"
- },
- "treadstone48/rev2": {
- "target": "marksard/treadstone48/rev2"
- },
- "tronguylabs/m122_3270": {
- "target": "ibm/model_m_122/m122_3270/teensy"
- },
- "tw40": {
- "target": "p3d/tw40"
- },
- "ua62": {
- "target": "nacly/ua62"
- },
- "underscore33/rev1": {
- "target": "tominabox1/underscore33/rev1"
- },
- "underscore33/rev2": {
- "target": "tominabox1/underscore33/rev2"
- },
- "uno": {
- "target": "keyhive/uno"
- },
- "ut472": {
- "target": "keyhive/ut472"
- },
- "vn66": {
- "target": "hnahkb/vn66"
- },
- "w1_at": {
- "target": "geonworks/w1_at"
- },
- "wallaby": {
- "target": "kkatano/wallaby"
- },
- "wanten": {
- "target": "qpockets/wanten"
- },
- "wheatfield/blocked65": {
- "target": "mt/blocked65"
- },
- "wheatfield/split75": {
- "target": "mt/split75"
- },
- "whitefox": {
- "target": "input_club/whitefox"
- },
- "wings42/rev1": {
- "target": "dailycraft/wings42/rev1"
- },
- "wings42/rev1_extkeys": {
- "target": "dailycraft/wings42/rev1_extkeys"
- },
- "wings42/rev2": {
- "target": "dailycraft/wings42/rev2"
- },
- "yasui": {
- "target": "rainkeebs/yasui"
- },
- "yd60mq": {
- "target": "ymdk/yd60mq"
- },
- "yd68": {
- "target": "ydkb/yd68"
- },
- "ymd75": {
- "target": "ymdk/ymd75/rev1"
- },
- "ymd96": {
- "target": "ymdk/ymd96"
- },
- "ymdk/id75": {
- "target": "ymdk/id75/f103"
- },
- "ymdk_np21": {
- "target": "ymdk/np21"
- },
- "yugo_m/model_m_101": {
- "target": "ibm/model_m/yugo_m"
- },
- "yurei": {
- "target": "kkatano/yurei"
- },
- "z12": {
- "target": "zigotica/z12"
- },
- "z34": {
- "target": "zigotica/z34"
- },
- "zinc": {
- "target": "25keys/zinc"
- },
- "zinc/rev1": {
- "target": "25keys/zinc/rev1"
- },
- "zinc/reva": {
- "target": "25keys/zinc/reva"
- },
+ "target": "keychron/q4/ansi/v1"
+ }
// Moved during 2023 Q4 cycle
- "ymdk/melody96": {
- "target": "ymdk/melody96/soldered"
- },
// Moved during 2024 Q2 cycle
- "kprepublic/jj40": {
- "target": "kprepublic/jj40/rev1"
- },
- "kprepublic/jj50": {
- "target": "kprepublic/jj50/rev1"
- },
- "dnworks/9973": {
- "target": "dnworks/tkl87"
- },
// DEFAULT_FOLDER removed during 2025 Q1 cycle
- "0_sixty": {
- "target": "0_sixty/base"
- },
- "0xcb/splaytoraid": {
- "target": "0xcb/splaytoraid/rp2040_ce"
- },
- "1upkeyboards/pi40": {
- "target": "1upkeyboards/pi40/mit_v1_0"
- },
- "1upkeyboards/pi50": {
- "target": "1upkeyboards/pi50/grid"
- },
- "1upkeyboards/sweet16": {
- "target": "1upkeyboards/sweet16/v1"
- },
- "25keys/aleth42": {
- "target": "25keys/aleth42/rev1"
- },
- "25keys/zinc": {
- "target": "25keys/zinc/rev1"
- },
- "40percentclub/i75": {
- "target": "40percentclub/i75/promicro"
- },
- "40percentclub/polyandry": {
- "target": "40percentclub/polyandry/promicro"
- },
- "8pack": {
- "target": "8pack/rev12"
- },
- "adkb96": {
- "target": "adkb96/rev1"
- },
- "adm42": {
- "target": "adm42/rev4"
- },
- "aeboards/satellite": {
- "target": "aeboards/satellite/rev1"
- },
- "atreus": {
- "target": "atreus/astar"
- },
- "atreyu": {
- "target": "atreyu/rev1"
- },
- "biacco42/ergo42": {
- "target": "biacco42/ergo42/rev1"
- },
- "binepad/bn009": {
- "target": "binepad/bn009/r2"
- },
- "binepad/bnr1": {
- "target": "binepad/bnr1/v2"
- },
- "boston_meetup": {
- "target": "boston_meetup/2019"
- },
- "bpiphany/frosty_flake": {
- "target": "bpiphany/frosty_flake/20140521"
- },
- "buzzard": {
- "target": "buzzard/rev1"
- },
- "cannonkeys/db60": {
- "target": "cannonkeys/db60/rev2"
- },
- "clickety_split/leeloo": {
- "target": "clickety_split/leeloo/rev3"
- },
- "converter/palm_usb": {
- "target": "converter/palm_usb/stowaway"
- },
- "crkbd": {
- "target": "crkbd/rev1"
- },
- "dailycraft/bat43": {
- "target": "dailycraft/bat43/rev2"
- },
- "dailycraft/claw44": {
- "target": "dailycraft/claw44/rev1"
- },
- "dailycraft/sandbox": {
- "target": "dailycraft/sandbox/rev2"
- },
- "dailycraft/wings42": {
- "target": "dailycraft/wings42/rev2"
- },
- "delikeeb/vanana": {
- "target": "delikeeb/vanana/rev2"
- },
- "delikeeb/waaffle/rev3": {
- "target": "delikeeb/waaffle/rev3/pro_micro"
- },
- "deltasplit75": {
- "target": "deltasplit75/v2"
- },
- "drhigsby/ogurec": {
- "target": "drhigsby/ogurec/left_pm"
- },
- "duck/octagon": {
- "target": "duck/octagon/v2"
- },
- "duck/orion": {
- "target": "duck/orion/v3"
- },
- "ducky/one2mini": {
- "target": "ducky/one2mini/1861st"
- },
- "ducky/one2sf": {
- "target": "ducky/one2sf/1967st"
- },
- "dumbpad": {
- "target": "dumbpad/v0x"
- },
- "durgod/galaxy": {
- "target": "durgod/dgk6x/galaxy"
- },
- "durgod/venus": {
- "target": "durgod/dgk6x/venus"
- },
- "dztech/tofu/ii": {
- "target": "dztech/tofu/ii/v1"
- },
- "dztech/tofu/jr": {
- "target": "dztech/tofu/jr/v1"
- },
- "eco": {
- "target": "eco/rev2"
- },
- "ergoslab": {
- "target": "ergoslab/rev1"
- },
- "ergotravel": {
- "target": "ergotravel/rev1"
- },
- "evyd13/atom47": {
- "target": "evyd13/atom47/rev4"
- },
- "fortitude60": {
- "target": "fortitude60/rev1"
- },
- "fruitykeeb/fruitbar/r1": {
- "target": "fruitykeeb/fruitbar/r1/promicro"
- },
- "ghs/jem": {
- "target": "ghs/jem/soldered"
- },
- "hadron": {
- "target": "hadron/ver2"
- },
- "handwired/bento": {
- "target": "handwired/bento/rev1"
- },
- "handwired/dactyl_manuform/6x6": {
- "target": "handwired/dactyl_manuform/6x6/promicro"
- },
- "handwired/meck_tkl": {
- "target": "handwired/meck_tkl/blackpill_f401"
- },
- "handwired/ms_sculpt_mobile": {
- "target": "handwired/ms_sculpt_mobile/teensy2pp"
- },
- "handwired/onekey": {
- "target": "handwired/onekey/promicro"
- },
- "handwired/pill60": {
- "target": "handwired/pill60/bluepill"
- },
- "handwired/postageboard": {
- "target": "handwired/postageboard/mini"
- },
- "handwired/qc60": {
- "target": "handwired/qc60/proto"
- },
- "handwired/sono1": {
- "target": "handwired/sono1/t2pp"
- },
- "handwired/splittest": {
- "target": "handwired/splittest/promicro"
- },
- "handwired/stef9998/split_5x7": {
- "target": "handwired/stef9998/split_5x7/rev1"
- },
- "handwired/symmetric70_proto/promicro": {
- "target": "handwired/symmetric70_proto/promicro/base"
- },
- "handwired/symmetric70_proto/proton_c": {
- "target": "handwired/symmetric70_proto/proton_c/base"
- },
- "handwired/unk": {
- "target": "handwired/unk/rev1"
- },
- "handwired/xealous": {
- "target": "handwired/xealous/rev1"
- },
- "hillside/46": {
- "target": "hillside/46/0_1"
- },
- "hhkb/ansi": {
- "target": "hhkb/ansi/32u4"
- },
- "hillside/48": {
- "target": "hillside/48/0_1"
- },
- "hillside/52": {
- "target": "hillside/52/0_1"
- },
- "ibnuda/squiggle": {
- "target": "ibnuda/squiggle/rev1"
- },
- "idobao/id80/v1": {
- "target": "idobao/id80/v2/ansi"
- },
- "idobao/id80/v3": {
- "target": "idobao/id80/v3/ansi"
- },
- "inett_studio/sq80": {
- "target": "inett_studio/sq80/hotswap_layout_i"
- },
- "input_club/infinity60": {
- "target": "input_club/infinity60/led"
- },
- "jacky_studio/bear_65": {
- "target": "jacky_studio/bear_65/rev1"
- },
- "jacky_studio/piggy60/rev1": {
- "target": "jacky_studio/piggy60/rev1/solder"
- },
- "jadookb/jkb65": {
- "target": "jadookb/jkb65/r1"
- },
- "jian": {
- "target": "jian/rev2"
- },
- "jiran": {
- "target": "jiran/rev1"
- },
- "jorne": {
- "target": "jorne/rev1"
- },
- "junco": {
- "target": "junco/rev1"
- },
- "kakunpc/angel17": {
- "target": "kakunpc/angel17/rev1"
- },
- "kakunpc/angel64": {
- "target": "kakunpc/angel64/rev1"
- },
- "kakunpc/business_card": {
- "target": "kakunpc/business_card/beta"
- },
- "kakunpc/suihankey": {
- "target": "kakunpc/suihankey/rev1"
- },
- "kakunpc/suihankey/split": {
- "target": "kakunpc/suihankey/split/rev1"
- },
- "kapcave/paladinpad": {
- "target": "kapcave/paladinpad/rev2"
- },
- "kapl": {
- "target": "kapl/rev1"
- },
- "kbdfans/d45": {
- "target": "kbdfans/d45/v2"
- },
- "kbdfans/kbd75": {
- "target": "kbdfans/kbd75/rev1"
- },
- "keebio/bdn9": {
- "target": "keebio/bdn9/rev1"
- },
- "keebio/convolution": {
- "target": "keebio/convolution/rev1"
- },
- "keebio/foldkb": {
- "target": "keebio/foldkb/rev1"
- },
- "keebio/kbo5000": {
- "target": "keebio/kbo5000/rev1"
- },
- "keebio/levinson": {
- "target": "keebio/levinson/rev2"
- },
- "keebio/quefrency": {
- "target": "keebio/quefrency/rev1"
- },
- "keebio/rorschach": {
- "target": "keebio/rorschach/rev1"
- },
- "keebio/sinc": {
- "target": "keebio/sinc/rev1"
- },
- "keebio/viterbi": {
- "target": "keebio/viterbi/rev2"
- },
- "keycapsss/kimiko": {
- "target": "keycapsss/kimiko/rev2"
- },
- "keyhive/navi10": {
- "target": "keyhive/navi10/rev3"
- },
- "keyhive/uno": {
- "target": "keyhive/uno/rev1"
- },
- "kin80": {
- "target": "kin80/blackpill401"
- },
- "kprepublic/cstc40/daughterboard": {
- "target": "kprepublic/cstc40/rev1"
- },
- "kprepublic/cstc40/single_pcb": {
- "target": "kprepublic/cstc40/rev2"
- },
- "kumaokobo/kudox_full": {
- "target": "kumaokobo/kudox_full/rev1"
- },
- "kumaokobo/kudox_game": {
- "target": "kumaokobo/kudox_game/rev2"
- },
- "kumaokobo/kudox": {
- "target": "kumaokobo/kudox/rev3"
- },
- "kumaokobo/pico": {
- "target": "kumaokobo/pico/65keys"
- },
- "lazydesigners/dimple": {
- "target": "lazydesigners/dimple/staggered/rev1"
- },
- "lets_split": {
- "target": "lets_split/rev2"
- },
- "lfkeyboards/lfk87": {
- "target": "lfkeyboards/lfk78/revc"
- },
- "lily58": {
- "target": "lily58/rev1"
- },
- "lime": {
- "target": "lime/rev1"
- },
- "maple_computing/christmas_tree": {
- "target": "maple_computing/christmas_tree/v2017"
- },
- "maple_computing/ivy": {
- "target": "maple_computing/ivy/rev1"
- },
- "maple_computing/launchpad": {
- "target": "maple_computing/launchpad/rev1"
- },
- "maple_computing/minidox": {
- "target": "maple_computing/minidox/rev1"
- },
- "mariorion_v25": {
- "target": "mariorion_v25/prod"
- },
- "marksard/rhymestone": {
- "target": "marksard/rhymestone/rev1"
- },
- "marksard/treadstone32": {
- "target": "marksard/treadstone32/rev1"
- },
- "marksard/treadstone48": {
- "target": "marksard/treadstone48/rev1"
- },
- "maxipad": {
- "target": "maxipad/promicro"
- },
- "mechkeys/mechmini": {
- "target": "mechkeys/mechmini/v2"
- },
- "mechllama/g35": {
- "target": "mechllama/g35/v2"
- },
- "mechlovin/adelais": {
- "target": "mechlovin/adelais/standard_led/arm/rev2"
- },
- "mechlovin/adelais/standard_led/arm/rev4": {
- "target": "mechlovin/adelais/standard_led/arm/rev4/stm32f303"
- },
- "mechlovin/adelais/standard_led/arm": {
- "target": "mechlovin/adelais/standard_led/arm/rev2"
- },
- "mechlovin/adelais/standard_led/avr": {
- "target": "mechlovin/adelais/standard_led/avr/rev1"
- },
- "mechlovin/hannah65/rev1": {
- "target": "mechlovin/hannah65/rev1/haus"
- },
- "mechlovin/infinity87/rev1": {
- "target": "mechlovin/infinity87/rev1/standard"
- },
- "mechlovin/infinity87": {
- "target": "mechlovin/infinity87/rgb_rev1"
- },
- "mechlovin/mechlovin9": {
- "target": "mechlovin/mechlovin9/rev1"
- },
- "mechlovin/olly/jf": {
- "target": "mechlovin/olly/jf/rev1"
- },
- "mechlovin/zed1800": {
- "target": "mechlovin/zed1800/saber"
- },
- "mechlovin/zed65/no_backlight": {
- "target": "mechlovin/zed65/no_backlight/wearhaus66"
- },
- "mechlovin/zed65": {
- "target": "mechlovin/zed65/no_backlight/wearhaus66"
- },
- "mechwild/bde": {
- "target": "mechwild/bde/rev2"
- },
- "mechwild/mokulua": {
- "target": "mechwild/mokulua/standard"
- },
- "mechwild/obe/f401": {
- "target": "mechwild/obe/f401/base"
- },
- "mechwild/obe/f411": {
- "target": "mechwild/obe/f411/base"
- },
- "mechwild/obe": {
- "target": "mechwild/obe/f401/base"
- },
- "mechwild/waka60/f401": {
- "target": "mechwild/waka60/f401/base"
- },
- "mechwild/waka60/f411": {
- "target": "mechwild/waka60/f411/base"
- },
- "mechwild/waka60": {
- "target": "mechwild/waka60/f401/base"
- },
- "murcielago": {
- "target": "murcielago/rev1"
- },
- "nullbitsco/scramble": {
- "target": "nullbitsco/scramble/v2"
- },
- "omkbd/ergodash": {
- "target": "omkbd/ergodash/rev1"
- },
- "omkbd/runner3680": {
- "target": "omkbd/runner3680/5x8"
- },
- "orthodox": {
- "target": "orthodox/rev3"
- },
- "peej/rosaline": {
- "target": "peej/rosaline/staggered"
- },
- "peej/tripel": {
- "target": "peej/tripel/left"
- },
- "peranekofactory/tone": {
- "target": "peranekofactory/tone/rev2"
- },
- "phase_studio/titan65": {
- "target": "phase_studio/titan65/hotswap"
- },
- "pica40": {
- "target": "pica40/rev2"
- },
- "pinky": {
- "target": "pinky/3"
- },
- "ploopyco/madromys": {
- "target": "ploopyco/madromys/rev1_001"
- },
- "ploopyco/trackball_mini": {
- "target": "ploopyco/trackball_mini/rev1_001"
- },
- "ploopyco/trackball_nano": {
- "target": "ploopyco/trackball_nano/rev1_001"
- },
- "ploopyco/trackball_thumb": {
- "target": "ploopyco/trackball_thumb/rev1_001"
- },
- "primekb/meridian": {
- "target": "primekb/meridian/ktr1010"
- },
- "primekb/prime_e": {
- "target": "primekb/prime_e/std"
- },
- "program_yoink": {
- "target": "program_yoink/staggered"
- },
- "projectcain/vault35": {
- "target": "projectcain/vault35/atmega32u4"
- },
- "qpockets/space_space": {
- "target": "qpockets/space_space/rev2"
- },
- "qwertyydox": {
- "target": "qwertyydox/rev1"
- },
- "rate/pistachio": {
- "target": "rate/pistachio/rev2"
- },
- "recompile_keys/choco60": {
- "target": "recompile_keys/choco60/rev1"
- },
- "recompile_keys/nomu30": {
- "target": "recompile_keys/nomu30/rev1"
- },
- "redox/rev1": {
- "target": "redox/rev1/base"
- },
- "rgbkb/mun": {
- "target": "rgbkb/mun/rev1"
- },
- "rgbkb/sol3": {
- "target": "rgbkb/sol3/rev1"
- },
- "rgbkb/sol": {
- "target": "rgbkb/sol/rev2"
- },
- "rgbkb/zen": {
- "target": "rgbkb/zen/rev2"
- },
- "rgbkb/zygomorph": {
- "target": "rgbkb/zygomorph/rev1"
- },
- "rmi_kb/herringbone": {
- "target": "rmi_kb/herringbone/v1"
- },
- "rmi_kb/mona": {
- "target": "rmi_kb/mona/v1_1"
- },
- "rmi_kb/tkl_ff": {
- "target": "rmi_kb/tkl_ff/v1"
- },
- "rmi_kb/wete": {
- "target": "rmi_kb/wete/v2"
- },
- "rookiebwoy/late9": {
- "target": "ivndbt/late9/rev1"
- },
- "rookiebwoy/neopad": {
- "target": "ivndbt/neopad/rev1"
- },
- "ivndbt/late9": {
- "target": "ivndbt/late9/rev1"
- },
- "ivndbt/neopad": {
- "target": "ivndbt/neopad/rev1"
- },
- "rura66": {
- "target": "rura66/rev1"
- },
- "salicylic_acid3/7skb": {
- "target": "salicylic_acid3/7skb/rev1"
- },
- "salicylic_acid3/getta25": {
- "target": "salicylic_acid3/getta25/rev1"
- },
- "salicylic_acid3/jisplit89": {
- "target": "salicylic_acid3/jisplit89/rev1"
- },
- "salicylic_acid3/naked48": {
- "target": "salicylic_acid3/naked48/rev1"
- },
- "salicylic_acid3/naked60": {
- "target": "salicylic_acid3/naked60/rev1"
- },
- "salicylic_acid3/naked64": {
- "target": "salicylic_acid3/naked64/rev1"
- },
- "salicylic_acid3/setta21": {
- "target": "salicylic_acid3/setta21/rev1"
- },
- "sawnsprojects/okayu": {
- "target": "sawnsprojects/okayu/stm32f072"
- },
- "smoll/lefty": {
- "target": "smoll/lefty/rev2"
- },
- "sofle": {
- "target": "sofle/rev1"
- },
- "spaceholdings/nebula68b": {
- "target": "spaceholdings/nebula68b/solder"
- },
- "spacetime": {
- "target": "spacetime/rev1"
- },
- "splitkb/aurora/corne": {
- "target": "splitkb/aurora/corne/rev1"
- },
- "splitkb/aurora/helix": {
- "target": "splitkb/aurora/helix/rev1"
- },
- "splitkb/aurora/lily58": {
- "target": "splitkb/aurora/lily58/rev1"
- },
- "splitkb/aurora/sofle_v2": {
- "target": "splitkb/aurora/sofle_v2/rev1"
- },
- "splitkb/aurora/sweep": {
- "target": "splitkb/aurora/sweep/rev1"
- },
- "splitkb/kyria": {
- "target": "splitkb/kyria/rev3"
- },
- "splitkb/kyria/rev1": {
- "target": "splitkb/kyria/rev1/base"
- },
- "splitkb/kyria/rev2": {
- "target": "splitkb/kyria/rev2/base"
- },
- "splitty": {
- "target": "splitty/rev1"
- },
- "studiokestra/galatea": {
- "target": "studiokestra/galatea/rev1"
- },
- "takashiski/hecomi": {
- "target": "takashiski/hecomi/alpha"
- },
- "takashiski/namecard2x4": {
- "target": "takashiski/namecard2x4/rev2"
- },
- "teleport/native": {
- "target": "teleport/native/iso"
- },
- "themadnoodle/noodlepad": {
- "target": "themadnoodle/noodlepad/v1"
- },
- "tkw/grandiceps": {
- "target": "tkw/grandiceps/rev1"
- },
- "tominabox1/le_chiffre": {
- "target": "tominabox1/le_chiffre/rev1"
- },
- "tominabox1/littlefoot_lx": {
- "target": "tominabox1/littlefoot_lx/rev1"
- },
- "tominabox1/underscore33": {
- "target": "tominabox1/underscore33/rev1"
- },
- "trnthsn/e8ghty": {
- "target": "trnthsn/e8ghty/stm32f103"
- },
- "trnthsn/s6xty5neor2": {
- "target": "trnthsn/s6xty5neor2/stm32f103"
- },
- "tweetydabird/lotus58/elite_c": {
- "target": "tweetydabird/lotus58"
- },
- "tweetydabird/lotus58/nanoboot": {
- "target": "tweetydabird/lotus58"
- },
- "tweetydabird/lotus58/promicro": {
- "target": "tweetydabird/lotus58"
- },
- "tweetydabird/lotus58/rp2040_ce": {
- "target": "tweetydabird/lotus58"
- },
- "unison": {
- "target": "unison/v04"
- },
- "uzu42": {
- "target": "uzu42/rev1"
- },
- "vitamins_included": {
- "target": "vitamins_included/rev2"
- },
- "westm/westm68": {
- "target": "westm/westm68/rev2"
- },
- "westm/westm9": {
- "target": "westm/westm9/rev2"
- },
- "woodkeys/meira": {
- "target": "woodkeys/meira/promicro"
- },
- "work_louder/loop": {
- "target": "work_louder/loop/rev3"
- },
- "work_louder/work_board": {
- "target": "work_louder/work_board/rev3"
- },
- "yanghu/unicorne": {
- "target": "yanghu/unicorne/f411"
- },
- "yosino58": {
- "target": "yosino58/rev1"
- },
- "ymdk/yd60mq": {
- "target": "ymdk/yd60mq/12led"
- },
- "yushakobo/navpad/10": {
- "target": "yushakobo/navpad/10/rev1"
- },
- "yynmt/acperience12": {
- "target": "yynmt/acperience12/rev1"
- },
- "zsa/planck_ez": {
- "target": "zsa/planck_ez/base"
- },
// DEFAULT_FOLDER removed during 2025 Q3 cycle
- "cannonkeys/satisfaction75": {
- "target": "cannonkeys/satisfaction75/rev1"
- },
- "converter/adb_usb": {
- "target": "converter/adb_usb/rev1"
- },
- "converter/sun_usb": {
- "target": "converter/sun_usb/type5"
- },
- "converter/usb_usb": {
- "target": "converter/usb_usb/hasu"
- },
- "durgod/dgk6x": {
- "target": "durgod/dgk6x/hades_ansi"
- },
- "ergodox_ez": {
- "target": "ergodox_ez/base"
- },
- "ferris/0_2": {
- "target": "ferris/0_2/base"
- },
- "handwired/dygma/raise": {
- "target": "handwired/dygma/raise/ansi"
- },
- "helix/rev3_4rows": {
- "target": "helix/rev3"
- },
- "helix/rev3_5rows": {
- "target": "helix/rev3"
- },
- "ibm/model_m/mschwingen": {
- "target": "ibm/model_m/mschwingen/led_wired"
- },
- "mechwild/sugarglider": {
- "target": "mechwild/sugarglider/wide_oled/f401"
- },
- "mechwild/sugarglider/wide_oled": {
- "target": "mechwild/sugarglider/wide_oled/f401"
- },
- "novelkeys/nk65": {
- "target": "novelkeys/nk65/v1"
- },
- "novelkeys/nk65/base": {
- "target": "novelkeys/nk65/v1"
- },
- "sirius/uni660/rev2": {
- "target": "sirius/uni660/rev2/ansi"
- }
}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 000000000000..76645580fb25
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,27 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1780930886,
+ "narHash": "sha256-rppURzHviaQN131F+nLiLdGfcb0uCd9gGP0E5+iw9MI=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "8c3cede7ddc26bd659d2d383b5610efbd2c7a16e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 000000000000..7bdf274b3110
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,49 @@
+{
+ description = "QMK Firmware build environment for Keychron K2 HE";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+ };
+
+ outputs = { self, nixpkgs }:
+ let
+ system = "x86_64-linux";
+ pkgs = import nixpkgs { inherit system; };
+
+ pythonEnv = pkgs.python3.withPackages (ps: with ps; [
+ appdirs
+ argcomplete
+ colorama
+ dotty-dict
+ hid
+ hjson
+ jsonschema
+ milc
+ pygments
+ pyserial
+ pyusb
+ pillow
+ ]);
+ in
+ {
+ devShells.${system}.default = pkgs.mkShell {
+ name = "qmk-firmware";
+
+ buildInputs = with pkgs; [
+ clang-tools
+ dfu-programmer
+ dfu-util
+ diffutils
+ git
+ gnumake
+ pythonEnv
+ qmk
+ gcc-arm-embedded
+ ];
+
+ shellHook = ''
+ unset NIX_CFLAGS_COMPILE_FOR_TARGET
+ '';
+ };
+ };
+}
diff --git a/keyboards/keychron/common/keychron_common.mk b/keyboards/keychron/common/keychron_common.mk
index aa8801f0cd2d..c8ad0eee31b6 100644
--- a/keyboards/keychron/common/keychron_common.mk
+++ b/keyboards/keychron/common/keychron_common.mk
@@ -31,6 +31,12 @@ include $(KEYCHRON_COMMON_DIR)/rgb/rgb.mk
endif
endif
+ifeq ($(strip $(OPENRGB_ENABLE)), yes)
+ OPT_DEFS += -DOPENRGB_ENABLE
+ SRC += $(KEYCHRON_COMMON_DIR)/openrgb.c
+ RAW_ENABLE = yes
+endif
+
ifeq ($(strip $(USB_REPORT_INTERVAL_ENABLE)), yes)
OPT_DEFS += -DUSB_REPORT_INTERVAL_ENABLE
SRC += $(KEYCHRON_COMMON_DIR)/usb_report_rate.c
diff --git a/keyboards/keychron/common/keychron_raw_hid.c b/keyboards/keychron/common/keychron_raw_hid.c
index 194d97fede38..4b6d2460c896 100644
--- a/keyboards/keychron/common/keychron_raw_hid.c
+++ b/keyboards/keychron/common/keychron_raw_hid.c
@@ -26,6 +26,9 @@
#ifdef ANANLOG_MATRIX
# include "analog_matrix.h"
#endif
+#ifdef OPENRGB_ENABLE
+# include "openrgb.h"
+#endif
#ifdef DYNAMIC_DEBOUNCE_ENABLE
# include "keychron_debounce.h"
#endif
@@ -263,6 +266,11 @@ bool kc_raw_hid_rx(uint8_t src, uint8_t *data, uint8_t length) {
# endif
default:
+# ifdef OPENRGB_ENABLE
+ if (src == RAW_HID_SRC_USB && openrgb_command_handler(data, length)) {
+ return true;
+ }
+# endif
return false;
}
diff --git a/keyboards/keychron/common/openrgb.c b/keyboards/keychron/common/openrgb.c
new file mode 100644
index 000000000000..1d6733c1ec16
--- /dev/null
+++ b/keyboards/keychron/common/openrgb.c
@@ -0,0 +1,431 @@
+/* Copyright 2020 Kasper
+ * Copyright 2025 Keychron K2 HE OpenRGB patch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "quantum.h"
+#include "openrgb.h"
+#include "raw_hid.h"
+#include "usb_descriptor.h"
+#include "version.h"
+#include "keymap_introspection.h"
+#include "keychron_raw_hid.h"
+#include "string.h"
+
+#if !defined(OPENRGB_DIRECT_MODE_STARTUP_RED)
+# define OPENRGB_DIRECT_MODE_STARTUP_RED 0
+#endif
+
+#if !defined(OPENRGB_DIRECT_MODE_STARTUP_GREEN)
+# define OPENRGB_DIRECT_MODE_STARTUP_GREEN 0
+#endif
+
+#if !defined(OPENRGB_DIRECT_MODE_STARTUP_BLUE)
+# define OPENRGB_DIRECT_MODE_STARTUP_BLUE 0
+#endif
+
+RGB g_openrgb_direct_mode_colors[RGB_MATRIX_LED_COUNT] = {[0 ... RGB_MATRIX_LED_COUNT - 1] = {OPENRGB_DIRECT_MODE_STARTUP_RED, OPENRGB_DIRECT_MODE_STARTUP_GREEN, OPENRGB_DIRECT_MODE_STARTUP_BLUE}};
+
+typedef struct {
+ uint8_t openrgb_id;
+ uint8_t qmk_mode;
+} openrgb_mode_map_t;
+
+/* Maps OpenRGB modes to QMK rgb_matrix effects, in OpenRGB's own mode order
+ * (the if-order of OpenRGB's RGBController_QMKOpenRGBRevD.cpp), with OpenRGB
+ * Direct last. OpenRGB assigns each enabled mode a sequential value in this
+ * order and sends that value back in SET_MODE, so the index into this table is
+ * exactly the value OpenRGB uses -- decoupling the protocol from QMK's own
+ * effect numbering (which differs and contains effects OpenRGB doesn't know).
+ * Each entry is guarded by the same ENABLE_RGB_MATRIX_* flag that gates the
+ * effect, so the table reduces to precisely the effects compiled in. */
+static const openrgb_mode_map_t openrgb_mode_map[] = {
+ {OPENRGB_MODE_SOLID_COLOR, RGB_MATRIX_SOLID_COLOR},
+#ifdef ENABLE_RGB_MATRIX_ALPHAS_MODS
+ {OPENRGB_MODE_ALPHA_MOD, RGB_MATRIX_ALPHAS_MODS},
+#endif
+#ifdef ENABLE_RGB_MATRIX_GRADIENT_UP_DOWN
+ {OPENRGB_MODE_GRADIENT_UP_DOWN, RGB_MATRIX_GRADIENT_UP_DOWN},
+#endif
+#ifdef ENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT
+ {OPENRGB_MODE_GRADIENT_LEFT_RIGHT, RGB_MATRIX_GRADIENT_LEFT_RIGHT},
+#endif
+#ifdef ENABLE_RGB_MATRIX_BREATHING
+ {OPENRGB_MODE_BREATHING, RGB_MATRIX_BREATHING},
+#endif
+#ifdef ENABLE_RGB_MATRIX_BAND_SAT
+ {OPENRGB_MODE_BAND_SAT, RGB_MATRIX_BAND_SAT},
+#endif
+#ifdef ENABLE_RGB_MATRIX_BAND_VAL
+ {OPENRGB_MODE_BAND_VAL, RGB_MATRIX_BAND_VAL},
+#endif
+#ifdef ENABLE_RGB_MATRIX_BAND_PINWHEEL_SAT
+ {OPENRGB_MODE_BAND_PINWHEEL_SAT, RGB_MATRIX_BAND_PINWHEEL_SAT},
+#endif
+#ifdef ENABLE_RGB_MATRIX_BAND_PINWHEEL_VAL
+ {OPENRGB_MODE_BAND_PINWHEEL_VAL, RGB_MATRIX_BAND_PINWHEEL_VAL},
+#endif
+#ifdef ENABLE_RGB_MATRIX_BAND_SPIRAL_SAT
+ {OPENRGB_MODE_BAND_SPIRAL_SAT, RGB_MATRIX_BAND_SPIRAL_SAT},
+#endif
+#ifdef ENABLE_RGB_MATRIX_BAND_SPIRAL_VAL
+ {OPENRGB_MODE_BAND_SPIRAL_VAL, RGB_MATRIX_BAND_SPIRAL_VAL},
+#endif
+#ifdef ENABLE_RGB_MATRIX_CYCLE_ALL
+ {OPENRGB_MODE_CYCLE_ALL, RGB_MATRIX_CYCLE_ALL},
+#endif
+#ifdef ENABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT
+ {OPENRGB_MODE_CYCLE_LEFT_RIGHT, RGB_MATRIX_CYCLE_LEFT_RIGHT},
+#endif
+#ifdef ENABLE_RGB_MATRIX_CYCLE_UP_DOWN
+ {OPENRGB_MODE_CYCLE_UP_DOWN, RGB_MATRIX_CYCLE_UP_DOWN},
+#endif
+#ifdef ENABLE_RGB_MATRIX_CYCLE_OUT_IN
+ {OPENRGB_MODE_CYCLE_OUT_IN, RGB_MATRIX_CYCLE_OUT_IN},
+#endif
+#ifdef ENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL
+ {OPENRGB_MODE_CYCLE_OUT_IN_DUAL, RGB_MATRIX_CYCLE_OUT_IN_DUAL},
+#endif
+#ifdef ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
+ {OPENRGB_MODE_RAINBOW_MOVING_CHEVRON, RGB_MATRIX_RAINBOW_MOVING_CHEVRON},
+#endif
+#ifdef ENABLE_RGB_MATRIX_CYCLE_PINWHEEL
+ {OPENRGB_MODE_CYCLE_PINWHEEL, RGB_MATRIX_CYCLE_PINWHEEL},
+#endif
+#ifdef ENABLE_RGB_MATRIX_CYCLE_SPIRAL
+ {OPENRGB_MODE_CYCLE_SPIRAL, RGB_MATRIX_CYCLE_SPIRAL},
+#endif
+#ifdef ENABLE_RGB_MATRIX_DUAL_BEACON
+ {OPENRGB_MODE_DUAL_BEACON, RGB_MATRIX_DUAL_BEACON},
+#endif
+#ifdef ENABLE_RGB_MATRIX_RAINBOW_BEACON
+ {OPENRGB_MODE_RAINBOW_BEACON, RGB_MATRIX_RAINBOW_BEACON},
+#endif
+#ifdef ENABLE_RGB_MATRIX_RAINBOW_PINWHEELS
+ {OPENRGB_MODE_RAINBOW_PINWHEELS, RGB_MATRIX_RAINBOW_PINWHEELS},
+#endif
+#ifdef ENABLE_RGB_MATRIX_RAINDROPS
+ {OPENRGB_MODE_RAINDROPS, RGB_MATRIX_RAINDROPS},
+#endif
+#ifdef ENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS
+ {OPENRGB_MODE_JELLYBEAN_RAINDROPS, RGB_MATRIX_JELLYBEAN_RAINDROPS},
+#endif
+#ifdef ENABLE_RGB_MATRIX_HUE_BREATHING
+ {OPENRGB_MODE_HUE_BREATHING, RGB_MATRIX_HUE_BREATHING},
+#endif
+#ifdef ENABLE_RGB_MATRIX_HUE_PENDULUM
+ {OPENRGB_MODE_HUE_PENDULUM, RGB_MATRIX_HUE_PENDULUM},
+#endif
+#ifdef ENABLE_RGB_MATRIX_HUE_WAVE
+ {OPENRGB_MODE_HUE_WAVE, RGB_MATRIX_HUE_WAVE},
+#endif
+#ifdef ENABLE_RGB_MATRIX_TYPING_HEATMAP
+ {OPENRGB_MODE_TYPING_HEATMAP, RGB_MATRIX_TYPING_HEATMAP},
+#endif
+#ifdef ENABLE_RGB_MATRIX_DIGITAL_RAIN
+ {OPENRGB_MODE_DIGITAL_RAIN, RGB_MATRIX_DIGITAL_RAIN},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
+ {OPENRGB_MODE_SOLID_REACTIVE_SIMPLE, RGB_MATRIX_SOLID_REACTIVE_SIMPLE},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE
+ {OPENRGB_MODE_SOLID_REACTIVE, RGB_MATRIX_SOLID_REACTIVE},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_WIDE
+ {OPENRGB_MODE_SOLID_REACTIVE_WIDE, RGB_MATRIX_SOLID_REACTIVE_WIDE},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE
+ {OPENRGB_MODE_SOLID_REACTIVE_MULTIWIDE, RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_CROSS
+ {OPENRGB_MODE_SOLID_REACTIVE_CROSS, RGB_MATRIX_SOLID_REACTIVE_CROSS},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTICROSS
+ {OPENRGB_MODE_SOLID_REACTIVE_MULTICROSS, RGB_MATRIX_SOLID_REACTIVE_MULTICROSS},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_NEXUS
+ {OPENRGB_MODE_SOLID_REACTIVE_NEXUS, RGB_MATRIX_SOLID_REACTIVE_NEXUS},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS
+ {OPENRGB_MODE_SOLID_REACTIVE_MULTINEXUS, RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SPLASH
+ {OPENRGB_MODE_SPLASH, RGB_MATRIX_SPLASH},
+#endif
+#ifdef ENABLE_RGB_MATRIX_MULTISPLASH
+ {OPENRGB_MODE_MULTISPLASH, RGB_MATRIX_MULTISPLASH},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_SPLASH
+ {OPENRGB_MODE_SOLID_SPLASH, RGB_MATRIX_SOLID_SPLASH},
+#endif
+#ifdef ENABLE_RGB_MATRIX_SOLID_MULTISPLASH
+ {OPENRGB_MODE_SOLID_MULTISPLASH, RGB_MATRIX_SOLID_MULTISPLASH},
+#endif
+ {OPENRGB_MODE_OPENRGB_DIRECT, RGB_MATRIX_OPENRGB_DIRECT},
+};
+
+#define OPENRGB_MODE_COUNT (sizeof(openrgb_mode_map) / sizeof(openrgb_mode_map[0]))
+
+static uint8_t raw_hid_buffer[RAW_EPSIZE];
+
+static void openrgb_get_protocol_version(void) {
+ raw_hid_buffer[0] = OPENRGB_GET_PROTOCOL_VERSION;
+ raw_hid_buffer[1] = OPENRGB_PROTOCOL_VERSION;
+}
+
+static void openrgb_get_qmk_version(void) {
+ raw_hid_buffer[0] = OPENRGB_GET_QMK_VERSION;
+ uint8_t current_byte = 1;
+ for (uint8_t i = 0; (current_byte < (RAW_EPSIZE - 2)) && (QMK_VERSION[i] != 0); i++) {
+ raw_hid_buffer[current_byte] = QMK_VERSION[i];
+ current_byte++;
+ }
+}
+
+static void openrgb_get_device_info(void) {
+ raw_hid_buffer[0] = OPENRGB_GET_DEVICE_INFO;
+ raw_hid_buffer[1] = RGB_MATRIX_LED_COUNT;
+ raw_hid_buffer[2] = MATRIX_COLS * MATRIX_ROWS;
+
+#define MASSDROP_VID 0x04D8
+#if VENDOR_ID == MASSDROP_VID
+# define PRODUCT_STRING PRODUCT
+# define MANUFACTURER_STRING MANUFACTURER
+#else
+# define PRODUCT_STRING STR(PRODUCT)
+# define MANUFACTURER_STRING STR(MANUFACTURER)
+#endif
+
+ // Reserve the last 2 bytes for the status / end-of-message markers. The
+ // product string takes the first half of the remaining space, the
+ // manufacturer string the rest; both are explicitly null-terminated.
+ uint8_t current_byte = 3;
+ for (uint8_t i = 0; (current_byte < ((RAW_EPSIZE - 2) / 2)) && (PRODUCT_STRING[i] != 0); i++) {
+ raw_hid_buffer[current_byte++] = PRODUCT_STRING[i];
+ }
+ raw_hid_buffer[current_byte++] = 0;
+
+ for (uint8_t i = 0; (current_byte < (RAW_EPSIZE - 2)) && (MANUFACTURER_STRING[i] != 0); i++) {
+ raw_hid_buffer[current_byte++] = MANUFACTURER_STRING[i];
+ }
+ raw_hid_buffer[current_byte++] = 0;
+
+#undef MASSDROP_VID
+#undef PRODUCT_STRING
+#undef MANUFACTURER_STRING
+}
+
+static void openrgb_get_mode_info(void) {
+ const HSV hsv_color = rgb_matrix_get_hsv();
+ const uint8_t qmk_mode = rgb_matrix_get_mode();
+
+ // Translate the active QMK effect back to OpenRGB's 1-based mode value. If
+ // the current effect has no OpenRGB equivalent (e.g. one selected on the
+ // keyboard itself), report 0 so OpenRGB just doesn't highlight a mode.
+ uint8_t openrgb_mode = 0;
+ for (uint8_t i = 0; i < OPENRGB_MODE_COUNT; i++) {
+ if (openrgb_mode_map[i].qmk_mode == qmk_mode) {
+ openrgb_mode = i + 1;
+ break;
+ }
+ }
+
+ raw_hid_buffer[0] = OPENRGB_GET_MODE_INFO;
+ raw_hid_buffer[1] = openrgb_mode;
+ raw_hid_buffer[2] = rgb_matrix_get_speed();
+ raw_hid_buffer[3] = hsv_color.h;
+ raw_hid_buffer[4] = hsv_color.s;
+ raw_hid_buffer[5] = hsv_color.v;
+}
+
+static void openrgb_get_led_info(uint8_t *data) {
+ const uint8_t first_led = data[1];
+ const uint8_t number_leds = data[2];
+
+ raw_hid_buffer[0] = OPENRGB_GET_LED_INFO;
+
+ const uint8_t max_leds = (RAW_EPSIZE - 2) / 7; // 7 bytes per LED entry
+ for (uint8_t i = 0; i < number_leds && i < max_leds; i++) {
+ const uint8_t led_idx = first_led + i;
+ const uint8_t data_idx = i * 7;
+
+ if (led_idx >= RGB_MATRIX_LED_COUNT) {
+ raw_hid_buffer[data_idx + 3] = OPENRGB_FAILURE;
+ } else {
+ raw_hid_buffer[data_idx + 1] = g_led_config.point[led_idx].x;
+ raw_hid_buffer[data_idx + 2] = g_led_config.point[led_idx].y;
+ raw_hid_buffer[data_idx + 3] = g_led_config.flags[led_idx];
+ raw_hid_buffer[data_idx + 4] = g_openrgb_direct_mode_colors[led_idx].r;
+ raw_hid_buffer[data_idx + 5] = g_openrgb_direct_mode_colors[led_idx].g;
+ raw_hid_buffer[data_idx + 6] = g_openrgb_direct_mode_colors[led_idx].b;
+ }
+
+ uint8_t row = 0;
+ uint8_t col = 0;
+ uint8_t found = 0;
+
+ for (row = 0; row < MATRIX_ROWS; row++) {
+ for (col = 0; col < MATRIX_COLS; col++) {
+ if (g_led_config.matrix_co[row][col] == led_idx) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 1) {
+ break;
+ }
+ }
+
+ if (col >= MATRIX_COLS || row >= MATRIX_ROWS) {
+ raw_hid_buffer[data_idx + 7] = KC_NO;
+ } else {
+ raw_hid_buffer[data_idx + 7] = (uint8_t)keycode_at_keymap_location(0, row, col);
+ }
+ }
+}
+
+static void openrgb_get_enabled_modes(void) {
+ raw_hid_buffer[0] = OPENRGB_GET_ENABLED_MODES;
+
+ // Report the OpenRGB mode ids we support, as a null-terminated list (the
+ // host reads bytes until it hits a 0). Order is irrelevant here -- OpenRGB
+ // matches by value -- but keep one byte free for the terminator.
+ const uint8_t max_entries = RAW_EPSIZE - 2;
+ uint8_t idx = 0;
+ for (uint8_t i = 0; i < OPENRGB_MODE_COUNT && idx < max_entries - 1; i++) {
+ raw_hid_buffer[1 + idx++] = openrgb_mode_map[i].openrgb_id;
+ }
+ raw_hid_buffer[1 + idx] = 0;
+}
+
+static void openrgb_set_mode(uint8_t *data) {
+ const uint8_t h = data[1];
+ const uint8_t s = data[2];
+ const uint8_t v = data[3];
+ const uint8_t mode = data[4]; // OpenRGB's 1-based mode value == index into openrgb_mode_map
+ const uint8_t speed = data[5];
+ const uint8_t save = data[6];
+
+ raw_hid_buffer[0] = OPENRGB_SET_MODE;
+
+ if (mode < 1 || mode > OPENRGB_MODE_COUNT) {
+ raw_hid_buffer[RAW_EPSIZE - 2] = OPENRGB_FAILURE;
+ return;
+ }
+
+ const uint8_t qmk_mode = openrgb_mode_map[mode - 1].qmk_mode;
+
+ if (save == 1) {
+ rgb_matrix_mode(qmk_mode);
+ rgb_matrix_set_speed(speed);
+ rgb_matrix_sethsv(h, s, v);
+ } else {
+ rgb_matrix_mode_noeeprom(qmk_mode);
+ rgb_matrix_set_speed_noeeprom(speed);
+ rgb_matrix_sethsv_noeeprom(h, s, v);
+ }
+
+ raw_hid_buffer[RAW_EPSIZE - 2] = OPENRGB_SUCCESS;
+}
+
+static void openrgb_direct_mode_set_single_led(uint8_t *data) {
+ const uint8_t led = data[1];
+ const uint8_t r = data[2];
+ const uint8_t g = data[3];
+ const uint8_t b = data[4];
+
+ raw_hid_buffer[0] = OPENRGB_DIRECT_MODE_SET_SINGLE_LED;
+
+ if (led >= RGB_MATRIX_LED_COUNT) {
+ raw_hid_buffer[RAW_EPSIZE - 2] = OPENRGB_FAILURE;
+ return;
+ }
+
+ g_openrgb_direct_mode_colors[led].r = r;
+ g_openrgb_direct_mode_colors[led].g = g;
+ g_openrgb_direct_mode_colors[led].b = b;
+
+ raw_hid_buffer[RAW_EPSIZE - 2] = OPENRGB_SUCCESS;
+}
+
+static void openrgb_direct_mode_set_leds(uint8_t *data, uint8_t length) {
+ const uint8_t number_leds = data[1];
+ // Each LED entry is 4 bytes ([index, r, g, b]) following the 2-byte header
+ // (command id + count). Clamp to what actually fits in the received report so
+ // a malformed packet with an inflated count cannot drive reads past the buffer.
+ const uint8_t max_leds = length > 2 ? (uint8_t)((length - 2) / 4) : 0;
+
+ for (uint8_t i = 0; i < number_leds && i < max_leds; i++) {
+ const uint8_t data_idx = i * 4;
+ const uint8_t color_idx = data[data_idx + 2];
+
+ if (color_idx < RGB_MATRIX_LED_COUNT) {
+ g_openrgb_direct_mode_colors[color_idx].r = data[data_idx + 3];
+ g_openrgb_direct_mode_colors[color_idx].g = data[data_idx + 4];
+ g_openrgb_direct_mode_colors[color_idx].b = data[data_idx + 5];
+ }
+ }
+}
+
+bool openrgb_command_handler(uint8_t *data, uint8_t length) {
+ if (length < 1) {
+ return false;
+ }
+
+ switch (data[0]) {
+ case OPENRGB_GET_PROTOCOL_VERSION:
+ openrgb_get_protocol_version();
+ break;
+ case OPENRGB_GET_QMK_VERSION:
+ openrgb_get_qmk_version();
+ break;
+ case OPENRGB_GET_DEVICE_INFO:
+ openrgb_get_device_info();
+ break;
+ case OPENRGB_GET_MODE_INFO:
+ openrgb_get_mode_info();
+ break;
+ case OPENRGB_GET_LED_INFO:
+ openrgb_get_led_info(data);
+ break;
+ case OPENRGB_GET_ENABLED_MODES:
+ openrgb_get_enabled_modes();
+ break;
+ case OPENRGB_SET_MODE:
+ openrgb_set_mode(data);
+ break;
+ case OPENRGB_DIRECT_MODE_SET_SINGLE_LED:
+ openrgb_direct_mode_set_single_led(data);
+ break;
+ case OPENRGB_DIRECT_MODE_SET_LEDS:
+ openrgb_direct_mode_set_leds(data, length);
+ break;
+ default:
+ return false;
+ }
+
+ if (data[0] != OPENRGB_DIRECT_MODE_SET_LEDS) {
+ raw_hid_buffer[RAW_EPSIZE - 1] = OPENRGB_END_OF_MESSAGE;
+ // Reply over USB explicitly (like the Keychron Launcher's KC_* handlers do).
+ // The core raw_hid_send() routes through the active host driver, whose
+ // send_raw_hid is NULL while the wireless transport is installed (side
+ // switch on BT/2.4G) -- silently dropping the reply. OpenRGB is USB-only
+ // and the dispatch is already gated on src == RAW_HID_SRC_USB.
+ kc_raw_hid_send(RAW_HID_SRC_USB, raw_hid_buffer, RAW_EPSIZE);
+ memset(raw_hid_buffer, 0x00, RAW_EPSIZE);
+ }
+
+ return true;
+}
diff --git a/keyboards/keychron/common/openrgb.h b/keyboards/keychron/common/openrgb.h
new file mode 100644
index 000000000000..1f6b41a38f24
--- /dev/null
+++ b/keyboards/keychron/common/openrgb.h
@@ -0,0 +1,98 @@
+/* Copyright 2020 Kasper
+ * Copyright 2025 Keychron K2 HE OpenRGB patch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+#include "color.h"
+
+#define OPENRGB_PROTOCOL_VERSION 0xD
+
+enum openrgb_command_id {
+ OPENRGB_GET_PROTOCOL_VERSION = 1,
+ OPENRGB_GET_QMK_VERSION,
+ OPENRGB_GET_DEVICE_INFO,
+ OPENRGB_GET_MODE_INFO,
+ OPENRGB_GET_LED_INFO,
+ OPENRGB_GET_ENABLED_MODES,
+
+ OPENRGB_SET_MODE,
+ OPENRGB_DIRECT_MODE_SET_SINGLE_LED,
+ OPENRGB_DIRECT_MODE_SET_LEDS,
+};
+
+enum openrgb_responses {
+ OPENRGB_FAILURE = 25,
+ OPENRGB_SUCCESS = 50,
+ OPENRGB_END_OF_MESSAGE = 100,
+};
+
+/* OpenRGB mode identifiers. These mirror the `Modes` enum in OpenRGB's
+ * QMKOpenRGBController.h: OpenRGB looks for these values in the
+ * GET_ENABLED_MODES reply (via find()) and never assumes the keyboard's own
+ * rgb_matrix effect order. openrgb.c maps each to a QMK rgb_matrix effect. */
+enum openrgb_mode_id {
+ OPENRGB_MODE_OPENRGB_DIRECT = 1,
+ OPENRGB_MODE_SOLID_COLOR,
+ OPENRGB_MODE_ALPHA_MOD,
+ OPENRGB_MODE_GRADIENT_UP_DOWN,
+ OPENRGB_MODE_GRADIENT_LEFT_RIGHT,
+ OPENRGB_MODE_BREATHING,
+ OPENRGB_MODE_BAND_SAT,
+ OPENRGB_MODE_BAND_VAL,
+ OPENRGB_MODE_BAND_PINWHEEL_SAT,
+ OPENRGB_MODE_BAND_PINWHEEL_VAL,
+ OPENRGB_MODE_BAND_SPIRAL_SAT,
+ OPENRGB_MODE_BAND_SPIRAL_VAL,
+ OPENRGB_MODE_CYCLE_ALL,
+ OPENRGB_MODE_CYCLE_LEFT_RIGHT,
+ OPENRGB_MODE_CYCLE_UP_DOWN,
+ OPENRGB_MODE_CYCLE_OUT_IN,
+ OPENRGB_MODE_CYCLE_OUT_IN_DUAL,
+ OPENRGB_MODE_RAINBOW_MOVING_CHEVRON,
+ OPENRGB_MODE_CYCLE_PINWHEEL,
+ OPENRGB_MODE_CYCLE_SPIRAL,
+ OPENRGB_MODE_DUAL_BEACON,
+ OPENRGB_MODE_RAINBOW_BEACON,
+ OPENRGB_MODE_RAINBOW_PINWHEELS,
+ OPENRGB_MODE_RAINDROPS,
+ OPENRGB_MODE_JELLYBEAN_RAINDROPS,
+ OPENRGB_MODE_HUE_BREATHING,
+ OPENRGB_MODE_HUE_PENDULUM,
+ OPENRGB_MODE_HUE_WAVE,
+ OPENRGB_MODE_TYPING_HEATMAP,
+ OPENRGB_MODE_DIGITAL_RAIN,
+ OPENRGB_MODE_SOLID_REACTIVE_SIMPLE,
+ OPENRGB_MODE_SOLID_REACTIVE,
+ OPENRGB_MODE_SOLID_REACTIVE_WIDE,
+ OPENRGB_MODE_SOLID_REACTIVE_MULTIWIDE,
+ OPENRGB_MODE_SOLID_REACTIVE_CROSS,
+ OPENRGB_MODE_SOLID_REACTIVE_MULTICROSS,
+ OPENRGB_MODE_SOLID_REACTIVE_NEXUS,
+ OPENRGB_MODE_SOLID_REACTIVE_MULTINEXUS,
+ OPENRGB_MODE_SPLASH,
+ OPENRGB_MODE_MULTISPLASH,
+ OPENRGB_MODE_SOLID_SPLASH,
+ OPENRGB_MODE_SOLID_MULTISPLASH,
+ OPENRGB_MODE_PIXEL_RAIN,
+ OPENRGB_MODE_PIXEL_FLOW,
+ OPENRGB_MODE_PIXEL_FRACTAL,
+};
+
+extern RGB g_openrgb_direct_mode_colors[];
+
+bool openrgb_command_handler(uint8_t *data, uint8_t length);
diff --git a/keyboards/keychron/common/tests/mocks/color.h b/keyboards/keychron/common/tests/mocks/color.h
new file mode 100644
index 000000000000..b2b6a2a3de9c
--- /dev/null
+++ b/keyboards/keychron/common/tests/mocks/color.h
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Shadow of color.h for the OpenRGB unit tests.
+#pragma once
+#include "openrgb_test_shim.h"
diff --git a/keyboards/keychron/common/tests/mocks/keychron_raw_hid.h b/keyboards/keychron/common/tests/mocks/keychron_raw_hid.h
new file mode 100644
index 000000000000..7b4a63497317
--- /dev/null
+++ b/keyboards/keychron/common/tests/mocks/keychron_raw_hid.h
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Shadow of keychron_raw_hid.h for the OpenRGB unit tests.
+#pragma once
+#include "openrgb_test_shim.h"
diff --git a/keyboards/keychron/common/tests/mocks/keymap_introspection.h b/keyboards/keychron/common/tests/mocks/keymap_introspection.h
new file mode 100644
index 000000000000..8dd0fe534ebe
--- /dev/null
+++ b/keyboards/keychron/common/tests/mocks/keymap_introspection.h
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Shadow of keymap_introspection.h for the OpenRGB unit tests.
+#pragma once
+#include "openrgb_test_shim.h"
diff --git a/keyboards/keychron/common/tests/mocks/openrgb_test_shim.h b/keyboards/keychron/common/tests/mocks/openrgb_test_shim.h
new file mode 100644
index 000000000000..f2278edd3ed1
--- /dev/null
+++ b/keyboards/keychron/common/tests/mocks/openrgb_test_shim.h
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// Shared mock shim for the Keychron K2 HE OpenRGB unit tests.
+//
+// openrgb.c (the code under test, UNMODIFIED) pulls in a pile of QMK headers via
+// "quantum.h" et al. To compile it in isolation against the host gtest harness we
+// place a mocks/ directory FIRST on the include path so the shadow headers
+// (quantum.h, color.h, raw_hid.h, usb_descriptor.h, version.h,
+// keymap_introspection.h, keychron_raw_hid.h) shadow the real QMK ones. Every one
+// of those shadow headers just #includes this single shim, which declares exactly
+// the symbols openrgb.c references.
+//
+// This file is C/C++ shared: the recording state and the mock function bodies live
+// in openrgb_test_mocks.cpp; the gtest cases in openrgb_mapping.cpp reach into the
+// recording state through openrgb_test_recorder.h.
+
+#pragma once
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ---------------------------------------------------------------------------
+// Color types (mirror quantum/color.h rgb_t / hsv_t layout)
+// ---------------------------------------------------------------------------
+typedef struct {
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+} RGB;
+
+typedef struct {
+ uint8_t h;
+ uint8_t s;
+ uint8_t v;
+} HSV;
+
+// ---------------------------------------------------------------------------
+// LED config (mirror led_config_t / led_point_t)
+// ---------------------------------------------------------------------------
+typedef struct {
+ uint8_t x;
+ uint8_t y;
+} led_point_t;
+
+typedef struct {
+ uint8_t matrix_co[MATRIX_ROWS][MATRIX_COLS];
+ led_point_t point[RGB_MATRIX_LED_COUNT];
+ uint8_t flags[RGB_MATRIX_LED_COUNT];
+} led_config_t;
+
+extern led_config_t g_led_config;
+
+// ---------------------------------------------------------------------------
+// Keycodes / keymap introspection
+// ---------------------------------------------------------------------------
+#define KC_NO 0
+
+uint16_t keycode_at_keymap_location(uint8_t layer, uint8_t row, uint8_t col);
+
+// ---------------------------------------------------------------------------
+// RGB matrix effect enum -- mirrors the real 2025q3
+// quantum/rgb_matrix/animations/rgb_matrix_effects.inc ORDER so that the OpenRGB
+// mode order (in openrgb.h) genuinely differs from QMK's effect numbering.
+//
+// Crucially: RAINBOW_MOVING_CHEVRON sits BEFORE CYCLE_OUT_IN here (real QMK
+// order), but AFTER it in OpenRGB's order -- this divergence is what makes the
+// mode-mapping fix observable. Only the effects gated by the -DENABLE_RGB_MATRIX_*
+// flags actually land in openrgb_mode_map[]; the rest just occupy enum slots
+// (exactly as in real QMK, where every animation file contributes an enum value
+// regardless of which are compiled in).
+// ---------------------------------------------------------------------------
+enum rgb_matrix_effects {
+ RGB_MATRIX_NONE = 0,
+ RGB_MATRIX_SOLID_COLOR,
+ RGB_MATRIX_ALPHAS_MODS,
+ RGB_MATRIX_GRADIENT_UP_DOWN,
+ RGB_MATRIX_GRADIENT_LEFT_RIGHT,
+ RGB_MATRIX_BREATHING,
+ RGB_MATRIX_BAND_SAT,
+ RGB_MATRIX_BAND_VAL,
+ RGB_MATRIX_BAND_PINWHEEL_SAT,
+ RGB_MATRIX_BAND_PINWHEEL_VAL,
+ RGB_MATRIX_BAND_SPIRAL_SAT,
+ RGB_MATRIX_BAND_SPIRAL_VAL,
+ RGB_MATRIX_CYCLE_ALL,
+ RGB_MATRIX_CYCLE_LEFT_RIGHT,
+ RGB_MATRIX_CYCLE_UP_DOWN,
+ RGB_MATRIX_RAINBOW_MOVING_CHEVRON, // index 15 (before CYCLE_OUT_IN)
+ RGB_MATRIX_CYCLE_OUT_IN, // index 16
+ RGB_MATRIX_CYCLE_OUT_IN_DUAL,
+ RGB_MATRIX_CYCLE_PINWHEEL,
+ RGB_MATRIX_CYCLE_SPIRAL,
+ RGB_MATRIX_DUAL_BEACON,
+ RGB_MATRIX_RAINBOW_BEACON,
+ RGB_MATRIX_RAINBOW_PINWHEELS,
+ RGB_MATRIX_FLOWER_BLOOMING,
+ RGB_MATRIX_RAINDROPS,
+ RGB_MATRIX_JELLYBEAN_RAINDROPS,
+ RGB_MATRIX_HUE_BREATHING,
+ RGB_MATRIX_HUE_PENDULUM,
+ RGB_MATRIX_HUE_WAVE,
+ RGB_MATRIX_PIXEL_RAIN,
+ RGB_MATRIX_PIXEL_FLOW,
+ RGB_MATRIX_PIXEL_FRACTAL,
+ RGB_MATRIX_TYPING_HEATMAP,
+ RGB_MATRIX_DIGITAL_RAIN,
+ RGB_MATRIX_SOLID_REACTIVE_SIMPLE,
+ RGB_MATRIX_SOLID_REACTIVE,
+ RGB_MATRIX_SOLID_REACTIVE_WIDE,
+ RGB_MATRIX_SOLID_REACTIVE_CROSS,
+ RGB_MATRIX_SOLID_REACTIVE_NEXUS,
+ RGB_MATRIX_SPLASH,
+ RGB_MATRIX_SOLID_SPLASH,
+ RGB_MATRIX_STARLIGHT_SMOOTH,
+ RGB_MATRIX_STARLIGHT,
+ RGB_MATRIX_STARLIGHT_DUAL_SAT,
+ RGB_MATRIX_STARLIGHT_DUAL_HUE,
+ RGB_MATRIX_RIVERFLOW,
+ RGB_MATRIX_OPENRGB_DIRECT,
+ RGB_MATRIX_EFFECT_MAX
+};
+
+// ---------------------------------------------------------------------------
+// RGB matrix API used by openrgb.c. Implementations live in the mocks cpp and
+// record / serve values for the tests.
+// ---------------------------------------------------------------------------
+HSV rgb_matrix_get_hsv(void);
+uint8_t rgb_matrix_get_mode(void);
+uint8_t rgb_matrix_get_speed(void);
+void rgb_matrix_mode(uint8_t mode);
+void rgb_matrix_mode_noeeprom(uint8_t mode);
+void rgb_matrix_set_speed(uint8_t speed);
+void rgb_matrix_set_speed_noeeprom(uint8_t speed);
+void rgb_matrix_sethsv(uint8_t h, uint8_t s, uint8_t v);
+void rgb_matrix_sethsv_noeeprom(uint8_t h, uint8_t s, uint8_t v);
+
+// ---------------------------------------------------------------------------
+// Raw HID transports
+// ---------------------------------------------------------------------------
+#define RAW_HID_SRC_USB 0
+
+// Keychron USB-explicit transport (the FIXED reply path).
+void kc_raw_hid_send(uint8_t src, uint8_t *data, uint8_t len);
+
+// Core QMK raw_hid_send (the OLD reply path -- on wireless this is dropped).
+void raw_hid_send(uint8_t *data, uint8_t length);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/keyboards/keychron/common/tests/mocks/quantum.h b/keyboards/keychron/common/tests/mocks/quantum.h
new file mode 100644
index 000000000000..f9d8d3ecc4f8
--- /dev/null
+++ b/keyboards/keychron/common/tests/mocks/quantum.h
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Shadow of quantum.h for the OpenRGB unit tests.
+#pragma once
+#include "openrgb_test_shim.h"
diff --git a/keyboards/keychron/common/tests/mocks/raw_hid.h b/keyboards/keychron/common/tests/mocks/raw_hid.h
new file mode 100644
index 000000000000..81b4de308314
--- /dev/null
+++ b/keyboards/keychron/common/tests/mocks/raw_hid.h
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Shadow of raw_hid.h for the OpenRGB unit tests.
+#pragma once
+#include "openrgb_test_shim.h"
diff --git a/keyboards/keychron/common/tests/mocks/usb_descriptor.h b/keyboards/keychron/common/tests/mocks/usb_descriptor.h
new file mode 100644
index 000000000000..9b8481e4ed2a
--- /dev/null
+++ b/keyboards/keychron/common/tests/mocks/usb_descriptor.h
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Shadow of usb_descriptor.h for the OpenRGB unit tests.
+#pragma once
+#include "openrgb_test_shim.h"
diff --git a/keyboards/keychron/common/tests/mocks/version.h b/keyboards/keychron/common/tests/mocks/version.h
new file mode 100644
index 000000000000..96ee82497d45
--- /dev/null
+++ b/keyboards/keychron/common/tests/mocks/version.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Shadow of version.h for the OpenRGB unit tests.
+//
+// openrgb.c reads QMK_VERSION as a char array and stringizes PRODUCT /
+// MANUFACTURER via STR(). Provide a 2-level stringize helper (matching QMK's
+// usual STR macro) and string-literal-able product/manufacturer tokens.
+#pragma once
+#include "openrgb_test_shim.h"
+
+#ifndef QMK_VERSION
+# define QMK_VERSION "test"
+#endif
+
+#ifndef PRODUCT
+# define PRODUCT Keychron K2 HE
+#endif
+
+#ifndef MANUFACTURER
+# define MANUFACTURER Keychron
+#endif
+
+#ifndef STR
+# define STR_HELPER(x) #x
+# define STR(x) STR_HELPER(x)
+#endif
diff --git a/keyboards/keychron/common/tests/openrgb_firmware_wiring.sh b/keyboards/keychron/common/tests/openrgb_firmware_wiring.sh
new file mode 100755
index 000000000000..d5186c96a40c
--- /dev/null
+++ b/keyboards/keychron/common/tests/openrgb_firmware_wiring.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# Build-wiring guard for the K2 HE OpenRGB feature.
+#
+# The gtest unit tests (openrgb_mapping.cpp) compile keyboards/keychron/common/
+# openrgb.c *directly*, so they pass even when the keyboard build never actually
+# compiles openrgb.c. This guard builds the real default-keymap firmware and
+# asserts the OpenRGB sources are linked in.
+#
+# It catches the regression where `OPENRGB_ENABLE = yes` is set in the keymap
+# rules.mk but keychron_common.mk (which adds openrgb.c when OPENRGB_ENABLE=yes)
+# is `include`d from the *keyboard* rules.mk -- i.e. evaluated before the keymap's
+# rules.mk is read -- so the flag is seen too late and openrgb.c is silently
+# dropped, making OpenRGB commands 1-9 time out on real hardware.
+#
+# Run from the nix dev shell: nix develop --command bash keyboards/keychron/common/tests/openrgb_firmware_wiring.sh
+set -uo pipefail
+cd "$(git -C "$(dirname "$0")" rev-parse --show-toplevel)"
+
+qmk compile -kb keychron/k2_he/ansi -km default >/dev/null 2>&1 || true
+
+obj=$(find .build -path '*k2_he_ansi*' -name 'openrgb.o' 2>/dev/null | head -1)
+if [ -n "$obj" ]; then
+ echo "PASS: openrgb.c is compiled into the K2 HE default firmware"
+ exit 0
+fi
+echo "FAIL: openrgb.c is NOT compiled into the K2 HE default firmware --"
+echo " OpenRGB commands 1-9 will be dropped (cmd reaches the dispatch default"
+echo " case -> 'return false' -> no reply). Check OPENRGB_ENABLE wiring."
+exit 1
diff --git a/keyboards/keychron/common/tests/openrgb_mapping.cpp b/keyboards/keychron/common/tests/openrgb_mapping.cpp
new file mode 100644
index 000000000000..f9c23426910b
--- /dev/null
+++ b/keyboards/keychron/common/tests/openrgb_mapping.cpp
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// gtest cases proving two real bugs/fixes in the Keychron K2 HE OpenRGB code
+// (keyboards/keychron/common/openrgb.c), compiled here against host mocks.
+//
+// BUG 1 (mode mapping): GET_ENABLED_MODES returns OpenRGB mode ids (not a raw
+// 1..N list); SET_MODE translates the OpenRGB counter through
+// openrgb_mode_map[] into a QMK effect; GET_MODE_INFO translates the
+// active QMK effect back to its 1-based OpenRGB counter.
+//
+// BUG 2 (reply transport): replies go via kc_raw_hid_send(RAW_HID_SRC_USB,...)
+// (USB-explicit), NOT the core raw_hid_send() which is dropped on
+// wireless.
+//
+// Every case PASSES against the current (fixed) openrgb.c and would FAIL if the
+// corresponding fix were reverted.
+
+#include "gtest/gtest.h"
+
+#include
+#include
+
+#include "openrgb_test_recorder.h"
+
+// The shim (RGB/HSV types + rgb_matrix_effects enum) and the REAL openrgb.h
+// (OPENRGB_MODE_* ids, openrgb_command_handler). mocks/ is first on the include
+// path so openrgb.h's `#include "color.h"` resolves to the shadow.
+extern "C" {
+#include "openrgb_test_shim.h"
+#include "openrgb.h"
+}
+
+// RAW_EPSIZE comes in via -DRAW_EPSIZE on the command line.
+static constexpr int kEpSize = RAW_EPSIZE;
+
+class OpenRgbMapping : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ OpenRgbMockState::Instance().reset();
+ }
+
+ // Run GET_ENABLED_MODES and return the null-terminated id list (terminator
+ // excluded). Used to discover N (the Direct counter) at runtime.
+ static std::vector EnabledModeIds() {
+ OpenRgbMockState::Instance().reset();
+ uint8_t data[kEpSize] = {0};
+ data[0] = OPENRGB_GET_ENABLED_MODES;
+ EXPECT_TRUE(openrgb_command_handler(data, kEpSize));
+ const auto& buf = OpenRgbMockState::Instance().kc_send_buf;
+ EXPECT_FALSE(buf.empty());
+ std::vector ids;
+ for (size_t i = 1; i < buf.size(); ++i) {
+ if (buf[i] == 0) break;
+ ids.push_back(buf[i]);
+ }
+ return ids;
+ }
+};
+
+// 1. GET_ENABLED_MODES returns OpenRGB mode IDs, null-terminated -- NOT a raw
+// 1..N list. The first table entry is SOLID_COLOR whose OpenRGB id is 2, so
+// byte[1] must be 2 (not 1). The list must contain Direct's id (1) and end in 0.
+TEST_F(OpenRgbMapping, EnabledModesAreOpenRgbIds) {
+ uint8_t data[kEpSize] = {0};
+ data[0] = OPENRGB_GET_ENABLED_MODES;
+
+ ASSERT_TRUE(openrgb_command_handler(data, kEpSize));
+
+ const auto& buf = OpenRgbMockState::Instance().kc_send_buf;
+ ASSERT_EQ((int)buf.size(), kEpSize);
+
+ EXPECT_EQ(buf[0], OPENRGB_GET_ENABLED_MODES) << "reply command id";
+
+ // First emitted id is SOLID_COLOR's OpenRGB id (2), proving these are ids and
+ // not a raw 1-based counter list (old code would emit 1 here).
+ EXPECT_EQ(buf[1], (uint8_t)OPENRGB_MODE_SOLID_COLOR) << "first id should be the OpenRGB SOLID_COLOR id (2), not 1";
+ EXPECT_NE(buf[1], 1) << "a value of 1 would mean the old raw 1..N list";
+
+ // The list must contain the Direct id (1) somewhere.
+ auto ids = EnabledModeIds();
+ bool has_direct = false;
+ for (uint8_t id : ids) {
+ if (id == (uint8_t)OPENRGB_MODE_OPENRGB_DIRECT) has_direct = true;
+ }
+ EXPECT_TRUE(has_direct) << "enabled-modes list must include OPENRGB_MODE_OPENRGB_DIRECT (==1)";
+
+ // Null-terminated: the byte right after the last id must be 0.
+ ASSERT_LT(ids.size() + 1, (size_t)kEpSize);
+ EXPECT_EQ(buf[1 + ids.size()], 0) << "enabled-modes list must be null-terminated";
+}
+
+// 2. SET_MODE with counter N (= Direct, the last table slot) maps to
+// RGB_MATRIX_OPENRGB_DIRECT -- NOT the raw counter N.
+TEST_F(OpenRgbMapping, SetModeDirectMapsToDirectEffect) {
+ const auto ids = EnabledModeIds();
+ const uint8_t N = (uint8_t)ids.size();
+ ASSERT_GT(N, 0);
+
+ OpenRgbMockState::Instance().reset();
+
+ // {cmd, h, s, v, mode-counter, speed, save}
+ uint8_t data[kEpSize] = {0};
+ data[0] = OPENRGB_SET_MODE;
+ data[1] = 0; // h
+ data[2] = 255; // s
+ data[3] = 255; // v
+ data[4] = N; // counter N == Direct
+ data[5] = 127; // speed
+ data[6] = 0; // save=0 -> _noeeprom path
+
+ ASSERT_TRUE(openrgb_command_handler(data, kEpSize));
+
+ auto& s = OpenRgbMockState::Instance();
+ ASSERT_TRUE(s.mode_set) << "rgb_matrix_mode*() must have been called";
+ EXPECT_TRUE(s.mode_set_via_noeeprom) << "save=0 must use the _noeeprom variant";
+ EXPECT_EQ(s.last_mode, (uint8_t)RGB_MATRIX_OPENRGB_DIRECT) << "Direct counter must map to the Direct QMK effect, not the raw counter";
+ EXPECT_NE(s.last_mode, N) << "must not be the raw counter N (old behavior)";
+}
+
+// 3. SET_MODE with a mid-list counter k whose mapped QMK effect != k goes through
+// the table. Counter 15 in the table is CYCLE_OUT_IN, whose QMK enum value
+// (16) differs from 15.
+TEST_F(OpenRgbMapping, SetModeMidListMapsThroughTable) {
+ const auto ids = EnabledModeIds();
+ const uint8_t k = 15; // OpenRGB counter -> table slot 15 -> CYCLE_OUT_IN
+ ASSERT_GE((int)ids.size(), (int)k);
+
+ // Sanity: the chosen slot's QMK effect must actually differ from the counter,
+ // otherwise this test can't distinguish the fix from identity behavior.
+ ASSERT_NE((uint8_t)RGB_MATRIX_CYCLE_OUT_IN, k) << "test precondition: mapped effect must differ from counter";
+
+ OpenRgbMockState::Instance().reset();
+
+ uint8_t data[kEpSize] = {0};
+ data[0] = OPENRGB_SET_MODE;
+ data[1] = 0;
+ data[2] = 255;
+ data[3] = 255;
+ data[4] = k;
+ data[5] = 127;
+ data[6] = 0;
+
+ ASSERT_TRUE(openrgb_command_handler(data, kEpSize));
+
+ auto& s = OpenRgbMockState::Instance();
+ ASSERT_TRUE(s.mode_set);
+ EXPECT_EQ(s.last_mode, (uint8_t)RGB_MATRIX_CYCLE_OUT_IN) << "counter 15 must map through the table to CYCLE_OUT_IN";
+ EXPECT_NE(s.last_mode, k) << "must not be the raw counter k (old behavior)";
+}
+
+// 4. GET_MODE_INFO translates the active QMK effect back to its 1-based OpenRGB
+// counter. With Direct active, byte[1] must be N (Direct's counter), not the
+// raw enum value.
+TEST_F(OpenRgbMapping, GetModeInfoReportsOpenRgbCounter) {
+ const auto ids = EnabledModeIds();
+ const uint8_t N = (uint8_t)ids.size();
+ ASSERT_GT(N, 0);
+
+ OpenRgbMockState::Instance().reset();
+ OpenRgbMockState::Instance().get_mode_value = (uint8_t)RGB_MATRIX_OPENRGB_DIRECT;
+
+ uint8_t data[kEpSize] = {0};
+ data[0] = OPENRGB_GET_MODE_INFO;
+
+ ASSERT_TRUE(openrgb_command_handler(data, kEpSize));
+
+ const auto& buf = OpenRgbMockState::Instance().kc_send_buf;
+ ASSERT_EQ((int)buf.size(), kEpSize);
+ EXPECT_EQ(buf[0], OPENRGB_GET_MODE_INFO);
+ EXPECT_EQ(buf[1], N) << "GET_MODE_INFO must report Direct's 1-based OpenRGB counter (N)";
+ EXPECT_NE(buf[1], (uint8_t)RGB_MATRIX_OPENRGB_DIRECT) << "must not report the raw QMK enum value";
+}
+
+// 5. Replies are delivered via kc_raw_hid_send(RAW_HID_SRC_USB,...), NOT the core
+// raw_hid_send() (which our mock drops, modeling the wireless NULL driver).
+TEST_F(OpenRgbMapping, ReplyGoesOverUsbNotCoreRawHid) {
+ uint8_t data[kEpSize] = {0};
+ data[0] = OPENRGB_GET_PROTOCOL_VERSION;
+
+ ASSERT_TRUE(openrgb_command_handler(data, kEpSize));
+
+ auto& s = OpenRgbMockState::Instance();
+ EXPECT_TRUE(s.kc_send_called) << "reply must be delivered via kc_raw_hid_send";
+ EXPECT_EQ(s.kc_send_src, (uint8_t)RAW_HID_SRC_USB) << "reply must be sent with src == RAW_HID_SRC_USB";
+ ASSERT_EQ((int)s.kc_send_buf.size(), kEpSize);
+ EXPECT_EQ(s.kc_send_buf[0], OPENRGB_GET_PROTOCOL_VERSION) << "USB-delivered reply must carry the protocol-version reply";
+
+ // The reply must NOT have been carried (and dropped) by the core transport.
+ EXPECT_FALSE(s.core_send_called) << "reply must not be routed through core raw_hid_send (dropped on wireless)";
+}
+
+// 6. Full OpenRGB discovery handshake -- the exact command sequence OpenRGB
+// issues on detection. Each command must reply over USB (kc_raw_hid_send with
+// src == RAW_HID_SRC_USB) with sane data: i.e. OpenRGB can see + enumerate the
+// keyboard at the protocol level. This is the unit-level equivalent of
+// "OpenRGB sees the keyboard" -- it exercises the compiled openrgb.c against
+// mocks and never touches real hardware.
+TEST_F(OpenRgbMapping, ProtocolDiscoveryHandshake) {
+ auto& s = OpenRgbMockState::Instance();
+
+ auto run = [&](uint8_t cmd) -> std::vector {
+ s.reset();
+ uint8_t data[kEpSize] = {0};
+ data[0] = cmd;
+ if (cmd == OPENRGB_GET_LED_INFO) { // GET_LED_INFO needs (first_led, count)
+ data[1] = 0;
+ data[2] = 8;
+ }
+ EXPECT_TRUE(openrgb_command_handler(data, kEpSize)) << "handler rejected cmd " << (int)cmd;
+ EXPECT_TRUE(s.kc_send_called) << "cmd " << (int)cmd << " did not reply over USB";
+ EXPECT_EQ(s.kc_send_src, (uint8_t)RAW_HID_SRC_USB) << "cmd " << (int)cmd << " reply not USB-src";
+ EXPECT_FALSE(s.core_send_called) << "cmd " << (int)cmd << " routed via core raw_hid_send (would drop on wireless)";
+ EXPECT_EQ((int)s.kc_send_buf.size(), kEpSize);
+ return s.kc_send_buf;
+ };
+
+ // 1) GET_PROTOCOL_VERSION -> protocol version (RevD = 0x0D) in byte[1].
+ auto pv = run(OPENRGB_GET_PROTOCOL_VERSION);
+ EXPECT_EQ(pv[0], OPENRGB_GET_PROTOCOL_VERSION);
+ EXPECT_EQ(pv[1], (uint8_t)OPENRGB_PROTOCOL_VERSION) << "discovery: protocol version must be RevD (0x0D)";
+
+ // 2) GET_QMK_VERSION -> non-empty version string from byte[1].
+ auto qv = run(OPENRGB_GET_QMK_VERSION);
+ EXPECT_EQ(qv[0], OPENRGB_GET_QMK_VERSION);
+ EXPECT_NE(qv[1], 0) << "discovery: QMK version string must be present";
+
+ // 3) GET_DEVICE_INFO -> LED count, matrix cells, device name.
+ auto di = run(OPENRGB_GET_DEVICE_INFO);
+ EXPECT_EQ(di[0], OPENRGB_GET_DEVICE_INFO);
+ EXPECT_EQ(di[1], (uint8_t)RGB_MATRIX_LED_COUNT) << "discovery: device-info LED count";
+ EXPECT_GT(di[2], 0) << "discovery: device-info matrix-cell count";
+ EXPECT_NE(di[3], 0) << "discovery: device name must be present";
+
+ // 4) GET_ENABLED_MODES -> at least one enabled mode.
+ auto em = run(OPENRGB_GET_ENABLED_MODES);
+ EXPECT_EQ(em[0], OPENRGB_GET_ENABLED_MODES);
+ EXPECT_NE(em[1], 0) << "discovery: at least one enabled mode";
+
+ // 5) GET_MODE_INFO -> replies with the active-mode info block.
+ auto mi = run(OPENRGB_GET_MODE_INFO);
+ EXPECT_EQ(mi[0], OPENRGB_GET_MODE_INFO);
+
+ // 6) GET_LED_INFO -> replies with the per-LED layout block.
+ auto li = run(OPENRGB_GET_LED_INFO);
+ EXPECT_EQ(li[0], OPENRGB_GET_LED_INFO);
+}
diff --git a/keyboards/keychron/common/tests/openrgb_test_mocks.cpp b/keyboards/keychron/common/tests/openrgb_test_mocks.cpp
new file mode 100644
index 000000000000..3ce8d762aa7d
--- /dev/null
+++ b/keyboards/keychron/common/tests/openrgb_test_mocks.cpp
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// C-linkage mock implementations for the symbols openrgb.c references, plus the
+// global g_led_config it indexes. All recording goes through OpenRgbMockState.
+
+#include
+#include
+
+#include "openrgb_test_recorder.h"
+
+// Pull in the same shim openrgb.c sees (for the RGB/HSV/led_config_t types and the
+// C function prototypes), so our definitions match the declarations exactly.
+extern "C" {
+#include "openrgb_test_shim.h"
+}
+
+// The global LED config openrgb.c reads (g_led_config.point/.flags/.matrix_co).
+extern "C" {
+led_config_t g_led_config = {};
+}
+
+extern "C" uint16_t keycode_at_keymap_location(uint8_t layer, uint8_t row, uint8_t col) {
+ (void)layer;
+ (void)row;
+ (void)col;
+ return 0;
+}
+
+// ---- rgb_matrix getters: serve what the test preloaded ----
+extern "C" HSV rgb_matrix_get_hsv(void) {
+ auto& s = OpenRgbMockState::Instance();
+ HSV v;
+ v.h = s.get_hsv_h;
+ v.s = s.get_hsv_s;
+ v.v = s.get_hsv_v;
+ return v;
+}
+
+extern "C" uint8_t rgb_matrix_get_mode(void) {
+ return OpenRgbMockState::Instance().get_mode_value;
+}
+
+extern "C" uint8_t rgb_matrix_get_speed(void) {
+ return OpenRgbMockState::Instance().get_speed_value;
+}
+
+// ---- rgb_matrix setters: record what openrgb.c chose ----
+extern "C" void rgb_matrix_mode(uint8_t mode) {
+ auto& s = OpenRgbMockState::Instance();
+ s.mode_set = true;
+ s.last_mode = mode;
+ s.mode_set_via_eeprom = true;
+}
+
+extern "C" void rgb_matrix_mode_noeeprom(uint8_t mode) {
+ auto& s = OpenRgbMockState::Instance();
+ s.mode_set = true;
+ s.last_mode = mode;
+ s.mode_set_via_noeeprom = true;
+}
+
+extern "C" void rgb_matrix_set_speed(uint8_t speed) {
+ (void)speed;
+}
+
+extern "C" void rgb_matrix_set_speed_noeeprom(uint8_t speed) {
+ (void)speed;
+}
+
+extern "C" void rgb_matrix_sethsv(uint8_t h, uint8_t s, uint8_t v) {
+ (void)h;
+ (void)s;
+ (void)v;
+}
+
+extern "C" void rgb_matrix_sethsv_noeeprom(uint8_t h, uint8_t s, uint8_t v) {
+ (void)h;
+ (void)s;
+ (void)v;
+}
+
+// ---- transports ----
+
+// The FIXED reply path: explicit USB send.
+extern "C" void kc_raw_hid_send(uint8_t src, uint8_t *data, uint8_t len) {
+ auto& s = OpenRgbMockState::Instance();
+ s.kc_send_called = true;
+ s.kc_send_src = src;
+ s.kc_send_buf.assign(data, data + len);
+}
+
+// The OLD reply path: core raw_hid_send. On real hardware in wireless mode this is
+// dropped (active host driver's send_raw_hid is NULL). We model the drop by simply
+// recording that it was the carrier and NOT delivering to the USB capture -- so a
+// reverted openrgb.c (which would call this) fails the "delivered over USB" test.
+extern "C" void raw_hid_send(uint8_t *data, uint8_t length) {
+ auto& s = OpenRgbMockState::Instance();
+ s.core_send_called = true;
+ s.core_send_buf.assign(data, data + length);
+ // DROPPED: nothing is forwarded to kc_send_buf.
+}
diff --git a/keyboards/keychron/common/tests/openrgb_test_recorder.h b/keyboards/keychron/common/tests/openrgb_test_recorder.h
new file mode 100644
index 000000000000..fbf6abfde3a3
--- /dev/null
+++ b/keyboards/keychron/common/tests/openrgb_test_recorder.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// Recording state shared between the OpenRGB mock implementations
+// (openrgb_test_mocks.cpp) and the gtest cases (openrgb_mapping.cpp).
+#pragma once
+
+#include
+#include
+
+// Singleton holding everything the mocks record and everything the tests preload.
+struct OpenRgbMockState {
+ // ---- values the test SETS, served back to openrgb.c ----
+ uint8_t get_mode_value = 0; // returned by rgb_matrix_get_mode()
+ uint8_t get_speed_value = 0; // returned by rgb_matrix_get_speed()
+ uint8_t get_hsv_h = 0;
+ uint8_t get_hsv_s = 0;
+ uint8_t get_hsv_v = 0;
+
+ // ---- values the mocks RECORD ----
+ bool mode_set = false; // any rgb_matrix_mode*() called?
+ uint8_t last_mode = 0; // arg to last rgb_matrix_mode*()
+ bool mode_set_via_noeeprom = false; // true if last setter was the _noeeprom variant
+ bool mode_set_via_eeprom = false; // true if last setter was the eeprom variant
+
+ // kc_raw_hid_send (the FIXED USB transport)
+ bool kc_send_called = false;
+ uint8_t kc_send_src = 0xFF;
+ std::vector kc_send_buf;
+
+ // core raw_hid_send (the OLD transport -- on wireless this drops the reply)
+ bool core_send_called = false;
+ std::vector core_send_buf;
+
+ void reset() { *this = OpenRgbMockState{}; }
+
+ static OpenRgbMockState& Instance() {
+ static OpenRgbMockState inst;
+ return inst;
+ }
+};
diff --git a/keyboards/keychron/common/tests/rules.mk b/keyboards/keychron/common/tests/rules.mk
new file mode 100644
index 000000000000..590a65307ac9
--- /dev/null
+++ b/keyboards/keychron/common/tests/rules.mk
@@ -0,0 +1,56 @@
+KEYCHRON_OPENRGB_PATH := keyboards/keychron/common
+KEYCHRON_OPENRGB_TESTS := $(KEYCHRON_OPENRGB_PATH)/tests
+
+openrgb_keychron_DEFS := \
+ -DRAW_EPSIZE=64 \
+ -DRGB_MATRIX_LED_COUNT=84 \
+ -DMATRIX_ROWS=6 \
+ -DMATRIX_COLS=16 \
+ -DVENDOR_ID=0x3434 \
+ -DOPENRGB_ENABLE \
+ -DENABLE_RGB_MATRIX_ALPHAS_MODS \
+ -DENABLE_RGB_MATRIX_GRADIENT_UP_DOWN \
+ -DENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT \
+ -DENABLE_RGB_MATRIX_BREATHING \
+ -DENABLE_RGB_MATRIX_BAND_SAT \
+ -DENABLE_RGB_MATRIX_BAND_VAL \
+ -DENABLE_RGB_MATRIX_BAND_PINWHEEL_SAT \
+ -DENABLE_RGB_MATRIX_BAND_PINWHEEL_VAL \
+ -DENABLE_RGB_MATRIX_BAND_SPIRAL_SAT \
+ -DENABLE_RGB_MATRIX_BAND_SPIRAL_VAL \
+ -DENABLE_RGB_MATRIX_CYCLE_ALL \
+ -DENABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT \
+ -DENABLE_RGB_MATRIX_CYCLE_UP_DOWN \
+ -DENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON \
+ -DENABLE_RGB_MATRIX_CYCLE_OUT_IN \
+ -DENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL \
+ -DENABLE_RGB_MATRIX_CYCLE_PINWHEEL \
+ -DENABLE_RGB_MATRIX_CYCLE_SPIRAL \
+ -DENABLE_RGB_MATRIX_DUAL_BEACON \
+ -DENABLE_RGB_MATRIX_RAINBOW_BEACON \
+ -DENABLE_RGB_MATRIX_RAINBOW_PINWHEELS \
+ -DENABLE_RGB_MATRIX_RAINDROPS \
+ -DENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS \
+ -DENABLE_RGB_MATRIX_HUE_BREATHING \
+ -DENABLE_RGB_MATRIX_HUE_PENDULUM \
+ -DENABLE_RGB_MATRIX_HUE_WAVE \
+ -DENABLE_RGB_MATRIX_TYPING_HEATMAP \
+ -DENABLE_RGB_MATRIX_DIGITAL_RAIN \
+ -DENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE \
+ -DENABLE_RGB_MATRIX_SOLID_REACTIVE \
+ -DENABLE_RGB_MATRIX_SPLASH \
+ -DENABLE_RGB_MATRIX_SOLID_SPLASH
+
+openrgb_keychron_SRC := \
+ $(KEYCHRON_OPENRGB_PATH)/openrgb.c \
+ $(KEYCHRON_OPENRGB_TESTS)/openrgb_test_mocks.cpp \
+ $(KEYCHRON_OPENRGB_TESTS)/openrgb_mapping.cpp
+
+# mocks/ must come FIRST so the shadow headers (quantum.h, color.h, raw_hid.h,
+# usb_descriptor.h, version.h, keymap_introspection.h, keychron_raw_hid.h) shadow
+# the real QMK headers; the second entry exposes the real openrgb.h under test and
+# the recorder header.
+openrgb_keychron_INC := \
+ $(KEYCHRON_OPENRGB_TESTS)/mocks \
+ $(KEYCHRON_OPENRGB_TESTS) \
+ $(KEYCHRON_OPENRGB_PATH)
diff --git a/keyboards/keychron/common/tests/testlist.mk b/keyboards/keychron/common/tests/testlist.mk
new file mode 100644
index 000000000000..e6519f98b1d3
--- /dev/null
+++ b/keyboards/keychron/common/tests/testlist.mk
@@ -0,0 +1 @@
+TEST_LIST += openrgb_keychron
diff --git a/keyboards/keychron/k2_he/ansi/keymaps/default/config.h b/keyboards/keychron/k2_he/ansi/keymaps/default/config.h
new file mode 100644
index 000000000000..ddec96092638
--- /dev/null
+++ b/keyboards/keychron/k2_he/ansi/keymaps/default/config.h
@@ -0,0 +1,20 @@
+/* Copyright 2025 @ Keychron (https://www.keychron.com)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+/* OpenRGB uses 64-byte raw HID reports */
+#define RAW_EPSIZE 64
diff --git a/keyboards/keychron/k2_he/ansi/keymaps/default/keymap.c b/keyboards/keychron/k2_he/ansi/keymaps/default/keymap.c
index 8c41ef7daca0..41ca00dfa038 100644
--- a/keyboards/keychron/k2_he/ansi/keymaps/default/keymap.c
+++ b/keyboards/keychron/k2_he/ansi/keymaps/default/keymap.c
@@ -61,3 +61,9 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
_______, _______, _______, _______, _______, BAT_LVL, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______)
};
+
+#ifdef OPENRGB_ENABLE
+void keyboard_post_init_user(void) {
+ rgb_matrix_mode_noeeprom(RGB_MATRIX_OPENRGB_DIRECT);
+}
+#endif
diff --git a/keyboards/keychron/k2_he/ansi/keymaps/default/rules.mk b/keyboards/keychron/k2_he/ansi/keymaps/default/rules.mk
new file mode 100644
index 000000000000..6a587130e456
--- /dev/null
+++ b/keyboards/keychron/k2_he/ansi/keymaps/default/rules.mk
@@ -0,0 +1,12 @@
+# OpenRGB for the K2 HE default keymap.
+#
+# keychron_common.mk (which turns OPENRGB_ENABLE into the openrgb.c source +
+# -DOPENRGB_ENABLE) is `include`d from the *keyboard* rules.mk, i.e. evaluated
+# before this keymap rules.mk is read. A plain `OPENRGB_ENABLE = yes` here is
+# therefore seen too late and the sources are silently dropped (OpenRGB commands
+# 1-9 then time out on hardware). Wire it directly instead: SRC/OPT_DEFS
+# accumulate regardless of include order, and this keeps OpenRGB scoped to this
+# keymap (the `keychron` keymap keeps VIA). See tests/openrgb_firmware_wiring.sh.
+RAW_ENABLE = yes
+OPT_DEFS += -DOPENRGB_ENABLE
+SRC += $(TOP_DIR)/keyboards/keychron/common/openrgb.c
diff --git a/quantum/rgb_matrix/animations/openrgb_direct_anim.h b/quantum/rgb_matrix/animations/openrgb_direct_anim.h
new file mode 100644
index 000000000000..40c6be0e38b0
--- /dev/null
+++ b/quantum/rgb_matrix/animations/openrgb_direct_anim.h
@@ -0,0 +1,21 @@
+#ifdef OPENRGB_ENABLE
+RGB_MATRIX_EFFECT(OPENRGB_DIRECT)
+# ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS
+
+extern RGB g_openrgb_direct_mode_colors[];
+
+bool OPENRGB_DIRECT(effect_params_t *params) {
+ RGB_MATRIX_USE_LIMITS(led_min, led_max);
+
+ for (uint8_t i = led_min; i < led_max; i++) {
+# ifdef OPENRGB_DIRECT_MODE_USE_UNIVERSAL_BRIGHTNESS
+ const uint8_t v = rgb_matrix_config.hsv.v;
+ rgb_matrix_set_color(i, (uint8_t)((uint16_t)g_openrgb_direct_mode_colors[i].r * v / UINT8_MAX), (uint8_t)((uint16_t)g_openrgb_direct_mode_colors[i].g * v / UINT8_MAX), (uint8_t)((uint16_t)g_openrgb_direct_mode_colors[i].b * v / UINT8_MAX));
+# else
+ rgb_matrix_set_color(i, g_openrgb_direct_mode_colors[i].r, g_openrgb_direct_mode_colors[i].g, g_openrgb_direct_mode_colors[i].b);
+# endif
+ }
+ return rgb_matrix_check_finished_leds(led_max);
+}
+# endif
+#endif
diff --git a/quantum/rgb_matrix/animations/rgb_matrix_effects.inc b/quantum/rgb_matrix/animations/rgb_matrix_effects.inc
index abc2adf2cc7f..2a771b4599c2 100644
--- a/quantum/rgb_matrix/animations/rgb_matrix_effects.inc
+++ b/quantum/rgb_matrix/animations/rgb_matrix_effects.inc
@@ -44,3 +44,4 @@
#include "starlight_dual_sat_anim.h"
#include "starlight_dual_hue_anim.h"
#include "riverflow_anim.h"
+#include "openrgb_direct_anim.h"
diff --git a/quantum/wear_leveling/wear_leveling.c b/quantum/wear_leveling/wear_leveling.c
index fca6968a9225..f65ff9c1ca5d 100644
--- a/quantum/wear_leveling/wear_leveling.c
+++ b/quantum/wear_leveling/wear_leveling.c
@@ -302,7 +302,6 @@ static wear_leveling_status_t wear_leveling_write_consolidated(void) {
*/
static wear_leveling_status_t wear_leveling_consolidate_force(void) {
wl_dprintf("Erasing backing store\n");
- wear_leveling_unlock();
// Erase the backing store. Expectation is that any un-written values that are read back after this call come back as zero.
bool ok = backing_store_erase();
@@ -359,7 +358,7 @@ static wear_leveling_status_t wear_leveling_append_raw(backing_store_int_t value
* @return true if consolidation occurred
*/
static wear_leveling_status_t wear_leveling_write_raw_multibyte(uint32_t address, const void *value, size_t length) {
- const uint8_t * p = value;
+ const uint8_t *p = value;
write_log_entry_t log = LOG_ENTRY_MAKE_MULTIBYTE(address, length);
for (size_t i = 0; i < length; ++i) {
log.raw8[3 + i] = p[i];
@@ -416,7 +415,7 @@ static wear_leveling_status_t wear_leveling_write_raw_multibyte(uint32_t address
* Handles the actual writing of logical data into the write log section of the backing store.
*/
static wear_leveling_status_t wear_leveling_write_raw(uint32_t address, const void *value, size_t length) {
- const uint8_t * p = value;
+ const uint8_t *p = value;
size_t remaining = length;
wear_leveling_status_t status = WEAR_LEVELING_SUCCESS;
while (remaining > 0) {
@@ -556,7 +555,7 @@ static wear_leveling_status_t wear_leveling_playback_log(void) {
if (!ok) {
wl_dprintf("Failed to load from backing store, skipping playback of write log\n");
cancel_playback = true;
- status = WEAR_LEVELING_FAILED;
+ status = WEAR_LEVELING_FAILED;
break;
}
address += (BACKING_STORE_WRITE_SIZE);
diff --git a/tmk_core/protocol/usb_descriptor.h b/tmk_core/protocol/usb_descriptor.h
index 260f038da0ae..583e9dd37c08 100644
--- a/tmk_core/protocol/usb_descriptor.h
+++ b/tmk_core/protocol/usb_descriptor.h
@@ -53,7 +53,7 @@
#endif
#ifndef XINPUT_HID_DESCRIPTOR_LEN
-#define XINPUT_HID_DESCRIPTOR_LEN 0x11
+# define XINPUT_HID_DESCRIPTOR_LEN 0x11
#endif
/*
@@ -224,11 +224,11 @@ enum usb_endpoints {
__unused_epnum__ = NEXT_EPNUM, // Endpoint numbering starts at 1
#ifdef XINPUT_ENABLE
- XINPUT_IN_EPNUM = NEXT_EPNUM,
+ XINPUT_IN_EPNUM = NEXT_EPNUM,
# ifdef USB_ENDPOINTS_ARE_REORDERABLE
# define XINPUT_OUT_EPNUM XINPUT_IN_EPNUM
# else
- #error("xinput require same endpoint for in and out")
+# error("xinput require same endpoint for in and out")
# endif
#endif
@@ -249,7 +249,7 @@ enum usb_endpoints {
# ifdef USB_ENDPOINTS_ARE_REORDERABLE
# define RAW_OUT_EPNUM RAW_IN_EPNUM
# else
- RAW_OUT_EPNUM = NEXT_EPNUM,
+ RAW_OUT_EPNUM = NEXT_EPNUM,
# endif
#endif
@@ -276,7 +276,7 @@ enum usb_endpoints {
# ifdef USB_ENDPOINTS_ARE_REORDERABLE
# define CDC_OUT_EPNUM CDC_IN_EPNUM
# else
- CDC_OUT_EPNUM = NEXT_EPNUM,
+ CDC_OUT_EPNUM = NEXT_EPNUM,
# endif
#endif
@@ -312,7 +312,9 @@ enum usb_endpoints {
#define KEYBOARD_EPSIZE 8
#define SHARED_EPSIZE 32
#define MOUSE_EPSIZE 16
-#define RAW_EPSIZE 32
+#ifndef RAW_EPSIZE
+# define RAW_EPSIZE 32
+#endif
#define CONSOLE_EPSIZE 32
#define MIDI_STREAM_EPSIZE 64
#define CDC_NOTIFICATION_EPSIZE 8
@@ -333,6 +335,5 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
uint16_t get_usb_vendor_descriptor(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress);
#ifdef XINPUT_ENABLE
uint16_t get_usb_vendor_descriptor(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress);
-void get_usb_vendor_descriptor_kb(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress, uint16_t *size);
+void get_usb_vendor_descriptor_kb(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress, uint16_t* size);
#endif
-