Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Prime mice #169

Merged
merged 8 commits into from
Nov 28, 2021
Merged
1 change: 1 addition & 0 deletions doc/devices/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Supported Devices
./aerox3.rst
./kanav2.rst
./kinzuv2.rst
./prime.rst
./rival3.rst
./rival95.rst
./rival100.rst
Expand Down
56 changes: 56 additions & 0 deletions doc/devices/prime.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
SteelSeries Prime
===================


Supported Models
----------------

.. rivalcfg_device_family:: prime


Command-Line Usage
------------------

.. rivalcfg_device_cli:: prime


Sensitivity (DPI)
-----------------

This mouse supports up to 5 sensitivity presets. You can define them like this:

::

rivalcfg --sensitivity 800 # one preset
rivalcfg --sensitivity 800,1600 # two presets

You can switch preset using the button under the mouse wheel.

.. NOTE::

When you set the sensitivity through the CLI, the selected preset always
back to the first one.


.. NOTE:: From Python API, you can pass an ``int``, a ``tuple`` or a ``list``
as parameter. You are also able to change the currently selected preset::

mouse.sensitivity(800)
mouse.sensitivity("800, 1600")
mouse.sensitivity([800, 1600])
# select the second preset (1600 dpi)
mouse.sensitivity([800, 1600, 2000, 4000], selected_preset=2)


Colors
------

This mouse supports colors. Various formats are supported.

.. include:: ./_colors.rst


Python API
----------

TODO
1 change: 1 addition & 0 deletions rivalcfg/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
from . import aerox3 # noqa: F401
from . import kanav2 # noqa: F401
from . import kinzuv2 # noqa: F401
from . import prime # noqa: F401
from . import rival3 # noqa: F401
from . import rival95 # noqa: F401
from . import rival100 # noqa: F401
Expand Down
89 changes: 89 additions & 0 deletions rivalcfg/devices/prime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from .. import usbhid


profile = {
"name": "SteelSeries Prime",
"models": [
{
"name": "SteelSeries Prime",
"vendor_id": 0x1038,
"product_id": 0x182E,
"endpoint": 0,
},
],
"settings": {
"sensitivity": {
"label": "Sensitivity presets",
"description": "Set sensitivity preset (DPI)",
"cli": ["-s", "--sensitivity"],
"report_type": usbhid.HID_REPORT_TYPE_OUTPUT,
"command": [0x61],
"value_type": "multidpi_range",
"input_range": [50, 18000, 50],
"output_range": [1, 0x0168, 1],
"dpi_length_byte": 2,
"count_mode": "number",
"first_preset": 0,
"max_preset_count": 5,
"default": "400, 800, 1200, 2400, 3200",
},
"polling_rate": {
"label": "Polling rate",
"description": "Set polling rate (Hz)",
"cli": ["-p", "--polling-rate"],
"report_type": usbhid.HID_REPORT_TYPE_OUTPUT,
"command": [0x5D],
"value_type": "choice",
"choices": {
125: 0x04,
250: 0x03,
500: 0x02,
1000: 0x01,
},
"default": 1000,
},
"color": {
"label": "Wheel LED color",
"description": "Set the color of the wheel LED",
"cli": ["-c", "--color"],
"report_type": usbhid.HID_REPORT_TYPE_OUTPUT,
"command": [0x62, 0x01],
"command_suffix": [
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xFF,
],
"value_type": "rgbcolor",
"default": "#FF5200",
},
"wheel_brightness": {
flozz marked this conversation as resolved.
Show resolved Hide resolved
"label": "Wheel LED brightness",
"description": "Set the brightness of the wheel LED",
"cli": ["-l", "--led-brightness"],
"report_type": usbhid.HID_REPORT_TYPE_OUTPUT,
"command": [0x5F],
"value_type": "range",
"input_range": [0, 256, 1],
"output_range": [0, 256, 1],
"range_length_byte": 2,
"default": 256,
},
},
"save_command": {
"report_type": usbhid.HID_REPORT_TYPE_OUTPUT,
"command": [0x59],
},
}
6 changes: 3 additions & 3 deletions rivalcfg/handlers/multidpi_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
import argparse

from ..helpers import merge_bytes, uint_to_little_endian_bytearray
from .range import process_value as range_process_value
from .range import process_range


def process_value(setting_info, value, selected_preset=None):
Expand Down Expand Up @@ -154,8 +154,8 @@ def process_value(setting_info, value, selected_preset=None):
output_values = []

for dpi in dpis:
value = range_process_value(setting_info, dpi)
value = uint_to_little_endian_bytearray(value[0], dpi_length)
value = process_range(setting_info, dpi)
value = uint_to_little_endian_bytearray(value, dpi_length)
output_values = merge_bytes(output_values, value)

# Count
Expand Down
30 changes: 25 additions & 5 deletions rivalcfg/handlers/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
"""


from ..helpers import uint_to_little_endian_bytearray


def matches_value_in_range(range_start, range_stop, range_step, value):
"""Helper function that matches the value with the nearest value in the
given range.
Expand Down Expand Up @@ -128,14 +131,14 @@ def custom_range(start, stop, step):
i += step


def process_value(setting_info, value):
"""Called by the :class:`rivalcfg.mouse.Mouse` class when processing a
"range" type setting.
def process_range(setting_info, value):
"""Called by the "range" functions to process 'value' with the specified
range settings in 'setting_info'.

:param dict setting_info: The information dict of the setting from the
device profile.
:param value: The input value.
:rtype: list[int]
:rtype: int
"""
input_range = list(
range(
Expand All @@ -161,7 +164,24 @@ def process_value(setting_info, value):
setting_info["input_range"][2],
int(value),
)
return [output_range[input_range.index(matched_value)]]
return output_range[input_range.index(matched_value)]


def process_value(setting_info, value):
"""Called by the :class:`rivalcfg.mouse.Mouse` class when processing a
"range" type setting.

:param dict setting_info: The information dict of the setting from the
device profile.
:param value: The input value.
:rtype: list[int]
"""
range_length = 1
if "range_length_byte" in setting_info:
range_length = setting_info["range_length_byte"]
return uint_to_little_endian_bytearray(
process_range(setting_info, value), range_length
)


def add_cli_option(cli_parser, setting_name, setting_info):
Expand Down
87 changes: 87 additions & 0 deletions test/devices/test_prime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import pytest

from rivalcfg import usbhid
from rivalcfg import mouse
from rivalcfg.devices import prime
from rivalcfg import mouse_settings


class TestDevice(object):
@pytest.fixture
def mouse(self):
settings = mouse_settings.FakeMouseSettings(
0x1038,
0xBAAD,
prime.profile,
)
return mouse.Mouse(
usbhid.FakeDevice(),
prime.profile,
settings,
)

@pytest.mark.parametrize(
"value,expected_hid_report",
[
(100, b"\x02\x00\x61\x01\x00\x02\x00"),
("100", b"\x02\x00\x61\x01\x00\x02\x00"),
("500,2500", b"\x02\x00\x61\x02\x00\x0A\x00\x32\x00"),
(
"500,2500,11050,18000",
b"\x02\x00\x61\x04\x00\x0A\x00\x32\x00\xDD\x00\x68\x01",
),
],
)
def test_set_sensitivity(self, mouse, value, expected_hid_report):
mouse.set_sensitivity(value)
mouse._hid_device.bytes.seek(0)
hid_report = mouse._hid_device.bytes.read()
assert hid_report == expected_hid_report

@pytest.mark.parametrize(
"value,expected_hid_report",
[
(125, b"\x02\x00\x5D\x04"),
(250, b"\x02\x00\x5D\x03"),
(500, b"\x02\x00\x5D\x02"),
(1000, b"\x02\x00\x5D\x01"),
],
)
def test_set_polling_rate(self, mouse, value, expected_hid_report):
mouse.set_polling_rate(value)
mouse._hid_device.bytes.seek(0)
hid_report = mouse._hid_device.bytes.read()
assert hid_report == expected_hid_report

@pytest.mark.parametrize(
"value,expected_hid_report",
[
(
"#ABCDEF",
b"\x02\x00\x62\x01\xAB\xCD\xEF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF",
),
(
"red",
b"\x02\x00\x62\x01\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF",
),
],
)
def test_set_color(self, mouse, value, expected_hid_report):
mouse.set_color(value)
mouse._hid_device.bytes.seek(0)
hid_report = mouse._hid_device.bytes.read()
assert hid_report == expected_hid_report

@pytest.mark.parametrize(
"value,expected_hid_report",
[
(0, b"\x02\x00\x5F\x00\x00"),
(256, b"\x02\x00\x5F\x00\x01"),
(111, b"\x02\x00\x5F\x6F\x00"),
],
)
def test_set_wheel_brightness(self, mouse, value, expected_hid_report):
mouse.set_wheel_brightness(value)
mouse._hid_device.bytes.seek(0)
hid_report = mouse._hid_device.bytes.read()
assert hid_report == expected_hid_report
21 changes: 21 additions & 0 deletions test/handlers/test_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ def setting_info(self):
"output_range": [2, 40, 2],
}

@pytest.fixture
def setting_info2(self):
return {
"value_type": "range",
"input_range": [1, 20, 1],
"output_range": [1000, 40000, 2000],
"range_length_byte": 2,
}

@pytest.mark.parametrize(
"input_,expected_output",
[
Expand All @@ -28,6 +37,18 @@ def setting_info(self):
def test_range_values(self, setting_info, input_, expected_output):
assert range_.process_value(setting_info, input_) == [expected_output]

@pytest.mark.parametrize(
"input_,expected_output",
[
(1, [0xE8, 0x03]),
(2, [0xB8, 0x0B]),
(20, [0x58, 0x98]),
(14, [0x78, 0x69]),
],
)
def test_range_values2(self, setting_info2, input_, expected_output):
assert range_.process_value(setting_info2, input_) == expected_output


class TestAddCliOption(object):
@pytest.fixture
Expand Down