# How OpenLogi Configures Native Scroll Inversion Per-Device (HID++ 0x2121)

> Discover how OpenLogi uses HID++ 0x2121 to configure native scroll inversion per-device. Learn about device-wide defaults and per-link overrides for seamless control.

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

---

**OpenLogi stores the native scroll-inversion flag in persistent device configuration and applies it via the HID++ 0x2121 HiResWheel feature, supporting both device-wide defaults and per-link overrides.**

The OpenLogi daemon manages Logitech HID++ peripherals through a layered configuration system. Native scroll inversion allows users to flip scroll direction at the hardware level rather than relying on OS settings. This article examines how the `invert_scroll` flag flows from configuration files through to the HID++ command sent to the device.

## Configuration Architecture

OpenLogi defines scroll inversion settings in [`crates/openlogi-core/src/config/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/device.rs) through two complementary mechanisms.

### Device-Wide Flag

The primary setting resides in the `DeviceConfig` struct as a boolean field:

```rust
/// Invert this device's scroll-wheel direction relative to the OS setting (issue #126)
#[serde(default, skip_serializing_if = "is_false")]
pub invert_scroll: bool,

```

This field defaults to `false` (native direction) and is omitted from [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) when unchanged to keep configuration files tidy.

### Per-Link Override

For advanced routing scenarios, the `LinkOverrides` struct provides route-specific control:

```rust
pub invert_scroll: Option<bool>,

```

Located in the same file, this allows different values for specific connection paths—such as distinguishing between a USB receiver and a direct USB cable.

### Effective Value Resolution

The runtime resolves the final boolean through the `effective_invert_scroll` method (lines 85-92):

```rust
pub fn effective_invert_scroll(&self, route_key: &str) -> bool {
    self.link_overrides(route_key)
        .and_then(|o| o.invert_scroll)
        .unwrap_or(self.invert_scroll)
}

```

Per-link values shadow the device-wide default when present.

## Runtime Application Flow

Changing the setting triggers a coordinated update across the desktop UI and background agent.

### Desktop UI Toggle

The interface implements inversion control in [`crates/openlogi-desktop/src/state/scroll.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/scroll.rs). When a user activates the toggle, the system executes:

1. **Validation**: Checks `current_scroll_inversion_supported` to verify the device exposes the HID++ feature.
2. **Persistence**: Calls `Config::set_invert_scroll` to update the device entry.
3. **Reload**: Invokes `persist_and_reload("invert scroll")` to signal the agent.

The relevant code (lines 30-49) handles the UI callback and commits the change only after confirming hardware compatibility.

### Agent HID++ Command

Upon reload, the agent invokes `openlogi_hid::set_scroll_inversion(route, invert).await` as defined in [`crates/openlogi-hid/src/host.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/host.rs) (lines 8-11). This facade method routes the command to the appropriate HID++ feature implementation.

## HID++ 0x2121 Implementation

The actual hardware toggle uses the **HiResWheel** feature (feature index `0x2121`) implemented in [`crates/openlogi-hidpp/src/feature/hires_wheel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/feature/hires_wheel.rs).

### Capability Detection

The feature parser examines capability flags from the device payload:

```rust
has_invert: payload[1] & (1 << 3) != 0,

```

This bit indicates whether the hardware supports native inversion.

### Inversion Bit Manipulation

When applying settings, the code toggles the inversion state via bit 2 of payload byte 0:

```rust
inverted: payload[0] & (1 << 2) != 0,

```

The agent sets or clears this bit to enable or disable native scroll inversion without OS-level manipulation.

## Configuration Persistence

Settings persist in [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) under device-specific tables. The system uses the device route key as the identifier:

```toml
[devices."direct:046d:c08d:unit:6be9d300"]
invert_scroll = true

```

As implemented in the test fixtures at [`crates/openlogi-core/src/config/tests.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/tests.rs) (lines 398-405), false values are omitted during serialization to maintain clean configuration files.

## Practical Configuration Examples

### Programmatic Device Setup

Configure inversion programmatically using the core configuration API:

```rust
use openlogi_core::config::Config;

let mut cfg = Config::default();
let device_key = "direct:046d:c08d:unit:6be9d300";

// Enable hardware scroll inversion
cfg.set_invert_scroll(device_key, true);

```

### Command Line Interface

Toggle inversion via the CLI utility:

```bash
openlogi-cli set invert-scroll <device-key> true

```

This invokes `Config::set_invert_scroll` internally and triggers the same reload sequence as the desktop UI.

### Advanced Per-Link Configuration

Override settings for specific connection routes:

```rust
use openlogi_core::config::Config;

let mut cfg = Config::default();
let device_key = "receiver:82839805:slot:1";

cfg.devices
   .entry(device_key.to_string())
   .or_default()
   .links
   .entry("receiver:82839805:slot:1".to_string())
   .or_default()
   .overrides
   .invert_scroll = Some(true);

```

This configuration inverts scroll only when connected through the specified receiver slot, leaving other routes unaffected.

## Summary

- **Configuration Layer**: The `invert_scroll` boolean in `DeviceConfig` provides device-wide defaults, while `LinkOverrides` enables route-specific exceptions.
- **Resolution Logic**: The `effective_invert_scroll` method prioritizes per-link settings over global device configuration.
- **UI Integration**: The desktop application validates hardware support through `current_scroll_inversion_supported` before persisting changes.
- **Hardware Interface**: The agent communicates via HID++ feature `0x2121` (HiResWheel), manipulating bit 2 of the feature payload to toggle inversion.
- **Storage Format**: Settings persist in TOML format under device tables, with false values elided to minimize configuration file size.

## Frequently Asked Questions

### What is HID++ feature 0x2121?

HID++ feature `0x2121` corresponds to the HiResWheel feature on modern Logitech devices. According to the OpenLogi source code in [`crates/openlogi-hidpp/src/feature/hires_wheel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/feature/hires_wheel.rs), this feature exposes native scroll-direction control through specific bits in the feature payload, allowing the driver to invert scroll direction at the hardware level before the OS processes input events.

### Can different connection methods have different scroll directions?

Yes. The `LinkOverrides` structure in [`crates/openlogi-core/src/config/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/device.rs) supports per-route configuration. By setting `invert_scroll` within a specific link entry, you can enable inversion for a USB receiver while maintaining standard direction for direct USB connections, or vice versa. The `effective_invert_scroll` function resolves these overrides at runtime.

### Where is the scroll inversion setting stored?

The configuration persists in the OpenLogi [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) file under device-specific table headers using the device route key (e.g., `[devices."direct:046d:c08d:unit:6be9d300"]`). The `invert_scroll` field appears only when set to `true`, as the serialization logic skips default false values to keep the configuration file concise.

### Does this work on all Logitech devices?

No. The setting requires hardware support for the HiResWheel feature. The desktop UI checks `current_scroll_inversion_supported` before presenting the toggle, which verifies the capability bit (bit 3 of payload byte 1) reported by the device. Devices lacking this feature bit cannot perform native scroll inversion and rely on OS-level scroll direction settings instead.