Add configurable wheel mode: sequential vs sets-based navigation
Add wheel_mode configuration option to allow users to choose between classic sequential cycling and new multi-click set-based navigation. Configuration: - wheel_mode: sequential | sets - Default: sequential (maintains backward compatibility) Sequential Mode (Legacy): - Single-click cycles through all wheel functions sequentially - Example: 0 → 1 → 2 → 3 → 4 → 5 → 0... Sets Mode (New): - Single-click: Toggle within current set (functions 0↔1, 2↔3, 4↔5) - Double-click: Switch between Set 1 ↔ Set 2 - Triple-click: Toggle to/from Set 3 - Supports up to 6 wheel functions in 3 sets of 2 Set Navigation Mapping: - Set 1 → double-click → Set 2 - Set 1 → triple-click → Set 3 - Set 2 → double-click → Set 1 - Set 3 → triple-click → Set 1 - Set 3 → double-click → Set 1 Implementation: - Add wheel_mode_t enum to config.h - Add parse/print functions for wheel_mode in config.c - Conditionally execute sequential or sets logic in device.c - Update default.cfg with comprehensive documentation Files modified: - src/config.h: Add wheel_mode_t enum and field - src/config.c: Add parsing, initialization, and debug printing - src/device.c: Add mode-conditional logic for wheel toggle - default.cfg: Add wheel_mode config with detailed documentation
+154 −81 · 4 files changed
-
default.cfg+30 −11 -
src/config.c+31 −0 -
src/config.h+7 −0 -
src/device.c+86 −70
@@ -76,21 +76,40 @@ leader_function: shift leader_timeout: 1000 leader_mode: toggle +// +// Wheel Toggle Mode Configuration +// wheel_mode: Choose how button 18 (wheel toggle) cycles through wheel functions +// Options: sequential | sets +// Default: sequential +// +// sequential: Classic behavior - single-click cycles through all functions sequentially +// Example: Click → Function 0 → Click → Function 1 → Click → Function 2 → etc. +// +// sets: Set-based navigation using multi-click detection +// - Single-click: Toggle within current set (e.g., function 0 ↔ 1) +// - Double-click: Switch between Set 1 ↔ Set 2 +// - Triple-click: Toggle to/from Set 3 +// +// Set Navigation: +// • Set 1 → double-click → Set 2 +// • Set 1 → triple-click → Set 3 +// • Set 2 → double-click → Set 1 +// • Set 3 → triple-click → Set 1 +// • Set 3 → double-click → Set 1 +// +// With 6 wheel functions defined: +// Set 1: Wheel functions 0-1 (e.g., brush size ↔ opacity) +// Set 2: Wheel functions 2-3 (e.g., flow ↔ rotation) +// Set 3: Wheel functions 4-5 (e.g., scatter ↔ spacing) +// +wheel_mode: sequential + // // Wheel Click Timeout Configuration -// wheel_click_timeout: Time in milliseconds for multi-click detection on button 18 (wheel toggle) +// wheel_click_timeout: Time in milliseconds for multi-click detection on button 18 // Range: 20-990 ms (hard limit enforced) // Default: 300 ms -// -// Multi-click behavior for button 18 (wheel toggle): -// - Single-click: Toggle within current set (e.g., function 0 ↔ 1) -// - Double-click: Switch between Set 1 (functions 0-1) ↔ Set 2 (functions 2-3) -// - Triple-click: Toggle to/from Set 3 (functions 4-5) -// -// Example: With 6 wheel functions defined: -// Set 1: Wheel functions 0-1 (e.g., brush size ↔ opacity) -// Set 2: Wheel functions 2-3 (e.g., flow ↔ rotation) -// Set 3: Wheel functions 4-5 (e.g., scatter ↔ spacing) +// Note: Only applies when wheel_mode is set to "sets" // wheel_click_timeout: 300 @@ -6,6 +6,26 @@ #include <pwd.h> #include <unistd.h> +// Helper function to convert wheel mode enum to string +static const char* wheel_mode_to_string(wheel_mode_t mode) { + switch (mode) { + case WHEEL_MODE_SEQUENTIAL: return "sequential"; + case WHEEL_MODE_SETS: return "sets"; + default: return "unknown"; + } +} + +// Helper function to parse wheel mode string to enum +static wheel_mode_t parse_wheel_mode(const char* mode_str) { + if (strcasecmp(mode_str, "sequential") == 0) { + return WHEEL_MODE_SEQUENTIAL; + } else if (strcasecmp(mode_str, "sets") == 0) { + return WHEEL_MODE_SETS; + } + // Default to sequential for backward compatibility + return WHEEL_MODE_SEQUENTIAL; +} + // Create a new configuration structure config_t* config_create(void) { config_t* config = malloc(sizeof(config_t)); @@ -32,6 +52,7 @@ config_t* config_create(void) { config->totalWheels = 0; config->enable_uclogic = 0; config->wheel_click_timeout_ms = 300; // 300ms default timeout + config->wheel_mode = WHEEL_MODE_SEQUENTIAL; // Default to sequential (legacy behavior) // Initialize leader state config->leader.leader_button = -1; @@ -153,6 +174,15 @@ int config_load(config_t* config, const char* filename, int debug) { continue; } + // Parse wheel_mode + if (strncasecmp(line, "wheel_mode:", 11) == 0) { + char* value = line + 11; + while (*value == ' ') value++; + config->wheel_mode = parse_wheel_mode(value); + if (debug) printf("Config: wheel_mode = %s\n", wheel_mode_to_string(config->wheel_mode)); + continue; + } + // Parse leader_button if (strncasecmp(line, "leader_button:", 14) == 0) { char* value = line + 14; @@ -365,5 +395,6 @@ void config_print(const config_t* config, int debug) { printf("\n=== Wheel Click Configuration ===\n"); printf("Multi-click timeout: %d ms\n", config->wheel_click_timeout_ms); + printf("Wheel mode: %s\n", wheel_mode_to_string(config->wheel_mode)); printf("\n"); } @@ -3,6 +3,12 @@ #include "leader.h" +// Wheel mode enumeration +typedef enum { + WHEEL_MODE_SEQUENTIAL, // Single-click cycles through all functions (default/legacy) + WHEEL_MODE_SETS // Multi-click for set-based navigation +} wheel_mode_t; + // Wheel event structure typedef struct { char* right; @@ -18,6 +24,7 @@ typedef struct { leader_state leader; int enable_uclogic; int wheel_click_timeout_ms; // Multi-click detection timeout (20-990ms) + wheel_mode_t wheel_mode; // Wheel toggle mode (sequential or sets) } config_t; // Configuration functions @@ -339,88 +339,104 @@ void device_run(libusb_context* ctx, config_t* config, int debug, int accept, in prevEvent.function = ""; } } else if (strcmp(config->events[button_index].function, "swap") == 0) { - // Multi-click detection for button 18 - struct timeval now; - gettimeofday(&now, NULL); - - long time_since_last_click = 0; - if (last_button18_time.tv_sec != 0 || last_button18_time.tv_usec != 0) { - time_since_last_click = - (now.tv_sec - last_button18_time.tv_sec) * 1000 + - (now.tv_usec - last_button18_time.tv_usec) / 1000; - } - - // If click is within timeout window, increment count - if (time_since_last_click > 0 && time_since_last_click < config->wheel_click_timeout_ms) { - button18_click_count++; + // Check wheel mode + if (config->wheel_mode == WHEEL_MODE_SEQUENTIAL) { + // Sequential mode: simple cycling through all functions + if (wheelFunction != config->totalWheels - 1) { + wheelFunction++; + } else { + wheelFunction = 0; + } + if (debug == 1) { + printf("Sequential mode - Wheel Function: %d\n", wheelFunction); + printf("Function: %s | %s\n", + config->wheelEvents[wheelFunction].left ? config->wheelEvents[wheelFunction].left : "(null)", + config->wheelEvents[wheelFunction].right ? config->wheelEvents[wheelFunction].right : "(null)"); + } } else { - // This is a new click sequence - button18_click_count = 1; - } + // Sets mode: multi-click detection for set-based navigation + struct timeval now; + gettimeofday(&now, NULL); + + long time_since_last_click = 0; + if (last_button18_time.tv_sec != 0 || last_button18_time.tv_usec != 0) { + time_since_last_click = + (now.tv_sec - last_button18_time.tv_sec) * 1000 + + (now.tv_usec - last_button18_time.tv_usec) / 1000; + } - last_button18_time = now; + // If click is within timeout window, increment count + if (time_since_last_click > 0 && time_since_last_click < config->wheel_click_timeout_ms) { + button18_click_count++; + } else { + // This is a new click sequence + button18_click_count = 1; + } - // Wait for timeout to process the click(s) - usleep(config->wheel_click_timeout_ms * 1000); + last_button18_time = now; - // Check if more clicks arrived during the wait - gettimeofday(&now, NULL); - time_since_last_click = - (now.tv_sec - last_button18_time.tv_sec) * 1000 + - (now.tv_usec - last_button18_time.tv_usec) / 1000; + // Wait for timeout to process the click(s) + usleep(config->wheel_click_timeout_ms * 1000); - // Only process if no more clicks arrived - if (time_since_last_click >= config->wheel_click_timeout_ms) { - int final_click_count = button18_click_count; - button18_click_count = 0; + // Check if more clicks arrived during the wait + gettimeofday(&now, NULL); + time_since_last_click = + (now.tv_sec - last_button18_time.tv_sec) * 1000 + + (now.tv_usec - last_button18_time.tv_usec) / 1000; - if (debug == 1) { - printf("Button 18 clicks: %d\n", final_click_count); - } + // Only process if no more clicks arrived + if (time_since_last_click >= config->wheel_click_timeout_ms) { + int final_click_count = button18_click_count; + button18_click_count = 0; - // Process based on click count - if (final_click_count == 1) { - // Single-click: toggle within current set - wheel_position_in_set = 1 - wheel_position_in_set; - } else if (final_click_count == 2) { - // Double-click: toggle between Set 0 and Set 1 - if (wheel_current_set == 0) { - wheel_current_set = 1; - } else if (wheel_current_set == 1) { - wheel_current_set = 0; - } else { - // From Set 2, go to Set 0 - wheel_current_set = 0; + if (debug == 1) { + printf("Sets mode - Button 18 clicks: %d\n", final_click_count); } - wheel_position_in_set = 0; // Start at first function in new set - } else if (final_click_count >= 3) { - // Triple-click: toggle to/from Set 2 - if (wheel_current_set == 2) { - // From Set 2, go back to Set 0 - wheel_current_set = 0; - } else { - // From Set 0 or 1, go to Set 2 - wheel_current_set = 2; + + // Process based on click count + if (final_click_count == 1) { + // Single-click: toggle within current set + wheel_position_in_set = 1 - wheel_position_in_set; + } else if (final_click_count == 2) { + // Double-click: toggle between Set 0 and Set 1 + if (wheel_current_set == 0) { + wheel_current_set = 1; + } else if (wheel_current_set == 1) { + wheel_current_set = 0; + } else { + // From Set 2, go to Set 0 + wheel_current_set = 0; + } + wheel_position_in_set = 0; // Start at first function in new set + } else if (final_click_count >= 3) { + // Triple-click: toggle to/from Set 2 + if (wheel_current_set == 2) { + // From Set 2, go back to Set 0 + wheel_current_set = 0; + } else { + // From Set 0 or 1, go to Set 2 + wheel_current_set = 2; + } + wheel_position_in_set = 0; // Start at first function in new set } - wheel_position_in_set = 0; // Start at first function in new set - } - // Calculate actual wheel function index - wheelFunction = (wheel_current_set * 2) + wheel_position_in_set; + // Calculate actual wheel function index + wheelFunction = (wheel_current_set * 2) + wheel_position_in_set; - // Ensure we don't go beyond available wheels - if (wheelFunction >= config->totalWheels) { - wheelFunction = 0; - wheel_current_set = 0; - wheel_position_in_set = 0; - } + // Ensure we don't go beyond available wheels + if (wheelFunction >= config->totalWheels) { + wheelFunction = 0; + wheel_current_set = 0; + wheel_position_in_set = 0; + } - if (debug == 1) { - printf("Set: %d | Position: %d | Wheel Function: %d\n", - wheel_current_set, wheel_position_in_set, wheelFunction); - printf("Function: %s | %s\n", - config->wheelEvents[wheelFunction].left ? config->wheelEvents[wheelFunction].left : "(null)", - config->wheelEvents[wheelFunction].right ? config->wheelEvents[wheelFunction].right : "(null)"); + if (debug == 1) { + printf("Set: %d | Position: %d | Wheel Function: %d\n", + wheel_current_set, wheel_position_in_set, wheelFunction); + printf("Function: %s | %s\n", + config->wheelEvents[wheelFunction].left ? config->wheelEvents[wheelFunction].left : "(null)", + config->wheelEvents[wheelFunction].right ? config->wheelEvents[wheelFunction].right : "(null)"); + } } } } else if (strcmp(config->events[button_index].function, "mouse1") == 0 ||