diff --git a/app/proto/zmk/keymap.proto b/app/proto/zmk/keymap.proto index 6ec2c9950ba0..d6247a634806 100644 --- a/app/proto/zmk/keymap.proto +++ b/app/proto/zmk/keymap.proto @@ -4,6 +4,47 @@ package zmk.keymap; message Request { oneof request_type { - bool get_layer_summaries = 1; + bool get_keymap = 1; + SetLayerBindingRequest set_layer_binding = 2; + bool save_changes = 3; + bool discard_changes = 4; } +} + +message Response { + oneof response_type { + Keymap get_keymap = 1; + SetLayerBindingResponse set_layer_binding = 2; + bool save_changes = 3; + bool discard_changes = 4; + } +} + +enum SetLayerBindingResponse { + SUCCESS = 0; + INVALID_LOCATION = 1; + INVALID_BEHAVIOR = 2; + INVALID_PARAMETERS = 3; +} + +message SetLayerBindingRequest { + int32 layer = 1; + int32 key_position = 2; + + BehaviorBinding binding = 3; +} + +message Keymap { + repeated Layer layers = 1; +} + +message Layer { + string name = 1; + repeated BehaviorBinding bindings = 2; +} + +message BehaviorBinding { + sint32 behavior_id = 1; + uint32 param1 = 2; + uint32 param2 = 3; } \ No newline at end of file diff --git a/app/proto/zmk/studio.proto b/app/proto/zmk/studio.proto index fd9e98a36bcf..07234b414184 100644 --- a/app/proto/zmk/studio.proto +++ b/app/proto/zmk/studio.proto @@ -12,9 +12,9 @@ message Request { uint32 request_id = 1; oneof subsystem { - zmk.core.Request core = 2; - zmk.keymap.Request keymap = 3; + zmk.core.Request core = 3; zmk.behaviors.Request behaviors = 4; + zmk.keymap.Request keymap = 5; } } @@ -31,6 +31,7 @@ message RequestResponse { zmk.meta.Response meta = 2; zmk.core.Response core = 3; zmk.behaviors.Response behaviors = 4; + zmk.keymap.Response keymap = 5; } } 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 140821910b8e..7fb9514a5ec9 100644 --- a/app/src/studio/Kconfig +++ b/app/src/studio/Kconfig @@ -9,6 +9,7 @@ menuconfig ZMK_STUDIO imply NANOPB_WITHOUT_64BIT select ZMK_BEHAVIOR_METADATA select ZMK_BEHAVIOR_LOCAL_IDS + select ZMK_KEYMAP_SETTINGS_STORAGE 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..1d21f75f8ce7 --- /dev/null +++ b/app/src/studio/keymap_subsystem.c @@ -0,0 +1,183 @@ +/* + * 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 + +ZMK_RPC_SUBSYSTEM(keymap) + +#define KEYMAP_RESPONSE(type, ...) ZMK_RPC_RESPONSE(keymap, type, __VA_ARGS__) + +static int32_t get_behavior_id_for_dev_name(const char *behavior_dev) { + const struct device *dev = zmk_behavior_get_binding(behavior_dev); + LOG_DBG("God device pointer %p for %s", dev, behavior_dev); + int i = 0; + STRUCT_SECTION_FOREACH(zmk_behavior_ref, ref) { + if (ref->device == dev) { + LOG_DBG("Found the device"); + return i; + } + i++; + } + + return -ENODEV; +} + +static bool encode_layer_bindings(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { + const uint8_t layer_idx = *(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_idx, b); + + zmk_keymap_BehaviorBinding bb = zmk_keymap_BehaviorBinding_init_zero; + + bb.behavior_id = get_behavior_id_for_dev_name(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 uint8_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 (int l = 0; l < ZMK_KEYMAP_LAYERS_LEN; 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.name.funcs.encode = encode_layer_name; + layer.name.arg = &l; + + layer.bindings.funcs.encode = encode_layer_bindings; + layer.bindings.arg = &l; + + 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; + + int16_t bid = set_req->binding.behavior_id; + + uint32_t behavior_count; + STRUCT_SECTION_COUNT(zmk_behavior_ref, &behavior_count); + + if (bid < 0 || bid >= behavior_count) { + return KEYMAP_RESPONSE(set_layer_binding, + zmk_keymap_SetLayerBindingResponse_INVALID_BEHAVIOR); + } + + const struct zmk_behavior_ref *ref; + STRUCT_SECTION_GET(zmk_behavior_ref, bid, &ref); + + struct zmk_behavior_binding binding = (struct zmk_behavior_binding){ + .behavior_dev = ref->device->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, 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); + } + } + + return KEYMAP_RESPONSE(set_layer_binding, zmk_keymap_SetLayerBindingResponse_SUCCESS); +} + +zmk_Response save_changes(const zmk_Request *req) { + int ret = zmk_keymap_save_changes(); + if (ret < 0) { + return ZMK_RPC_SIMPLE_ERR(GENERIC); + } + + return KEYMAP_RESPONSE(save_changes, true); +} + +zmk_Response discard_changes(const zmk_Request *req) { + int ret = zmk_keymap_discard_changes(); + if (ret < 0) { + return ZMK_RPC_SIMPLE_ERR(GENERIC); + } + + return KEYMAP_RESPONSE(discard_changes, true); +} + +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, get_keymap, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, set_layer_binding, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, save_changes, true); +ZMK_RPC_SUBSYSTEM_HANDLER(keymap, discard_changes, true); + +static int event_mapper(const zmk_event_t *eh, zmk_Notification *n) { return 0; } + +ZMK_RPC_EVENT_MAPPER(keymap, event_mapper);