/* * Copyright (c) 2024 The ZMK Contributors * * SPDX-License-Identifier: MIT */ #define DT_DRV_COMPAT zmk_input_processor_temp_layer #include #include #include #include #include #include #include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); /* Constants and Types */ #define MAX_LAYERS ZMK_KEYMAP_LAYERS_LEN struct temp_layer_config { int16_t require_prior_idle_ms; const uint16_t *excluded_positions; size_t num_positions; }; struct temp_layer_state { uint8_t toggle_layer; bool is_active; int64_t last_tapped_timestamp; }; struct temp_layer_data { const struct device *dev; struct temp_layer_state state; }; /* Static Work Queue Items */ static struct k_work_delayable layer_disable_works[MAX_LAYERS]; /* Position Search */ static bool position_is_excluded(const struct temp_layer_config *config, uint32_t position) { if (!config->excluded_positions || !config->num_positions) { return false; } const uint16_t *end = config->excluded_positions + config->num_positions; for (const uint16_t *pos = config->excluded_positions; pos < end; pos++) { if (*pos == position) { return true; } } return false; } /* Timing Check */ static bool should_quick_tap(const struct temp_layer_config *config, int64_t last_tapped, int64_t current_time) { return (last_tapped + config->require_prior_idle_ms) > current_time; } /* Layer State Management */ static void update_layer_state(struct temp_layer_state *state, bool activate) { if (state->is_active == activate) { return; } state->is_active = activate; if (activate) { zmk_keymap_layer_activate(state->toggle_layer); LOG_DBG("Layer %d activated", state->toggle_layer); } else { zmk_keymap_layer_deactivate(state->toggle_layer); LOG_DBG("Layer %d deactivated", state->toggle_layer); } } /* Work Queue Callback */ static void layer_disable_callback(struct k_work *work) { struct k_work_delayable *d_work = k_work_delayable_from_work(work); int layer_index = ARRAY_INDEX(layer_disable_works, d_work); const struct device *dev = DEVICE_DT_INST_GET(0); struct temp_layer_data *data = (struct temp_layer_data *)dev->data; if (zmk_keymap_layer_active(layer_index)) { update_layer_state(&data->state, false); } } /* Event Handlers */ static int handle_position_state_changed(const zmk_event_t *eh) { const struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh); if (!ev->state) { return ZMK_EV_EVENT_BUBBLE; } const struct device *dev = DEVICE_DT_INST_GET(0); struct temp_layer_data *data = (struct temp_layer_data *)dev->data; const struct temp_layer_config *cfg = dev->config; if (data->state.is_active && cfg->excluded_positions && cfg->num_positions > 0) { if (!position_is_excluded(cfg, ev->position)) { LOG_DBG("Position not excluded, deactivating layer"); update_layer_state(&data->state, false); } } LOG_DBG("Position excluded, continuing"); return ZMK_EV_EVENT_BUBBLE; } static int handle_keycode_state_changed(const zmk_event_t *eh) { const struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh); if (!ev->state) { return ZMK_EV_EVENT_BUBBLE; } const struct device *dev = DEVICE_DT_INST_GET(0); struct temp_layer_data *data = (struct temp_layer_data *)dev->data; LOG_DBG("Setting last_tapped_timestamp to: %d", ev->timestamp); data->state.last_tapped_timestamp = ev->timestamp; return ZMK_EV_EVENT_BUBBLE; } static int handle_state_changed_dispatcher(const zmk_event_t *eh) { if (as_zmk_position_state_changed(eh) != NULL) { LOG_DBG("Dispatching handle_position_state_changed"); return handle_position_state_changed(eh); } else if (as_zmk_keycode_state_changed(eh) != NULL) { LOG_DBG("Dispatching handle_keycode_state_changed"); return handle_keycode_state_changed(eh); } return ZMK_EV_EVENT_BUBBLE; } /* Driver Implementation */ static int temp_layer_handle_event(const struct device *dev, struct input_event *event, uint32_t param1, uint32_t param2, struct zmk_input_processor_state *state) { if (param1 >= MAX_LAYERS) { LOG_ERR("Invalid layer index: %d", param1); return -EINVAL; } struct temp_layer_data *data = (struct temp_layer_data *)dev->data; const struct temp_layer_config *cfg = dev->config; data->state.toggle_layer = param1; if (!data->state.is_active && !should_quick_tap(cfg, data->state.last_tapped_timestamp, k_uptime_get())) { update_layer_state(&data->state, true); } if (param2 > 0) { k_work_reschedule(&layer_disable_works[param1], K_MSEC(param2)); } return 0; } static int temp_layer_init(const struct device *dev) { for (int i = 0; i < MAX_LAYERS; i++) { k_work_init_delayable(&layer_disable_works[i], layer_disable_callback); } return 0; } /* Driver API */ static const struct zmk_input_processor_driver_api temp_layer_driver_api = { .handle_event = temp_layer_handle_event, }; /* Event Listeners Conditions */ #define NEEDS_POSITION_HANDLERS(n, ...) DT_INST_PROP_HAS_IDX(n, excluded_positions, 0) #define NEEDS_KEYCODE_HANDLERS(n, ...) (DT_INST_PROP_OR(n, require_prior_idle_ms, 0) > 0) /* Event Handlers Registration */ #if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_POSITION_HANDLERS, ||) || \ DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_KEYCODE_HANDLERS, ||) ZMK_LISTENER(processor_temp_layer, handle_state_changed_dispatcher); #endif /* Individual Subscriptions */ #if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_POSITION_HANDLERS, ||) ZMK_SUBSCRIPTION(processor_temp_layer, zmk_position_state_changed); #endif #if DT_INST_FOREACH_STATUS_OKAY_VARGS(NEEDS_KEYCODE_HANDLERS, ||) ZMK_SUBSCRIPTION(processor_temp_layer, zmk_keycode_state_changed); #endif /* Device Instantiation */ #define TEMP_LAYER_INST(n) \ static struct temp_layer_data processor_temp_layer_data_##n = {}; \ static const uint16_t excluded_positions_##n[] = DT_INST_PROP(n, excluded_positions); \ static const struct temp_layer_config processor_temp_layer_config_##n = { \ .require_prior_idle_ms = DT_INST_PROP_OR(n, require_prior_idle_ms, 0), \ .excluded_positions = excluded_positions_##n, \ .num_positions = DT_INST_PROP_LEN(n, excluded_positions), \ }; \ DEVICE_DT_INST_DEFINE(n, temp_layer_init, NULL, &processor_temp_layer_data_##n, \ &processor_temp_layer_config_##n, POST_KERNEL, \ CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &temp_layer_driver_api); DT_INST_FOREACH_STATUS_OKAY(TEMP_LAYER_INST)