# Scroll Inversion Mechanism for HID++ Devices in OpenLogi: A Deep Dive into HiResWheel

> Learn how OpenLogi enables scroll inversion for HID++ devices by leveraging the HiResWheel feature. Discover how the `has_invert` bit and native HID++ registers facilitate this functionality.

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

---

**OpenLogi implements scroll inversion for HID++ devices by detecting the `has_invert` bit of the HiResWheel feature, caching this capability, and exposing a toggle that writes the inverted state directly to the device's native HID++ register.**

OpenLogi is an open-source device management framework for Logitech peripherals built in Rust. The scroll inversion mechanism for supported HID++ devices leverages the native `0x2121 HiResWheel` feature to detect and control scroll direction at the hardware level, eliminating the need for OS-level workarounds.

## Capability Discovery: Probing the HiResWheel Feature

During device enumeration, OpenLogi constructs a `Capabilities` data transfer object defined in [`crates/openlogi-core/src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/device.rs). The `scroll_inversion` boolean field initializes to `false` by default.

The system probes for native support through the `probe_extra_capabilities` routine in [`crates/openlogi-device/src/inventory/features.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory/features.rs). This function queries whether the device implements the HiResWheel feature and inspects the `has_invert` bit within the wheel capabilities bitmask:

```rust
// crates/openlogi-device/src/inventory/features.rs
if let Some(feature) = device.get_feature::<HiResWheelFeature>() {
    caps.scroll_inversion = feature
        .get_wheel_capabilities()
        .await
        .is_ok_and(|wheel| wheel.has_invert);
}

```

When `has_invert` is present, OpenLogi sets `caps.scroll_inversion = true`, caching this value for subsequent IPC calls to the desktop client.

## Runtime Capability Query

The desktop client determines UI visibility through the `State::current_scroll_inversion_supported` method located in [`crates/openlogi-desktop/src/state/scroll.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/scroll.rs). This accessor checks the cached capabilities of the active device:

```rust
// crates/openlogi-desktop/src/state/scroll.rs
pub fn current_scroll_inversion_supported(&self) -> bool {
    self.active_capabilities()
        .is_some_and(|capabilities| capabilities.scroll_inversion)
}

```

If the method returns `true`, the interface renders the "Scroll inversion" toggle as enabled; otherwise, the control remains disabled to prevent unsupported operations.

## Writing the Scroll Inversion State

When a user toggles the setting, OpenLogi commits the change via the HID++ protocol. The core write logic resides in [`crates/openlogi-device/src/write/hires_wheel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/hires_wheel.rs), which provides two entry points:

- **`set_scroll_inversion`** – Opens a new route to the device and forwards the inversion request.
- **`set_scroll_inversion_on`** – Operates on an existing `SharedChannel` for reduced latency during high-frequency updates.

Both functions invoke `change_wheel_mode_on_channel`, which validates that the device previously advertised `has_invert` support. If validation fails, the function returns `WriteError::FeatureUnsupported` immediately:

```rust
// crates/openlogi-device/src/write/hires_wheel.rs
pub async fn set_scroll_inversion(
    backend: &dyn HidBackend,
    route: &DeviceRoute,
    inverted: bool,
) -> Result<(), WriteError> {
    let index = route.device_index();
    with_route(backend, route, move |channel| async move {
        change_wheel_mode_on_channel(&channel, index, None, Some(inverted), true)
            .await
            .map(|_| ())
    })
    .await
}

```

The final parameter (`require_invert_support`) ensures that writes only occur on compatible hardware, preventing silent failures on legacy devices.

## Practical Implementation Examples

To query whether the active device supports native scroll inversion:

```rust
// Example: Query whether the active device supports native scroll inversion
let scroll_state = desktop_state.scroll(); // `desktop_state` from openlogi-desktop
if scroll_state.current_scroll_inversion_supported() {
    println!("Device can invert scroll direction natively.");
}

```

To programmatically invert scroll direction:

```rust
// Example: Invert scroll direction for the active device
let route = /* obtain DeviceRoute from the agent */;
openlogi_hid::set_scroll_inversion(&*native_backend(), &route, true).await?;

```

## Summary

- **OpenLogi detects scroll inversion capability** by probing the `has_invert` bit of the `0x2121 HiResWheel` feature during device enumeration in [`crates/openlogi-device/src/inventory/features.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory/features.rs).
- **Capability state propagates** through the `Capabilities.scroll_inversion` field and surfaces to the UI via `current_scroll_inversion_supported` in [`crates/openlogi-desktop/src/state/scroll.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/scroll.rs).
- **Hardware-level writes** execute through `set_scroll_inversion` in [`crates/openlogi-device/src/write/hires_wheel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/hires_wheel.rs), which validates support before encoding the `ScrollWheelMode` command.
- **Error handling** returns `WriteError::FeatureUnsupported` when attempting to write inversion settings to devices lacking native support.

## Frequently Asked Questions

### How does OpenLogi determine if a device supports scroll inversion?

OpenLogi queries the device's feature table for the `HiResWheelFeature` during the enumeration phase. In [`crates/openlogi-device/src/inventory/features.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory/features.rs), the `probe_extra_capabilities` function calls `get_wheel_capabilities()` and checks the `has_invert` boolean field. If present, OpenLogi caches this capability in the `Capabilities.scroll_inversion` flag.

### What error occurs when toggling scroll inversion on unsupported hardware?

The system returns `WriteError::FeatureUnsupported` from `change_wheel_mode_on_channel` in [`crates/openlogi-device/src/write/hires_wheel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/hires_wheel.rs). This validation occurs because the write functions pass `require_invert_support = true`, enforcing strict capability checks before transmitting the HID++ command.

### Does OpenLogi store the scroll inversion state persistently?

No. OpenLogi writes the inversion flag directly to the device's native HID++ register via the HiResWheel feature. The state persists on the hardware itself rather than in local configuration files, ensuring the setting remains active across different host machines.

### Can scroll inversion be changed while the device is actively connected?

Yes. OpenLogi supports dynamic updates through `set_scroll_inversion_on`, which accepts an existing `SharedChannel` to avoid the overhead of route establishment. This allows real-time toggling without disconnecting or re-enumerating the HID++ device.