# How to Configure SmartShift with OpenLogi for Logitech Devices: A Complete Guide

> Learn how to configure SmartShift with OpenLogi for your Logitech devices. This complete guide explains the process using TOML configuration and Rust structs for seamless control.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-08-25

---

**OpenLogi configures Logitech SmartShift (free-spin/ratchet wheel modes) through a three-layer architecture that maps TOML configuration values to HID++ protocol commands via Rust structs in [`openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/hid/smartshift.rs) and IPC RPC calls.**

OpenLogi is an open-source user-space driver for Logitech HID++ devices that exposes the **SmartShift** feature—Logitech's proprietary wheel-ratchet toggle mechanism—through a clean configuration interface. Understanding how to configure SmartShift with OpenLogi for Logitech devices requires familiarity with its layered architecture spanning the HID++ driver, core abstraction module, and IPC transport layer.

## Understanding the SmartShift Architecture in OpenLogi

SmartShift configuration flows through three distinct layers that bridge raw hardware protocols with user-facing controls:

- **HID++ Driver Layer** (`openlogi-hidpp`): Handles raw byte transmission for SmartShift features `0x2110` and `0x2111`
- **Core Abstraction Layer** ([`openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/hid/smartshift.rs)): Defines `SmartShiftMode`, `SmartShiftAutoDisengage`, and `SmartShiftStatus` enums with conversion logic between persisted config and wire format
- **IPC/CLI Layer** ([`openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/src/ipc.rs)): Exposes async RPC functions `set_smartshift` and `read_smartshift` that forward configuration changes to the agent

### The Configuration Data Flow

When OpenLogi initializes, the agent reads `~/.config/openlogi/config.toml`, converts `WheelMode` values into the `SmartShiftMode` enum through `SmartShiftMode::from(crate::config::WheelMode)`, and writes the status to the device. Changes triggered via CLI or GUI invoke the IPC `set_smartshift` function, which ultimately calls the low-level HID++ write path `write::smartshift::set_smartshift`.

## Configuring SmartShift via TOML

The persistent configuration resides in `~/.config/openlogi/config.toml` under the `[smart_shift]` section:

```toml
[smart_shift]

# Mode can be "free" (free-spin) or "ratchet" (clicky)

mode = "ratchet"

# Auto-disengage threshold (0.25 turn/s steps). 255 = permanent ratchet.

auto_disengage = 30   # 30 × 0.25 turn/s = 7.5 turn/s

# Optional tunable-torque value (only on devices that support it)

tunable_torque = 2

```

The `mode` field accepts `"free"` for continuous scrolling or `"ratchet"` for stepped clicking. The `auto_disengage` value represents threshold steps where each step equals 0.25 turns per second; setting this to `255` locks the wheel in permanent ratchet mode regardless of spin speed.

## Three Methods to Configure SmartShift

Depending on your integration needs, OpenLogi exposes SmartShift controls through CLI commands, programmatic Rust APIs, or direct configuration file editing.

### Using the OpenLogi CLI

The `openlogi-cli` binary provides subcommands that serialize requests and transmit them via IPC:

```bash

# Set SmartShift to ratchet mode with a 30-step threshold (7.5 turn/s)

openlogi-cli set smartshift --mode ratchet --threshold 30

# Query the current SmartShift status

openlogi-cli get smartshift

```

These commands update the TOML file automatically after successfully writing to the device hardware.

### Using the Rust Library

For embedded applications or custom tooling, import the core HID types and device write functions:

```rust
use openlogi_core::hid::smartshift::{SmartShiftMode, SmartShiftThreshold};
use openlogi_device::write::{set_smartshift, get_smartshift_status};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Assume `device` is a previously opened HID++ device.
    let status = get_smartshift_status(&device).await?;
    println!("Current mode: {:?}, threshold: {:?}", status.mode, status.auto_disengage);

    // Switch to free-spin with a 20-step threshold.
    let new_status = openlogi_core::hid::smartshift::SmartShiftStatus {
        mode: SmartShiftMode::Free,
        auto_disengage: openlogi_core::hid::smartshift::SmartShiftAutoDisengage::Threshold(
            SmartShiftThreshold::try_new(20)?,
        ),
        tunable_torque: status.tunable_torque,
    };
    set_smartshift(&device, new_status).await?;
    Ok(())
}

```

This approach uses `SmartShiftStatus` structs defined in [`crates/openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/smartshift.rs) and write helpers in [`crates/openlogi-device/src/write/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/smartshift.rs).

### Direct Configuration File Editing

Modify `~/.config/openlogi/config.toml` directly and restart the OpenLogi agent:

```toml
[smart_shift]
mode = "free"
auto_disengage = 20

# tunable_torque omitted → will retain current value

```

The agent detects configuration changes on startup and applies them through the same conversion logic used by the IPC layer.

## The SmartShift Configuration Lifecycle

OpenLogi manages SmartShift state through a five-phase pipeline:

1. **Read**: `openlogi-hidpp` queries feature `0x2110`, and `SmartShift::status()` returns a `SmartShiftStatus` struct
2. **Convert**: The raw status transforms into the core `SmartShiftStatus` type via `impl From<crate::config::SmartShift>`
3. **Expose**: The IPC layer makes the status available through `read_smartshift` for GUI consumption
4. **Modify**: CLI or GUI constructs a new `SmartShiftStatus` with desired `mode` and `auto_disengage` values
5. **Write**: `write::smartshift::set_smartshift` serializes the struct back to HID++ wire bytes using `SmartShiftMode::into()` and `SmartShiftAutoDisengage::into()`, then transmits to the device

## Protocol Version Safety

Because the IPC serializes `SmartShiftMode` enums by their numeric index (`Free = 0`, `Ratchet = 1`), OpenLogi implements safeguards in [`openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/tests/wire_format.rs). Any change to the enum order requires a `PROTOCOL_VERSION` bump to prevent deserialization errors between agent and client components.

## Summary

- Configure SmartShift with OpenLogi by editing `~/.config/openlogi/config.toml` or using `openlogi-cli set smartshift` commands
- Configuration flows through [`openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/hid/smartshift.rs) where `SmartShiftMode` and `SmartShiftStatus` types bridge TOML values and HID++ protocol bytes
- The `auto_disengage` threshold uses 0.25 turn/s steps; set to `255` for permanent ratchet mode
- Programmatic control requires importing `openlogi_device::write::set_smartshift` and constructing `SmartShiftStatus` structs
- Wire format tests in [`openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/tests/wire_format.rs) ensure binary compatibility across protocol versions

## Frequently Asked Questions

### Which Logitech devices support SmartShift configuration through OpenLogi?

OpenLogi supports any Logitech device implementing HID++ features `0x2110` and `0x2111`, typically found in MX Master series mice and similar high-end input devices. Check [`openlogi-device/src/write/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-device/src/write/smartshift.rs) for the specific feature detection logic that validates hardware compatibility before attempting configuration.

### What values should I use for the auto_disengage threshold?

The `auto_disengage` field accepts values `0` to `255`, where each unit represents 0.25 turns per second. For example, `30` equals 7.5 turns per second. Setting this to `255` disables automatic mode switching and forces permanent ratchet mode, while lower values trigger free-spin earlier during fast scrolling.

### Does changing SmartShift mode require restarting the OpenLogi agent?

Changes made through `openlogi-cli` or the GUI apply immediately via IPC calls to `set_smartshift`. However, direct edits to `~/.config/openlogi/config.toml` require an agent restart to take effect, as the configuration is read at startup time in `openlogi-core` initialization routines.

### Why does the IPC layer serialize SmartShiftMode as an integer index?

The IPC uses numeric indices (`Free = 0`, `Ratchet = 1`) for `SmartShiftMode` to minimize wire format size and parsing complexity. This design requires strict `PROTOCOL_VERSION` management in [`openlogi-ipc/tests/wire_format.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/tests/wire_format.rs)—altering the enum definition without bumping the protocol version breaks compatibility between the agent and CLI components.