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

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. When a button operates in gesture mode, the variant is Binding::Gesture, which contains a map from each GestureDirection to a specific Action:

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:

[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, 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.
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, the handler queries the user configuration and extracts the specific action for the reported direction:

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:

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 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 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, which listens for CapturedInput::Gesture messages from the device session. It calls direction_action from 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →