What APIs Does OpenLogi Expose? A Complete Guide to the Rust-Based Logitech Control Interface
OpenLogi exposes three distinct programmatic interfaces: a Tarpc-based IPC service for remote device control, a core Rust library for type-safe hardware abstraction, and a command-line interface for scripting and automation.
OpenLogi is an open-source Rust application for managing Logitech HID++ devices. The project provides well-defined API surfaces that allow external code to enumerate peripherals, configure DPI settings, control RGB lighting, and monitor real-time state changes. This guide examines the concrete implementations in the AprilNEA/OpenLogi repository, including the Agent trait definition, the protocol versioning strategy, and the client-server architecture.
Tarpc IPC Service: The Primary Public API
The main entry point for external applications is the Tarpc IPC service, defined in crates/openlogi-ipc/src/ipc.rs. This binary-to-binary Remote Procedure Call (RPC) contract enables GUI clients, scripts, and third-party tools to communicate with the background agent through strongly-typed methods.
The Agent Trait and Protocol Versioning
The service centers on the Agent trait, annotated with #[tarpc::service], which currently operates at protocol version 29 (PROTOCOL_VERSION = 29). The contract guarantees backward compatibility through an append-only policy—new methods are added to the end of the trait without reordering existing definitions, as the on-wire enum indexes are determined by trait method order.
Key methods exposed by the Agent trait include:
protocol_version()– Returns the wire-protocol integer for client-server handshake validationstatus()– RetrievesAgentStatuscontaining hook installation state and accessibility permissionsinventory()– ReturnsVec<DeviceInventory>describing all connected Logitech devicesset_dpi(route, dpi)andread_dpi(route)– Configure pointer sensitivity usingDeviceRouteandDpitypesset_light(route, command)– TransmitLightCommandinstructions for brightness and power controlstart_pairing(selector),pair_device(address),cancel_pairing()– Orchestrate Bolt/Unifying receiver pairing workflowssnapshot()– Atomically fetch complete agent state (status, inventory, and pairing state) in a single RPC callobserve(since)– Long-polling endpoint for reactive state updates; blocks until agent state changes past the providedGenerationobserve_action_ring(since)– Monitor Actions Ring invocations for custom overlay implementationsdeclare_client(kind)– Register client type (Gui,Cli, orOverlay) required for macOS dormancy gating
The trait generates both a client struct (openlogi_ipc::client::AgentClient) and server implementation via Tarpc macros. The client implementation resides in crates/openlogi-ipc/src/client.rs.
Connecting to the Agent via IPC Client
To consume the API, instantiate AgentClient with a LocalSocketTransport (Unix domain socket on Linux/macOS, named pipe on Windows):
use openlogi_ipc::client::AgentClient;
use openlogi_ipc::transport::LocalSocketTransport;
use tarpc::client::Config;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let transport = LocalSocketTransport::new().await?;
let client = Config::default()
.set_timeout(Some(std::time::Duration::from_secs(5)))
.build(transport)
.await?;
// Verify protocol compatibility before operations
let version = client.protocol_version().await?;
assert_eq!(version, openlogi_ipc::PROTOCOL_VERSION);
// Retrieve current agent status
let status = client.status().await?;
println!("Hook installed: {}", status.hook_installed);
Ok(())
}
Modifying Device Settings
The API supports granular hardware control. For example, changing DPI requires constructing the appropriate core types and invoking the RPC method:
use openlogi_core::hid::{DeviceRoute, Dpi};
use openlogi_ipc::client::AgentClient;
async fn set_device_dpi(
client: &AgentClient,
route: DeviceRoute,
dpi: u16
) -> anyhow::Result<()> {
let dpi_val = Dpi::new(dpi);
client.set_dpi(route, dpi_val).await?;
Ok(())
}
The set_dpi method is defined in the Agent trait at crates/openlogi-ipc/src/ipc.rs lines 60-61, while the Dpi type resides in crates/openlogi-core/src/hid/dpi.rs.
Reactive State Observation
For applications requiring real-time updates without polling overhead, the observe method implements long-polling semantics:
use openlogi_ipc::{AgentClient, Observation, Generation};
async fn watch_state(mut client: AgentClient) -> anyhow::Result<()> {
let mut gen: Generation = 0;
loop {
// Blocks until state changes, then returns immediately
let obs: Observation = client.observe(gen).await?;
println!("New generation {}: {:?}", obs.generation, obs.snapshot.status);
gen = obs.generation;
}
}
The Observation struct and observe method are defined in crates/openlogi-ipc/src/ipc.rs. This pattern is used by the official GUI to maintain synchronization with the agent.
Core Rust Library: Platform-Agnostic Types
Beyond the IPC layer, OpenLogi exposes openlogi_core, a pure Rust library containing serializable data structures and hardware abstractions. Located in crates/openlogi-core/src/lib.rs, this library contains no asynchronous I/O or networking code—only types that can be statically linked by other Rust crates or used to implement alternative clients.
Key exported types include:
openlogi_core::device::DeviceInventory– Describes HID++ capabilities and firmware versionsopenlogi_core::hid::DeviceRoute– Hardware addressing scheme for targeting specific devicesopenlogi_core::hid::Dpi– Wrapper type for sensor resolution valuesopenlogi_core::hid::LightCommand– Enum representing on/off toggles and brightness levelsopenlogi_core::binding::{ActionRingIcon, ActionRingSlot}– Visual definitions for the Actions Ring overlay
All types implement serde::Serialize and serde::Deserialize, enabling seamless transmission over the IPC channel or persistence in TOML configuration files.
Command-Line Interface: Scripting API
The CLI binary (openlogi) provides a ready-to-use consumer of the IPC service, implemented with clap in crates/openlogi-cli/src/cmd/mod.rs. Each sub-command parses arguments and forwards them to the IPC client, exposing functionality to shell scripts and automation tools.
Available commands include:
openlogi list– Prints a JSON array ofDeviceInventoryobjects by callingclient.inventory()(seecmd/list.rs)openlogi light <device> on|off|brightness <value>– Maps toset_lightRPC calls defined incmd/light.rsopenlogi diag dpi <device>– Queries current DPI viaread_dpi
Example usage enumerating connected hardware:
$ openlogi list
# => [{"name": "MX Master 3", "route": {...}, " DPI": 1000}, ...]
Summary
- OpenLogi exposes three distinct API layers: a Tarpc IPC service for real-time bidirectional communication, a Core Rust library for hardware type definitions, and a CLI for shell scripting.
- The
Agenttrait incrates/openlogi-ipc/src/ipc.rsdefines the public RPC surface at protocol version 29, maintaining backward compatibility through append-only method signatures. - Core types including
DeviceInventory,Dpi, andLightCommandare defined inopenlogi_coreand exposed viacrates/openlogi-core/src/lib.rs. - The CLI implementation in
crates/openlogi-cli/src/cmd/wraps IPC calls into sub-commands likelist,light, anddiag. - All APIs support comprehensive device lifecycle management: enumeration, DPI configuration, lighting control, pairing workflows, and reactive state observation.
Frequently Asked Questions
What protocol version does OpenLogi use for IPC communication?
OpenLogi currently uses protocol version 29, defined as the constant PROTOCOL_VERSION in crates/openlogi-ipc/src/ipc.rs. The wire protocol maintains backward compatibility only when new methods are appended to the Agent trait; reordering methods would break the on-wire enum indexes, so the project follows strict append-only versioning.
Can I use OpenLogi from programming languages other than Rust?
Direct integration requires speaking the Tarpc protocol over local sockets, which is technically feasible but non-trivial. The most accessible approach for non-Rust code is the CLI interface, which provides JSON output and accepts plain-text arguments. For deeper integration, you would need to implement the Tarpc client protocol (using the definitions in crates/openlogi-ipc/src/ipc.rs) in your target language.
How do I monitor device state changes without polling continuously?
Use the observe(since) method available on AgentClient. This implements server-sent long-polling where the RPC call blocks until the agent's internal Generation counter increments, then returns an Observation containing the complete state snapshot. The official GUI uses this mechanism in crates/openlogi-ipc/src/ipc.rs to achieve reactive updates without network chatter.
Where are the DPI and lighting control types defined?
The hardware abstraction types reside in the core library. Dpi is defined in crates/openlogi-core/src/hid/dpi.rs, while LightCommand and DeviceRoute are located in crates/openlogi-core/src/hid/. These types are re-exported through openlogi_core::hid and are used by both the IPC service implementation and client consumers.
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 →