# How OpenLogi Handles Device Capabilities Differently Per Link

> Discover how OpenLogi uniquely manages device capabilities per link using LinkConfig, ensuring precise feature detection across USB, Bluetooth, and wireless transports.

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

---

**OpenLogi stores device capabilities at the link level via `LinkConfig` rather than treating them as universal device properties, enabling accurate feature detection across different transports like USB, Bluetooth, and wireless receivers.**

In the OpenLogi input device management system, a single physical mouse or keyboard can connect through multiple routes—wired USB, a 2.4 GHz wireless dongle, or Bluetooth. Each transport may expose a different subset of HID++ features. The OpenLogi source code implements a layered configuration model that separates **device-wide identity** from **per-link capabilities**, ensuring the GUI presents only supported controls for the currently active connection.

## The LinkConfig Architecture

OpenLogi defines one `DeviceConfig` per unique device model, but stores a separate `LinkConfig` for every route on which that device has been observed. This design is implemented in [`crates/openlogi-core/src/config/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/device.rs).

### Per-Link Capabilities Storage

The `LinkConfig` struct contains an optional `capabilities` field:

```rust
// crates/openlogi-core/src/config/device.rs (lines 84-91)
pub struct LinkConfig {
    /// Capabilities discovered on this specific link.
    /// 
    /// Different links may expose different feature sets due to transport
    /// limitations or firmware variations.
    pub capabilities: Option<Capabilities>,
    /// User-defined overrides that apply only to this link.
    pub overrides: LinkOverrides,
}

```

This allows the same device model to report **Hi-Res Wheel**, **SmartShift**, or **RGB lighting** features on one transport but not another. For example, a Logitech G502 LIGHTSPEED mouse exposes high-resolution scrolling over its wireless receiver link, yet presents a reduced feature set when connected via USB cable.

### Runtime Capability Discovery

When a device appears online, the desktop client probes its HID++ feature table and updates the per-link capabilities. The `adopt_routes` helper in [`crates/openlogi-desktop/src/state/inventory.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/inventory.rs) merges these discovered capabilities into the canonical configuration:

```rust
// crates/openlogi-desktop/src/state/inventory.rs (lines 24-26, 462-466)
pub fn adopt_routes(&mut self, routes: &[DeviceRoute]) {
    for route in routes {
        if let Some(link_cfg) = device.links.get_mut(&route.key()) {
            // Merge freshly discovered capabilities into stored LinkConfig
            link_cfg.capabilities = Some(route.capabilities.clone());
        }
    }
}

```

This ensures the correct capability set is always available regardless of which link is currently active.

## Per-Link User Overrides

Beyond raw hardware capabilities, each `LinkConfig` maintains a `LinkOverrides` struct for settings that should vary by connection type. Common override categories include:

- **DPI sensitivity** — different values for desk versus travel mousepad surfaces
- **Scroll direction inversion** — enabled for Bluetooth laptop use, disabled for desktop
- **Lighting profiles** — battery-saving dim mode on wireless, full brightness on USB
- **SmartShift tension** — looser setting for gaming, tighter for productivity

These overrides shadow device-wide defaults only while the device is reachable via that specific route. The GUI queries `DeviceConfig.links` using the current `DeviceStableId::route_key` to determine which panels to render.

## Accessing Per-Link Capabilities in Code

The following example demonstrates iterating over stored links to inspect capability differences:

```rust
use openlogi_core::config::{Config, LinkConfig};

fn print_capabilities(cfg: &Config, device_key: &str) {
    // Resolve the device's config entry
    if let Some(device_cfg) = cfg.device(device_key) {
        // Iterate over each stored link (route key → LinkConfig)
        for (route_key, link_cfg) in &device_cfg.links {
            // `capabilities` is optional — only present if the link has been probed
            if let Some(caps) = &link_cfg.capabilities {
                println!("Link {} reports capabilities: {:#?}", route_key, caps);
            } else {
                println!("Link {} has no discovered capabilities yet.", route_key);
            }
        }
    }
}

```

This pattern is used by the inventory system to populate device tiles and filter unavailable settings.

## Writing Per-Link Configuration Overrides

To persist a setting that applies only to a specific transport:

```rust
use openlogi_core::config::{Config, LinkOverrides};

fn set_link_dpi(cfg: &mut Config, device_key: &str, route_key: &str, new_dpi: Dpi) {
    if let Some(device_cfg) = cfg.device_mut(device_key) {
        // Ensure a LinkConfig exists for the requested route
        let link_cfg = device_cfg.links.entry(route_key.to_string())
                                       .or_default();
        // Record the DPI override for this link only
        link_cfg.overrides.dpi = Some(new_dpi);
        cfg.save(); // persist the change
    }
}

```

The `BTreeMap<String, LinkConfig>` keyed by `route_key` guarantees stable lookup even when connection order varies.

## GUI Integration

The desktop client's state layer bridges the core configuration with the user interface. As implemented in [`crates/openlogi-desktop/src/state/agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/agent.rs), the current `AgentLink` status determines which `LinkConfig` to query. The UI then:

1. Reads `LinkConfig.capabilities` to decide which feature panels to display
2. Applies `LinkOverrides` values as initial control states
3. Writes changes back to the specific `LinkConfig` rather than global device settings

This produces accurate device representation—users never see unsupported controls, and preferences automatically follow transport-specific contexts.

## Summary

- **Capabilities are link-scoped**: The `LinkConfig.capabilities: Option<Capabilities>` field stores HID++ feature sets independently per transport, defined in [`crates/openlogi-core/src/config/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/device.rs)
- **Runtime updates merge intelligently**: [`crates/openlogi-desktop/src/state/inventory.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/inventory.rs) updates stored capabilities each time a device appears online, with `adopt_routes` synchronizing route-derived data
- **Overrides enable transport-specific preferences**: `LinkOverrides` within `LinkConfig` shadows global defaults for DPI, lighting, and gesture settings
- **Single logical device, multiple capability contexts**: The `DeviceConfig.links: BTreeMap<String, LinkConfig>` structure preserves one unified device identity while supporting heterogeneous transport behavior

## Frequently Asked Questions

### Why doesn't OpenLogi store one capability set per device model?

Device capabilities vary by transport due to protocol limitations and firmware differences. A wireless receiver may support HID++ 2.0 features that the Bluetooth HID profile omits. Storing capabilities per `LinkConfig` in [`crates/openlogi-core/src/config/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/device.rs) ensures accurate feature detection for each connection type rather than assuming universal behavior.

### How does OpenLogi handle capability changes after firmware updates?

The `adopt_routes` function in [`crates/openlogi-desktop/src/state/inventory.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/state/inventory.rs) refreshes `LinkConfig.capabilities` whenever a device is observed online. New firmware features are automatically detected on the next connection and merged into the stored configuration, superseding cached values without manual intervention.

### Can users manually override detected capabilities?

Users cannot directly edit `capabilities` fields—those reflect hardware-reported HID++ feature tables. However, the `LinkOverrides` struct in each `LinkConfig` provides comprehensive control over functional behavior. Settings like disabled buttons or suppressed gestures achieve the same practical effect as capability masking for UI purposes.

### What happens if the same device connects via an unrecognized route?

The configuration system creates a new `LinkConfig` entry via `BTreeMap::entry().or_default()` when encountering unknown `route_key` values. Initial capabilities are `None` until the device is probed, and defaults apply until the user customizes `overrides`. This ensures graceful handling of new dongles, USB ports, or Bluetooth pairings.