Understanding the Button Remapping Event Lifecycle in OpenLogi

OpenLogi processes button remapping through an eight-stage pipeline that captures raw OS mouse events, validates Logitech device eligibility, transmits instructions via IPC to an agent process, resolves user-defined TOML configurations, and injects synthetic events while suppressing the original input to prevent feedback loops.

OpenLogi is a Rust-based input remapping system designed specifically for Logitech peripherals. Understanding the button remapping event lifecycle in OpenLogi requires tracing how raw hardware signals transform into remapped actions across platform-specific hooks, inter-process communication bridges, and injection subsystems. The entire flow ensures that only supported devices trigger remaps while maintaining zero-latency response across Windows, macOS, and Linux.

Stage 1: OS Hook Captures Raw Input Events

The pipeline initiates when the operating system emits a raw mouse-button event. The OS hook layer translates platform-specific event codes into OpenLogi’s internal MouseEvent::Button abstraction with a corresponding ButtonId (e.g., MiddleClick, Back, or Forward).

Windows Implementation

On Windows, the hook monitors WM_MBUTTONDOWN and related window messages. The translation logic resides in crates/openlogi-hook/src/windows/hook.rs:

// Lines 449-454 handle the conversion from Windows messages to ButtonId
match msg {
    WM_MBUTTONDOWN => Some(ButtonId::MiddleClick),
    WM_XBUTTONDOWN => parse_xbutton(wparam), // Back/Forward
    // ...
}

macOS Implementation

macOS uses CGEventType constants from the Core Graphics framework. The mapping occurs in crates/openlogi-hook/src/macos.rs:

// Lines 324-334 translate CGEventType to internal ButtonId
match event_type {
    CGEventType::LeftMouseDown => ButtonId::Primary,
    CGEventType::OtherMouseDown => map_other_button(event), // Middle, Back, Forward
    // ...
}

Linux Implementation

Linux systems utilizing evdev map kernel input codes in crates/openlogi-hook/src/linux.rs:

// Lines 422-430 convert evdev BTN_* codes to ButtonId
match key {
    BTN_MIDDLE => Some(ButtonId::MiddleClick),
    BTN_SIDE => Some(ButtonId::Back),
    BTN_EXTRA => Some(ButtonId::Forward),
    // ...
}

Stage 2: Remappable Device Validation

Before forwarding any event, the hook verifies device eligibility via the source_is_remappable function in crates/openlogi-hook/src/lib.rs (lines 96-99). This gate returns true only for Logitech mice, explicitly excluding trackpads and non-Logitech hardware to prevent accidental remapping of system input devices.

pub fn source_is_remappable(device_info: &DeviceInfo) -> bool {
    device_info.vendor_id == LOGITECH_VENDOR_ID && !device_info.is_trackpad
}

Stage 3: IPC Transmission to the Agent

Once validated as remappable, the MouseEvent::Button traverses the IPC bridge provided by the openlogi-ipc crate. The system utilizes a local-socket channel with tarpc and bincode serialization to transfer events from the privileged hook process to the user-space agent. The exact contract is documented in crates/openlogi-ipc/AGENTS.md.

Stage 4: Configuration Lookup and Remap Resolution

The agent loads per-device TOML profiles and resolves the ButtonId against user configurations stored in crates/openlogi-core/src/binding/button.rs (lines 119-123). Each button binding contains a remaps field specifying the target action:

pub struct ButtonBinding {
    /// remaps: Middle, Back, or Forward. The primary L/R clicks always pass
    /// through unmodified to maintain OS compatibility.
    pub remaps: RemapConfig,
}

Users define these relationships in their configuration files:

[device."Logitech G502"]
button = { middle = "dpi_toggle", back = "cmd+left", forward = "cmd+right" }

Stage 5: Action Execution and Decision Logic

Within crates/openlogi-agent/src/lifecycle.rs (line 452), the agent determines whether the remap target is an internal action (such as DPI toggle) or a translated button event. Internal actions execute immediately within the agent process. Button-to-button remaps proceed to the injection subsystem.

// Agent lifecycle processing
match resolved_action {
    Action::DpiToggle => self.toggle_dpi(),
    Action::Button(target) => inject::mouse_button(target), // Proceed to Stage 6
}

Stage 6: Synthetic Event Injection

The openlogi-inject crate generates synthetic mouse events using platform-specific injection APIs. Located in crates/openlogi-inject/src/inject.rs, this subsystem creates new hardware events while ensuring the original event is suppressed. Lines 451-452 implement critical re-entry protection markers:

// Mark the injection to prevent the hook from re-processing this event
let synthetic_event = create_synthetic_event(target_button);
inject_event(synthetic_event); // Platform-specific implementation

Stage 7: Loop Prevention and Re-entry Protection

To prevent infinite remapping loops, the hook recognizes events generated by OpenLogi itself. In crates/openlogi-hook/src/macos.rs (line 379), a comment documents this safeguard: the hook checks event flags to identify synthesized events and returns None to halt further processing:

// Skip events OpenLogi itself synthesised, so a remapped click or inverted
// scroll does not trigger another remap cycle
if event_is_synthesized(event) {
    return None;
}

Stage 8: OS Processes the Remapped Event

Finally, the operating system receives the injected synthetic event as if the user had physically pressed the target button. The original event remains suppressed throughout the pipeline, ensuring the user experiences only the remapped behavior.

Configuration Example: Defining Button Remaps

OpenLogi uses TOML configuration files to define device-specific button mappings. The ButtonBinding struct in openlogi-core parses these definitions at runtime:

/// Example: Mapping the middle button to a system command
pub fn load_binding(&self, button: ButtonId) -> Option<&RemapConfig> {
    match button {
        ButtonId::MiddleClick => Some(&self.remaps.middle),
        ButtonId::Back => Some(&self.remaps.back),
        ButtonId::Forward => Some(&self.remaps.forward),
        _ => None,
    }
}

Summary

  • Platform abstraction: OpenLogi normalizes Windows (WM_*), macOS (CGEventType), and Linux (evdev) events into a unified MouseEvent::Button type.
  • Device gating: The source_is_remappable function in openlogi-hook/src/lib.rs restricts remapping to Logitech mice only.
  • IPC architecture: Events traverse a tarpc/bincode channel via openlogi-ipc to reach the user-space agent.
  • Configuration-driven: Remaps resolve against TOML profiles in openlogi-core/src/binding/button.rs.
  • Safe injection: The openlogi-inject crate generates synthetic events while the hook suppresses originals and prevents re-entry loops.

Frequently Asked Questions

What devices are supported for button remapping in OpenLogi?

Only Logitech mice are eligible for button remapping. The source_is_remappable function in crates/openlogi-hook/src/lib.rs explicitly checks the USB vendor ID against Logitech’s identifier and excludes trackpads and non-Logitech peripherals from the remapping pipeline.

How does OpenLogi prevent infinite remapping loops?

The hook implements re-entry protection by detecting synthetic events it previously injected. According to the implementation in crates/openlogi-hook/src/macos.rs, the system checks event flags to identify OpenLogi-generated events and immediately returns None to prevent processing loops.

What IPC mechanism does OpenLogi use between the hook and agent?

OpenLogi utilizes a local-socket based IPC channel with tarpc for RPC semantics and bincode for binary serialization, as defined in crates/openlogi-ipc/AGENTS.md. This architecture separates the privileged OS hook from the user-space agent process while maintaining low-latency communication.

Can OpenLogi remap mouse buttons to keyboard shortcuts?

Yes. The remaps field in ButtonBinding supports complex actions beyond simple button-to-button mapping. User configurations can specify keyboard combinations (e.g., cmd+left) or system commands in the TOML profile, which the agent resolves and executes during the action execution stage.

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 →