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

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. 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. This function queries whether the device implements the HiResWheel feature and inspects the has_invert bit within the wheel capabilities bitmask:

// 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. This accessor checks the cached capabilities of the active device:

// 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, 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:

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

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

// 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

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, 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. 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.

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 →