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 -