Refactor: Introduce Feature enum

Convert Feature NamedInts to SupportedFeature integer enum.

Related #2273
This commit is contained in:
MattHag 2024-10-13 17:58:17 +02:00 committed by Peter F. Patel-Schneider
parent 11e7cbde69
commit 0cd9c0c9b5
14 changed files with 432 additions and 376 deletions

View File

@ -31,11 +31,11 @@ from . import exceptions
from . import hidpp10
from . import hidpp10_constants
from . import hidpp20
from . import hidpp20_constants
from . import settings
from . import settings_templates
from .common import Alert
from .common import Battery
from .hidpp20_constants import SupportedFeature
logger = logging.getLogger(__name__)
@ -295,9 +295,9 @@ class Device:
@property
def led_effects(self):
if not self._led_effects and self.online and self.protocol >= 2.0:
if hidpp20_constants.FEATURE.COLOR_LED_EFFECTS in self.features:
if SupportedFeature.COLOR_LED_EFFECTS in self.features:
self._led_effects = hidpp20.LEDEffectsInfo(self)
elif hidpp20_constants.FEATURE.RGB_EFFECTS in self.features:
elif SupportedFeature.RGB_EFFECTS in self.features:
self._led_effects = hidpp20.RGBEffectsInfo(self)
return self._led_effects
@ -435,7 +435,7 @@ class Device:
was_active is None
or not was_active
or push
and (not self.features or hidpp20_constants.FEATURE.WIRELESS_DEVICE_STATUS not in self.features)
and (not self.features or SupportedFeature.WIRELESS_DEVICE_STATUS not in self.features)
):
if logger.isEnabledFor(logging.INFO):
logger.info("%s pushing device settings %s", self, self.settings)

View File

@ -47,7 +47,7 @@ else:
import evdev
from .common import NamedInt
from .hidpp20 import FEATURE
from .hidpp20 import SupportedFeature
from .special_keys import CONTROL
gi.require_version("Gdk", "3.0") # isort:skip
@ -434,7 +434,7 @@ def simulate_scroll(dx, dy):
def thumb_wheel_up(f, r, d, a):
global thumb_wheel_displacement
if f != FEATURE.THUMB_WHEEL or r != 0:
if f != SupportedFeature.THUMB_WHEEL or r != 0:
return False
if a is None:
return signed(d[0:2]) < 0 and signed(d[0:2])
@ -447,7 +447,7 @@ def thumb_wheel_up(f, r, d, a):
def thumb_wheel_down(f, r, d, a):
global thumb_wheel_displacement
if f != FEATURE.THUMB_WHEEL or r != 0:
if f != SupportedFeature.THUMB_WHEEL or r != 0:
return False
if a is None:
return signed(d[0:2]) > 0 and signed(d[0:2])
@ -460,9 +460,9 @@ def thumb_wheel_down(f, r, d, a):
def charging(f, r, d, _a):
if (
(f == FEATURE.BATTERY_STATUS and r == 0 and 1 <= d[2] <= 4)
or (f == FEATURE.BATTERY_VOLTAGE and r == 0 and d[2] & (1 << 7))
or (f == FEATURE.UNIFIED_BATTERY and r == 0 and 1 <= d[2] <= 3)
(f == SupportedFeature.BATTERY_STATUS and r == 0 and 1 <= d[2] <= 4)
or (f == SupportedFeature.BATTERY_VOLTAGE and r == 0 and d[2] & (1 << 7))
or (f == SupportedFeature.UNIFIED_BATTERY and r == 0 and 1 <= d[2] <= 3)
):
return 1
else:
@ -470,30 +470,30 @@ def charging(f, r, d, _a):
TESTS = {
"crown_right": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and d[1] < 128 and d[1], False],
"crown_left": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and d[1] >= 128 and 256 - d[1], False],
"crown_right_ratchet": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and d[2] < 128 and d[2], False],
"crown_left_ratchet": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and d[2] >= 128 and 256 - d[2], False],
"crown_tap": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and d[5] == 0x01 and d[5], False],
"crown_start_press": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and d[6] == 0x01 and d[6], False],
"crown_end_press": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and d[6] == 0x05 and d[6], False],
"crown_pressed": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and 0x01 <= d[6] <= 0x04 and d[6], False],
"crown_right": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[1] < 128 and d[1], False],
"crown_left": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[1] >= 128 and 256 - d[1], False],
"crown_right_ratchet": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[2] < 128 and d[2], False],
"crown_left_ratchet": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[2] >= 128 and 256 - d[2], False],
"crown_tap": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[5] == 0x01 and d[5], False],
"crown_start_press": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[6] == 0x01 and d[6], False],
"crown_end_press": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[6] == 0x05 and d[6], False],
"crown_pressed": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and 0x01 <= d[6] <= 0x04 and d[6], False],
"thumb_wheel_up": [thumb_wheel_up, True],
"thumb_wheel_down": [thumb_wheel_down, True],
"lowres_wheel_up": [
lambda f, r, d, a: f == FEATURE.LOWRES_WHEEL and r == 0 and signed(d[0:1]) > 0 and signed(d[0:1]),
lambda f, r, d, a: f == SupportedFeature.LOWRES_WHEEL and r == 0 and signed(d[0:1]) > 0 and signed(d[0:1]),
False,
],
"lowres_wheel_down": [
lambda f, r, d, a: f == FEATURE.LOWRES_WHEEL and r == 0 and signed(d[0:1]) < 0 and signed(d[0:1]),
lambda f, r, d, a: f == SupportedFeature.LOWRES_WHEEL and r == 0 and signed(d[0:1]) < 0 and signed(d[0:1]),
False,
],
"hires_wheel_up": [
lambda f, r, d, a: f == FEATURE.HIRES_WHEEL and r == 0 and signed(d[1:3]) > 0 and signed(d[1:3]),
lambda f, r, d, a: f == SupportedFeature.HIRES_WHEEL and r == 0 and signed(d[1:3]) > 0 and signed(d[1:3]),
False,
],
"hires_wheel_down": [
lambda f, r, d, a: f == FEATURE.HIRES_WHEEL and r == 0 and signed(d[1:3]) < 0 and signed(d[1:3]),
lambda f, r, d, a: f == SupportedFeature.HIRES_WHEEL and r == 0 and signed(d[1:3]) < 0 and signed(d[1:3]),
False,
],
"charging": [charging, False],
@ -738,12 +738,13 @@ class MouseProcess(Condition):
class Feature(Condition):
def __init__(self, feature, warn=True):
if not (isinstance(feature, str) and feature in FEATURE):
def __init__(self, feature: str, warn: bool = True):
try:
self.feature = SupportedFeature[feature]
except KeyError:
self.feature = None
if warn:
logger.warning("rule Feature argument not name of a feature: %s", feature)
self.feature = None
self.feature = FEATURE[feature]
def __str__(self):
return "Feature: " + str(self.feature)
@ -1052,7 +1053,7 @@ class MouseGesture(Condition):
def evaluate(self, feature, notification: HIDPPNotification, device, last_result):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self)
if feature == FEATURE.MOUSE_GESTURE:
if feature == SupportedFeature.MOUSE_GESTURE:
d = notification.data
data = struct.unpack("!" + (int(len(d) / 2) * "h"), d)
data_offset = 1
@ -1501,7 +1502,7 @@ def process_notification(device, notification: HIDPPNotification, feature) -> No
key_down, key_up = None, None
# need to keep track of keys that are down to find a new key down
if notification.address == 0x00:
if feature == FEATURE.REPROG_CONTROLS_V4:
if feature == SupportedFeature.REPROG_CONTROLS_V4:
new_keys_down = struct.unpack("!4H", notification.data[:8])
for key in new_keys_down:
if key and key not in keys_down:
@ -1511,7 +1512,7 @@ def process_notification(device, notification: HIDPPNotification, feature) -> No
key_up = key
keys_down = new_keys_down
# and also G keys down
elif feature == FEATURE.GKEY:
elif feature == SupportedFeature.GKEY:
new_g_keys_down = struct.unpack("<I", notification.data[:4])[0]
for i in range(32):
if new_g_keys_down & (0x01 << i) and not g_keys_down & (0x01 << i):
@ -1520,7 +1521,7 @@ def process_notification(device, notification: HIDPPNotification, feature) -> No
key_up = CONTROL["G" + str(i + 1)]
g_keys_down = new_g_keys_down
# and also M keys down
elif feature == FEATURE.MKEYS:
elif feature == SupportedFeature.MKEYS:
new_m_keys_down = struct.unpack("!1B", notification.data[:1])[0]
for i in range(1, 9):
if new_m_keys_down & (0x01 << (i - 1)) and not m_keys_down & (0x01 << (i - 1)):
@ -1529,7 +1530,7 @@ def process_notification(device, notification: HIDPPNotification, feature) -> No
key_up = CONTROL["M" + str(i)]
m_keys_down = new_m_keys_down
# and also MR key
elif feature == FEATURE.MR:
elif feature == SupportedFeature.MR:
new_mr_key_down = struct.unpack("!1B", notification.data[:1])[0]
if not mr_key_down and new_mr_key_down:
key_down = CONTROL["MR"]
@ -1537,7 +1538,7 @@ def process_notification(device, notification: HIDPPNotification, feature) -> No
key_up = CONTROL["MR"]
mr_key_down = new_mr_key_down
# keep track of thumb wheel movement
elif feature == FEATURE.THUMB_WHEEL:
elif feature == SupportedFeature.THUMB_WHEEL:
if notification.data[4] <= 0x01: # when wheel starts, zero out last movement
thumb_wheel_displacement = 0
thumb_wheel_displacement += signed(notification.data[0:2])

View File

@ -14,6 +14,7 @@
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import annotations
import logging
import socket
@ -44,9 +45,9 @@ from .hidpp20_constants import CHARGE_STATUS
from .hidpp20_constants import CHARGE_TYPE
from .hidpp20_constants import DEVICE_KIND
from .hidpp20_constants import ERROR
from .hidpp20_constants import FEATURE
from .hidpp20_constants import FIRMWARE_KIND
from .hidpp20_constants import GESTURE
from .hidpp20_constants import SupportedFeature
logger = logging.getLogger(__name__)
@ -95,7 +96,7 @@ class FeaturesArray(dict):
return False
if self.count > 0:
return True
reply = self.device.request(0x0000, struct.pack("!H", FEATURE.FEATURE_SET))
reply = self.device.request(0x0000, struct.pack("!H", SupportedFeature.FEATURE_SET))
if reply is not None:
fs_index = reply[0]
if fs_index:
@ -105,14 +106,14 @@ class FeaturesArray(dict):
return False
else:
self.count = count[0] + 1 # ROOT feature not included in count
self[FEATURE.ROOT] = 0
self[FEATURE.FEATURE_SET] = fs_index
self[SupportedFeature.ROOT] = 0
self[SupportedFeature.FEATURE_SET] = fs_index
return True
else:
self.supported = False
return False
def get_feature(self, index: int) -> Optional[NamedInt]:
def get_feature(self, index: int) -> SupportedFeature | None:
feature = self.inverse.get(index)
if feature is not None:
return feature
@ -120,9 +121,13 @@ class FeaturesArray(dict):
feature = self.inverse.get(index)
if feature is not None:
return feature
response = self.device.feature_request(FEATURE.FEATURE_SET, 0x10, index)
response = self.device.feature_request(SupportedFeature.FEATURE_SET, 0x10, index)
if response:
feature = FEATURE[struct.unpack("!H", response[:2])[0]]
data = struct.unpack("!H", response[:2])[0]
try:
feature = SupportedFeature(data)
except ValueError:
feature = f"unknown:{data:04X}"
self[feature] = index
self.version[feature] = response[3]
return feature
@ -290,7 +295,7 @@ class ReprogrammableKeyV4(ReprogrammableKey):
def _getCidReporting(self):
try:
mapped_data = self._device.feature_request(
FEATURE.REPROG_CONTROLS_V4,
SupportedFeature.REPROG_CONTROLS_V4,
0x20,
*tuple(struct.pack("!H", self._cid)),
)
@ -373,7 +378,7 @@ class ReprogrammableKeyV4(ReprogrammableKey):
pkt = tuple(struct.pack("!HBH", self._cid, bfield & 0xFF, remap))
# TODO: to fully support version 4 of REPROG_CONTROLS_V4, append `(bfield >> 8) & 0xff` here.
# But older devices might behave oddly given that byte, so we don't send it.
ret = self._device.feature_request(FEATURE.REPROG_CONTROLS_V4, 0x30, *pkt)
ret = self._device.feature_request(SupportedFeature.REPROG_CONTROLS_V4, 0x30, *pkt)
if ret is None or struct.unpack("!BBBBB", ret[:5]) != pkt and logger.isEnabledFor(logging.DEBUG):
logger.debug(f"REPROG_CONTROLS_v4 setCidReporting on device {self._device} didn't echo request packet.")
@ -434,13 +439,13 @@ class PersistentRemappableAction:
def remap(self, data_bytes):
cid = common.int2bytes(self._cid, 2)
if common.bytes2int(data_bytes) == special_keys.KEYS_Default: # map back to default
self._device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x50, cid, 0xFF)
self._device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x50, cid, 0xFF)
self._device.remap_keys._query_key(self.index)
return self._device.remap_keys.keys[self.index].data_bytes
else:
self.actionId, self.remapped, self._modifierMask = struct.unpack("!BHB", data_bytes)
self.cidStatus = 0x01
self._device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x40, cid, 0xFF, data_bytes)
self._device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x40, cid, 0xFF, data_bytes)
return True
@ -451,10 +456,10 @@ class KeysArray:
assert device is not None
self.device = device
self.lock = threading.Lock()
if FEATURE.REPROG_CONTROLS_V4 in self.device.features:
self.keyversion = FEATURE.REPROG_CONTROLS_V4
elif FEATURE.REPROG_CONTROLS_V2 in self.device.features:
self.keyversion = FEATURE.REPROG_CONTROLS_V2
if SupportedFeature.REPROG_CONTROLS_V4 in self.device.features:
self.keyversion = SupportedFeature.REPROG_CONTROLS_V4
elif SupportedFeature.REPROG_CONTROLS_V2 in self.device.features:
self.keyversion = SupportedFeature.REPROG_CONTROLS_V2
else:
if logger.isEnabledFor(logging.ERROR):
logger.error(f"Trying to read keys on device {device} which has no REPROG_CONTROLS(_VX) support.")
@ -515,7 +520,7 @@ class KeysArrayV2(KeysArray):
def _query_key(self, index: int):
if index < 0 or index >= len(self.keys):
raise IndexError(index)
keydata = self.device.feature_request(FEATURE.REPROG_CONTROLS, 0x10, index)
keydata = self.device.feature_request(SupportedFeature.REPROG_CONTROLS, 0x10, index)
if keydata:
cid, tid, flags = struct.unpack("!HHB", keydata[:5])
self.keys[index] = ReprogrammableKey(self.device, index, cid, tid, flags)
@ -531,7 +536,7 @@ class KeysArrayV4(KeysArrayV2):
def _query_key(self, index: int):
if index < 0 or index >= len(self.keys):
raise IndexError(index)
keydata = self.device.feature_request(FEATURE.REPROG_CONTROLS_V4, 0x10, index)
keydata = self.device.feature_request(SupportedFeature.REPROG_CONTROLS_V4, 0x10, index)
if keydata:
cid, tid, flags1, pos, group, gmask, flags2 = struct.unpack("!HHBBBBB", keydata[:9])
flags = flags1 | (flags2 << 8)
@ -552,7 +557,7 @@ class KeysArrayPersistent(KeysArray):
@property
def capabilities(self):
if self._capabilities is None and self.device.online:
capabilities = self.device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x00)
capabilities = self.device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x00)
assert capabilities, "Oops, persistent remappable key capabilities cannot be retrieved!"
self._capabilities = struct.unpack("!H", capabilities[:2])[0] # flags saying what the mappings are possible
return self._capabilities
@ -560,11 +565,11 @@ class KeysArrayPersistent(KeysArray):
def _query_key(self, index: int):
if index < 0 or index >= len(self.keys):
raise IndexError(index)
keydata = self.device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x20, index, 0xFF)
keydata = self.device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x20, index, 0xFF)
if keydata:
key = struct.unpack("!H", keydata[:2])[0]
mapped_data = self.device.feature_request(
FEATURE.PERSISTENT_REMAPPABLE_ACTION,
SupportedFeature.PERSISTENT_REMAPPABLE_ACTION,
0x30,
key >> 8,
key & 0xFF,
@ -708,7 +713,7 @@ class Gesture:
def enabled(self): # is the gesture enabled?
if self._enabled is None and self.index is not None:
offset, mask = self.enable_offset_mask()
result = self._device.feature_request(FEATURE.GESTURE_2, 0x10, offset, 0x01, mask)
result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x10, offset, 0x01, mask)
self._enabled = bool(result[0] & mask) if result else None
return self._enabled
@ -717,13 +722,15 @@ class Gesture:
return None
if self.index is not None:
offset, mask = self.enable_offset_mask()
reply = self._device.feature_request(FEATURE.GESTURE_2, 0x20, offset, 0x01, mask, mask if enable else 0x00)
reply = self._device.feature_request(
SupportedFeature.GESTURE_2, 0x20, offset, 0x01, mask, mask if enable else 0x00
)
return reply
def diverted(self): # is the gesture diverted?
if self._diverted is None and self.diversion_index is not None:
offset, mask = self.diversion_offset_mask()
result = self._device.feature_request(FEATURE.GESTURE_2, 0x30, offset, 0x01, mask)
result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x30, offset, 0x01, mask)
self._diverted = bool(result[0] & mask) if result else None
return self._diverted
@ -733,7 +740,7 @@ class Gesture:
if self.diversion_index is not None:
offset, mask = self.diversion_offset_mask()
reply = self._device.feature_request(
FEATURE.GESTURE_2,
SupportedFeature.GESTURE_2,
0x40,
offset,
0x01,
@ -776,7 +783,7 @@ class Param:
return self._value if self._value is not None else self.read()
def read(self): # returns the bytes for the parameter
result = self._device.feature_request(FEATURE.GESTURE_2, 0x70, self.index, 0xFF)
result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x70, self.index, 0xFF)
if result:
self._value = common.bytes2int(result[: self.size])
return self._value
@ -788,14 +795,14 @@ class Param:
return self._default_value
def _read_default(self):
result = self._device.feature_request(FEATURE.GESTURE_2, 0x60, self.index, 0xFF)
result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x60, self.index, 0xFF)
if result:
self._default_value = common.bytes2int(result[: self.size])
return self._default_value
def write(self, bytes):
self._value = bytes
return self._device.feature_request(FEATURE.GESTURE_2, 0x80, self.index, bytes, 0xFF)
return self._device.feature_request(SupportedFeature.GESTURE_2, 0x80, self.index, bytes, 0xFF)
def __str__(self):
return str(self.param)
@ -820,7 +827,7 @@ class Spec:
def read(self):
try:
value = self._device.feature_request(FEATURE.GESTURE_2, 0x50, self.id, 0xFF)
value = self._device.feature_request(SupportedFeature.GESTURE_2, 0x50, self.id, 0xFF)
except exceptions.FeatureCallError: # some calls produce an error (notably spec 5 multiplier on K400Plus)
if logger.isEnabledFor(logging.WARNING):
logger.warning(
@ -849,7 +856,7 @@ class Gestures:
field_high = 0x00
while field_high != 0x01: # end of fields
# retrieve the next eight fields
fields = device.feature_request(FEATURE.GESTURE_2, 0x00, index >> 8, index & 0xFF)
fields = device.feature_request(SupportedFeature.GESTURE_2, 0x00, index >> 8, index & 0xFF)
if not fields:
break
for offset in range(8):
@ -907,7 +914,7 @@ class Backlight:
"""Information about the current settings of x1982 Backlight2 v3, but also works for previous versions"""
def __init__(self, device):
response = device.feature_request(FEATURE.BACKLIGHT2, 0x00)
response = device.feature_request(SupportedFeature.BACKLIGHT2, 0x00)
if not response:
raise exceptions.FeatureCallError(msg="No reply from device.")
self.device = device
@ -923,7 +930,7 @@ class Backlight:
self.options = (self.options & 0x07) | (self.mode << 3)
level = self.level if self.mode == 0x3 else 0
data_bytes = struct.pack("<BBBBHHH", self.enabled, self.options, 0xFF, level, self.dho, self.dhi, self.dpow)
return self.device.feature_request(FEATURE.BACKLIGHT2, 0x10, data_bytes)
return self.device.feature_request(SupportedFeature.BACKLIGHT2, 0x10, data_bytes)
class LEDParam:
@ -1067,12 +1074,12 @@ class LEDZoneInfo: # effects that a zone can do
class LEDEffectsInfo: # effects that the LEDs can do, using COLOR_LED_EFFECTS
def __init__(self, device):
self.device = device
info = device.feature_request(FEATURE.COLOR_LED_EFFECTS, 0x00)
info = device.feature_request(SupportedFeature.COLOR_LED_EFFECTS, 0x00)
self.count, _, capabilities = struct.unpack("!BHH", info[0:5])
self.readable = capabilities & 0x1
self.zones = []
for i in range(0, self.count):
self.zones.append(LEDZoneInfo(FEATURE.COLOR_LED_EFFECTS, 0x10, 0, 0x20, device, i))
self.zones.append(LEDZoneInfo(SupportedFeature.COLOR_LED_EFFECTS, 0x10, 0, 0x20, device, i))
def to_command(self, index, setting):
return self.zones[index].to_command(setting)
@ -1085,12 +1092,12 @@ class LEDEffectsInfo: # effects that the LEDs can do, using COLOR_LED_EFFECTS
class RGBEffectsInfo(LEDEffectsInfo): # effects that the LEDs can do using RGB_EFFECTS
def __init__(self, device):
self.device = device
info = device.feature_request(FEATURE.RGB_EFFECTS, 0x00, 0xFF, 0xFF, 0x00)
info = device.feature_request(SupportedFeature.RGB_EFFECTS, 0x00, 0xFF, 0xFF, 0x00)
_, _, self.count, _, capabilities = struct.unpack("!BBBHH", info[0:7])
self.readable = capabilities & 0x1
self.zones = []
for i in range(0, self.count):
self.zones.append(LEDZoneInfo(FEATURE.RGB_EFFECTS, 0x00, 1, 0x00, device, i))
self.zones.append(LEDZoneInfo(SupportedFeature.RGB_EFFECTS, 0x00, 1, 0x00, device, i))
ButtonBehaviors = common.NamedInts(MacroExecute=0x0, MacroStop=0x1, MacroStopAll=0x2, Send=0x8, Function=0x9)
@ -1310,23 +1317,23 @@ class OnboardProfiles:
def get_profile_headers(cls, device):
i = 0
headers = []
chunk = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x50, 0, 0, 0, i)
chunk = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x50, 0, 0, 0, i)
s = 0x00
if chunk[0:4] == b"\x00\x00\x00\x00" or chunk[0:4] == b"\xff\xff\xff\xff": # look in ROM instead
chunk = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x50, 0x01, 0, 0, i)
chunk = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x50, 0x01, 0, 0, i)
s = 0x01
while chunk[0:2] != b"\xff\xff":
sector, enabled = struct.unpack("!HB", chunk[0:3])
headers.append((sector, enabled))
i += 1
chunk = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x50, s, 0, 0, i * 4)
chunk = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x50, s, 0, 0, i * 4)
return headers
@classmethod
def from_device(cls, device):
if not device.online: # wake the device up if necessary
device.ping()
response = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x00)
response = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x00)
memory, profile, _macro = struct.unpack("!BBB", response[0:3])
if memory != 0x01 or profile > 0x04:
return
@ -1366,11 +1373,11 @@ class OnboardProfiles:
bytes = b""
o = 0
while o < s - 15:
chunk = dev.feature_request(FEATURE.ONBOARD_PROFILES, 0x50, sector >> 8, sector & 0xFF, o >> 8, o & 0xFF)
chunk = dev.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x50, sector >> 8, sector & 0xFF, o >> 8, o & 0xFF)
bytes += chunk
o += 16
chunk = dev.feature_request(
FEATURE.ONBOARD_PROFILES,
SupportedFeature.ONBOARD_PROFILES,
0x50,
sector >> 8,
sector & 0xFF,
@ -1385,12 +1392,12 @@ class OnboardProfiles:
rbs = OnboardProfiles.read_sector(device, s, len(bs))
if rbs[:-2] == bs[:-2]:
return False
device.feature_request(FEATURE.ONBOARD_PROFILES, 0x60, s >> 8, s & 0xFF, 0, 0, len(bs) >> 8, len(bs) & 0xFF)
device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x60, s >> 8, s & 0xFF, 0, 0, len(bs) >> 8, len(bs) & 0xFF)
o = 0
while o < len(bs) - 1:
device.feature_request(FEATURE.ONBOARD_PROFILES, 0x70, bs[o : o + 16])
device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x70, bs[o : o + 16])
o += 16
device.feature_request(FEATURE.ONBOARD_PROFILES, 0x80)
device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x80)
return True
def write(self, device):
@ -1449,13 +1456,13 @@ class Hidpp20:
:returns: a list of FirmwareInfo tuples, ordered by firmware layer.
"""
count = device.feature_request(FEATURE.DEVICE_FW_VERSION)
count = device.feature_request(SupportedFeature.DEVICE_FW_VERSION)
if count:
count = ord(count[:1])
fw = []
for index in range(0, count):
fw_info = device.feature_request(FEATURE.DEVICE_FW_VERSION, 0x10, index)
fw_info = device.feature_request(SupportedFeature.DEVICE_FW_VERSION, 0x10, index)
if fw_info:
level = ord(fw_info[:1]) & 0x0F
if level == 0 or level == 1:
@ -1475,7 +1482,7 @@ class Hidpp20:
def get_ids(self, device):
"""Reads a device's ids (unit and model numbers)"""
ids = device.feature_request(FEATURE.DEVICE_FW_VERSION)
ids = device.feature_request(SupportedFeature.DEVICE_FW_VERSION)
if ids:
unitId = ids[1:5]
modelId = ids[7:13]
@ -1495,7 +1502,7 @@ class Hidpp20:
:returns: a string describing the device type, or ``None`` if the device is
not available or does not support the ``DEVICE_NAME`` feature.
"""
kind = device.feature_request(FEATURE.DEVICE_NAME, 0x20)
kind = device.feature_request(SupportedFeature.DEVICE_NAME, 0x20)
if kind:
kind = ord(kind[:1])
try:
@ -1509,13 +1516,13 @@ class Hidpp20:
:returns: a string with the device name, or ``None`` if the device is not
available or does not support the ``DEVICE_NAME`` feature.
"""
name_length = device.feature_request(FEATURE.DEVICE_NAME)
name_length = device.feature_request(SupportedFeature.DEVICE_NAME)
if name_length:
name_length = ord(name_length[:1])
name = b""
while len(name) < name_length:
fragment = device.feature_request(FEATURE.DEVICE_NAME, 0x10, len(name))
fragment = device.feature_request(SupportedFeature.DEVICE_NAME, 0x10, len(name))
if fragment:
name += fragment[: name_length - len(name)]
else:
@ -1530,13 +1537,13 @@ class Hidpp20:
:returns: a string with the device name, or ``None`` if the device is not
available or does not support the ``DEVICE_NAME`` feature.
"""
name_length = device.feature_request(FEATURE.DEVICE_FRIENDLY_NAME)
name_length = device.feature_request(SupportedFeature.DEVICE_FRIENDLY_NAME)
if name_length:
name_length = ord(name_length[:1])
name = b""
while len(name) < name_length:
fragment = device.feature_request(FEATURE.DEVICE_FRIENDLY_NAME, 0x10, len(name))
fragment = device.feature_request(SupportedFeature.DEVICE_FRIENDLY_NAME, 0x10, len(name))
if fragment:
name += fragment[1 : name_length - len(name) + 1]
else:
@ -1546,27 +1553,27 @@ class Hidpp20:
return name.decode("utf-8")
def get_battery_status(self, device: Device):
report = device.feature_request(FEATURE.BATTERY_STATUS)
report = device.feature_request(SupportedFeature.BATTERY_STATUS)
if report:
return decipher_battery_status(report)
def get_battery_unified(self, device: Device):
report = device.feature_request(FEATURE.UNIFIED_BATTERY, 0x10)
report = device.feature_request(SupportedFeature.UNIFIED_BATTERY, 0x10)
if report is not None:
return decipher_battery_unified(report)
def get_battery_voltage(self, device: Device):
report = device.feature_request(FEATURE.BATTERY_VOLTAGE)
report = device.feature_request(SupportedFeature.BATTERY_VOLTAGE)
if report is not None:
return decipher_battery_voltage(report)
def get_adc_measurement(self, device: Device):
try: # this feature call produces an error for headsets that are connected but inactive
report = device.feature_request(FEATURE.ADC_MEASUREMENT)
report = device.feature_request(SupportedFeature.ADC_MEASUREMENT)
if report is not None:
return decipher_adc_measurement(report)
except exceptions.FeatureCallError:
return FEATURE.ADC_MEASUREMENT if FEATURE.ADC_MEASUREMENT in device.features else None
return SupportedFeature.ADC_MEASUREMENT if SupportedFeature.ADC_MEASUREMENT in device.features else None
def get_battery(self, device, feature):
"""Return battery information - feature, approximate level, next, charging, voltage
@ -1588,39 +1595,39 @@ class Hidpp20:
def get_keys(self, device: Device):
# TODO: add here additional variants for other REPROG_CONTROLS
count = None
if FEATURE.REPROG_CONTROLS_V2 in device.features:
count = device.feature_request(FEATURE.REPROG_CONTROLS_V2)
if SupportedFeature.REPROG_CONTROLS_V2 in device.features:
count = device.feature_request(SupportedFeature.REPROG_CONTROLS_V2)
return KeysArrayV2(device, ord(count[:1]))
elif FEATURE.REPROG_CONTROLS_V4 in device.features:
count = device.feature_request(FEATURE.REPROG_CONTROLS_V4)
elif SupportedFeature.REPROG_CONTROLS_V4 in device.features:
count = device.feature_request(SupportedFeature.REPROG_CONTROLS_V4)
return KeysArrayV4(device, ord(count[:1]))
return None
def get_remap_keys(self, device: Device):
count = device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x10)
count = device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x10)
if count:
return KeysArrayPersistent(device, ord(count[:1]))
def get_gestures(self, device: Device):
if getattr(device, "_gestures", None) is not None:
return device._gestures
if FEATURE.GESTURE_2 in device.features:
if SupportedFeature.GESTURE_2 in device.features:
return Gestures(device)
def get_backlight(self, device: Device):
if getattr(device, "_backlight", None) is not None:
return device._backlight
if FEATURE.BACKLIGHT2 in device.features:
if SupportedFeature.BACKLIGHT2 in device.features:
return Backlight(device)
def get_profiles(self, device: Device):
if getattr(device, "_profiles", None) is not None:
return device._profiles
if FEATURE.ONBOARD_PROFILES in device.features:
if SupportedFeature.ONBOARD_PROFILES in device.features:
return OnboardProfiles.from_device(device)
def get_mouse_pointer_info(self, device: Device):
pointer_info = device.feature_request(FEATURE.MOUSE_POINTER)
pointer_info = device.feature_request(SupportedFeature.MOUSE_POINTER)
if pointer_info:
dpi, flags = struct.unpack("!HB", pointer_info[:3])
acceleration = ("none", "low", "med", "high")[flags & 0x3]
@ -1634,7 +1641,7 @@ class Hidpp20:
}
def get_vertical_scrolling_info(self, device: Device):
vertical_scrolling_info = device.feature_request(FEATURE.VERTICAL_SCROLLING)
vertical_scrolling_info = device.feature_request(SupportedFeature.VERTICAL_SCROLLING)
if vertical_scrolling_info:
roller, ratchet, lines = struct.unpack("!BBB", vertical_scrolling_info[:3])
roller_type = (
@ -1650,13 +1657,13 @@ class Hidpp20:
return {"roller": roller_type, "ratchet": ratchet, "lines": lines}
def get_hi_res_scrolling_info(self, device: Device):
hi_res_scrolling_info = device.feature_request(FEATURE.HI_RES_SCROLLING)
hi_res_scrolling_info = device.feature_request(SupportedFeature.HI_RES_SCROLLING)
if hi_res_scrolling_info:
mode, resolution = struct.unpack("!BB", hi_res_scrolling_info[:2])
return mode, resolution
def get_pointer_speed_info(self, device: Device):
pointer_speed_info = device.feature_request(FEATURE.POINTER_SPEED)
pointer_speed_info = device.feature_request(SupportedFeature.POINTER_SPEED)
if pointer_speed_info:
pointer_speed_hi, pointer_speed_lo = struct.unpack("!BB", pointer_speed_info[:2])
# if pointer_speed_lo > 0:
@ -1664,16 +1671,16 @@ class Hidpp20:
return pointer_speed_hi + pointer_speed_lo / 256
def get_lowres_wheel_status(self, device: Device):
lowres_wheel_status = device.feature_request(FEATURE.LOWRES_WHEEL)
lowres_wheel_status = device.feature_request(SupportedFeature.LOWRES_WHEEL)
if lowres_wheel_status:
wheel_flag = struct.unpack("!B", lowres_wheel_status[:1])[0]
wheel_reporting = ("HID", "HID++")[wheel_flag & 0x01]
return wheel_reporting
def get_hires_wheel(self, device: Device):
caps = device.feature_request(FEATURE.HIRES_WHEEL, 0x00)
mode = device.feature_request(FEATURE.HIRES_WHEEL, 0x10)
ratchet = device.feature_request(FEATURE.HIRES_WHEEL, 0x030)
caps = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x00)
mode = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x10)
ratchet = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x030)
if caps and mode and ratchet:
# Parse caps
@ -1697,7 +1704,7 @@ class Hidpp20:
return multi, has_invert, has_ratchet, inv, res, target, ratchet
def get_new_fn_inversion(self, device: Device):
state = device.feature_request(FEATURE.NEW_FN_INVERSION, 0x00)
state = device.feature_request(SupportedFeature.NEW_FN_INVERSION, 0x00)
if state:
inverted, default_inverted = struct.unpack("!BB", state[:2])
inverted = (inverted & 0x01) != 0
@ -1705,18 +1712,18 @@ class Hidpp20:
return inverted, default_inverted
def get_host_names(self, device: Device):
state = device.feature_request(FEATURE.HOSTS_INFO, 0x00)
state = device.feature_request(SupportedFeature.HOSTS_INFO, 0x00)
host_names = {}
if state:
capability_flags, _ignore, numHosts, currentHost = struct.unpack("!BBBB", state[:4])
if capability_flags & 0x01: # device can get host names
for host in range(0, numHosts):
hostinfo = device.feature_request(FEATURE.HOSTS_INFO, 0x10, host)
hostinfo = device.feature_request(SupportedFeature.HOSTS_INFO, 0x10, host)
_ignore, status, _ignore, _ignore, nameLen, _ignore = struct.unpack("!BBBBBB", hostinfo[:6])
name = ""
remaining = nameLen
while remaining > 0:
name_piece = device.feature_request(FEATURE.HOSTS_INFO, 0x30, host, nameLen - remaining)
name_piece = device.feature_request(SupportedFeature.HOSTS_INFO, 0x30, host, nameLen - remaining)
if name_piece:
name += name_piece[2 : 2 + min(remaining, 14)].decode()
remaining = max(0, remaining - 14)
@ -1735,62 +1742,64 @@ class Hidpp20:
currentName = bytearray(currentName, "utf-8")
if logger.isEnabledFor(logging.INFO):
logger.info("Setting host name to %s", name)
state = device.feature_request(FEATURE.HOSTS_INFO, 0x00)
state = device.feature_request(SupportedFeature.HOSTS_INFO, 0x00)
if state:
flags, _ignore, _ignore, currentHost = struct.unpack("!BBBB", state[:4])
if flags & 0x02:
hostinfo = device.feature_request(FEATURE.HOSTS_INFO, 0x10, currentHost)
hostinfo = device.feature_request(SupportedFeature.HOSTS_INFO, 0x10, currentHost)
_ignore, _ignore, _ignore, _ignore, _ignore, maxNameLen = struct.unpack("!BBBBBB", hostinfo[:6])
if name[:maxNameLen] == currentName[:maxNameLen] and False:
return True
length = min(maxNameLen, len(name))
chunk = 0
while chunk < length:
response = device.feature_request(FEATURE.HOSTS_INFO, 0x40, currentHost, chunk, name[chunk : chunk + 14])
response = device.feature_request(
SupportedFeature.HOSTS_INFO, 0x40, currentHost, chunk, name[chunk : chunk + 14]
)
if not response:
return False
chunk += 14
return True
def get_onboard_mode(self, device: Device):
state = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x20)
state = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x20)
if state:
mode = struct.unpack("!B", state[:1])[0]
return mode
def set_onboard_mode(self, device: Device, mode):
state = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x10, mode)
state = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x10, mode)
return state
def get_polling_rate(self, device: Device):
state = device.feature_request(FEATURE.REPORT_RATE, 0x10)
state = device.feature_request(SupportedFeature.REPORT_RATE, 0x10)
if state:
rate = struct.unpack("!B", state[:1])[0]
return str(rate) + "ms"
else:
rates = ["8ms", "4ms", "2ms", "1ms", "500us", "250us", "125us"]
state = device.feature_request(FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE, 0x20)
state = device.feature_request(SupportedFeature.EXTENDED_ADJUSTABLE_REPORT_RATE, 0x20)
if state:
rate = struct.unpack("!B", state[:1])[0]
return rates[rate]
def get_remaining_pairing(self, device: Device):
result = device.feature_request(FEATURE.REMAINING_PAIRING, 0x0)
result = device.feature_request(SupportedFeature.REMAINING_PAIRING, 0x0)
if result:
result = struct.unpack("!B", result[:1])[0]
FEATURE._fallback = lambda x: f"unknown:{x:04X}"
SupportedFeature._fallback = lambda x: f"unknown:{x:04X}"
return result
def config_change(self, device: Device, configuration, no_reply=False):
return device.feature_request(FEATURE.CONFIG_CHANGE, 0x10, configuration, no_reply=no_reply)
return device.feature_request(SupportedFeature.CONFIG_CHANGE, 0x10, configuration, no_reply=no_reply)
battery_functions = {
FEATURE.BATTERY_STATUS: Hidpp20.get_battery_status,
FEATURE.BATTERY_VOLTAGE: Hidpp20.get_battery_voltage,
FEATURE.UNIFIED_BATTERY: Hidpp20.get_battery_unified,
FEATURE.ADC_MEASUREMENT: Hidpp20.get_adc_measurement,
SupportedFeature.BATTERY_STATUS: Hidpp20.get_battery_status,
SupportedFeature.BATTERY_VOLTAGE: Hidpp20.get_battery_voltage,
SupportedFeature.UNIFIED_BATTERY: Hidpp20.get_battery_unified,
SupportedFeature.ADC_MEASUREMENT: Hidpp20.get_adc_measurement,
}
@ -1807,7 +1816,7 @@ def decipher_battery_status(report: FixedBytes5) -> Tuple[Any, Battery]:
logger.debug(
"battery status %s%% charged, next %s%%, status %s", battery_discharge_level, battery_discharge_next_level, status
)
return FEATURE.BATTERY_STATUS, Battery(battery_discharge_level, battery_discharge_next_level, status, None)
return SupportedFeature.BATTERY_STATUS, Battery(battery_discharge_level, battery_discharge_next_level, status, None)
def decipher_battery_voltage(report):
@ -1845,7 +1854,7 @@ def decipher_battery_voltage(report):
charge_lvl,
charge_type,
)
return FEATURE.BATTERY_VOLTAGE, Battery(charge_lvl, None, status, voltage)
return SupportedFeature.BATTERY_VOLTAGE, Battery(charge_lvl, None, status, voltage)
def decipher_battery_unified(report):
@ -1869,7 +1878,7 @@ def decipher_battery_unified(report):
else:
level = BatteryLevelApproximation.EMPTY
return FEATURE.UNIFIED_BATTERY, Battery(discharge if discharge else level, None, status, None)
return SupportedFeature.UNIFIED_BATTERY, Battery(discharge if discharge else level, None, status, None)
def decipher_adc_measurement(report):
@ -1882,4 +1891,4 @@ def decipher_adc_measurement(report):
break
if flags & 0x01:
status = BatteryStatus.RECHARGING if flags & 0x02 else BatteryStatus.DISCHARGING
return FEATURE.ADC_MEASUREMENT, Battery(charge_level, None, status, adc)
return SupportedFeature.ADC_MEASUREMENT, Battery(charge_level, None, status, adc)

View File

@ -14,6 +14,7 @@
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from enum import IntEnum
from .common import NamedInts
@ -25,127 +26,131 @@ from .common import NamedInts
A particular device might not support all these features, and may support other
unknown features as well.
"""
FEATURE = NamedInts(
ROOT=0x0000,
FEATURE_SET=0x0001,
FEATURE_INFO=0x0002,
class SupportedFeature(IntEnum):
ROOT = 0x0000
FEATURE_SET = 0x0001
FEATURE_INFO = 0x0002
# Common
DEVICE_FW_VERSION=0x0003,
DEVICE_UNIT_ID=0x0004,
DEVICE_NAME=0x0005,
DEVICE_GROUPS=0x0006,
DEVICE_FRIENDLY_NAME=0x0007,
KEEP_ALIVE=0x0008,
CONFIG_CHANGE=0x0020,
CRYPTO_ID=0x0021,
TARGET_SOFTWARE=0x0030,
WIRELESS_SIGNAL_STRENGTH=0x0080,
DFUCONTROL_LEGACY=0x00C0,
DFUCONTROL_UNSIGNED=0x00C1,
DFUCONTROL_SIGNED=0x00C2,
DFUCONTROL=0x00C3,
DFU=0x00D0,
BATTERY_STATUS=0x1000,
BATTERY_VOLTAGE=0x1001,
UNIFIED_BATTERY=0x1004,
CHARGING_CONTROL=0x1010,
LED_CONTROL=0x1300,
FORCE_PAIRING=0x1500,
GENERIC_TEST=0x1800,
DEVICE_RESET=0x1802,
OOBSTATE=0x1805,
CONFIG_DEVICE_PROPS=0x1806,
CHANGE_HOST=0x1814,
HOSTS_INFO=0x1815,
BACKLIGHT=0x1981,
BACKLIGHT2=0x1982,
BACKLIGHT3=0x1983,
ILLUMINATION=0x1990,
PRESENTER_CONTROL=0x1A00,
SENSOR_3D=0x1A01,
REPROG_CONTROLS=0x1B00,
REPROG_CONTROLS_V2=0x1B01,
REPROG_CONTROLS_V2_2=0x1B02, # LogiOptions 2.10.73 features.xml
REPROG_CONTROLS_V3=0x1B03,
REPROG_CONTROLS_V4=0x1B04,
REPORT_HID_USAGE=0x1BC0,
PERSISTENT_REMAPPABLE_ACTION=0x1C00,
WIRELESS_DEVICE_STATUS=0x1D4B,
REMAINING_PAIRING=0x1DF0,
FIRMWARE_PROPERTIES=0x1F1F,
ADC_MEASUREMENT=0x1F20,
DEVICE_FW_VERSION = 0x0003
DEVICE_UNIT_ID = 0x0004
DEVICE_NAME = 0x0005
DEVICE_GROUPS = 0x0006
DEVICE_FRIENDLY_NAME = 0x0007
KEEP_ALIVE = 0x0008
CONFIG_CHANGE = 0x0020
CRYPTO_ID = 0x0021
TARGET_SOFTWARE = 0x0030
WIRELESS_SIGNAL_STRENGTH = 0x0080
DFUCONTROL_LEGACY = 0x00C0
DFUCONTROL_UNSIGNED = 0x00C1
DFUCONTROL_SIGNED = 0x00C2
DFUCONTROL = 0x00C3
DFU = 0x00D0
BATTERY_STATUS = 0x1000
BATTERY_VOLTAGE = 0x1001
UNIFIED_BATTERY = 0x1004
CHARGING_CONTROL = 0x1010
LED_CONTROL = 0x1300
FORCE_PAIRING = 0x1500
GENERIC_TEST = 0x1800
DEVICE_RESET = 0x1802
OOBSTATE = 0x1805
CONFIG_DEVICE_PROPS = 0x1806
CHANGE_HOST = 0x1814
HOSTS_INFO = 0x1815
BACKLIGHT = 0x1981
BACKLIGHT2 = 0x1982
BACKLIGHT3 = 0x1983
ILLUMINATION = 0x1990
PRESENTER_CONTROL = 0x1A00
SENSOR_3D = 0x1A01
REPROG_CONTROLS = 0x1B00
REPROG_CONTROLS_V2 = 0x1B01
REPROG_CONTROLS_V2_2 = 0x1B02 # LogiOptions 2.10.73 features.xml
REPROG_CONTROLS_V3 = 0x1B03
REPROG_CONTROLS_V4 = 0x1B04
REPORT_HID_USAGE = 0x1BC0
PERSISTENT_REMAPPABLE_ACTION = 0x1C00
WIRELESS_DEVICE_STATUS = 0x1D4B
REMAINING_PAIRING = 0x1DF0
FIRMWARE_PROPERTIES = 0x1F1F
ADC_MEASUREMENT = 0x1F20
# Mouse
LEFT_RIGHT_SWAP=0x2001,
SWAP_BUTTON_CANCEL=0x2005,
POINTER_AXIS_ORIENTATION=0x2006,
VERTICAL_SCROLLING=0x2100,
SMART_SHIFT=0x2110,
SMART_SHIFT_ENHANCED=0x2111,
HI_RES_SCROLLING=0x2120,
HIRES_WHEEL=0x2121,
LOWRES_WHEEL=0x2130,
THUMB_WHEEL=0x2150,
MOUSE_POINTER=0x2200,
ADJUSTABLE_DPI=0x2201,
EXTENDED_ADJUSTABLE_DPI=0x2202,
POINTER_SPEED=0x2205,
ANGLE_SNAPPING=0x2230,
SURFACE_TUNING=0x2240,
XY_STATS=0x2250,
WHEEL_STATS=0x2251,
HYBRID_TRACKING=0x2400,
LEFT_RIGHT_SWAP = 0x2001
SWAP_BUTTON_CANCEL = 0x2005
POINTER_AXIS_ORIENTATION = 0x2006
VERTICAL_SCROLLING = 0x2100
SMART_SHIFT = 0x2110
SMART_SHIFT_ENHANCED = 0x2111
HI_RES_SCROLLING = 0x2120
HIRES_WHEEL = 0x2121
LOWRES_WHEEL = 0x2130
THUMB_WHEEL = 0x2150
MOUSE_POINTER = 0x2200
ADJUSTABLE_DPI = 0x2201
EXTENDED_ADJUSTABLE_DPI = 0x2202
POINTER_SPEED = 0x2205
ANGLE_SNAPPING = 0x2230
SURFACE_TUNING = 0x2240
XY_STATS = 0x2250
WHEEL_STATS = 0x2251
HYBRID_TRACKING = 0x2400
# Keyboard
FN_INVERSION=0x40A0,
NEW_FN_INVERSION=0x40A2,
K375S_FN_INVERSION=0x40A3,
ENCRYPTION=0x4100,
LOCK_KEY_STATE=0x4220,
SOLAR_DASHBOARD=0x4301,
KEYBOARD_LAYOUT=0x4520,
KEYBOARD_DISABLE_KEYS=0x4521,
KEYBOARD_DISABLE_BY_USAGE=0x4522,
DUALPLATFORM=0x4530,
MULTIPLATFORM=0x4531,
KEYBOARD_LAYOUT_2=0x4540,
CROWN=0x4600,
FN_INVERSION = 0x40A0
NEW_FN_INVERSION = 0x40A2
K375S_FN_INVERSION = 0x40A3
ENCRYPTION = 0x4100
LOCK_KEY_STATE = 0x4220
SOLAR_DASHBOARD = 0x4301
KEYBOARD_LAYOUT = 0x4520
KEYBOARD_DISABLE_KEYS = 0x4521
KEYBOARD_DISABLE_BY_USAGE = 0x4522
DUALPLATFORM = 0x4530
MULTIPLATFORM = 0x4531
KEYBOARD_LAYOUT_2 = 0x4540
CROWN = 0x4600
# Touchpad
TOUCHPAD_FW_ITEMS=0x6010,
TOUCHPAD_SW_ITEMS=0x6011,
TOUCHPAD_WIN8_FW_ITEMS=0x6012,
TAP_ENABLE=0x6020,
TAP_ENABLE_EXTENDED=0x6021,
CURSOR_BALLISTIC=0x6030,
TOUCHPAD_RESOLUTION=0x6040,
TOUCHPAD_RAW_XY=0x6100,
TOUCHMOUSE_RAW_POINTS=0x6110,
TOUCHMOUSE_6120=0x6120,
GESTURE=0x6500,
GESTURE_2=0x6501,
TOUCHPAD_FW_ITEMS = 0x6010
TOUCHPAD_SW_ITEMS = 0x6011
TOUCHPAD_WIN8_FW_ITEMS = 0x6012
TAP_ENABLE = 0x6020
TAP_ENABLE_EXTENDED = 0x6021
CURSOR_BALLISTIC = 0x6030
TOUCHPAD_RESOLUTION = 0x6040
TOUCHPAD_RAW_XY = 0x6100
TOUCHMOUSE_RAW_POINTS = 0x6110
TOUCHMOUSE_6120 = 0x6120
GESTURE = 0x6500
GESTURE_2 = 0x6501
# Gaming Devices
GKEY=0x8010,
MKEYS=0x8020,
MR=0x8030,
BRIGHTNESS_CONTROL=0x8040,
REPORT_RATE=0x8060,
EXTENDED_ADJUSTABLE_REPORT_RATE=0x8061,
COLOR_LED_EFFECTS=0x8070,
RGB_EFFECTS=0x8071,
PER_KEY_LIGHTING=0x8080,
PER_KEY_LIGHTING_V2=0x8081,
MODE_STATUS=0x8090,
ONBOARD_PROFILES=0x8100,
MOUSE_BUTTON_SPY=0x8110,
LATENCY_MONITORING=0x8111,
GAMING_ATTACHMENTS=0x8120,
FORCE_FEEDBACK=0x8123,
GKEY = 0x8010
MKEYS = 0x8020
MR = 0x8030
BRIGHTNESS_CONTROL = 0x8040
REPORT_RATE = 0x8060
EXTENDED_ADJUSTABLE_REPORT_RATE = 0x8061
COLOR_LED_EFFECTS = 0x8070
RGB_EFFECTS = 0x8071
PER_KEY_LIGHTING = 0x8080
PER_KEY_LIGHTING_V2 = 0x8081
MODE_STATUS = 0x8090
ONBOARD_PROFILES = 0x8100
MOUSE_BUTTON_SPY = 0x8110
LATENCY_MONITORING = 0x8111
GAMING_ATTACHMENTS = 0x8120
FORCE_FEEDBACK = 0x8123
# Headsets
SIDETONE=0x8300,
EQUALIZER=0x8310,
HEADSET_OUT=0x8320,
SIDETONE = 0x8300
EQUALIZER = 0x8310
HEADSET_OUT = 0x8320
# Fake features for Solaar internal use
MOUSE_GESTURE=0xFE00,
)
FEATURE._fallback = lambda x: f"unknown:{x:04X}"
MOUSE_GESTURE = 0xFE00
def __str__(self):
return self.name.replace("_", " ")
FEATURE_FLAG = NamedInts(internal=0x20, hidden=0x40, obsolete=0x80)

View File

@ -40,7 +40,7 @@ logger = logging.getLogger(__name__)
_hidpp10 = hidpp10.Hidpp10()
_hidpp20 = hidpp20.Hidpp20()
_F = hidpp20_constants.FEATURE
_F = hidpp20_constants.SupportedFeature
notification_lock = threading.Lock()

View File

@ -629,8 +629,17 @@ class FeatureRW:
default_read_fnid = 0x00
default_write_fnid = 0x10
def __init__(self, feature, read_fnid=0x00, write_fnid=0x10, prefix=b"", suffix=b"", read_prefix=b"", no_reply=False):
assert isinstance(feature, NamedInt)
def __init__(
self,
feature: hidpp20_constants.SupportedFeature,
read_fnid=0x00,
write_fnid=0x10,
prefix=b"",
suffix=b"",
read_prefix=b"",
no_reply=False,
):
assert isinstance(feature, hidpp20_constants.SupportedFeature)
self.feature = feature
self.read_fnid = read_fnid
self.write_fnid = write_fnid
@ -658,13 +667,13 @@ class FeatureRWMap(FeatureRW):
def __init__(
self,
feature,
feature: hidpp20_constants.SupportedFeature,
read_fnid=default_read_fnid,
write_fnid=default_write_fnid,
key_byte_count=default_key_byte_count,
no_reply=False,
):
assert isinstance(feature, NamedInt)
assert isinstance(feature, hidpp20_constants.SupportedFeature)
self.feature = feature
self.read_fnid = read_fnid
self.write_fnid = write_fnid
@ -1422,7 +1431,10 @@ class ActionSettingRW:
def write(self, device, data_bytes):
def handler(device, n): # Called on notification events from the device
if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == hidpp20_constants.FEATURE.REPROG_CONTROLS_V4:
if (
n.sub_id < 0x40
and device.features.get_feature(n.sub_id) == hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4
):
if n.address == 0x00:
cids = struct.unpack("!HHHH", n.data[:8])
if not self.pressed and int(self.key.key) in cids: # trigger key pressed
@ -1484,11 +1496,11 @@ class RawXYProcessing:
self.keys = [] # the keys that can initiate processing
self.initiating_key = None # the key that did initiate processing
self.active = False
self.feature_offset = device.features[hidpp20_constants.FEATURE.REPROG_CONTROLS_V4]
self.feature_offset = device.features[hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4]
assert self.feature_offset is not False
def handler(self, device, n): # Called on notification events from the device
if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == hidpp20_constants.FEATURE.REPROG_CONTROLS_V4:
if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4:
if n.address == 0x00:
cids = struct.unpack("!HHHH", n.data[:8])
## generalize to list of keys
@ -1556,7 +1568,7 @@ class RawXYProcessing:
def apply_all_settings(device):
if device.features and hidpp20_constants.FEATURE.HIRES_WHEEL in device.features:
if device.features and hidpp20_constants.SupportedFeature.HIRES_WHEEL in device.features:
time.sleep(0.2) # delay to try to get out of race condition with Linux HID++ driver
persister = getattr(device, "persister", None)
sensitives = persister.get("_sensitive", {}) if persister else {}

View File

@ -13,6 +13,8 @@
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import annotations
import enum
import logging
import socket
@ -20,6 +22,7 @@ import struct
import traceback
from time import time
from typing import Any
from typing import Callable
from solaar.i18n import _
@ -40,7 +43,7 @@ logger = logging.getLogger(__name__)
_hidpp20 = hidpp20.Hidpp20()
_DK = hidpp10_constants.DEVICE_KIND
_F = hidpp20_constants.FEATURE
_F = hidpp20_constants.SupportedFeature
_GG = hidpp20_constants.GESTURE
_GP = hidpp20_constants.PARAM
@ -1795,18 +1798,20 @@ SETTINGS = [
]
def check_feature(device, sclass):
if sclass.feature not in device.features:
def check_feature(device, settings_class: settings.Setting) -> None | bool | Any:
if settings_class.feature not in device.features:
return
if sclass.min_version > device.features.get_feature_version(sclass.feature):
if settings_class.min_version > device.features.get_feature_version(settings_class.feature):
return
try:
detected = sclass.build(device)
detected = settings_class.build(device)
if logger.isEnabledFor(logging.DEBUG):
logger.debug("check_feature %s [%s] detected %s", sclass.name, sclass.feature, detected)
logger.debug("check_feature %s [%s] detected %s", settings_class.name, settings_class.feature, detected)
return detected
except Exception as e:
logger.error("check_feature %s [%s] error %s\n%s", sclass.name, sclass.feature, e, traceback.format_exc())
logger.error(
"check_feature %s [%s] error %s\n%s", settings_class.name, settings_class.feature, e, traceback.format_exc()
)
return False # differentiate from an error-free determination that the setting is not supported

View File

@ -24,6 +24,7 @@ from logitech_receiver import settings_templates
from logitech_receiver.common import LOGITECH_VENDOR_ID
from logitech_receiver.common import NamedInt
from logitech_receiver.common import strhex
from logitech_receiver.hidpp20_constants import SupportedFeature
from solaar import NAME
from solaar import __version__
@ -152,7 +153,7 @@ def _print_device(dev, num=None):
version = dev.features.get_feature_version(int(feature))
version = version if version else 0
print(" %2d: %-22s {%04X} V%s %s " % (index, feature, feature, version, ", ".join(flags)))
if feature == hidpp20_constants.FEATURE.HIRES_WHEEL:
if feature == SupportedFeature.HIRES_WHEEL:
wheel = _hidpp20.get_hires_wheel(dev)
if wheel:
multi, has_invert, has_switch, inv, res, target, ratchet = wheel
@ -169,7 +170,7 @@ def _print_device(dev, num=None):
print(" HID++ notification")
else:
print(" HID notification")
elif feature == hidpp20_constants.FEATURE.MOUSE_POINTER:
elif feature == SupportedFeature.MOUSE_POINTER:
mouse_pointer = _hidpp20.get_mouse_pointer_info(dev)
if mouse_pointer:
print(f" DPI: {mouse_pointer['dpi']}")
@ -182,13 +183,13 @@ def _print_device(dev, num=None):
print(" Provide vertical tuning, trackball")
else:
print(" No vertical tuning, standard mice")
elif feature == hidpp20_constants.FEATURE.VERTICAL_SCROLLING:
elif feature == SupportedFeature.VERTICAL_SCROLLING:
vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev)
if vertical_scrolling_info:
print(f" Roller type: {vertical_scrolling_info['roller']}")
print(f" Ratchet per turn: {vertical_scrolling_info['ratchet']}")
print(f" Scroll lines: {vertical_scrolling_info['lines']}")
elif feature == hidpp20_constants.FEATURE.HI_RES_SCROLLING:
elif feature == SupportedFeature.HI_RES_SCROLLING:
scrolling_mode, scrolling_resolution = _hidpp20.get_hi_res_scrolling_info(dev)
if scrolling_mode:
print(" Hi-res scrolling enabled")
@ -196,30 +197,30 @@ def _print_device(dev, num=None):
print(" Hi-res scrolling disabled")
if scrolling_resolution:
print(f" Hi-res scrolling multiplier: {scrolling_resolution}")
elif feature == hidpp20_constants.FEATURE.POINTER_SPEED:
elif feature == SupportedFeature.POINTER_SPEED:
pointer_speed = _hidpp20.get_pointer_speed_info(dev)
if pointer_speed:
print(f" Pointer Speed: {pointer_speed}")
elif feature == hidpp20_constants.FEATURE.LOWRES_WHEEL:
elif feature == SupportedFeature.LOWRES_WHEEL:
wheel_status = _hidpp20.get_lowres_wheel_status(dev)
if wheel_status:
print(f" Wheel Reports: {wheel_status}")
elif feature == hidpp20_constants.FEATURE.NEW_FN_INVERSION:
elif feature == SupportedFeature.NEW_FN_INVERSION:
inversion = _hidpp20.get_new_fn_inversion(dev)
if inversion:
inverted, default_inverted = inversion
print(" Fn-swap:", "enabled" if inverted else "disabled")
print(" Fn-swap default:", "enabled" if default_inverted else "disabled")
elif feature == hidpp20_constants.FEATURE.HOSTS_INFO:
elif feature == SupportedFeature.HOSTS_INFO:
host_names = _hidpp20.get_host_names(dev)
for host, (paired, name) in host_names.items():
print(f" Host {host} ({'paired' if paired else 'unpaired'}): {name}")
elif feature == hidpp20_constants.FEATURE.DEVICE_NAME:
elif feature == SupportedFeature.DEVICE_NAME:
print(f" Name: {_hidpp20.get_name(dev)}")
print(f" Kind: {_hidpp20.get_kind(dev)}")
elif feature == hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME:
elif feature == SupportedFeature.DEVICE_FRIENDLY_NAME:
print(f" Friendly Name: {_hidpp20.get_friendly_name(dev)}")
elif feature == hidpp20_constants.FEATURE.DEVICE_FW_VERSION:
elif feature == SupportedFeature.DEVICE_FW_VERSION:
for fw in _hidpp20.get_firmware(dev):
extras = strhex(fw.extras) if fw.extras else ""
print(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}")
@ -227,17 +228,14 @@ def _print_device(dev, num=None):
if ids:
unitId, modelId, tid_map = ids
print(f" Unit ID: {unitId} Model ID: {modelId} Transport IDs: {tid_map}")
elif (
feature == hidpp20_constants.FEATURE.REPORT_RATE
or feature == hidpp20_constants.FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE
):
elif feature == SupportedFeature.REPORT_RATE or feature == SupportedFeature.EXTENDED_ADJUSTABLE_REPORT_RATE:
print(f" Report Rate: {_hidpp20.get_polling_rate(dev)}")
elif feature == hidpp20_constants.FEATURE.CONFIG_CHANGE:
response = dev.feature_request(hidpp20_constants.FEATURE.CONFIG_CHANGE, 0x00)
elif feature == SupportedFeature.CONFIG_CHANGE:
response = dev.feature_request(SupportedFeature.CONFIG_CHANGE, 0x00)
print(f" Configuration: {response.hex()}")
elif feature == hidpp20_constants.FEATURE.REMAINING_PAIRING:
elif feature == SupportedFeature.REMAINING_PAIRING:
print(f" Remaining Pairings: {int(_hidpp20.get_remaining_pairing(dev))}")
elif feature == hidpp20_constants.FEATURE.ONBOARD_PROFILES:
elif feature == SupportedFeature.ONBOARD_PROFILES:
if _hidpp20.get_onboard_mode(dev) == hidpp20_constants.ONBOARD_MODES.MODE_HOST:
mode = "Host"
else:
@ -267,9 +265,9 @@ def _print_device(dev, num=None):
print(f" Has {len(dev.keys)} reprogrammable keys:")
for k in dev.keys:
# TODO: add here additional variants for other REPROG_CONTROLS
if dev.keys.keyversion == hidpp20_constants.FEATURE.REPROG_CONTROLS_V2:
if dev.keys.keyversion == SupportedFeature.REPROG_CONTROLS_V2:
print(" %2d: %-26s => %-27s %s" % (k.index, k.key, k.default_task, ", ".join(k.flags)))
if dev.keys.keyversion == hidpp20_constants.FEATURE.REPROG_CONTROLS_V4:
if dev.keys.keyversion == SupportedFeature.REPROG_CONTROLS_V4:
print(" %2d: %-26s, default: %-27s => %-26s" % (k.index, k.key, k.default_task, k.mapped_to))
gmask_fmt = ",".join(k.group_mask)
gmask_fmt = gmask_fmt if gmask_fmt else "empty"

View File

@ -18,7 +18,7 @@ from dataclasses import dataclass
from gi.repository import Gtk
from logitech_receiver import diversion
from logitech_receiver.diversion import Key
from logitech_receiver.hidpp20 import FEATURE
from logitech_receiver.hidpp20 import SupportedFeature
from logitech_receiver.special_keys import CONTROL
from solaar.i18n import _
@ -97,15 +97,15 @@ class MouseProcessUI(ConditionUI):
class FeatureUI(ConditionUI):
CLASS = diversion.Feature
FEATURES_WITH_DIVERSION = [
str(FEATURE.CROWN),
str(FEATURE.THUMB_WHEEL),
str(FEATURE.LOWRES_WHEEL),
str(FEATURE.HIRES_WHEEL),
str(FEATURE.GESTURE_2),
str(FEATURE.REPROG_CONTROLS_V4),
str(FEATURE.GKEY),
str(FEATURE.MKEYS),
str(FEATURE.MR),
str(SupportedFeature.CROWN),
str(SupportedFeature.THUMB_WHEEL),
str(SupportedFeature.LOWRES_WHEEL),
str(SupportedFeature.HIRES_WHEEL),
str(SupportedFeature.GESTURE_2),
str(SupportedFeature.REPROG_CONTROLS_V4),
str(SupportedFeature.GKEY),
str(SupportedFeature.MKEYS),
str(SupportedFeature.MR),
]
def create_widgets(self):
@ -120,7 +120,7 @@ class FeatureUI(ConditionUI):
self.field.set_valign(Gtk.Align.CENTER)
self.field.set_size_request(600, 0)
self.field.connect("changed", self._on_update)
all_features = [str(f) for f in FEATURE]
all_features = [str(f) for f in SupportedFeature]
CompletionEntry.add_completion_to_entry(self.field.get_child(), all_features)
self.widgets[self.field] = (0, 1, 1, 1)

View File

@ -407,8 +407,17 @@ class Device:
self.settings = []
if self.feature is not None:
self.features = hidpp20.FeaturesArray(self)
self.responses = [Response("010001", 0x0000, "0001"), Response("20", 0x0100)] + self.responses
self.responses.append(Response(f"{self.offset:0>2X}00{self.version:0>2X}", 0x0000, f"{self.feature:0>4X}"))
self.responses = [
Response("010001", 0x0000, "0001"),
Response("20", 0x0100),
] + self.responses
self.responses.append(
Response(
f"{int(self.offset):0>2X}00{int(self.version):0>2X}",
0x0000,
f"{int(self.feature):0>4X}",
)
)
if self.setting_callback is None:
self.setting_callback = lambda x, y, z: None
self.add_notification_handler = lambda x, y: None

View File

@ -7,7 +7,7 @@ import pytest
from logitech_receiver import diversion
from logitech_receiver.base import HIDPPNotification
from logitech_receiver.hidpp20_constants import FEATURE
from logitech_receiver.hidpp20_constants import SupportedFeature
@pytest.fixture
@ -104,14 +104,14 @@ def test_feature():
"feature, data",
[
(
FEATURE.REPROG_CONTROLS_V4,
SupportedFeature.REPROG_CONTROLS_V4,
[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
),
(FEATURE.GKEY, [0x01, 0x02, 0x03, 0x04]),
(FEATURE.MKEYS, [0x01, 0x02, 0x03, 0x04]),
(FEATURE.MR, [0x01, 0x02, 0x03, 0x04]),
(FEATURE.THUMB_WHEEL, [0x01, 0x02, 0x03, 0x04, 0x05]),
(FEATURE.DEVICE_UNIT_ID, [0x01, 0x02, 0x03, 0x04, 0x05]),
(SupportedFeature.GKEY, [0x01, 0x02, 0x03, 0x04]),
(SupportedFeature.MKEYS, [0x01, 0x02, 0x03, 0x04]),
(SupportedFeature.MR, [0x01, 0x02, 0x03, 0x04]),
(SupportedFeature.THUMB_WHEEL, [0x01, 0x02, 0x03, 0x04, 0x05]),
(SupportedFeature.DEVICE_UNIT_ID, [0x01, 0x02, 0x03, 0x04, 0x05]),
],
)
def test_process_notification(feature, data):

View File

@ -56,7 +56,7 @@ def test_FeaturesArray_check(device, expected_result, expected_count):
assert result == expected_result
assert result2 == expected_result
assert (hidpp20_constants.FEATURE.ROOT in featuresarray) == expected_result
assert (hidpp20_constants.SupportedFeature.ROOT in featuresarray) == expected_result
assert len(featuresarray) == expected_count
assert bool(featuresarray) == expected_result
@ -65,7 +65,7 @@ def test_FeaturesArray_check(device, expected_result, expected_count):
"device, expected0, expected1, expected2, expected5, expected5v",
[
(device_zerofeatures, None, None, None, None, None),
(device_standard, 0x0000, 0x0001, 0x0020, hidpp20_constants.FEATURE.REPROG_CONTROLS_V4, 3),
(device_standard, 0x0000, 0x0001, 0x0020, hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, 3),
],
)
def test_FeaturesArray_get_feature(device, expected0, expected1, expected2, expected5, expected5v):
@ -77,7 +77,7 @@ def test_FeaturesArray_get_feature(device, expected0, expected1, expected2, expe
result2 = featuresarray.get_feature(2)
result5 = featuresarray.get_feature(5)
result2r = featuresarray.get_feature(2)
result5v = featuresarray.get_feature_version(hidpp20_constants.FEATURE.REPROG_CONTROLS_V4)
result5v = featuresarray.get_feature_version(hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4)
assert result0 == expected0
assert result1 == expected1
@ -94,15 +94,15 @@ def test_FeaturesArray_get_feature(device, expected0, expected1, expected2, expe
(
device_standard,
[
(hidpp20_constants.FEATURE.ROOT, 0),
(hidpp20_constants.FEATURE.FEATURE_SET, 1),
(hidpp20_constants.FEATURE.CONFIG_CHANGE, 2),
(hidpp20_constants.FEATURE.DEVICE_FW_VERSION, 3),
(hidpp20_constants.SupportedFeature.ROOT, 0),
(hidpp20_constants.SupportedFeature.FEATURE_SET, 1),
(hidpp20_constants.SupportedFeature.CONFIG_CHANGE, 2),
(hidpp20_constants.SupportedFeature.DEVICE_FW_VERSION, 3),
(common.NamedInt(256, "unknown:0100"), 4),
(hidpp20_constants.FEATURE.REPROG_CONTROLS_V4, 5),
(hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, 5),
(None, 6),
(None, 7),
(hidpp20_constants.FEATURE.BATTERY_STATUS, 8),
(hidpp20_constants.SupportedFeature.BATTERY_STATUS, 8),
],
),
],
@ -118,12 +118,12 @@ def test_FeaturesArray_enumerate(device, expected_result):
def test_FeaturesArray_setitem():
featuresarray = hidpp20.FeaturesArray(device_standard)
featuresarray[hidpp20_constants.FEATURE.ROOT] = 3
featuresarray[hidpp20_constants.FEATURE.FEATURE_SET] = 5
featuresarray[hidpp20_constants.FEATURE.FEATURE_SET] = 4
featuresarray[hidpp20_constants.SupportedFeature.ROOT] = 3
featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] = 5
featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] = 4
assert featuresarray[hidpp20_constants.FEATURE.FEATURE_SET] == 4
assert featuresarray.inverse[4] == hidpp20_constants.FEATURE.FEATURE_SET
assert featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] == 4
assert featuresarray.inverse[4] == hidpp20_constants.SupportedFeature.FEATURE_SET
def test_FeaturesArray_delitem():
@ -141,10 +141,10 @@ def test_FeaturesArray_getitem(device, expected0, expected1, expected2, expected
featuresarray = hidpp20.FeaturesArray(device)
device.features = featuresarray
result_get0 = featuresarray[hidpp20_constants.FEATURE.ROOT]
result_get1 = featuresarray[hidpp20_constants.FEATURE.REPROG_CONTROLS_V4]
result_get2 = featuresarray[hidpp20_constants.FEATURE.GKEY]
result_1v = featuresarray.get_feature_version(hidpp20_constants.FEATURE.REPROG_CONTROLS_V4)
result_get0 = featuresarray[hidpp20_constants.SupportedFeature.ROOT]
result_get1 = featuresarray[hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4]
result_get2 = featuresarray[hidpp20_constants.SupportedFeature.GKEY]
result_1v = featuresarray.get_feature_version(hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4)
assert result_get0 == expected0
assert result_get1 == expected1
@ -220,7 +220,9 @@ def test_ReprogrammableKeyV4_key(device, index, cid, tid, flags, pos, group, gma
)
# these fields need access all the key data, so start by setting up a device and its key data
def test_ReprogrammableKeyV4_query(responses, index, mapped_to, remappable_to, mapping_flags):
device = fake_hidpp.Device("KEY", responses=responses, feature=hidpp20_constants.FEATURE.REPROG_CONTROLS_V4, offset=5)
device = fake_hidpp.Device(
"KEY", responses=responses, feature=hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, offset=5
)
device._keys = _hidpp20.get_keys(device)
key = device.keys[index]
@ -241,7 +243,9 @@ def test_ReprogrammableKeyV4_query(responses, index, mapped_to, remappable_to, m
)
def test_ReprogrammableKeyV4_set(responses, index, diverted, persistently_diverted, rawXY_reporting, remap, sets, mocker):
responses += [fake_hidpp.Response(r, 0x530, r) for r in sets]
device = fake_hidpp.Device("KEY", responses=responses, feature=hidpp20_constants.FEATURE.REPROG_CONTROLS_V4, offset=5)
device = fake_hidpp.Device(
"KEY", responses=responses, feature=hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, offset=5
)
device._keys = _hidpp20.get_keys(device)
device._keys._ensure_all_keys_queried() # do this now so that the last requests are sets
spy_request = mocker.spy(device, "request")
@ -299,7 +303,9 @@ def test_remappable_action(r, index, cid, actionId, remapped, mask, status, acti
fake_hidpp.Response("040000", 0x0000, "1C00"),
fake_hidpp.Response("00", 0x440, f"{cid:04X}" + "FF" + remap),
]
device = fake_hidpp.Device("KEY", responses=responses, feature=hidpp20_constants.FEATURE.REPROG_CONTROLS_V4, offset=5)
device = fake_hidpp.Device(
"KEY", responses=responses, feature=hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, offset=5
)
key = hidpp20.PersistentRemappableAction(device, index, cid, actionId, remapped, mask, status)
spy_request = mocker.spy(device, "request")
@ -387,7 +393,7 @@ def test_KeysArrayV4_index(key, index):
device_key = fake_hidpp.Device(
"KEY", responses=fake_hidpp.responses_key, feature=hidpp20_constants.FEATURE.REPROG_CONTROLS_V4, offset=5
"KEY", responses=fake_hidpp.responses_key, feature=hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, offset=5
)
@ -450,7 +456,9 @@ def test_KeysArrayPersistent_index_error(device, index):
],
)
def test_KeysArrayPersistent_key(responses, key, index, mapped_to, capabilities):
device = fake_hidpp.Device("REMAP", responses=responses, feature=hidpp20_constants.FEATURE.PERSISTENT_REMAPPABLE_ACTION)
device = fake_hidpp.Device(
"REMAP", responses=responses, feature=hidpp20_constants.SupportedFeature.PERSISTENT_REMAPPABLE_ACTION
)
device._remap_keys = _hidpp20.get_remap_keys(device)
device._remap_keys._ensure_all_keys_queried()
@ -510,7 +518,7 @@ def test_Gesture(device, low, high, next_index, next_diversion_index, name, cbe,
],
)
def test_Gesture_set(responses, gest, enabled, diverted, set_result, unset_result, divert_result, undivert_result):
device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.FEATURE.GESTURE_2)
device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.SupportedFeature.GESTURE_2)
gestures = _hidpp20.get_gestures(device)
gesture = gestures.gesture(gest)
@ -530,7 +538,7 @@ def test_Gesture_set(responses, gest, enabled, diverted, set_result, unset_resul
],
)
def test_Param(responses, prm, id, index, size, value, default_value, write1, write2):
device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.FEATURE.GESTURE_2)
device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.SupportedFeature.GESTURE_2)
gestures = _hidpp20.get_gestures(device)
param = gestures.param(prm)
@ -555,7 +563,7 @@ def test_Param(responses, prm, id, index, size, value, default_value, write1, wr
],
)
def test_Spec(responses, id, s, byte_count, value, string):
device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.FEATURE.GESTURE_2)
device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.SupportedFeature.GESTURE_2)
gestures = _hidpp20.get_gestures(device)
spec = gestures.specs[id]
@ -569,7 +577,7 @@ def test_Spec(responses, id, s, byte_count, value, string):
def test_Gestures():
device = fake_hidpp.Device(
"GESTURES", responses=fake_hidpp.responses_gestures, feature=hidpp20_constants.FEATURE.GESTURE_2
"GESTURES", responses=fake_hidpp.responses_gestures, feature=hidpp20_constants.SupportedFeature.GESTURE_2
)
gestures = _hidpp20.get_gestures(device)
@ -600,7 +608,9 @@ responses_backlight = [
fake_hidpp.Response("0101FF00020003000400", 0x0410, "0101FF00020003000400"),
]
device_backlight = fake_hidpp.Device("BACKLIGHT", responses=responses_backlight, feature=hidpp20_constants.FEATURE.BACKLIGHT2)
device_backlight = fake_hidpp.Device(
"BACKLIGHT", responses=responses_backlight, feature=hidpp20_constants.SupportedFeature.BACKLIGHT2
)
def test_Backlight():
@ -655,7 +665,7 @@ def test_LEDEffectSetting(hex, ID, color, speed, period, intensity, ramp, form):
"feature, function, response, ID, capabilities, period",
[
[
hidpp20_constants.FEATURE.COLOR_LED_EFFECTS,
hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS,
0x20,
fake_hidpp.Response("0102000300040005", 0x0420, "010200"),
3,
@ -663,7 +673,7 @@ def test_LEDEffectSetting(hex, ID, color, speed, period, intensity, ramp, form):
5,
],
[
hidpp20_constants.FEATURE.COLOR_LED_EFFECTS,
hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS,
0x20,
fake_hidpp.Response("0102000700080009", 0x0420, "010200"),
7,
@ -687,8 +697,8 @@ def test_LEDEffectInfo(feature, function, response, ID, capabilities, period):
@pytest.mark.parametrize(
"feature, function, offset, effect_function, responses, index, location, count, id_1",
[
[hidpp20_constants.FEATURE.COLOR_LED_EFFECTS, 0x10, 0, 0x20, fake_hidpp.zone_responses_1, 0, 1, 2, 0xB],
[hidpp20_constants.FEATURE.RGB_EFFECTS, 0x00, 1, 0x00, fake_hidpp.zone_responses_2, 0, 1, 2, 2],
[hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, 0x10, 0, 0x20, fake_hidpp.zone_responses_1, 0, 1, 2, 0xB],
[hidpp20_constants.SupportedFeature.RGB_EFFECTS, 0x00, 1, 0x00, fake_hidpp.zone_responses_2, 0, 1, 2, 2],
],
)
def test_LEDZoneInfo(feature, function, offset, effect_function, responses, index, location, count, id_1):
@ -716,8 +726,8 @@ def test_LEDZoneInfo(feature, function, offset, effect_function, responses, inde
],
)
def test_LEDZoneInfo_to_command(responses, setting, expected_command):
device = fake_hidpp.Device(feature=hidpp20_constants.FEATURE.COLOR_LED_EFFECTS, responses=responses, offset=0x07)
zone = hidpp20.LEDZoneInfo(hidpp20_constants.FEATURE.COLOR_LED_EFFECTS, 0x10, 0, 0x20, device, 0)
device = fake_hidpp.Device(feature=hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, responses=responses, offset=0x07)
zone = hidpp20.LEDZoneInfo(hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, 0x10, 0, 0x20, device, 0)
command = zone.to_command(setting)
@ -727,8 +737,15 @@ def test_LEDZoneInfo_to_command(responses, setting, expected_command):
@pytest.mark.parametrize(
"feature, cls, responses, readable, count, count_0",
[
[hidpp20_constants.FEATURE.COLOR_LED_EFFECTS, hidpp20.LEDEffectsInfo, fake_hidpp.effects_responses_1, 1, 1, 2],
[hidpp20_constants.FEATURE.RGB_EFFECTS, hidpp20.RGBEffectsInfo, fake_hidpp.effects_responses_2, 1, 1, 2],
[
hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS,
hidpp20.LEDEffectsInfo,
fake_hidpp.effects_responses_1,
1,
1,
2,
],
[hidpp20_constants.SupportedFeature.RGB_EFFECTS, hidpp20.RGBEffectsInfo, fake_hidpp.effects_responses_2, 1, 1, 2],
],
)
def test_LED_RGB_EffectsInfo(feature, cls, responses, readable, count, count_0):
@ -857,7 +874,7 @@ def test_OnboardProfile_bytes(hex, name, sector, enabled, buttons, gbuttons, res
)
def test_OnboardProfiles_device(responses, name, count, buttons, gbuttons, sectors, size):
device = fake_hidpp.Device(
name, True, 4.5, responses=responses, feature=hidpp20_constants.FEATURE.ONBOARD_PROFILES, offset=0x9
name, True, 4.5, responses=responses, feature=hidpp20_constants.SupportedFeature.ONBOARD_PROFILES, offset=0x9
)
device._profiles = None
profiles = _hidpp20.get_profiles(device)

View File

@ -18,7 +18,7 @@ import pytest
from logitech_receiver import common
from logitech_receiver import hidpp20
from logitech_receiver import hidpp20_constants
from logitech_receiver.hidpp20_constants import SupportedFeature
from . import fake_hidpp
@ -31,7 +31,7 @@ def test_get_firmware():
fake_hidpp.Response("01414243030401000101000102030405", 0x0410, "00"),
fake_hidpp.Response("02414243030401000101000102030405", 0x0410, "01"),
]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.DEVICE_FW_VERSION)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_FW_VERSION)
result = _hidpp20.get_firmware(device)
@ -42,7 +42,7 @@ def test_get_firmware():
def test_get_ids():
responses = [fake_hidpp.Response("FF12345678000D123456789ABC", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.DEVICE_FW_VERSION)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_FW_VERSION)
unitId, modelId, tid_map = _hidpp20.get_ids(device)
@ -53,7 +53,7 @@ def test_get_ids():
def test_get_kind():
responses = [fake_hidpp.Response("00", 0x0420)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.DEVICE_NAME)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_NAME)
result = _hidpp20.get_kind(device)
@ -67,7 +67,7 @@ def test_get_name():
fake_hidpp.Response("4142434445464748494A4B4C4D4E4F", 0x0410, "00"),
fake_hidpp.Response("505152530000000000000000000000", 0x0410, "0F"),
]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.DEVICE_NAME)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_NAME)
result = _hidpp20.get_name(device)
@ -80,7 +80,7 @@ def test_get_friendly_name():
fake_hidpp.Response("004142434445464748494A4B4C4D4E", 0x0410, "00"),
fake_hidpp.Response("0E4F50515253000000000000000000", 0x0410, "0E"),
]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_FRIENDLY_NAME)
result = _hidpp20.get_friendly_name(device)
@ -89,11 +89,11 @@ def test_get_friendly_name():
def test_get_battery_status():
responses = [fake_hidpp.Response("502000FFFF", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.BATTERY_STATUS)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.BATTERY_STATUS)
feature, battery = _hidpp20.get_battery_status(device)
assert feature == hidpp20_constants.FEATURE.BATTERY_STATUS
assert feature == SupportedFeature.BATTERY_STATUS
assert battery.level == 80
assert battery.next_level == 32
assert battery.status == common.BatteryStatus.DISCHARGING
@ -101,11 +101,11 @@ def test_get_battery_status():
def test_get_battery_voltage():
responses = [fake_hidpp.Response("1000FFFFFF", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.BATTERY_VOLTAGE)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.BATTERY_VOLTAGE)
feature, battery = _hidpp20.get_battery_voltage(device)
assert feature == hidpp20_constants.FEATURE.BATTERY_VOLTAGE
assert feature == SupportedFeature.BATTERY_VOLTAGE
assert battery.level == 90
assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000
@ -113,22 +113,22 @@ def test_get_battery_voltage():
def test_get_battery_unified():
responses = [fake_hidpp.Response("500100FFFF", 0x0410)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.UNIFIED_BATTERY)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.UNIFIED_BATTERY)
feature, battery = _hidpp20.get_battery_unified(device)
assert feature == hidpp20_constants.FEATURE.UNIFIED_BATTERY
assert feature == SupportedFeature.UNIFIED_BATTERY
assert battery.level == 80
assert battery.status == common.BatteryStatus.DISCHARGING
def test_get_adc_measurement():
responses = [fake_hidpp.Response("100003", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.ADC_MEASUREMENT)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.ADC_MEASUREMENT)
feature, battery = _hidpp20.get_adc_measurement(device)
assert feature == hidpp20_constants.FEATURE.ADC_MEASUREMENT
assert feature == SupportedFeature.ADC_MEASUREMENT
assert battery.level == 90
assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000
@ -136,11 +136,11 @@ def test_get_adc_measurement():
def test_get_battery():
responses = [fake_hidpp.Response("502000FFFF", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.BATTERY_STATUS)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.BATTERY_STATUS)
feature, battery = _hidpp20.get_battery(device, hidpp20_constants.FEATURE.BATTERY_STATUS)
feature, battery = _hidpp20.get_battery(device, SupportedFeature.BATTERY_STATUS)
assert feature == hidpp20_constants.FEATURE.BATTERY_STATUS
assert feature == SupportedFeature.BATTERY_STATUS
assert battery.level == 80
assert battery.next_level == 32
assert battery.status == common.BatteryStatus.DISCHARGING
@ -148,15 +148,15 @@ def test_get_battery():
def test_get_battery_none():
responses = [
fake_hidpp.Response(None, 0x0000, f"{hidpp20_constants.FEATURE.BATTERY_STATUS:0>4X}"),
fake_hidpp.Response(None, 0x0000, f"{hidpp20_constants.FEATURE.BATTERY_VOLTAGE:0>4X}"),
fake_hidpp.Response(None, 0x0000, f"{int(SupportedFeature.BATTERY_STATUS):0>4X}"),
fake_hidpp.Response(None, 0x0000, f"{int(SupportedFeature.BATTERY_VOLTAGE):0>4X}"),
fake_hidpp.Response("500100ffff", 0x0410),
]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.UNIFIED_BATTERY)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.UNIFIED_BATTERY)
feature, battery = _hidpp20.get_battery(device, None)
assert feature == hidpp20_constants.FEATURE.UNIFIED_BATTERY
assert feature == SupportedFeature.UNIFIED_BATTERY
assert battery.level == 80
assert battery.status == common.BatteryStatus.DISCHARGING
@ -170,7 +170,7 @@ def test_get_battery_none():
def test_get_mouse_pointer_info():
responses = [fake_hidpp.Response("01000A", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.MOUSE_POINTER)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.MOUSE_POINTER)
result = _hidpp20.get_mouse_pointer_info(device)
@ -184,7 +184,7 @@ def test_get_mouse_pointer_info():
def test_get_vertical_scrolling_info():
responses = [fake_hidpp.Response("01080C", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.VERTICAL_SCROLLING)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.VERTICAL_SCROLLING)
result = _hidpp20.get_vertical_scrolling_info(device)
@ -193,7 +193,7 @@ def test_get_vertical_scrolling_info():
def test_get_hi_res_scrolling_info():
responses = [fake_hidpp.Response("0102", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.HI_RES_SCROLLING)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.HI_RES_SCROLLING)
mode, resolution = _hidpp20.get_hi_res_scrolling_info(device)
@ -203,7 +203,7 @@ def test_get_hi_res_scrolling_info():
def test_get_pointer_speed_info():
responses = [fake_hidpp.Response("0102", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.POINTER_SPEED)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.POINTER_SPEED)
result = _hidpp20.get_pointer_speed_info(device)
@ -212,7 +212,7 @@ def test_get_pointer_speed_info():
def test_get_lowres_wheel_status():
responses = [fake_hidpp.Response("01", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.LOWRES_WHEEL)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.LOWRES_WHEEL)
result = _hidpp20.get_lowres_wheel_status(device)
@ -225,7 +225,7 @@ def test_get_hires_wheel():
fake_hidpp.Response("05FF", 0x0410),
fake_hidpp.Response("03FF", 0x0430),
]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.HIRES_WHEEL)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.HIRES_WHEEL)
multi, has_invert, has_ratchet, inv, res, target, ratchet = _hidpp20.get_hires_wheel(device)
@ -240,7 +240,7 @@ def test_get_hires_wheel():
def test_get_new_fn_inversion():
responses = [fake_hidpp.Response("0300", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.NEW_FN_INVERSION)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.NEW_FN_INVERSION)
result = _hidpp20.get_new_fn_inversion(device)
@ -273,7 +273,7 @@ def mock_gethostname(mocker):
],
)
def test_get_host_names(responses, expected_result, mock_gethostname):
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.HOSTS_INFO)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.HOSTS_INFO)
result = _hidpp20.get_host_names(device)
@ -304,7 +304,7 @@ def test_get_host_names(responses, expected_result, mock_gethostname):
],
)
def test_set_host_name(responses, expected_result):
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.HOSTS_INFO)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.HOSTS_INFO)
result = _hidpp20.set_host_name(device, "ABCDEFGHIJKLMNOPQRSTUVWX")
@ -313,7 +313,7 @@ def test_set_host_name(responses, expected_result):
def test_get_onboard_mode():
responses = [fake_hidpp.Response("03FFFFFFFF", 0x0420)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.ONBOARD_PROFILES)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.ONBOARD_PROFILES)
result = _hidpp20.get_onboard_mode(device)
@ -322,7 +322,7 @@ def test_get_onboard_mode():
def test_set_onboard_mode():
responses = [fake_hidpp.Response("03FFFFFFFF", 0x0410, "03")]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.ONBOARD_PROFILES)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.ONBOARD_PROFILES)
res = _hidpp20.set_onboard_mode(device, 0x3)
@ -335,7 +335,7 @@ def test_set_onboard_mode():
([fake_hidpp.Response("03FFFF", 0x0420)], "1ms"),
(
[
fake_hidpp.Response(None, 0x0000, f"{hidpp20_constants.FEATURE.REPORT_RATE:0>4X}"),
fake_hidpp.Response(None, 0x0000, f"{int(SupportedFeature.REPORT_RATE):04X}"),
fake_hidpp.Response("04FFFF", 0x0420),
],
"500us",
@ -346,7 +346,7 @@ def test_get_polling_rate(
responses,
expected_result,
):
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.EXTENDED_ADJUSTABLE_REPORT_RATE)
result = _hidpp20.get_polling_rate(device)
@ -355,7 +355,7 @@ def test_get_polling_rate(
def test_get_remaining_pairing():
responses = [fake_hidpp.Response("03FFFF", 0x0400)]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.REMAINING_PAIRING)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.REMAINING_PAIRING)
result = _hidpp20.get_remaining_pairing(device)
@ -364,7 +364,7 @@ def test_get_remaining_pairing():
def test_config_change():
responses = [fake_hidpp.Response("03FFFF", 0x0410, "02")]
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.CONFIG_CHANGE)
device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.CONFIG_CHANGE)
result = _hidpp20.config_change(device, 0x2)
@ -376,7 +376,7 @@ def test_decipher_battery_status():
feature, battery = hidpp20.decipher_battery_status(report)
assert feature == hidpp20_constants.FEATURE.BATTERY_STATUS
assert feature == SupportedFeature.BATTERY_STATUS
assert battery.level == 80
assert battery.next_level == 32
assert battery.status == common.BatteryStatus.DISCHARGING
@ -387,7 +387,7 @@ def test_decipher_battery_voltage():
feature, battery = hidpp20.decipher_battery_voltage(report)
assert feature == hidpp20_constants.FEATURE.BATTERY_VOLTAGE
assert feature == SupportedFeature.BATTERY_VOLTAGE
assert battery.level == 90
assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000
@ -398,7 +398,7 @@ def test_decipher_battery_unified():
feature, battery = hidpp20.decipher_battery_unified(report)
assert feature == hidpp20_constants.FEATURE.UNIFIED_BATTERY
assert feature == SupportedFeature.UNIFIED_BATTERY
assert battery.level == 80
assert battery.status == common.BatteryStatus.DISCHARGING
@ -408,7 +408,7 @@ def test_decipher_adc_measurement():
feature, battery = hidpp20.decipher_adc_measurement(report)
assert feature == hidpp20_constants.FEATURE.ADC_MEASUREMENT
assert feature == SupportedFeature.ADC_MEASUREMENT
assert battery.level == 90
assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000

View File

@ -795,7 +795,7 @@ def test_key_template(test, mocker):
],
)
def test_SpeedChange_action(responses, currentSpeed, newSpeed, mocker):
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.FEATURE.POINTER_SPEED)
device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.SupportedFeature.POINTER_SPEED)
spy_setting_callback = mocker.spy(device, "setting_callback")
settings_templates.check_feature_settings(device, device.settings) # need to set up all the settings
device.persister = {"pointer_speed": currentSpeed, "_speed-change": newSpeed}