Fix multi-click detection: remove blocking sleep
The previous implementation used usleep() to wait for additional clicks,
which blocked the main USB event loop and prevented subsequent clicks
from being detected. This made double-click and triple-click impossible.
Solution:
- Move click processing to the top of the event loop (non-blocking)
- When button 18 is pressed, just record the click timestamp and increment counter
- Check for timeout at the start of each loop iteration
- Process accumulated clicks only after timeout expires
This allows the driver to continue receiving button presses while waiting
for the multi-click timeout, enabling proper double-click and triple-click
detection.
Before: Every click was processed as single-click because the event loop
was blocked during the timeout period
After: Clicks accumulate properly and are processed as multi-clicks
after the timeout expires
Files modified:
- src/device.c: Refactor multi-click logic to be non-blocking
+75 −64 · 1 file changed
-
src/device.c+75 −64
@@ -301,6 +301,73 @@ void device_run(libusb_context* ctx, config_t* config, int debug, int accept, in break; } + // Check if we have pending button 18 clicks that need to be processed + if (config->wheel_mode == WHEEL_MODE_SETS && button18_click_count > 0 && + (last_button18_time.tv_sec != 0 || last_button18_time.tv_usec != 0)) { + struct timeval now; + gettimeofday(&now, NULL); + long time_since_last_click = + (now.tv_sec - last_button18_time.tv_sec) * 1000 + + (now.tv_usec - last_button18_time.tv_usec) / 1000; + + // If timeout has expired, process the accumulated clicks + if (time_since_last_click >= config->wheel_click_timeout_ms) { + int final_click_count = button18_click_count; + button18_click_count = 0; + last_button18_time.tv_sec = 0; + last_button18_time.tv_usec = 0; + + if (debug == 1) { + printf("Sets mode - Button 18 clicks: %d\n", final_click_count); + } + + // 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 + } + + // 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; + } + + 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)"); + } + } + } + // Convert data to keycodes if (data[4] != 0) keycode = data[4]; @@ -360,6 +427,7 @@ void device_run(libusb_context* ctx, config_t* config, int debug, int accept, in } } else { // Sets mode: multi-click detection for set-based navigation + // Just record the click - processing happens at the top of the event loop struct timeval now; gettimeofday(&now, NULL); @@ -373,76 +441,19 @@ void device_run(libusb_context* ctx, config_t* config, int debug, int accept, in // 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++; + if (debug == 1) { + printf("Button 18 click recorded (count now: %d)\n", button18_click_count); + } } else { // This is a new click sequence button18_click_count = 1; - } - - last_button18_time = now; - - // Wait for timeout to process the click(s) - usleep(config->wheel_click_timeout_ms * 1000); - - // 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; - - // 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; - - if (debug == 1) { - printf("Sets mode - Button 18 clicks: %d\n", final_click_count); - } - - // 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 - } - - // 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; - } - 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)"); + printf("Button 18 new click sequence started\n"); } } + + last_button18_time = now; + // Don't process yet - wait for timeout (checked at top of loop) } } else if (strcmp(config->events[button_index].function, "mouse1") == 0 || strcmp(config->events[button_index].function, "mouse2") == 0 ||