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

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 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. 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) 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. 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. 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) 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.

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
// 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()?;
// 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);
// 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.

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 →