Skip to content

Commit

Permalink
feat(studio): Add new keymap subsystem.
Browse files Browse the repository at this point in the history
* Add APIs for fetching the current keymap, and updating a particular
  binding in a layer of the keymap, including validation of parameters.
  • Loading branch information
petejohanson committed Apr 10, 2024
1 parent 99fd090 commit 88b424b
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 3 deletions.
40 changes: 39 additions & 1 deletion app/proto/zmk/keymap.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,46 @@ syntax = "proto3";

package zmk.keymap;


message Request {
oneof request_type {
bool get_layer_summaries = 1;
bool get_keymap = 1;
SetLayerBindingRequest set_layer_binding = 2;
}
}

message Response {
oneof response_type {
Keymap get_keymap = 1;
SetLayerBindingResponse set_layer_binding = 2;
}
}

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;
}
5 changes: 3 additions & 2 deletions app/proto/zmk/studio.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand All @@ -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;
}
}

Expand Down
1 change: 1 addition & 0 deletions app/src/studio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
163 changes: 163 additions & 0 deletions app/src/studio/keymap_subsystem.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/logging/log.h>

LOG_MODULE_DECLARE(zmk_studio, CONFIG_ZMK_STUDIO_LOG_LEVEL);

#include <drivers/behavior.h>

#include <zmk/behavior.h>
#include <zmk/matrix.h>
#include <zmk/keymap.h>
#include <zmk/studio/rpc.h>

#include <pb_encode.h>

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_RPC_SUBSYSTEM_HANDLER(keymap, get_keymap, true);
ZMK_RPC_SUBSYSTEM_HANDLER(keymap, set_layer_binding, true);

static int event_mapper(const zmk_event_t *eh, zmk_Notification *n) { return 0; }

ZMK_RPC_EVENT_MAPPER(keymap, event_mapper);

0 comments on commit 88b424b

Please sign in to comment.