# What Is the Purpose of the openlogi-core Crate in OpenLogi?

> Discover the purpose of the openlogi-core crate. It defines the pure data model and TOML configurations for Logitech HID++ devices, ensuring no hardware I/O for easy testing and reuse in OpenLogi.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-08-25

---

**The openlogi-core crate defines the pure data model and TOML-serializable configuration structures for Logitech HID++ devices, containing absolutely no hardware I/O or async runtime code to enable testing and reuse across the entire OpenLogi application stack.**

The **openlogi-core** crate serves as the foundational data layer for [AprilNEA/OpenLogi](https://github.com/AprilNEA/OpenLogi), a native, local-first alternative to Logitech Options+ written in Rust. It establishes the **single source of truth** for device capabilities, button bindings, and global settings that power the CLI, background agent, and desktop GUI components.

## Core Responsibilities of the openlogi-core Crate

### Pure Types and Zero I/O Design

At its heart, **openlogi-core** is a collection of **serde**-serializable Rust structs and enums that describe every aspect of Logitech device configuration. According to the crate's documentation, it contains **strictly no platform-specific I/O**—it never talks to HID devices, never spawns async tasks, and never accesses the file system except through explicit helper functions provided by consuming crates.

This **I/O-free architecture** means the crate focuses exclusively on:

- **Device descriptors** (vendor/product IDs, DPI ranges, LED capabilities, button layouts)
- **Configuration schemas** (global defaults and per-device overrides in TOML format)
- **Action definitions** (click, scroll, key-combo, swipe, DPI step, and macro actions)
- **Binding catalogs** (mappings between physical buttons and logical actions, including per-application overrides)

### Configuration Management via the Settings Struct

The central configuration type is the **`Settings`** struct defined in [`src/config/settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/config/settings.rs). This struct captures global defaults that apply to all connected devices unless explicitly overridden:

```rust
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Settings {
    #[serde(default = "default_smart_shift")]
    pub smart_shift: bool,
    #[serde(default = "default_step")]
    pub step: u16,
    #[serde(default = "default_colors")]
    pub colors: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub assets_dir: Option<PathBuf>,
}

```

The `smart_shift` field controls automatic DPI shifting behavior, `step` defines the DPI adjustment increment (defaulting to 100), and `colors` stores the global LED palette as hex RGB strings. Because these types implement **serde** traits, they serialize cleanly to human-readable TOML configuration files.

### Device Model and Action Catalog

Beyond global settings, **openlogi-core** houses the complete device model in [`src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/device.rs) and the action catalog in [`src/binding.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/binding.rs). These modules define:

- **Device identification** using vendor/product ID pairs (e.g., `0x046d` for Logitech)
- **Capability matrices** describing available buttons, LED zones, and sensor resolutions
- **Action kinds** ranging from simple key combinations to complex multi-step macros

## Architecture Benefits of the I/O-Free Design

### Testability Without Hardware

Because **openlogi-core** contains no hardware interaction logic, developers can run comprehensive unit tests on the data model without any physical Logitech devices attached. The separation allows validation of configuration parsing, binding resolution, and device capability matching in pure Rust test suites.

### Shared Foundation Across Binaries

The crate's clean separation enables reuse across OpenLogi's multi-binary architecture:

- **`openlogi-cli`** uses the core for diagnostic output and configuration file validation
- **`openlogi-agent`** layers the runtime HID++ I/O on top of core types
- **`openlogi-desktop`** and **`openlogi-overlay`** present the core data model through the GPUI interface

This ensures that changes to the configuration format or action definitions only require modifications in **openlogi-core**, automatically propagating consistency to all consuming components.

## Practical Usage Examples

### Loading a TOML Configuration File

The `load_from_path` function in [`src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/config/file.rs) provides a synchronous interface for deserializing configuration files into the `Settings` struct:

```rust
use openlogi_core::config::file::load_from_path;

fn main() -> anyhow::Result<()> {
    // Load from a standard TOML configuration file
    let settings = load_from_path("docs/config.example.toml")?;
    println!("SmartShift enabled? {}", settings.smart_shift);
    println!("Global DPI step: {}", settings.step);
    Ok(())
}

```

### Creating Settings Programmatically

You can construct configuration objects directly and serialize them back to TOML for persistence:

```rust
use openlogi_core::config::Settings;

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(())
}

```

### Defining Button Bindings

The binding system maps physical buttons to actions using the types defined in [`src/binding.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/binding.rs):

```rust
use openlogi_core::binding::{Binding, Action, ActionKind};

let binding = Binding {
    device: "G502".into(),
    button: "thumb_button".into(),
    action: Action {
        kind: ActionKind::KeyCombo,
        keys: vec!["Ctrl".into(), "Alt".into(), "Delete".into()],
        ..Default::default()
    },
};

```

## Key Source Files and API Surface

| File Path | Responsibility |
|---|---|
| [`src/lib.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/lib.rs) | Public crate entry point and type re-exports |
| [`src/config/settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/config/settings.rs) | Global `Settings` struct with default value functions |
| [`src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/config/file.rs) | `load_from_path()` function for TOML deserialization |
| [`src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/device.rs) | Device model definitions and capability descriptors |
| [`src/binding.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/binding.rs) | Action types, binding structures, and the action catalog |

## Summary

- **openlogi-core** provides the **pure data model** for the entire OpenLogi ecosystem, ensuring type safety across CLI, agent, and GUI components.
- The crate maintains a **strict zero-I/O policy**, containing no hardware communication, async runtimes, or platform-specific system calls.
- All configuration types implement **serde** traits for seamless TOML serialization and deserialization.
- The `Settings` struct in [`src/config/settings.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/config/settings.rs) manages global defaults for SmartShift behavior, DPI steps, and LED color palettes.
- By centralizing device definitions and action catalogs, the crate creates a **single source of truth** that prevents configuration drift between application layers.

## Frequently Asked Questions

### Does openlogi-core handle hardware communication?

No. The crate intentionally contains no HID++ protocol implementation, no async I/O, and no hardware abstraction layers. Device communication is handled by separate crates such as `openlogi-hid` and `openlogi-hidpp`, which consume the pure types defined in **openlogi-core** but never expose I/O operations within the core itself.

### How does openlogi-core handle configuration file parsing?

The crate uses the **`toml`** crate combined with **serde** derives for deserialization. The `load_from_path` function in [`src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/config/file.rs) provides a thin wrapper around `std::fs::read_to_string` and `toml::from_str`, returning a strongly-typed `Settings` struct or a descriptive error through `anyhow`.

### Can I use openlogi-core in my own Rust project?

Yes, though it is primarily designed for OpenLogi's internal architecture. The crate exposes public types for Logitech device configuration that could theoretically be reused by other Rust applications needing structured HID++ device descriptors, provided they adhere to the same TOML configuration schema.

### What serialization format does openlogi-core use?

**TOML** is the primary configuration format. All core types implement `Serialize` and `Deserialize` from the **serde** ecosystem, enabling human-readable configuration files with comments (as seen in [`docs/config.example.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/docs/config.example.toml)) while maintaining strict type safety at compile time.