Giter Site home page Giter Site logo

zmk-tri-state's Introduction

ZMK-TRI-STATE

Implement Nick Conways's tri-state behavior for ZMK.

This is proof of concept that shows that without maintaining fork, new behavior can be implemented.

This implementation supports latest Zephyr 3.5 release with ZMK.

How to use this module?

Under config/west.yml add remotes and projects, here is an example of full file. Feel free to visit my config located here.

manifest:
  remotes:
    - name: zmkfirmware
      url-base: https://github.com/zmkfirmware
    - name: dhruvinsh
      url-base: https://github.com/dhruvinsh
  projects:
    - name: zmk
      remote: zmkfirmware
      revision: main
      import: app/west.yml
    - name: zmk-tri-state
      remote: dhruvinsh
      revision: main
  self:
    path: config

NOTE: common use-case hasn't be define for this behavior.

To use,

#include <dt-bindings/zmk/keys.h>

/ {
  behaviors {
    swapper: swapper {
      compatible = "zmk,behavior-tri-state";
      #binding-cells = <0>;
      bindings = <&kp A>, <&kp B>, <&kt C>;
    };
  };
};

Once this define, &swapper can be use wherever it require. See below documentation for further details. Working example can be find here.

Summary: Tri-State

Tri-States are a way to have something persist while other behaviors occur.

The tri-state key will fire the 'start' behavior when the key is pressed for the first time. Subsequent presses of the same key will output the second, 'continue' behavior, and any key position or layer state change that is not specified (see below) will trigger the 'interrupt behavior'.

Basic Usage

The following is a basic definition of a tri-state:

/ {
    behaviors {
        tri-state: tri-state {
            compatible = "zmk,behavior-tri-state";
            label = "TRI-STATE";
            #binding-cells = <0>;
            bindings = <&kp A>, <&kp B>, <&kt C>;
        };
    };

    keymap {
        compatible = "zmk,keymap";
        label ="Default keymap";

        default_layer {
            bindings = <
                &tri-state  &kp D
                &kp E       &kp F>;
        };
    };
};

Pressing tri-state will fire the first behavior, and output A, as well as the second behavior, outputting B. Subsequent presses of tri-state will output B. When another key is pressed or a layer change occurs, the third, 'interrupt' behavior will fire.

Advanced Configuration

timeout-ms

Setting timeout-ms will cause the deactivation behavior to fire when the time has elapsed after releasing the Tri-State or a ignored key.

ignored-key-positions

  • Including ignored-key-positions in your tri-state definition will let the key positions specified NOT trigger the interrupt behavior when a tri-state is active.
  • Pressing any key NOT listed in ignored-key-positions will cause the interrupt behavior to fire.
  • Note that ignored-key-positions is an array of key position indexes. Key positions are numbered according to your keymap, starting with 0. So if the first key in your keymap is Q, this key is in position 0. The next key (probably W) will be in position 1, et cetera.
  • See the following example, which is an implementation of the popular Swapper from Callum Oakley:
/ {
    behaviors {
        swap: swapper {
            compatible = "zmk,behavior-tri-state";
            label = "SWAPPER";
            #binding-cells = <0>;
            bindings = <&kt LALT>, <&kp TAB>, <&kt LALT>;
            ignored-key-positions = <1>;
        };
    };

    keymap {
        compatible = "zmk,keymap";
        label ="Default keymap";

        default_layer {
            bindings = <
                &swap    &kp LS(TAB)
                &kp B    &kp C>;
        };
    };
};
  • The sequence (swap, swap, LS(TAB)) produces (LA(TAB), LA(TAB), LA(LS(TAB))). The LS(TAB) behavior does not fire the interrupt behavior, because it is included in ignored-key-positions.
  • The sequence (swap, swap, B) produces (LA(TAB), LA(TAB), B). The B behavior does fire the interrupt behavior, because it is not included in ignored-key-positions.

ignored-layers

  • By default, any layer change will trigger the end behavior.
  • Including ignored-layers in your tri-state definition will let the specified layers NOT trigger the end behavior when they become active (include the layer the behavior is on to accommodate for layer toggling).
  • Activating any layer NOT listed in ignored-layers will cause the interrupt behavior to fire.
  • Note that ignored-layers is an array of layer indexes. Layers are numbered according to your keymap, starting with 0. The first layer in your keymap is layer 0. The next layer will be layer 1, et cetera.
  • Looking back at the swapper implementation, we can see how ignored-layers can affect things
/ {
    behaviors {
        swap: swapper {
            compatible = "zmk,behavior-tri-state";
            label = "SWAPPER";
            #binding-cells = <0>;
            bindings = <&kt LALT>, <&kp TAB>, <&kt LALT>;
            ignored-key-positions = <1 2 3>;
            ignored-layers = <1>;
        };
    };

    keymap {
        compatible = "zmk,keymap";
        label ="Default keymap";

        default_layer {
            bindings = <
                &swap    &kp LS(TAB)
                &kp B    &tog 1>;
        };

        layer2 {
            bindings = <
                &kp DOWN    &kp B
                &tog 2    &trans>;
        };

        layer3 {
            bindings = <
                &kp LEFT  &kp N2
                &trans    &kp N3>;
        };
    };
};
  • The sequence (swap, tog 1, DOWN) produces (LA(TAB), LA(DOWN)). The change to layer 1 does not fire the interrupt behavior, because it is included in ignored-layers, and DOWN is in the same position as the tri-state, also not firing the interrupt behavior.
  • The sequence (swap, tog 1, tog 2, LEFT) produces (LA(TAB), LEFT. The change to layer 2 does fire the interrupt behavior, because it is not included in ignored-layers.

Credits

Other Project

zmk-tri-state's People

Contributors

dhruvinsh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

zmk-tri-state's Issues

Integrate tri-state with the morph behavior

I loved this concept of stand-alone modules for ZMK on this package, and it made me wonder if it's possible to achieve a deeper integration with current behaviors this way.

What I would like to achieve is the following. Assume I have two keys:

+----+  +----+
| K1 |  | K2 |
+----+  +----+
  • K1 is a tri-sate that will produce the Alt+Tab (or Cmd+Tab on Macs);
  • K2 is a "morph-key" that, when pressed by itself, produces A, but if pressed while holding Alt (or Cmd on Macs), then it will produce a Shift+Alt+Tab (or Shift+Cmd+Tab or Cmd+Grave on Macs);

This is pretty straightforward today, and works like a charm:

/ {
  behaviors {
    appswtnxt: application_switch_next {
      compatible = "zmk,behavior-tri-state";
      #binding-cells = <0>;
      bindings = <&kt LGUI>, <&kp TAB>, <&kt LGUI>;
      ignored-key-positions = <LT5 LM5>;
    };

    winswtnxt: window_switch_next {
      compatible = "zmk,behavior-tri-state";
      #binding-cells = <0>;
      bindings = <&kt LGUI>, <&kp GRAVE>, <&kt LGUI>;
      ignored-key-positions = <LB5 LM5>;
    };

    appPwinN: application_switch_previous_window_next {
      compatible = "zmk,behavior-mod-morph";
      #binding-cells = <0>;
      bindings = <&winswtnxt>, <&kp GRAVE>;
      mods = <(MOD_LGUI|MOD_RGUI)>;
      keep-mods = <(MOD_LGUI|MOD_RGUI)>;
    };

    keymap {
        compatible = "zmk,keymap";
        label ="Default keymap";

        default_layer {
            bindings = <
                &appswtnxt  &appPwinN
        };
    };
};

Now, I'm not sure if Windows or Linux have such a concept, but on macOS, when you press Cmd+Grave (usually the key above the Tab), it starts the same "app switching" but only for the windows of the current application. Once started, the window switch works like an app switch, including the reverse behavior when holding down the Shift key.

I want to configure K2 to start this window-switch action with a tri-state, and use K1 for the reverse behavior.

In this case, because the key held down is the same on both behaviors, if I use the same trick with "morph" keys on K1, it will break the main behavior of if because when the tri-state for switching applications starts, it will have the Cmd key held-down, so the "morph" behavior will convert the key into its "window switching" variant.

Ideally, I would like to change morph to allow me to specify a tri-state behavior, that, when active, it will force the key to use its alternative value. Something like this:

/ {
  behaviors {
    appswtnxt: application_switch_next {
      compatible = "zmk,behavior-tri-state";
      #binding-cells = <0>;
      bindings = <&kt LGUI>, <&kp TAB>, <&kt LGUI>;
      ignored-key-positions = <LT5 LM5>;
    };

    winswtnxt: window_switch_next {
      compatible = "zmk,behavior-tri-state";
      #binding-cells = <0>;
      bindings = <&kt LGUI>, <&kp GRAVE>, <&kt LGUI>;
      ignored-key-positions = <LB5 LM5>;
    };

    appPwinN: application_switch_previous_window_next {
      compatible = "zmk,behavior-mod-morph";
      #binding-cells = <0>;
      bindings = <&winswtnxt>, <&kp LG(GRAVE)>;
      tri_state = <&appswtnxt>;
    };

    winPappN: window_switch_previous_app_next {
      compatible = "zmk,behavior-mod-morph";
      #binding-cells = <0>;
      bindings = <&appswtnxt>, <&kp LG(LS(GRAVE))>;
      tri_state = <&winswtnxt>;
    };

    keymap {
        compatible = "zmk,keymap";
        label ="Default keymap";

        default_layer {
            bindings = <
                &winPappN  &appPwinN
        };
    };
};

Now, if I set K1 to &appPwinN and K2 to &winPappN, I can use just those two keys to handle two types of tri-state behavior that uses the same holding-down key.

Can we achieve something like this with this type of module repository? If not, what would be your suggestion for this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.