Skip to content

Instantly share code, notes, and snippets.

@petejohanson
Created June 18, 2020 02:58
Show Gist options
  • Save petejohanson/3461fc8bb8afe5fdddbcf50ac918f060 to your computer and use it in GitHub Desktop.
Save petejohanson/3461fc8bb8afe5fdddbcf50ac918f060 to your computer and use it in GitHub Desktop.
ZMK behavior driver example

An approach at encoding behaviours. Even though this is defined in DT, it ends up generating sane structs holding the "behaviour label" and configured user params (e.g. keycode for that binding) which could be compiled in, and then updated with the values from some config stored in NVS.

The equivalent JSON for a "key press with keycode A" in the JSON would be:

{
  "bindings": [
    { "behavior": "KEY_PRESS", "cells": [123] }, // THis is the keycode for 'A'
    { "behavior": "LAYER_TO", "cells": [1] }, // THis is the layer number
    { "behavior": "MOD_TAP", "cells": [1, 123] }, // THis is the modifier, and the keycode, for mod-tap behavior.
  ]
}
/ {
// True custom behaviors are just another driver implementing the "behavior API" that can be implemented in user configs
// or even in shared libraries outside of the ZMK core.
behaviors {
// This can be referenced in your key bindings via <&private_user_crazy>
private_user_crazy: behaviour_user_crazy {
compatible = "bobjoe123,user-crazy";
label = "BOBJOE_123_USER_CRAZY";
};
};
};
/ {
behaviors {
// For users who really want to just override a "weak" symbol with their own implementation, this
// recreates the "magic keycode" type behavior. This compatible ends up generating a function definition like:
// __attribute__((weak)) int zmk_behavior_custom_one(struct driver *dev, u32_t custom_param) {
// return 0;
// }
// That can just be overridden in the user config code.
custom_one: behavior_custom_one {
compatible = "zmk,behavior-custom";
label = "custom_one";
binding-cells = <2>;
};
};
};
#include <drivers/zmk/behavior.h>
// They keycode is passed by the "keymap" based on the parameter created as part of the assignment.
// Other drivers instead might activate a layer, update the consumer page state, or update the RGB state, etc.
// Returns:
// * > 0 - indicate successful processing, and halt further handling,
// * 0 - Indicate successful processing, and continue propagation.
// * < 0 - Indicate error processing, report and halt further propagation.
static int on_position_pressed(struct device *dev, u32_t keycode) {
// Invoking this triggers a *new* event, that can be linked to other behaviours.
return zmk_key_state_press(u32_t keycode);
}
// They keycode is passed by the "keymap" based on the parameter created as part of the assignment.
static int on_position_released(struct device *dev, u32_t keycode) {
// Invoking this triggers a *new* event, that can will be handled by other behaviors
// This is the "command" piece. Which could be better/richer, but captures essence here.
return zmk_key_state_release(u32_t keycode);
}
static const zmk_behaviour_driver_api kp_behavior_driver_api = {
// These callbacks are all optional, and define which kinds of events the behavior can handle.
// They can reference local functions defined here, or shared event handlers.
.on_key_position_pressed = on_key_position_pressed,
.on_key_position_released = on_key_position_released,
// Other optional callbacks a behavior can implement
// .on_mouse_moved
// .on_sensor_data - Any behaviour that wants to be linked to a censor can implement this behavior
};
static const struct kp_behavior_config kp_behavior_config = {};
static struct kp_behavior_data kp_behavior_data;
DEVICE_AND_API_INIT(kscan_composite, DT_INST_LABEL(0), kp_behavior_init,
&kp_behavior_data,
&kp_behavior_config,
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&kp_behavior_driver_api);
/ {
chosen {
zmk,keymap = &default_key_map;
};
default_key_map: keymap_0 {
layers = <&default &raise &lower>;
};
// THe values like `A` and `RET` and `LSHIFT` are all defines of keycodes/values that gets replaced
// by the preprocessor by the integer equivalents.
default: layer_default {
bindings =
<&kp Q>, <&kp W>, <&kp E> <&kp R>, <&kp T> <&kp Y>, <&kp U>, <&kp I>, <&kp O>, <&kp P>,
<&kp A>, <&kp S>, <&kp D> <&kp F>, <&kp G> <&kp H>, <&kp J>, <&kp K>, <&kp L>, <&kp SEMI>,
<&kp Z>, <&kp X>, <&kp C> <&kp V>, <&kp B> <&kp N>, <&kp M>, <&kp COMMA>, <&kp DOT>, <&kp FSLASH>,
<&kp BSPACE>, <&lt LOWER RET>, <&mt LSHIFT SPACE>, <&mt LCTRL DEL>;
};
lower: layer_default {
bindings =
<&kp Q>, <&kp W>, <&kp E> <&kp R>, <&kp T> <&kp Y>, <&kp U>, <&kp I>, <&kp O>, <&kp P>,
<&kp A>, <&kp S>, <&kp D> <&kp F>, <&kp G> <&kp H>, <&kp J>, <&kp K>, <&kp L>, <&kp SEMI>,
<&kp Z>, <&kp X>, <&kp C> <&kp V>, <&kp B> <&kp N>, <&kp M>, <&kp COMMA>, <&kp DOT>, <&kp FSLASH>,
<&kp BSPACE>, <&trans>, <&mt LSHIFT SPACE>, <&mt LCTRL DEL>;
};
}
/ {
behaviors {
kp: behaviour_key_press {
compatible = "zmk,behavior-key-press";
label = "KEY_PRESS";
binding-cells = <1>;
};
mt: behavior_mod_tap {
comptabile = "zmk,behavior-mod-tap";
label = "MOD_TAP";
binding-cells = <2>;
};
lt: behavior_layer_tap {
comptabile = "zmk,behavior-layer-tap";
label = "MOD_TAP";
binding-cells = <2>;
};
cp: behavior_consumer_page_key_press {
compatible = "zmk,behavior-consumer-page-key-press";
label = "CONSUMER_PAGE";
status = "disabled";
binding-cells = <1>;
};
to: behavior_layer_to {
compatible = "zmk,behavior-layer-to";
label = "LAYER_TO";
binding-cells = <1>; // The layer to activate while held
};
rgb: behaviour_rgb_controls {
compatible = "zmk,behavior-rgb-matrix";
label = "RGB";
binding-cells = <1>;
};
chord: behaviour_chord_engine {
compatible = "zmk,behavior-chording";
label = "CHORDING";
binding-cells = <1>;
chord-matrix = <&chord_matrix>; // Points to the chording driver, with the matrix of chord combinations!
};
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment