# Understanding the Button Remapping Event Lifecycle in OpenLogi

> Explore the eight-stage button remapping event lifecycle in OpenLogi. Learn how raw OS events are processed, validated, and transformed into synthetic inputs for a seamless user experience.

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

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/windows/hook.rs):

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/macos.rs):

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hook/src/linux.rs):

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/button.rs) (lines 119-123). Each button binding contains a `remaps` field specifying the target action:

```rust
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:

```toml
[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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
// 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:

```rust
/// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.