How to Configure Gesture Bindings on Any Button with Live Capture in OpenLogi

Gesture bindings in OpenLogi map directional movements of a held button to custom actions, configured via TOML files or captured live through the GUI's capture mode.

OpenLogi is an open-source input remapping system for Logitech devices. Its gesture binding feature lets any physical button act as a gesture pad—when held, directional movements trigger different commands. This article covers both manual TOML configuration and the live capture workflow, grounded in the actual source implementation.


Understanding Gesture Bindings in OpenLogi

A gesture binding consists of two components: a gesture-owner button (the physical button you hold down) and directional mappings (up, down, left, right, tap). When the owner button is pressed and held, the low-level hook subsystem tracks movement vectors and emits GestureDirection events. The core receives these events through IPC and resolves them to configured actions.

According to the OpenLogi source, gesture events originate in crates/openlogi-hook/src/lib.rs, travel through crates/openlogi-ipc/src/transport.rs, and are processed by crates/openlogi-core/src/binding/gesture.rs before finally being persisted via crates/openlogi-core/src/bindings.rs.


Manual Configuration via TOML

OpenLogi stores all bindings in a profile-based TOML file, typically located at ~/.config/openlogi/config.toml. The gesture table exists inside each profile's bindings section.

Gesture Table Structure

[bindings.my_profile.gesture]
button = "B5"           # The physical button that enters gesture mode

up = "volume_up"
down = "volume_down"
left = "prev_track"
right = "next_track"
tap = "play_pause"
  • button: Identifies which physical button activates gesture mode. This can be any button identifier recognized by the device—common values include "B5", "G4", or "G5" on gaming mice.
  • Direction keys (up, down, left, right, tap): Map to action names defined elsewhere in the configuration. These actions may invoke system commands, emit keystrokes, or trigger OpenLogi-internal functions.

Example: Media Control Gestures

[bindings.work_profile.gesture]
button = "G4"
up = "brightness_up"
down = "brightness_down"
left = "desktop_left"
right = "desktop_right"
tap = "mission_control"

[bindings.work_profile.actions]
brightness_up = { type = "keyseq", keys = ["XF86MonBrightnessUp"] }
brightness_down = { type = "keyseq", keys = ["XF86MonBrightnessDown"] }
desktop_left = { type = "keyseq", keys = ["Control", "Left"] }
desktop_right = { type = "keyseq", keys = ["Control", "Right"] }
mission_control = { type = "keyseq", keys = ["Command", "Up"] }

The configuration parser in crates/openlogi-core/src/config.rs deserializes this structure into a GestureMap, which the runtime queries when processing incoming GestureDirection events.


Live Capture Workflow in OpenLogi Desktop

The GUI application (openlogi-desktop) provides a live capture mode that eliminates manual TOML editing. This mode streams gesture events in real-time and writes bindings directly to the active profile.

How Live Capture Works

  1. Activation: Select a button in the UI and enter capture mode. The GUI signals the hook to begin emitting GestureDirection events for that specific button.

  2. Event Streaming: While the button is held, the hook in crates/openlogi-hook/src/lib.rs samples HID++ reports, calculates movement vectors, and translates them into discrete directions. These events travel through the IPC channel defined in crates/openlogi-ipc/src/transport.rs.

  3. UI Feedback: The GUI displays live directional indicators as the user moves their mouse or trackball. Each detected direction highlights in the interface.

  4. Binding Assignment: When the user clicks a direction in the UI and selects an action, the core constructs a GestureBinding struct (defined in crates/openlogi-core/src/binding/gesture.rs) and attaches it to the owner button.

  5. Persistence: On save, crates/openlogi-core/src/bindings.rs serializes the updated GestureMap back to the TOML file.

Capture Mode Code Flow

// Simplified representation of the GUI capture implementation
use openlogi_core::binding::gesture::{GestureBinding, GestureDirection};
use openlogi_core::profile::Profile;

fn run_capture_session(profile: &mut Profile, owner_button: String) {
    let mut capture = GestureCapture::new(owner_button.clone());
    capture.start();  // Signal hook to begin streaming events
    
    // Live preview loop
    while let Some(direction) = capture.next_event() {
        ui.highlight_direction(direction);  // Visual feedback
    }
    
    // User finalizes binding
    let binding = GestureBinding {
        up: ui.selected_action("up"),
        down: ui.selected_action("down"),
        left: ui.selected_action("left"),
        right: ui.selected_action("right"),
        tap: ui.selected_action("tap"),
    };
    
    profile.gesture_map.insert(owner_button, binding);
    profile.save();  // Delegates to bindings.rs for TOML serialization
}

The GestureCapture struct manages the lifecycle of a capture session, ensuring that events from other buttons are filtered and only the designated owner button generates gesture events.


Key Source Files and Their Roles

File Purpose GitHub Link
crates/openlogi-hook/src/lib.rs Low-level HID++ hook that emits GestureDirection events from raw device reports View source
crates/openlogi-ipc/src/transport.rs IPC channel (tarpc/bincode) carrying events between hook, GUI, and core View source
crates/openlogi-core/src/binding/gesture.rs GestureBinding definition and From<&GestureDirection> conversion logic View source
crates/openlogi-core/src/config.rs TOML deserialization for profiles, including the gesture table View source
crates/openlogi-core/src/bindings.rs Persistence layer that writes updated bindings to disk View source
docs/CONFIGURATION.md Official documentation of the TOML schema View docs

Triggering Bound Gestures Programmatically

The openlogi-inject crate allows synthetic gesture injection for testing or automation:

use openlogi_core::binding::gesture::GestureDirection;

fn trigger_bound_action(direction: GestureDirection) {
    // Injects a gesture as if it came from the physical device
    openlogi_inject::inject_gesture(direction);
}

// Fire the "right" binding on the current gesture-owner button
trigger_bound_action(GestureDirection::Right);

This capability is used by the test suite in crates/openlogi-core/tests/ to verify that gesture bindings resolve to correct actions without requiring physical hardware.


Summary


Frequently Asked Questions

What buttons can be used as gesture-owner buttons?

Any button recognized by your Logitech device can serve as a gesture-owner button. The button field in the TOML accepts standard identifiers like "B5" (thumb button), "G4", "G5" (G-series buttons), or device-specific codes. The hook subsystem in crates/openlogi-hook/src/lib.rs treats all buttons uniformly once configured.

Why aren't my gesture bindings triggering?

Three common causes: the hook is not running (required to emit GestureDirection events), the owner button is misidentified in the button field, or the action names are undefined in the profile. Verify that openlogi-hook has necessary permissions for low-level input access, and check that action references match keys in your [bindings.<profile>.actions] table.

How do I disable live capture without saving?

The live capture UI provides a Discard button that terminates the capture session without invoking profile.save(). This prevents crates/openlogi-core/src/bindings.rs from writing to the TOML file, leaving your configuration unchanged.

Can I bind multiple gesture-owner buttons in the same profile?

Yes. The gesture_map in crates/openlogi-core/src/binding/gesture.rs supports multiple entries—each distinct button value can have its own GestureBinding. This allows, for example, one button for media gestures and another for window management gestures within the same profile.

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 →