# How OpenLogi Implements Gesture Detection and Dispatch for Mouse Buttons

> Discover how OpenLogi implements gesture detection and dispatch for mouse buttons. Learn about its HID++ controls, motion processing pipeline, and captured input events.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-09-13

---

**OpenLogi detects mouse-button gestures by diverting HID++ controls to report raw XY motion, then processes those deltas through a three-layer pipeline—binding definitions, capture sessions, and event handlers—to emit high-level `CapturedInput::Gesture` events.**

OpenLogi is an open-source driver for Logitech mice that replicates the gesture functionality found in proprietary tools like Logitech Options+. The implementation treats a mouse-button gesture as a **diverted HID++ control** that reports raw XY motion rather than standard button clicks. This article examines the complete pipeline from hardware diversion to GUI event emission based on the AprilNEA/OpenLogi source code.

## Architecture Overview: The Three-Layer Pipeline

The gesture detection system consists of three distinct layers, each with specific responsibilities:

- **Binding Layer**: Defines the five possible gesture directions (↑, ↓, ←, →, Click) and provides stable serialization keys. Implemented in [`crates/openlogi-core/src/binding/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/gesture.rs).
- **Capture Session Layer**: Opens a dedicated HID++ channel, arms gesture-source controls for raw-XY reporting, and manages the event loop. Lives in [`crates/openlogi-device/src/session/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/session/gesture.rs).
- **Event Handling Layer**: Translates raw XY deltas into directional gestures, tracks button holds, and emits click fallbacks. Also located in the session [`gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/gesture.rs) file.

## Defining Gesture Directions and Bindings

The foundation of the system is the `GestureDirection` enum, which enumerates the five logical slots exposed by Logitech mice for their dedicated gesture button or haptic panel.

```rust
pub enum GestureDirection {
    Up,
    Down,
    Left,
    Right,
    Click,
}

impl GestureDirection {
    pub const ALL: [Self; 5] = [Self::Up, Self::Down, Self::Left, Self::Right, Self::Click];
    
    pub fn label(self) -> &'static str { /* … */ }
    pub fn translation_key(self) -> &'static str { /* … */ }
}

```

This enum is **serde-serializable**, enabling persistence in TOML binding files. The definitions reside in [[`crates/openlogi-core/src/binding/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/gesture.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-core/src/binding/gesture.rs#L15-L71).

## Initializing the Capture Session

When the GUI requests gesture handling, OpenLogi creates a **capture session** via `run_capture_session`. This async function establishes the communication pipeline required for raw motion reporting.

The session initialization follows this sequence:

1. **Open HID++ Channel**: Establishes a single route channel using `open_route_channel`.

2. **Arm Gesture Controls**: Marks gesture-source controls (identified by `GESTURE_SOURCE_BUTTONS`) for raw-XY reporting via `arm_reprog_control`:

   ```rust
   arm_reprog_control(&rc, cid, true, &mut armed.reporting).await?;
   armed.gesture_cids.push(cid);
   ```

   This code appears in [[`crates/openlogi-device/src/session/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/session/gesture.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-device/src/session/gesture.rs#L58-L66).

3. **Register Message Listener**: Adds a guarded listener to the channel that distinguishes between reprog-control events (button states) and raw-XY events (gesture motion).

## Translating Raw XY Motion into Gestures

The core detection logic lives in `handle_raw_xy`, which processes diverted raw motion reports and accumulates them into directional gestures.

```rust
fn handle_raw_xy(acc: &mut CaptureAccum, dx: i16, dy: i16, sink: &mpsc::UnboundedSender<CapturedInput>) {
    // The current hold owns the motion…
    let HoldState::Holding { button, swipe, overlap, skip_first_raw_xy, .. } = &mut acc.hold else { return };
    
    // …but overlapping holds are ignored.
    if *overlap { return; }
    
    // Discard the first jump from the haptic panel.
    if *skip_first_raw_xy { *skip_first_raw_xy = false; return; }
    
    // Accumulate the delta; when a clean direction appears, emit a gesture.
    if let Some(direction) = swipe.accumulate(i32::from(dx), i32::from(dy)) {
        let _ = sink.send(CapturedInput::Gesture(*button, direction));
    }
}

```

Key technical details from this implementation:

- **CaptureAccum**: Tracks the `HoldState` to determine which button currently owns the raw-XY stream.
- **SwipeAccumulator**: Normalizes motion vectors and determines when a direction is committed (mid-swipe, matching Logitech Options+ behavior).
- **Haptic Panel Compensation**: Discards the first XY jump to prevent phantom gestures from the MX Master 4 haptic panel.
- **Overlap Protection**: Ignores motion when multiple buttons are held simultaneously to prevent gesture conflicts.

## Handling Button Edges and Click Fallbacks

The `handle_reprog_with_gesture_buttons` function manages the **diverted button reports** that indicate physical press and release edges. It also determines when a gesture should fall back to a simple click.

When a button release occurs without sufficient motion to trigger a directional gesture, the system emits a click event:

```rust
if let HoldState::Holding { button, mut swipe, .. } = previous && swipe.end() {
    let _ = sink.send(CapturedInput::Gesture(button, GestureDirection::Click));
}

```

This logic ensures that tapping the gesture button without swiping registers as a `GestureDirection::Click` rather than being lost entirely. The function also updates internal state trackers (`acc.gestures_down`, `acc.dpi_down`, `acc.buttons_down`) to maintain synchronization between physical hardware state and the GUI representation.

All button-edge handling is implemented in [[`crates/openlogi-device/src/session/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/session/gesture.rs)](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-device/src/session/gesture.rs#L24-L61).

## Restoring Device State After Capture

When the session terminates—whether through shutdown, device disconnect, or channel failure—the `finish_capture` function restores each diverted control to its original firmware state using `ReprogRestore` and `ArmedThumbwheel`. This guarantees that gesture-source buttons return to their default behavior after OpenLogi exits, preventing the hardware from remaining in a diverted, non-functional state.

## Summary

OpenLogi's **gesture detection and dispatch for mouse buttons** operates through a sophisticated pipeline that bridges raw HID++ hardware reports with high-level GUI events:

- **Binding definitions** in `openlogi-core` establish the five possible gesture directions (Up, Down, Left, Right, Click) as serializable enums.
- **Capture sessions** in `openlogi-device` divert HID++ controls to report raw XY motion instead of standard button presses.
- **Event handlers** accumulate motion deltas using `SwipeAccumulator`, filter haptic panel artifacts, and emit `CapturedInput::Gesture` events only when clear directional intent is detected.
- **Click fallback logic** ensures that brief button presses without motion register as explicit Click gestures.
- **State restoration** returns hardware controls to their original firmware configuration when the driver shuts down.

## Frequently Asked Questions

### How does OpenLogi distinguish between a click and a gesture swipe?

OpenLogi uses the `SwipeAccumulator` to track motion deltas while the gesture button is held. If the button is released before the accumulator determines a clear directional threshold has been crossed, `handle_reprog_with_gesture_buttons` emits a `CapturedInput::Gesture` event with `GestureDirection::Click`. This matches the behavior of Logitech's proprietary Options+ software, where a quick tap without movement registers as a click.

### What prevents phantom gestures from the MX Master 4 haptic panel?

The `handle_raw_xy` function includes a `skip_first_raw_xy` flag in the `HoldState` struct. When a gesture button press initiates a new capture session, this flag is set to true, causing the first raw-XY report to be discarded. This compensates for the initial position jump reported by the haptic panel on MX Master 4 devices, ensuring that resting your finger on the button does not trigger an unintended swipe.

### Which source files contain the core gesture detection logic?

The primary implementation spans two crates: [`crates/openlogi-core/src/binding/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/gesture.rs) defines the `GestureDirection` enum and serialization logic, while [`crates/openlogi-device/src/session/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/session/gesture.rs) contains the capture session management, `handle_raw_xy` processing, and button edge detection. Device state restoration is handled in [`crates/openlogi-device/src/session/capture_restore.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/session/capture_restore.rs).

### Can the gesture sensitivity or direction thresholds be customized?

While the provided source analysis focuses on the core detection pipeline, the `SwipeAccumulator` struct (referenced in `handle_raw_xy`) handles the normalization and threshold logic for determining when motion constitutes a directional gesture. Customization of sensitivity would involve modifying the accumulator's internal threshold constants or exposing them through the binding configuration system, which uses serde-serializable definitions in the core crate.