Introduce Device protocol and type hints
This commit is contained in:
parent
675cd6ee34
commit
469c04faaf
|
@ -20,6 +20,7 @@ import logging
|
||||||
import threading as _threading
|
import threading as _threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from typing import Callable
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import hidapi as _hid
|
import hidapi as _hid
|
||||||
|
@ -66,8 +67,8 @@ class DeviceFactory:
|
||||||
|
|
||||||
class Device:
|
class Device:
|
||||||
instances = []
|
instances = []
|
||||||
read_register = hidpp10.read_register
|
read_register: Callable = hidpp10.read_register
|
||||||
write_register = hidpp10.write_register
|
write_register: Callable = hidpp10.write_register
|
||||||
|
|
||||||
def __init__(self, receiver, number, online, pairing_info=None, handle=None, device_info=None, setting_callback=None):
|
def __init__(self, receiver, number, online, pairing_info=None, handle=None, device_info=None, setting_callback=None):
|
||||||
assert receiver or device_info
|
assert receiver or device_info
|
||||||
|
|
|
@ -13,10 +13,17 @@
|
||||||
## 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
|
||||||
|
|
||||||
from .common import Battery as _Battery
|
from typing import Any
|
||||||
|
|
||||||
|
from typing_extensions import Protocol
|
||||||
|
|
||||||
|
from .common import Battery
|
||||||
|
from .common import BatteryChargeApproximation
|
||||||
|
from .common import BatteryStatus
|
||||||
from .common import FirmwareInfo as _FirmwareInfo
|
from .common import FirmwareInfo as _FirmwareInfo
|
||||||
from .common import bytes2int as _bytes2int
|
from .common import bytes2int as _bytes2int
|
||||||
from .common import int2bytes as _int2bytes
|
from .common import int2bytes as _int2bytes
|
||||||
|
@ -26,19 +33,36 @@ from .hidpp20_constants import FIRMWARE_KIND
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
#
|
|
||||||
# functions
|
class Device(Protocol):
|
||||||
#
|
def request(self, request_id, *params):
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kind(self) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def online(self) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def protocol(self) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def registers(self) -> list:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
def read_register(device, register_number, *params):
|
def read_register(device: Device, register_number, *params):
|
||||||
assert device is not None, f"tried to read register {register_number:02X} from invalid device {device}"
|
assert device is not None, f"tried to read register {register_number:02X} from invalid device {device}"
|
||||||
# support long registers by adding a 2 in front of the register number
|
# support long registers by adding a 2 in front of the register number
|
||||||
request_id = 0x8100 | (int(register_number) & 0x2FF)
|
request_id = 0x8100 | (int(register_number) & 0x2FF)
|
||||||
return device.request(request_id, *params)
|
return device.request(request_id, *params)
|
||||||
|
|
||||||
|
|
||||||
def write_register(device, register_number, *value):
|
def write_register(device: Device, register_number, *value):
|
||||||
assert device is not None, f"tried to write register {register_number:02X} to invalid device {device}"
|
assert device is not None, f"tried to write register {register_number:02X} to invalid device {device}"
|
||||||
# support long registers by adding a 2 in front of the register number
|
# support long registers by adding a 2 in front of the register number
|
||||||
request_id = 0x8000 | (int(register_number) & 0x2FF)
|
request_id = 0x8000 | (int(register_number) & 0x2FF)
|
||||||
|
@ -59,7 +83,7 @@ def set_configuration_pending_flags(receiver, devices):
|
||||||
|
|
||||||
|
|
||||||
class Hidpp10:
|
class Hidpp10:
|
||||||
def get_battery(self, device):
|
def get_battery(self, device: Device):
|
||||||
assert device is not None
|
assert device is not None
|
||||||
assert device.kind is not None
|
assert device.kind is not None
|
||||||
if not device.online:
|
if not device.online:
|
||||||
|
@ -89,7 +113,7 @@ class Hidpp10:
|
||||||
device.registers.append(REGISTERS.battery_status)
|
device.registers.append(REGISTERS.battery_status)
|
||||||
return parse_battery_status(REGISTERS.battery_status, reply)
|
return parse_battery_status(REGISTERS.battery_status, reply)
|
||||||
|
|
||||||
def get_firmware(self, device):
|
def get_firmware(self, device: Device):
|
||||||
assert device is not None
|
assert device is not None
|
||||||
|
|
||||||
firmware = [None, None, None]
|
firmware = [None, None, None]
|
||||||
|
@ -124,7 +148,7 @@ class Hidpp10:
|
||||||
if any(firmware):
|
if any(firmware):
|
||||||
return tuple(f for f in firmware if f)
|
return tuple(f for f in firmware if f)
|
||||||
|
|
||||||
def set_3leds(self, device, battery_level=None, charging=None, warning=None):
|
def set_3leds(self, device: Device, battery_level=None, charging=None, warning=None):
|
||||||
assert device is not None
|
assert device is not None
|
||||||
assert device.kind is not None
|
assert device.kind is not None
|
||||||
if not device.online:
|
if not device.online:
|
||||||
|
@ -134,17 +158,17 @@ class Hidpp10:
|
||||||
return
|
return
|
||||||
|
|
||||||
if battery_level is not None:
|
if battery_level is not None:
|
||||||
if battery_level < _Battery.APPROX.critical:
|
if battery_level < BatteryChargeApproximation.CRITICAL:
|
||||||
# 1 orange, and force blink
|
# 1 orange, and force blink
|
||||||
v1, v2 = 0x22, 0x00
|
v1, v2 = 0x22, 0x00
|
||||||
warning = True
|
warning = True
|
||||||
elif battery_level < _Battery.APPROX.low:
|
elif battery_level < BatteryChargeApproximation.LOW:
|
||||||
# 1 orange
|
# 1 orange
|
||||||
v1, v2 = 0x22, 0x00
|
v1, v2 = 0x22, 0x00
|
||||||
elif battery_level < _Battery.APPROX.good:
|
elif battery_level < BatteryChargeApproximation.GOOD:
|
||||||
# 1 green
|
# 1 green
|
||||||
v1, v2 = 0x20, 0x00
|
v1, v2 = 0x20, 0x00
|
||||||
elif battery_level < _Battery.APPROX.full:
|
elif battery_level < BatteryChargeApproximation.FULL:
|
||||||
# 2 greens
|
# 2 greens
|
||||||
v1, v2 = 0x20, 0x02
|
v1, v2 = 0x20, 0x02
|
||||||
else:
|
else:
|
||||||
|
@ -166,10 +190,10 @@ class Hidpp10:
|
||||||
|
|
||||||
write_register(device, REGISTERS.three_leds, v1, v2)
|
write_register(device, REGISTERS.three_leds, v1, v2)
|
||||||
|
|
||||||
def get_notification_flags(self, device):
|
def get_notification_flags(self, device: Device):
|
||||||
return self._get_register(device, REGISTERS.notifications)
|
return self._get_register(device, REGISTERS.notifications)
|
||||||
|
|
||||||
def set_notification_flags(self, device, *flag_bits):
|
def set_notification_flags(self, device: Device, *flag_bits):
|
||||||
assert device is not None
|
assert device is not None
|
||||||
|
|
||||||
# Avoid a call if the device is not online,
|
# Avoid a call if the device is not online,
|
||||||
|
@ -184,10 +208,10 @@ class Hidpp10:
|
||||||
result = write_register(device, REGISTERS.notifications, _int2bytes(flag_bits, 3))
|
result = write_register(device, REGISTERS.notifications, _int2bytes(flag_bits, 3))
|
||||||
return result is not None
|
return result is not None
|
||||||
|
|
||||||
def get_device_features(self, device):
|
def get_device_features(self, device: Device):
|
||||||
return self._get_register(device, REGISTERS.mouse_button_flags)
|
return self._get_register(device, REGISTERS.mouse_button_flags)
|
||||||
|
|
||||||
def _get_register(self, device, register):
|
def _get_register(self, device: Device, register):
|
||||||
assert device is not None
|
assert device is not None
|
||||||
|
|
||||||
# Avoid a call if the device is not online,
|
# Avoid a call if the device is not online,
|
||||||
|
@ -203,50 +227,61 @@ class Hidpp10:
|
||||||
return _bytes2int(flags)
|
return _bytes2int(flags)
|
||||||
|
|
||||||
|
|
||||||
def parse_battery_status(register, reply):
|
def parse_battery_status(register, reply) -> Battery | None:
|
||||||
|
def status_byte_to_charge(status_byte_: int) -> BatteryChargeApproximation:
|
||||||
|
if status_byte_ == 7:
|
||||||
|
charge_ = BatteryChargeApproximation.FULL
|
||||||
|
elif status_byte_ == 5:
|
||||||
|
charge_ = BatteryChargeApproximation.GOOD
|
||||||
|
elif status_byte_ == 3:
|
||||||
|
charge_ = BatteryChargeApproximation.LOW
|
||||||
|
elif status_byte_ == 1:
|
||||||
|
charge_ = BatteryChargeApproximation.CRITICAL
|
||||||
|
else:
|
||||||
|
# pure 'charging' notifications may come without a status
|
||||||
|
charge_ = BatteryChargeApproximation.EMPTY
|
||||||
|
return charge_
|
||||||
|
|
||||||
|
def status_byte_to_battery_status(status_byte_: int) -> BatteryStatus:
|
||||||
|
if status_byte_ == 0x30:
|
||||||
|
status_text_ = BatteryStatus.DISCHARGING
|
||||||
|
elif status_byte_ == 0x50:
|
||||||
|
status_text_ = BatteryStatus.RECHARGING
|
||||||
|
elif status_byte_ == 0x90:
|
||||||
|
status_text_ = BatteryStatus.FULL
|
||||||
|
else:
|
||||||
|
status_text_ = None
|
||||||
|
return status_text_
|
||||||
|
|
||||||
|
def charging_byte_to_status_text(charging_byte_: int) -> BatteryStatus:
|
||||||
|
if charging_byte_ == 0x00:
|
||||||
|
status_text_ = BatteryStatus.DISCHARGING
|
||||||
|
elif charging_byte_ & 0x21 == 0x21:
|
||||||
|
status_text_ = BatteryStatus.RECHARGING
|
||||||
|
elif charging_byte_ & 0x22 == 0x22:
|
||||||
|
status_text_ = BatteryStatus.FULL
|
||||||
|
else:
|
||||||
|
logger.warning("could not parse 0x07 battery status: %02X (level %02X)", charging_byte_, status_byte)
|
||||||
|
status_text_ = None
|
||||||
|
return status_text_
|
||||||
|
|
||||||
if register == REGISTERS.battery_charge:
|
if register == REGISTERS.battery_charge:
|
||||||
charge = ord(reply[:1])
|
charge = ord(reply[:1])
|
||||||
status_byte = ord(reply[2:3]) & 0xF0
|
status_byte = ord(reply[2:3]) & 0xF0
|
||||||
status_text = (
|
|
||||||
_Battery.STATUS.discharging
|
battery_status = status_byte_to_battery_status(status_byte)
|
||||||
if status_byte == 0x30
|
return Battery(charge, None, battery_status, None)
|
||||||
else _Battery.STATUS.recharging
|
|
||||||
if status_byte == 0x50
|
|
||||||
else _Battery.STATUS.full
|
|
||||||
if status_byte == 0x90
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
return _Battery(charge, None, status_text, None)
|
|
||||||
|
|
||||||
if register == REGISTERS.battery_status:
|
if register == REGISTERS.battery_status:
|
||||||
status_byte = ord(reply[:1])
|
status_byte = ord(reply[:1])
|
||||||
charge = (
|
|
||||||
_Battery.APPROX.full
|
|
||||||
if status_byte == 7 # full
|
|
||||||
else _Battery.APPROX.good
|
|
||||||
if status_byte == 5 # good
|
|
||||||
else _Battery.APPROX.low
|
|
||||||
if status_byte == 3 # low
|
|
||||||
else _Battery.APPROX.critical
|
|
||||||
if status_byte == 1 # critical
|
|
||||||
# pure 'charging' notifications may come without a status
|
|
||||||
else _Battery.APPROX.empty
|
|
||||||
)
|
|
||||||
|
|
||||||
charging_byte = ord(reply[1:2])
|
charging_byte = ord(reply[1:2])
|
||||||
if charging_byte == 0x00:
|
|
||||||
status_text = _Battery.STATUS.discharging
|
status_text = charging_byte_to_status_text(charging_byte)
|
||||||
elif charging_byte & 0x21 == 0x21:
|
charge = status_byte_to_charge(status_byte)
|
||||||
status_text = _Battery.STATUS.recharging
|
|
||||||
elif charging_byte & 0x22 == 0x22:
|
|
||||||
status_text = _Battery.STATUS.full
|
|
||||||
else:
|
|
||||||
logger.warning("could not parse 0x07 battery status: %02X (level %02X)", charging_byte, status_byte)
|
|
||||||
status_text = None
|
|
||||||
|
|
||||||
if charging_byte & 0x03 and status_byte == 0:
|
if charging_byte & 0x03 and status_byte == 0:
|
||||||
# some 'charging' notifications may come with no battery level information
|
# some 'charging' notifications may come with no battery level information
|
||||||
charge = None
|
charge = None
|
||||||
|
|
||||||
# Return None for next charge level and voltage as these are not in HID++ 1.0 spec
|
# Return None for next charge level and voltage as these are not in HID++ 1.0 spec
|
||||||
return _Battery(charge, None, status_text, None)
|
return Battery(charge, None, status_text, None)
|
||||||
|
|
|
@ -27,6 +27,7 @@ from typing import Optional
|
||||||
import yaml as _yaml
|
import yaml as _yaml
|
||||||
|
|
||||||
from solaar.i18n import _
|
from solaar.i18n import _
|
||||||
|
from typing_extensions import Protocol
|
||||||
|
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
from . import hidpp10_constants as _hidpp10_constants
|
from . import hidpp10_constants as _hidpp10_constants
|
||||||
|
@ -53,6 +54,30 @@ logger = logging.getLogger(__name__)
|
||||||
KIND_MAP = {kind: _hidpp10_constants.DEVICE_KIND[str(kind)] for kind in DEVICE_KIND}
|
KIND_MAP = {kind: _hidpp10_constants.DEVICE_KIND[str(kind)] for kind in DEVICE_KIND}
|
||||||
|
|
||||||
|
|
||||||
|
class Device(Protocol):
|
||||||
|
def feature_request(self, feature: FEATURE) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
def request(self) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def features(self) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _gestures(self) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _backlight(self) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _profiles(self) -> Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
class FeaturesArray(dict):
|
class FeaturesArray(dict):
|
||||||
def __init__(self, device):
|
def __init__(self, device):
|
||||||
assert device is not None
|
assert device is not None
|
||||||
|
@ -159,7 +184,7 @@ class ReprogrammableKey:
|
||||||
- flags {List[str]} -- capabilities and desired software handling of the control
|
- flags {List[str]} -- capabilities and desired software handling of the control
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, device, index, cid, tid, flags):
|
def __init__(self, device: Device, index, cid, tid, flags):
|
||||||
self._device = device
|
self._device = device
|
||||||
self.index = index
|
self.index = index
|
||||||
self._cid = cid
|
self._cid = cid
|
||||||
|
@ -201,7 +226,7 @@ class ReprogrammableKeyV4(ReprogrammableKey):
|
||||||
- mapping_flags {List[str]} -- mapping flags set on the control
|
- mapping_flags {List[str]} -- mapping flags set on the control
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, device, index, cid, tid, flags, pos, group, gmask):
|
def __init__(self, device: Device, index, cid, tid, flags, pos, group, gmask):
|
||||||
ReprogrammableKey.__init__(self, device, index, cid, tid, flags)
|
ReprogrammableKey.__init__(self, device, index, cid, tid, flags)
|
||||||
self.pos = pos
|
self.pos = pos
|
||||||
self.group = group
|
self.group = group
|
||||||
|
@ -460,7 +485,7 @@ class KeysArray:
|
||||||
|
|
||||||
|
|
||||||
class KeysArrayV2(KeysArray):
|
class KeysArrayV2(KeysArray):
|
||||||
def __init__(self, device, count, version=1):
|
def __init__(self, device: Device, count, version=1):
|
||||||
super().__init__(device, count, version)
|
super().__init__(device, count, version)
|
||||||
"""The mapping from Control IDs to their native Task IDs.
|
"""The mapping from Control IDs to their native Task IDs.
|
||||||
For example, Control "Left Button" is mapped to Task "Left Click".
|
For example, Control "Left Button" is mapped to Task "Left Click".
|
||||||
|
@ -1417,7 +1442,7 @@ class Hidpp20:
|
||||||
offset = offset + 2
|
offset = offset + 2
|
||||||
return (unitId.hex().upper(), modelId.hex().upper(), tid_map)
|
return (unitId.hex().upper(), modelId.hex().upper(), tid_map)
|
||||||
|
|
||||||
def get_kind(self, device):
|
def get_kind(self, device: Device):
|
||||||
"""Reads a device's type.
|
"""Reads a device's type.
|
||||||
|
|
||||||
:see DEVICE_KIND:
|
:see DEVICE_KIND:
|
||||||
|
@ -1431,7 +1456,7 @@ class Hidpp20:
|
||||||
# logger.debug("device %d type %d = %s", devnumber, kind, DEVICE_KIND[kind])
|
# logger.debug("device %d type %d = %s", devnumber, kind, DEVICE_KIND[kind])
|
||||||
return KIND_MAP[DEVICE_KIND[kind]]
|
return KIND_MAP[DEVICE_KIND[kind]]
|
||||||
|
|
||||||
def get_name(self, device):
|
def get_name(self, device: Device):
|
||||||
"""Reads a device's name.
|
"""Reads a device's name.
|
||||||
|
|
||||||
: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
|
||||||
|
@ -1452,7 +1477,7 @@ class Hidpp20:
|
||||||
|
|
||||||
return name.decode("utf-8")
|
return name.decode("utf-8")
|
||||||
|
|
||||||
def get_friendly_name(self, device):
|
def get_friendly_name(self, device: Device):
|
||||||
"""Reads a device's friendly name.
|
"""Reads a device's friendly name.
|
||||||
|
|
||||||
: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
|
||||||
|
@ -1473,22 +1498,22 @@ class Hidpp20:
|
||||||
|
|
||||||
return name.decode("utf-8")
|
return name.decode("utf-8")
|
||||||
|
|
||||||
def get_battery_status(self, device):
|
def get_battery_status(self, device: Device):
|
||||||
report = device.feature_request(FEATURE.BATTERY_STATUS)
|
report = device.feature_request(FEATURE.BATTERY_STATUS)
|
||||||
if report:
|
if report:
|
||||||
return decipher_battery_status(report)
|
return decipher_battery_status(report)
|
||||||
|
|
||||||
def get_battery_unified(self, device):
|
def get_battery_unified(self, device: Device):
|
||||||
report = device.feature_request(FEATURE.UNIFIED_BATTERY, 0x10)
|
report = device.feature_request(FEATURE.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):
|
def get_battery_voltage(self, device: Device):
|
||||||
report = device.feature_request(FEATURE.BATTERY_VOLTAGE)
|
report = device.feature_request(FEATURE.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):
|
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(FEATURE.ADC_MEASUREMENT)
|
||||||
if report is not None:
|
if report is not None:
|
||||||
|
@ -1513,7 +1538,7 @@ class Hidpp20:
|
||||||
return result
|
return result
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_keys(self, 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 FEATURE.REPROG_CONTROLS_V2 in device.features:
|
||||||
|
@ -1524,30 +1549,30 @@ class Hidpp20:
|
||||||
return KeysArrayV4(device, ord(count[:1]))
|
return KeysArrayV4(device, ord(count[:1]))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_remap_keys(self, device):
|
def get_remap_keys(self, device: Device):
|
||||||
count = device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x10)
|
count = device.feature_request(FEATURE.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):
|
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 FEATURE.GESTURE_2 in device.features:
|
||||||
return Gestures(device)
|
return Gestures(device)
|
||||||
|
|
||||||
def get_backlight(self, 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 FEATURE.BACKLIGHT2 in device.features:
|
||||||
return Backlight(device)
|
return Backlight(device)
|
||||||
|
|
||||||
def get_profiles(self, 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 FEATURE.ONBOARD_PROFILES in device.features:
|
||||||
return OnboardProfiles.from_device(device)
|
return OnboardProfiles.from_device(device)
|
||||||
|
|
||||||
def get_mouse_pointer_info(self, device):
|
def get_mouse_pointer_info(self, device: Device):
|
||||||
pointer_info = device.feature_request(FEATURE.MOUSE_POINTER)
|
pointer_info = device.feature_request(FEATURE.MOUSE_POINTER)
|
||||||
if pointer_info:
|
if pointer_info:
|
||||||
dpi, flags = _unpack("!HB", pointer_info[:3])
|
dpi, flags = _unpack("!HB", pointer_info[:3])
|
||||||
|
@ -1561,7 +1586,7 @@ class Hidpp20:
|
||||||
"suggest_vertical_orientation": suggest_vertical_orientation,
|
"suggest_vertical_orientation": suggest_vertical_orientation,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_vertical_scrolling_info(self, device):
|
def get_vertical_scrolling_info(self, device: Device):
|
||||||
vertical_scrolling_info = device.feature_request(FEATURE.VERTICAL_SCROLLING)
|
vertical_scrolling_info = device.feature_request(FEATURE.VERTICAL_SCROLLING)
|
||||||
if vertical_scrolling_info:
|
if vertical_scrolling_info:
|
||||||
roller, ratchet, lines = _unpack("!BBB", vertical_scrolling_info[:3])
|
roller, ratchet, lines = _unpack("!BBB", vertical_scrolling_info[:3])
|
||||||
|
@ -1577,13 +1602,13 @@ class Hidpp20:
|
||||||
)[roller]
|
)[roller]
|
||||||
return {"roller": roller_type, "ratchet": ratchet, "lines": lines}
|
return {"roller": roller_type, "ratchet": ratchet, "lines": lines}
|
||||||
|
|
||||||
def get_hi_res_scrolling_info(self, 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(FEATURE.HI_RES_SCROLLING)
|
||||||
if hi_res_scrolling_info:
|
if hi_res_scrolling_info:
|
||||||
mode, resolution = _unpack("!BB", hi_res_scrolling_info[:2])
|
mode, resolution = _unpack("!BB", hi_res_scrolling_info[:2])
|
||||||
return mode, resolution
|
return mode, resolution
|
||||||
|
|
||||||
def get_pointer_speed_info(self, device):
|
def get_pointer_speed_info(self, device: Device):
|
||||||
pointer_speed_info = device.feature_request(FEATURE.POINTER_SPEED)
|
pointer_speed_info = device.feature_request(FEATURE.POINTER_SPEED)
|
||||||
if pointer_speed_info:
|
if pointer_speed_info:
|
||||||
pointer_speed_hi, pointer_speed_lo = _unpack("!BB", pointer_speed_info[:2])
|
pointer_speed_hi, pointer_speed_lo = _unpack("!BB", pointer_speed_info[:2])
|
||||||
|
@ -1591,14 +1616,14 @@ class Hidpp20:
|
||||||
# pointer_speed_lo = pointer_speed_lo
|
# pointer_speed_lo = pointer_speed_lo
|
||||||
return pointer_speed_hi + pointer_speed_lo / 256
|
return pointer_speed_hi + pointer_speed_lo / 256
|
||||||
|
|
||||||
def get_lowres_wheel_status(self, device):
|
def get_lowres_wheel_status(self, device: Device):
|
||||||
lowres_wheel_status = device.feature_request(FEATURE.LOWRES_WHEEL)
|
lowres_wheel_status = device.feature_request(FEATURE.LOWRES_WHEEL)
|
||||||
if lowres_wheel_status:
|
if lowres_wheel_status:
|
||||||
wheel_flag = _unpack("!B", lowres_wheel_status[:1])[0]
|
wheel_flag = _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):
|
def get_hires_wheel(self, device: Device):
|
||||||
caps = device.feature_request(FEATURE.HIRES_WHEEL, 0x00)
|
caps = device.feature_request(FEATURE.HIRES_WHEEL, 0x00)
|
||||||
mode = device.feature_request(FEATURE.HIRES_WHEEL, 0x10)
|
mode = device.feature_request(FEATURE.HIRES_WHEEL, 0x10)
|
||||||
ratchet = device.feature_request(FEATURE.HIRES_WHEEL, 0x030)
|
ratchet = device.feature_request(FEATURE.HIRES_WHEEL, 0x030)
|
||||||
|
@ -1624,7 +1649,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):
|
def get_new_fn_inversion(self, device: Device):
|
||||||
state = device.feature_request(FEATURE.NEW_FN_INVERSION, 0x00)
|
state = device.feature_request(FEATURE.NEW_FN_INVERSION, 0x00)
|
||||||
if state:
|
if state:
|
||||||
inverted, default_inverted = _unpack("!BB", state[:2])
|
inverted, default_inverted = _unpack("!BB", state[:2])
|
||||||
|
@ -1632,7 +1657,7 @@ class Hidpp20:
|
||||||
default_inverted = (default_inverted & 0x01) != 0
|
default_inverted = (default_inverted & 0x01) != 0
|
||||||
return inverted, default_inverted
|
return inverted, default_inverted
|
||||||
|
|
||||||
def get_host_names(self, device):
|
def get_host_names(self, device: Device):
|
||||||
state = device.feature_request(FEATURE.HOSTS_INFO, 0x00)
|
state = device.feature_request(FEATURE.HOSTS_INFO, 0x00)
|
||||||
host_names = {}
|
host_names = {}
|
||||||
if state:
|
if state:
|
||||||
|
@ -1658,7 +1683,7 @@ class Hidpp20:
|
||||||
host_names[currentHost] = (host_names[currentHost][0], hostname)
|
host_names[currentHost] = (host_names[currentHost][0], hostname)
|
||||||
return host_names
|
return host_names
|
||||||
|
|
||||||
def set_host_name(self, device, name, currentName=""):
|
def set_host_name(self, device: Device, name, currentName=""):
|
||||||
name = bytearray(name, "utf-8")
|
name = bytearray(name, "utf-8")
|
||||||
currentName = bytearray(currentName, "utf-8")
|
currentName = bytearray(currentName, "utf-8")
|
||||||
if logger.isEnabledFor(logging.INFO):
|
if logger.isEnabledFor(logging.INFO):
|
||||||
|
@ -1680,18 +1705,18 @@ class Hidpp20:
|
||||||
chunk += 14
|
chunk += 14
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_onboard_mode(self, device):
|
def get_onboard_mode(self, device: Device):
|
||||||
state = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x20)
|
state = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x20)
|
||||||
|
|
||||||
if state:
|
if state:
|
||||||
mode = _unpack("!B", state[:1])[0]
|
mode = _unpack("!B", state[:1])[0]
|
||||||
return mode
|
return mode
|
||||||
|
|
||||||
def set_onboard_mode(self, device, mode):
|
def set_onboard_mode(self, device: Device, mode):
|
||||||
state = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x10, mode)
|
state = device.feature_request(FEATURE.ONBOARD_PROFILES, 0x10, mode)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def get_polling_rate(self, device):
|
def get_polling_rate(self, device: Device):
|
||||||
state = device.feature_request(FEATURE.REPORT_RATE, 0x10)
|
state = device.feature_request(FEATURE.REPORT_RATE, 0x10)
|
||||||
if state:
|
if state:
|
||||||
rate = _unpack("!B", state[:1])[0]
|
rate = _unpack("!B", state[:1])[0]
|
||||||
|
@ -1703,14 +1728,14 @@ class Hidpp20:
|
||||||
rate = _unpack("!B", state[:1])[0]
|
rate = _unpack("!B", state[:1])[0]
|
||||||
return rates[rate]
|
return rates[rate]
|
||||||
|
|
||||||
def get_remaining_pairing(self, device):
|
def get_remaining_pairing(self, device: Device):
|
||||||
result = device.feature_request(FEATURE.REMAINING_PAIRING, 0x0)
|
result = device.feature_request(FEATURE.REMAINING_PAIRING, 0x0)
|
||||||
if result:
|
if result:
|
||||||
result = _unpack("!B", result[:1])[0]
|
result = _unpack("!B", result[:1])[0]
|
||||||
FEATURE._fallback = lambda x: f"unknown:{x:04X}"
|
FEATURE._fallback = lambda x: f"unknown:{x:04X}"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def config_change(self, 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(FEATURE.CONFIG_CHANGE, 0x10, configuration, no_reply=no_reply)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,6 @@ from .common import strhex as _strhex
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_hidpp10 = hidpp10.Hidpp10()
|
|
||||||
_hidpp20 = hidpp20.Hidpp20()
|
|
||||||
_R = _hidpp10_constants.REGISTERS
|
_R = _hidpp10_constants.REGISTERS
|
||||||
_F = _hidpp20_constants.FEATURE
|
_F = _hidpp20_constants.FEATURE
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import hidapi as _hid
|
import hidapi as _hid
|
||||||
|
@ -62,6 +63,8 @@ class Receiver:
|
||||||
The paired devices are available through the sequence interface.
|
The paired devices are available through the sequence interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
read_register: Callable = hidpp10.read_register
|
||||||
|
write_register: Callable = hidpp10.write_register
|
||||||
number = 0xFF
|
number = 0xFF
|
||||||
kind = None
|
kind = None
|
||||||
|
|
||||||
|
@ -255,9 +258,6 @@ class Receiver:
|
||||||
def reset_pairing(self):
|
def reset_pairing(self):
|
||||||
self.pairing = Pairing()
|
self.pairing = Pairing()
|
||||||
|
|
||||||
read_register = hidpp10.read_register
|
|
||||||
write_register = hidpp10.write_register
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
connected_devices = self.count()
|
connected_devices = self.count()
|
||||||
found_devices = 0
|
found_devices = 0
|
||||||
|
|
Loading…
Reference in New Issue