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
from . import hidpp10_constants from . import hidpp10_constants
from . import hidpp20 from . import hidpp20
from . import hidpp20_constants
from . import settings from . import settings
from . import settings_templates from . import settings_templates
from .common import Alert from .common import Alert
from .common import Battery from .common import Battery
from .hidpp20_constants import SupportedFeature
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -295,9 +295,9 @@ class Device:
@property @property
def led_effects(self): def led_effects(self):
if not self._led_effects and self.online and self.protocol >= 2.0: 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) 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) self._led_effects = hidpp20.RGBEffectsInfo(self)
return self._led_effects return self._led_effects
@ -435,7 +435,7 @@ class Device:
was_active is None was_active is None
or not was_active or not was_active
or push 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): if logger.isEnabledFor(logging.INFO):
logger.info("%s pushing device settings %s", self, self.settings) logger.info("%s pushing device settings %s", self, self.settings)

View File

@ -47,7 +47,7 @@ else:
import evdev import evdev
from .common import NamedInt from .common import NamedInt
from .hidpp20 import FEATURE from .hidpp20 import SupportedFeature
from .special_keys import CONTROL from .special_keys import CONTROL
gi.require_version("Gdk", "3.0") # isort:skip 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): def thumb_wheel_up(f, r, d, a):
global thumb_wheel_displacement global thumb_wheel_displacement
if f != FEATURE.THUMB_WHEEL or r != 0: if f != SupportedFeature.THUMB_WHEEL or r != 0:
return False return False
if a is None: if a is None:
return signed(d[0:2]) < 0 and signed(d[0:2]) 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): def thumb_wheel_down(f, r, d, a):
global thumb_wheel_displacement global thumb_wheel_displacement
if f != FEATURE.THUMB_WHEEL or r != 0: if f != SupportedFeature.THUMB_WHEEL or r != 0:
return False return False
if a is None: if a is None:
return signed(d[0:2]) > 0 and signed(d[0:2]) 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): def charging(f, r, d, _a):
if ( if (
(f == FEATURE.BATTERY_STATUS and r == 0 and 1 <= d[2] <= 4) (f == SupportedFeature.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 == SupportedFeature.BATTERY_VOLTAGE and r == 0 and d[2] & (1 << 7))
or (f == FEATURE.UNIFIED_BATTERY and r == 0 and 1 <= d[2] <= 3) or (f == SupportedFeature.UNIFIED_BATTERY and r == 0 and 1 <= d[2] <= 3)
): ):
return 1 return 1
else: else:
@ -470,30 +470,30 @@ def charging(f, r, d, _a):
TESTS = { TESTS = {
"crown_right": [lambda f, r, d, a: f == FEATURE.CROWN and r == 0 and d[1] < 128 and d[1], 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 == FEATURE.CROWN and r == 0 and d[1] >= 128 and 256 - 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 == FEATURE.CROWN and r == 0 and d[2] < 128 and d[2], 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 == FEATURE.CROWN and r == 0 and d[2] >= 128 and 256 - 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 == FEATURE.CROWN and r == 0 and d[5] == 0x01 and d[5], 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 == FEATURE.CROWN and r == 0 and d[6] == 0x01 and d[6], 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 == FEATURE.CROWN and r == 0 and d[6] == 0x05 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 == FEATURE.CROWN and r == 0 and 0x01 <= d[6] <= 0x04 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_up": [thumb_wheel_up, True],
"thumb_wheel_down": [thumb_wheel_down, True], "thumb_wheel_down": [thumb_wheel_down, True],
"lowres_wheel_up": [ "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, False,
], ],
"lowres_wheel_down": [ "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, False,
], ],
"hires_wheel_up": [ "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, False,
], ],
"hires_wheel_down": [ "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, False,
], ],
"charging": [charging, False], "charging": [charging, False],
@ -738,12 +738,13 @@ class MouseProcess(Condition):
class Feature(Condition): class Feature(Condition):
def __init__(self, feature, warn=True): def __init__(self, feature: str, warn: bool = True):
if not (isinstance(feature, str) and feature in FEATURE): try:
self.feature = SupportedFeature[feature]
except KeyError:
self.feature = None
if warn: if warn:
logger.warning("rule Feature argument not name of a feature: %s", feature) logger.warning("rule Feature argument not name of a feature: %s", feature)
self.feature = None
self.feature = FEATURE[feature]
def __str__(self): def __str__(self):
return "Feature: " + str(self.feature) return "Feature: " + str(self.feature)
@ -1052,7 +1053,7 @@ class MouseGesture(Condition):
def evaluate(self, feature, notification: HIDPPNotification, device, last_result): def evaluate(self, feature, notification: HIDPPNotification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
if feature == FEATURE.MOUSE_GESTURE: if feature == SupportedFeature.MOUSE_GESTURE:
d = notification.data d = notification.data
data = struct.unpack("!" + (int(len(d) / 2) * "h"), d) data = struct.unpack("!" + (int(len(d) / 2) * "h"), d)
data_offset = 1 data_offset = 1
@ -1501,7 +1502,7 @@ def process_notification(device, notification: HIDPPNotification, feature) -> No
key_down, key_up = None, None key_down, key_up = None, None
# need to keep track of keys that are down to find a new key down # need to keep track of keys that are down to find a new key down
if notification.address == 0x00: 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]) new_keys_down = struct.unpack("!4H", notification.data[:8])
for key in new_keys_down: for key in new_keys_down:
if key and key not in 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 key_up = key
keys_down = new_keys_down keys_down = new_keys_down
# and also G 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] new_g_keys_down = struct.unpack("<I", notification.data[:4])[0]
for i in range(32): for i in range(32):
if new_g_keys_down & (0x01 << i) and not g_keys_down & (0x01 << i): 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)] key_up = CONTROL["G" + str(i + 1)]
g_keys_down = new_g_keys_down g_keys_down = new_g_keys_down
# and also M 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] new_m_keys_down = struct.unpack("!1B", notification.data[:1])[0]
for i in range(1, 9): for i in range(1, 9):
if new_m_keys_down & (0x01 << (i - 1)) and not m_keys_down & (0x01 << (i - 1)): 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)] key_up = CONTROL["M" + str(i)]
m_keys_down = new_m_keys_down m_keys_down = new_m_keys_down
# and also MR key # and also MR key
elif feature == FEATURE.MR: elif feature == SupportedFeature.MR:
new_mr_key_down = struct.unpack("!1B", notification.data[:1])[0] new_mr_key_down = struct.unpack("!1B", notification.data[:1])[0]
if not mr_key_down and new_mr_key_down: if not mr_key_down and new_mr_key_down:
key_down = CONTROL["MR"] key_down = CONTROL["MR"]
@ -1537,7 +1538,7 @@ def process_notification(device, notification: HIDPPNotification, feature) -> No
key_up = CONTROL["MR"] key_up = CONTROL["MR"]
mr_key_down = new_mr_key_down mr_key_down = new_mr_key_down
# keep track of thumb wheel movement # 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 if notification.data[4] <= 0x01: # when wheel starts, zero out last movement
thumb_wheel_displacement = 0 thumb_wheel_displacement = 0
thumb_wheel_displacement += signed(notification.data[0:2]) 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 ## 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., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import annotations
import logging import logging
import socket import socket
@ -44,9 +45,9 @@ from .hidpp20_constants import CHARGE_STATUS
from .hidpp20_constants import CHARGE_TYPE from .hidpp20_constants import CHARGE_TYPE
from .hidpp20_constants import DEVICE_KIND from .hidpp20_constants import DEVICE_KIND
from .hidpp20_constants import ERROR from .hidpp20_constants import ERROR
from .hidpp20_constants import FEATURE
from .hidpp20_constants import FIRMWARE_KIND from .hidpp20_constants import FIRMWARE_KIND
from .hidpp20_constants import GESTURE from .hidpp20_constants import GESTURE
from .hidpp20_constants import SupportedFeature
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -95,7 +96,7 @@ class FeaturesArray(dict):
return False return False
if self.count > 0: if self.count > 0:
return True 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: if reply is not None:
fs_index = reply[0] fs_index = reply[0]
if fs_index: if fs_index:
@ -105,14 +106,14 @@ class FeaturesArray(dict):
return False return False
else: else:
self.count = count[0] + 1 # ROOT feature not included in count self.count = count[0] + 1 # ROOT feature not included in count
self[FEATURE.ROOT] = 0 self[SupportedFeature.ROOT] = 0
self[FEATURE.FEATURE_SET] = fs_index self[SupportedFeature.FEATURE_SET] = fs_index
return True return True
else: else:
self.supported = False self.supported = False
return False return False
def get_feature(self, index: int) -> Optional[NamedInt]: def get_feature(self, index: int) -> SupportedFeature | None:
feature = self.inverse.get(index) feature = self.inverse.get(index)
if feature is not None: if feature is not None:
return feature return feature
@ -120,9 +121,13 @@ class FeaturesArray(dict):
feature = self.inverse.get(index) feature = self.inverse.get(index)
if feature is not None: if feature is not None:
return feature return feature
response = self.device.feature_request(FEATURE.FEATURE_SET, 0x10, index) response = self.device.feature_request(SupportedFeature.FEATURE_SET, 0x10, index)
if response: 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[feature] = index
self.version[feature] = response[3] self.version[feature] = response[3]
return feature return feature
@ -290,7 +295,7 @@ class ReprogrammableKeyV4(ReprogrammableKey):
def _getCidReporting(self): def _getCidReporting(self):
try: try:
mapped_data = self._device.feature_request( mapped_data = self._device.feature_request(
FEATURE.REPROG_CONTROLS_V4, SupportedFeature.REPROG_CONTROLS_V4,
0x20, 0x20,
*tuple(struct.pack("!H", self._cid)), *tuple(struct.pack("!H", self._cid)),
) )
@ -373,7 +378,7 @@ class ReprogrammableKeyV4(ReprogrammableKey):
pkt = tuple(struct.pack("!HBH", self._cid, bfield & 0xFF, remap)) 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. # 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. # 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): 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.") 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): def remap(self, data_bytes):
cid = common.int2bytes(self._cid, 2) cid = common.int2bytes(self._cid, 2)
if common.bytes2int(data_bytes) == special_keys.KEYS_Default: # map back to default 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) self._device.remap_keys._query_key(self.index)
return self._device.remap_keys.keys[self.index].data_bytes return self._device.remap_keys.keys[self.index].data_bytes
else: else:
self.actionId, self.remapped, self._modifierMask = struct.unpack("!BHB", data_bytes) self.actionId, self.remapped, self._modifierMask = struct.unpack("!BHB", data_bytes)
self.cidStatus = 0x01 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 return True
@ -451,10 +456,10 @@ class KeysArray:
assert device is not None assert device is not None
self.device = device self.device = device
self.lock = threading.Lock() self.lock = threading.Lock()
if FEATURE.REPROG_CONTROLS_V4 in self.device.features: if SupportedFeature.REPROG_CONTROLS_V4 in self.device.features:
self.keyversion = FEATURE.REPROG_CONTROLS_V4 self.keyversion = SupportedFeature.REPROG_CONTROLS_V4
elif FEATURE.REPROG_CONTROLS_V2 in self.device.features: elif SupportedFeature.REPROG_CONTROLS_V2 in self.device.features:
self.keyversion = FEATURE.REPROG_CONTROLS_V2 self.keyversion = SupportedFeature.REPROG_CONTROLS_V2
else: else:
if logger.isEnabledFor(logging.ERROR): if logger.isEnabledFor(logging.ERROR):
logger.error(f"Trying to read keys on device {device} which has no REPROG_CONTROLS(_VX) support.") 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): def _query_key(self, index: int):
if index < 0 or index >= len(self.keys): if index < 0 or index >= len(self.keys):
raise IndexError(index) 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: if keydata:
cid, tid, flags = struct.unpack("!HHB", keydata[:5]) cid, tid, flags = struct.unpack("!HHB", keydata[:5])
self.keys[index] = ReprogrammableKey(self.device, index, cid, tid, flags) self.keys[index] = ReprogrammableKey(self.device, index, cid, tid, flags)
@ -531,7 +536,7 @@ class KeysArrayV4(KeysArrayV2):
def _query_key(self, index: int): def _query_key(self, index: int):
if index < 0 or index >= len(self.keys): if index < 0 or index >= len(self.keys):
raise IndexError(index) 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: if keydata:
cid, tid, flags1, pos, group, gmask, flags2 = struct.unpack("!HHBBBBB", keydata[:9]) cid, tid, flags1, pos, group, gmask, flags2 = struct.unpack("!HHBBBBB", keydata[:9])
flags = flags1 | (flags2 << 8) flags = flags1 | (flags2 << 8)
@ -552,7 +557,7 @@ class KeysArrayPersistent(KeysArray):
@property @property
def capabilities(self): def capabilities(self):
if self._capabilities is None and self.device.online: 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!" 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 self._capabilities = struct.unpack("!H", capabilities[:2])[0] # flags saying what the mappings are possible
return self._capabilities return self._capabilities
@ -560,11 +565,11 @@ class KeysArrayPersistent(KeysArray):
def _query_key(self, index: int): def _query_key(self, index: int):
if index < 0 or index >= len(self.keys): if index < 0 or index >= len(self.keys):
raise IndexError(index) 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: if keydata:
key = struct.unpack("!H", keydata[:2])[0] key = struct.unpack("!H", keydata[:2])[0]
mapped_data = self.device.feature_request( mapped_data = self.device.feature_request(
FEATURE.PERSISTENT_REMAPPABLE_ACTION, SupportedFeature.PERSISTENT_REMAPPABLE_ACTION,
0x30, 0x30,
key >> 8, key >> 8,
key & 0xFF, key & 0xFF,
@ -708,7 +713,7 @@ class Gesture:
def enabled(self): # is the gesture enabled? def enabled(self): # is the gesture enabled?
if self._enabled is None and self.index is not None: if self._enabled is None and self.index is not None:
offset, mask = self.enable_offset_mask() 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 self._enabled = bool(result[0] & mask) if result else None
return self._enabled return self._enabled
@ -717,13 +722,15 @@ class Gesture:
return None return None
if self.index is not None: if self.index is not None:
offset, mask = self.enable_offset_mask() 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 return reply
def diverted(self): # is the gesture diverted? def diverted(self): # is the gesture diverted?
if self._diverted is None and self.diversion_index is not None: if self._diverted is None and self.diversion_index is not None:
offset, mask = self.diversion_offset_mask() 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 self._diverted = bool(result[0] & mask) if result else None
return self._diverted return self._diverted
@ -733,7 +740,7 @@ class Gesture:
if self.diversion_index is not None: if self.diversion_index is not None:
offset, mask = self.diversion_offset_mask() offset, mask = self.diversion_offset_mask()
reply = self._device.feature_request( reply = self._device.feature_request(
FEATURE.GESTURE_2, SupportedFeature.GESTURE_2,
0x40, 0x40,
offset, offset,
0x01, 0x01,
@ -776,7 +783,7 @@ class Param:
return self._value if self._value is not None else self.read() return self._value if self._value is not None else self.read()
def read(self): # returns the bytes for the parameter 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: if result:
self._value = common.bytes2int(result[: self.size]) self._value = common.bytes2int(result[: self.size])
return self._value return self._value
@ -788,14 +795,14 @@ class Param:
return self._default_value return self._default_value
def _read_default(self): 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: if result:
self._default_value = common.bytes2int(result[: self.size]) self._default_value = common.bytes2int(result[: self.size])
return self._default_value return self._default_value
def write(self, bytes): def write(self, bytes):
self._value = 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): def __str__(self):
return str(self.param) return str(self.param)
@ -820,7 +827,7 @@ class Spec:
def read(self): def read(self):
try: 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) except exceptions.FeatureCallError: # some calls produce an error (notably spec 5 multiplier on K400Plus)
if logger.isEnabledFor(logging.WARNING): if logger.isEnabledFor(logging.WARNING):
logger.warning( logger.warning(
@ -849,7 +856,7 @@ class Gestures:
field_high = 0x00 field_high = 0x00
while field_high != 0x01: # end of fields while field_high != 0x01: # end of fields
# retrieve the next eight 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: if not fields:
break break
for offset in range(8): 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""" """Information about the current settings of x1982 Backlight2 v3, but also works for previous versions"""
def __init__(self, device): def __init__(self, device):
response = device.feature_request(FEATURE.BACKLIGHT2, 0x00) response = device.feature_request(SupportedFeature.BACKLIGHT2, 0x00)
if not response: if not response:
raise exceptions.FeatureCallError(msg="No reply from device.") raise exceptions.FeatureCallError(msg="No reply from device.")
self.device = device self.device = device
@ -923,7 +930,7 @@ class Backlight:
self.options = (self.options & 0x07) | (self.mode << 3) self.options = (self.options & 0x07) | (self.mode << 3)
level = self.level if self.mode == 0x3 else 0 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) 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: 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 class LEDEffectsInfo: # effects that the LEDs can do, using COLOR_LED_EFFECTS
def __init__(self, device): def __init__(self, device):
self.device = 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.count, _, capabilities = struct.unpack("!BHH", info[0:5])
self.readable = capabilities & 0x1 self.readable = capabilities & 0x1
self.zones = [] self.zones = []
for i in range(0, self.count): 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): def to_command(self, index, setting):
return self.zones[index].to_command(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 class RGBEffectsInfo(LEDEffectsInfo): # effects that the LEDs can do using RGB_EFFECTS
def __init__(self, device): def __init__(self, device):
self.device = 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.count, _, capabilities = struct.unpack("!BBBHH", info[0:7])
self.readable = capabilities & 0x1 self.readable = capabilities & 0x1
self.zones = [] self.zones = []
for i in range(0, self.count): 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) 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): def get_profile_headers(cls, device):
i = 0 i = 0
headers = [] 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 s = 0x00
if chunk[0:4] == b"\x00\x00\x00\x00" or chunk[0:4] == b"\xff\xff\xff\xff": # look in ROM instead 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 s = 0x01
while chunk[0:2] != b"\xff\xff": while chunk[0:2] != b"\xff\xff":
sector, enabled = struct.unpack("!HB", chunk[0:3]) sector, enabled = struct.unpack("!HB", chunk[0:3])
headers.append((sector, enabled)) headers.append((sector, enabled))
i += 1 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 return headers
@classmethod @classmethod
def from_device(cls, device): def from_device(cls, device):
if not device.online: # wake the device up if necessary if not device.online: # wake the device up if necessary
device.ping() 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]) memory, profile, _macro = struct.unpack("!BBB", response[0:3])
if memory != 0x01 or profile > 0x04: if memory != 0x01 or profile > 0x04:
return return
@ -1366,11 +1373,11 @@ class OnboardProfiles:
bytes = b"" bytes = b""
o = 0 o = 0
while o < s - 15: 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 bytes += chunk
o += 16 o += 16
chunk = dev.feature_request( chunk = dev.feature_request(
FEATURE.ONBOARD_PROFILES, SupportedFeature.ONBOARD_PROFILES,
0x50, 0x50,
sector >> 8, sector >> 8,
sector & 0xFF, sector & 0xFF,
@ -1385,12 +1392,12 @@ class OnboardProfiles:
rbs = OnboardProfiles.read_sector(device, s, len(bs)) rbs = OnboardProfiles.read_sector(device, s, len(bs))
if rbs[:-2] == bs[:-2]: if rbs[:-2] == bs[:-2]:
return False 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 o = 0
while o < len(bs) - 1: 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 o += 16
device.feature_request(FEATURE.ONBOARD_PROFILES, 0x80) device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x80)
return True return True
def write(self, device): def write(self, device):
@ -1449,13 +1456,13 @@ class Hidpp20:
:returns: a list of FirmwareInfo tuples, ordered by firmware layer. :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: if count:
count = ord(count[:1]) count = ord(count[:1])
fw = [] fw = []
for index in range(0, count): 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: if fw_info:
level = ord(fw_info[:1]) & 0x0F level = ord(fw_info[:1]) & 0x0F
if level == 0 or level == 1: if level == 0 or level == 1:
@ -1475,7 +1482,7 @@ class Hidpp20:
def get_ids(self, device): def get_ids(self, device):
"""Reads a device's ids (unit and model numbers)""" """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: if ids:
unitId = ids[1:5] unitId = ids[1:5]
modelId = ids[7:13] modelId = ids[7:13]
@ -1495,7 +1502,7 @@ class Hidpp20:
:returns: a string describing the device type, or ``None`` if the device is :returns: a string describing the device type, or ``None`` if the device is
not available or does not support the ``DEVICE_NAME`` feature. 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: if kind:
kind = ord(kind[:1]) kind = ord(kind[:1])
try: try:
@ -1509,13 +1516,13 @@ class Hidpp20:
:returns: a string with the device name, or ``None`` if the device is not :returns: a string with the device name, or ``None`` if the device is not
available or does not support the ``DEVICE_NAME`` feature. 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: if name_length:
name_length = ord(name_length[:1]) name_length = ord(name_length[:1])
name = b"" name = b""
while len(name) < name_length: 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: if fragment:
name += fragment[: name_length - len(name)] name += fragment[: name_length - len(name)]
else: else:
@ -1530,13 +1537,13 @@ class Hidpp20:
:returns: a string with the device name, or ``None`` if the device is not :returns: a string with the device name, or ``None`` if the device is not
available or does not support the ``DEVICE_NAME`` feature. 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: if name_length:
name_length = ord(name_length[:1]) name_length = ord(name_length[:1])
name = b"" name = b""
while len(name) < name_length: 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: if fragment:
name += fragment[1 : name_length - len(name) + 1] name += fragment[1 : name_length - len(name) + 1]
else: else:
@ -1546,27 +1553,27 @@ class Hidpp20:
return name.decode("utf-8") return name.decode("utf-8")
def get_battery_status(self, device: Device): def get_battery_status(self, device: Device):
report = device.feature_request(FEATURE.BATTERY_STATUS) report = device.feature_request(SupportedFeature.BATTERY_STATUS)
if report: if report:
return decipher_battery_status(report) return decipher_battery_status(report)
def get_battery_unified(self, device: Device): 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: if report is not None:
return decipher_battery_unified(report) return decipher_battery_unified(report)
def get_battery_voltage(self, device: Device): 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: if report is not None:
return decipher_battery_voltage(report) return decipher_battery_voltage(report)
def get_adc_measurement(self, device: Device): def get_adc_measurement(self, device: Device):
try: # this feature call produces an error for headsets that are connected but inactive 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: if report is not None:
return decipher_adc_measurement(report) return decipher_adc_measurement(report)
except exceptions.FeatureCallError: 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): def get_battery(self, device, feature):
"""Return battery information - feature, approximate level, next, charging, voltage """Return battery information - feature, approximate level, next, charging, voltage
@ -1588,39 +1595,39 @@ class Hidpp20:
def get_keys(self, device: Device): def get_keys(self, device: Device):
# TODO: add here additional variants for other REPROG_CONTROLS # TODO: add here additional variants for other REPROG_CONTROLS
count = None count = None
if FEATURE.REPROG_CONTROLS_V2 in device.features: if SupportedFeature.REPROG_CONTROLS_V2 in device.features:
count = device.feature_request(FEATURE.REPROG_CONTROLS_V2) count = device.feature_request(SupportedFeature.REPROG_CONTROLS_V2)
return KeysArrayV2(device, ord(count[:1])) return KeysArrayV2(device, ord(count[:1]))
elif FEATURE.REPROG_CONTROLS_V4 in device.features: elif SupportedFeature.REPROG_CONTROLS_V4 in device.features:
count = device.feature_request(FEATURE.REPROG_CONTROLS_V4) count = device.feature_request(SupportedFeature.REPROG_CONTROLS_V4)
return KeysArrayV4(device, ord(count[:1])) return KeysArrayV4(device, ord(count[:1]))
return None return None
def get_remap_keys(self, device: Device): 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: if count:
return KeysArrayPersistent(device, ord(count[:1])) return KeysArrayPersistent(device, ord(count[:1]))
def get_gestures(self, device: Device): def get_gestures(self, device: Device):
if getattr(device, "_gestures", None) is not None: if getattr(device, "_gestures", None) is not None:
return device._gestures return device._gestures
if FEATURE.GESTURE_2 in device.features: if SupportedFeature.GESTURE_2 in device.features:
return Gestures(device) return Gestures(device)
def get_backlight(self, device: Device): def get_backlight(self, device: Device):
if getattr(device, "_backlight", None) is not None: if getattr(device, "_backlight", None) is not None:
return device._backlight return device._backlight
if FEATURE.BACKLIGHT2 in device.features: if SupportedFeature.BACKLIGHT2 in device.features:
return Backlight(device) return Backlight(device)
def get_profiles(self, device: Device): def get_profiles(self, device: Device):
if getattr(device, "_profiles", None) is not None: if getattr(device, "_profiles", None) is not None:
return device._profiles return device._profiles
if FEATURE.ONBOARD_PROFILES in device.features: if SupportedFeature.ONBOARD_PROFILES in device.features:
return OnboardProfiles.from_device(device) return OnboardProfiles.from_device(device)
def get_mouse_pointer_info(self, 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: if pointer_info:
dpi, flags = struct.unpack("!HB", pointer_info[:3]) dpi, flags = struct.unpack("!HB", pointer_info[:3])
acceleration = ("none", "low", "med", "high")[flags & 0x3] acceleration = ("none", "low", "med", "high")[flags & 0x3]
@ -1634,7 +1641,7 @@ class Hidpp20:
} }
def get_vertical_scrolling_info(self, device: Device): 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: if vertical_scrolling_info:
roller, ratchet, lines = struct.unpack("!BBB", vertical_scrolling_info[:3]) roller, ratchet, lines = struct.unpack("!BBB", vertical_scrolling_info[:3])
roller_type = ( roller_type = (
@ -1650,13 +1657,13 @@ class Hidpp20:
return {"roller": roller_type, "ratchet": ratchet, "lines": lines} return {"roller": roller_type, "ratchet": ratchet, "lines": lines}
def get_hi_res_scrolling_info(self, device: Device): 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: if hi_res_scrolling_info:
mode, resolution = struct.unpack("!BB", hi_res_scrolling_info[:2]) mode, resolution = struct.unpack("!BB", hi_res_scrolling_info[:2])
return mode, resolution return mode, resolution
def get_pointer_speed_info(self, device: Device): 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: if pointer_speed_info:
pointer_speed_hi, pointer_speed_lo = struct.unpack("!BB", pointer_speed_info[:2]) pointer_speed_hi, pointer_speed_lo = struct.unpack("!BB", pointer_speed_info[:2])
# if pointer_speed_lo > 0: # if pointer_speed_lo > 0:
@ -1664,16 +1671,16 @@ class Hidpp20:
return pointer_speed_hi + pointer_speed_lo / 256 return pointer_speed_hi + pointer_speed_lo / 256
def get_lowres_wheel_status(self, device: Device): 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: if lowres_wheel_status:
wheel_flag = struct.unpack("!B", lowres_wheel_status[:1])[0] wheel_flag = struct.unpack("!B", lowres_wheel_status[:1])[0]
wheel_reporting = ("HID", "HID++")[wheel_flag & 0x01] wheel_reporting = ("HID", "HID++")[wheel_flag & 0x01]
return wheel_reporting return wheel_reporting
def get_hires_wheel(self, device: Device): def get_hires_wheel(self, device: Device):
caps = device.feature_request(FEATURE.HIRES_WHEEL, 0x00) caps = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x00)
mode = device.feature_request(FEATURE.HIRES_WHEEL, 0x10) mode = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x10)
ratchet = device.feature_request(FEATURE.HIRES_WHEEL, 0x030) ratchet = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x030)
if caps and mode and ratchet: if caps and mode and ratchet:
# Parse caps # Parse caps
@ -1697,7 +1704,7 @@ class Hidpp20:
return multi, has_invert, has_ratchet, inv, res, target, ratchet return multi, has_invert, has_ratchet, inv, res, target, ratchet
def get_new_fn_inversion(self, device: Device): 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: if state:
inverted, default_inverted = struct.unpack("!BB", state[:2]) inverted, default_inverted = struct.unpack("!BB", state[:2])
inverted = (inverted & 0x01) != 0 inverted = (inverted & 0x01) != 0
@ -1705,18 +1712,18 @@ class Hidpp20:
return inverted, default_inverted return inverted, default_inverted
def get_host_names(self, device: Device): 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 = {} host_names = {}
if state: if state:
capability_flags, _ignore, numHosts, currentHost = struct.unpack("!BBBB", state[:4]) capability_flags, _ignore, numHosts, currentHost = struct.unpack("!BBBB", state[:4])
if capability_flags & 0x01: # device can get host names if capability_flags & 0x01: # device can get host names
for host in range(0, numHosts): 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]) _ignore, status, _ignore, _ignore, nameLen, _ignore = struct.unpack("!BBBBBB", hostinfo[:6])
name = "" name = ""
remaining = nameLen remaining = nameLen
while remaining > 0: 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: if name_piece:
name += name_piece[2 : 2 + min(remaining, 14)].decode() name += name_piece[2 : 2 + min(remaining, 14)].decode()
remaining = max(0, remaining - 14) remaining = max(0, remaining - 14)
@ -1735,62 +1742,64 @@ class Hidpp20:
currentName = bytearray(currentName, "utf-8") currentName = bytearray(currentName, "utf-8")
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
logger.info("Setting host name to %s", name) 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: if state:
flags, _ignore, _ignore, currentHost = struct.unpack("!BBBB", state[:4]) flags, _ignore, _ignore, currentHost = struct.unpack("!BBBB", state[:4])
if flags & 0x02: 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]) _ignore, _ignore, _ignore, _ignore, _ignore, maxNameLen = struct.unpack("!BBBBBB", hostinfo[:6])
if name[:maxNameLen] == currentName[:maxNameLen] and False: if name[:maxNameLen] == currentName[:maxNameLen] and False:
return True return True
length = min(maxNameLen, len(name)) length = min(maxNameLen, len(name))
chunk = 0 chunk = 0
while chunk < length: 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: if not response:
return False return False
chunk += 14 chunk += 14
return True return True
def get_onboard_mode(self, device: Device): 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: if state:
mode = struct.unpack("!B", state[:1])[0] mode = struct.unpack("!B", state[:1])[0]
return mode return mode
def set_onboard_mode(self, device: Device, 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 return state
def get_polling_rate(self, device: Device): 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: if state:
rate = struct.unpack("!B", state[:1])[0] rate = struct.unpack("!B", state[:1])[0]
return str(rate) + "ms" return str(rate) + "ms"
else: else:
rates = ["8ms", "4ms", "2ms", "1ms", "500us", "250us", "125us"] 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: if state:
rate = struct.unpack("!B", state[:1])[0] rate = struct.unpack("!B", state[:1])[0]
return rates[rate] return rates[rate]
def get_remaining_pairing(self, device: Device): 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: if result:
result = struct.unpack("!B", result[:1])[0] 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 return result
def config_change(self, device: Device, configuration, no_reply=False): 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 = { battery_functions = {
FEATURE.BATTERY_STATUS: Hidpp20.get_battery_status, SupportedFeature.BATTERY_STATUS: Hidpp20.get_battery_status,
FEATURE.BATTERY_VOLTAGE: Hidpp20.get_battery_voltage, SupportedFeature.BATTERY_VOLTAGE: Hidpp20.get_battery_voltage,
FEATURE.UNIFIED_BATTERY: Hidpp20.get_battery_unified, SupportedFeature.UNIFIED_BATTERY: Hidpp20.get_battery_unified,
FEATURE.ADC_MEASUREMENT: Hidpp20.get_adc_measurement, SupportedFeature.ADC_MEASUREMENT: Hidpp20.get_adc_measurement,
} }
@ -1807,7 +1816,7 @@ def decipher_battery_status(report: FixedBytes5) -> Tuple[Any, Battery]:
logger.debug( logger.debug(
"battery status %s%% charged, next %s%%, status %s", battery_discharge_level, battery_discharge_next_level, status "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): def decipher_battery_voltage(report):
@ -1845,7 +1854,7 @@ def decipher_battery_voltage(report):
charge_lvl, charge_lvl,
charge_type, 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): def decipher_battery_unified(report):
@ -1869,7 +1878,7 @@ def decipher_battery_unified(report):
else: else:
level = BatteryLevelApproximation.EMPTY 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): def decipher_adc_measurement(report):
@ -1882,4 +1891,4 @@ def decipher_adc_measurement(report):
break break
if flags & 0x01: if flags & 0x01:
status = BatteryStatus.RECHARGING if flags & 0x02 else BatteryStatus.DISCHARGING 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 ## 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., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from enum import IntEnum
from .common import NamedInts 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 A particular device might not support all these features, and may support other
unknown features as well. unknown features as well.
""" """
FEATURE = NamedInts(
ROOT=0x0000,
FEATURE_SET=0x0001, class SupportedFeature(IntEnum):
FEATURE_INFO=0x0002, ROOT = 0x0000
FEATURE_SET = 0x0001
FEATURE_INFO = 0x0002
# Common # Common
DEVICE_FW_VERSION=0x0003, DEVICE_FW_VERSION = 0x0003
DEVICE_UNIT_ID=0x0004, DEVICE_UNIT_ID = 0x0004
DEVICE_NAME=0x0005, DEVICE_NAME = 0x0005
DEVICE_GROUPS=0x0006, DEVICE_GROUPS = 0x0006
DEVICE_FRIENDLY_NAME=0x0007, DEVICE_FRIENDLY_NAME = 0x0007
KEEP_ALIVE=0x0008, KEEP_ALIVE = 0x0008
CONFIG_CHANGE=0x0020, CONFIG_CHANGE = 0x0020
CRYPTO_ID=0x0021, CRYPTO_ID = 0x0021
TARGET_SOFTWARE=0x0030, TARGET_SOFTWARE = 0x0030
WIRELESS_SIGNAL_STRENGTH=0x0080, WIRELESS_SIGNAL_STRENGTH = 0x0080
DFUCONTROL_LEGACY=0x00C0, DFUCONTROL_LEGACY = 0x00C0
DFUCONTROL_UNSIGNED=0x00C1, DFUCONTROL_UNSIGNED = 0x00C1
DFUCONTROL_SIGNED=0x00C2, DFUCONTROL_SIGNED = 0x00C2
DFUCONTROL=0x00C3, DFUCONTROL = 0x00C3
DFU=0x00D0, DFU = 0x00D0
BATTERY_STATUS=0x1000, BATTERY_STATUS = 0x1000
BATTERY_VOLTAGE=0x1001, BATTERY_VOLTAGE = 0x1001
UNIFIED_BATTERY=0x1004, UNIFIED_BATTERY = 0x1004
CHARGING_CONTROL=0x1010, CHARGING_CONTROL = 0x1010
LED_CONTROL=0x1300, LED_CONTROL = 0x1300
FORCE_PAIRING=0x1500, FORCE_PAIRING = 0x1500
GENERIC_TEST=0x1800, GENERIC_TEST = 0x1800
DEVICE_RESET=0x1802, DEVICE_RESET = 0x1802
OOBSTATE=0x1805, OOBSTATE = 0x1805
CONFIG_DEVICE_PROPS=0x1806, CONFIG_DEVICE_PROPS = 0x1806
CHANGE_HOST=0x1814, CHANGE_HOST = 0x1814
HOSTS_INFO=0x1815, HOSTS_INFO = 0x1815
BACKLIGHT=0x1981, BACKLIGHT = 0x1981
BACKLIGHT2=0x1982, BACKLIGHT2 = 0x1982
BACKLIGHT3=0x1983, BACKLIGHT3 = 0x1983
ILLUMINATION=0x1990, ILLUMINATION = 0x1990
PRESENTER_CONTROL=0x1A00, PRESENTER_CONTROL = 0x1A00
SENSOR_3D=0x1A01, SENSOR_3D = 0x1A01
REPROG_CONTROLS=0x1B00, REPROG_CONTROLS = 0x1B00
REPROG_CONTROLS_V2=0x1B01, REPROG_CONTROLS_V2 = 0x1B01
REPROG_CONTROLS_V2_2=0x1B02, # LogiOptions 2.10.73 features.xml REPROG_CONTROLS_V2_2 = 0x1B02 # LogiOptions 2.10.73 features.xml
REPROG_CONTROLS_V3=0x1B03, REPROG_CONTROLS_V3 = 0x1B03
REPROG_CONTROLS_V4=0x1B04, REPROG_CONTROLS_V4 = 0x1B04
REPORT_HID_USAGE=0x1BC0, REPORT_HID_USAGE = 0x1BC0
PERSISTENT_REMAPPABLE_ACTION=0x1C00, PERSISTENT_REMAPPABLE_ACTION = 0x1C00
WIRELESS_DEVICE_STATUS=0x1D4B, WIRELESS_DEVICE_STATUS = 0x1D4B
REMAINING_PAIRING=0x1DF0, REMAINING_PAIRING = 0x1DF0
FIRMWARE_PROPERTIES=0x1F1F, FIRMWARE_PROPERTIES = 0x1F1F
ADC_MEASUREMENT=0x1F20, ADC_MEASUREMENT = 0x1F20
# Mouse # Mouse
LEFT_RIGHT_SWAP=0x2001, LEFT_RIGHT_SWAP = 0x2001
SWAP_BUTTON_CANCEL=0x2005, SWAP_BUTTON_CANCEL = 0x2005
POINTER_AXIS_ORIENTATION=0x2006, POINTER_AXIS_ORIENTATION = 0x2006
VERTICAL_SCROLLING=0x2100, VERTICAL_SCROLLING = 0x2100
SMART_SHIFT=0x2110, SMART_SHIFT = 0x2110
SMART_SHIFT_ENHANCED=0x2111, SMART_SHIFT_ENHANCED = 0x2111
HI_RES_SCROLLING=0x2120, HI_RES_SCROLLING = 0x2120
HIRES_WHEEL=0x2121, HIRES_WHEEL = 0x2121
LOWRES_WHEEL=0x2130, LOWRES_WHEEL = 0x2130
THUMB_WHEEL=0x2150, THUMB_WHEEL = 0x2150
MOUSE_POINTER=0x2200, MOUSE_POINTER = 0x2200
ADJUSTABLE_DPI=0x2201, ADJUSTABLE_DPI = 0x2201
EXTENDED_ADJUSTABLE_DPI=0x2202, EXTENDED_ADJUSTABLE_DPI = 0x2202
POINTER_SPEED=0x2205, POINTER_SPEED = 0x2205
ANGLE_SNAPPING=0x2230, ANGLE_SNAPPING = 0x2230
SURFACE_TUNING=0x2240, SURFACE_TUNING = 0x2240
XY_STATS=0x2250, XY_STATS = 0x2250
WHEEL_STATS=0x2251, WHEEL_STATS = 0x2251
HYBRID_TRACKING=0x2400, HYBRID_TRACKING = 0x2400
# Keyboard # Keyboard
FN_INVERSION=0x40A0, FN_INVERSION = 0x40A0
NEW_FN_INVERSION=0x40A2, NEW_FN_INVERSION = 0x40A2
K375S_FN_INVERSION=0x40A3, K375S_FN_INVERSION = 0x40A3
ENCRYPTION=0x4100, ENCRYPTION = 0x4100
LOCK_KEY_STATE=0x4220, LOCK_KEY_STATE = 0x4220
SOLAR_DASHBOARD=0x4301, SOLAR_DASHBOARD = 0x4301
KEYBOARD_LAYOUT=0x4520, KEYBOARD_LAYOUT = 0x4520
KEYBOARD_DISABLE_KEYS=0x4521, KEYBOARD_DISABLE_KEYS = 0x4521
KEYBOARD_DISABLE_BY_USAGE=0x4522, KEYBOARD_DISABLE_BY_USAGE = 0x4522
DUALPLATFORM=0x4530, DUALPLATFORM = 0x4530
MULTIPLATFORM=0x4531, MULTIPLATFORM = 0x4531
KEYBOARD_LAYOUT_2=0x4540, KEYBOARD_LAYOUT_2 = 0x4540
CROWN=0x4600, CROWN = 0x4600
# Touchpad # Touchpad
TOUCHPAD_FW_ITEMS=0x6010, TOUCHPAD_FW_ITEMS = 0x6010
TOUCHPAD_SW_ITEMS=0x6011, TOUCHPAD_SW_ITEMS = 0x6011
TOUCHPAD_WIN8_FW_ITEMS=0x6012, TOUCHPAD_WIN8_FW_ITEMS = 0x6012
TAP_ENABLE=0x6020, TAP_ENABLE = 0x6020
TAP_ENABLE_EXTENDED=0x6021, TAP_ENABLE_EXTENDED = 0x6021
CURSOR_BALLISTIC=0x6030, CURSOR_BALLISTIC = 0x6030
TOUCHPAD_RESOLUTION=0x6040, TOUCHPAD_RESOLUTION = 0x6040
TOUCHPAD_RAW_XY=0x6100, TOUCHPAD_RAW_XY = 0x6100
TOUCHMOUSE_RAW_POINTS=0x6110, TOUCHMOUSE_RAW_POINTS = 0x6110
TOUCHMOUSE_6120=0x6120, TOUCHMOUSE_6120 = 0x6120
GESTURE=0x6500, GESTURE = 0x6500
GESTURE_2=0x6501, GESTURE_2 = 0x6501
# Gaming Devices # Gaming Devices
GKEY=0x8010, GKEY = 0x8010
MKEYS=0x8020, MKEYS = 0x8020
MR=0x8030, MR = 0x8030
BRIGHTNESS_CONTROL=0x8040, BRIGHTNESS_CONTROL = 0x8040
REPORT_RATE=0x8060, REPORT_RATE = 0x8060
EXTENDED_ADJUSTABLE_REPORT_RATE=0x8061, EXTENDED_ADJUSTABLE_REPORT_RATE = 0x8061
COLOR_LED_EFFECTS=0x8070, COLOR_LED_EFFECTS = 0x8070
RGB_EFFECTS=0x8071, RGB_EFFECTS = 0x8071
PER_KEY_LIGHTING=0x8080, PER_KEY_LIGHTING = 0x8080
PER_KEY_LIGHTING_V2=0x8081, PER_KEY_LIGHTING_V2 = 0x8081
MODE_STATUS=0x8090, MODE_STATUS = 0x8090
ONBOARD_PROFILES=0x8100, ONBOARD_PROFILES = 0x8100
MOUSE_BUTTON_SPY=0x8110, MOUSE_BUTTON_SPY = 0x8110
LATENCY_MONITORING=0x8111, LATENCY_MONITORING = 0x8111
GAMING_ATTACHMENTS=0x8120, GAMING_ATTACHMENTS = 0x8120
FORCE_FEEDBACK=0x8123, FORCE_FEEDBACK = 0x8123
# Headsets # Headsets
SIDETONE=0x8300, SIDETONE = 0x8300
EQUALIZER=0x8310, EQUALIZER = 0x8310
HEADSET_OUT=0x8320, HEADSET_OUT = 0x8320
# Fake features for Solaar internal use # Fake features for Solaar internal use
MOUSE_GESTURE=0xFE00, MOUSE_GESTURE = 0xFE00
)
FEATURE._fallback = lambda x: f"unknown:{x:04X}" def __str__(self):
return self.name.replace("_", " ")
FEATURE_FLAG = NamedInts(internal=0x20, hidden=0x40, obsolete=0x80) FEATURE_FLAG = NamedInts(internal=0x20, hidden=0x40, obsolete=0x80)

View File

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

View File

@ -629,8 +629,17 @@ class FeatureRW:
default_read_fnid = 0x00 default_read_fnid = 0x00
default_write_fnid = 0x10 default_write_fnid = 0x10
def __init__(self, feature, read_fnid=0x00, write_fnid=0x10, prefix=b"", suffix=b"", read_prefix=b"", no_reply=False): def __init__(
assert isinstance(feature, NamedInt) 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.feature = feature
self.read_fnid = read_fnid self.read_fnid = read_fnid
self.write_fnid = write_fnid self.write_fnid = write_fnid
@ -658,13 +667,13 @@ class FeatureRWMap(FeatureRW):
def __init__( def __init__(
self, self,
feature, feature: hidpp20_constants.SupportedFeature,
read_fnid=default_read_fnid, read_fnid=default_read_fnid,
write_fnid=default_write_fnid, write_fnid=default_write_fnid,
key_byte_count=default_key_byte_count, key_byte_count=default_key_byte_count,
no_reply=False, no_reply=False,
): ):
assert isinstance(feature, NamedInt) assert isinstance(feature, hidpp20_constants.SupportedFeature)
self.feature = feature self.feature = feature
self.read_fnid = read_fnid self.read_fnid = read_fnid
self.write_fnid = write_fnid self.write_fnid = write_fnid
@ -1422,7 +1431,10 @@ class ActionSettingRW:
def write(self, device, data_bytes): def write(self, device, data_bytes):
def handler(device, n): # Called on notification events from the device 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: if n.address == 0x00:
cids = struct.unpack("!HHHH", n.data[:8]) cids = struct.unpack("!HHHH", n.data[:8])
if not self.pressed and int(self.key.key) in cids: # trigger key pressed 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.keys = [] # the keys that can initiate processing
self.initiating_key = None # the key that did initiate processing self.initiating_key = None # the key that did initiate processing
self.active = False 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 assert self.feature_offset is not False
def handler(self, device, n): # Called on notification events from the device 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: if n.address == 0x00:
cids = struct.unpack("!HHHH", n.data[:8]) cids = struct.unpack("!HHHH", n.data[:8])
## generalize to list of keys ## generalize to list of keys
@ -1556,7 +1568,7 @@ class RawXYProcessing:
def apply_all_settings(device): 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 time.sleep(0.2) # delay to try to get out of race condition with Linux HID++ driver
persister = getattr(device, "persister", None) persister = getattr(device, "persister", None)
sensitives = persister.get("_sensitive", {}) if persister else {} 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 ## 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., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import annotations
import enum import enum
import logging import logging
import socket import socket
@ -20,6 +22,7 @@ import struct
import traceback import traceback
from time import time from time import time
from typing import Any
from typing import Callable from typing import Callable
from solaar.i18n import _ from solaar.i18n import _
@ -40,7 +43,7 @@ logger = logging.getLogger(__name__)
_hidpp20 = hidpp20.Hidpp20() _hidpp20 = hidpp20.Hidpp20()
_DK = hidpp10_constants.DEVICE_KIND _DK = hidpp10_constants.DEVICE_KIND
_F = hidpp20_constants.FEATURE _F = hidpp20_constants.SupportedFeature
_GG = hidpp20_constants.GESTURE _GG = hidpp20_constants.GESTURE
_GP = hidpp20_constants.PARAM _GP = hidpp20_constants.PARAM
@ -1795,18 +1798,20 @@ SETTINGS = [
] ]
def check_feature(device, sclass): def check_feature(device, settings_class: settings.Setting) -> None | bool | Any:
if sclass.feature not in device.features: if settings_class.feature not in device.features:
return 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 return
try: try:
detected = sclass.build(device) detected = settings_class.build(device)
if logger.isEnabledFor(logging.DEBUG): 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 return detected
except Exception as e: 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 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 LOGITECH_VENDOR_ID
from logitech_receiver.common import NamedInt from logitech_receiver.common import NamedInt
from logitech_receiver.common import strhex from logitech_receiver.common import strhex
from logitech_receiver.hidpp20_constants import SupportedFeature
from solaar import NAME from solaar import NAME
from solaar import __version__ from solaar import __version__
@ -152,7 +153,7 @@ def _print_device(dev, num=None):
version = dev.features.get_feature_version(int(feature)) version = dev.features.get_feature_version(int(feature))
version = version if version else 0 version = version if version else 0
print(" %2d: %-22s {%04X} V%s %s " % (index, feature, feature, version, ", ".join(flags))) 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) wheel = _hidpp20.get_hires_wheel(dev)
if wheel: if wheel:
multi, has_invert, has_switch, inv, res, target, ratchet = wheel multi, has_invert, has_switch, inv, res, target, ratchet = wheel
@ -169,7 +170,7 @@ def _print_device(dev, num=None):
print(" HID++ notification") print(" HID++ notification")
else: else:
print(" HID notification") print(" HID notification")
elif feature == hidpp20_constants.FEATURE.MOUSE_POINTER: elif feature == SupportedFeature.MOUSE_POINTER:
mouse_pointer = _hidpp20.get_mouse_pointer_info(dev) mouse_pointer = _hidpp20.get_mouse_pointer_info(dev)
if mouse_pointer: if mouse_pointer:
print(f" DPI: {mouse_pointer['dpi']}") print(f" DPI: {mouse_pointer['dpi']}")
@ -182,13 +183,13 @@ def _print_device(dev, num=None):
print(" Provide vertical tuning, trackball") print(" Provide vertical tuning, trackball")
else: else:
print(" No vertical tuning, standard mice") 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) vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev)
if vertical_scrolling_info: if vertical_scrolling_info:
print(f" Roller type: {vertical_scrolling_info['roller']}") print(f" Roller type: {vertical_scrolling_info['roller']}")
print(f" Ratchet per turn: {vertical_scrolling_info['ratchet']}") print(f" Ratchet per turn: {vertical_scrolling_info['ratchet']}")
print(f" Scroll lines: {vertical_scrolling_info['lines']}") 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) scrolling_mode, scrolling_resolution = _hidpp20.get_hi_res_scrolling_info(dev)
if scrolling_mode: if scrolling_mode:
print(" Hi-res scrolling enabled") print(" Hi-res scrolling enabled")
@ -196,30 +197,30 @@ def _print_device(dev, num=None):
print(" Hi-res scrolling disabled") print(" Hi-res scrolling disabled")
if scrolling_resolution: if scrolling_resolution:
print(f" Hi-res scrolling multiplier: {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) pointer_speed = _hidpp20.get_pointer_speed_info(dev)
if pointer_speed: if pointer_speed:
print(f" Pointer Speed: {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) wheel_status = _hidpp20.get_lowres_wheel_status(dev)
if wheel_status: if wheel_status:
print(f" Wheel Reports: {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) inversion = _hidpp20.get_new_fn_inversion(dev)
if inversion: if inversion:
inverted, default_inverted = inversion inverted, default_inverted = inversion
print(" Fn-swap:", "enabled" if inverted else "disabled") print(" Fn-swap:", "enabled" if inverted else "disabled")
print(" Fn-swap default:", "enabled" if default_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) host_names = _hidpp20.get_host_names(dev)
for host, (paired, name) in host_names.items(): for host, (paired, name) in host_names.items():
print(f" Host {host} ({'paired' if paired else 'unpaired'}): {name}") 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" Name: {_hidpp20.get_name(dev)}")
print(f" Kind: {_hidpp20.get_kind(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)}") 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): for fw in _hidpp20.get_firmware(dev):
extras = strhex(fw.extras) if fw.extras else "" extras = strhex(fw.extras) if fw.extras else ""
print(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}") print(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}")
@ -227,17 +228,14 @@ def _print_device(dev, num=None):
if ids: if ids:
unitId, modelId, tid_map = ids unitId, modelId, tid_map = ids
print(f" Unit ID: {unitId} Model ID: {modelId} Transport IDs: {tid_map}") print(f" Unit ID: {unitId} Model ID: {modelId} Transport IDs: {tid_map}")
elif ( elif feature == SupportedFeature.REPORT_RATE or feature == SupportedFeature.EXTENDED_ADJUSTABLE_REPORT_RATE:
feature == hidpp20_constants.FEATURE.REPORT_RATE
or feature == hidpp20_constants.FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE
):
print(f" Report Rate: {_hidpp20.get_polling_rate(dev)}") print(f" Report Rate: {_hidpp20.get_polling_rate(dev)}")
elif feature == hidpp20_constants.FEATURE.CONFIG_CHANGE: elif feature == SupportedFeature.CONFIG_CHANGE:
response = dev.feature_request(hidpp20_constants.FEATURE.CONFIG_CHANGE, 0x00) response = dev.feature_request(SupportedFeature.CONFIG_CHANGE, 0x00)
print(f" Configuration: {response.hex()}") 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))}") 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: if _hidpp20.get_onboard_mode(dev) == hidpp20_constants.ONBOARD_MODES.MODE_HOST:
mode = "Host" mode = "Host"
else: else:
@ -267,9 +265,9 @@ def _print_device(dev, num=None):
print(f" Has {len(dev.keys)} reprogrammable keys:") print(f" Has {len(dev.keys)} reprogrammable keys:")
for k in dev.keys: for k in dev.keys:
# TODO: add here additional variants for other REPROG_CONTROLS # 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))) 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)) print(" %2d: %-26s, default: %-27s => %-26s" % (k.index, k.key, k.default_task, k.mapped_to))
gmask_fmt = ",".join(k.group_mask) gmask_fmt = ",".join(k.group_mask)
gmask_fmt = gmask_fmt if gmask_fmt else "empty" 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 gi.repository import Gtk
from logitech_receiver import diversion from logitech_receiver import diversion
from logitech_receiver.diversion import Key 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 logitech_receiver.special_keys import CONTROL
from solaar.i18n import _ from solaar.i18n import _
@ -97,15 +97,15 @@ class MouseProcessUI(ConditionUI):
class FeatureUI(ConditionUI): class FeatureUI(ConditionUI):
CLASS = diversion.Feature CLASS = diversion.Feature
FEATURES_WITH_DIVERSION = [ FEATURES_WITH_DIVERSION = [
str(FEATURE.CROWN), str(SupportedFeature.CROWN),
str(FEATURE.THUMB_WHEEL), str(SupportedFeature.THUMB_WHEEL),
str(FEATURE.LOWRES_WHEEL), str(SupportedFeature.LOWRES_WHEEL),
str(FEATURE.HIRES_WHEEL), str(SupportedFeature.HIRES_WHEEL),
str(FEATURE.GESTURE_2), str(SupportedFeature.GESTURE_2),
str(FEATURE.REPROG_CONTROLS_V4), str(SupportedFeature.REPROG_CONTROLS_V4),
str(FEATURE.GKEY), str(SupportedFeature.GKEY),
str(FEATURE.MKEYS), str(SupportedFeature.MKEYS),
str(FEATURE.MR), str(SupportedFeature.MR),
] ]
def create_widgets(self): def create_widgets(self):
@ -120,7 +120,7 @@ class FeatureUI(ConditionUI):
self.field.set_valign(Gtk.Align.CENTER) self.field.set_valign(Gtk.Align.CENTER)
self.field.set_size_request(600, 0) self.field.set_size_request(600, 0)
self.field.connect("changed", self._on_update) 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) CompletionEntry.add_completion_to_entry(self.field.get_child(), all_features)
self.widgets[self.field] = (0, 1, 1, 1) self.widgets[self.field] = (0, 1, 1, 1)

View File

@ -407,8 +407,17 @@ class Device:
self.settings = [] self.settings = []
if self.feature is not None: if self.feature is not None:
self.features = hidpp20.FeaturesArray(self) self.features = hidpp20.FeaturesArray(self)
self.responses = [Response("010001", 0x0000, "0001"), Response("20", 0x0100)] + self.responses self.responses = [
self.responses.append(Response(f"{self.offset:0>2X}00{self.version:0>2X}", 0x0000, f"{self.feature:0>4X}")) 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: if self.setting_callback is None:
self.setting_callback = lambda x, y, z: None self.setting_callback = lambda x, y, z: None
self.add_notification_handler = lambda x, y: 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 import diversion
from logitech_receiver.base import HIDPPNotification from logitech_receiver.base import HIDPPNotification
from logitech_receiver.hidpp20_constants import FEATURE from logitech_receiver.hidpp20_constants import SupportedFeature
@pytest.fixture @pytest.fixture
@ -104,14 +104,14 @@ def test_feature():
"feature, data", "feature, data",
[ [
( (
FEATURE.REPROG_CONTROLS_V4, SupportedFeature.REPROG_CONTROLS_V4,
[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
), ),
(FEATURE.GKEY, [0x01, 0x02, 0x03, 0x04]), (SupportedFeature.GKEY, [0x01, 0x02, 0x03, 0x04]),
(FEATURE.MKEYS, [0x01, 0x02, 0x03, 0x04]), (SupportedFeature.MKEYS, [0x01, 0x02, 0x03, 0x04]),
(FEATURE.MR, [0x01, 0x02, 0x03, 0x04]), (SupportedFeature.MR, [0x01, 0x02, 0x03, 0x04]),
(FEATURE.THUMB_WHEEL, [0x01, 0x02, 0x03, 0x04, 0x05]), (SupportedFeature.THUMB_WHEEL, [0x01, 0x02, 0x03, 0x04, 0x05]),
(FEATURE.DEVICE_UNIT_ID, [0x01, 0x02, 0x03, 0x04, 0x05]), (SupportedFeature.DEVICE_UNIT_ID, [0x01, 0x02, 0x03, 0x04, 0x05]),
], ],
) )
def test_process_notification(feature, data): 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 result == expected_result
assert result2 == 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 len(featuresarray) == expected_count
assert bool(featuresarray) == expected_result 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, expected0, expected1, expected2, expected5, expected5v",
[ [
(device_zerofeatures, None, None, None, None, None), (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): 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) result2 = featuresarray.get_feature(2)
result5 = featuresarray.get_feature(5) result5 = featuresarray.get_feature(5)
result2r = featuresarray.get_feature(2) 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 result0 == expected0
assert result1 == expected1 assert result1 == expected1
@ -94,15 +94,15 @@ def test_FeaturesArray_get_feature(device, expected0, expected1, expected2, expe
( (
device_standard, device_standard,
[ [
(hidpp20_constants.FEATURE.ROOT, 0), (hidpp20_constants.SupportedFeature.ROOT, 0),
(hidpp20_constants.FEATURE.FEATURE_SET, 1), (hidpp20_constants.SupportedFeature.FEATURE_SET, 1),
(hidpp20_constants.FEATURE.CONFIG_CHANGE, 2), (hidpp20_constants.SupportedFeature.CONFIG_CHANGE, 2),
(hidpp20_constants.FEATURE.DEVICE_FW_VERSION, 3), (hidpp20_constants.SupportedFeature.DEVICE_FW_VERSION, 3),
(common.NamedInt(256, "unknown:0100"), 4), (common.NamedInt(256, "unknown:0100"), 4),
(hidpp20_constants.FEATURE.REPROG_CONTROLS_V4, 5), (hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, 5),
(None, 6), (None, 6),
(None, 7), (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(): def test_FeaturesArray_setitem():
featuresarray = hidpp20.FeaturesArray(device_standard) featuresarray = hidpp20.FeaturesArray(device_standard)
featuresarray[hidpp20_constants.FEATURE.ROOT] = 3 featuresarray[hidpp20_constants.SupportedFeature.ROOT] = 3
featuresarray[hidpp20_constants.FEATURE.FEATURE_SET] = 5 featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] = 5
featuresarray[hidpp20_constants.FEATURE.FEATURE_SET] = 4 featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] = 4
assert featuresarray[hidpp20_constants.FEATURE.FEATURE_SET] == 4 assert featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] == 4
assert featuresarray.inverse[4] == hidpp20_constants.FEATURE.FEATURE_SET assert featuresarray.inverse[4] == hidpp20_constants.SupportedFeature.FEATURE_SET
def test_FeaturesArray_delitem(): def test_FeaturesArray_delitem():
@ -141,10 +141,10 @@ def test_FeaturesArray_getitem(device, expected0, expected1, expected2, expected
featuresarray = hidpp20.FeaturesArray(device) featuresarray = hidpp20.FeaturesArray(device)
device.features = featuresarray device.features = featuresarray
result_get0 = featuresarray[hidpp20_constants.FEATURE.ROOT] result_get0 = featuresarray[hidpp20_constants.SupportedFeature.ROOT]
result_get1 = featuresarray[hidpp20_constants.FEATURE.REPROG_CONTROLS_V4] result_get1 = featuresarray[hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4]
result_get2 = featuresarray[hidpp20_constants.FEATURE.GKEY] result_get2 = featuresarray[hidpp20_constants.SupportedFeature.GKEY]
result_1v = featuresarray.get_feature_version(hidpp20_constants.FEATURE.REPROG_CONTROLS_V4) result_1v = featuresarray.get_feature_version(hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4)
assert result_get0 == expected0 assert result_get0 == expected0
assert result_get1 == expected1 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 # 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): 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) device._keys = _hidpp20.get_keys(device)
key = device.keys[index] 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): 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] 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 = _hidpp20.get_keys(device)
device._keys._ensure_all_keys_queried() # do this now so that the last requests are sets device._keys._ensure_all_keys_queried() # do this now so that the last requests are sets
spy_request = mocker.spy(device, "request") 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("040000", 0x0000, "1C00"),
fake_hidpp.Response("00", 0x440, f"{cid:04X}" + "FF" + remap), 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) key = hidpp20.PersistentRemappableAction(device, index, cid, actionId, remapped, mask, status)
spy_request = mocker.spy(device, "request") spy_request = mocker.spy(device, "request")
@ -387,7 +393,7 @@ def test_KeysArrayV4_index(key, index):
device_key = fake_hidpp.Device( 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): 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 = _hidpp20.get_remap_keys(device)
device._remap_keys._ensure_all_keys_queried() 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): 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) gestures = _hidpp20.get_gestures(device)
gesture = gestures.gesture(gest) 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): 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) gestures = _hidpp20.get_gestures(device)
param = gestures.param(prm) 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): 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) gestures = _hidpp20.get_gestures(device)
spec = gestures.specs[id] spec = gestures.specs[id]
@ -569,7 +577,7 @@ def test_Spec(responses, id, s, byte_count, value, string):
def test_Gestures(): def test_Gestures():
device = fake_hidpp.Device( 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) gestures = _hidpp20.get_gestures(device)
@ -600,7 +608,9 @@ responses_backlight = [
fake_hidpp.Response("0101FF00020003000400", 0x0410, "0101FF00020003000400"), 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(): def test_Backlight():
@ -655,7 +665,7 @@ def test_LEDEffectSetting(hex, ID, color, speed, period, intensity, ramp, form):
"feature, function, response, ID, capabilities, period", "feature, function, response, ID, capabilities, period",
[ [
[ [
hidpp20_constants.FEATURE.COLOR_LED_EFFECTS, hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS,
0x20, 0x20,
fake_hidpp.Response("0102000300040005", 0x0420, "010200"), fake_hidpp.Response("0102000300040005", 0x0420, "010200"),
3, 3,
@ -663,7 +673,7 @@ def test_LEDEffectSetting(hex, ID, color, speed, period, intensity, ramp, form):
5, 5,
], ],
[ [
hidpp20_constants.FEATURE.COLOR_LED_EFFECTS, hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS,
0x20, 0x20,
fake_hidpp.Response("0102000700080009", 0x0420, "010200"), fake_hidpp.Response("0102000700080009", 0x0420, "010200"),
7, 7,
@ -687,8 +697,8 @@ def test_LEDEffectInfo(feature, function, response, ID, capabilities, period):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, function, offset, effect_function, responses, index, location, count, id_1", "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.SupportedFeature.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.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): 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): def test_LEDZoneInfo_to_command(responses, setting, expected_command):
device = fake_hidpp.Device(feature=hidpp20_constants.FEATURE.COLOR_LED_EFFECTS, responses=responses, offset=0x07) device = fake_hidpp.Device(feature=hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, responses=responses, offset=0x07)
zone = hidpp20.LEDZoneInfo(hidpp20_constants.FEATURE.COLOR_LED_EFFECTS, 0x10, 0, 0x20, device, 0) zone = hidpp20.LEDZoneInfo(hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, 0x10, 0, 0x20, device, 0)
command = zone.to_command(setting) command = zone.to_command(setting)
@ -727,8 +737,15 @@ def test_LEDZoneInfo_to_command(responses, setting, expected_command):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, cls, responses, readable, count, count_0", "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): 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): def test_OnboardProfiles_device(responses, name, count, buttons, gbuttons, sectors, size):
device = fake_hidpp.Device( 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 device._profiles = None
profiles = _hidpp20.get_profiles(device) profiles = _hidpp20.get_profiles(device)

View File

@ -18,7 +18,7 @@ import pytest
from logitech_receiver import common from logitech_receiver import common
from logitech_receiver import hidpp20 from logitech_receiver import hidpp20
from logitech_receiver import hidpp20_constants from logitech_receiver.hidpp20_constants import SupportedFeature
from . import fake_hidpp from . import fake_hidpp
@ -31,7 +31,7 @@ def test_get_firmware():
fake_hidpp.Response("01414243030401000101000102030405", 0x0410, "00"), fake_hidpp.Response("01414243030401000101000102030405", 0x0410, "00"),
fake_hidpp.Response("02414243030401000101000102030405", 0x0410, "01"), 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) result = _hidpp20.get_firmware(device)
@ -42,7 +42,7 @@ def test_get_firmware():
def test_get_ids(): def test_get_ids():
responses = [fake_hidpp.Response("FF12345678000D123456789ABC", 0x0400)] 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) unitId, modelId, tid_map = _hidpp20.get_ids(device)
@ -53,7 +53,7 @@ def test_get_ids():
def test_get_kind(): def test_get_kind():
responses = [fake_hidpp.Response("00", 0x0420)] 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) result = _hidpp20.get_kind(device)
@ -67,7 +67,7 @@ def test_get_name():
fake_hidpp.Response("4142434445464748494A4B4C4D4E4F", 0x0410, "00"), fake_hidpp.Response("4142434445464748494A4B4C4D4E4F", 0x0410, "00"),
fake_hidpp.Response("505152530000000000000000000000", 0x0410, "0F"), 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) result = _hidpp20.get_name(device)
@ -80,7 +80,7 @@ def test_get_friendly_name():
fake_hidpp.Response("004142434445464748494A4B4C4D4E", 0x0410, "00"), fake_hidpp.Response("004142434445464748494A4B4C4D4E", 0x0410, "00"),
fake_hidpp.Response("0E4F50515253000000000000000000", 0x0410, "0E"), 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) result = _hidpp20.get_friendly_name(device)
@ -89,11 +89,11 @@ def test_get_friendly_name():
def test_get_battery_status(): def test_get_battery_status():
responses = [fake_hidpp.Response("502000FFFF", 0x0400)] 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) 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.level == 80
assert battery.next_level == 32 assert battery.next_level == 32
assert battery.status == common.BatteryStatus.DISCHARGING assert battery.status == common.BatteryStatus.DISCHARGING
@ -101,11 +101,11 @@ def test_get_battery_status():
def test_get_battery_voltage(): def test_get_battery_voltage():
responses = [fake_hidpp.Response("1000FFFFFF", 0x0400)] 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) 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.level == 90
assert battery.status == common.BatteryStatus.RECHARGING assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000 assert battery.voltage == 0x1000
@ -113,22 +113,22 @@ def test_get_battery_voltage():
def test_get_battery_unified(): def test_get_battery_unified():
responses = [fake_hidpp.Response("500100FFFF", 0x0410)] 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) 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.level == 80
assert battery.status == common.BatteryStatus.DISCHARGING assert battery.status == common.BatteryStatus.DISCHARGING
def test_get_adc_measurement(): def test_get_adc_measurement():
responses = [fake_hidpp.Response("100003", 0x0400)] 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) 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.level == 90
assert battery.status == common.BatteryStatus.RECHARGING assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000 assert battery.voltage == 0x1000
@ -136,11 +136,11 @@ def test_get_adc_measurement():
def test_get_battery(): def test_get_battery():
responses = [fake_hidpp.Response("502000FFFF", 0x0400)] 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.level == 80
assert battery.next_level == 32 assert battery.next_level == 32
assert battery.status == common.BatteryStatus.DISCHARGING assert battery.status == common.BatteryStatus.DISCHARGING
@ -148,15 +148,15 @@ def test_get_battery():
def test_get_battery_none(): def test_get_battery_none():
responses = [ responses = [
fake_hidpp.Response(None, 0x0000, f"{hidpp20_constants.FEATURE.BATTERY_STATUS:0>4X}"), fake_hidpp.Response(None, 0x0000, f"{int(SupportedFeature.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_VOLTAGE):0>4X}"),
fake_hidpp.Response("500100ffff", 0x0410), 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) 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.level == 80
assert battery.status == common.BatteryStatus.DISCHARGING assert battery.status == common.BatteryStatus.DISCHARGING
@ -170,7 +170,7 @@ def test_get_battery_none():
def test_get_mouse_pointer_info(): def test_get_mouse_pointer_info():
responses = [fake_hidpp.Response("01000A", 0x0400)] 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) result = _hidpp20.get_mouse_pointer_info(device)
@ -184,7 +184,7 @@ def test_get_mouse_pointer_info():
def test_get_vertical_scrolling_info(): def test_get_vertical_scrolling_info():
responses = [fake_hidpp.Response("01080C", 0x0400)] 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) result = _hidpp20.get_vertical_scrolling_info(device)
@ -193,7 +193,7 @@ def test_get_vertical_scrolling_info():
def test_get_hi_res_scrolling_info(): def test_get_hi_res_scrolling_info():
responses = [fake_hidpp.Response("0102", 0x0400)] 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) 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(): def test_get_pointer_speed_info():
responses = [fake_hidpp.Response("0102", 0x0400)] 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) result = _hidpp20.get_pointer_speed_info(device)
@ -212,7 +212,7 @@ def test_get_pointer_speed_info():
def test_get_lowres_wheel_status(): def test_get_lowres_wheel_status():
responses = [fake_hidpp.Response("01", 0x0400)] 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) result = _hidpp20.get_lowres_wheel_status(device)
@ -225,7 +225,7 @@ def test_get_hires_wheel():
fake_hidpp.Response("05FF", 0x0410), fake_hidpp.Response("05FF", 0x0410),
fake_hidpp.Response("03FF", 0x0430), 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) 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(): def test_get_new_fn_inversion():
responses = [fake_hidpp.Response("0300", 0x0400)] 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) 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): 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) 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): 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") result = _hidpp20.set_host_name(device, "ABCDEFGHIJKLMNOPQRSTUVWX")
@ -313,7 +313,7 @@ def test_set_host_name(responses, expected_result):
def test_get_onboard_mode(): def test_get_onboard_mode():
responses = [fake_hidpp.Response("03FFFFFFFF", 0x0420)] 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) result = _hidpp20.get_onboard_mode(device)
@ -322,7 +322,7 @@ def test_get_onboard_mode():
def test_set_onboard_mode(): def test_set_onboard_mode():
responses = [fake_hidpp.Response("03FFFFFFFF", 0x0410, "03")] 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) 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("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), fake_hidpp.Response("04FFFF", 0x0420),
], ],
"500us", "500us",
@ -346,7 +346,7 @@ def test_get_polling_rate(
responses, responses,
expected_result, 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) result = _hidpp20.get_polling_rate(device)
@ -355,7 +355,7 @@ def test_get_polling_rate(
def test_get_remaining_pairing(): def test_get_remaining_pairing():
responses = [fake_hidpp.Response("03FFFF", 0x0400)] 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) result = _hidpp20.get_remaining_pairing(device)
@ -364,7 +364,7 @@ def test_get_remaining_pairing():
def test_config_change(): def test_config_change():
responses = [fake_hidpp.Response("03FFFF", 0x0410, "02")] 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) result = _hidpp20.config_change(device, 0x2)
@ -376,7 +376,7 @@ def test_decipher_battery_status():
feature, battery = hidpp20.decipher_battery_status(report) 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.level == 80
assert battery.next_level == 32 assert battery.next_level == 32
assert battery.status == common.BatteryStatus.DISCHARGING assert battery.status == common.BatteryStatus.DISCHARGING
@ -387,7 +387,7 @@ def test_decipher_battery_voltage():
feature, battery = hidpp20.decipher_battery_voltage(report) 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.level == 90
assert battery.status == common.BatteryStatus.RECHARGING assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000 assert battery.voltage == 0x1000
@ -398,7 +398,7 @@ def test_decipher_battery_unified():
feature, battery = hidpp20.decipher_battery_unified(report) 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.level == 80
assert battery.status == common.BatteryStatus.DISCHARGING assert battery.status == common.BatteryStatus.DISCHARGING
@ -408,7 +408,7 @@ def test_decipher_adc_measurement():
feature, battery = hidpp20.decipher_adc_measurement(report) 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.level == 90
assert battery.status == common.BatteryStatus.RECHARGING assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000 assert battery.voltage == 0x1000

View File

@ -795,7 +795,7 @@ def test_key_template(test, mocker):
], ],
) )
def test_SpeedChange_action(responses, currentSpeed, newSpeed, 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") spy_setting_callback = mocker.spy(device, "setting_callback")
settings_templates.check_feature_settings(device, device.settings) # need to set up all the settings settings_templates.check_feature_settings(device, device.settings) # need to set up all the settings
device.persister = {"pointer_speed": currentSpeed, "_speed-change": newSpeed} device.persister = {"pointer_speed": currentSpeed, "_speed-change": newSpeed}