Skip to content

Commit

Permalink
Refactor DeviceKind: Remove dependency of NamedInts
Browse files Browse the repository at this point in the history
  • Loading branch information
some_developer authored and MattHag committed Dec 20, 2024
1 parent 840d4df commit 28e5566
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 66 deletions.
44 changes: 21 additions & 23 deletions lib/logitech_receiver/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
- the name or codename should be different from what the device reports
"""

from .hidpp10_constants import DEVICE_KIND
from .hidpp10_constants import DeviceKind
from .hidpp10_constants import Registers as Reg


Expand Down Expand Up @@ -71,15 +71,15 @@ def _D(
):
if kind is None:
kind = (
DEVICE_KIND.mouse
DeviceKind.MOUSE
if "Mouse" in name
else DEVICE_KIND.keyboard
else DeviceKind.KEYBOARD
if "Keyboard" in name
else DEVICE_KIND.numpad
else DeviceKind.NUMPAD
if "Number Pad" in name
else DEVICE_KIND.touchpad
else DeviceKind.TOUCHPAD
if "Touchpad" in name
else DEVICE_KIND.trackball
else DeviceKind.TRACKBALL
if "Trackball" in name
else None
)
Expand All @@ -92,11 +92,11 @@ def _D(
assert w[0:1] == "4", f"{name} has protocol {protocol:0.1f}, wpid {w}"
else:
if w[0:1] == "1":
assert kind == DEVICE_KIND.mouse, f"{name} has protocol {protocol:0.1f}, wpid {w}"
assert kind == DeviceKind.MOUSE, f"{name} has protocol {protocol:0.1f}, wpid {w}"
elif w[0:1] == "2":
assert kind in (
DEVICE_KIND.keyboard,
DEVICE_KIND.numpad,
DeviceKind.KEYBOARD,
DeviceKind.NUMPAD,
), f"{name} has protocol {protocol:0.1f}, wpid {w}"

device_descriptor = _DeviceDescriptor(
Expand Down Expand Up @@ -254,7 +254,7 @@ def get_btid(btid):
_D(
"VX Revolution",
codename="VX Revolution",
kind=DEVICE_KIND.mouse,
kind=DeviceKind.MOUSE,
protocol=1.0,
wpid=("1006", "100D", "0612"),
registers=(Reg.BATTERY_CHARGE,),
Expand All @@ -263,15 +263,15 @@ def get_btid(btid):
"MX Air",
codename="MX Air",
protocol=1.0,
kind=DEVICE_KIND.mouse,
kind=DeviceKind.MOUSE,
wpid=("1007", "100E"),
registers=Reg.BATTERY_CHARGE,
)
_D(
"MX Revolution",
codename="MX Revolution",
protocol=1.0,
kind=DEVICE_KIND.mouse,
kind=DeviceKind.MOUSE,
wpid=("1008", "100C"),
registers=(Reg.BATTERY_CHARGE,),
)
Expand Down Expand Up @@ -307,7 +307,7 @@ def get_btid(btid):
"MX 1100 Cordless Laser Mouse",
codename="MX 1100",
protocol=1.0,
kind=DEVICE_KIND.mouse,
kind=DeviceKind.MOUSE,
wpid="1014",
registers=(Reg.BATTERY_CHARGE,),
)
Expand Down Expand Up @@ -421,7 +421,7 @@ def get_btid(btid):
_D("MX518 Gaming Mouse", codename="MX518", usbid=0xC08E, interface=1)
_D("G703 Hero Gaming Mouse", codename="G703 Hero", usbid=0xC090)
_D("G903 Hero Gaming Mouse", codename="G903 Hero", usbid=0xC091)
_D(None, kind=DEVICE_KIND.mouse, usbid=0xC092, interface=1) # two mice share this ID
_D(None, kind=DeviceKind.MOUSE, usbid=0xC092, interface=1) # two mice share this ID
_D("M500S Mouse", codename="M500S", usbid=0xC093, interface=1)
# _D('G600 Gaming Mouse', codename='G600 Gaming', usbid=0xc24a, interface=1) # not an HID++ device
_D("G500s Gaming Mouse", codename="G500s Gaming", usbid=0xC24E, interface=1, protocol=1.0)
Expand All @@ -438,29 +438,27 @@ def get_btid(btid):

_D("Wireless Touchpad", codename="Wireless Touch", protocol=2.0, wpid="4011")
_D("Wireless Rechargeable Touchpad T650", codename="T650", protocol=2.0, wpid="4101")
_D(
"G Powerplay", codename="Powerplay", protocol=2.0, kind=DEVICE_KIND.touchpad, wpid="405F"
) # To override self-identification
_D("G Powerplay", codename="Powerplay", protocol=2.0, kind=DeviceKind.TOUCHPAD, wpid="405F") # To override self-identification

# Headset

_D("G533 Gaming Headset", codename="G533 Headset", protocol=2.0, interface=3, kind=DEVICE_KIND.headset, usbid=0x0A66)
_D("G535 Gaming Headset", codename="G535 Headset", protocol=2.0, interface=3, kind=DEVICE_KIND.headset, usbid=0x0AC4)
_D("G935 Gaming Headset", codename="G935 Headset", protocol=2.0, interface=3, kind=DEVICE_KIND.headset, usbid=0x0A87)
_D("G733 Gaming Headset", codename="G733 Headset", protocol=2.0, interface=3, kind=DEVICE_KIND.headset, usbid=0x0AB5)
_D("G533 Gaming Headset", codename="G533 Headset", protocol=2.0, interface=3, kind=DeviceKind.HEADSET, usbid=0x0A66)
_D("G535 Gaming Headset", codename="G535 Headset", protocol=2.0, interface=3, kind=DeviceKind.HEADSET, usbid=0x0AC4)
_D("G935 Gaming Headset", codename="G935 Headset", protocol=2.0, interface=3, kind=DeviceKind.HEADSET, usbid=0x0A87)
_D("G733 Gaming Headset", codename="G733 Headset", protocol=2.0, interface=3, kind=DeviceKind.HEADSET, usbid=0x0AB5)
_D(
"G733 Gaming Headset",
codename="G733 Headset New",
protocol=2.0,
interface=3,
kind=DEVICE_KIND.headset,
kind=DeviceKind.HEADSET,
usbid=0x0AFE,
)
_D(
"PRO X Wireless Gaming Headset",
codename="PRO Headset",
protocol=2.0,
interface=3,
kind=DEVICE_KIND.headset,
kind=DeviceKind.HEADSET,
usbid=0x0ABA,
)
34 changes: 16 additions & 18 deletions lib/logitech_receiver/hidpp10_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,28 @@
from enum import IntEnum
from typing import List

from .common import NamedInts

"""HID constants for HID++ 1.0.
Most of them as defined by the official Logitech HID++ 1.0
documentation, some of them guessed.
"""

DEVICE_KIND = NamedInts(
unknown=0x00,
keyboard=0x01,
mouse=0x02,
numpad=0x03,
presenter=0x04,
remote=0x07,
trackball=0x08,
touchpad=0x09,
tablet=0x0A,
gamepad=0x0B,
joystick=0x0C,
headset=0x0D, # not from Logitech documentation
remote_control=0x0E, # for compatibility with HID++ 2.0
receiver=0x0F, # for compatibility with HID++ 2.0
)

class DeviceKind(IntEnum):
UNKNOWN = 0x00
KEYBOARD = 0x01
MOUSE = 0x02
NUMPAD = 0x03
PRESENTER = 0x04
REMOTE = 0x07
TRACKBALL = 0x08
TOUCHPAD = 0x09
TABLET = 0x0A
GAMEPAD = 0x0B
JOYSTICK = 0x0C
HEADSET = 0x0D # not from Logitech documentation
REMOTE_CONTROL = 0x0E # for compatibility with HID++ 2.0
RECEIVER = 0x0F # for compatibility with HID++ 2.0


class PowerSwitchLocation(IntEnum):
Expand Down
20 changes: 18 additions & 2 deletions lib/logitech_receiver/hidpp20.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@

from . import common
from . import exceptions
from . import hidpp10_constants
from . import special_keys
from .common import Battery
from .common import BatteryLevelApproximation
from .common import BatteryStatus
from .common import FirmwareKind
from .common import NamedInt
from .common import NamedInts
from .hidpp20_constants import DEVICE_KIND
from .hidpp20_constants import ChargeLevel
from .hidpp20_constants import ChargeType
Expand All @@ -55,7 +55,23 @@

FixedBytes5 = bytes

KIND_MAP = {kind: hidpp10_constants.DEVICE_KIND[str(kind)] for kind in DEVICE_KIND}
HIDPP10_DEVICE_KIND = NamedInts(
unknown=0x00,
keyboard=0x01,
mouse=0x02,
numpad=0x03,
presenter=0x04,
remote=0x07,
trackball=0x08,
touchpad=0x09,
tablet=0x0A,
gamepad=0x0B,
joystick=0x0C,
headset=0x0D, # not from Logitech documentation
remote_control=0x0E, # for compatibility with HID++ 2.0
receiver=0x0F, # for compatibility with HID++ 2.0
)
KIND_MAP = {kind: HIDPP10_DEVICE_KIND[str(kind)] for kind in DEVICE_KIND}


class Device(Protocol):
Expand Down
32 changes: 15 additions & 17 deletions lib/logitech_receiver/receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from . import exceptions
from . import hidpp10
from . import hidpp10_constants
from .base import HIDPPNotification
from .common import Alert
from .common import Notification
from .device import Device
Expand Down Expand Up @@ -202,12 +203,12 @@ def notify_devices(self):
if not self.write_register(Registers.RECEIVER_CONNECTION, 0x02):
logger.warning("%s: failed to trigger device link notifications", self)

def notification_information(self, number, notification):
def notification_information(self, number, notification: HIDPPNotification):
"""Extract information from unifying-style notification"""
assert notification.address != 0x02
online = not bool(notification.data[0] & 0x40)
encrypted = bool(notification.data[0] & 0x20) or notification.address == 0x10
kind = hidpp10_constants.DEVICE_KIND[notification.data[0] & 0x0F]
kind = hidpp10_constants.DeviceKind(notification.data[0] & 0x0F)
wpid = (notification.data[2:3] + notification.data[1:2]).hex().upper()
return online, encrypted, wpid, kind

Expand All @@ -219,14 +220,14 @@ def device_pairing_information(self, n: int) -> dict:
pair_info = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.PAIRING_INFORMATION + n - 1)
if pair_info: # a receiver that uses Unifying-style pairing registers
wpid = pair_info[3:5].hex().upper()
kind = hidpp10_constants.DEVICE_KIND[pair_info[7] & 0x0F]
kind = hidpp10_constants.DeviceKind(pair_info[7] & 0x0F)
polling_rate = str(pair_info[2]) + "ms"
elif not self.receiver_kind == "unifying": # may be an old Nano receiver
device_info = self.read_register(Registers.RECEIVER_INFO, 0x04) # undocumented
if device_info:
logger.warning("using undocumented register for device wpid")
wpid = device_info[3:5].hex().upper()
kind = hidpp10_constants.DEVICE_KIND[0x00] # unknown kind
kind = hidpp10_constants.DeviceKind.UNKNOWN
else:
raise exceptions.NoSuchDevice(number=n, receiver=self, error="read pairing information - non-unifying")
else:
Expand Down Expand Up @@ -423,7 +424,7 @@ def device_pairing_information(self, n: int) -> dict:
pair_info = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.BOLT_PAIRING_INFORMATION + n)
if pair_info:
wpid = (pair_info[3:4] + pair_info[2:3]).hex().upper()
kind = hidpp10_constants.DEVICE_KIND[pair_info[1] & 0x0F]
kind = hidpp10_constants.DeviceKind(pair_info[1] & 0x0F)
serial = pair_info[4:8].hex().upper()
return {"wpid": wpid, "kind": kind, "polling": None, "serial": serial, "power_switch": "(unknown)"}
else:
Expand Down Expand Up @@ -482,7 +483,7 @@ def notification_information(self, number, notification):
assert notification.address == 0x02
online = True
encrypted = bool(notification.data[0] & 0x80)
kind = hidpp10_constants.DEVICE_KIND[_get_kind_from_index(self, number)]
kind = _get_kind_from_index(self, number)
wpid = "00" + notification.data[2:3].hex().upper()
return online, encrypted, wpid, kind

Expand All @@ -492,25 +493,22 @@ def device_pairing_information(self, number: int) -> dict:
if not wpid:
logger.error("Unable to get wpid from udev for device %d of %s", number, self)
raise exceptions.NoSuchDevice(number=number, receiver=self, error="Not present 27Mhz device")
kind = hidpp10_constants.DEVICE_KIND[_get_kind_from_index(self, number)]
kind = _get_kind_from_index(self, number)
return {"wpid": wpid, "kind": kind, "polling": "", "serial": None, "power_switch": "(unknown)"}


def _get_kind_from_index(receiver, index):
def _get_kind_from_index(receiver, index) -> hidpp10_constants.DeviceKind:
"""Get device kind from 27Mhz device index"""
# From drivers/hid/hid-logitech-dj.c
if index == 1: # mouse
kind = 2
elif index == 2: # mouse
kind = 2
elif index == 3: # keyboard
kind = 1
elif index == 4: # numpad
kind = 3
if index in [1, 2]:
return hidpp10_constants.DeviceKind.MOUSE
elif index == 3:
return hidpp10_constants.DeviceKind.KEYBOARD
elif index == 4:
return hidpp10_constants.DeviceKind.NUMPAD
else: # unknown device number on 27Mhz receiver
logger.error("failed to calculate device kind for device %d of %s", index, receiver)
raise exceptions.NoSuchDevice(number=index, receiver=receiver, error="Unknown 27Mhz device number")
return kind


receiver_class_mapping = {
Expand Down
2 changes: 1 addition & 1 deletion lib/solaar/cli/pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def notifications_hook(self, n):
receiver.pair_device(
address=address,
authentication=authentication,
entropy=20 if kind == hidpp10_constants.DEVICE_KIND.keyboard else 10,
entropy=20 if kind == hidpp10_constants.DeviceKind.KEYBOARD else 10,
)
pairing_start = time()
patience = 5 # the discovering notification may come slightly later, so be patient
Expand Down
2 changes: 1 addition & 1 deletion lib/solaar/ui/pair_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def _check_lock_state(assistant, receiver, count):
return True
elif receiver.pairing.discovering and receiver.pairing.device_address and receiver.pairing.device_name:
add = receiver.pairing.device_address
ent = 20 if receiver.pairing.device_kind == hidpp10_constants.DEVICE_KIND.keyboard else 10
ent = 20 if receiver.pairing.device_kind == hidpp10_constants.DeviceKind.KEYBOARD else 10
if receiver.pair_device(address=add, authentication=receiver.pairing.device_authentication, entropy=ent):
return True
else:
Expand Down
Loading

0 comments on commit 28e5566

Please sign in to comment.