# How OpenLogi Persists and Restores DPI Presets Across User Sessions

> Discover how OpenLogi saves and loads DPI presets. Learn how the DPI cycle system uses serde and TOML to persist DPI settings across user sessions for a seamless experience.

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

---

**OpenLogi persists DPI cycle states by serializing the `DpiCycles` struct to a TOML configuration file using serde, then deserializing it on startup to restore preset lists and active indices for each device.**

OpenLogi is an open-source input device management framework that maintains user-specific DPI configurations across system reboots. The DPI cycle system achieves session persistence through a serde-backed configuration layer that stores device-specific preset lists and current indices in a user-scoped TOML file, ensuring seamless continuity between launches.

## The DpiCycles Runtime Structure

The core state container is the **`DpiCycles`** struct defined in [`openlogi-agent-core/src/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-agent-core/src/dpi.rs). This structure manages a collection of **`DpiCycleState`** entries keyed by device identifiers, where each state object tracks:

- **`presets: Vec<Dpi>`** — The ordered list of DPI values available for cycling.
- **`index`** — The currently active position within the preset vector.
- **`target: Option<DeviceRoute>`** — The HID route specifying which physical device to address.
- **`capabilities`** — Optional device constraints used to round requested DPI values to supported increments.

During operation, the runtime holds this map in memory, allowing the background hook thread and GUI panels to access and modify DPI settings instantaneously.

## Serde-Based Configuration Persistence

Long-term storage is handled by the configuration subsystem located in [`openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/config.rs). When the application state needs to be saved, the entire **`DpiCycles`** map is serialized using **serde** into TOML format and written to `~/.config/openlogi/config.toml`.

This approach ensures that complex nested structures—such as the `DeviceRoute` variants and DPI metadata—are converted into a human-readable, editable format while maintaining type safety during deserialization.

## Session Restoration on Startup

When the `openlogi-agent-core` runtime initializes, it loads the user configuration file from disk. The deserialization process reconstructs the `DpiCycles` object, populating the internal map with restored `DpiCycleState` entries. Consequently, the runtime resumes with the exact same preset lists and active indices that were present during the previous session.

This restoration mechanism is transparent to the user; the DPI panel implementation in [`openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/src/ipc.rs) immediately reflects the persisted state without requiring manual reconfiguration.

## Runtime Updates and Save Triggers

The DPI cycle pipeline operates through a predictable sequence:

1. The OS hook requests the current state via **`DpiCycles::state_for(key)`**, retrieving the mutable `DpiCycleState` for the target device.
2. A call to **`DpiCycleState::cycle()`** increments the internal `index` with wrap-around logic and returns the new DPI value paired with its `DeviceRoute`.
3. The runtime issues a HID++ write command to update the physical device.
4. The modified `DpiCycles` structure remains in memory until the application shuts down or an explicit save event occurs, at which point the configuration layer serializes the updated state back to the TOML file.

This design minimizes disk I/O by batching writes while ensuring no data loss between sessions.

## Implementation Example

The following Rust snippet demonstrates how the runtime interacts with the DPI cycle system:

```rust
use openlogi_agent_core::{DpiCycles, DpiCycleState};
use openlogi_hid::{DeviceRoute, Dpi};

/// Initialize a DpiCycles map with a preset list for a specific mouse.
let mut cycles = DpiCycles::default();
cycles.by_key.insert(
    "my_mouse".to_string(),
    DpiCycleState {
        presets: vec![Dpi::new(800), Dpi::new(1600), Dpi::new(2400)],
        index: 0,
        target: Some(DeviceRoute::Bolt {
            receiver_uid: "AA00".into(),
            slot: 1,
        }),
        capabilities: None,
    },
);

// Advance to the next preset when the user presses the DPI-cycle button.
if let Some((new_dpi, route)) = cycles.state_for(Some("my_mouse")).and_then(|s| s.cycle())
{
    // `new_dpi` is applied to the device via the HID layer using `route`.
}

```

## Summary

- The **`DpiCycles`** struct in [`openlogi-agent-core/src/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-agent-core/src/dpi.rs) maintains active preset states and current indices in memory.
- **serde** serializes the entire state to `~/.config/openlogi/config.toml` via the configuration layer in [`openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/config.rs).
- On startup, the runtime deserializes this file to restore previous preset lists and active indices for all configured devices.
- The **`DpiCycleState::cycle()`** method handles wrap-around indexing and returns the target `DeviceRoute` required for HID commands.
- Modified states are flushed to disk on application shutdown, ensuring seamless DPI continuity across user sessions.

## Frequently Asked Questions

### How does OpenLogi know which DPI preset was active before a restart?

The active index is stored as an integer field inside the `DpiCycleState` structure for each device. When the application restarts, serde deserializes this value from the TOML configuration file back into the runtime's memory, allowing the system to resume with the exact same preset selected.

### Where is the DPI configuration file located on disk?

According to the source implementation, the configuration is stored in the user's config directory at `~/.config/openlogi/config.toml`. This path is managed by the persistence logic in [`openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-core/src/config.rs).

### What serialization library does OpenLogi use for DPI persistence?

The codebase utilizes **serde** for data serialization. The `DpiCycles` struct is serialized into TOML format and written to disk, then deserialized back into the runtime structures when the agent core initializes in [`openlogi-agent-core/src/runtime.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-agent-core/src/runtime.rs).

### Can DPI presets be modified while the application is running?

Yes. The runtime updates the in-memory `DpiCycles` map immediately when a user cycles through presets via the OS hook or GUI panel defined in [`openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-ipc/src/ipc.rs). These modifications persist to the TOML file when the application shuts down or when an explicit save operation is triggered.