diff --git a/docs/PRO_X2_SUPERSTRIKE_CLI.md b/docs/PRO_X2_SUPERSTRIKE_CLI.md new file mode 100644 index 00000000..ed767e1b --- /dev/null +++ b/docs/PRO_X2_SUPERSTRIKE_CLI.md @@ -0,0 +1,374 @@ +# Logitech PRO X 2 Superstrike - Solaar CLI Reference + +This document describes all available settings for the Logitech PRO X 2 Superstrike mouse via the Solaar CLI. + +## Device Identification + +| Property | Value | +|----------|-------| +| Name | PRO X 2 Superstrike | +| WPID | 40BD | +| Protocol | HID++ 4.2 | +| Kind | mouse | + +## General CLI Syntax + +```bash +# List all settings for device +solaar config + +# Read a specific setting +solaar config + +# Write a specific setting +solaar config +``` + +The `` can be: +- Device number (e.g., `1`) +- Device name (e.g., `"PRO X 2 Superstrike"`) +- Serial number (e.g., `A1C55DB2`) + +--- + +## Available Settings + +### 1. Onboard Profiles + +Controls whether the device uses its onboard profile or host-controlled settings. + +| Property | Value | +|----------|-------| +| Setting Name | `onboard_profiles` | +| Type | Choice | +| Possible Values | `Disabled`, `Profile 1` | + +**Commands:** + +```bash +# Read current value +solaar config 1 onboard_profiles + +# Set to disabled (allows host control of DPI, report rate, etc.) +solaar config 1 onboard_profiles Disabled + +# Set to Profile 1 (use onboard profile) +solaar config 1 onboard_profiles "Profile 1" +``` + +**Note:** Many settings require `onboard_profiles` to be set to `Disabled` to be effective. + +--- + +### 2. Report Rate + +Controls the frequency of device movement reports. + +| Property | Value | +|----------|-------| +| Setting Name | `report_rate_extended` | +| Type | Choice | +| Possible Values | `8ms`, `4ms`, `2ms`, `1ms`, `500us`, `250us`, `125us` | + +**Commands:** + +```bash +# Read current value +solaar config 1 report_rate_extended + +# Set to 1ms (1000Hz) +solaar config 1 report_rate_extended 1ms + +# Set to 500us (2000Hz) +solaar config 1 report_rate_extended 500us + +# Set to 125us (8000Hz) +solaar config 1 report_rate_extended 125us +``` + +**Polling Rate Reference:** + +| Value | Polling Rate | +|-------|--------------| +| `8ms` | 125 Hz | +| `4ms` | 250 Hz | +| `2ms` | 500 Hz | +| `1ms` | 1000 Hz | +| `500us` | 2000 Hz | +| `250us` | 4000 Hz | +| `125us` | 8000 Hz | + +--- + +### 3. Sensitivity (DPI) + +Controls mouse movement sensitivity. + +| Property | Value | +|----------|-------| +| Setting Name | `dpi_extended` | +| Type | Complex (X, Y, LOD) | +| DPI Range | 100 - 32000 | +| LOD Values | `LOW`, `HIGH` | + +**Commands:** + +```bash +# Read current value +solaar config 1 dpi_extended + +# Set DPI (format: {X:, Y:, LOD:}) +solaar config 1 dpi_extended "{X:800, Y:800, LOD:HIGH}" + +# Set to 1600 DPI +solaar config 1 dpi_extended "{X:1600, Y:1600, LOD:HIGH}" + +# Set different X and Y sensitivity +solaar config 1 dpi_extended "{X:800, Y:1600, LOD:LOW}" +``` + +--- + +## HITS Tuning Settings (Hall-Effect Inductive Trigger Switch) + +These settings control the advanced click behavior of the PRO X 2 Superstrike's hall-effect switches. + +### 4. Actuation Point + +Controls how deep the button must be pressed to register a click. + +| Property | Value | +|----------|-------| +| Setting Name (Left) | `superstrike-tuning_actuation-0` | +| Setting Name (Right) | `superstrike-tuning_actuation-1` | +| Type | Range | +| Range | 1 - 10 | +| Default | 5 | + +**Value Interpretation:** +- `1` = Shallowest (hair trigger, minimal press) +- `10` = Deepest (full press required) + +**Commands:** + +```bash +# Read left button actuation +solaar config 1 superstrike-tuning_actuation-0 + +# Read right button actuation +solaar config 1 superstrike-tuning_actuation-1 + +# Set left button to shallow actuation (hair trigger) +solaar config 1 superstrike-tuning_actuation-0 1 + +# Set left button to deep actuation +solaar config 1 superstrike-tuning_actuation-0 10 + +# Set right button to medium actuation +solaar config 1 superstrike-tuning_actuation-1 5 +``` + +--- + +### 5. Rapid Trigger Level + +Controls the rapid trigger sensitivity, which allows the button to re-actuate quickly after partial release. + +| Property | Value | +|----------|-------| +| Setting Name (Left) | `superstrike-tuning_rapid-trigger-level-0` | +| Setting Name (Right) | `superstrike-tuning_rapid-trigger-level-1` | +| Type | Range | +| Range | 1 - 5 | +| Default | 3 | + +**Value Interpretation:** +- `1` = Fastest (most sensitive, smallest movement to re-trigger) +- `5` = Slowest (least sensitive, larger movement needed) + +**Note:** Rapid trigger cannot be disabled on this device. The minimum level is 1. + +**Commands:** + +```bash +# Read left button rapid trigger level +solaar config 1 superstrike-tuning_rapid-trigger-level-0 + +# Read right button rapid trigger level +solaar config 1 superstrike-tuning_rapid-trigger-level-1 + +# Set left button to fastest rapid trigger +solaar config 1 superstrike-tuning_rapid-trigger-level-0 1 + +# Set left button to slowest rapid trigger +solaar config 1 superstrike-tuning_rapid-trigger-level-0 5 + +# Set right button to medium rapid trigger +solaar config 1 superstrike-tuning_rapid-trigger-level-1 3 +``` + +--- + +### 6. Click Haptics + +Controls the intensity of the haptic feedback when clicking. + +| Property | Value | +|----------|-------| +| Setting Name (Left) | `superstrike-tuning_haptics-0` | +| Setting Name (Right) | `superstrike-tuning_haptics-1` | +| Type | Range | +| Range | 0 - 5 | +| Default | 3 | + +**Value Interpretation:** +- `0` = Off (no haptic feedback) +- `1` = Minimal +- `2` = Light +- `3` = Medium +- `4` = Strong +- `5` = Strongest (maximum haptic feedback) + +**Commands:** + +```bash +# Read left button haptics level +solaar config 1 superstrike-tuning_haptics-0 + +# Read right button haptics level +solaar config 1 superstrike-tuning_haptics-1 + +# Disable haptics on left button +solaar config 1 superstrike-tuning_haptics-0 0 + +# Set left button to maximum haptics +solaar config 1 superstrike-tuning_haptics-0 5 + +# Set right button to medium haptics +solaar config 1 superstrike-tuning_haptics-1 3 +``` + +--- + +## Complete Settings Summary + +| Setting | CLI Name | Type | Range/Values | Button-Specific | +|---------|----------|------|--------------|-----------------| +| Onboard Profiles | `onboard_profiles` | Choice | `Disabled`, `Profile 1` | No | +| Report Rate | `report_rate_extended` | Choice | `8ms` to `125us` | No | +| Sensitivity | `dpi_extended` | Complex | 100-32000 DPI | No | +| Actuation Point | `superstrike-tuning_actuation-{0,1}` | Range | 1-10 | Yes | +| Rapid Trigger | `superstrike-tuning_rapid-trigger-level-{0,1}` | Range | 1-5 | Yes | +| Click Haptics | `superstrike-tuning_haptics-{0,1}` | Range | 0-5 | Yes | + +--- + +## Batch Configuration Examples + +### Gaming Profile (Fast Response) + +```bash +#!/bin/bash +# Gaming profile: fast actuation, sensitive rapid trigger, medium haptics + +solaar config 1 onboard_profiles Disabled +solaar config 1 report_rate_extended 125us +solaar config 1 dpi_extended "{X:800, Y:800, LOD:HIGH}" + +# Left button - hair trigger +solaar config 1 superstrike-tuning_actuation-0 1 +solaar config 1 superstrike-tuning_rapid-trigger-level-0 1 +solaar config 1 superstrike-tuning_haptics-0 3 + +# Right button - hair trigger +solaar config 1 superstrike-tuning_actuation-1 1 +solaar config 1 superstrike-tuning_rapid-trigger-level-1 1 +solaar config 1 superstrike-tuning_haptics-1 3 +``` + +### Productivity Profile (Comfortable) + +```bash +#!/bin/bash +# Productivity profile: deeper actuation, slower rapid trigger, strong haptics + +solaar config 1 onboard_profiles Disabled +solaar config 1 report_rate_extended 1ms +solaar config 1 dpi_extended "{X:1600, Y:1600, LOD:HIGH}" + +# Left button - comfortable click +solaar config 1 superstrike-tuning_actuation-0 7 +solaar config 1 superstrike-tuning_rapid-trigger-level-0 4 +solaar config 1 superstrike-tuning_haptics-0 5 + +# Right button - comfortable click +solaar config 1 superstrike-tuning_actuation-1 7 +solaar config 1 superstrike-tuning_rapid-trigger-level-1 4 +solaar config 1 superstrike-tuning_haptics-1 5 +``` + +### Silent Profile (No Haptics) + +```bash +#!/bin/bash +# Silent profile: no haptic feedback + +solaar config 1 superstrike-tuning_haptics-0 0 +solaar config 1 superstrike-tuning_haptics-1 0 +``` + +--- + +## Programmatic Usage + +### Reading All Settings (JSON-like parsing) + +```bash +# Get all settings as output +solaar config 1 2>/dev/null | grep "^[a-z]" | while read line; do + setting=$(echo "$line" | cut -d'=' -f1 | tr -d ' ') + value=$(echo "$line" | cut -d'=' -f2 | tr -d ' ') + echo "{\"setting\": \"$setting\", \"value\": \"$value\"}" +done +``` + +### Reading a Single Setting Value + +```bash +# Extract just the value +solaar config 1 superstrike-tuning_actuation-0 2>/dev/null | grep "^superstrike" | cut -d'=' -f2 | tr -d ' ' +``` + +### Error Handling + +```bash +# Check if command succeeded +if solaar config 1 superstrike-tuning_actuation-0 5 2>/dev/null; then + echo "Setting applied successfully" +else + echo "Failed to apply setting" +fi +``` + +--- + +## Exit Codes + +| Code | Meaning | +|------|---------| +| 0 | Success | +| 1 | Error (device not found, invalid setting, invalid value) | + +--- + +## Notes + +1. **Device Discovery**: Use `solaar show` to list all connected devices and their indices. + +2. **Persistence**: Settings are saved to `~/.config/solaar/config.yaml` and automatically reapplied when the device reconnects. + +3. **Onboard Profiles**: When `onboard_profiles` is set to `Profile 1`, some settings (DPI, report rate) are controlled by the device's onboard memory and cannot be changed via Solaar. + +4. **HITS Settings**: The actuation, rapid trigger, and haptics settings are stored in the device and persist across reconnections, regardless of the onboard profile setting. + +5. **Button Index**: `0` = Left button, `1` = Right button. diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py index 3d8da83e..fc81d494 100644 --- a/lib/logitech_receiver/descriptors.py +++ b/lib/logitech_receiver/descriptors.py @@ -406,7 +406,6 @@ _D("MX Vertical Wireless Mouse", codename="MX Vertical", protocol=4.5, wpid="407 _D("Wireless Mouse Pebble M350", codename="Pebble", protocol=2.0, wpid="4080") _D("MX Master 3 Wireless Mouse", codename="MX Master 3", protocol=4.5, wpid="4082", btid=0xB023) _D("PRO X Wireless", kind="mouse", codename="PRO X", wpid="4093", usbid=0xC094) -_D("PRO X 2 Superstrike", kind="mouse", codename="PRO X 2 Superstrike", wpid="40BD") _D("G9 Laser Mouse", codename="G9", usbid=0xC048, interface=1, protocol=1.0) _D("G9x Laser Mouse", codename="G9x", usbid=0xC066, interface=1, protocol=1.0) diff --git a/lib/logitech_receiver/hidpp20_constants.py b/lib/logitech_receiver/hidpp20_constants.py index 5a5f15a8..9f55ce21 100644 --- a/lib/logitech_receiver/hidpp20_constants.py +++ b/lib/logitech_receiver/hidpp20_constants.py @@ -76,7 +76,7 @@ class SupportedFeature(IntEnum): REPROG_CONTROLS_V2_2 = 0x1B02 # LogiOptions 2.10.73 features.xml REPROG_CONTROLS_V3 = 0x1B03 REPROG_CONTROLS_V4 = 0x1B04 - SUPERSTRIKE_TUNING = 0x1B0C # PRO X 2 Superstrike HITS tuning (actuation, rapid trigger, haptics) + ANALOG_BUTTONS = 0x1B0C # Analog button tuning (actuation point, rapid trigger, haptics) FULL_KEY_CUSTOMIZATION = 0x1B05 CONTROL_LIST = 0x1B10 SWITCH_SWAPABILITY = 0x1B20 diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 7ee88c84..3a47a961 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -1809,108 +1809,76 @@ class ForceSensing(settings_new.Settings): return setting -# PRO X 2 Superstrike HITS (Hall-Effect Inductive Trigger Switch) settings +# Analog button tuning settings (actuation point, rapid trigger, haptics) -class _SuperstrikeActuationRW(settings.FeatureRW): - """RW for Superstrike actuation point (1-10) per button.""" +class _AnalogButtonActuationRW(settings.FeatureRW): + """RW for analog button actuation point per button.""" - def __init__(self, feature, button_index, max_actuation): - super().__init__(feature, read_fnid=0x20, write_fnid=0x10) # Write is 0x10, not 0x30 - self.button_index = button_index - self.max_actuation = max_actuation # Device's max value (e.g., 40) - - def read(self, device, data_bytes=b""): - res = device.feature_request(self.feature, 0x20, self.button_index) - if not res: - return b"\x05" # default mid-point - # Actuation is in byte 1, scale from device range (1-max) to UI range (1-10) - device_val = res[1] - ui_val = max(1, min(10, round(device_val * 10 / self.max_actuation))) - return bytes([ui_val]) - - def write(self, device, data_bytes): - current = device.feature_request(self.feature, 0x20, self.button_index) - if not current: - return None - # Scale from UI range (1-10) to device range (4-max, step 4 based on observed behavior) - ui_val = data_bytes[0] - device_val = max(4, min(self.max_actuation, round(ui_val * self.max_actuation / 10))) - # Round to nearest multiple of 4 (device seems to quantize) - device_val = ((device_val + 2) // 4) * 4 - # Write using func 0x10: [button_index, actuation, rt, byte3] - return device.feature_request(self.feature, 0x10, self.button_index, device_val, current[2], current[3]) - - -class _SuperstrikeRapidTriggerLevelRW(settings.FeatureRW): - """RW for Superstrike rapid trigger sensitivity (1=fast to 5=slow) per button.""" - - def __init__(self, feature, button_index, max_rt_level): - super().__init__(feature, read_fnid=0x20, write_fnid=0x10) # Write is 0x10 - self.button_index = button_index - self.max_rt_level = max_rt_level # Device's max RT level (e.g., 20) - - def read(self, device, data_bytes=b""): - res = device.feature_request(self.feature, 0x20, self.button_index) - if not res or res[2] == 0: - return b"\x03" # default mid-point if RT disabled - # Scale from device range (1-max) to UI range (1-5) - device_val = res[2] - ui_val = max(1, min(5, round(device_val * 5 / self.max_rt_level))) - return bytes([ui_val]) - - def write(self, device, data_bytes): - current = device.feature_request(self.feature, 0x20, self.button_index) - if not current: - return None - # Scale from UI range (1-5) to device range (1-max) - ui_val = data_bytes[0] - device_val = max(1, min(self.max_rt_level, round(ui_val * self.max_rt_level / 5))) - return device.feature_request(self.feature, 0x10, self.button_index, current[1], device_val, current[3]) - - -class _SuperstrikeHapticsRW(settings.FeatureRW): - """RW for Superstrike click haptics (0-5) per button via TUNING feature byte3. - - Device stores haptics in byte3 of the TUNING response with values 0, 4, 8, 12, 16, 20. - UI range is 0-5 where 0=off and 5=strongest. - """ - - def __init__(self, feature, button_index, max_haptics): + def __init__(self, feature, button_index): super().__init__(feature, read_fnid=0x20, write_fnid=0x10) self.button_index = button_index - self.max_haptics = max_haptics # Device's max haptics value (e.g., 20) def read(self, device, data_bytes=b""): res = device.feature_request(self.feature, 0x20, self.button_index) if not res: - return b"\x03" # default mid-point - # Haptics is in byte 3, scale from device range (0-max) to UI range (0-5) - device_val = res[3] - ui_val = round(device_val * 5 / self.max_haptics) - return bytes([ui_val]) + return b"\x14" # default mid-point + return bytes([res[1]]) def write(self, device, data_bytes): current = device.feature_request(self.feature, 0x20, self.button_index) if not current: return None - # Scale from UI range (0-5) to device range (0-max, step 4) - ui_val = data_bytes[0] - device_val = round(ui_val * self.max_haptics / 5) - # Round to nearest multiple of 4 (device quantizes to 0, 4, 8, 12, 16, 20) - device_val = ((device_val + 2) // 4) * 4 - device_val = min(device_val, self.max_haptics) - # Write: [button_index, actuation, rt, haptics] - return device.feature_request(self.feature, 0x10, self.button_index, current[1], current[2], device_val) + return device.feature_request(self.feature, 0x10, self.button_index, data_bytes[0], current[2], current[3]) -class SuperstrikeTuning_(settings.Setting): - """HITS tuning settings: actuation point, rapid trigger, and haptics configuration.""" +class _AnalogButtonRapidTriggerRW(settings.FeatureRW): + """RW for analog button rapid trigger sensitivity per button.""" - name = "superstrike-tuning_" - label = _("HITS Tuning") - description = _("Configure Hall-Effect Inductive Trigger Switch settings including haptics.") - feature = _F.SUPERSTRIKE_TUNING + def __init__(self, feature, button_index): + super().__init__(feature, read_fnid=0x20, write_fnid=0x10) + self.button_index = button_index + + def read(self, device, data_bytes=b""): + res = device.feature_request(self.feature, 0x20, self.button_index) + if not res: + return b"\x0a" # default mid-point + return bytes([res[2]]) + + def write(self, device, data_bytes): + current = device.feature_request(self.feature, 0x20, self.button_index) + if not current: + return None + return device.feature_request(self.feature, 0x10, self.button_index, current[1], data_bytes[0], current[3]) + + +class _AnalogButtonHapticsRW(settings.FeatureRW): + """RW for analog button click haptics per button.""" + + def __init__(self, feature, button_index): + super().__init__(feature, read_fnid=0x20, write_fnid=0x10) + self.button_index = button_index + + def read(self, device, data_bytes=b""): + res = device.feature_request(self.feature, 0x20, self.button_index) + if not res: + return b"\x0a" # default mid-point + return bytes([res[3]]) + + def write(self, device, data_bytes): + current = device.feature_request(self.feature, 0x20, self.button_index) + if not current: + return None + return device.feature_request(self.feature, 0x10, self.button_index, current[1], current[2], data_bytes[0]) + + +class AnalogButtonTuning(settings.Setting): + """Analog button tuning: actuation point, rapid trigger, and haptics configuration.""" + + name = "analog-button-tuning" + label = _("Analog Button Tuning") + description = _("Configure analog button settings including actuation point, rapid trigger, and haptics.") + feature = _F.ANALOG_BUTTONS @classmethod def build(cls, device): @@ -1931,40 +1899,31 @@ class SuperstrikeTuning_(settings.Setting): button_names = [_("Left Button"), _("Right Button")] all_settings = [] - # Group settings by feature type for better UI organization - - # Actuation Points (UI: 1-10, device: 1-max_actuation) for i in range(button_count): btn_name = button_names[i] if i < len(button_names) else f"Button {i}" - rw_act = _SuperstrikeActuationRW(cls.feature, i, max_actuation) - val_act = settings_validator.RangeValidator(min_value=1, max_value=10) + + rw_act = _AnalogButtonActuationRW(cls.feature, i) + val_act = settings_validator.RangeValidator(min_value=1, max_value=max_actuation) s_act = settings.Setting(device, rw_act, val_act) - s_act.name = f"superstrike-tuning_actuation-{i}" + s_act.name = f"analog-button-tuning_actuation-{i}" s_act.label = f"{btn_name} Actuation Point" - s_act.description = _("Actuation point depth (1=shallow, 10=deep).") + s_act.description = _("Actuation point depth (raw device value).") all_settings.append(s_act) - # Rapid Trigger Levels (UI: 1-5, device: 1-max_rt_level) - # Note: Device doesn't support disabling rapid trigger (rt_level=0 returns error) - for i in range(button_count): - btn_name = button_names[i] if i < len(button_names) else f"Button {i}" - rw_rt_lvl = _SuperstrikeRapidTriggerLevelRW(cls.feature, i, max_rt_level) - val_rt_lvl = settings_validator.RangeValidator(min_value=1, max_value=5) - s_rt_lvl = settings.Setting(device, rw_rt_lvl, val_rt_lvl) - s_rt_lvl.name = f"superstrike-tuning_rapid-trigger-level-{i}" - s_rt_lvl.label = f"{btn_name} Rapid Trigger Level" - s_rt_lvl.description = _("Rapid trigger sensitivity (1=fast, 5=slow).") - all_settings.append(s_rt_lvl) + rw_rt = _AnalogButtonRapidTriggerRW(cls.feature, i) + val_rt = settings_validator.RangeValidator(min_value=1, max_value=max_rt_level) + s_rt = settings.Setting(device, rw_rt, val_rt) + s_rt.name = f"analog-button-tuning_rapid-trigger-{i}" + s_rt.label = f"{btn_name} Rapid Trigger" + s_rt.description = _("Rapid trigger sensitivity (raw device value).") + all_settings.append(s_rt) - # Click Haptics (UI: 0-5, device: 0-max_haptics in steps of 4) - for i in range(button_count): - btn_name = button_names[i] if i < len(button_names) else f"Button {i}" - rw_haptics = _SuperstrikeHapticsRW(cls.feature, i, max_haptics) - val_haptics = settings_validator.RangeValidator(min_value=0, max_value=5) + rw_haptics = _AnalogButtonHapticsRW(cls.feature, i) + val_haptics = settings_validator.RangeValidator(min_value=0, max_value=max_haptics) s_haptics = settings.Setting(device, rw_haptics, val_haptics) - s_haptics.name = f"superstrike-tuning_haptics-{i}" + s_haptics.name = f"analog-button-tuning_haptics-{i}" s_haptics.label = f"{btn_name} Click Haptics" - s_haptics.description = _("Click haptic feedback intensity (0=off, 5=strongest).") + s_haptics.description = _("Click haptic feedback intensity (raw device value, 0=off).") all_settings.append(s_haptics) return all_settings if all_settings else None @@ -2093,7 +2052,7 @@ SETTINGS: list[settings.Setting] = [ Gesture2Gestures, # working Gesture2Divert, Gesture2Params, # working - SuperstrikeTuning_, + AnalogButtonTuning, HapticLevel, PlayHapticWaveForm, Sidetone,