#include "os_input.h" #include "os_event.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "esp_timer.h" static const char *TAG = "SYS_INPUT"; #define DEBOUNCE_TIME_MS 12 #define DOUBLE_CLICK_GAP_MS 260 #define LONG_PRESS_TIME_MS 380 #define HOME_PRESS_TIME_MS 1400 typedef struct { sys_input_handler_t handler; void *user_arg; } input_listener_t; #define MAX_INPUT_LISTENERS 8 static input_listener_t s_listeners[MAX_INPUT_LISTENERS]; static size_t s_listener_count = 0; static gpio_num_t s_button_pin = GPIO_NUM_9; static void dispatch_action(input_action_t action) { sys_event_id_t evt_id = SYS_EVENT_CUSTOM; switch (action) { case INPUT_ACTION_CLICK: evt_id = SYS_EVENT_INPUT_SHORT_PRESS; break; case INPUT_ACTION_DOUBLE_CLICK: evt_id = SYS_EVENT_INPUT_DOUBLE_CLICK; break; case INPUT_ACTION_LONG_PRESS: evt_id = SYS_EVENT_INPUT_LONG_PRESS; break; case INPUT_ACTION_HOLD_HOME: evt_id = SYS_EVENT_INPUT_EXTRA_LONG_PRESS; break; default: return; } sys_event_post(evt_id, (int32_t)action, NULL); for (size_t i = 0; i < s_listener_count; i++) { if (s_listeners[i].handler) { s_listeners[i].handler(action, s_listeners[i].user_arg); } } } void sys_input_inject(input_action_t action) { dispatch_action(action); } esp_err_t sys_input_register_handler(sys_input_handler_t handler, void *user_arg) { if (!handler) return ESP_ERR_INVALID_ARG; if (s_listener_count >= MAX_INPUT_LISTENERS) return ESP_ERR_NO_MEM; s_listeners[s_listener_count].handler = handler; s_listeners[s_listener_count].user_arg = user_arg; s_listener_count++; return ESP_OK; } static void button_scanner_task(void *pvParameters) { bool last_state = false; uint32_t press_start_ms = 0; uint32_t last_click_time_ms = 0; bool long_press_fired = false; bool home_press_fired = false; while (1) { vTaskDelay(pdMS_TO_TICKS(5)); // High-rate 200 Hz scan loop for zero input lag uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000ULL); bool current_state = (gpio_get_level(s_button_pin) == 0); if (current_state && !last_state) { // Button pressed down press_start_ms = now_ms; long_press_fired = false; home_press_fired = false; } else if (current_state && last_state) { // Button held down uint32_t held_duration = now_ms - press_start_ms; if (!home_press_fired && held_duration >= HOME_PRESS_TIME_MS) { home_press_fired = true; dispatch_action(INPUT_ACTION_HOLD_HOME); } else if (!long_press_fired && !home_press_fired && held_duration >= LONG_PRESS_TIME_MS) { long_press_fired = true; dispatch_action(INPUT_ACTION_LONG_PRESS); } } else if (!current_state && last_state) { // Button released uint32_t press_duration = now_ms - press_start_ms; if (press_duration >= DEBOUNCE_TIME_MS && !long_press_fired && !home_press_fired) { if ((now_ms - last_click_time_ms) <= DOUBLE_CLICK_GAP_MS) { dispatch_action(INPUT_ACTION_DOUBLE_CLICK); last_click_time_ms = 0; } else { // Instant click dispatch upon tap release (no 280ms lag penalty!) dispatch_action(INPUT_ACTION_CLICK); last_click_time_ms = now_ms; } } } last_state = current_state; } } esp_err_t sys_input_init(gpio_num_t button_gpio) { s_button_pin = button_gpio; gpio_config_t io_conf = { .pin_bit_mask = (1ULL << s_button_pin), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; esp_err_t err = gpio_config(&io_conf); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to configure button GPIO %d: %s", (int)s_button_pin, esp_err_to_name(err)); return err; } BaseType_t ret = xTaskCreate(button_scanner_task, "btn_scan_task", 3072, NULL, 12, NULL); if (ret != pdPASS) { ESP_LOGE(TAG, "Failed to create button scanner task"); return ESP_FAIL; } ESP_LOGI(TAG, "Input gesture engine started on GPIO %d (Instant-Response Mode)", (int)s_button_pin); return ESP_OK; }