Custom QMK Layers
Contents
I’ve been using the Ergodox EZ for a while, but I’ve never liked the different layer options. I always found they did half of what I wanted. The closest layer behavior is the One shot keys, in particular the OSL(N) one shot layer functionality. Unfortunately it still relies a lot on timeouts and does not have immediate application or cancelation in many scenarios. Below is how I wish the one shot layer functionality worked:
- Tap to toggle oneshot, tap again to cancel
- Tap + press another key for one-shot behavior
- Hold for momentary access, upon release reset layer
- Long hold without pressing others keys cancels on release
- Triple-tap for locked mode, tap again to unlock
#include "../custom_keys.h"
#include <assert.h>
// State tracking for each layer
typedef struct {
bool key_held;
bool oneshot;
bool locked;
bool other_key_pressed;
int layer_num;
uint16_t keycode;
uint16_t hold_timer;
uint8_t tap_count;
uint16_t last_tap_time;
} custom_oneshot_layer_state_t;
static custom_oneshot_layer_state_t layer_states[4] = {
{.keycode = CUSTOM_OSL1, .layer_num = 1},
{.keycode = CUSTOM_OSL2, .layer_num = 2},
{.keycode = CUSTOM_OSL3, .layer_num = 3},
{.keycode = CUSTOM_OSL4, .layer_num = 4},
};
#define LAYER_HOLD_TIMEOUT 500 // ms
#define TAP_TIMEOUT 300 // ms
static const int NUM_ENTRIES = sizeof(layer_states) / sizeof(layer_states[0]);
_Static_assert((sizeof(layer_states) / sizeof(layer_states[0])) == 4, "mismatch of entries");
int get_keycode_index(uint16_t keycode) {
for (int i = 0; i < NUM_ENTRIES; ++i) {
if (layer_states[i].keycode == keycode) {
return i;
}
}
return -1;
}
bool process_custom_oneshot_layers(uint16_t keycode, keyrecord_t *record) {
const int layer_idx = get_keycode_index(keycode);
if (layer_idx >= 0) {
custom_oneshot_layer_state_t *state = &layer_states[layer_idx];
if (record->event.pressed) {
// Key down
state->key_held = true;
state->other_key_pressed = false;
state->hold_timer = timer_read();
layer_on(state->layer_num);
} else {
// Key up
if (!state->other_key_pressed) {
// No other keys pressed while holding
uint16_t hold_duration = timer_elapsed(state->hold_timer);
if (hold_duration > LAYER_HOLD_TIMEOUT) {
// Held too long - cancel
layer_off(state->layer_num);
state->tap_count = 0;
} else {
// This was a quick tap
if (state->locked) {
// In locked mode - unlock and exit
state->locked = false;
state->oneshot = false;
layer_off(state->layer_num);
state->tap_count = 0;
} else {
// Not locked - handle tap counting (whether oneshot or not)
if (timer_elapsed(state->last_tap_time) > TAP_TIMEOUT) {
state->tap_count = 1;
} else {
state->tap_count++;
}
state->last_tap_time = timer_read();
// Handle based on tap count
if (state->tap_count == 1) {
if (state->oneshot) {
state->oneshot = false;
layer_off(state->layer_num);
} else {
// First tap - enter oneshot
state->oneshot = true;
}
} else if (state->tap_count == 2) {
// Second tap - exit oneshot
state->oneshot = false;
layer_off(state->layer_num);
} else if (state->tap_count >= 3) {
// Third tap - enter locked mode
state->oneshot = false;
state->locked = true;
layer_on(state->layer_num);
state->tap_count = 0;
}
}
}
} else {
// This was a hold - turn off layer unless in oneshot mode
if (!state->locked && !state->oneshot) {
layer_off(state->layer_num);
}
state->tap_count = 0;
}
state->key_held = false;
}
return false;
}
// Handle all other keys
if (record->event.pressed) {
// Key down - just mark that another key was pressed if layer key is held
for (int i = 0; i < NUM_ENTRIES; i++) {
custom_oneshot_layer_state_t *state = &layer_states[i];
if (state->key_held) {
state->other_key_pressed = true;
}
}
// Reset tap counts when other keys are pressed
for (int i = 0; i < NUM_ENTRIES; i++) {
custom_oneshot_layer_state_t *state = &layer_states[i];
if (!state->locked && !state->key_held) {
state->tap_count = 0;
}
}
} else {
// Key up - now turn off the layer if in oneshot mode
for (int i = 0; i < NUM_ENTRIES; i++) {
custom_oneshot_layer_state_t *state = &layer_states[i];
if (state->oneshot && !state->key_held) {
state->oneshot = false;
layer_off(state->layer_num);
}
}
}
return true;
}