diff --git a/app/Kconfig b/app/Kconfig index 1189c6547b07..28ac17221034 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -258,6 +258,18 @@ rsource "src/split/Kconfig" #Basic Keyboard Setup endmenu +menu "Keymaps" + +config ZMK_KEYMAP_LAYER_REORDERING + bool "Layer Reordering Support" + +config ZMK_KEYMAP_SETTINGS_STORAGE + bool "Settings Save/Load" + depends on SETTINGS + depends on ZMK_BEHAVIOR_LOCAL_IDS + +endmenu # Keymaps + rsource "src/studio/Kconfig" menu "Display/LED Options" diff --git a/app/include/zmk/keymap.h b/app/include/zmk/keymap.h index 0d7dbaf33b3d..c30f9ecb2932 100644 --- a/app/include/zmk/keymap.h +++ b/app/include/zmk/keymap.h @@ -12,17 +12,44 @@ #define ZMK_KEYMAP_LAYERS_LEN \ (DT_FOREACH_CHILD(DT_INST(0, zmk_keymap), ZMK_LAYER_CHILD_LEN_PLUS_ONE) 0) +typedef uint8_t zmk_keymap_layer_id_t; +typedef uint8_t zmk_keymap_layer_index_t; + typedef uint32_t zmk_keymap_layers_state_t; -uint8_t zmk_keymap_layer_default(void); +zmk_keymap_layer_id_t zmk_keymap_layer_index_to_id(zmk_keymap_layer_index_t layer_index); + +zmk_keymap_layer_id_t zmk_keymap_layer_default(void); zmk_keymap_layers_state_t zmk_keymap_layer_state(void); -bool zmk_keymap_layer_active(uint8_t layer); -uint8_t zmk_keymap_highest_layer_active(void); -int zmk_keymap_layer_activate(uint8_t layer); -int zmk_keymap_layer_deactivate(uint8_t layer); -int zmk_keymap_layer_toggle(uint8_t layer); -int zmk_keymap_layer_to(uint8_t layer); -const char *zmk_keymap_layer_name(uint8_t layer); +bool zmk_keymap_layer_active(zmk_keymap_layer_id_t layer); +zmk_keymap_layer_id_t zmk_keymap_highest_layer_active(void); +int zmk_keymap_layer_activate(zmk_keymap_layer_id_t layer); +int zmk_keymap_layer_deactivate(zmk_keymap_layer_id_t layer); +int zmk_keymap_layer_toggle(zmk_keymap_layer_id_t layer); +int zmk_keymap_layer_to(zmk_keymap_layer_id_t layer); +const char *zmk_keymap_layer_name(zmk_keymap_layer_id_t layer); + +const struct zmk_behavior_binding *zmk_keymap_get_layer_binding_at_idx(zmk_keymap_layer_id_t layer, + uint8_t binding_idx); +int zmk_keymap_set_layer_binding_at_idx(zmk_keymap_layer_id_t layer, uint8_t binding_idx, + const struct zmk_behavior_binding binding); + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + +int zmk_keymap_move_layer(zmk_keymap_layer_index_t start_idx, zmk_keymap_layer_index_t dest_idx); + +#endif + +/** + * @brief Check if there are any unsaved keymap changes. + * + * @retval 0 if there are no changes. + * @retval 1 if there are changes. + */ +int zmk_keymap_check_unsaved_changes(void); + +int zmk_keymap_save_changes(void); +int zmk_keymap_discard_changes(void); int zmk_keymap_position_state_changed(uint8_t source, uint32_t position, bool pressed, int64_t timestamp); diff --git a/app/src/keymap.c b/app/src/keymap.c index 94bd12048cf8..833f11fe7f0e 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -6,6 +6,7 @@ #include #include +#include #include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -27,7 +28,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include static zmk_keymap_layers_state_t _zmk_keymap_layer_state = 0; -static uint8_t _zmk_keymap_layer_default = 0; +static zmk_keymap_layer_id_t _zmk_keymap_layer_default = 0; #define DT_DRV_COMPAT zmk_keymap @@ -67,6 +68,12 @@ static uint8_t _zmk_keymap_layer_default = 0; // still send the release event to the behavior in that layer also. static uint32_t zmk_keymap_active_behavior_layer[ZMK_KEYMAP_LEN]; +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + +static uint8_t keymap_layer_orders[ZMK_KEYMAP_LAYERS_LEN]; + +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + static struct zmk_behavior_binding zmk_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { DT_INST_FOREACH_CHILD_SEP(0, TRANSFORMED_LAYER, (, ))}; @@ -81,23 +88,50 @@ static struct zmk_behavior_binding #endif /* ZMK_KEYMAP_HAS_SENSORS */ -static inline int set_layer_state(uint8_t layer, bool state) { +#define ASSERT_LAYER_VAL(_layer, _fail_ret) \ + if ((_layer) >= ZMK_KEYMAP_LAYERS_LEN) { \ + return (_fail_ret); \ + } + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + +uint8_t map_layer_id_to_index(zmk_keymap_layer_id_t layer_id) { + for (uint8_t i = 0; i < ZMK_KEYMAP_LAYERS_LEN; i++) { + if (keymap_layer_orders[i] == layer_id) { + return i; + } + } + + return UINT8_MAX; +} + +#define LAYER_INDEX_TO_ID(_layer) keymap_layer_orders[_layer] +#define LAYER_ID_TO_INDEX(_layer) map_layer_id_to_index(_layer) + +#else + +#define LAYER_INDEX_TO_ID(_layer) _layer +#define LAYER_ID_TO_INDEX(_layer) _layer + +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + +static inline int set_layer_state(zmk_keymap_layer_id_t layer_id, bool state) { int ret = 0; - if (layer >= ZMK_KEYMAP_LAYERS_LEN) { + if (layer_id >= ZMK_KEYMAP_LAYERS_LEN) { return -EINVAL; } // Default layer should *always* remain active - if (layer == _zmk_keymap_layer_default && !state) { + if (layer_id == _zmk_keymap_layer_default && !state) { return 0; } zmk_keymap_layers_state_t old_state = _zmk_keymap_layer_state; - WRITE_BIT(_zmk_keymap_layer_state, layer, state); + WRITE_BIT(_zmk_keymap_layer_state, layer_id, state); // Don't send state changes unless there was an actual change if (old_state != _zmk_keymap_layer_state) { - LOG_DBG("layer_changed: layer %d state %d", layer, state); - ret = raise_layer_state_changed(layer, state); + LOG_DBG("layer_changed: layer %d state %d", layer_id, state); + ret = raise_layer_state_changed(layer_id, state); if (ret < 0) { LOG_WRN("Failed to raise layer state changed (%d)", ret); } @@ -106,21 +140,28 @@ static inline int set_layer_state(uint8_t layer, bool state) { return ret; } -uint8_t zmk_keymap_layer_default(void) { return _zmk_keymap_layer_default; } +zmk_keymap_layer_id_t zmk_keymap_layer_index_to_id(zmk_keymap_layer_index_t layer_index) { + ASSERT_LAYER_VAL(layer_index, UINT8_MAX); + + return LAYER_INDEX_TO_ID(layer_index); +} + +zmk_keymap_layer_id_t zmk_keymap_layer_default(void) { return _zmk_keymap_layer_default; } zmk_keymap_layers_state_t zmk_keymap_layer_state(void) { return _zmk_keymap_layer_state; } -bool zmk_keymap_layer_active_with_state(uint8_t layer, zmk_keymap_layers_state_t state_to_test) { +bool zmk_keymap_layer_active_with_state(zmk_keymap_layer_id_t layer, + zmk_keymap_layers_state_t state_to_test) { // The default layer is assumed to be ALWAYS ACTIVE so we include an || here to ensure nobody // breaks up that assumption by accident return (state_to_test & (BIT(layer))) == (BIT(layer)) || layer == _zmk_keymap_layer_default; }; -bool zmk_keymap_layer_active(uint8_t layer) { +bool zmk_keymap_layer_active(zmk_keymap_layer_id_t layer) { return zmk_keymap_layer_active_with_state(layer, _zmk_keymap_layer_state); }; -uint8_t zmk_keymap_highest_layer_active(void) { +zmk_keymap_layer_id_t zmk_keymap_highest_layer_active(void) { for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) { if (zmk_keymap_layer_active(layer)) { return layer; @@ -129,11 +170,13 @@ uint8_t zmk_keymap_highest_layer_active(void) { return zmk_keymap_layer_default(); } -int zmk_keymap_layer_activate(uint8_t layer) { return set_layer_state(layer, true); }; +int zmk_keymap_layer_activate(zmk_keymap_layer_id_t layer) { return set_layer_state(layer, true); }; -int zmk_keymap_layer_deactivate(uint8_t layer) { return set_layer_state(layer, false); }; +int zmk_keymap_layer_deactivate(zmk_keymap_layer_id_t layer) { + return set_layer_state(layer, false); +}; -int zmk_keymap_layer_toggle(uint8_t layer) { +int zmk_keymap_layer_toggle(zmk_keymap_layer_id_t layer) { if (zmk_keymap_layer_active(layer)) { return zmk_keymap_layer_deactivate(layer); } @@ -141,7 +184,7 @@ int zmk_keymap_layer_toggle(uint8_t layer) { return zmk_keymap_layer_activate(layer); }; -int zmk_keymap_layer_to(uint8_t layer) { +int zmk_keymap_layer_to(zmk_keymap_layer_id_t layer) { for (int i = ZMK_KEYMAP_LAYERS_LEN - 1; i >= 0; i--) { zmk_keymap_layer_deactivate(i); } @@ -151,18 +194,179 @@ int zmk_keymap_layer_to(uint8_t layer) { return 0; } -bool is_active_layer(uint8_t layer, zmk_keymap_layers_state_t layer_state) { - return (layer_state & BIT(layer)) == BIT(layer) || layer == _zmk_keymap_layer_default; +const char *zmk_keymap_layer_name(zmk_keymap_layer_id_t layer_id) { + ASSERT_LAYER_VAL(layer_id, NULL) + + return zmk_keymap_layer_names[layer_id]; } -const char *zmk_keymap_layer_name(uint8_t layer) { - if (layer >= ZMK_KEYMAP_LAYERS_LEN) { +const struct zmk_behavior_binding * +zmk_keymap_get_layer_binding_at_idx(zmk_keymap_layer_id_t layer_id, uint8_t binding_idx) { + if (binding_idx >= ZMK_KEYMAP_LEN) { return NULL; } - return zmk_keymap_layer_names[layer]; + ASSERT_LAYER_VAL(layer_id, NULL) + + return &zmk_keymap[layer_id][binding_idx]; +} + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + +#define PENDING_ARRAY_SIZE DIV_ROUND_UP(ZMK_KEYMAP_LEN, 8) + +static uint8_t zmk_keymap_layer_pending_changes[ZMK_KEYMAP_LAYERS_LEN][PENDING_ARRAY_SIZE]; + +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + +int zmk_keymap_set_layer_binding_at_idx(zmk_keymap_layer_id_t layer_id, uint8_t binding_idx, + struct zmk_behavior_binding binding) { + if (binding_idx >= ZMK_KEYMAP_LEN) { + return -EINVAL; + } + + ASSERT_LAYER_VAL(layer_id, -EINVAL) + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + uint8_t *pending = zmk_keymap_layer_pending_changes[layer_id]; + + WRITE_BIT(pending[binding_idx / 8], binding_idx % 8, 1); +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + + // TODO: Need a mutex to protect access to the keymap data? + memcpy(&zmk_keymap[layer_id][binding_idx], &binding, sizeof(binding)); + + return 0; +} + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + +static uint8_t settings_layer_orders[ZMK_KEYMAP_LAYERS_LEN]; + +#endif + +int zmk_keymap_move_layer(zmk_keymap_layer_index_t start_idx, zmk_keymap_layer_index_t dest_idx) { + ASSERT_LAYER_VAL(start_idx, -EINVAL) + ASSERT_LAYER_VAL(dest_idx, -EINVAL) + + if (start_idx == dest_idx) { + return 0; + } else if (dest_idx > start_idx) { + uint8_t val = keymap_layer_orders[start_idx]; + + for (int i = start_idx; i < dest_idx; i++) { + keymap_layer_orders[i] = keymap_layer_orders[i + 1]; + } + + keymap_layer_orders[dest_idx] = val; + } else { + uint8_t val = keymap_layer_orders[start_idx]; + + for (int i = start_idx; i > dest_idx; i--) { + keymap_layer_orders[i] = keymap_layer_orders[i - 1]; + } + + keymap_layer_orders[dest_idx] = val; + } + + return 0; +} + +#else + +int zmk_keymap_move_layer(zmk_keymap_layer_index_t layer, zmk_keymap_layer_index_t dest) { + return -ENOTSUP; +} + +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + +#define PENDING_ARRAY_SIZE DIV_ROUND_UP(ZMK_KEYMAP_LEN, 8) + +static uint8_t zmk_keymap_layer_pending_changes[ZMK_KEYMAP_LAYERS_LEN][PENDING_ARRAY_SIZE]; + +struct zmk_behavior_binding_setting { + zmk_behavior_local_id_t behavior_local_id; + uint32_t param1; + uint32_t param2; +} __packed; + +int zmk_keymap_check_unsaved_changes(void) { + for (int l = 0; l < ZMK_KEYMAP_LAYERS_LEN; l++) { + uint8_t *pending = zmk_keymap_layer_pending_changes[l]; + for (int kp = 0; kp < ZMK_KEYMAP_LEN; kp++) { + if (pending[kp / 8] & BIT(kp % 8)) { + return 1; + } + } + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + if (settings_layer_orders[l] != keymap_layer_orders[l]) { + return 1; + } +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + } + + return 0; +} + +int zmk_keymap_save_changes(void) { + for (int l = 0; l < ZMK_KEYMAP_LAYERS_LEN; l++) { + uint8_t *pending = zmk_keymap_layer_pending_changes[l]; + + for (int kp = 0; kp < ZMK_KEYMAP_LEN; kp++) { + if (pending[kp / 8] & BIT(kp % 8)) { + LOG_DBG("Pending save for layer %d at key position %d", l, kp); + + struct zmk_behavior_binding *binding = &zmk_keymap[l][kp]; + struct zmk_behavior_binding_setting binding_setting = { + .behavior_local_id = zmk_behavior_get_local_id(binding->behavior_dev), + .param1 = binding->param1, + .param2 = binding->param2, + }; + + // We can skip any trailing zero params, regardless of the behavior + // and if those params are meaningful. + size_t len = sizeof(binding_setting); + if (binding_setting.param2 == 0) { + len -= 4; + + if (binding_setting.param1 == 0) { + len -= 4; + } + } + + char setting_name[20]; + sprintf(setting_name, "keymap/l/%d/%d", l, kp); + + settings_save_one(setting_name, &binding_setting, len); + } + } + + *pending = 0; + } + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + settings_save_one("keymap/layer_order", keymap_layer_orders, ARRAY_SIZE(keymap_layer_orders)); + memcpy(settings_layer_orders, keymap_layer_orders, ARRAY_SIZE(keymap_layer_orders)); +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + + return 0; } +int zmk_keymap_discard_changes(void) { return settings_load_subtree("keymap/l"); } + +#else + +int zmk_keymap_save_changes(void) { return -ENOTSUP; } + +int zmk_keymap_discard_changes(void) { return -ENOTSUP; } + +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + int invoke_locally(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, bool pressed) { if (pressed) { @@ -172,24 +376,28 @@ int invoke_locally(struct zmk_behavior_binding *binding, struct zmk_behavior_bin } } -int zmk_keymap_apply_position_state(uint8_t source, int layer, uint32_t position, bool pressed, - int64_t timestamp) { +int zmk_keymap_apply_position_state(uint8_t source, zmk_keymap_layer_id_t layer_id, + uint32_t position, bool pressed, int64_t timestamp) { // We want to make a copy of this, since it may be converted from // relative to absolute before being invoked - struct zmk_behavior_binding binding = zmk_keymap[layer][position]; + + ASSERT_LAYER_VAL(layer_id, -EINVAL); + + struct zmk_behavior_binding binding = zmk_keymap[layer_id][position]; const struct device *behavior; struct zmk_behavior_binding_event event = { - .layer = layer, + .layer = layer_id, .position = position, .timestamp = timestamp, }; - LOG_DBG("layer: %d position: %d, binding name: %s", layer, position, binding.behavior_dev); + LOG_DBG("layer_id: %d position: %d, binding name: %s", layer_id, position, + binding.behavior_dev); behavior = zmk_behavior_get_binding(binding.behavior_dev); if (!behavior) { - LOG_WRN("No behavior assigned to %d on layer %d", position, layer); + LOG_WRN("No behavior assigned to %d on layer %d", position, layer_id); return 1; } @@ -236,9 +444,13 @@ int zmk_keymap_position_state_changed(uint8_t source, uint32_t position, bool pr if (pressed) { zmk_keymap_active_behavior_layer[position] = _zmk_keymap_layer_state; } - for (int layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer >= _zmk_keymap_layer_default; layer--) { - if (zmk_keymap_layer_active_with_state(layer, zmk_keymap_active_behavior_layer[position])) { - int ret = zmk_keymap_apply_position_state(source, layer, position, pressed, timestamp); + for (zmk_keymap_layer_index_t layer_idx = ZMK_KEYMAP_LAYERS_LEN - 1; + layer_idx >= LAYER_ID_TO_INDEX(_zmk_keymap_layer_default); layer_idx--) { + zmk_keymap_layer_id_t layer_id = LAYER_INDEX_TO_ID(layer_idx); + if (zmk_keymap_layer_active_with_state(layer_id, + zmk_keymap_active_behavior_layer[position])) { + int ret = + zmk_keymap_apply_position_state(source, layer_id, position, pressed, timestamp); if (ret > 0) { LOG_DBG("behavior processing to continue to next layer"); continue; @@ -260,20 +472,26 @@ int zmk_keymap_sensor_event(uint8_t sensor_index, size_t channel_data_size, int64_t timestamp) { bool opaque_response = false; - for (int layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer >= 0; layer--) { - struct zmk_behavior_binding *binding = &zmk_sensor_keymap[layer][sensor_index]; + for (int layer_idx = ZMK_KEYMAP_LAYERS_LEN - 1; layer_idx >= 0; layer_idx--) { + uint8_t layer_id = LAYER_INDEX_TO_ID(layer_idx); - LOG_DBG("layer: %d sensor_index: %d, binding name: %s", layer, sensor_index, - binding->behavior_dev); + if (layer_id >= ZMK_KEYMAP_LAYERS_LEN) { + continue; + } + + struct zmk_behavior_binding *binding = &zmk_sensor_keymap[layer_id][sensor_index]; + + LOG_DBG("layer idx: %d, layer id: %d sensor_index: %d, binding name: %s", layer_idx, + layer_id, sensor_index, binding->behavior_dev); const struct device *behavior = zmk_behavior_get_binding(binding->behavior_dev); if (!behavior) { - LOG_DBG("No behavior assigned to %d on layer %d", sensor_index, layer); + LOG_DBG("No behavior assigned to %d on layer %d", sensor_index, layer_id); continue; } struct zmk_behavior_binding_event event = { - .layer = layer, + .layer = layer_id, .position = ZMK_VIRTUAL_KEY_POSITION_SENSOR(sensor_index), .timestamp = timestamp, }; @@ -290,8 +508,8 @@ int zmk_keymap_sensor_event(uint8_t sensor_index, } enum behavior_sensor_binding_process_mode mode = - (!opaque_response && layer >= _zmk_keymap_layer_default && - zmk_keymap_layer_active(layer)) + (!opaque_response && layer_idx >= LAYER_ID_TO_INDEX(_zmk_keymap_layer_default) && + zmk_keymap_layer_active(layer_id)) ? BEHAVIOR_SENSOR_BINDING_PROCESS_MODE_TRIGGER : BEHAVIOR_SENSOR_BINDING_PROCESS_MODE_DISCARD; @@ -335,3 +553,121 @@ ZMK_SUBSCRIPTION(keymap, zmk_position_state_changed); #if ZMK_KEYMAP_HAS_SENSORS ZMK_SUBSCRIPTION(keymap, zmk_sensor_event); #endif /* ZMK_KEYMAP_HAS_SENSORS */ + +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + +static int keymap_handle_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { + const char *next; + + LOG_DBG("Setting Keymap setting %s", name); + + if (settings_name_steq(name, "l", &next) && next) { + char *endptr; + uint8_t layer = strtoul(next, &endptr, 10); + if (*endptr != '/') { + LOG_WRN("Invalid layer number: %s with endptr %s", next, endptr); + return -EINVAL; + } + + uint8_t key_position = strtoul(endptr + 1, &endptr, 10); + + if (*endptr != '\0') { + LOG_WRN("Invalid key_position number: %s with endptr %s", next, endptr); + return -EINVAL; + } + + if (len > sizeof(struct zmk_behavior_binding_setting)) { + LOG_ERR("Too large binding setting size (got %d expected %d)", len, + sizeof(struct zmk_behavior_binding_setting)); + return -EINVAL; + } + + if (layer >= ZMK_KEYMAP_LAYERS_LEN) { + LOG_WRN("Layer %d is larger than max of %d", layer, ZMK_KEYMAP_LAYERS_LEN); + return -EINVAL; + } + + if (key_position >= ZMK_KEYMAP_LEN) { + LOG_WRN("Key position %d is larger than max of %d", key_position, ZMK_KEYMAP_LEN); + return -EINVAL; + } + + struct zmk_behavior_binding_setting binding_setting = {0}; + int err = read_cb(cb_arg, &binding_setting, len); + if (err <= 0) { + LOG_ERR("Failed to handle keymap binding from settings (err %d)", err); + return err; + } + + const char *name = + zmk_behavior_find_behavior_name_from_local_id(binding_setting.behavior_local_id); + + if (!name) { + LOG_WRN("Loaded device %d from settings but no device found by that local ID", + binding_setting.behavior_local_id); + } + + zmk_keymap[layer][key_position] = (struct zmk_behavior_binding){ + .local_id = binding_setting.behavior_local_id, + .behavior_dev = name, + .param1 = binding_setting.param1, + .param2 = binding_setting.param2, + }; + } +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + else if (settings_name_steq(name, "layer_order", &next) && !next) { + int err = + read_cb(cb_arg, settings_layer_orders, MIN(len, ARRAY_SIZE(settings_layer_orders))); + if (err <= 0) { + LOG_ERR("Failed to handle keymap layer orders from settings (err %d)", err); + return err; + } + + LOG_HEXDUMP_DBG(settings_layer_orders, ARRAY_SIZE(settings_layer_orders), + "Settings Layer Order"); + + memcpy(keymap_layer_orders, settings_layer_orders, + MIN(len, ARRAY_SIZE(settings_layer_orders))); + } +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + + return 0; +}; + +static int keymap_handle_commit(void) { + for (int l = 0; l < ZMK_KEYMAP_LAYERS_LEN; l++) { + for (int p = 0; p < ZMK_KEYMAP_LEN; p++) { + struct zmk_behavior_binding *binding = &zmk_keymap[l][p]; + + if (binding->local_id > 0 && !binding->behavior_dev) { + binding->behavior_dev = + zmk_behavior_find_behavior_name_from_local_id(binding->local_id); + + if (!binding->behavior_dev) { + LOG_ERR("Failed to finding device for local ID %d after settings load", + binding->local_id); + } + } + } + } + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(keymap, "keymap", NULL, keymap_handle_set, keymap_handle_commit, + NULL); + +#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE) + +int keymap_init(void) { +#if IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING) + // Initial values, may be overridden by the settings load. + for (int i = 0; i < ZMK_KEYMAP_LAYERS_LEN; i++) { + keymap_layer_orders[i] = i; + } +#endif + + return 0; +} + +SYS_INIT(keymap_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/studio/CMakeLists.txt b/app/src/studio/CMakeLists.txt index e8f0d49d261f..7df5fd3d8e99 100644 --- a/app/src/studio/CMakeLists.txt +++ b/app/src/studio/CMakeLists.txt @@ -11,5 +11,6 @@ target_sources(app PRIVATE rpc.c) target_sources(app PRIVATE core.c) target_sources(app PRIVATE behavior_subsystem.c) target_sources(app PRIVATE core_subsystem.c) +target_sources(app PRIVATE keymap_subsystem.c) target_sources_ifdef(CONFIG_ZMK_STUDIO_TRANSPORT_UART app PRIVATE uart_rpc_transport.c) target_sources_ifdef(CONFIG_ZMK_STUDIO_TRANSPORT_BLE app PRIVATE gatt_rpc_transport.c) \ No newline at end of file diff --git a/app/src/studio/Kconfig b/app/src/studio/Kconfig index 5b922fe36165..057672de0de8 100644 --- a/app/src/studio/Kconfig +++ b/app/src/studio/Kconfig @@ -7,9 +7,12 @@ menuconfig ZMK_STUDIO # These two save stack size imply NANOPB_NO_ERRMSG imply NANOPB_WITHOUT_64BIT + select SETTINGS select ZMK_BEHAVIOR_METADATA select ZMK_BEHAVIOR_LOCAL_IDS select RING_BUFFER + select ZMK_KEYMAP_SETTINGS_STORAGE + select ZMK_KEYMAP_LAYER_REORDERING help Add firmware support for realtime keymap updates (ZMK Studio) diff --git a/app/src/studio/keymap_subsystem.c b/app/src/studio/keymap_subsystem.c new file mode 100644 index 000000000000..eceb53e0b34f --- /dev/null +++ b/app/src/studio/keymap_subsystem.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +LOG_MODULE_DECLARE(zmk_studio, CONFIG_ZMK_STUDIO_LOG_LEVEL); + +#include + +#include +#include +#include +#include +#include + +#include + +ZMK_RPC_SUBSYSTEM(keymap) + +#define KEYMAP_RESPONSE(type, ...) ZMK_RPC_RESPONSE(keymap, type, __VA_ARGS__) +#define KEYMAP_NOTIFICATION(type, ...) ZMK_RPC_NOTIFICATION(keymap, type, __VA_ARGS__) + +static bool encode_layer_bindings(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { + const zmk_keymap_layer_id_t layer_id = *(uint8_t *)*arg; + + for (int b = 0; b < ZMK_KEYMAP_LEN; b++) { + const struct zmk_behavior_binding *binding = + zmk_keymap_get_layer_binding_at_idx(layer_id, b); + + zmk_keymap_BehaviorBinding bb = zmk_keymap_BehaviorBinding_init_zero; + + bb.behavior_id = zmk_behavior_get_local_id(binding->behavior_dev); + bb.param1 = binding->param1; + bb.param2 = binding->param2; + + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + if (!pb_encode_submessage(stream, &zmk_keymap_BehaviorBinding_msg, &bb)) { + return false; + } + } + + return true; +} + +static bool encode_layer_name(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { + const zmk_keymap_layer_index_t layer_idx = *(uint8_t *)*arg; + + const char *name = zmk_keymap_layer_name(layer_idx); + + if (!name) { + return true; + } + + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + return pb_encode_string(stream, name, strlen(name)); +} + +static bool encode_keymap_layers(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { + for (zmk_keymap_layer_index_t l = 0; l < ZMK_KEYMAP_LAYERS_LEN; l++) { + zmk_keymap_layer_id_t layer_id = zmk_keymap_layer_index_to_id(l); + if (!pb_encode_tag_for_field(stream, field)) { + LOG_DBG("Failed to encode tag"); + return false; + } + + zmk_keymap_Layer layer = zmk_keymap_Layer_init_zero; + layer.id = layer_id; + + layer.name.funcs.encode = encode_layer_name; + layer.name.arg = &layer_id; + + layer.bindings.funcs.encode = encode_layer_bindings; + layer.bindings.arg = &layer_id; + + if (!pb_encode_submessage(stream, &zmk_keymap_Layer_msg, &layer)) { + LOG_DBG("Failed to encode layer submessage"); + return false; + } + } + + return true; +} + +zmk_Response get_keymap(const zmk_Request *req) { + zmk_keymap_Keymap resp = zmk_keymap_Keymap_init_zero; + + resp.layers.funcs.encode = encode_keymap_layers; + + return KEYMAP_RESPONSE(get_keymap, resp); +} + +zmk_Response set_layer_binding(const zmk_Request *req) { + const zmk_keymap_SetLayerBindingRequest *set_req = + &req->subsystem.keymap.request_type.set_layer_binding; + + zmk_behavior_local_id_t bid = set_req->binding.behavior_id; + + const char *behavior_name = zmk_behavior_find_behavior_name_from_local_id(bid); + + if (!behavior_name) { + return KEYMAP_RESPONSE(set_layer_binding, + zmk_keymap_SetLayerBindingResponse_INVALID_BEHAVIOR); + } + + struct zmk_behavior_binding binding = (struct zmk_behavior_binding){ + .behavior_dev = behavior_name, + .param1 = set_req->binding.param1, + .param2 = set_req->binding.param2, + }; + + int ret = zmk_behavior_validate_binding(&binding); + if (ret < 0) { + return KEYMAP_RESPONSE(set_layer_binding, + zmk_keymap_SetLayerBindingResponse_INVALID_PARAMETERS); + } + + ret = zmk_keymap_set_layer_binding_at_idx(set_req->layer_id, set_req->key_position, binding); + + if (ret < 0) { + LOG_DBG("Setting the binding failed with %d", ret); + switch (ret) { + case -EINVAL: + return KEYMAP_RESPONSE(set_layer_binding, + zmk_keymap_SetLayerBindingResponse_INVALID_LOCATION); + default: + return ZMK_RPC_SIMPLE_ERR(GENERIC); + } + } + + raise_zmk_studio_rpc_notification((struct zmk_studio_rpc_notification){ + .notification = KEYMAP_NOTIFICATION(unsaved_changes_status_changed, true)}); + + return KEYMAP_RESPONSE(set_layer_binding, zmk_keymap_SetLayerBindingResponse_SUCCESS); +} + +zmk_Response check_unsaved_changes(const zmk_Request *req) { + int layout_changes = zmk_physical_layouts_check_unsaved_selection(); + int keymap_changes = zmk_keymap_check_unsaved_changes(); + + LOG_DBG("Keymap changes? %d", keymap_changes); + return KEYMAP_RESPONSE(check_unsaved_changes, layout_changes > 0 || keymap_changes > 0); +} + +zmk_Response save_changes(const zmk_Request *req) { + int ret = zmk_physical_layouts_save_selected(); + + if (ret < 0) { + return ZMK_RPC_SIMPLE_ERR(GENERIC); + } + + ret = zmk_keymap_save_changes(); + if (ret < 0) { + return ZMK_RPC_SIMPLE_ERR(GENERIC); + } + + raise_zmk_studio_rpc_notification((struct zmk_studio_rpc_notification){ + .notification = KEYMAP_NOTIFICATION(unsaved_changes_status_changed, false)}); + + return KEYMAP_RESPONSE(save_changes, true); +} + +zmk_Response discard_changes(const zmk_Request *req) { + int ret = zmk_physical_layouts_revert_selected(); + if (ret < 0) { + return ZMK_RPC_SIMPLE_ERR(GENERIC); + } + + ret = zmk_keymap_discard_changes(); + if (ret < 0) { + return ZMK_RPC_SIMPLE_ERR(GENERIC); + } + + raise_zmk_studio_rpc_notification((struct zmk_studio_rpc_notification){ + .notification = KEYMAP_NOTIFICATION(unsaved_changes_status_changed, false)}); + + return KEYMAP_RESPONSE(discard_changes, true); +} + +static bool encode_layout_name(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { + struct zmk_physical_layout *layout = (struct zmk_physical_layout *)*arg; + + if (!layout->display_name) { + return true; + } + + if (!pb_encode_tag_for_field(stream, field)) { + LOG_WRN("Failed to encode tag"); + return false; + } + + pb_encode_string(stream, layout->display_name, strlen(layout->display_name)); + + return true; +} + +static bool encode_layout_keys(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { + struct zmk_physical_layout *layout = (struct zmk_physical_layout *)*arg; + + for (int kp = 0; kp < layout->keys_len; kp++) { + const struct zmk_key_physical_attrs *layout_kp = &layout->keys[kp]; + + if (!pb_encode_tag_for_field(stream, field)) { + LOG_DBG("Failed to encode tag"); + return false; + } + + zmk_keymap_KeyPhysicalAttrs layout_kp_msg = { + .width = layout_kp->width, + .height = layout_kp->height, + .x = layout_kp->x, + .y = layout_kp->y, + .r = layout_kp->r, + .rx = layout_kp->rx, + .ry = layout_kp->ry, + }; + + if (!pb_encode_submessage(stream, &zmk_keymap_KeyPhysicalAttrs_msg, &layout_kp_msg)) { + LOG_WRN("Failed to encode layout key position submessage"); + return false; + } + } + return true; +} + +static bool encode_layouts(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { + struct zmk_physical_layout const *const *layouts; + const size_t layout_count = zmk_physical_layouts_get_list(&layouts); + + for (int i = 0; i < layout_count; i++) { + const struct zmk_physical_layout *l = layouts[i]; + + if (!pb_encode_tag_for_field(stream, field)) { + LOG_DBG("Failed to encode tag"); + return false; + } + + zmk_keymap_PhysicalLayout layout = zmk_keymap_PhysicalLayout_init_zero; + + layout.name.funcs.encode = encode_layout_name; + layout.name.arg = l; + + layout.keys.funcs.encode = encode_layout_keys; + layout.keys.arg = l; + + if (!pb_encode_submessage(stream, &zmk_keymap_PhysicalLayout_msg, &layout)) { + LOG_WRN("Failed to encode layout submessage"); + return false; + } + } + + return true; +} + +zmk_Response get_physical_layouts(const zmk_Request *req) { + zmk_keymap_PhysicalLayouts resp = zmk_keymap_PhysicalLayouts_init_zero; + resp.active_layout_index = zmk_physical_layouts_get_selected(); + resp.layouts.funcs.encode = encode_layouts; + return KEYMAP_RESPONSE(get_physical_layouts, resp); +} + +static void migrate_keymap(const uint8_t old) { + int new = zmk_physical_layouts_get_selected(); + + uint32_t new_to_old_map[ZMK_KEYMAP_LEN]; + int layout_size = zmk_physical_layouts_get_position_map(old, new, new_to_old_map); + + if (layout_size < 0) { + return; + } + + for (int l = 0; l < ZMK_KEYMAP_LAYERS_LEN; l++) { + struct zmk_behavior_binding new_layer[ZMK_KEYMAP_LEN]; + + for (int b = 0; b < layout_size; b++) { + uint32_t old_b = new_to_old_map[b]; + + if (old_b == UINT32_MAX) { + memset(&new_layer[b], 0, sizeof(struct zmk_behavior_binding)); + continue; + } + + const struct zmk_behavior_binding *binding = + zmk_keymap_get_layer_binding_at_idx(l, old_b); + + if (!binding) { + memset(&new_layer[b], 0, sizeof(struct zmk_behavior_binding)); + continue; + } + + memcpy(&new_layer[b], binding, sizeof(struct zmk_behavior_binding)); + } + + for (int b = 0; b < layout_size; b++) { + zmk_keymap_set_layer_binding_at_idx(l, b, new_layer[b]); + } + } + + // TODO: Migrate combos? +} + +zmk_Response set_active_physical_layout(const zmk_Request *req) { + uint8_t index = (uint8_t)req->subsystem.keymap.request_type.set_active_physical_layout; + int old = zmk_physical_layouts_get_selected(); + + zmk_keymap_SetActivePhysicalLayoutResponse resp = + zmk_keymap_SetActivePhysicalLayoutResponse_init_zero; + resp.which_result = zmk_keymap_SetActivePhysicalLayoutResponse_ok_tag; + resp.result.ok.layers.funcs.encode = encode_keymap_layers; + + if (old == index) { + return KEYMAP_RESPONSE(set_active_physical_layout, resp); + } + + int ret = zmk_physical_layouts_select(index); + if (ret >= 0) { + migrate_keymap(old); + } else { + resp.which_result = zmk_keymap_SetActivePhysicalLayoutResponse_err_tag; + resp.result.err = zmk_keymap_SetActivePhysicalLayoutErrorCode_GENERIC; + } + + raise_zmk_studio_rpc_notification((struct zmk_studio_rpc_notification){ + .notification = KEYMAP_NOTIFICATION(unsaved_changes_status_changed, true)}); + + return KEYMAP_RESPONSE(set_active_physical_layout, resp); +} + +zmk_Response move_layer(const zmk_Request *req) { + const zmk_keymap_MoveLayerRequest *move_req = &req->subsystem.keymap.request_type.move_layer; + + zmk_keymap_MoveLayerResponse resp = zmk_keymap_MoveLayerResponse_init_zero; + + int ret = zmk_keymap_move_layer(move_req->start_index, move_req->dest_index); + + if (ret >= 0) { + resp.which_result = zmk_keymap_SetActivePhysicalLayoutResponse_ok_tag; + resp.result.ok.layers.funcs.encode = encode_keymap_layers; + + raise_zmk_studio_rpc_notification((struct zmk_studio_rpc_notification){ + .notification = KEYMAP_NOTIFICATION(unsaved_changes_status_changed, true)}); + } else { + LOG_WRN("Failed to move layer: %d", ret); + resp.which_result = zmk_keymap_SetActivePhysicalLayoutResponse_err_tag; + resp.result.err = zmk_keymap_SetActivePhysicalLayoutErrorCode_GENERIC; + } + + return KEYMAP_RESPONSE(move_layer, resp); +} + +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, get_keymap, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, set_layer_binding, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, check_unsaved_changes, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, save_changes, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, discard_changes, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, get_physical_layouts, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, set_active_physical_layout, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, move_layer, true); + +static int event_mapper(const zmk_event_t *eh, zmk_Notification *n) { return 0; } + +ZMK_RPC_EVENT_MAPPER(keymap, event_mapper); diff --git a/app/tests/macros/press-mid-macro/keycode_events.snapshot b/app/tests/macros/press-mid-macro/keycode_events.snapshot index 0ec7ccb3cd65..28551b1ccd5d 100644 --- a/app/tests/macros/press-mid-macro/keycode_events.snapshot +++ b/app/tests/macros/press-mid-macro/keycode_events.snapshot @@ -1,8 +1,8 @@ -pos_state: layer: 0 position: 0, binding name: abc_macro +pos_state: layer_id: 0 position: 0, binding name: abc_macro kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 -pos_state: layer: 0 position: 0, binding name: abc_macro -pos_state: layer: 0 position: 1, binding name: momentary_layer -pos_state: layer: 0 position: 1, binding name: momentary_layer +pos_state: layer_id: 0 position: 0, binding name: abc_macro +pos_state: layer_id: 0 position: 1, binding name: momentary_layer +pos_state: layer_id: 0 position: 1, binding name: momentary_layer kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00