# How Per-Direction Gesture Bindings with Live Capture Attach to Physical Buttons in OpenLogi

> Discover how OpenLogi maps per-direction gestures to physical buttons using live capture and directional action maps. Learn the technical details of this innovative input system.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: deep-dive
- Published: 2026-08-23

---

**OpenLogi attaches per-direction gesture bindings to physical buttons by storing directional action maps in a `Binding::Gesture` enum variant, capturing raw XY motion through a diverted HID++ channel, and dispatching the mapped action via `direction_action` when a swipe completes.**

OpenLogi enables advanced input customization by allowing physical buttons to trigger different actions based on swipe direction. This article explains how per-direction gesture bindings with live capture attach to physical buttons according to the AprilNEA/OpenLogi source code, covering the configuration format, capture session mechanics, and action dispatch pipeline.

## The Binding Enum and Gesture Direction Maps

OpenLogi stores a button’s behavior in the `Binding` enum defined in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs). When a button operates in gesture mode, the variant is `Binding::Gesture`, which contains a map from each `GestureDirection` to a specific `Action`:

```rust
pub enum Binding {
    Single(Action),                         // ordinary button
    Gesture(BTreeMap<GestureDirection, Action>) // per‑direction gestures
}

```

The `GestureDirection` type supports five discrete values: `Up`, `Down`, `Left`, `Right`, and `Click`. This structure allows a single physical button to host up to five distinct actions depending on how the user interacts with it.

## Configuring Per-Direction Gestures in TOML

Users define gesture mappings in the TOML configuration file using a dedicated table syntax. The configuration assigns specific actions to each directional key:

```toml
[bindings."GestureButton"]
Gesture = { Up = "MissionControl", Down = "ShowDesktop", Click = "Back" }

```

This definition creates a `Binding::Gesture` instance where swiping up triggers Mission Control, swiping down shows the desktop, and clicking the button performs a back navigation.

## Live Capture Session and Raw XY Processing

The **live-capture** subsystem handles the physical input translation. Located in [`crates/openlogi-device/src/session/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/session/gesture.rs), the `run_capture_session` function manages the entire gesture detection pipeline:

1. **Diversion setup**: The session arms controls that require raw XY reporting via `CaptureSpec::divert_gesture_sources`.
2. **Motion accumulation**: Raw XY reports feed into a `SwipeAccumulator` that tracks motion vectors.
3. **Event emission**: When motion ceases or a tap registers, the system emits `CapturedInput::Gesture(ButtonId, GestureDirection)` through an async channel.

```rust
if let Some(direction) = swipe.finish() {
    let _ = sink.send(CapturedInput::Gesture(button, direction));
}

```

This implementation opens a single HID++ channel for the device and diverts gesture-capable controls away from standard button reporting, enabling high-resolution motion tracking.

## Dispatching Actions from Capture Events

The desktop process receives `CapturedInput` values via an IPC pipe and resolves the appropriate action. In [`crates/openlogi-desktop/src/state.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state.rs), the handler queries the user configuration and extracts the specific action for the reported direction:

```rust
pub fn direction_action(cfg: &Config, button: ButtonId, dir: GestureDirection) -> Option<Action> {
    cfg.get_binding(button).and_then(|b| b.direction_action(dir))
}

```

The pipeline flows from raw hardware input to executed action: the capture session emits a direction, the configuration layer looks up the binding, and the desktop executes the mapped command through the action-ring system.

## Runtime Binding Management

The core API supports programmatic conversion of standard bindings to gesture bindings. The `upgrade_to_gesture` method converts a `Binding::Single` into a `Binding::Gesture`, preserving the original action as the `Click` direction:

```rust
use openlogi_core::binding::{Binding, Action};

let mut bind = Binding::Single(Action::BrowserBack);
bind.upgrade_to_gesture(); // Converts to Gesture with Click = BrowserBack
bind.fill_gesture_defaults(); // Populates remaining directions with sensible defaults

```

This allows applications to dynamically enable gesture support without requiring manual configuration file edits.

## Summary

- OpenLogi uses the `Binding::Gesture` enum variant to store per-direction action maps for physical buttons.
- The live capture session in `run_capture_session` diverts HID++ controls and accumulates raw XY motion into directional gestures.
- When a swipe completes, the system emits `CapturedInput::Gesture` containing the button ID and detected direction.
- The desktop process resolves the final action through `direction_action`, which queries the configuration's gesture map.
- Runtime APIs like `upgrade_to_gesture` enable dynamic conversion between single-action and gesture bindings.

## Frequently Asked Questions

### What GestureDirection values does OpenLogi support?

OpenLogi recognizes five discrete gesture directions: `Up`, `Down`, `Left`, `Right`, and `Click`. The `Click` variant represents a physical press without significant motion, allowing the same button to handle both tap and swipe interactions.

### How does the live capture session detect when a gesture finishes?

The capture logic in [`crates/openlogi-device/src/session/gesture.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/session/gesture.rs) uses a `SwipeAccumulator` to track motion reports. When the accumulator's `finish` method returns `Some(direction)`, indicating motion has ceased or a tap pattern matched, the session emits the `CapturedInput::Gesture` event through the async sink channel.

### Can I convert a single-action binding to a gesture binding programmatically?

Yes. The `Binding` type provides `upgrade_to_gesture` in [`crates/openlogi-core/src/binding/value.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/value.rs) to convert `Binding::Single` into `Binding::Gesture`, automatically assigning the original action to the `Click` direction. The `fill_gesture_defaults` method then populates remaining directions with fallback actions.

### Where does the desktop process handle captured gesture events?

The desktop process receives events in [`crates/openlogi-desktop/src/state.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state.rs), which listens for `CapturedInput::Gesture` messages from the device session. It calls `direction_action` from [`crates/openlogi-core/src/bindings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/bindings.rs) to resolve the specific action for the reported button and direction, then dispatches the result through the action-ring system.