# How OpenLogi Handles and Stores Data: A Deep Dive into the Config Architecture

> Explore how OpenLogi handles and stores data using atomic TOML Config objects and isolated DeviceConfig structs. Learn about its efficient IPC snapshot sharing for per-device state.

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

---

**OpenLogi stores all user settings in a single, schema-versioned `Config` object serialized as atomic TOML, with per-device state isolated in `DeviceConfig` structs and shared across processes via immutable IPC snapshots.**

The Rust-based input device manager [AprilNEA/OpenLogi](https://github.com/AprilNEA/OpenLogi) implements a strict separation between data and I/O. All configuration logic lives in the `openlogi-core` crate, which defines pure data types independent of OS-specific operations, ensuring the entire data model compiles for WebAssembly and remains portable across platforms.

## The Central Config Object

At the heart of OpenLogi’s data layer sits the **`Config`** struct defined in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs). This top-level container aggregates every user-visible setting into a single append-only document that survives application restarts.

### TOML-Backed Document Structure

When the **`fs`** feature is enabled during compilation, `Config` serializes to a TOML file stored in the platform-standard configuration directory. The struct organizes data into distinct domains:

- **`app_settings: AppSettings`** – Global preferences including UI scale and SmartShift defaults
- **`selected_device: Option<String>`** – The stable identifier of the last-used device
- **`devices: BTreeMap<String, DeviceConfig>`** – A map keyed by stable physical identifiers (e.g., `"receiver:abc123:slot:2"`)
- **`keyboard: KeyboardConfig`** – Global keyboard shortcuts independent of specific hardware
- **`ephemeral: bool`** – Feature-gated flag preventing disk writes during testing

### Schema Versioning and Migration

OpenLogi guards against data corruption through explicit schema versioning. The constant **`SCHEMA_VERSION = 6`** (defined at line 89 of [`config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/config.rs)) validates compatibility during load operations:

1. `Config::load_from_path` reads the TOML file from disk
2. The method checks the stored schema version against the current constant
3. If versions mismatch, automatic migrations execute before the structure is instantiated
4. Any invalid version triggers a graceful error rather than silent corruption

### Platform-Independent Design

The `openlogi-core` crate deliberately excludes all platform-specific I/O. Persistence logic is feature-gated behind the `fs` flag and implemented in [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs). This architecture allows the core data types to compile for WebAssembly targets while still supporting full desktop functionality when the filesystem feature is active.

## Per-Device State Management

Individual hardware configurations live in **`DeviceConfig`**, defined in [`crates/openlogi-core/src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/device.rs). This struct encapsulates every configurable aspect of a physical device without performing any hardware I/O itself.

### DeviceConfig Structure

Each device entry contains granular control mappings and hardware-specific settings:

- **`bindings: BTreeMap<ButtonId, Binding>`** – Maps physical buttons to actions, including gesture recognition
- **`per_app_bindings: BTreeMap<String, BTreeMap<ButtonId, Action>>`** – Context-aware overrides triggered by foreground application
- **`dpi_presets: Vec<Dpi>`** – User-defined sensitivity stages
- **`lighting: Option<Lighting>`** – LED color configurations for supported hardware
- **`camera_controls: Option<CameraControls>`** – UVC camera image adjustments
- **`smartshift: Option<SmartShift>`** – Wheel acceleration behavior parameters
- **`enabled: bool`** – Whether OpenLogi manages the device
- **`custom_name: Option<String>`** – User-assigned friendly identifier

The agent process reads these fields, applies them through the HID++ protocol layer, and writes modifications back to the central `Config`.

### Device Identity and Capabilities

OpenLogi maintains stable device references through **`DeviceIdentity`**, populated from HID++ DeviceInformation packets. This identity survives USB reconnections and receiver re-pairing.

The **`Capabilities`** struct (lines 97-104 of [`device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/device.rs)) reflects the HID++ feature set reported by the hardware, including button availability, DPI support, lighting zones, and haptic feedback. When a device is offline, `Capabilities::presumed_from_kind` provides safe defaults based on the `DeviceKind` enumeration, preventing configuration errors for disconnected hardware.

## Data Persistence and Atomic Writes

Configuration changes are committed to disk through **`Config::save_atomic`**, which ensures data integrity during write operations. The method:

1. Serializes the in-memory `Config` to TOML using Serde
2. Writes to a temporary file in the same directory
3. Performs an atomic rename operation to replace the existing config
4. Respects the `ephemeral` flag to prevent accidental overwrites during CI testing

This approach eliminates the risk of configuration corruption if the process crashes mid-write, as the original file remains untouched until the atomic rename succeeds.

## Inter-Process Communication (IPC) Architecture

The OpenLogi architecture splits functionality between a background **agent** process and a **GUI** frontend. Data sharing occurs through the `openlogi-ipc` crate, specifically in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs).

The agent owns the mutable `Config` instance and exposes **immutable snapshots** to the GUI via append-only bincode streams. This design choice means:

- **`PROTOCOL_VERSION`** must increment whenever `Config` or nested struct shapes change
- The GUI receives a read-only view, preventing race conditions in configuration state
- Cross-process communication remains type-safe through Rust’s serialization guarantees

## Runtime Data Flow

Understanding how data moves through OpenLogi requires tracing the lifecycle from startup to persistence:

1. **Initialization** – The agent calls `Config::load_from_path` to hydrate the TOML document into memory
2. **Discovery** – HID++ probing populates `DeviceIdentity` and `Capabilities` for connected hardware
3. **Application** – The agent iterates `Config.devices`, transmitting DPI values, lighting settings, and SmartShift parameters to each online device
4. **Modification** – User interactions through GUI or CLI mutate the in-memory structure via methods like `Config::set_binding` or `Config::set_dpi_presets`
5. **Persistence** – The agent triggers `Config::save_atomic` to serialize the updated state to TOML

```rust
// Load configuration from the platform config directory
let cfg = openlogi_core::config::Config::load_from_path()?;
assert_eq!(cfg.schema_version, openlogi_core::config::SCHEMA_VERSION);

// Configure a button binding for the active device
let dev_key = cfg.selected_device().unwrap(); // e.g., "unit:1234abcd"
let button = openlogi_core::binding::ButtonId::Button1;
let action = openlogi_core::binding::Action::LaunchApp("https://example.com".into());

let mut cfg_mut = cfg;
cfg_mut.set_binding(dev_key, button, openlogi_core::binding::Binding::Single(action));

// Atomically persist changes to disk
cfg_mut.save_atomic()?;

```

```rust
// Query current DPI presets for a specific device
let dev_key = "unit:1234abcd";
let dpi_list = config.dpi_presets(dev_key);
println!("Current DPI presets: {:?}", dpi_list);

```

```rust
// Update SmartShift configuration
let mut cfg = config;
cfg.set_smartshift(
    "unit:1234abcd",
    openlogi_core::config::SmartShift { enabled: true, timeout_ms: 3000 },
);
cfg.save_atomic()?;

```

## Summary

- **OpenLogi centralizes all data** in the `Config` struct within the `openlogi-core` crate, ensuring portability across operating systems and WebAssembly targets.
- **TOML serialization** provides human-readable, editable configuration files stored atomically via `Config::save_atomic` in the platform-standard config directory.
- **Per-device state** is isolated in `DeviceConfig` structs keyed by stable physical identifiers, with schema version 6 guarding against incompatible changes.
- **Feature-gated I/O** keeps the core crate pure; filesystem operations require the `fs` feature flag, while the `ephemeral` flag prevents disk writes during testing.
- **IPC architecture** uses versioned bincode streams to share immutable `Config` snapshots between the agent process and GUI, maintaining thread safety and type consistency.

## Frequently Asked Questions

### Where does OpenLogi store its configuration files?

OpenLogi writes configuration data to a TOML file located in the platform-standard configuration directory. The exact path depends on the operating system—typically `~/.config/openlogi/` on Linux, `~/Library/Application Support/openlogi/` on macOS, or `%APPDATA%\openlogi\` on Windows. The `fs` feature must be enabled at compile time for filesystem persistence to function.

### What happens if I edit the OpenLogi configuration file manually while the application is running?

Manual edits to the TOML file are safe while OpenLogi is not running. If you modify the file while the agent is active, changes will be overwritten on the next `Config::save_atomic` call, as the agent maintains the authoritative in-memory state and periodically persists it to disk. To apply external edits, restart the agent after saving the file.

### How does OpenLogi prevent configuration corruption during crashes?

OpenLogi implements atomic file writes through `Config::save_atomic`. The method writes the serialized TOML to a temporary file first, then performs an atomic rename operation to replace the existing configuration. This ensures that the original file remains intact until the entire write completes successfully, preventing partial writes or corruption if the process terminates unexpectedly.

### Can OpenLogi run without storing any data to disk?

Yes. When compiled with the `ephemeral` feature flag, OpenLogi operates entirely in memory without touching the filesystem. This mode is primarily used for automated testing in CI environments to avoid side effects, but it can also be used for temporary sessions where configuration persistence is undesirable. In ephemeral mode, `Config::save_atomic` executes as a no-op.