How OpenLogi Implements DPI Cycling Using HID++ Extended Adjustable DPI (0x2202)
OpenLogi handles DPI cycling by intercepting the hardware's 0xD2 task and translating it into HID++ 0x2202 feature calls that read the sensor's DPI list, increment the index with wrap-around logic, and write the new value via set_dpi.
The OpenLogi project provides an open-source Rust implementation for managing Logitech HID++ devices. When a user presses the physical DPI switch button, the software leverages the Extended Adjustable DPI feature (0x2202) to cycle through predefined sensitivity levels. This architecture separates hardware event handling from the actual sensor configuration, ensuring precise control over gaming and productivity mice.
Understanding the HID++ DPI Switch Task
OpenLogi recognizes DPI switch events through a specific task ID that the device sends when the configured button triggers. This mapping bridges the physical button press to the software's internal command system.
Task ID 0xD2 Definition
In crates/openlogi-hidpp/src/feature/reprog_controls/task_ids.rs, the constant DPI_SWITCH is defined as TaskId(0xD2). This hexadecimal identifier represents the specific HID++ task that devices emit when the DPI switch button activates. The task serves as the entry point for all DPI cycling operations within the OpenLogi stack.
Control ID 0xFD Mapping
The corresponding control mapping resides in crates/openlogi-hidpp/src/feature/reprog_controls/control_ids.rs, where the DPI_SWITCH control is assigned ID 0xFD. This separation of task IDs and control IDs allows the reprogramming controls feature to distinguish between the raw hardware signal and the logical function it represents.
The Extended Adjustable DPI Feature (0x2202)
OpenLogi implements DPI manipulation through the Extended Adjustable DPI feature rather than the basic Adjustable DPI (0x2201). The core implementation lives in crates/openlogi-hidpp/src/feature/extended_dpi.rs, which exposes three primary capabilities:
get_capabilities– Discovers how many DPI levels the sensor supports and identifies whether the device uses explicit value lists or stepped ranges.get_dpi_list– Retrieves the complete array of supported DPI values indexed by sensor.set_dpi– Writes a new DPI value to the device’s persistent storage and applies it immediately.set_dpi_led– Controls the on-device DPI indicator LED to reflect the current level visually.
DPI Cycling Execution Flow
The cycling mechanism follows a strict pipeline from hardware interrupt to UI update, managed primarily within the agent core.
Receiving the Hardware Event
When the device transmits the 0xD2 task, the HID++ message decoder (decode_event) in the agent core identifies it as a DPI_SWITCH event. The handler in crates/openlogi-agent-core/src/commands/dpi_cycle.rs immediately claims the task to prevent further propagation and begins the cycling sequence.
Reading Sensor Capabilities
The handler queries the device’s Extended Adjustable DPI feature to obtain the sensor's capability list. It calls get_dpi_list(sensor_index) to retrieve the vector of supported DPI values and determines the current active index by comparing the present DPI against this list.
Calculating the Next DPI Level
OpenLogi employs modulo arithmetic to ensure seamless cycling. The algorithm finds the current DPI’s position in the list, increments the index by one, and applies modulo division against the list length to wrap back to the first entry after reaching the maximum. The handler then invokes set_dpi(sensor_index, dpi_list[next]) to commit the change to hardware.
Synchronizing with the UI
After successfully updating the sensor, the agent core dispatches a message through crates/openlogi-ipc/src/ipc.rs to notify the GUI layer. This IPC message carries the new DPI value, allowing the interface to update its display and trigger the set_dpi_led method to illuminate the correct LED indicator on the physical device.
Practical Implementation in Rust
The following simplified Rust code demonstrates the core cycling logic as implemented in OpenLogi’s agent core:
// 1. Task constant (0xD2) – DPI switch
pub const DPI_SWITCH: TaskId = TaskId(0xD2);
// 2. Handling the task in the agent core (simplified)
fn handle_dpi_switch(device: &mut Device) -> Result<()> {
// Obtain the extended DPI feature for the device
let ext_dpi = device.get_feature::<ExtendedDpiFeature>()?;
// Get the list of supported DPI values
let dpi_list = ext_dpi.get_dpi_list(sensor_index)?;
// Determine the current DPI and compute the next index
let current = ext_dpi.get_current_dpi(sensor_index)?;
let next = (dpi_list.iter().position(|&v| v == current).unwrap() + 1) % dpi_list.len();
// Apply the next DPI value
ext_dpi.set_dpi(sensor_index, dpi_list[next])?;
// Notify the UI via IPC (omitted for brevity)
Ok(())
}
This implementation relies on the ExtendedDpiFeature struct defined in crates/openlogi-hidpp/src/feature/extended_dpi.rs, which abstracts the raw HID++ protocol bytes into type-safe Rust methods.
Summary
- OpenLogi uses HID++ feature 0x2202 (Extended Adjustable DPI), not the basic 0x2201 feature, for full cycling capabilities.
- The Task ID 0xD2 in
task_ids.rsidentifies DPI switch hardware events, while Control ID 0xFD maps the logical function. - The cycling logic resides in
crates/openlogi-agent-core/src/commands/dpi_cycle.rs, utilizingget_dpi_listandset_dpifrom the extended DPI feature. - Index wrap-around uses modulo arithmetic to ensure seamless cycling from the last DPI level back to the first.
- IPC messages in
crates/openlogi-ipc/src/ipc.rssynchronize hardware state with the GUI and control on-device LED indicators.
Frequently Asked Questions
What is the difference between HID++ feature 0x2201 and 0x2202?
Feature 0x2201 is the basic Adjustable DPI protocol, while 0x2202 is the Extended Adjustable DPI feature. OpenLogi specifically implements 0x2202 because it provides richer sensor capabilities discovery, explicit DPI value lists, and LED control methods that the basic feature lacks.
How does OpenLogi handle DPI wrapping when reaching the last level?
The agent core calculates the next index using modulo arithmetic: (current_index + 1) % dpi_list.len(). This ensures that pressing the DPI switch at the highest level immediately wraps to the first entry in the sensor’s supported list without requiring special case handling.
Which file contains the DPI switch task definition?
The DPI_SWITCH task constant is defined in crates/openlogi-hidpp/src/feature/reprog_controls/task_ids.rs as TaskId(0xD2). The corresponding control ID 0xFD is declared in crates/openlogi-hidpp/src/feature/reprog_controls/control_ids.rs.
How does the UI know when the DPI changes?
After the agent core successfully calls set_dpi in crates/openlogi-agent-core/src/commands/dpi_cycle.rs, it transmits an IPC message through crates/openlogi-ipc/src/ipc.rs. The GUI process listens to this channel and updates its displayed DPI value while potentially invoking set_dpi_led to synchronize the physical device’s indicator lights.
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 →