Skip to content

Commit

Permalink
feat(supertab): modkey timeout for rotate key press
Browse files Browse the repository at this point in the history
  • Loading branch information
lewurm committed Dec 30, 2021
1 parent 7b2edba commit 53476ca
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ properties:
type: int
required: true
const: 2
modifier-key:
type: int
default: -1
mod-timeout-ms:
type: int
default: -1

sensor-binding-cells:
- param1
Expand Down
99 changes: 96 additions & 3 deletions app/src/behaviors/behavior_sensor_rotate_key_press.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,54 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

static int behavior_sensor_rotate_key_press_init(const struct device *dev) { return 0; };
#define ZMK_BHV_SENSOR_MAX_MODS 2

struct behavior_sensor_rotate_key_press_config {
int modifier_key;
int mod_timeout_ms;
};

struct active_mod_press {
struct k_delayed_work work;
const struct behavior_sensor_rotate_key_press_config *config;
int64_t last_timestamp;
};

struct active_mod_press active_mod_presses[ZMK_BHV_SENSOR_MAX_MODS] = {};

void behavior_sensor_rotate_key_press_work_handler(struct k_work *item) {
struct active_mod_press *c = CONTAINER_OF(item, struct active_mod_press, work);
const struct behavior_sensor_rotate_key_press_config *cfg = c->config;

/* timeout expired, release modifier */
ZMK_EVENT_RAISE(
zmk_keycode_state_changed_from_encoded(cfg->modifier_key, false, k_uptime_get()));
c->config = NULL;
c->last_timestamp = 0;
}

static int behavior_sensor_rotate_key_press_init(const struct device *dev) {
static bool init_first_run = true;

if (init_first_run) {
init_first_run = false;
for (int i = 0; i < ZMK_BHV_SENSOR_MAX_MODS; i++) {
k_delayed_work_init(&active_mod_presses[i].work,
behavior_sensor_rotate_key_press_work_handler);
active_mod_presses[i].config = NULL;
active_mod_presses[i].last_timestamp = 0;
}
}
return 0;
}

static int on_sensor_binding_triggered(struct zmk_behavior_binding *binding,
const struct device *sensor, int64_t timestamp) {
struct sensor_value value;
int err;
const struct device *dev = device_get_binding(binding->behavior_dev);
const struct behavior_sensor_rotate_key_press_config *cfg = dev->config;

uint32_t keycode;
LOG_DBG("inc keycode 0x%02X dec keycode 0x%02X", binding->param1, binding->param2);

Expand All @@ -47,20 +89,71 @@ static int on_sensor_binding_triggered(struct zmk_behavior_binding *binding,

LOG_DBG("SEND %d", keycode);

bool mod_timeout_enabled = cfg->mod_timeout_ms != -1;
struct active_mod_press *c = NULL;

if (mod_timeout_enabled) {
// lookup slot
for (int i = 0; i < ZMK_BHV_SENSOR_MAX_MODS; i++) {
if (active_mod_presses[i].config == cfg) {
c = &active_mod_presses[i];
c->config = cfg;
}
}

// nothing found, take a slot
if (c == NULL) {
for (int i = 0; i < ZMK_BHV_SENSOR_MAX_MODS; i++) {
if (active_mod_presses[i].config == NULL) {
c = &active_mod_presses[i];
c->config = cfg;
}
}
}

if (c == NULL) {
LOG_WRN("increase ZMK_BHV_SENSOR_MAX_MODS");
}

if ((timestamp - c->last_timestamp) < cfg->mod_timeout_ms) {
/* another input, restart timeout */
k_delayed_work_cancel(&c->work);
} else {
/* first time, activate modifier key */
ZMK_EVENT_RAISE(
zmk_keycode_state_changed_from_encoded(cfg->modifier_key, true, timestamp));

// TODO: Better way to do this?
k_msleep(5);
}
}

ZMK_EVENT_RAISE(zmk_keycode_state_changed_from_encoded(keycode, true, timestamp));

if (mod_timeout_enabled) {
c->last_timestamp = timestamp;
k_delayed_work_submit(&c->work, K_MSEC(cfg->mod_timeout_ms));
}

// TODO: Better way to do this?
k_msleep(5);

return ZMK_EVENT_RAISE(zmk_keycode_state_changed_from_encoded(keycode, false, timestamp));
}

static const struct behavior_driver_api behavior_sensor_rotate_key_press_driver_api = {
.sensor_binding_triggered = on_sensor_binding_triggered};
.sensor_binding_triggered = on_sensor_binding_triggered,
};

#define KP_INST(n) \
const struct behavior_sensor_rotate_key_press_config \
behavior_sensor_rotate_key_press_config_##n = { \
.modifier_key = DT_INST_PROP(n, modifier_key), \
.mod_timeout_ms = DT_INST_PROP(n, mod_timeout_ms), \
}; \
DEVICE_DT_INST_DEFINE(n, behavior_sensor_rotate_key_press_init, device_pm_control_nop, NULL, \
NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_sensor_rotate_key_press_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_sensor_rotate_key_press_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KP_INST)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/docs/assets/features/encoders/supertab.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions docs/docs/features/encoders.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,46 @@ sensor-bindings = <&inc_dec_kp C_VOL_UP C_VOL_DN &inc_dec_kp PG_UP PG_DN>;

Here, the left encoder is configured to control volume up and down while the right encoder sends either Page Up or Page Down.

### Modifier with built-in delay

In addition to the keycodes above a modifier (`modifier-key`) can be configured that is pressed simultaneously with the configured keycodes, and then held until another keycode is send via an encoder action or a timeout (`mod-timeout-ms`) is reached.

Example for application switching:

```
sm: supermods {
compatible = "zmk,behavior-sensor-rotate-key-press";
label = "encoder supermod";
#sensor-binding-cells = <2>;
modifier-key = <LWIN>;
mod-timeout-ms = <1200>;
};
[...]
sensor-bindings = <&sm TAB LS(TAB)>;
```

![Application switching with rotary encoder](../assets/features/encoders/supertab.gif)

Example for selecting characters in an text editor:

```
hlm: highlightmods {
compatible = "zmk,behavior-sensor-rotate-key-press";
label = "encoder highlighter";
#sensor-binding-cells = <2>;
modifier-key = <LSHFT>;
mod-timeout-ms = <500>;
};
[...]
sensor-bindings = <&hlm RIGHT LEFT>;
```

![Select characters with rotary encoder](../assets/features/encoders/highlighter.gif)

## Adding Encoder Support

See the [New Keyboard Shield](../development/new-shield.md#encoders) documentation for how to add or modify additional encoders to your shield.

0 comments on commit 53476ca

Please sign in to comment.