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 version
  • app_settings: Global application preferences
  • selected_device: Identifier for the currently active device
  • devices: A BTreeMap<String, DeviceConfig> providing deterministic ordering of per-device configurations
  • keyboard: Global keyboard-wide bindings stored in a KeyboardConfig section

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 migrations
  • ConfigFile::save_atomic(): Writes updates to disk using atomic file operations to prevent corruption during write failures
  • Config::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.toml located in platform-standard configuration directories
  • The Config struct in crates/openlogi-core/src/config.rs uses #[serde(deny_unknown_fields)] for strict TOML validation
  • Schema versioning via SCHEMA_VERSION (currently 6) enables automatic migrations from older file formats
  • The ConfigFile utility in crates/openlogi-core/src/config/file.rs handles 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:

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 →