How OpenLogi Handles Device Capabilities Differently Per Link
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.
Per-Link Capabilities Storage
The LinkConfig struct contains an optional capabilities field:
// 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 merges these discovered capabilities into the canonical configuration:
// 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:
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:
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, the current AgentLink status determines which LinkConfig to query. The UI then:
- Reads
LinkConfig.capabilitiesto decide which feature panels to display - Applies
LinkOverridesvalues as initial control states - Writes changes back to the specific
LinkConfigrather 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 incrates/openlogi-core/src/config/device.rs - Runtime updates merge intelligently:
crates/openlogi-desktop/src/state/inventory.rsupdates stored capabilities each time a device appears online, withadopt_routessynchronizing route-derived data - Overrides enable transport-specific preferences:
LinkOverrideswithinLinkConfigshadows 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 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 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.
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 →