# How to Change DPI Settings for Logitech Devices with OpenLogi: A Complete Guide

> Learn how to change DPI settings for Logitech devices using OpenLogi. This guide covers the CLI diagnostic tool and Rust API for full control over your mouse sensitivity.

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

---

**OpenLogi enables DPI control through the HID++ protocol stack, exposing both a CLI diagnostic tool and Rust API that support Adjustable DPI (0x2201) and Extended Adjustable DPI (0x2202) features found in modern Logitech mice.**

OpenLogi is an open-source framework for managing Logitech peripherals outside the official ecosystem. When you need to change DPI settings for Logitech devices with OpenLogi, the project provides a unified abstraction through the `openlogi-hid` crate, handling protocol negotiation and device capability detection automatically.

## Understanding the HID++ DPI Features

Logitech mice implement DPI control through two distinct HID++ feature IDs that OpenLogi detects and handles transparently.

### Adjustable DPI (0x2201)

The **Adjustable DPI** feature (`0x2201`) represents single-sensor DPI as a flat list of discrete values. This is the legacy implementation found in older Logitech gaming mice and productivity devices. According to the OpenLogi source code, this feature provides basic read/write access to sensor DPI without independent axis control.

### Extended Adjustable DPI (0x2202)

Newer devices expose **Extended Adjustable DPI** (`0x2202`), which supports independent X/Y DPI configuration, lift-off distance adjustment, and stepped ranges. In [`crates/openlogi-hid/src/write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/write/dpi.rs), the code handles this feature by reading current parameters and writing full `SetDpiParameters` structs to preserve device-specific settings like lift-off distance when modifying DPI values.

### Feature Detection Logic

The internal `DpiFeature` enum probes for `0x2201` first, falling back to `0x2202` if necessary. As implemented in [`openlogi-hid/src/write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hid/src/write/dpi.rs) (lines 45-60), this ensures newer mice exposing only the extended feature remain fully operable while maintaining compatibility with legacy hardware.

## Core API for DPI Control

The `openlogi-hid` crate exposes three primary functions for DPI management, all operating on a `DeviceRoute` obtained from the agent.

### Reading Current DPI Values

**`openlogi_hid::get_dpi(&route)`** returns the current DPI for sensor 0. This function wraps the HID++ `get_sensor_dpi` and `get_sensor_dpi_parameters` calls, as seen in [`openlogi-hid/src/write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hid/src/write/dpi.rs) (lines 86-92). Both the diagnostic CLI and GUI components use this to display real-time DPI values.

```rust
use openlogi_hid;

let current_dpi = openlogi_hid::get_dpi(&route).await?;
println!("Current DPI: {}", current_dpi);

```

### Writing New DPI Settings

**`openlogi_hid::set_dpi(&route, target)`** configures a new DPI value. Internally, this delegates to `DpiFeature::set_dpi`, which either calls `AdjustableDpiFeature::set_sensor_dpi` for the simple feature or, for extended features, reads current parameters before writing a full parameter struct (lines 111-124 in [`openlogi-hid/src/write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hid/src/write/dpi.rs)). This approach preserves Y-axis DPI and lift-off distance settings when changing values.

```rust
use openlogi_hid;

// Set DPI to 800 (must be a supported value)
openlogi_hid::set_dpi(&route, 800.into()).await?;

```

### Querying Device Capabilities

**`openlogi_hid::DpiCapabilities`** describes supported DPI values through methods like `values()`, `contains()`, and `adjacent_test_target()`. The function `expand_dpi_ranges` (lines 58-84) parses raw HID++ responses from extended feature devices to populate these capabilities, allowing applications to validate user input against hardware limits before attempting writes.

```rust
use openlogi_hid::DpiCapabilities;

let info = openlogi_hid::get_dpi_info(&route).await?;
let caps = info.capabilities;

// Check if 1600 DPI is supported
if caps.contains(1600) {
    println!("1600 DPI is available");
}

```

## Using the CLI Diagnostic Tool

The **`openlogi diag dpi`** command provides immediate access to DPI functions without writing code. Located in [`crates/openlogi-cli/src/cmd/diag/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-cli/src/cmd/diag/dpi.rs), this diagnostic tool performs a complete read-write-verify cycle.

### Basic DPI Inspection

Running the command without arguments displays the current DPI and all supported values:

```bash
openlogi diag dpi

```

This executes the `DpiSummaryDisplay` logic (lines 31-37) and filters for devices exposing either DPI feature ID (lines 26-30).

### Setting Specific DPI Values

Use the `--target` flag to specify a desired DPI. The CLI determines the target either from this flag or by selecting a nearby supported value that differs from the current setting (lines 38-52):

```bash

# Set DPI to 800

openlogi diag dpi --target 800

# Target a specific device by name

openlogi diag dpi --device "MX Master 3" --target 1200

```

### Verification and Restoration

The diagnostic command writes the target DPI, reads it back, and verifies the operation succeeded (lines 61-85). It handles "snapped" values where the device maps requests to the nearest supported DPI. Finally, it restores the original DPI to leave the device in its initial state (lines 89-94), making the tool safe for testing.

## Programmatic DPI Control in Rust

For integration into custom applications, implement the following pattern using the `openlogi-hid` crate:

```rust
use openlogi_hid::{self, DpiCapabilities};
use openlogi_ipc::DeviceRoute;
use anyhow;

async fn change_dpi(route: &DeviceRoute, desired: u16) -> anyhow::Result<()> {
    // 1️⃣ Query capabilities
    let caps = openlogi_hid::get_dpi_info(route).await?.capabilities;
    
    // Validate the desired DPI is supported
    let dpi = caps
        .values()
        .into_iter()
        .find(|&v| v == desired)
        .ok_or_else(|| anyhow::anyhow!("Unsupported DPI {desired}"))?;

    // 2️⃣ Write DPI
    openlogi_hid::set_dpi(route, dpi.into()).await?;

    // 3️⃣ Verify the change
    let after = openlogi_hid::get_dpi(route).await?;
    println!("DPI now set to {after}");
    Ok(())
}

```

To enumerate available options for a UI slider:

```rust
let info = openlogi_hid::get_dpi_info(&route).await?;
let values: Vec<u16> = info.capabilities.values();
println!("Supported DPI values: {:?}", values);

```

## Architecture and Data Flow

OpenLogi's DPI control follows a layered architecture spanning multiple crates:

1. **UI/CLI Layer**: `openlogi-cli` (command line) and `openlogi-desktop` (GUI) receive user input
2. **IPC Layer**: `openlogi-ipc` routes requests to the system agent
3. **Agent Layer**: `openlogi-agent` (see [`src/tray.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/tray.rs)) processes requests and manages device connections
4. **HID++ Layer**: `openlogi-hid` (see [`src/write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/src/write/dpi.rs)) translates API calls into HID++ protocol messages

The pure data types (`Dpi`, `DpiCapabilities`, `DpiInfo`) defined in [`crates/openlogi-core/src/hid/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/dpi.rs) ensure type safety across all layers, while the settings UI in [`openlogi-desktop/src/windows/settings/general.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-desktop/src/windows/settings/general.rs) demonstrates real-world integration of the DPI slider using these same APIs.

## Summary

- OpenLogi supports both **Adjustable DPI (0x2201)** and **Extended Adjustable DPI (0x2202)** HID++ features through automatic detection
- Use **`openlogi_hid::get_dpi()`** and **`openlogi_hid::set_dpi()`** for programmatic control, or **`openlogi diag dpi`** for CLI management
- The **`DpiCapabilities`** struct validates supported values before writing, preventing invalid configuration attempts
- The CLI diagnostic tool automatically verifies writes and restores original values, making it safe for testing
- Source implementations in [`crates/openlogi-hid/src/write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/write/dpi.rs) handle protocol details like preserving lift-off distance and Y-axis settings during updates

## Frequently Asked Questions

### What HID++ features does OpenLogi support for DPI control?

OpenLogi implements two features: **Adjustable DPI (0x2201)** for basic single-sensor devices, and **Extended Adjustable DPI (0x2202)** for advanced mice supporting independent X/Y axes and lift-off distance. The `DpiFeature` enum in [`openlogi-hid/src/write/dpi.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hid/src/write/dpi.rs) automatically probes for `0x2201` first, then falls back to `0x2202` if necessary.

### How do I verify my DPI change was applied successfully?

The **`openlogi diag dpi`** command performs automatic verification by reading the DPI value immediately after writing and comparing it to the target. For programmatic use, call `openlogi_hid::get_dpi(&route).await?` after `set_dpi()` to confirm the device reports the expected value, handling cases where the mouse "snaps" to the nearest supported DPI.

### Can I set different DPI values for X and Y axes?

Yes, but only on devices exposing the **Extended Adjustable DPI (0x2202)** feature. The basic `set_dpi()` API sets both axes to the same value, but the underlying implementation in `DpiFeature::set_dpi` preserves existing Y-axis values when modifying X-axis DPI for extended feature devices. For explicit independent control, you would need to construct a full `SetDpiParameters` struct with distinct X and Y values.

### What happens if I request an unsupported DPI value?

The device will "snap" to the nearest supported value. OpenLogi handles this gracefully: the `DpiCapabilities` API provides `values()` and `contains()` methods to check support before writing, while the CLI tool reports the actual value post-write so you can see what the device selected. Always validate against `DpiCapabilities` first, or use `adjacent_test_target()` to find valid nearby values.