What is the Purpose of the openlogi-core Crate in OpenLogi?
The openlogi-core crate provides the platform-agnostic data model and TOML-serializable configuration types for Logitech HID++ devices, serving as a pure, hardware-I/O-free foundation that enables unit testing without physical hardware and ensures type consistency across the CLI, agent, and GUI components.
The openlogi-core crate acts as the foundational data layer for OpenLogi (AprilNEA/OpenLogi), a native Rust alternative to Logitech Options+. According to docs/DEVELOPMENT.md, this crate isolates the data definitions—device capabilities, configuration schemas, and action catalogs—from hardware communication and async runtimes, allowing the same serde-driven types to power every interface in the stack.
Core Responsibilities of openlogi-core
Pure Type Definitions and Serialization
At its heart, openlogi-core contains only Rust structs and enums that describe the entire domain model. These types implement Serialize and Deserialize from serde, enabling seamless conversion to and from TOML configuration files. As noted in crates/openlogi-core/README.md, the crate strictly avoids hardware-specific I/O, async runtimes, and operating system dependencies. This separation ensures that data structures can be unit-tested in isolation without requiring attached Logitech devices or platform-specific SDKs.
Global Configuration Management
The crate defines the Settings struct in crates/openlogi-core/src/config/settings.rs, which governs global defaults such as SmartShift behavior, DPI step increments, and LED color palettes. Each field carries serde attributes for default values:
smart_shift: Boolean toggling automatic DPI shifting, defaulting totrueviadefault_smart_shift()step: Global DPI step size, defaulting to100viadefault_step()colors: Vector of hex color strings for LED devices, defaulting to red, green, and blue viadefault_colors()assets_dir: OptionalPathBufto custom asset directories
Individual devices can override these global values in their specific configuration sections, as illustrated in docs/config.example.toml.
Device Models and Action Catalogs
openlogi-core enumerates supported hardware and user interactions through strict type definitions. The crate maps:
- Device identifiers: Vendor and product IDs (e.g.,
0x046dfor Logitech), DPI ranges, and button layouts defined in the device model source - Action kinds: Enumerated variants for clicks, scrolls, key combinations, swipes, and DPI steps found in the action catalog
- Bindings: Structures linking physical buttons to actions, including per-application overrides and macro definitions
These definitions ensure that openlogi-cli, openlogi-agent, and openlogi-desktop all operate on identical data structures without duplication.
The I/O-Free Architecture Design
Testability Without Hardware Dependencies
By isolating data models from the async-hid and raw input handling found in sibling crates like openlogi-hid and openlogi-hook, openlogi-core enables comprehensive unit testing. Developers can instantiate Settings, Binding, and Device structs in test suites without mocking USB HID protocols or attaching physical mice. This design is documented in the crate's README.md as a deliberate strategy to validate configuration logic and action parsing independently of hardware state.
Cross-Crate Consistency
The crate functions as the single source of truth for the entire workspace. Because openlogi-core contains no platform-specific code, it compiles identically on macOS, Linux, and Windows. The agent binary adds the runtime HID++ I/O layer, the CLI adds command-line parsing, and the GUI adds GPUI presentation, but all three consume the same Settings and Action definitions from src/lib.rs.
Working with openlogi-core: Code Examples
Loading TOML Configuration Files
To load a configuration file into the Settings struct, use the synchronous helper load_from_path defined in crates/openlogi-core/src/config/file.rs:
use openlogi_core::config::file::load_from_path;
fn main() -> anyhow::Result<()> {
let settings = load_from_path("docs/config.example.toml")?;
println!("SmartShift enabled? {}", settings.smart_shift);
println!("Global DPI step: {}", settings.step);
Ok(())
}
This function reads the file via std::fs::read_to_string and parses the TOML content using toml::from_str, returning a fully populated Settings instance or a descriptive error via anyhow.
Creating Configuration Programmatically
You can construct Settings instances directly and serialize them to TOML for generating default configurations:
use openlogi_core::config::Settings;
use toml;
fn main() -> anyhow::Result<()> {
let custom = Settings {
smart_shift: false,
step: 200,
colors: vec!["#ff0000".into(), "#00ff00".into()],
assets_dir: None,
};
let toml_str = toml::to_string_pretty(&custom)?;
println!("{}", toml_str);
Ok(())
}
This approach leverages the Default trait implementation and the default functions default_smart_shift(), default_step(), and default_colors() defined in src/config/settings.rs.
Defining Button Bindings
The crate provides types for describing input bindings, allowing you to define what actions trigger when specific buttons are pressed:
use openlogi_core::binding::{Binding, Action, ActionKind};
fn create_thumb_binding() -> Binding {
Binding {
device: "G502".into(),
button: "thumb_button".into(),
action: Action {
kind: ActionKind::KeyCombo,
keys: vec!["Ctrl".into(), "Alt".into(), "Delete".into()],
..Default::default()
},
}
}
These binding definitions correspond directly to the [[binding]] tables in docs/config.example.toml, where buttons map to actions like key_combo or dpi_step.
Summary
- Pure Data Layer:
openlogi-corecontainsserde-serializable structs forSettings,Device, andActiontypes without hardware I/O or async code. - Configuration Management: Defines global defaults and device-specific overrides via the
Settingsstruct insrc/config/settings.rs, loadable viaload_from_pathinsrc/config/file.rs. - Cross-Component Foundation: Shared by
openlogi-cli,openlogi-agent, andopenlogi-desktopto ensure consistent data interpretation across the stack. - Testing Isolation: Enables unit testing of configuration logic and binding rules without physical Logitech devices attached.
Frequently Asked Questions
Does openlogi-core communicate directly with Logitech devices?
No. According to docs/DEVELOPMENT.md and the crate's README.md, openlogi-core contains no hardware I/O. Direct HID++ communication is handled by separate crates such as openlogi-hid and openlogi-agent, while openlogi-core remains limited to data definitions and synchronous file loading for configuration.
How does openlogi-core handle TOML parsing?
The crate utilizes serde derive macros on structs like Settings combined with the toml crate for serialization. The load_from_path function in src/config/file.rs orchestrates file reading and parsing, while default values for fields like smart_shift and step are provided via dedicated default functions referenced in serde attributes.
Which OpenLogi binaries depend on openlogi-core?
Three primary binaries consume this crate: openlogi-cli for diagnostics and configuration editing, openlogi-agent for the runtime device management layer, and openlogi-desktop (along with openlogi-overlay) for the graphical interface. This shared dependency ensures that configuration changes in TOML files are interpreted identically across all interfaces.
Why is the openlogi-core crate considered platform-agnostic?
The crate contains no platform-specific code, async runtimes, or hardware drivers—only pure Rust data structures. While it performs synchronous file I/O for configuration loading, it avoids OS-specific APIs found in crates like openlogi-hook (macOS CGEventTap, Linux evdev) or openlogi-inject, allowing it to compile and function identically on macOS, Linux, and Windows.
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 →