-
Notifications
You must be signed in to change notification settings - Fork 314
Autoshift using ZMK behaviors
A common feature of user keymaps is to replicate QMK's autoshift
feature with
a combination of hold-tap and a custom preprocessor macro to reduce code
repetition.
See: the Autoshift tab from Hold-Tap Behavior Example Use-Cases.
You can replicate an easy-to-use autoshift by composing various ZMK behaviors to
combine functionality with simplicity. Copy the behaviors from the following
example keymap into your own and use the &as
binding in place of AS()
.
You can also see this implemented through the Keymap Editor by opening the app and switching to the Demo Keyboard source or the below screenshots:
A full example keymap
#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
/ {
behaviors {
as_ht: autoshift_hold_tap {
compatible = "zmk,behavior-hold-tap";
label = "AUTOSHIFT_HOLD_TAP";
#binding-cells = <2>;
tapping-term-ms = <200>;
bindings = <&shifted>, <&kp>;
};
};
macros {
shifted: macro_shifted_kp {
#binding-cells = <1>;
label = "MACRO_SHIFTED_KP";
compatible = "zmk,behavior-macro-one-param";
bindings =
<¯o_press &kp LSHFT>,
<¯o_param_1to1 ¯o_tap &kp MACRO_PLACEHOLDER>,
<¯o_release &kp LSHFT>;
};
as: autoshift {
compatible = "zmk,behavior-macro-one-param";
#binding-cells = <1>;
label = "AUTOSHIFT_KP";
bindings =
<¯o_press>,
<¯o_param_1to1>,
<¯o_param_1to2>,
<&as_ht MACRO_PLACEHOLDER MACRO_PLACEHOLDER>,
<¯o_pause_for_release>,
<¯o_release>,
<¯o_param_1to1>,
<¯o_param_1to2>,
<&as_ht MACRO_PLACEHOLDER MACRO_PLACEHOLDER>;
};
};
keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&as Q &as W &as E &as R &as T &as Y &as U &as I &as O &as P
&as A &as S &as D &as F &as G &as H &as J &as K &as L &as SEMI
&as Z &as X &as C &as V &as B &as N &as M &as COMMA &as DOT &as FSLH
>;
};
};
};
The approach described in the ZMK documentation boils down to using the hold-tap behavior to trigger a shifted or regular keypress. With the recent addition of parameterized macro behaviors in ZMK it is now possible to effectively create an "autoshift" behavior that a) is only slightly longer to type out, and b) looks more like normal ZMK bindings.
shifted: macro_shifted_kp {
#binding-cells = <1>;
label = "MACRO_SHIFTED_KP";
compatible = "zmk,behavior-macro-one-param";
bindings =
<¯o_press &kp LSHFT>,
<¯o_param_1to1 ¯o_tap &kp MACRO_PLACEHOLDER>,
<¯o_release &kp LSHFT>;
};
This gives us a behavior version of the LS()
modifier function. Now instead of
writing &kp LS(A)
we can write &shifted A
.
as_ht: autoshift_hold_tap {
compatible = "zmk,behavior-hold-tap";
label = "AUTOSHIFT_HOLD_TAP";
#binding-cells = <2>;
tapping-term-ms = <135>;
flavor = "tap-preferred";
bindings = <&shifted>, <&kp>;
};
This follows the example autoshift behavior from ZMK's documentation, except it
replaces the &kp
binding for the "hold" action with our &shifted
macro
behavior. What's the difference? This version wraps our keypress in a sequence
of pressing and releasing the LSHFT
key. If we were to use this behavior as it
is it would look like &as_ht A A
.
as: autoshift {
compatible = "zmk,behavior-macro-one-param";
#binding-cells = <1>;
label = "AUTOSHIFT_KP";
bindings =
<¯o_press>,
<¯o_param_1to1>,
<¯o_param_1to2>,
<&as_ht MACRO_PLACEHOLDER MACRO_PLACEHOLDER>,
<¯o_pause_for_release>,
<¯o_release>,
<¯o_param_1to1>,
<¯o_param_1to2>,
<&as_ht MACRO_PLACEHOLDER MACRO_PLACEHOLDER>;
};
This macro accepts a single parameter and triggers a single behavior, &as_ht
using that parameter for both binding cells. We use ¯o_pause_for_release
so that holding a key bound to this macro also holds the &as_ht
binding in the
ZMK behavior queue.
The end result is the simplifed syntax &as A
If you're really interested, I go into much more detail in Preprocessor Support