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 defaultsselected_device: Option<String>– The stable identifier of the last-used devicedevices: BTreeMap<String, DeviceConfig>– A map keyed by stable physical identifiers (e.g.,"receiver:abc123:slot:2")keyboard: KeyboardConfig– Global keyboard shortcuts independent of specific hardwareephemeral: 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:
Config::load_from_pathreads the TOML file from disk- The method checks the stored schema version against the current constant
- If versions mismatch, automatic migrations execute before the structure is instantiated
- 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 recognitionper_app_bindings: BTreeMap<String, BTreeMap<ButtonId, Action>>– Context-aware overrides triggered by foreground applicationdpi_presets: Vec<Dpi>– User-defined sensitivity stageslighting: Option<Lighting>– LED color configurations for supported hardwarecamera_controls: Option<CameraControls>– UVC camera image adjustmentssmartshift: Option<SmartShift>– Wheel acceleration behavior parametersenabled: bool– Whether OpenLogi manages the devicecustom_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:
- Serializes the in-memory
Configto TOML using Serde - Writes to a temporary file in the same directory
- Performs an atomic rename operation to replace the existing config
- Respects the
ephemeralflag 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_VERSIONmust increment wheneverConfigor 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:
- Initialization – The agent calls
Config::load_from_pathto hydrate the TOML document into memory - Discovery – HID++ probing populates
DeviceIdentityandCapabilitiesfor connected hardware - Application – The agent iterates
Config.devices, transmitting DPI values, lighting settings, and SmartShift parameters to each online device - Modification – User interactions through GUI or CLI mutate the in-memory structure via methods like
Config::set_bindingorConfig::set_dpi_presets - Persistence – The agent triggers
Config::save_atomicto 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
Configstruct within theopenlogi-corecrate, ensuring portability across operating systems and WebAssembly targets. - TOML serialization provides human-readable, editable configuration files stored atomically via
Config::save_atomicin the platform-standard config directory. - Per-device state is isolated in
DeviceConfigstructs 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
fsfeature flag, while theephemeralflag prevents disk writes during testing. - IPC architecture uses versioned bincode streams to share immutable
Configsnapshots 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →