From 31d795fcb80f7eb1f55724656a286ac6f29f7a8b Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Sat, 10 Feb 2024 17:10:37 -0500 Subject: [PATCH] device: improve imports in logitech_receiver device: move some imports to top of modules device: break up imports loop with device descriptors device: break up imports loop by moving a function from notifications.py to setting_templates.py device: break import loop between device.py and diversion.py by using device to access method --- lib/logitech_receiver/base.py | 52 +++++++- lib/logitech_receiver/base_usb.py | 42 ------- lib/logitech_receiver/descriptors.py | 127 +++++++++----------- lib/logitech_receiver/device.py | 5 +- lib/logitech_receiver/diversion.py | 7 +- lib/logitech_receiver/notifications.py | 14 +-- lib/logitech_receiver/receiver.py | 2 +- lib/logitech_receiver/settings_templates.py | 66 ++++++++-- 8 files changed, 169 insertions(+), 146 deletions(-) diff --git a/lib/logitech_receiver/base.py b/lib/logitech_receiver/base.py index 0024b021..05903bb5 100644 --- a/lib/logitech_receiver/base.py +++ b/lib/logitech_receiver/base.py @@ -33,13 +33,57 @@ import hidapi as _hid from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 from .base_usb import ALL as _RECEIVER_USB_IDS -from .base_usb import DEVICES as _DEVICE_IDS -from .base_usb import other_device_check as _other_device_check from .common import KwException as _KwException from .common import strhex as _strhex +from .descriptors import DEVICES as _DEVICES logger = logging.getLogger(__name__) +# +# +# + +_wired_device = lambda product_id, interface: { + 'vendor_id': 0x046d, + 'product_id': product_id, + 'bus_id': 0x3, + 'usb_interface': interface, + 'isDevice': True +} + +_bt_device = lambda product_id: {'vendor_id': 0x046d, 'product_id': product_id, 'bus_id': 0x5, 'isDevice': True} + +DEVICE_IDS = [] + +for _ignore, d in _DEVICES.items(): + if d.usbid: + DEVICE_IDS.append(_wired_device(d.usbid, d.interface if d.interface else 2)) + if d.btid: + DEVICE_IDS.append(_bt_device(d.btid)) + + +def other_device_check(bus_id, vendor_id, product_id): + """Check whether product is a Logitech USB-connected or Bluetooth device based on bus, vendor, and product IDs + This allows Solaar to support receiverless HID++ 2.0 devices that it knows nothing about""" + if vendor_id != 0x46d: # Logitech + return + if bus_id == 0x3: # USB + if (product_id >= 0xC07D and product_id <= 0xC094 or product_id >= 0xC32B and product_id <= 0xC344): + return _wired_device(product_id, 2) + elif bus_id == 0x5: # Bluetooth + if (product_id >= 0xB012 and product_id <= 0xB0FF or product_id >= 0xB317 and product_id <= 0xB3FF): + return _bt_device(product_id) + + +def product_information(usb_id): + if isinstance(usb_id, str): + usb_id = int(usb_id, 16) + for r in _RECEIVER_USB_IDS: + if usb_id == r.get('product_id'): + return r + return {} + + # # # @@ -122,13 +166,13 @@ def filter(bus_id, vendor_id, product_id, hidpp_short=False, hidpp_long=False): record = filter_receivers(bus_id, vendor_id, product_id, hidpp_short, hidpp_long) if record: # known or unknown receiver return record - for record in _DEVICE_IDS: # known devices + for record in DEVICE_IDS: # known devices if match(record, bus_id, vendor_id, product_id): return record if hidpp_short or hidpp_long: # unknown devices that use HID++ return {'vendor_id': vendor_id, 'product_id': product_id, 'bus_id': bus_id, 'isDevice': True} elif hidpp_short is None and hidpp_long is None: # unknown devices in correct range of IDs - return _other_device_check(bus_id, vendor_id, product_id) + return other_device_check(bus_id, vendor_id, product_id) def receivers_and_devices(): diff --git a/lib/logitech_receiver/base_usb.py b/lib/logitech_receiver/base_usb.py index d3fa3589..c21dcb0a 100644 --- a/lib/logitech_receiver/base_usb.py +++ b/lib/logitech_receiver/base_usb.py @@ -26,7 +26,6 @@ # USB ids of Logitech wireless receivers. # Only receivers supporting the HID++ protocol can go in here. -from .descriptors import DEVICES as _DEVICES from .i18n import _ # max_devices is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to 1 @@ -206,45 +205,4 @@ ALL = ( EX100_27MHZ_RECEIVER_C517, ) -_wired_device = lambda product_id, interface: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'bus_id': 0x3, - 'usb_interface': interface, - 'isDevice': True -} - -_bt_device = lambda product_id: {'vendor_id': 0x046d, 'product_id': product_id, 'bus_id': 0x5, 'isDevice': True} - -DEVICES = [] - -for _ignore, d in _DEVICES.items(): - if d.usbid: - DEVICES.append(_wired_device(d.usbid, d.interface if d.interface else 2)) - if d.btid: - DEVICES.append(_bt_device(d.btid)) - - -def other_device_check(bus_id, vendor_id, product_id): - """Check whether product is a Logitech USB-connected or Bluetooth device based on bus, vendor, and product IDs - This allows Solaar to support receiverless HID++ 2.0 devices that it knows nothing about""" - if vendor_id != 0x46d: # Logitech - return - if bus_id == 0x3: # USB - if (product_id >= 0xC07D and product_id <= 0xC094 or product_id >= 0xC32B and product_id <= 0xC344): - return _wired_device(product_id, 2) - elif bus_id == 0x5: # Bluetooth - if (product_id >= 0xB012 and product_id <= 0xB0FF or product_id >= 0xB317 and product_id <= 0xB3FF): - return _bt_device(product_id) - - -def product_information(usb_id): - if isinstance(usb_id, str): - usb_id = int(usb_id, 16) - for r in ALL: - if usb_id == r.get('product_id'): - return r - return {} - - del _DRIVER, _unifying_receiver, _nano_receiver, _lenovo_receiver, _lightspeed_receiver diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py index 6c709ad9..b6537f2f 100644 --- a/lib/logitech_receiver/descriptors.py +++ b/lib/logitech_receiver/descriptors.py @@ -24,10 +24,6 @@ # - the device uses a USB interface other than 2 # - the name or codename should be different from what the device reports -from collections import namedtuple - -from . import settings_templates as _ST -from .common import NamedInts as _NamedInts from .hidpp10 import DEVICE_KIND as _DK from .hidpp10 import REGISTERS as _R @@ -35,11 +31,32 @@ from .hidpp10 import REGISTERS as _R # # -_DeviceDescriptor = namedtuple( - '_DeviceDescriptor', - ('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings', 'usbid', 'interface', 'btid') -) -del namedtuple + +class _DeviceDescriptor: + + def __init__( + self, + name=None, + kind=None, + wpid=None, + codename=None, + protocol=None, + registers=None, + usbid=None, + interface=None, + btid=None + ): + self.name = name + self.kind = kind + self.wpid = wpid + self.codename = codename + self.protocol = protocol + self.registers = registers + self.usbid = usbid + self.interface = interface + self.btid = btid + self.settings = None + DEVICES_WPID = {} DEVICES = {} @@ -57,7 +74,6 @@ def _D( interface=None, btid=None, ): - if kind is None: kind = ( _DK.mouse if 'Mouse' in name else _DK.keyboard if 'Keyboard' in name else _DK.numpad @@ -83,7 +99,6 @@ def _D( codename=codename, protocol=protocol, registers=registers, - settings=settings, usbid=usbid, interface=interface, btid=btid @@ -157,13 +172,13 @@ def get_btid(btid): # The 'protocol' and 'wpid' fields are optional (they can be discovered at # runtime), but specifying them here speeds up device discovery and reduces the # USB traffic Solaar has to do to fully identify peripherals. -# Same goes for HID++ 2.0 feature settings (like _feature_fn_swap). # # The 'registers' field indicates read-only registers, specifying a state. These # are valid (AFAIK) only to HID++ 1.0 devices. # The 'settings' field indicates a read/write register; based on them Solaar -# generates, at runtime, the settings controls in the device panel. HID++ 1.0 -# devices may only have register-based settings; HID++ 2.0 devices may only have +# generates, at runtime, the settings controls in the device panel. +# Solaar now sets up this field in settings_templates.py to eliminate a imports loop. +# HID++ 1.0 devices may only have register-based settings; HID++ 2.0 devices may only have # feature-based settings. # Devices are organized by kind @@ -175,40 +190,38 @@ def get_btid(btid): _D('Wireless Keyboard EX110', codename='EX110', protocol=1.0, wpid='0055', registers=(_R.battery_status, )) _D('Wireless Keyboard S510', codename='S510', protocol=1.0, wpid='0056', registers=(_R.battery_status, )) -_D('Wireless Wave Keyboard K550', codename='K550', protocol=1.0, wpid='0060', registers=(_R.battery_status, ), - settings=[_ST.RegisterFnSwap]) +_D('Wireless Wave Keyboard K550', codename='K550', protocol=1.0, wpid='0060', registers=(_R.battery_status, )) _D('Wireless Keyboard EX100', codename='EX100', protocol=1.0, wpid='0065', registers=(_R.battery_status, )) _D('Wireless Keyboard MK300', codename='MK300', protocol=1.0, wpid='0068', registers=(_R.battery_status, )) _D('Number Pad N545', codename='N545', protocol=1.0, wpid='2006', registers=(_R.battery_status, )) _D('Wireless Compact Keyboard K340', codename='K340', protocol=1.0, wpid='2007', registers=(_R.battery_status, )) _D('Wireless Keyboard MK700', codename='MK700', protocol=1.0, wpid='2008', - registers=(_R.battery_status, ), settings=[_ST.RegisterFnSwap]) + registers=(_R.battery_status, )) _D('Wireless Wave Keyboard K350', codename='K350', protocol=1.0, wpid='200A', registers=(_R.battery_status, )) _D('Wireless Keyboard MK320', codename='MK320', protocol=1.0, wpid='200F', registers=(_R.battery_status, )) _D('Wireless Illuminated Keyboard K800', codename='K800', protocol=1.0, wpid='2010', - registers=(_R.battery_status, _R.three_leds), settings=[_ST.RegisterFnSwap, _ST.RegisterHandDetection]) -_D('Wireless Keyboard K520', codename='K520', protocol=1.0, wpid='2011', - registers=(_R.battery_status, ), settings=[_ST.RegisterFnSwap]) -_D('Wireless Solar Keyboard K750', codename='K750', protocol=2.0, wpid='4002', settings=[_ST.FnSwap]) + registers=(_R.battery_status, _R.three_leds)) +_D('Wireless Keyboard K520', codename='K520', protocol=1.0, wpid='2011', registers=(_R.battery_status, )) +_D('Wireless Solar Keyboard K750', codename='K750', protocol=2.0, wpid='4002') _D('Wireless Keyboard K270 (unifying)', codename='K270', protocol=2.0, wpid='4003') -_D('Wireless Keyboard K360', codename='K360', protocol=2.0, wpid='4004', settings=[_ST.FnSwap]) +_D('Wireless Keyboard K360', codename='K360', protocol=2.0, wpid='4004') _D('Wireless Keyboard K230', codename='K230', protocol=2.0, wpid='400D') -_D('Wireless Touch Keyboard K400', codename='K400', protocol=2.0, wpid=('400E', '4024'), settings=[_ST.FnSwap]) -_D('Wireless Keyboard MK270', codename='MK270', protocol=2.0, wpid='4023', settings=[_ST.FnSwap]) -_D('Illuminated Living-Room Keyboard K830', codename='K830', protocol=2.0, wpid='4032', settings=[_ST.NewFnSwap]) +_D('Wireless Touch Keyboard K400', codename='K400', protocol=2.0, wpid=('400E', '4024')) +_D('Wireless Keyboard MK270', codename='MK270', protocol=2.0, wpid='4023') +_D('Illuminated Living-Room Keyboard K830', codename='K830', protocol=2.0, wpid='4032') _D('Wireless Touch Keyboard K400 Plus', codename='K400 Plus', protocol=2.0, wpid='404D') -_D('Wireless Multi-Device Keyboard K780', codename='K780', protocol=4.5, wpid='405B', settings=[_ST.NewFnSwap]) -_D('Wireless Keyboard K375s', codename='K375s', protocol=2.0, wpid='4061', settings=[_ST.K375sFnSwap]) +_D('Wireless Multi-Device Keyboard K780', codename='K780', protocol=4.5, wpid='405B') +_D('Wireless Keyboard K375s', codename='K375s', protocol=2.0, wpid='4061') _D('Craft Advanced Keyboard', codename='Craft', protocol=4.5, wpid='4066', btid=0xB350) -_D('Wireless Illuminated Keyboard K800 new', codename='K800 new', protocol=4.5, wpid='406E', settings=[_ST.FnSwap]) -_D('Wireless Keyboard K470', codename='K470', protocol=4.5, wpid='4075', settings=[_ST.FnSwap]) +_D('Wireless Illuminated Keyboard K800 new', codename='K800 new', protocol=4.5, wpid='406E') +_D('Wireless Keyboard K470', codename='K470', protocol=4.5, wpid='4075') _D('MX Keys Keyboard', codename='MX Keys', protocol=4.5, wpid='408A', btid=0xB35B) _D('G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard', codename='G915 TKL', protocol=4.2, wpid='408E', usbid=0xC343) -_D('Illuminated Keyboard', codename='Illuminated', protocol=1.0, usbid=0xc318, interface=1, settings=[_ST.RegisterFnSwap]) +_D('Illuminated Keyboard', codename='Illuminated', protocol=1.0, usbid=0xc318, interface=1) _D('G213 Prodigy Gaming Keyboard', codename='G213', usbid=0xc336, interface=1) _D('G512 RGB Mechanical Gaming Keyboard', codename='G512', usbid=0xc33c, interface=1) _D('G815 Mechanical Keyboard', codename='G815', usbid=0xc33f, interface=1) -_D('diNovo Edge Keyboard', codename='diNovo', protocol=1.0, wpid='C714', settings=[_ST.RegisterFnSwap]) +_D('diNovo Edge Keyboard', codename='diNovo', protocol=1.0, wpid='C714') _D('K845 Mechanical Keyboard', codename='K845', usbid=0xc341, interface=3) # Mice @@ -229,41 +242,25 @@ _D('MX Air', codename='MX Air', protocol=1.0, kind=_DK.mouse, wpid=('1007', '100 _D('MX Revolution', codename='MX Revolution', protocol=1.0, kind=_DK.mouse, wpid=('1008', '100C'), registers=(_R.battery_charge, )) _D('MX620 Laser Cordless Mouse', codename='MX620', protocol=1.0, wpid=('100A', '1016'), registers=(_R.battery_charge, )) -_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid=('100B', '100F'), - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) +_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid=('100B', '100F'), registers=(_R.battery_charge, )) _D('V450 Nano Cordless Laser Mouse', codename='V450 Nano', protocol=1.0, wpid='1011', registers=(_R.battery_charge, )) -_D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1013', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll, ]) +_D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1013', registers=(_R.battery_charge, )) _D('MX 1100 Cordless Laser Mouse', codename='MX 1100', protocol=1.0, kind=_DK.mouse, wpid='1014', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) - - -class _PerformanceMXDpi(_ST.RegisterDpi): - choices_universe = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) - validator_options = {'choices': choices_universe} - - -_D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A', - registers=(_R.battery_status, _R.three_leds), - settings=[_PerformanceMXDpi, _ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Marathon Mouse M705 (M-R0009)', codename='M705 (M-R0009)', protocol=1.0, wpid='101B', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) + registers=(_R.battery_charge, )) +_D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017', registers=(_R.battery_charge, )) +_D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A', registers=(_R.battery_status, _R.three_leds)) +_D('Marathon Mouse M705 (M-R0009)', codename='M705 (M-R0009)', protocol=1.0, wpid='101B', registers=(_R.battery_charge, )) _D('Wireless Mouse M350', codename='M350', protocol=1.0, wpid='101C', registers=(_R.battery_charge, )) -_D('Wireless Mouse M505', codename='M505/B605', protocol=1.0, wpid='101D', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Wireless Mouse M305', codename='M305', protocol=1.0, wpid='101F', - registers=(_R.battery_status, ), settings=[_ST.RegisterSideScroll]) +_D('Wireless Mouse M505', codename='M505/B605', protocol=1.0, wpid='101D', registers=(_R.battery_charge, )) +_D('Wireless Mouse M305', codename='M305', protocol=1.0, wpid='101F', registers=(_R.battery_status, )) _D('Wireless Mouse M215', codename='M215', protocol=1.0, wpid='1020') _D('G700 Gaming Mouse', codename='G700', protocol=1.0, wpid='1023', usbid=0xc06b, interface=1, - registers=(_R.battery_status, _R.three_leds,), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) + registers=(_R.battery_status, _R.three_leds,)) _D('Wireless Mouse M310', codename='M310', protocol=1.0, wpid='1024', registers=(_R.battery_status, )) -_D('Wireless Mouse M510', codename='M510', protocol=1.0, wpid='1025', - registers=(_R.battery_status, ), settings=[_ST.RegisterSideScroll]) +_D('Wireless Mouse M510', codename='M510', protocol=1.0, wpid='1025', registers=(_R.battery_status, )) _D('Fujitsu Sonic Mouse', codename='Sonic', protocol=1.0, wpid='1029') _D('G700s Gaming Mouse', codename='G700s', protocol=1.0, wpid='102A', usbid=0xc07c, interface=1, - registers=(_R.battery_status, _R.three_leds,), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) + registers=(_R.battery_status, _R.three_leds,)) _D('Couch Mouse M515', codename='M515', protocol=2.0, wpid='4007') _D('Wireless Mouse M175', codename='M175', protocol=2.0, wpid='4008') _D('Wireless Mouse M325', codename='M325', protocol=2.0, wpid='400A') @@ -274,24 +271,20 @@ _D('Touch Mouse M600', codename='M600', protocol=2.0, wpid='401A') _D('Wireless Mouse M150', codename='M150', protocol=2.0, wpid='4022') _D('Wireless Mouse M185', codename='M185', protocol=2.0, wpid='4038') _D('Wireless Mouse MX Master', codename='MX Master', protocol=4.5, wpid='4041', btid=0xb012) -_D('Anywhere Mouse MX 2', codename='Anywhere MX 2', protocol=4.5, wpid='404A', settings=[_ST.HiresSmoothInvert]) +_D('Anywhere Mouse MX 2', codename='Anywhere MX 2', protocol=4.5, wpid='404A') _D('Wireless Mouse M510', codename='M510v2', protocol=2.0, wpid='4051') _D('Wireless Mouse M185 new', codename='M185n', protocol=4.5, wpid='4054') _D('Wireless Mouse M185/M235/M310', codename='M185/M235/M310', protocol=4.5, wpid='4055') -_D('Wireless Mouse MX Master 2S', codename='MX Master 2S', protocol=4.5, wpid='4069', btid=0xb019, - settings=[_ST.HiresSmoothInvert]) +_D('Wireless Mouse MX Master 2S', codename='MX Master 2S', protocol=4.5, wpid='4069', btid=0xb019) _D('Multi Device Silent Mouse M585/M590', codename='M585/M590', protocol=4.5, wpid='406B') -_D('Marathon Mouse M705 (M-R0073)', codename='M705 (M-R0073)', protocol=4.5, wpid='406D', - settings=[_ST.HiresSmoothInvert, _ST.PointerSpeed]) +_D('Marathon Mouse M705 (M-R0073)', codename='M705 (M-R0073)', protocol=4.5, wpid='406D',) _D('MX Vertical Wireless Mouse', codename='MX Vertical', protocol=4.5, wpid='407B', btid=0xb020, usbid=0xc08a) _D('Wireless Mouse Pebble M350', codename='Pebble', protocol=2.0, wpid='4080') _D('MX Master 3 Wireless Mouse', codename='MX Master 3', protocol=4.5, wpid='4082', btid=0xb023) _D('PRO X Wireless', kind='mouse', codename='PRO X', wpid='4093', usbid=0xc094) -_D('G9 Laser Mouse', codename='G9', usbid=0xc048, interface=1, protocol=1.0, - settings=[_PerformanceMXDpi, _ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('G9x Laser Mouse', codename='G9x', usbid=0xc066, interface=1, protocol=1.0, - settings=[_PerformanceMXDpi, _ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) +_D('G9 Laser Mouse', codename='G9', usbid=0xc048, interface=1, protocol=1.0) +_D('G9x Laser Mouse', codename='G9x', usbid=0xc066, interface=1, protocol=1.0) _D('G502 Gaming Mouse', codename='G502', usbid=0xc07d, interface=1) _D('G402 Gaming Mouse', codename='G402', usbid=0xc07e, interface=1) _D('G900 Chaos Spectrum Gaming Mouse', codename='G900', usbid=0xc081) diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index 18799f89..c8a6dc1b 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -19,6 +19,7 @@ import errno as _errno import logging import threading as _threading +import time from typing import Optional @@ -107,7 +108,6 @@ class Device: self.handle = _base.open_path(self.path) if self.path else None except Exception: # maybe the device wasn't set up try: - import time time.sleep(1) self.handle = _base.open_path(self.path) if self.path else None except Exception: # give up @@ -170,8 +170,7 @@ class Device: # may be a 2.0 device; if not, it will fix itself later self.features = _hidpp20.FeaturesArray(self) - @classmethod - def find(self, serial): + def find(self, serial): # find a device by serial number or unit ID assert serial, 'need serial number or unit ID to find a device' result = None for device in Device.instances: diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py index e3147918..adbb6050 100644 --- a/lib/logitech_receiver/diversion.py +++ b/lib/logitech_receiver/diversion.py @@ -43,7 +43,6 @@ from yaml import dump_all as _yaml_dump_all from yaml import safe_load_all as _yaml_safe_load_all from .common import NamedInt -from .device import Device as _Device from .hidpp20 import FEATURE as _F from .special_keys import CONTROL as _CONTROL @@ -749,7 +748,7 @@ class Setting(Condition): logger.debug('evaluate condition: %s', self) if len(self.args) < 3: return None - dev = _Device.find(self.args[0]) if self.args[0] is not None else device + dev = device.find(self.args[0]) if self.args[0] is not None else device if dev is None: logger.warning('Setting condition: device %s is not known', self.args[0]) return False @@ -1040,7 +1039,7 @@ class Active(Condition): def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): logger.debug('evaluate condition: %s', self) - dev = _Device.find(self.devID) + dev = device.find(self.devID) return bool(dev and dev.ping()) def data(self): @@ -1294,7 +1293,7 @@ class Set(Action): return None if logger.isEnabledFor(logging.INFO): logger.info('Set action: %s', self.args) - dev = _Device.find(self.args[0]) if self.args[0] is not None else device + dev = device.find(self.args[0]) if self.args[0] is not None else device if dev is None: logger.warning('Set action: device %s is not known', self.args[0]) return None diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 4dbb3db3..9586aacd 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -454,7 +454,7 @@ def _process_feature_notification(device, status, n, feature): if (n.address == 0x00): profile_sector = _unpack('!H', n.data[:2])[0] if profile_sector: - profile_change(device, profile_sector) + _st.profile_change(device, profile_sector) elif (n.address == 0x10): resolution_index = _unpack('!B', n.data[:1])[0] profile_sector = _unpack('!H', device.feature_request(_F.ONBOARD_PROFILES, 0x40)[:2])[0] @@ -465,15 +465,3 @@ def _process_feature_notification(device, status, n, feature): _diversion.process_notification(device, status, n, feature) return True - - -# change UI to show result of onboard profile change -def profile_change(device, profile_sector): - from solaar.ui.config_panel import record_setting # prevent circular import - record_setting(device, _st.OnboardProfiles, [profile_sector]) - for profile in device.profiles.profiles.values() if device.profiles else []: - if profile.sector == profile_sector: - resolution_index = profile.resolution_default_index - record_setting(device, _st.AdjustableDpi, [profile.resolutions[resolution_index]]) - record_setting(device, _st.ReportRate, [profile.report_rate]) - break diff --git a/lib/logitech_receiver/receiver.py b/lib/logitech_receiver/receiver.py index 55786b0e..b600c768 100644 --- a/lib/logitech_receiver/receiver.py +++ b/lib/logitech_receiver/receiver.py @@ -23,7 +23,7 @@ import hidapi as _hid from . import base as _base from . import hidpp10 as _hidpp10 -from .base_usb import product_information as _product_information +from .base import product_information as _product_information from .common import strhex as _strhex from .device import Device diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 15b65c5d..8066e315 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -17,19 +17,24 @@ ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import logging +import socket as _socket from logging import WARN as _WARN from struct import pack as _pack from struct import unpack as _unpack from time import time as _time +from traceback import format_exc as _format_exc +from . import descriptors as _descriptors from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 from . import special_keys as _special_keys +from .base import _HIDPP_Notification as _HIDPP_Notification from .common import NamedInt as _NamedInt from .common import NamedInts as _NamedInts from .common import bytes2int as _bytes2int from .common import int2bytes as _int2bytes +from .diversion import process_notification as _process_notification from .i18n import _ from .settings import KIND as _KIND from .settings import ActionSettingRW as _ActionSettingRW @@ -187,12 +192,34 @@ class RegisterFnSwap(FnSwapVirtual): validator_options = {'true_value': b'\x00\x01', 'mask': b'\x00\x01'} -class FnSwap(FnSwapVirtual): - feature = _F.FN_INVERSION +class _PerformanceMXDpi(RegisterDpi): + choices_universe = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) + validator_options = {'choices': choices_universe} -class NewFnSwap(FnSwapVirtual): - feature = _F.NEW_FN_INVERSION +# set up register settings for devices - this is done here to break up an import loop +_descriptors.get_wpid('0060').settings = [RegisterFnSwap] +_descriptors.get_wpid('2008').settings = [RegisterFnSwap] +_descriptors.get_wpid('2010').settings = [RegisterFnSwap, RegisterHandDetection] +_descriptors.get_wpid('2011').settings = [RegisterFnSwap] +_descriptors.get_usbid(0xc318).settings = [RegisterFnSwap] +_descriptors.get_wpid('C714').settings = [RegisterFnSwap] +_descriptors.get_wpid('100B').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('100F').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('1013').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('1014').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('1017').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('1023').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('4004').settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('101A').settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('101B').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('101D').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('101F').settings = [RegisterSideScroll] +_descriptors.get_usbid(0xc06b).settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_wpid('1025').settings = [RegisterSideScroll] +_descriptors.get_wpid('102A').settings = [RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_usbid(0xc048).settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] +_descriptors.get_usbid(0xc066).settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] # ignore the capabilities part of the feature - all devices should be able to swap Fn state @@ -203,6 +230,14 @@ class K375sFnSwap(FnSwapVirtual): validator_options = {'true_value': b'\x01', 'false_value': b'\x00', 'read_skip_byte_count': 1} +class FnSwap(FnSwapVirtual): + feature = _F.FN_INVERSION + + +class NewFnSwap(FnSwapVirtual): + feature = _F.NEW_FN_INVERSION + + class Backlight(_Setting): name = 'backlight-qualitative' label = _('Backlight') @@ -441,6 +476,18 @@ class ThumbInvert(_Setting): validator_options = {'true_value': b'\x00\x01', 'false_value': b'\x00\x00', 'mask': b'\x00\x01'} +# change UI to show result of onboard profile change +def profile_change(device, profile_sector): + from solaar.ui.config_panel import record_setting # prevent circular import + record_setting(device, OnboardProfiles, [profile_sector]) + for profile in device.profiles.profiles.values() if device.profiles else []: + if profile.sector == profile_sector: + resolution_index = profile.resolution_default_index + record_setting(device, AdjustableDpi, [profile.resolutions[resolution_index]]) + record_setting(device, ReportRate, [profile.report_rate]) + break + + class OnboardProfiles(_Setting): name = 'onboard_profiles' label = _('Onboard Profiles') @@ -466,13 +513,12 @@ class OnboardProfiles(_Setting): return b'\x00\x00' def write(self, device, data_bytes): - from . import notifications as _notifications # prevent circular import if data_bytes == b'\x00\x00': result = device.feature_request(_F.ONBOARD_PROFILES, 0x10, b'\x02') else: device.feature_request(_F.ONBOARD_PROFILES, 0x10, b'\x01') result = device.feature_request(_F.ONBOARD_PROFILES, 0x30, data_bytes) - _notifications.profile_change(device, _bytes2int(data_bytes)) + profile_change(device, _bytes2int(data_bytes)) return result class validator_class(_ChoicesV): @@ -798,8 +844,6 @@ class MouseGesturesXY(_RawXYProcessing): def release_action(self): if self.fsmState == 'pressed': # emit mouse gesture notification - from .base import _HIDPP_Notification as _HIDPP_Notification - from .diversion import process_notification as _process_notification self.push_mouse_event() if logger.isEnabledFor(logging.INFO): logger.info('mouse gesture notification %s', self.data) @@ -1098,8 +1142,7 @@ class ChangeHost(_Setting): hostNames = _hidpp20.get_host_names(device) hostNames = hostNames if hostNames is not None else {} if currentHost not in hostNames or hostNames[currentHost][1] == '': - import socket # find name of current host and use it - hostNames[currentHost] = (True, socket.gethostname().partition('.')[0]) + hostNames[currentHost] = (True, _socket.gethostname().partition('.')[0]) choices = _NamedInts() for host in range(0, numHosts): paired, hostName = hostNames.get(host, (True, '')) @@ -1533,8 +1576,7 @@ def check_feature(device, sclass): logger.debug('check_feature %s [%s] detected %s', sclass.name, sclass.feature, detected) return detected except Exception as e: - from traceback import format_exc - logger.error('check_feature %s [%s] error %s\n%s', sclass.name, sclass.feature, e, format_exc()) + logger.error('check_feature %s [%s] error %s\n%s', sclass.name, sclass.feature, e, _format_exc()) return False # differentiate from an error-free determination that the setting is not supported