# OpenLogi DPI Preset Cycling and Set-Preset Implementation Strategy

> Discover the OpenLogi DPI preset cycling and set-preset strategy. Learn how config.toml, runtime maps, and discrete actions manage DPI settings via IPC commands to the device agent.

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

---

**OpenLogi implements DPI preset cycling through a persistent [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) store, a per-device runtime cycle index map, and discrete actions (`CycleDpiPresets` and `SetDpiPreset`) that dispatch `SetDpi` IPC commands to the device agent.**

OpenLogi is an open-source mouse configuration platform written in Rust that manages DPI preset workflows through a layered architecture separating desktop UI state management from low-level agent runtime execution. The implementation strategy persists preset configurations per-device in the user's local configuration while maintaining an in-memory `dpi_cycle` index to track the current preset position during hardware-triggered cycling operations.

## DPI Preset Storage and Persistence Strategy

OpenLogi persists DPI presets per-device in the user's [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) file. When users create, remove, or reorder presets through the desktop interface, the application invokes `AppState::commit_dpi_presets` in [`crates/openlogi-desktop/src/state/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/dpi.rs).

This method accepts a `Vec<Dpi>` representing the ordered preset list and performs a transactional update. It writes the new preset vector into the configuration via `config.set_dpi_presets`, passing the persistent device key and the preset collection. The operation concludes with `persist_and_reload("DPI presets")`, which commits the change to disk and updates an internal hook map to ensure subsequent cycling operations observe the modified preset order.

```rust
pub fn commit_dpi_presets(&mut self, presets: Vec<Dpi>) {
    // … get the persistent device key …
    self.config.edit(|config| config.set_dpi_presets(&key, presets));
    self.persist_and_reload("DPI presets");   // push to the hook map
}

```

To add a new preset snapshot via the UI's "+" chip:

```rust
let mut presets = state.dpi_presets();
presets.push(state.dpi());          // snapshot current DPI
state.commit_dpi_presets(presets); // persist and update hook map

```

## Runtime Cycle Index Management

The OpenLogi agent runtime maintains the cycling state independently of the UI. It stores the current cycle position in a thread-safe `dpi_cycle` map defined in [`crates/openlogi-agent-core/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime.rs) as `RwLock<HashMap<DeviceKey, usize>>`.

This structure maps each `DeviceKey` to its current preset index. When the runtime processes a `CycleDpiPresets` action, it acquires a write lock on the map, retrieves the preset list for the target device, selects the preset at the stored index, and dispatches a `SetDpi` IPC command to the physical device. After applying the DPI value, the runtime increments the index with wrap-around logic (resetting to zero when reaching the end of the preset vector).

```rust
match action {
    Action::CycleDpiPresets => match self.dpi_cycle.write() {
        // read the preset list, apply the next one, advance the index
        // (implementation details omitted for brevity)
    },
    Action::SetDpiPreset(i) => match self.dpi_cycle.write() {
        // directly apply the preset at position `i`
    },
    …
}

```

## Action Type Definitions and IPC Dispatch

The core binding module in [`crates/openlogi-core/src/binding/action.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/action.rs) defines the action protocol supporting both cycling and direct preset selection. The `Action` enum includes two DPI-related variants:

```rust
pub enum Action {
    …,
    CycleDpiPresets,
    SetDpiPreset(u8),   // payload = preset index
}

```

**`CycleDpiPresets`** triggers sequential traversal through the preset list using the runtime cycle index, while **`SetDpiPreset(u8)`** accepts a zero-based preset index as payload and bypasses the cycle logic to apply a specific preset immediately.

When the agent runtime receives either action type, it resolves the appropriate preset from the device configuration, constructs a `SetDpi` IPC command with the target DPI values, and transmits it to the mouse firmware.

To trigger cycling programmatically:

```rust
// Triggered by the “Cycle DPI Presets” binding
let action = Action::CycleDpiPresets;
agent_runtime.handle_action(action);

```

To set a specific preset directly (e.g., preset 2):

```rust
// The UI sends the preset index (zero‑based) as payload
let action = Action::SetDpiPreset(2);
agent_runtime.handle_action(action);

```

## Thumb-Wheel Hardware Integration

Physical input mapping occurs in [`crates/openlogi-desktop/src/features/mouse/thumbwheel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/mouse/thumbwheel.rs). This module binds thumb-wheel gestures to the `CycleDpiPresets` action, allowing users to cycle through configured presets using the mouse's physical thumb-wheel without interacting with the desktop UI.

The mapping treats both forward and backward thumb-wheel directions as `CycleDpiPresets` invocations, relying on the runtime's wrap-around index logic to handle directionality and boundary conditions.

## Summary

- **Persistent Storage**: DPI presets are stored per-device in [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) and committed via `AppState::commit_dpi_presets` in [`crates/openlogi-desktop/src/state/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/dpi.rs).
- **Runtime State**: The agent maintains a `dpi_cycle` map (`RwLock<HashMap<DeviceKey, usize>>`) in [`crates/openlogi-agent-core/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime.rs) to track the current preset index per device.
- **Action Protocol**: Two distinct actions—`CycleDpiPresets` and `SetDpiPreset(u8)`—handle sequential cycling versus direct preset selection, both dispatching `SetDpi` IPC commands.
- **Hardware Binding**: The thumb-wheel implementation in [`crates/openlogi-desktop/src/features/mouse/thumbwheel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/mouse/thumbwheel.rs) maps physical gestures to the `CycleDpiPresets` action for hardware-triggered preset switching.

## Frequently Asked Questions

### How does OpenLogi handle DPI preset persistence across application restarts?

OpenLogi writes DPI preset configurations to the user's [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) file via the `commit_dpi_presets` method. This method uses `config.set_dpi_presets` to update the stored configuration and calls `persist_and_reload` to ensure the runtime's hook map reflects the latest preset order immediately after the write operation completes.

### What is the difference between `CycleDpiPresets` and `SetDpiPreset` actions?

`CycleDpiPresets` advances the runtime's internal cycle index, applies the preset at that position, and increments the index with wrap-around logic. `SetDpiPreset(u8)` accepts a specific zero-based index as an argument, ignores the cycle index, and immediately applies the preset at the requested position without modifying the cycle state.

### Where is the current DPI preset index stored during runtime?

The current preset index for each device is stored in the `dpi_cycle` field within [`crates/openlogi-agent-core/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime.rs). This field is a `RwLock<HashMap<DeviceKey, usize>>` that provides thread-safe access to the cycling state, allowing concurrent reads during UI updates and exclusive writes during action processing.

### Can the thumb-wheel cycle backward through DPI presets?

Yes. The thumb-wheel implementation in [`crates/openlogi-desktop/src/features/mouse/thumbwheel.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/mouse/thumbwheel.rs) maps both forward and backward wheel directions to the `CycleDpiPresets` action. The runtime's wrap-around index logic in the agent handles directional cycling, ensuring that decrementing from index zero moves to the last preset in the vector.