# How to Set Up and Cycle Through DPI Presets in OpenLogi

> Learn to set up and cycle DPI presets in OpenLogi. Configure config.toml and use hardware buttons with the CycleDpiPresets action for seamless DPI adjustments. Manage persistence easily.

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

---

**OpenLogi stores DPI presets per-device in [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) and cycles through them using the `CycleDpiPresets` action bound to hardware buttons, with the desktop app managing persistence via `AppState` and the agent handling hardware writes through IPC.**

OpenLogi is an open-source input device customization platform that enables per-device DPI configuration for gaming mice and precision pointers. Setting up and cycling through DPI presets in OpenLogi requires coordinating between the desktop application's state management, the persistent TOML configuration layer, and the agent's hardware control runtime.

## Understanding the DPI Preset Architecture

OpenLogi implements a three-tier architecture for DPI management that separates storage, application logic, and hardware communication.

### Configuration Layer (config.toml)

Per-device DPI presets persist in [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) under device-specific keys using the hardware serial number. The configuration stores presets as arrays of integer DPI values indexed by device identifier.

### Application State Management

The desktop application maintains an in-memory representation through `AppState` in [`crates/openlogi-desktop/src/state/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/dpi.rs). The primary methods include:

- `dpi_for_current()` – Returns the cached DPI value for the selected device from `AppState.pointer.reads`
- `dpi_presets()` – Retrieves the current `Vec<Dpi>` preset list from state
- `commit_dpi_presets()` – Persists changes to disk and triggers a UI reload via `persist_and_reload("DPI presets")`

### Agent Runtime and IPC

The `openlogi-agent-core` crate handles physical hardware communication through `openlogi-ipc`. When cycling presets, the agent invokes `ipc_sender.set_dpi(route, value)` to write values and `read_dpi` to query capabilities, as defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs).

## Configuring DPI Presets

### Discovering Device Capabilities

When a device is selected in the UI, the pointer service queries hardware capabilities. In [`crates/openlogi-desktop/src/state/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/dpi.rs) (lines 65-76), the system caches the queried DPI values in `AppState.pointer.reads`, exposing them through `AppState.dpi_for_current()` for UI display.

### Adding Presets via the UI

The Pointer panel renders DPI presets as interactive chips. The "+" chip triggers `add_preset_chip()` in [`crates/openlogi-desktop/src/features/pointer/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/pointer/dpi.rs) (lines 421-428), which appends the current DPI value to the device's preset list and invokes `AppState.commit_dpi_presets(presets)` to persist the configuration:

```rust
Button::new("dpi-preset-add")
    .on_click(move |cx| {
        let mut presets = state.dpi_presets();
        presets.push(state.dpi());
        state.commit_dpi_presets(presets);
    })

```

### Manual Configuration

Advanced users can pre-define presets by editing [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) directly:

```toml
[device."2b042"]
dpi_presets = [800, 1600, 3200]

[device."4082d"]
dpi_presets = [400, 1600]

```

## Cycling Through Presets at Runtime

### The CycleDpiPresets Action

The default binding configuration in [`crates/openlogi-core/src/binding/defaults.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/binding/defaults.rs) (line 53) maps `CycleDpiPresets` to the scroll-wheel thumb-button. This action triggers the preset rotation sequence when activated.

### Agent Processing Logic

When the agent receives `Action::CycleDpiPresets`, it executes the rotation logic in [`crates/openlogi-agent-core/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime.rs) (lines 83-86):

```rust
match action {
    Action::CycleDpiPresets => {
        let next_dpi = self.dpi_cycle.write().next();
        self.ipc_sender.set_dpi(route, next_dpi)?;
    }
    _ => {}
}

```

The `dpi_cycle` state maintains the preset list and current index. The cycle index resets to 0 every time `commit_dpi_presets()` rewrites the preset list, ensuring the rotation always starts from the first entry after modifications.

### UI Reflection and Chip Interaction

The Pointer panel implements immediate-mode interaction in [`crates/openlogi-desktop/src/features/pointer/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/pointer/dpi.rs) (lines 358-403). Clicking a preset chip writes that DPI value to the hardware immediately, while the "×" button removes the preset from the list and triggers configuration persistence.

## State Persistence and Temporary Devices

The `commit_dpi_presets()` method in [`crates/openlogi-desktop/src/state/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/dpi.rs) (lines 52-55) coordinates persistence through `persist_and_reload()`. This writes the updated [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) and triggers a UI state reload so new presets appear immediately.

For temporary devices without persistent keys (indicated by missing serial associations), OpenLogi maintains the preset list only in memory. These values generate debug messages indicating non-persistence and will not survive application restarts.

## Summary

- **OpenLogi stores DPI presets per-device** in [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) under the device serial number (e.g., `[device."2b042"].dpi_presets`).
- **The desktop app manages state** through `AppState` methods like `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), bridging the UI and configuration file.
- **Hardware cycling uses the `CycleDpiPresets` action**, which the agent processes in [`crates/openlogi-agent-core/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime.rs) by advancing through the preset list via `ipc_sender.set_dpi()`.
- **UI interactions** in [`crates/openlogi-desktop/src/features/pointer/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/pointer/dpi.rs) provide immediate application of presets and removal functionality.

## Frequently Asked Questions

### Where are DPI presets stored in OpenLogi?

DPI presets persist in the [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) configuration file under device-specific sections identified by serial numbers (e.g., `[[device."<serial>".dpi_presets]]`). The `AppState.commit_dpi_presets()` method in [`crates/openlogi-desktop/src/state/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/dpi.rs) handles writing these values to disk and reloading the application state.

### How does the DPI cycling action work in the agent?

When the `CycleDpiPresets` action triggers, the agent runtime in [`crates/openlogi-agent-core/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/runtime.rs) locks the shared `dpi_cycle` state, advances the internal index using `next()`, and writes the resulting DPI value to the hardware via `ipc_sender.set_dpi()`. The cycle automatically resets to the first preset when the preset list is modified.

### Can I set DPI presets for temporary or unpaired devices?

Yes, but with limitations. If a device lacks a persistent key (indicating a temporary connection), OpenLogi maintains the preset list only in memory within `AppState`. These values will not survive application restarts and generate debug messages indicating the lack of persistence.

### What happens when I click a DPI preset chip in the UI?

Clicking a preset chip in the Pointer panel immediately writes that DPI value to the device and updates the application state. The removal button ("×") deletes the preset from the list and triggers `commit_dpi_presets()` to synchronize the configuration file, as implemented in [`crates/openlogi-desktop/src/features/pointer/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/features/pointer/dpi.rs).