Skip to content

Commit ba1f1d1

Browse files
committed
feat(studio): Add new keymap subsystem.
* Add APIs for fetching the current keymap, and updating a particular binding in a layer of the keymap, including validation of parameters. * Add API for savings/discarding keymap changes.
1 parent 663eb58 commit ba1f1d1

File tree

5 files changed

+211
-3
lines changed

5 files changed

+211
-3
lines changed

app/proto/zmk/keymap.proto

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,47 @@ package zmk.keymap;
44

55
message Request {
66
oneof request_type {
7-
bool get_layer_summaries = 1;
7+
bool get_keymap = 1;
8+
SetLayerBindingRequest set_layer_binding = 2;
9+
bool save_changes = 3;
10+
bool discard_changes = 4;
811
}
12+
}
13+
14+
message Response {
15+
oneof response_type {
16+
Keymap get_keymap = 1;
17+
SetLayerBindingResponse set_layer_binding = 2;
18+
bool save_changes = 3;
19+
bool discard_changes = 4;
20+
}
21+
}
22+
23+
enum SetLayerBindingResponse {
24+
SUCCESS = 0;
25+
INVALID_LOCATION = 1;
26+
INVALID_BEHAVIOR = 2;
27+
INVALID_PARAMETERS = 3;
28+
}
29+
30+
message SetLayerBindingRequest {
31+
int32 layer = 1;
32+
int32 key_position = 2;
33+
34+
BehaviorBinding binding = 3;
35+
}
36+
37+
message Keymap {
38+
repeated Layer layers = 1;
39+
}
40+
41+
message Layer {
42+
string name = 1;
43+
repeated BehaviorBinding bindings = 2;
44+
}
45+
46+
message BehaviorBinding {
47+
sint32 behavior_id = 1;
48+
uint32 param1 = 2;
49+
uint32 param2 = 3;
950
}

app/proto/zmk/studio.proto

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ message Request {
1212
uint32 request_id = 1;
1313

1414
oneof subsystem {
15-
zmk.core.Request core = 2;
16-
zmk.keymap.Request keymap = 3;
15+
zmk.core.Request core = 3;
1716
zmk.behaviors.Request behaviors = 4;
17+
zmk.keymap.Request keymap = 5;
1818
}
1919
}
2020

@@ -31,6 +31,7 @@ message RequestResponse {
3131
zmk.meta.Response meta = 2;
3232
zmk.core.Response core = 3;
3333
zmk.behaviors.Response behaviors = 4;
34+
zmk.keymap.Response keymap = 5;
3435
}
3536
}
3637

app/src/studio/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ target_sources(app PRIVATE rpc.c)
1111
target_sources(app PRIVATE core.c)
1212
target_sources(app PRIVATE behavior_subsystem.c)
1313
target_sources(app PRIVATE core_subsystem.c)
14+
target_sources(app PRIVATE keymap_subsystem.c)
1415
target_sources_ifdef(CONFIG_ZMK_STUDIO_TRANSPORT_UART app PRIVATE uart_rpc_transport.c)
1516
target_sources_ifdef(CONFIG_ZMK_STUDIO_TRANSPORT_BLE app PRIVATE gatt_rpc_transport.c)

app/src/studio/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ menuconfig ZMK_STUDIO
99
imply NANOPB_WITHOUT_64BIT
1010
select ZMK_BEHAVIOR_METADATA
1111
select ZMK_BEHAVIOR_LOCAL_IDS
12+
select ZMK_KEYMAP_SETTINGS_STORAGE
1213
help
1314
Add firmware support for realtime keymap updates (ZMK Studio
1415

app/src/studio/keymap_subsystem.c

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (c) 2024 The ZMK Contributors
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
#include <zephyr/logging/log.h>
8+
9+
LOG_MODULE_DECLARE(zmk_studio, CONFIG_ZMK_STUDIO_LOG_LEVEL);
10+
11+
#include <drivers/behavior.h>
12+
13+
#include <zmk/behavior.h>
14+
#include <zmk/matrix.h>
15+
#include <zmk/keymap.h>
16+
#include <zmk/studio/rpc.h>
17+
18+
#include <pb_encode.h>
19+
20+
ZMK_RPC_SUBSYSTEM(keymap)
21+
22+
#define KEYMAP_RESPONSE(type, ...) ZMK_RPC_RESPONSE(keymap, type, __VA_ARGS__)
23+
24+
static bool encode_layer_bindings(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) {
25+
const uint8_t layer_idx = *(uint8_t *)*arg;
26+
27+
for (int b = 0; b < ZMK_KEYMAP_LEN; b++) {
28+
const struct zmk_behavior_binding *binding =
29+
zmk_keymap_get_layer_binding_at_idx(layer_idx, b);
30+
31+
zmk_keymap_BehaviorBinding bb = zmk_keymap_BehaviorBinding_init_zero;
32+
33+
bb.behavior_id = zmk_behavior_get_local_id(binding->behavior_dev);
34+
bb.param1 = binding->param1;
35+
bb.param2 = binding->param2;
36+
37+
if (!pb_encode_tag_for_field(stream, field)) {
38+
return false;
39+
}
40+
41+
if (!pb_encode_submessage(stream, &zmk_keymap_BehaviorBinding_msg, &bb)) {
42+
return false;
43+
}
44+
}
45+
46+
return true;
47+
}
48+
49+
static bool encode_layer_name(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) {
50+
const uint8_t layer_idx = *(uint8_t *)*arg;
51+
52+
const char *name = zmk_keymap_layer_name(layer_idx);
53+
54+
if (!name) {
55+
return true;
56+
}
57+
58+
if (!pb_encode_tag_for_field(stream, field)) {
59+
return false;
60+
}
61+
62+
return pb_encode_string(stream, name, strlen(name));
63+
}
64+
65+
static bool encode_keymap_layers(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) {
66+
for (int l = 0; l < ZMK_KEYMAP_LAYERS_LEN; l++) {
67+
68+
if (!pb_encode_tag_for_field(stream, field)) {
69+
LOG_DBG("Failed to encode tag");
70+
return false;
71+
}
72+
73+
zmk_keymap_Layer layer = zmk_keymap_Layer_init_zero;
74+
75+
layer.name.funcs.encode = encode_layer_name;
76+
layer.name.arg = &l;
77+
78+
layer.bindings.funcs.encode = encode_layer_bindings;
79+
layer.bindings.arg = &l;
80+
81+
if (!pb_encode_submessage(stream, &zmk_keymap_Layer_msg, &layer)) {
82+
LOG_DBG("Failed to encode layer submessage");
83+
return false;
84+
}
85+
}
86+
87+
return true;
88+
}
89+
90+
zmk_Response get_keymap(const zmk_Request *req) {
91+
zmk_keymap_Keymap resp = zmk_keymap_Keymap_init_zero;
92+
93+
resp.layers.funcs.encode = encode_keymap_layers;
94+
95+
return KEYMAP_RESPONSE(get_keymap, resp);
96+
}
97+
98+
zmk_Response set_layer_binding(const zmk_Request *req) {
99+
const zmk_keymap_SetLayerBindingRequest *set_req =
100+
&req->subsystem.keymap.request_type.set_layer_binding;
101+
102+
zmk_behavior_local_id_t bid = set_req->binding.behavior_id;
103+
104+
const char *behavior_name = zmk_behavior_find_behavior_name_from_local_id(bid);
105+
106+
if (!behavior_name) {
107+
return KEYMAP_RESPONSE(set_layer_binding,
108+
zmk_keymap_SetLayerBindingResponse_INVALID_BEHAVIOR);
109+
}
110+
111+
struct zmk_behavior_binding binding = (struct zmk_behavior_binding){
112+
.behavior_dev = behavior_name,
113+
.param1 = set_req->binding.param1,
114+
.param2 = set_req->binding.param2,
115+
};
116+
117+
int ret = zmk_behavior_validate_binding(&binding);
118+
if (ret < 0) {
119+
return KEYMAP_RESPONSE(set_layer_binding,
120+
zmk_keymap_SetLayerBindingResponse_INVALID_PARAMETERS);
121+
}
122+
123+
ret = zmk_keymap_set_layer_binding_at_idx(set_req->layer, set_req->key_position, binding);
124+
125+
if (ret < 0) {
126+
LOG_DBG("Setting the binding failed with %d", ret);
127+
switch (ret) {
128+
case -EINVAL:
129+
return KEYMAP_RESPONSE(set_layer_binding,
130+
zmk_keymap_SetLayerBindingResponse_INVALID_LOCATION);
131+
default:
132+
return ZMK_RPC_SIMPLE_ERR(GENERIC);
133+
}
134+
}
135+
136+
return KEYMAP_RESPONSE(set_layer_binding, zmk_keymap_SetLayerBindingResponse_SUCCESS);
137+
}
138+
139+
zmk_Response save_changes(const zmk_Request *req) {
140+
int ret = zmk_keymap_save_changes();
141+
if (ret < 0) {
142+
return ZMK_RPC_SIMPLE_ERR(GENERIC);
143+
}
144+
145+
return KEYMAP_RESPONSE(save_changes, true);
146+
}
147+
148+
zmk_Response discard_changes(const zmk_Request *req) {
149+
int ret = zmk_keymap_discard_changes();
150+
if (ret < 0) {
151+
return ZMK_RPC_SIMPLE_ERR(GENERIC);
152+
}
153+
154+
return KEYMAP_RESPONSE(discard_changes, true);
155+
}
156+
157+
ZMK_RPC_SUBSYSTEM_HANDLER(keymap, get_keymap, true);
158+
ZMK_RPC_SUBSYSTEM_HANDLER(keymap, set_layer_binding, true);
159+
ZMK_RPC_SUBSYSTEM_HANDLER(keymap, save_changes, true);
160+
ZMK_RPC_SUBSYSTEM_HANDLER(keymap, discard_changes, true);
161+
162+
static int event_mapper(const zmk_event_t *eh, zmk_Notification *n) { return 0; }
163+
164+
ZMK_RPC_EVENT_MAPPER(keymap, event_mapper);

0 commit comments

Comments
 (0)