# How to Configure SmartShift Wheel Settings in OpenLogi: A Complete Technical Guide

> Master OpenLogi SmartShift wheel settings with this technical guide. Learn to configure the four-layer architecture and persist your changes in config.toml for seamless device operation.

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

---

**OpenLogi configures SmartShift wheel settings through a four-layer architecture spanning core configuration structs in [`settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/settings.rs), HID++ protocol mappings in [`smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/smartshift.rs), IPC messages in the `openlogi-ipc` crate, and the desktop UI panel, with all values persisted to [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) to survive device power cycles.**

OpenLogi is an open-source driver replacement for Logitech devices that exposes the SmartShift ratchet mechanism through a Rust-based configuration stack. Configuring SmartShift wheel settings requires understanding how the codebase bridges high-level user preferences with low-level HID++ protocol commands. This guide walks through the specific file paths and APIs that control wheel mode, auto-disengage thresholds, and torque settings.

## Core Configuration Structure

The foundation for SmartShift configuration lives in [`crates/openlogi-core/src/config/settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/settings.rs). This file defines the `SmartShift` struct (lines 713-721) that persists user preferences between sessions.

The configuration includes:

- **`WheelMode` enum**: Defines `Free` and `Ratchet` states for the wheel mechanism
- **Auto-disengage thresholds**: Validated against `SMARTSHIFT_MIN_AUTO_DISENGAGE` and `SMARTSHIFT_AUTO_DISENGAGE_DEFAULT` constants
- **Structural validation**: The struct enforces type safety for all wheel parameters via Rust's type system

## HID++ Protocol Mapping

The translation between Rust structs and hardware bytes happens in [`crates/openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/smartshift.rs). This module handles the wire format for Logitech's HID++ protocol.

Key components include:

- **`SmartShiftMode` enum** (lines 33-39): Represents hardware values for ratchet states with a `flipped()` helper method to toggle between modes
- **`SmartShiftThreshold`**: Wraps raw threshold values with `from_rounded()` for UI conversions and `try_new()` for validation
- **`SmartShiftAutoDisengage`**: Enum distinguishing between threshold-based and permanent ratchet modes

Low-level device writes are implemented in [`crates/openlogi-device/src/write/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/smartshift.rs), which formats these structures into HID++ packets.

## Agent-GUI Communication Layer

The `openlogi-ipc` crate defines the contract between the desktop GUI and background agent. The relevant RPC endpoints are:

- **`set_smartshift`** (lines 465-472 in [`ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ipc.rs)): Transmits `SmartShiftStatus` from UI to agent
- **`read_smartshift`**: Queries current hardware state for UI initialization

These messages carry the same structures defined in the core crate, ensuring type safety across process boundaries.

## Desktop UI Implementation

The user-facing controls reside in [`crates/openlogi-desktop/src/features/pointer/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/pointer/smartshift.rs). This panel renders three primary controls that map directly to the configuration structs.

### Wheel Mode Selection

Segmented buttons toggle between `SmartShiftMode::Free` and `SmartShiftMode::Ratchet`, immediately updating the device state via `AppState::update_smartshift`.

### Auto-Disengage Threshold

A slider control (handled in the `threshold_sub` subscription, lines 90-108) writes `SmartShiftAutoDisengage::Threshold(value)` when the user releases the slider. The handler clamps values using `THRESHOLD_MIN` and `THRESHOLD_MAX` constants before transmission.

### Permanent Ratchet Toggle

When enabled, this writes `SmartShiftAutoDisengage::Permanent` to the hardware, bypassing threshold-based switching entirely.

## Persisting SmartShift Settings

SmartShift settings survive power cycles through [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) persistence. The `AppState::commit_smartshift` method serializes current values to disk, while the agent reapplies these settings on device reconnection.

This persistence is critical because Logitech devices store SmartShift configuration in volatile RAM. As noted in the source comments (lines 8-13 of the UI file), without OpenLogi's persistence layer, users would lose their wheel settings every time the mouse powers down.

## Programmatic Configuration Examples

Configure SmartShift to Ratchet mode with a specific threshold via the core types:

```rust
use openlogi_core::hid::{
    SmartShiftAutoDisengage, SmartShiftMode, SmartShiftStatus, SmartShiftThreshold,
};

let new_status = SmartShiftStatus {
    mode: SmartShiftMode::Ratchet,
    auto_disengage: SmartShiftAutoDisengage::Threshold(
        SmartShiftThreshold::try_new(20).unwrap()
    ),
    tunable_torque: None,
};

// Transmit via IPC to agent
openlogi_ipc::set_smartshift(device_route, new_status).await?;

```

The UI implements threshold changes using subscription handlers that validate ranges:

```rust
let threshold_sub = cx.subscribe(&threshold, |panel, _slider, event, cx| {
    match event {
        SliderEvent::Release(value) => {
            let thresh = SmartShiftThreshold::from_rounded(value.start())
                .clamp(THRESHOLD_MIN, THRESHOLD_MAX);
            
            AppState::update_smartshift(
                cx,
                SmartShiftStatus {
                    mode: SmartShiftMode::Ratchet,
                    auto_disengage: SmartShiftAutoDisengage::Threshold(thresh),
                    ..status
                },
            );
        }
        _ => {}
    }
});

```

## Summary

- OpenLogi persists SmartShift wheel settings in [`crates/openlogi-core/src/config/settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/settings.rs) using the `SmartShift` struct and `WheelMode` enum
- HID++ protocol translation occurs in [`crates/openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/smartshift.rs) via `SmartShiftMode` and `SmartShiftThreshold` types
- The `openlogi-ipc` crate provides `set_smartshift` and `read_smartshift` RPC methods for agent communication
- The desktop UI in [`smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/smartshift.rs) manages wheel mode, auto-disengage thresholds, and permanent ratchet states through `AppState::update_smartshift`
- Settings persist to [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) because hardware stores values only in volatile RAM
- CLI diagnostics are available in [`crates/openlogi-cli/src/cmd/diag/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-cli/src/cmd/diag/smartshift.rs) for troubleshooting

## Frequently Asked Questions

### Where does OpenLogi store SmartShift wheel settings?

OpenLogi stores SmartShift configuration in a local [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) file rather than depending on the device's volatile memory. According to the source comments in [`crates/openlogi-desktop/src/features/pointer/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/pointer/smartshift.rs) (lines 8-13), the hardware only keeps these settings in RAM, so OpenLogi's persistence layer ensures your ratchet preferences survive power cycles and reconnections.

### What is the valid range for SmartShift auto-disengage thresholds?

The auto-disengage threshold range is defined by `SMARTSHIFT_MIN_AUTO_DISENGAGE` and `SMARTSHIFT_AUTO_DISENGAGE_DEFAULT` constants in [`settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/settings.rs). When setting values programmatically, use `SmartShiftThreshold::try_new()` for validation or `from_rounded()` with clamping to ensure the value falls within hardware-supported limits.

### How do I toggle between Free spin and Ratchet modes programmatically?

Use the `SmartShiftMode` enum defined at lines 33-39 in [`crates/openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/smartshift.rs), which provides `Free` and `Ratchet` variants. The enum includes a `flipped()` helper method to toggle between states. Construct a `SmartShiftStatus` with your chosen mode and pass it to `openlogi_ipc::set_smartshift()` to update the device.

### What happens when I adjust the threshold slider in the OpenLogi desktop app?

The UI subscribes to slider release events via `threshold_sub` (lines 90-108 in [`smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/smartshift.rs)). Upon release, it converts the raw value using `SmartShiftThreshold::from_rounded()`, clamps it to valid bounds, and calls `AppState::update_smartshift()` to send the new configuration through the IPC layer to the agent, which writes it to both the hardware and the persistent config file.