How to Set Up and Cycle Through DPI Presets in OpenLogi

OpenLogi stores DPI presets per-device in 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 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. 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.

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 (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 (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:

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 directly:

[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 (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 (lines 83-86):

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 (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 (lines 52-55) coordinates persistence through persist_and_reload(). This writes the updated 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

Frequently Asked Questions

Where are DPI presets stored in OpenLogi?

DPI presets persist in the 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →