# How OpenLogi Implements DPI Cycling Using HID++ Extended Adjustable DPI (0x2202)

> OpenLogi implements DPI cycling using HID++ extended adjustable DPI (0x2202) by intercepting hardware tasks and translating them into feature calls. Learn how OpenLogi manages sensor DPI.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-09-11

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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:

```rust
// 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/task_ids.rs) identifies 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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/commands/dpi_cycle.rs), utilizing `get_dpi_list` and `set_dpi` from 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.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) synchronize 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent-core/src/commands/dpi_cycle.rs), it transmits an IPC message through [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/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.