From cf58a8733d35bbfea2f54bd0175747ee63d9b2e3 Mon Sep 17 00:00:00 2001
From: Joel Challis
Date: Fri, 12 Sep 2025 10:23:23 +0100
Subject: Add DIP Switch map support to keymap.json (#25431)
---
lib/python/qmk/keymap.py | 20 ++++++++++++++++++++
lib/python/qmk/tests/test_cli_commands.py | 2 ++
lib/python/qmk/tests/test_qmk_keymap.py | 1 +
3 files changed, 23 insertions(+)
(limited to 'lib/python/qmk')
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py
index 4cf07f59d6..0ac04f6f73 100644
--- a/lib/python/qmk/keymap.py
+++ b/lib/python/qmk/keymap.py
@@ -32,6 +32,7 @@ __INCLUDES__
__KEYMAP_GOES_HERE__
__ENCODER_MAP_GOES_HERE__
+__DIP_SWITCH_MAP_GOES_HERE__
__MACRO_OUTPUT_GOES_HERE__
#ifdef OTHER_KEYMAP_C
@@ -66,6 +67,19 @@ def _generate_encodermap_table(keymap_json):
return lines
+def _generate_dipswitchmap_table(keymap_json):
+ lines = [
+ '#if defined(DIP_SWITCH_ENABLE) && defined(DIP_SWITCH_MAP_ENABLE)',
+ 'const uint16_t PROGMEM dip_switch_map[NUM_DIP_SWITCHES][NUM_DIP_STATES] = {',
+ ]
+ for index, switch in enumerate(keymap_json['dip_switches']):
+ if index != 0:
+ lines[-1] = lines[-1] + ','
+ lines.append(f' DIP_SWITCH_OFF_ON({_strip_any(switch["off"])}, {_strip_any(switch["on"])})')
+ lines.extend(['};', '#endif // defined(DIP_SWITCH_ENABLE) && defined(DIP_SWITCH_MAP_ENABLE)'])
+ return lines
+
+
def _generate_macros_function(keymap_json):
macro_txt = [
'bool process_record_user(uint16_t keycode, keyrecord_t *record) {',
@@ -286,6 +300,12 @@ def generate_c(keymap_json):
encodermap = '\n'.join(encoder_txt)
new_keymap = new_keymap.replace('__ENCODER_MAP_GOES_HERE__', encodermap)
+ dipswitchmap = ''
+ if 'dip_switches' in keymap_json and keymap_json['dip_switches'] is not None:
+ dip_txt = _generate_dipswitchmap_table(keymap_json)
+ dipswitchmap = '\n'.join(dip_txt)
+ new_keymap = new_keymap.replace('__DIP_SWITCH_MAP_GOES_HERE__', dipswitchmap)
+
macros = ''
if 'macros' in keymap_json and keymap_json['macros'] is not None:
macro_txt = _generate_macros_function(keymap_json)
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py
index dd659fe0f2..2716459989 100644
--- a/lib/python/qmk/tests/test_cli_commands.py
+++ b/lib/python/qmk/tests/test_cli_commands.py
@@ -159,6 +159,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+
#ifdef OTHER_KEYMAP_C
# include OTHER_KEYMAP_C
#endif // OTHER_KEYMAP_C
@@ -196,6 +197,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+
#ifdef OTHER_KEYMAP_C
# include OTHER_KEYMAP_C
#endif // OTHER_KEYMAP_C
diff --git a/lib/python/qmk/tests/test_qmk_keymap.py b/lib/python/qmk/tests/test_qmk_keymap.py
index 80cc679b00..34360d3b6d 100644
--- a/lib/python/qmk/tests/test_qmk_keymap.py
+++ b/lib/python/qmk/tests/test_qmk_keymap.py
@@ -27,6 +27,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+
#ifdef OTHER_KEYMAP_C
# include OTHER_KEYMAP_C
#endif // OTHER_KEYMAP_C
--
cgit v1.2.3
From d34cade5abe11da93a6f3d204b00278127299f8b Mon Sep 17 00:00:00 2001
From: Joel Challis
Date: Fri, 12 Sep 2025 13:21:49 +0100
Subject: Generate default encoder resolution for sparse config (#25247)
---
lib/python/qmk/cli/generate/config_h.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
(limited to 'lib/python/qmk')
diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py
index d6d0299291..1ade452f95 100755
--- a/lib/python/qmk/cli/generate/config_h.py
+++ b/lib/python/qmk/cli/generate/config_h.py
@@ -125,11 +125,12 @@ def generate_encoder_config(encoder_json, config_h_lines, postfix=''):
config_h_lines.append(generate_define(f'ENCODER_A_PINS{postfix}', f'{{ {", ".join(a_pads)} }}'))
config_h_lines.append(generate_define(f'ENCODER_B_PINS{postfix}', f'{{ {", ".join(b_pads)} }}'))
- if None in resolutions:
- cli.log.debug(f"Unable to generate ENCODER_RESOLUTION{postfix} configuration")
- elif len(resolutions) == 0:
+ if len(resolutions) == 0 or all(r is None for r in resolutions):
cli.log.debug(f"Skipping ENCODER_RESOLUTION{postfix} configuration")
- elif len(set(resolutions)) == 1:
+ return
+
+ resolutions = [4 if r is None else r for r in resolutions]
+ if len(set(resolutions)) == 1:
config_h_lines.append(generate_define(f'ENCODER_RESOLUTION{postfix}', resolutions[0]))
else:
config_h_lines.append(generate_define(f'ENCODER_RESOLUTIONS{postfix}', f'{{ {", ".join(map(str,resolutions))} }}'))
--
cgit v1.2.3
From 883465d9fb29cd793684ee0ac688ff3517cf8bc0 Mon Sep 17 00:00:00 2001
From: Joel Challis
Date: Mon, 22 Sep 2025 00:01:23 +0100
Subject: Add generic handling to cycle LED/RGB Matrix flags (#24649)
---
data/constants/keycodes/keycodes_0.0.8.hjson | 0
.../keycodes/keycodes_0.0.8_lighting.hjson | 37 ++++++++++++++++
data/mappings/info_config.hjson | 2 +
data/schemas/keyboard.jsonschema | 10 +++++
docs/reference_info_json.md | 6 +++
lib/python/qmk/info.py | 8 ++++
quantum/keycodes.h | 22 +++++++---
quantum/led_matrix/led_matrix.c | 51 ++++++++++++++++++++++
quantum/led_matrix/led_matrix.h | 4 ++
quantum/process_keycode/process_led_matrix.c | 6 +++
quantum/process_keycode/process_rgb_matrix.c | 14 ++++++
quantum/rgb_matrix/rgb_matrix.c | 51 ++++++++++++++++++++++
quantum/rgb_matrix/rgb_matrix.h | 4 ++
13 files changed, 208 insertions(+), 7 deletions(-)
create mode 100644 data/constants/keycodes/keycodes_0.0.8.hjson
create mode 100644 data/constants/keycodes/keycodes_0.0.8_lighting.hjson
(limited to 'lib/python/qmk')
diff --git a/data/constants/keycodes/keycodes_0.0.8.hjson b/data/constants/keycodes/keycodes_0.0.8.hjson
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/data/constants/keycodes/keycodes_0.0.8_lighting.hjson b/data/constants/keycodes/keycodes_0.0.8_lighting.hjson
new file mode 100644
index 0000000000..fa7227235e
--- /dev/null
+++ b/data/constants/keycodes/keycodes_0.0.8_lighting.hjson
@@ -0,0 +1,37 @@
+{
+ "keycodes": {
+ "0x7819": {
+ "group": "led_matrix",
+ "key": "QK_LED_MATRIX_FLAG_NEXT",
+ "label": "LED Matrix Flag Next",
+ "aliases": [
+ "LM_FLGN"
+ ]
+ },
+ "0x781A": {
+ "group": "led_matrix",
+ "key": "QK_LED_MATRIX_FLAG_PREVIOUS",
+ "label": "LED Matrix Flag Previous",
+ "aliases": [
+ "LM_FLGP"
+ ]
+ },
+
+ "0x784D": {
+ "group": "rgb_matrix",
+ "key": "QK_RGB_MATRIX_FLAG_NEXT",
+ "label": "RGB Matrix Flag Next",
+ "aliases": [
+ "RM_FLGN"
+ ]
+ },
+ "0x784E": {
+ "group": "rgb_matrix",
+ "key": "QK_RGB_MATRIX_FLAG_PREVIOUS",
+ "label": "RGB Matrix Flag Previous",
+ "aliases": [
+ "RM_FLGP"
+ ]
+ }
+ }
+}
diff --git a/data/mappings/info_config.hjson b/data/mappings/info_config.hjson
index 4c53aa4339..c4052c64f6 100644
--- a/data/mappings/info_config.hjson
+++ b/data/mappings/info_config.hjson
@@ -98,6 +98,7 @@
// LED Matrix
"LED_MATRIX_CENTER": {"info_key": "led_matrix.center_point", "value_type": "array.int"},
+ "LED_MATRIX_FLAG_STEPS": {"info_key": "led_matrix.flag_steps", "value_type": "array.int"},
"LED_MATRIX_KEYRELEASES": {"info_key": "led_matrix.react_on_keyup", "value_type": "flag"},
"LED_MATRIX_LED_FLUSH_LIMIT": {"info_key": "led_matrix.led_flush_limit", "value_type": "int"},
"LED_MATRIX_LED_PROCESS_LIMIT": {"info_key": "led_matrix.led_process_limit", "value_type": "int", "to_json": false},
@@ -147,6 +148,7 @@
// RGB Matrix
"RGB_MATRIX_CENTER": {"info_key": "rgb_matrix.center_point", "value_type": "array.int"},
+ "RGB_MATRIX_FLAG_STEPS": {"info_key": "rgb_matrix.flag_steps", "value_type": "array.int"},
"RGB_MATRIX_HUE_STEP": {"info_key": "rgb_matrix.hue_steps", "value_type": "int"},
"RGB_MATRIX_KEYRELEASES": {"info_key": "rgb_matrix.react_on_keyup", "value_type": "flag"},
"RGB_MATRIX_LED_FLUSH_LIMIT": {"info_key": "rgb_matrix.led_flush_limit", "value_type": "int"},
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema
index 93b1c82b1c..93fc4ed8a2 100644
--- a/data/schemas/keyboard.jsonschema
+++ b/data/schemas/keyboard.jsonschema
@@ -571,6 +571,11 @@
"maxItems": 2,
"items": {"$ref": "./definitions.jsonschema#/unsigned_int_8"}
},
+ "flag_steps": {
+ "type": "array",
+ "minItems": 1,
+ "items": {"$ref": "./definitions.jsonschema#/unsigned_int_8"}
+ },
"max_brightness": {"$ref": "./definitions.jsonschema#/unsigned_int_8"},
"timeout": {"$ref": "./definitions.jsonschema#/unsigned_int"},
"val_steps": {"$ref": "./definitions.jsonschema#/unsigned_int"},
@@ -656,6 +661,11 @@
"maxItems": 2,
"items": {"$ref": "./definitions.jsonschema#/unsigned_int_8"}
},
+ "flag_steps": {
+ "type": "array",
+ "minItems": 1,
+ "items": {"$ref": "./definitions.jsonschema#/unsigned_int_8"}
+ },
"max_brightness": {"$ref": "./definitions.jsonschema#/unsigned_int_8"},
"timeout": {"$ref": "./definitions.jsonschema#/unsigned_int"},
"hue_steps": {"$ref": "./definitions.jsonschema#/unsigned_int"},
diff --git a/docs/reference_info_json.md b/docs/reference_info_json.md
index 84377ef36c..e7cb8c31e8 100644
--- a/docs/reference_info_json.md
+++ b/docs/reference_info_json.md
@@ -415,6 +415,9 @@ Configures the [LED Matrix](features/led_matrix) feature.
* `center_point` Array: Number
* The centroid (geometric center) of the LEDs. Used for certain effects.
* Default: `[112, 32]`
+ * `flag_steps` Array: Number
+ * A list of flag bitfields that can be cycled through.
+ * Default: `[255, 5, 0]`
* `default`
* `animation` String
* The default effect. Must be one of `led_matrix.animations`
@@ -660,6 +663,9 @@ Configures the [RGB Matrix](features/rgb_matrix) feature.
* `center_point` Array: Number
* The centroid (geometric center) of the LEDs. Used for certain effects.
* Default: `[112, 32]`
+ * `flag_steps` Array: Number
+ * A list of flag bitfields that can be cycled through.
+ * Default: `[255, 5, 2, 0]`
* `default`
* `animation` String
* The default effect. Must be one of `rgb_matrix.animations`
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index f63228b2bc..59a64095e7 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -800,6 +800,14 @@ def _extract_led_config(info_data, keyboard):
if info_data[feature].get('layout', None) and not info_data[feature].get('led_count', None):
info_data[feature]['led_count'] = len(info_data[feature]['layout'])
+ if info_data[feature].get('layout', None) and not info_data[feature].get('flag_steps', None):
+ flags = {0xFF, 0}
+ # if only a single flag is used, assume only all+none flags
+ unique_flags = set(x.get('flags', 0) for x in info_data[feature]['layout'])
+ if len(unique_flags) > 1:
+ flags.update(unique_flags)
+ info_data[feature]['flag_steps'] = sorted(list(flags), reverse=True)
+
return info_data
diff --git a/quantum/keycodes.h b/quantum/keycodes.h
index 6a59aa376d..e5a64d9a71 100644
--- a/quantum/keycodes.h
+++ b/quantum/keycodes.h
@@ -26,11 +26,11 @@
#pragma once
// clang-format off
-#define QMK_KEYCODES_VERSION "0.0.7"
-#define QMK_KEYCODES_VERSION_BCD 0x00000007
+#define QMK_KEYCODES_VERSION "0.0.8"
+#define QMK_KEYCODES_VERSION_BCD 0x00000008
#define QMK_KEYCODES_VERSION_MAJOR 0
#define QMK_KEYCODES_VERSION_MINOR 0
-#define QMK_KEYCODES_VERSION_PATCH 7
+#define QMK_KEYCODES_VERSION_PATCH 8
enum qk_keycode_ranges {
// Ranges
@@ -663,6 +663,8 @@ enum qk_keycode_defines {
QK_LED_MATRIX_BRIGHTNESS_DOWN = 0x7816,
QK_LED_MATRIX_SPEED_UP = 0x7817,
QK_LED_MATRIX_SPEED_DOWN = 0x7818,
+ QK_LED_MATRIX_FLAG_NEXT = 0x7819,
+ QK_LED_MATRIX_FLAG_PREVIOUS = 0x781A,
QK_UNDERGLOW_TOGGLE = 0x7820,
QK_UNDERGLOW_MODE_NEXT = 0x7821,
QK_UNDERGLOW_MODE_PREVIOUS = 0x7822,
@@ -697,6 +699,8 @@ enum qk_keycode_defines {
QK_RGB_MATRIX_VALUE_DOWN = 0x784A,
QK_RGB_MATRIX_SPEED_UP = 0x784B,
QK_RGB_MATRIX_SPEED_DOWN = 0x784C,
+ QK_RGB_MATRIX_FLAG_NEXT = 0x784D,
+ QK_RGB_MATRIX_FLAG_PREVIOUS = 0x784E,
QK_BOOTLOADER = 0x7C00,
QK_REBOOT = 0x7C01,
QK_DEBUG_TOGGLE = 0x7C02,
@@ -1352,6 +1356,8 @@ enum qk_keycode_defines {
LM_BRID = QK_LED_MATRIX_BRIGHTNESS_DOWN,
LM_SPDU = QK_LED_MATRIX_SPEED_UP,
LM_SPDD = QK_LED_MATRIX_SPEED_DOWN,
+ LM_FLGN = QK_LED_MATRIX_FLAG_NEXT,
+ LM_FLGP = QK_LED_MATRIX_FLAG_PREVIOUS,
UG_TOGG = QK_UNDERGLOW_TOGGLE,
UG_NEXT = QK_UNDERGLOW_MODE_NEXT,
UG_PREV = QK_UNDERGLOW_MODE_PREVIOUS,
@@ -1386,6 +1392,8 @@ enum qk_keycode_defines {
RM_VALD = QK_RGB_MATRIX_VALUE_DOWN,
RM_SPDU = QK_RGB_MATRIX_SPEED_UP,
RM_SPDD = QK_RGB_MATRIX_SPEED_DOWN,
+ RM_FLGN = QK_RGB_MATRIX_FLAG_NEXT,
+ RM_FLGP = QK_RGB_MATRIX_FLAG_PREVIOUS,
QK_BOOT = QK_BOOTLOADER,
QK_RBT = QK_REBOOT,
DB_TOGG = QK_DEBUG_TOGGLE,
@@ -1511,10 +1519,10 @@ enum qk_keycode_defines {
#define IS_MACRO_KEYCODE(code) ((code) >= QK_MACRO_0 && (code) <= QK_MACRO_31)
#define IS_CONNECTION_KEYCODE(code) ((code) >= QK_OUTPUT_AUTO && (code) <= QK_BLUETOOTH_PROFILE5)
#define IS_BACKLIGHT_KEYCODE(code) ((code) >= QK_BACKLIGHT_ON && (code) <= QK_BACKLIGHT_TOGGLE_BREATHING)
-#define IS_LED_MATRIX_KEYCODE(code) ((code) >= QK_LED_MATRIX_ON && (code) <= QK_LED_MATRIX_SPEED_DOWN)
+#define IS_LED_MATRIX_KEYCODE(code) ((code) >= QK_LED_MATRIX_ON && (code) <= QK_LED_MATRIX_FLAG_PREVIOUS)
#define IS_UNDERGLOW_KEYCODE(code) ((code) >= QK_UNDERGLOW_TOGGLE && (code) <= QK_UNDERGLOW_SPEED_DOWN)
#define IS_RGB_KEYCODE(code) ((code) >= RGB_MODE_PLAIN && (code) <= RGB_MODE_TWINKLE)
-#define IS_RGB_MATRIX_KEYCODE(code) ((code) >= QK_RGB_MATRIX_ON && (code) <= QK_RGB_MATRIX_SPEED_DOWN)
+#define IS_RGB_MATRIX_KEYCODE(code) ((code) >= QK_RGB_MATRIX_ON && (code) <= QK_RGB_MATRIX_FLAG_PREVIOUS)
#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_LAYER_LOCK)
#define IS_KB_KEYCODE(code) ((code) >= QK_KB_0 && (code) <= QK_KB_31)
#define IS_USER_KEYCODE(code) ((code) >= QK_USER_0 && (code) <= QK_USER_31)
@@ -1537,10 +1545,10 @@ enum qk_keycode_defines {
#define MACRO_KEYCODE_RANGE QK_MACRO_0 ... QK_MACRO_31
#define CONNECTION_KEYCODE_RANGE QK_OUTPUT_AUTO ... QK_BLUETOOTH_PROFILE5
#define BACKLIGHT_KEYCODE_RANGE QK_BACKLIGHT_ON ... QK_BACKLIGHT_TOGGLE_BREATHING
-#define LED_MATRIX_KEYCODE_RANGE QK_LED_MATRIX_ON ... QK_LED_MATRIX_SPEED_DOWN
+#define LED_MATRIX_KEYCODE_RANGE QK_LED_MATRIX_ON ... QK_LED_MATRIX_FLAG_PREVIOUS
#define UNDERGLOW_KEYCODE_RANGE QK_UNDERGLOW_TOGGLE ... QK_UNDERGLOW_SPEED_DOWN
#define RGB_KEYCODE_RANGE RGB_MODE_PLAIN ... RGB_MODE_TWINKLE
-#define RGB_MATRIX_KEYCODE_RANGE QK_RGB_MATRIX_ON ... QK_RGB_MATRIX_SPEED_DOWN
+#define RGB_MATRIX_KEYCODE_RANGE QK_RGB_MATRIX_ON ... QK_RGB_MATRIX_FLAG_PREVIOUS
#define QUANTUM_KEYCODE_RANGE QK_BOOTLOADER ... QK_LAYER_LOCK
#define KB_KEYCODE_RANGE QK_KB_0 ... QK_KB_31
#define USER_KEYCODE_RANGE QK_USER_0 ... QK_USER_31
diff --git a/quantum/led_matrix/led_matrix.c b/quantum/led_matrix/led_matrix.c
index 9c8004cc17..b2665597df 100644
--- a/quantum/led_matrix/led_matrix.c
+++ b/quantum/led_matrix/led_matrix.c
@@ -70,6 +70,13 @@ uint8_t g_led_frame_buffer[MATRIX_ROWS][MATRIX_COLS] = {{0}};
last_hit_t g_last_hit_tracker;
#endif // LED_MATRIX_KEYREACTIVE_ENABLED
+#ifndef LED_MATRIX_FLAG_STEPS
+# define LED_MATRIX_FLAG_STEPS \
+ { LED_FLAG_ALL, LED_FLAG_KEYLIGHT | LED_FLAG_MODIFIER, LED_FLAG_NONE }
+#endif
+static const uint8_t led_matrix_flag_steps[] = LED_MATRIX_FLAG_STEPS;
+#define LED_MATRIX_FLAG_STEPS_COUNT ARRAY_SIZE(led_matrix_flag_steps)
+
// internals
static bool suspend_state = false;
static uint8_t led_last_enable = UINT8_MAX;
@@ -661,6 +668,50 @@ void led_matrix_set_flags_noeeprom(led_flags_t flags) {
led_matrix_set_flags_eeprom_helper(flags, false);
}
+void led_matrix_flags_step_helper(bool write_to_eeprom) {
+ led_flags_t flags = led_matrix_get_flags();
+
+ uint8_t next = 0;
+ for (uint8_t i = 0; i < LED_MATRIX_FLAG_STEPS_COUNT; i++) {
+ if (led_matrix_flag_steps[i] == flags) {
+ next = i == LED_MATRIX_FLAG_STEPS_COUNT - 1 ? 0 : i + 1;
+ break;
+ }
+ }
+
+ led_matrix_set_flags_eeprom_helper(led_matrix_flag_steps[next], write_to_eeprom);
+}
+
+void led_matrix_flags_step_noeeprom(void) {
+ led_matrix_flags_step_helper(false);
+}
+
+void led_matrix_flags_step(void) {
+ led_matrix_flags_step_helper(true);
+}
+
+void led_matrix_flags_step_reverse_helper(bool write_to_eeprom) {
+ led_flags_t flags = led_matrix_get_flags();
+
+ uint8_t next = 0;
+ for (uint8_t i = 0; i < LED_MATRIX_FLAG_STEPS_COUNT; i++) {
+ if (led_matrix_flag_steps[i] == flags) {
+ next = i == 0 ? LED_MATRIX_FLAG_STEPS_COUNT - 1 : i - 1;
+ break;
+ }
+ }
+
+ led_matrix_set_flags_eeprom_helper(led_matrix_flag_steps[next], write_to_eeprom);
+}
+
+void led_matrix_flags_step_reverse_noeeprom(void) {
+ led_matrix_flags_step_reverse_helper(false);
+}
+
+void led_matrix_flags_step_reverse(void) {
+ led_matrix_flags_step_reverse_helper(true);
+}
+
// LED Matrix naming
#undef LED_MATRIX_EFFECT
#ifdef LED_MATRIX_MODE_NAME_ENABLE
diff --git a/quantum/led_matrix/led_matrix.h b/quantum/led_matrix/led_matrix.h
index 9a49515ab2..f484c700f4 100644
--- a/quantum/led_matrix/led_matrix.h
+++ b/quantum/led_matrix/led_matrix.h
@@ -183,6 +183,10 @@ void led_matrix_decrease_speed_noeeprom(void);
led_flags_t led_matrix_get_flags(void);
void led_matrix_set_flags(led_flags_t flags);
void led_matrix_set_flags_noeeprom(led_flags_t flags);
+void led_matrix_flags_step_noeeprom(void);
+void led_matrix_flags_step(void);
+void led_matrix_flags_step_reverse_noeeprom(void);
+void led_matrix_flags_step_reverse(void);
#ifdef LED_MATRIX_MODE_NAME_ENABLE
const char *led_matrix_get_mode_name(uint8_t mode);
diff --git a/quantum/process_keycode/process_led_matrix.c b/quantum/process_keycode/process_led_matrix.c
index 7f95bf1011..3342b33b92 100644
--- a/quantum/process_keycode/process_led_matrix.c
+++ b/quantum/process_keycode/process_led_matrix.c
@@ -40,6 +40,12 @@ bool process_led_matrix(uint16_t keycode, keyrecord_t *record) {
case QK_LED_MATRIX_SPEED_DOWN:
led_matrix_decrease_speed();
return false;
+ case QK_LED_MATRIX_FLAG_NEXT:
+ led_matrix_flags_step();
+ return false;
+ case QK_LED_MATRIX_FLAG_PREVIOUS:
+ led_matrix_flags_step_reverse();
+ return false;
}
}
diff --git a/quantum/process_keycode/process_rgb_matrix.c b/quantum/process_keycode/process_rgb_matrix.c
index fd2aa1a0c7..c18212294d 100644
--- a/quantum/process_keycode/process_rgb_matrix.c
+++ b/quantum/process_keycode/process_rgb_matrix.c
@@ -94,6 +94,20 @@ bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) {
rgb_matrix_decrease_speed();
}
return false;
+ case QK_RGB_MATRIX_FLAG_NEXT:
+ if (shifted) {
+ rgb_matrix_flags_step_reverse();
+ } else {
+ rgb_matrix_flags_step();
+ }
+ return false;
+ case QK_RGB_MATRIX_FLAG_PREVIOUS:
+ if (shifted) {
+ rgb_matrix_flags_step();
+ } else {
+ rgb_matrix_flags_step_reverse();
+ }
+ return false;
}
}
diff --git a/quantum/rgb_matrix/rgb_matrix.c b/quantum/rgb_matrix/rgb_matrix.c
index ab0aa17512..19edfb52b0 100644
--- a/quantum/rgb_matrix/rgb_matrix.c
+++ b/quantum/rgb_matrix/rgb_matrix.c
@@ -72,6 +72,13 @@ uint8_t g_rgb_frame_buffer[MATRIX_ROWS][MATRIX_COLS] = {{0}};
last_hit_t g_last_hit_tracker;
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED
+#ifndef RGB_MATRIX_FLAG_STEPS
+# define RGB_MATRIX_FLAG_STEPS \
+ { LED_FLAG_ALL, LED_FLAG_KEYLIGHT | LED_FLAG_MODIFIER, LED_FLAG_UNDERGLOW, LED_FLAG_NONE }
+#endif
+static const uint8_t rgb_matrix_flag_steps[] = RGB_MATRIX_FLAG_STEPS;
+#define RGB_MATRIX_FLAG_STEPS_COUNT ARRAY_SIZE(rgb_matrix_flag_steps)
+
// internals
static bool suspend_state = false;
static uint8_t rgb_last_enable = UINT8_MAX;
@@ -747,6 +754,50 @@ void rgb_matrix_set_flags_noeeprom(led_flags_t flags) {
rgb_matrix_set_flags_eeprom_helper(flags, false);
}
+void rgb_matrix_flags_step_helper(bool write_to_eeprom) {
+ led_flags_t flags = rgb_matrix_get_flags();
+
+ uint8_t next = 0;
+ for (uint8_t i = 0; i < RGB_MATRIX_FLAG_STEPS_COUNT; i++) {
+ if (rgb_matrix_flag_steps[i] == flags) {
+ next = i == RGB_MATRIX_FLAG_STEPS_COUNT - 1 ? 0 : i + 1;
+ break;
+ }
+ }
+
+ rgb_matrix_set_flags_eeprom_helper(rgb_matrix_flag_steps[next], write_to_eeprom);
+}
+
+void rgb_matrix_flags_step_noeeprom(void) {
+ rgb_matrix_flags_step_helper(false);
+}
+
+void rgb_matrix_flags_step(void) {
+ rgb_matrix_flags_step_helper(true);
+}
+
+void rgb_matrix_flags_step_reverse_helper(bool write_to_eeprom) {
+ led_flags_t flags = rgb_matrix_get_flags();
+
+ uint8_t next = 0;
+ for (uint8_t i = 0; i < RGB_MATRIX_FLAG_STEPS_COUNT; i++) {
+ if (rgb_matrix_flag_steps[i] == flags) {
+ next = i == 0 ? RGB_MATRIX_FLAG_STEPS_COUNT - 1 : i - 1;
+ break;
+ }
+ }
+
+ rgb_matrix_set_flags_eeprom_helper(rgb_matrix_flag_steps[next], write_to_eeprom);
+}
+
+void rgb_matrix_flags_step_reverse_noeeprom(void) {
+ rgb_matrix_flags_step_reverse_helper(false);
+}
+
+void rgb_matrix_flags_step_reverse(void) {
+ rgb_matrix_flags_step_reverse_helper(true);
+}
+
//----------------------------------------------------------
// RGB Matrix naming
#undef RGB_MATRIX_EFFECT
diff --git a/quantum/rgb_matrix/rgb_matrix.h b/quantum/rgb_matrix/rgb_matrix.h
index a91dded4a8..f800679b46 100644
--- a/quantum/rgb_matrix/rgb_matrix.h
+++ b/quantum/rgb_matrix/rgb_matrix.h
@@ -218,6 +218,10 @@ void rgb_matrix_decrease_speed_noeeprom(void);
led_flags_t rgb_matrix_get_flags(void);
void rgb_matrix_set_flags(led_flags_t flags);
void rgb_matrix_set_flags_noeeprom(led_flags_t flags);
+void rgb_matrix_flags_step_noeeprom(void);
+void rgb_matrix_flags_step(void);
+void rgb_matrix_flags_step_reverse_noeeprom(void);
+void rgb_matrix_flags_step_reverse(void);
void rgb_matrix_update_pwm_buffers(void);
#ifdef RGB_MATRIX_MODE_NAME_ENABLE
--
cgit v1.2.3
From 6e35013bc2e30af022cdb8c176e869c5c94ee17a Mon Sep 17 00:00:00 2001
From: Joel Challis
Date: Sat, 8 Nov 2025 20:48:48 +0000
Subject: Generate `CUSTOM_MATRIX = lite` without `matrix_pins.custom` (#25453)
---
keyboards/argyle/keyboard.json | 1 -
keyboards/keychron/c2_pro/info.json | 1 -
keyboards/keychron/q1v2/info.json | 1 -
lib/python/qmk/cli/generate/rules_mk.py | 9 ++++---
lib/python/qmk/info.py | 42 +++++++++++++++++++++------------
5 files changed, 31 insertions(+), 23 deletions(-)
(limited to 'lib/python/qmk')
diff --git a/keyboards/argyle/keyboard.json b/keyboards/argyle/keyboard.json
index a7bfc335df..d97f067be8 100755
--- a/keyboards/argyle/keyboard.json
+++ b/keyboards/argyle/keyboard.json
@@ -11,7 +11,6 @@
},
"matrix_pins": {
"cols": ["D1", "D4", "D5", "D6", "D7", "B0", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN"],
- "custom": true,
"custom_lite": true,
"rows": ["D0", "C3", "B1", "B2", "B3"]
},
diff --git a/keyboards/keychron/c2_pro/info.json b/keyboards/keychron/c2_pro/info.json
index 9c0e1ac72e..8a7d0968b1 100644
--- a/keyboards/keychron/c2_pro/info.json
+++ b/keyboards/keychron/c2_pro/info.json
@@ -26,7 +26,6 @@
"matrix_pins": {
"cols": ["A10", "A9", "A8", "B1", "B0", "A7", "A6", "A5", "A4", "A3", "A2", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "NO_PIN", "C14"],
"rows": ["B5", "B4", "B3", "A15", "A14", "A13"],
- "custom": true,
"custom_lite": true
},
"diode_direction": "ROW2COL"
diff --git a/keyboards/keychron/q1v2/info.json b/keyboards/keychron/q1v2/info.json
index f0342254fa..ba6f908bbb 100644
--- a/keyboards/keychron/q1v2/info.json
+++ b/keyboards/keychron/q1v2/info.json
@@ -27,7 +27,6 @@
"matrix_pins": {
"cols": ["C14", "C15", "A0", "A1", "A2", "A3", "A4", "A5", null, null, null, null, null, null, null, null],
"rows": ["B5", "B4", "B3", "A15", "A14", "A13"],
- "custom": true,
"custom_lite": true
},
"diode_direction": "ROW2COL",
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py
index 358a22fd1d..16084bded1 100755
--- a/lib/python/qmk/cli/generate/rules_mk.py
+++ b/lib/python/qmk/cli/generate/rules_mk.py
@@ -96,11 +96,10 @@ def generate_rules_mk(cli):
rules_mk_lines.append(generate_rule('SPLIT_TRANSPORT', 'custom'))
# Set CUSTOM_MATRIX, if needed
- if kb_info_json.get('matrix_pins', {}).get('custom'):
- if kb_info_json.get('matrix_pins', {}).get('custom_lite'):
- rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'lite'))
- else:
- rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'yes'))
+ if kb_info_json.get('matrix_pins', {}).get('custom_lite'):
+ rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'lite'))
+ elif kb_info_json.get('matrix_pins', {}).get('custom'):
+ rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'yes'))
if converter:
rules_mk_lines.append(generate_rule('CONVERT_TO', converter))
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index 59a64095e7..e6d51e1239 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -302,6 +302,24 @@ def _extract_features(info_data, rules):
return info_data
+def _extract_matrix_rules(info_data, rules):
+ """Find all the features enabled in rules.mk.
+ """
+ if rules.get('CUSTOM_MATRIX', 'no') != 'no':
+ if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']:
+ _log_warning(info_data, 'Custom Matrix is specified in both info.json and rules.mk, the rules.mk values win.')
+
+ if 'matrix_pins' not in info_data:
+ info_data['matrix_pins'] = {}
+
+ if rules['CUSTOM_MATRIX'] == 'lite':
+ info_data['matrix_pins']['custom_lite'] = True
+ else:
+ info_data['matrix_pins']['custom'] = True
+
+ return info_data
+
+
def _pin_name(pin):
"""Returns the proper representation for a pin.
"""
@@ -552,7 +570,6 @@ def _extract_matrix_info(info_data, config_c):
row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip()
col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip()
direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1]
- info_snippet = {}
if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:
if 'matrix_size' in info_data:
@@ -567,26 +584,20 @@ def _extract_matrix_info(info_data, config_c):
if 'matrix_pins' in info_data and 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']:
_log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.')
- info_snippet['cols'] = _extract_pins(col_pins)
- info_snippet['rows'] = _extract_pins(row_pins)
+ if 'matrix_pins' not in info_data:
+ info_data['matrix_pins'] = {}
+
+ info_data['matrix_pins']['cols'] = _extract_pins(col_pins)
+ info_data['matrix_pins']['rows'] = _extract_pins(row_pins)
if direct_pins:
if 'matrix_pins' in info_data and 'direct' in info_data['matrix_pins']:
_log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.')
- info_snippet['direct'] = _extract_direct_matrix(direct_pins)
-
- if config_c.get('CUSTOM_MATRIX', 'no') != 'no':
- if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']:
- _log_warning(info_data, 'Custom Matrix is specified in both info.json and config.h, the config.h values win.')
-
- info_snippet['custom'] = True
-
- if config_c['CUSTOM_MATRIX'] == 'lite':
- info_snippet['custom_lite'] = True
+ if 'matrix_pins' not in info_data:
+ info_data['matrix_pins'] = {}
- if info_snippet:
- info_data['matrix_pins'] = info_snippet
+ info_data['matrix_pins']['direct'] = _extract_direct_matrix(direct_pins)
return info_data
@@ -755,6 +766,7 @@ def _extract_rules_mk(info_data, rules):
# Merge in config values that can't be easily mapped
_extract_features(info_data, rules)
+ _extract_matrix_rules(info_data, rules)
return info_data
--
cgit v1.2.3
From 8ec3de0f92219ee783425f406ff188597ec5e8c6 Mon Sep 17 00:00:00 2001
From: Joel Challis
Date: Tue, 11 Nov 2025 21:59:06 +0000
Subject: Add return code to `qmk userspace-doctor` (#25775)
---
lib/python/qmk/cli/userspace/doctor.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
(limited to 'lib/python/qmk')
diff --git a/lib/python/qmk/cli/userspace/doctor.py b/lib/python/qmk/cli/userspace/doctor.py
index 2b7e29aa7e..7c016e5a2f 100644
--- a/lib/python/qmk/cli/userspace/doctor.py
+++ b/lib/python/qmk/cli/userspace/doctor.py
@@ -2,10 +2,12 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from milc import cli
-from qmk.constants import QMK_FIRMWARE
+from qmk.constants import QMK_FIRMWARE, HAS_QMK_USERSPACE
from qmk.cli.doctor.main import userspace_tests
@cli.subcommand('Checks userspace configuration.')
def userspace_doctor(cli):
userspace_tests(QMK_FIRMWARE)
+
+ return 0 if HAS_QMK_USERSPACE else 1
--
cgit v1.2.3
From 1a7f544e0df833f9709aaa724f9fd8b5c5c274de Mon Sep 17 00:00:00 2001
From: Jack Sangdahl
Date: Fri, 21 Nov 2025 23:57:58 -0700
Subject: [CLI] Lint error on missing keyboard readme (#25814)
---
lib/python/qmk/cli/lint.py | 4 ++++
1 file changed, 4 insertions(+)
(limited to 'lib/python/qmk')
diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py
index 484ddb5bd9..8a128ce6d2 100644
--- a/lib/python/qmk/cli/lint.py
+++ b/lib/python/qmk/cli/lint.py
@@ -304,6 +304,10 @@ def keyboard_check(kb): # noqa C901
cli.log.error(f'{kb}: The file "{file}" should not exist!')
ok = False
+ if not _get_readme_files(kb):
+ cli.log.error(f'{kb}: Is missing a readme.md file!')
+ ok = False
+
for file in _get_readme_files(kb):
if _is_invalid_readme(file):
cli.log.error(f'{kb}: The file "{file}" still contains template tokens!')
--
cgit v1.2.3
From 28a11ff6f7a721820cc335287e44e2fefe22ac71 Mon Sep 17 00:00:00 2001
From: Joel Challis
Date: Sun, 23 Nov 2025 05:02:32 +0000
Subject: Fix preference of output file for 'qmk generate-autocorrect-data'
(#25818)
---
lib/python/qmk/cli/generate/autocorrect_data.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
(limited to 'lib/python/qmk')
diff --git a/lib/python/qmk/cli/generate/autocorrect_data.py b/lib/python/qmk/cli/generate/autocorrect_data.py
index 01a29b46fe..4f322adce2 100644
--- a/lib/python/qmk/cli/generate/autocorrect_data.py
+++ b/lib/python/qmk/cli/generate/autocorrect_data.py
@@ -250,8 +250,8 @@ def to_hex(b: int) -> str:
@cli.argument('filename', type=normpath, help='The autocorrection database file')
-@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
-@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
+@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a output file is supplied.')
+@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a output file is supplied.')
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.subcommand('Generate the autocorrection data file from a dictionary file.')
@@ -263,7 +263,7 @@ def generate_autocorrect_data(cli):
current_keyboard = cli.args.keyboard or cli.config.user.keyboard or cli.config.generate_autocorrect_data.keyboard
current_keymap = cli.args.keymap or cli.config.user.keymap or cli.config.generate_autocorrect_data.keymap
- if current_keyboard and current_keymap:
+ if not cli.args.output and current_keyboard and current_keymap:
cli.args.output = locate_keymap(current_keyboard, current_keymap).parent / 'autocorrect_data.h'
assert all(0 <= b <= 255 for b in data)
--
cgit v1.2.3
From fd65390496cb47b3164c507656798664b8c2fcd1 Mon Sep 17 00:00:00 2001
From: Xelus22
Date: Sun, 23 Nov 2025 22:21:13 +1100
Subject: [core] add BCD versions of QMK Version (#25804)
Co-authored-by: Joel Challis
---
lib/python/qmk/cli/generate/version_h.py | 4 ++++
lib/python/qmk/util.py | 10 ++++++++++
2 files changed, 14 insertions(+)
(limited to 'lib/python/qmk')
diff --git a/lib/python/qmk/cli/generate/version_h.py b/lib/python/qmk/cli/generate/version_h.py
index fd87df3617..8156e85559 100644
--- a/lib/python/qmk/cli/generate/version_h.py
+++ b/lib/python/qmk/cli/generate/version_h.py
@@ -8,6 +8,7 @@ from qmk.path import normpath
from qmk.commands import dump_lines
from qmk.git import git_get_qmk_hash, git_get_version, git_is_dirty
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
+from qmk.util import triplet_to_bcd
TIME_FMT = '%Y-%m-%d-%H:%M:%S'
@@ -32,12 +33,14 @@ def generate_version_h(cli):
git_dirty = False
git_version = "NA"
git_qmk_hash = "NA"
+ git_bcd_version = "0x00000000"
chibios_version = "NA"
chibios_contrib_version = "NA"
else:
git_dirty = git_is_dirty()
git_version = git_get_version() or current_time
git_qmk_hash = git_get_qmk_hash() or "Unknown"
+ git_bcd_version = triplet_to_bcd(git_version)
chibios_version = git_get_version("chibios", "os") or current_time
chibios_contrib_version = git_get_version("chibios-contrib", "os") or current_time
@@ -48,6 +51,7 @@ def generate_version_h(cli):
f"""
#define QMK_VERSION "{git_version}"
#define QMK_BUILDDATE "{current_time}"
+#define QMK_VERSION_BCD {git_bcd_version}
#define QMK_GIT_HASH "{git_qmk_hash}{'*' if git_dirty else ''}"
#define CHIBIOS_VERSION "{chibios_version}"
#define CHIBIOS_CONTRIB_VERSION "{chibios_contrib_version}"
diff --git a/lib/python/qmk/util.py b/lib/python/qmk/util.py
index 8f99410e1d..6da684a577 100644
--- a/lib/python/qmk/util.py
+++ b/lib/python/qmk/util.py
@@ -3,9 +3,12 @@
import contextlib
import multiprocessing
import sys
+import re
from milc import cli
+TRIPLET_PATTERN = re.compile(r'^(\d+)\.(\d+)\.(\d+)')
+
maybe_exit_should_exit = True
maybe_exit_reraise = False
@@ -96,3 +99,10 @@ def parallel_map(*args, **kwargs):
# before the results are returned. Returning a list ensures results are
# materialised before any worker pool is shut down.
return list(map_fn(*args, **kwargs))
+
+
+def triplet_to_bcd(ver: str):
+ m = TRIPLET_PATTERN.match(ver)
+ if not m:
+ return '0x00000000'
+ return f'0x{int(m.group(1)):02d}{int(m.group(2)):02d}{int(m.group(3)):04d}'
--
cgit v1.2.3
From 53de903fb89d4138fdc38f98d266db0fec9548b1 Mon Sep 17 00:00:00 2001
From: Joel Challis
Date: Sun, 23 Nov 2025 11:21:55 +0000
Subject: Better defaulting of `{RGB,LED}_MATRIX_DEFAULT_FLAGS` (#25785)
---
lib/python/qmk/info.py | 31 ++++++++++++++++++++++++++-----
1 file changed, 26 insertions(+), 5 deletions(-)
(limited to 'lib/python/qmk')
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index e6d51e1239..a0b8fe72b6 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -5,6 +5,7 @@ import os
from pathlib import Path
import jsonschema
from dotty_dict import dotty
+from enum import IntFlag
from milc import cli
@@ -21,6 +22,15 @@ true_values = ['1', 'on', 'yes']
false_values = ['0', 'off', 'no']
+class LedFlags(IntFlag):
+ ALL = 0xFF
+ NONE = 0x00
+ MODIFIER = 0x01
+ UNDERGLOW = 0x02
+ KEYLIGHT = 0x04
+ INDICATOR = 0x08
+
+
def _keyboard_in_layout_name(keyboard, layout):
"""Validate that a layout macro does not contain name of keyboard
"""
@@ -813,12 +823,23 @@ def _extract_led_config(info_data, keyboard):
info_data[feature]['led_count'] = len(info_data[feature]['layout'])
if info_data[feature].get('layout', None) and not info_data[feature].get('flag_steps', None):
- flags = {0xFF, 0}
+ flags = {LedFlags.ALL, LedFlags.NONE}
+ default_flags = {LedFlags.MODIFIER | LedFlags.KEYLIGHT, LedFlags.UNDERGLOW}
+
# if only a single flag is used, assume only all+none flags
- unique_flags = set(x.get('flags', 0) for x in info_data[feature]['layout'])
- if len(unique_flags) > 1:
- flags.update(unique_flags)
- info_data[feature]['flag_steps'] = sorted(list(flags), reverse=True)
+ kb_flags = set(x.get('flags', LedFlags.NONE) for x in info_data[feature]['layout'])
+ if len(kb_flags) > 1:
+ # check if any part of LED flag is with the defaults
+ unique_flags = set()
+ for candidate in default_flags:
+ if any(candidate & flag for flag in kb_flags):
+ unique_flags.add(candidate)
+
+ # if we still have a single flag, assume only all+none
+ if len(unique_flags) > 1:
+ flags.update(unique_flags)
+
+ info_data[feature]['flag_steps'] = sorted([int(flag) for flag in flags], reverse=True)
return info_data
--
cgit v1.2.3