udev: add function to get wpid from udev

base: make workaround flag name more generic
descriptors: fix wpid's for 27Mhz devices
device: Improve wpid and kind processing for 27Mhz devices
notifications: Improve wpid generation for 27Mhz devices
docs: fix wpid's for EX100
This commit is contained in:
Alex Cherkayev 2020-09-05 13:49:21 +03:00 committed by Peter F. Patel-Schneider
parent 9de4d392d4
commit 7a82b93aaf
8 changed files with 68 additions and 26 deletions

View File

@ -211,8 +211,8 @@ setting is useful only to disable smooth scrolling.
| MK550 | | | | | | MK550 | | | | |
| MK700 | 2008 | 1.0 | yes | FN swap, reprog keys | | MK700 | 2008 | 1.0 | yes | FN swap, reprog keys |
| MK710 | | 1.0 | yes | FN swap, reprog keys | | MK710 | | 1.0 | yes | FN swap, reprog keys |
| EX100 keyboard | 6500 | 1.0 | yes | | | EX100 keyboard | 0065 | 1.0 | yes | |
| EX100 mouse | 3f00 | 1.0 | yes | | | EX100 mouse | 003f | 1.0 | yes | |
* The EX100 is old, preunifying set, supporting only part of HID++ 1.0 features * The EX100 is old, preunifying set, supporting only part of HID++ 1.0 features

View File

@ -23,6 +23,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
from hidapi.udev import close # noqa: F401 from hidapi.udev import close # noqa: F401
from hidapi.udev import enumerate # noqa: F401 from hidapi.udev import enumerate # noqa: F401
from hidapi.udev import find_paired_node # noqa: F401 from hidapi.udev import find_paired_node # noqa: F401
from hidapi.udev import find_paired_node_wpid # noqa: F401
from hidapi.udev import get_manufacturer # noqa: F401 from hidapi.udev import get_manufacturer # noqa: F401
from hidapi.udev import get_product # noqa: F401 from hidapi.udev import get_product # noqa: F401
from hidapi.udev import get_serial # noqa: F401 from hidapi.udev import get_serial # noqa: F401

View File

@ -176,6 +176,27 @@ def find_paired_node(receiver_path, index, timeout):
return None return None
def find_paired_node_wpid(receiver_path, index):
"""Find the node of a device paired with a receiver, get wpid from udev"""
context = _Context()
receiver_phys = _Devices.from_device_file(context, receiver_path).find_parent('hid').get('HID_PHYS')
if not receiver_phys:
return None
phys = f'{receiver_phys}:{index}'
for dev in context.list_devices(subsystem='hidraw'):
dev_phys = dev.find_parent('hid').get('HID_PHYS')
if dev_phys and dev_phys == phys:
# get hid id like 0003:0000046D:00000065
hid_id = dev.find_parent('hid').get('HID_ID')
# get wpid - last 4 symbols
udev_wpid = hid_id[-4:]
return udev_wpid
return None
def monitor_glib(callback, *device_filters): def monitor_glib(callback, *device_filters):
from gi.repository import GLib from gi.repository import GLib

View File

@ -27,7 +27,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
## should this last be changed so that may_unpair is used for all receivers? writing to _R.receiver_pairing doesn't seem right ## should this last be changed so that may_unpair is used for all receivers? writing to _R.receiver_pairing doesn't seem right
# re_pairs determines whether a receiver pairs by replacing existing pairings, default to False # re_pairs determines whether a receiver pairs by replacing existing pairings, default to False
## currently only one receiver is so marked - should there be more? ## currently only one receiver is so marked - should there be more?
# ex100_wpid_fix enable workarounds for EX100 and possible other old 27Mhz receivers # ex100_27mhz_wpid_fix enable workarounds for EX100 and possible other old 27Mhz receivers
_DRIVER = ('hid-generic', 'generic-usb', 'logitech-djreceiver') _DRIVER = ('hid-generic', 'generic-usb', 'logitech-djreceiver')
@ -96,7 +96,7 @@ _ex100_receiver = lambda product_id: {
'max_devices': 4, 'max_devices': 4,
'may_unpair': False, 'may_unpair': False,
're_pairs': True, 're_pairs': True,
'ex100_wpid_fix': True 'ex100_27mhz_wpid_fix': True
} }
_wired_device = lambda product_id: {'vendor_id': 0x046d, 'product_id': product_id, 'usb_interface': 2} _wired_device = lambda product_id: {'vendor_id': 0x046d, 'product_id': product_id, 'usb_interface': 2}

View File

@ -169,7 +169,7 @@ _D(
_D( _D(
'Wireless Keyboard MK300', 'Wireless Keyboard MK300',
protocol=1.0, protocol=1.0,
wpid='8521', wpid='0068',
registers=(_R.battery_status, ), registers=(_R.battery_status, ),
) )
@ -292,14 +292,14 @@ _D(
'Wireless Keyboard S510', 'Wireless Keyboard S510',
codename='S510', codename='S510',
protocol=1.0, protocol=1.0,
wpid='3622', wpid='0056',
registers=(_R.battery_status, ), registers=(_R.battery_status, ),
) )
_D( _D(
'Wireless Keyboard EX100', 'Wireless Keyboard EX100',
codename='EX100', codename='EX100',
protocol=1.0, protocol=1.0,
wpid='6500', wpid='0065',
registers=(_R.battery_status, ), registers=(_R.battery_status, ),
) )
@ -523,21 +523,21 @@ _D(
'LX5 Cordless Mouse', 'LX5 Cordless Mouse',
codename='LX5', codename='LX5',
protocol=1.0, protocol=1.0,
wpid='5612', wpid='0036',
registers=(_R.battery_status, ), registers=(_R.battery_status, ),
) )
_D( _D(
'Wireless Mouse M30', 'Wireless Mouse M30',
codename='M30', codename='M30',
protocol=1.0, protocol=1.0,
wpid='6822', wpid='0085',
registers=(_R.battery_status, ), registers=(_R.battery_status, ),
) )
_D( _D(
'Wireless Mouse EX100', 'Wireless Mouse EX100',
codename='EX100m', codename='EX100m',
protocol=1.0, protocol=1.0,
wpid='3F00', wpid='003F',
registers=(_R.battery_status, ), registers=(_R.battery_status, ),
# settings=[ _RS.smooth_scroll(), ], # command accepted, but no change in whell action # settings=[ _RS.smooth_scroll(), ], # command accepted, but no change in whell action
) )

View File

@ -81,9 +81,13 @@ class Device(object):
self.wpid = _strhex(link_notification.data[2:3] + link_notification.data[1:2]) self.wpid = _strhex(link_notification.data[2:3] + link_notification.data[1:2])
# assert link_notification.address == (0x04 if unifying else 0x03) # assert link_notification.address == (0x04 if unifying else 0x03)
kind = ord(link_notification.data[0:1]) & 0x0F kind = ord(link_notification.data[0:1]) & 0x0F
# get 27Mhz wpid and set kind based on index
if receiver.ex100_27mhz_wpid_fix: # 27 Mhz receiver
self.wpid = '00' + _strhex(link_notification.data[2:3])
kind = self.get_kind_from_index(number, receiver)
self._kind = _hidpp10.DEVICE_KIND[kind] self._kind = _hidpp10.DEVICE_KIND[kind]
else: else:
# force a reading of the wpid # Not a notification, force a reading of the wpid
pair_info = self.receiver.read_register(_R.receiver_info, 0x20 + number - 1) pair_info = self.receiver.read_register(_R.receiver_info, 0x20 + number - 1)
if pair_info: if pair_info:
# may be either a Unifying receiver, or an Unifying-ready # may be either a Unifying receiver, or an Unifying-ready
@ -91,20 +95,14 @@ class Device(object):
self.wpid = _strhex(pair_info[3:5]) self.wpid = _strhex(pair_info[3:5])
kind = ord(pair_info[7:8]) & 0x0F kind = ord(pair_info[7:8]) & 0x0F
self._kind = _hidpp10.DEVICE_KIND[kind] self._kind = _hidpp10.DEVICE_KIND[kind]
elif receiver.ex100_wpid_fix: elif receiver.ex100_27mhz_wpid_fix:
# ex100 receiver, fill fake device_info with known wpid's # 27Mhz receiver, fill extracting WPID from udev path
# accordingly to drivers/hid/hid-logitech-dj.c self.wpid = _hid.find_paired_node_wpid(receiver.path, number)
# index 1 or 2 always mouse, index 3 always the keyboard, if not self.wpid:
# index 4 is used for an optional separate numpad _log.error('Unable to get wpid from udev for device %d of %s', number, receiver)
if number == 1: # mouse raise _base.NoSuchDevice(number=number, receiver=receiver, error='Not present 27Mhz device')
self.wpid = '3F00' kind = self.get_kind_from_index(number, receiver)
self._kind = _hidpp10.DEVICE_KIND[2] self._kind = _hidpp10.DEVICE_KIND[kind]
elif number == 3: # keyboard
self.wpid = '6500'
self._kind = _hidpp10.DEVICE_KIND[1]
else: # unknown device number on EX100
_log.error('failed to set fake EX100 wpid for device %d of %s', number, receiver)
raise _base.NoSuchDevice(number=number, receiver=receiver, error='Unknown EX100 device')
else: else:
# unifying protocol not supported, must be a Nano receiver # unifying protocol not supported, must be a Nano receiver
device_info = self.receiver.read_register(_R.receiver_info, 0x04) device_info = self.receiver.read_register(_R.receiver_info, 0x04)
@ -289,6 +287,25 @@ class Device(object):
self._feature_settings_checked = _check_feature_settings(self, self._settings) self._feature_settings_checked = _check_feature_settings(self, self._settings)
return self._settings return self._settings
def get_kind_from_index(self, index, receiver):
"""Get device kind from 27Mhz device index"""
# accordingly to drivers/hid/hid-logitech-dj.c
# index 1 or 2 always mouse, index 3 always the keyboard,
# index 4 is used for an optional separate numpad
if index == 1: # mouse
kind = 2
elif index == 2: # mouse
kind = 2
elif index == 3: # keyboard
kind = 1
elif index == 4: # numpad
kind = 3
else: # unknown device number on 27Mhz receiver
_log.error('failed to calculate device kind for device %d of %s', index, receiver)
raise _base.NoSuchDevice(number=index, receiver=receiver, error='Unknown 27Mhz device number')
return kind
def enable_notifications(self, enable=True): def enable_notifications(self, enable=True):
"""Enable or disable device (dis)connection notifications on this """Enable or disable device (dis)connection notifications on this
receiver.""" receiver."""

View File

@ -203,6 +203,9 @@ def _process_hidpp10_notification(device, status, n):
) )
if protocol_name: if protocol_name:
wpid = _strhex(n.data[2:3] + n.data[1:2]) wpid = _strhex(n.data[2:3] + n.data[1:2])
# workaround for short EX100 and other 27 MHz wpids
if protocol_name == '27 MHz':
wpid = '00' + _strhex(n.data[2:3])
if wpid != device.wpid: if wpid != device.wpid:
_log.warn('%s wpid mismatch, got %s', device, wpid) _log.warn('%s wpid mismatch, got %s', device, wpid)
flags = ord(n.data[:1]) & 0xF0 flags = ord(n.data[:1]) & 0xF0

View File

@ -79,7 +79,7 @@ class Receiver(object):
self._str = '<%s(%s,%s%s)>' % ( self._str = '<%s(%s,%s%s)>' % (
self.name.replace(' ', ''), self.path, '' if isinstance(self.handle, int) else 'T', self.handle self.name.replace(' ', ''), self.path, '' if isinstance(self.handle, int) else 'T', self.handle
) )
self.ex100_wpid_fix = product_info.get('ex100_wpid_fix', False) self.ex100_27mhz_wpid_fix = product_info.get('ex100_27mhz_wpid_fix', False)
self._firmware = None self._firmware = None
self._devices = {} self._devices = {}