How OpenLogi Uses TOML for Configuration: Schema Versioning and File Management
OpenLogi persists all user settings in a strictly-versioned TOML file controlled by the Config struct in crates/openlogi-core/src/config.rs, using serde for validation and a dedicated ConfigFile utility for atomic disk operations.
OpenLogi manages device settings, keyboard mappings, and application preferences through a human-readable TOML configuration system. The implementation in the AprilNEA/OpenLogi repository employs strict schema validation with #[serde(deny_unknown_fields)] and automatic migrations to ensure settings survive software updates. This guide examines the source code architecture, file I/O operations, and versioning strategy behind OpenLogi's configuration management.
Configuration File Location and Format
OpenLogi stores the active configuration in platform-standard directories using the filename config.toml.
- Linux:
~/.config/openlogi/config.toml - Windows:
%APPDATA%\OpenLogi\config.toml - macOS:
~/Library/Application Support/OpenLogi/config.toml
The repository provides a fully documented reference file at docs/config.example.toml that demonstrates all supported sections and default values. Users can copy this example to the appropriate location to initialize their settings.
Core Configuration Architecture
The central Config struct defined in crates/openlogi-core/src/config.rs serves as the in-memory representation of the TOML document. The implementation derives Serialize and Deserialize from serde and applies #[serde(deny_unknown_fields)] to enforce strict correspondence between the file structure and the Rust type system.
Key structural components include:
schema_version: An integer field tracking the on-disk format versionapp_settings: Global application preferencesselected_device: Identifier for the currently active devicedevices: ABTreeMap<String, DeviceConfig>providing deterministic ordering of per-device configurationskeyboard: Global keyboard-wide bindings stored in aKeyboardConfigsection
The use of #[serde(default)] on optional sections ensures that older configuration files missing new fields still deserialize correctly without errors.
Schema Versioning and Migration Strategy
OpenLogi maintains a SCHEMA_VERSION constant (currently set to 6) in crates/openlogi-core/src/config.rs that represents the latest supported format. When loading a configuration file, the system compares the file's schema_version field against this constant.
If the file uses an older schema, OpenLogi executes specific migration functions before the configuration becomes active. The source code includes methods such as:
migrate_transport_scoped_keys()migrate_owner_locked_gestures()
These functions rewrite deprecated keys into the current schema layout, ensuring user settings persist across software updates. After migration, the system persists the updated configuration back to disk with the current schema version.
File I/O and Atomic Operations
The ConfigFile helper struct in crates/openlogi-core/src/config/file.rs abstracts all disk operations, providing safe loading, saving, and backup handling. This module uses toml::from_str for deserialization and toml::to_string_pretty for generating human-readable output.
Critical operations include:
ConfigFile::load(): Reads the TOML from the platform configuration directory and applies any necessary migrationsConfigFile::save_atomic(): Writes updates to disk using atomic file operations to prevent corruption during write failuresConfig::ephemeral(): Creates a temporary in-memory configuration for unit testing that never writes to disk, protecting developer settings during test runs
When the OpenLogi agent starts, it loads the file via ConfigFile::load() and populates the in-memory Config. Subsequent UI actions mutate the Config object, and the agent then calls ConfigFile::save_atomic() to persist changes.
Practical Implementation Examples
The following Rust code demonstrates loading, modifying, and persisting configuration values:
// Load persistent configuration from disk
let cfg = openlogi_core::config::ConfigFile::load()?;
// Configure DPI presets for a specific device
cfg.set_dpi_presets("receiver:abcd:slot:1", vec![800, 1200, 1600]);
// Add a global keyboard shortcut
use openlogi_core::config::{KeyTrigger, Action, KeyModifiers};
cfg.set_keyboard_binding(
KeyTrigger::new(KeyModifiers::default(), "F1".into()),
Some(Action::Mute),
);
// Persist changes atomically to prevent data corruption
openlogi_core::config::ConfigFile::save_atomic(&cfg)?;
A minimal valid configuration file appears as follows:
# docs/config.example.toml structure
schema_version = 6
[app_settings]
language = "en"
ui_scale = 1.0
[keyboard]
[[keyboard.bindings]]
trigger = { modifiers = [], key = "F1" }
action = "mute"
[devices."receiver:abcd:slot:1"]
custom_name = "My Mouse"
dpi_presets = [800, 1200, 1600]
smartshift = { enabled = true, threshold = 2 }
Summary
- OpenLogi stores settings in
config.tomllocated in platform-standard configuration directories - The
Configstruct incrates/openlogi-core/src/config.rsuses#[serde(deny_unknown_fields)]for strict TOML validation - Schema versioning via
SCHEMA_VERSION(currently 6) enables automatic migrations from older file formats - The
ConfigFileutility incrates/openlogi-core/src/config/file.rshandles atomic writes and safe loading operations - Per-device settings use a
BTreeMap<String, DeviceConfig>for deterministic ordering - Ephemeral configurations support testing without affecting production settings
Frequently Asked Questions
Where does OpenLogi store the configuration TOML file?
OpenLogi follows platform conventions for configuration storage. On Linux, the file resides at ~/.config/openlogi/config.toml; on Windows at %APPDATA%\OpenLogi\config.toml; and on macOS at ~/Library/Application Support/OpenLogi/config.toml. The ConfigFile::load() method automatically resolves these paths based on the operating system.
What happens to my settings when I upgrade OpenLogi?
The configuration system automatically migrates existing settings. When the TOML file loads, OpenLogi compares its schema_version field against the current SCHEMA_VERSION constant. If the versions differ, migration functions such as migrate_transport_scoped_keys() execute to transform deprecated fields into the new format before the configuration becomes active.
Can I edit the OpenLogi configuration file manually?
Yes. The TOML format is human-readable and editable. The repository includes docs/config.example.toml as a reference showing all valid sections including app_settings, devices, and keyboard bindings. However, because the parser uses #[serde(deny_unknown_fields)], any typos or unsupported keys will cause deserialization errors on startup.
How does OpenLogi prevent configuration corruption during saves?
The ConfigFile::save_atomic() method in crates/openlogi-core/src/config/file.rs implements atomic write operations. This ensures that the configuration file is either completely written or not modified at all, preventing partial writes that could occur during system crashes or power failures from corrupting the user's settings.
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 →