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
This commit is contained in:
Peter F. Patel-Schneider 2024-02-10 17:10:37 -05:00
parent 008d3df50b
commit 31d795fcb8
8 changed files with 169 additions and 146 deletions

View File

@ -33,13 +33,57 @@ import hidapi as _hid
from . import hidpp10 as _hidpp10 from . import hidpp10 as _hidpp10
from . import hidpp20 as _hidpp20 from . import hidpp20 as _hidpp20
from .base_usb import ALL as _RECEIVER_USB_IDS 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 KwException as _KwException
from .common import strhex as _strhex from .common import strhex as _strhex
from .descriptors import DEVICES as _DEVICES
logger = logging.getLogger(__name__) 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) record = filter_receivers(bus_id, vendor_id, product_id, hidpp_short, hidpp_long)
if record: # known or unknown receiver if record: # known or unknown receiver
return record 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): if match(record, bus_id, vendor_id, product_id):
return record return record
if hidpp_short or hidpp_long: # unknown devices that use HID++ 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} 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 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(): def receivers_and_devices():

View File

@ -26,7 +26,6 @@
# USB ids of Logitech wireless receivers. # USB ids of Logitech wireless receivers.
# Only receivers supporting the HID++ protocol can go in here. # Only receivers supporting the HID++ protocol can go in here.
from .descriptors import DEVICES as _DEVICES
from .i18n import _ from .i18n import _
# max_devices is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to 1 # 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, 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 del _DRIVER, _unifying_receiver, _nano_receiver, _lenovo_receiver, _lightspeed_receiver

View File

@ -24,10 +24,6 @@
# - the device uses a USB interface other than 2 # - the device uses a USB interface other than 2
# - the name or codename should be different from what the device reports # - 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 DEVICE_KIND as _DK
from .hidpp10 import REGISTERS as _R from .hidpp10 import REGISTERS as _R
@ -35,11 +31,32 @@ from .hidpp10 import REGISTERS as _R
# #
# #
_DeviceDescriptor = namedtuple(
'_DeviceDescriptor', class _DeviceDescriptor:
('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings', 'usbid', 'interface', 'btid')
) def __init__(
del namedtuple 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_WPID = {}
DEVICES = {} DEVICES = {}
@ -57,7 +74,6 @@ def _D(
interface=None, interface=None,
btid=None, btid=None,
): ):
if kind is None: if kind is None:
kind = ( kind = (
_DK.mouse if 'Mouse' in name else _DK.keyboard if 'Keyboard' in name else _DK.numpad _DK.mouse if 'Mouse' in name else _DK.keyboard if 'Keyboard' in name else _DK.numpad
@ -83,7 +99,6 @@ def _D(
codename=codename, codename=codename,
protocol=protocol, protocol=protocol,
registers=registers, registers=registers,
settings=settings,
usbid=usbid, usbid=usbid,
interface=interface, interface=interface,
btid=btid btid=btid
@ -157,13 +172,13 @@ def get_btid(btid):
# The 'protocol' and 'wpid' fields are optional (they can be discovered at # The 'protocol' and 'wpid' fields are optional (they can be discovered at
# runtime), but specifying them here speeds up device discovery and reduces the # runtime), but specifying them here speeds up device discovery and reduces the
# USB traffic Solaar has to do to fully identify peripherals. # 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 # The 'registers' field indicates read-only registers, specifying a state. These
# are valid (AFAIK) only to HID++ 1.0 devices. # are valid (AFAIK) only to HID++ 1.0 devices.
# The 'settings' field indicates a read/write register; based on them Solaar # 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 # generates, at runtime, the settings controls in the device panel.
# devices may only have register-based settings; HID++ 2.0 devices may only have # 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. # feature-based settings.
# Devices are organized by kind # 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 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 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, ), _D('Wireless Wave Keyboard K550', codename='K550', protocol=1.0, wpid='0060', registers=(_R.battery_status, ))
settings=[_ST.RegisterFnSwap])
_D('Wireless Keyboard EX100', codename='EX100', protocol=1.0, wpid='0065', 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('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('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 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', _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 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 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', _D('Wireless Illuminated Keyboard K800', codename='K800', protocol=1.0, wpid='2010',
registers=(_R.battery_status, _R.three_leds), settings=[_ST.RegisterFnSwap, _ST.RegisterHandDetection]) registers=(_R.battery_status, _R.three_leds))
_D('Wireless Keyboard K520', codename='K520', protocol=1.0, wpid='2011', _D('Wireless Keyboard K520', codename='K520', protocol=1.0, wpid='2011', registers=(_R.battery_status, ))
registers=(_R.battery_status, ), settings=[_ST.RegisterFnSwap]) _D('Wireless Solar Keyboard K750', codename='K750', protocol=2.0, wpid='4002')
_D('Wireless Solar Keyboard K750', codename='K750', protocol=2.0, wpid='4002', settings=[_ST.FnSwap])
_D('Wireless Keyboard K270 (unifying)', codename='K270', protocol=2.0, wpid='4003') _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 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 Touch Keyboard K400', codename='K400', protocol=2.0, wpid=('400E', '4024'))
_D('Wireless Keyboard MK270', codename='MK270', protocol=2.0, wpid='4023', settings=[_ST.FnSwap]) _D('Wireless Keyboard MK270', codename='MK270', protocol=2.0, wpid='4023')
_D('Illuminated Living-Room Keyboard K830', codename='K830', protocol=2.0, wpid='4032', settings=[_ST.NewFnSwap]) _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 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 Multi-Device Keyboard K780', codename='K780', protocol=4.5, wpid='405B')
_D('Wireless Keyboard K375s', codename='K375s', protocol=2.0, wpid='4061', settings=[_ST.K375sFnSwap]) _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('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 Illuminated Keyboard K800 new', codename='K800 new', protocol=4.5, wpid='406E')
_D('Wireless Keyboard K470', codename='K470', protocol=4.5, wpid='4075', settings=[_ST.FnSwap]) _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('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('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('G213 Prodigy Gaming Keyboard', codename='G213', usbid=0xc336, interface=1)
_D('G512 RGB Mechanical Gaming Keyboard', codename='G512', usbid=0xc33c, 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('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) _D('K845 Mechanical Keyboard', codename='K845', usbid=0xc341, interface=3)
# Mice # 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'), _D('MX Revolution', codename='MX Revolution', protocol=1.0, kind=_DK.mouse, wpid=('1008', '100C'),
registers=(_R.battery_charge, )) registers=(_R.battery_charge, ))
_D('MX620 Laser Cordless Mouse', codename='MX620', protocol=1.0, wpid=('100A', '1016'), 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'), _D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid=('100B', '100F'), registers=(_R.battery_charge, ))
registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll])
_D('V450 Nano Cordless Laser Mouse', codename='V450 Nano', protocol=1.0, wpid='1011', 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', _D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1013', registers=(_R.battery_charge, ))
registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll, ])
_D('MX 1100 Cordless Laser Mouse', codename='MX 1100', protocol=1.0, kind=_DK.mouse, wpid='1014', _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]) registers=(_R.battery_charge, ))
_D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017', _D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017', registers=(_R.battery_charge, ))
registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) _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, ))
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])
_D('Wireless Mouse M350', codename='M350', protocol=1.0, wpid='101C', 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', _D('Wireless Mouse M505', codename='M505/B605', protocol=1.0, wpid='101D', registers=(_R.battery_charge, ))
registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) _D('Wireless Mouse M305', codename='M305', protocol=1.0, wpid='101F', registers=(_R.battery_status, ))
_D('Wireless Mouse M305', codename='M305', protocol=1.0, wpid='101F',
registers=(_R.battery_status, ), settings=[_ST.RegisterSideScroll])
_D('Wireless Mouse M215', codename='M215', protocol=1.0, wpid='1020') _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, _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 M310', codename='M310', protocol=1.0, wpid='1024', registers=(_R.battery_status, ))
_D('Wireless Mouse M510', codename='M510', protocol=1.0, wpid='1025', _D('Wireless Mouse M510', codename='M510', protocol=1.0, wpid='1025', registers=(_R.battery_status, ))
registers=(_R.battery_status, ), settings=[_ST.RegisterSideScroll])
_D('Fujitsu Sonic Mouse', codename='Sonic', protocol=1.0, wpid='1029') _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, _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('Couch Mouse M515', codename='M515', protocol=2.0, wpid='4007')
_D('Wireless Mouse M175', codename='M175', protocol=2.0, wpid='4008') _D('Wireless Mouse M175', codename='M175', protocol=2.0, wpid='4008')
_D('Wireless Mouse M325', codename='M325', protocol=2.0, wpid='400A') _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 M150', codename='M150', protocol=2.0, wpid='4022')
_D('Wireless Mouse M185', codename='M185', protocol=2.0, wpid='4038') _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('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 M510', codename='M510v2', protocol=2.0, wpid='4051')
_D('Wireless Mouse M185 new', codename='M185n', protocol=4.5, wpid='4054') _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 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, _D('Wireless Mouse MX Master 2S', codename='MX Master 2S', protocol=4.5, wpid='4069', btid=0xb019)
settings=[_ST.HiresSmoothInvert])
_D('Multi Device Silent Mouse M585/M590', codename='M585/M590', protocol=4.5, wpid='406B') _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', _D('Marathon Mouse M705 (M-R0073)', codename='M705 (M-R0073)', protocol=4.5, wpid='406D',)
settings=[_ST.HiresSmoothInvert, _ST.PointerSpeed])
_D('MX Vertical Wireless Mouse', codename='MX Vertical', protocol=4.5, wpid='407B', btid=0xb020, usbid=0xc08a) _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('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('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('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, _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)
_D('G9x Laser Mouse', codename='G9x', usbid=0xc066, interface=1, protocol=1.0,
settings=[_PerformanceMXDpi, _ST.RegisterSmoothScroll, _ST.RegisterSideScroll])
_D('G502 Gaming Mouse', codename='G502', usbid=0xc07d, interface=1) _D('G502 Gaming Mouse', codename='G502', usbid=0xc07d, interface=1)
_D('G402 Gaming Mouse', codename='G402', usbid=0xc07e, interface=1) _D('G402 Gaming Mouse', codename='G402', usbid=0xc07e, interface=1)
_D('G900 Chaos Spectrum Gaming Mouse', codename='G900', usbid=0xc081) _D('G900 Chaos Spectrum Gaming Mouse', codename='G900', usbid=0xc081)

View File

@ -19,6 +19,7 @@
import errno as _errno import errno as _errno
import logging import logging
import threading as _threading import threading as _threading
import time
from typing import Optional from typing import Optional
@ -107,7 +108,6 @@ class Device:
self.handle = _base.open_path(self.path) if self.path else None self.handle = _base.open_path(self.path) if self.path else None
except Exception: # maybe the device wasn't set up except Exception: # maybe the device wasn't set up
try: try:
import time
time.sleep(1) time.sleep(1)
self.handle = _base.open_path(self.path) if self.path else None self.handle = _base.open_path(self.path) if self.path else None
except Exception: # give up except Exception: # give up
@ -170,8 +170,7 @@ class Device:
# may be a 2.0 device; if not, it will fix itself later # may be a 2.0 device; if not, it will fix itself later
self.features = _hidpp20.FeaturesArray(self) self.features = _hidpp20.FeaturesArray(self)
@classmethod def find(self, serial): # find a device by serial number or unit ID
def find(self, serial):
assert serial, 'need serial number or unit ID to find a device' assert serial, 'need serial number or unit ID to find a device'
result = None result = None
for device in Device.instances: for device in Device.instances:

View File

@ -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 yaml import safe_load_all as _yaml_safe_load_all
from .common import NamedInt from .common import NamedInt
from .device import Device as _Device
from .hidpp20 import FEATURE as _F from .hidpp20 import FEATURE as _F
from .special_keys import CONTROL as _CONTROL from .special_keys import CONTROL as _CONTROL
@ -749,7 +748,7 @@ class Setting(Condition):
logger.debug('evaluate condition: %s', self) logger.debug('evaluate condition: %s', self)
if len(self.args) < 3: if len(self.args) < 3:
return None 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: if dev is None:
logger.warning('Setting condition: device %s is not known', self.args[0]) logger.warning('Setting condition: device %s is not known', self.args[0])
return False return False
@ -1040,7 +1039,7 @@ class Active(Condition):
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, status, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug('evaluate condition: %s', self) logger.debug('evaluate condition: %s', self)
dev = _Device.find(self.devID) dev = device.find(self.devID)
return bool(dev and dev.ping()) return bool(dev and dev.ping())
def data(self): def data(self):
@ -1294,7 +1293,7 @@ class Set(Action):
return None return None
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
logger.info('Set action: %s', self.args) 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: if dev is None:
logger.warning('Set action: device %s is not known', self.args[0]) logger.warning('Set action: device %s is not known', self.args[0])
return None return None

View File

@ -454,7 +454,7 @@ def _process_feature_notification(device, status, n, feature):
if (n.address == 0x00): if (n.address == 0x00):
profile_sector = _unpack('!H', n.data[:2])[0] profile_sector = _unpack('!H', n.data[:2])[0]
if profile_sector: if profile_sector:
profile_change(device, profile_sector) _st.profile_change(device, profile_sector)
elif (n.address == 0x10): elif (n.address == 0x10):
resolution_index = _unpack('!B', n.data[:1])[0] resolution_index = _unpack('!B', n.data[:1])[0]
profile_sector = _unpack('!H', device.feature_request(_F.ONBOARD_PROFILES, 0x40)[:2])[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) _diversion.process_notification(device, status, n, feature)
return True 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

View File

@ -23,7 +23,7 @@ import hidapi as _hid
from . import base as _base from . import base as _base
from . import hidpp10 as _hidpp10 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 .common import strhex as _strhex
from .device import Device from .device import Device

View File

@ -17,19 +17,24 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import logging import logging
import socket as _socket
from logging import WARN as _WARN from logging import WARN as _WARN
from struct import pack as _pack from struct import pack as _pack
from struct import unpack as _unpack from struct import unpack as _unpack
from time import time as _time 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 hidpp10 as _hidpp10
from . import hidpp20 as _hidpp20 from . import hidpp20 as _hidpp20
from . import special_keys as _special_keys from . import special_keys as _special_keys
from .base import _HIDPP_Notification as _HIDPP_Notification
from .common import NamedInt as _NamedInt from .common import NamedInt as _NamedInt
from .common import NamedInts as _NamedInts from .common import NamedInts as _NamedInts
from .common import bytes2int as _bytes2int from .common import bytes2int as _bytes2int
from .common import int2bytes as _int2bytes from .common import int2bytes as _int2bytes
from .diversion import process_notification as _process_notification
from .i18n import _ from .i18n import _
from .settings import KIND as _KIND from .settings import KIND as _KIND
from .settings import ActionSettingRW as _ActionSettingRW from .settings import ActionSettingRW as _ActionSettingRW
@ -187,12 +192,34 @@ class RegisterFnSwap(FnSwapVirtual):
validator_options = {'true_value': b'\x00\x01', 'mask': b'\x00\x01'} validator_options = {'true_value': b'\x00\x01', 'mask': b'\x00\x01'}
class FnSwap(FnSwapVirtual): class _PerformanceMXDpi(RegisterDpi):
feature = _F.FN_INVERSION choices_universe = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100))
validator_options = {'choices': choices_universe}
class NewFnSwap(FnSwapVirtual): # set up register settings for devices - this is done here to break up an import loop
feature = _F.NEW_FN_INVERSION _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 # 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} 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): class Backlight(_Setting):
name = 'backlight-qualitative' name = 'backlight-qualitative'
label = _('Backlight') 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'} 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): class OnboardProfiles(_Setting):
name = 'onboard_profiles' name = 'onboard_profiles'
label = _('Onboard Profiles') label = _('Onboard Profiles')
@ -466,13 +513,12 @@ class OnboardProfiles(_Setting):
return b'\x00\x00' return b'\x00\x00'
def write(self, device, data_bytes): def write(self, device, data_bytes):
from . import notifications as _notifications # prevent circular import
if data_bytes == b'\x00\x00': if data_bytes == b'\x00\x00':
result = device.feature_request(_F.ONBOARD_PROFILES, 0x10, b'\x02') result = device.feature_request(_F.ONBOARD_PROFILES, 0x10, b'\x02')
else: else:
device.feature_request(_F.ONBOARD_PROFILES, 0x10, b'\x01') device.feature_request(_F.ONBOARD_PROFILES, 0x10, b'\x01')
result = device.feature_request(_F.ONBOARD_PROFILES, 0x30, data_bytes) 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 return result
class validator_class(_ChoicesV): class validator_class(_ChoicesV):
@ -798,8 +844,6 @@ class MouseGesturesXY(_RawXYProcessing):
def release_action(self): def release_action(self):
if self.fsmState == 'pressed': if self.fsmState == 'pressed':
# emit mouse gesture notification # emit mouse gesture notification
from .base import _HIDPP_Notification as _HIDPP_Notification
from .diversion import process_notification as _process_notification
self.push_mouse_event() self.push_mouse_event()
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
logger.info('mouse gesture notification %s', self.data) logger.info('mouse gesture notification %s', self.data)
@ -1098,8 +1142,7 @@ class ChangeHost(_Setting):
hostNames = _hidpp20.get_host_names(device) hostNames = _hidpp20.get_host_names(device)
hostNames = hostNames if hostNames is not None else {} hostNames = hostNames if hostNames is not None else {}
if currentHost not in hostNames or hostNames[currentHost][1] == '': 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() choices = _NamedInts()
for host in range(0, numHosts): for host in range(0, numHosts):
paired, hostName = hostNames.get(host, (True, '')) 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) logger.debug('check_feature %s [%s] detected %s', sclass.name, sclass.feature, detected)
return detected return detected
except Exception as e: 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 return False # differentiate from an error-free determination that the setting is not supported