diff --git a/bin/solaar b/bin/solaar index 1ed01d8100..13072c98b3 100755 --- a/bin/solaar +++ b/bin/solaar @@ -32,16 +32,16 @@ def init_paths(): except UnicodeError: sys.stderr.write( - 'ERROR: Solaar cannot recognize encoding of filesystem path, ' - 'this may happen because non UTF-8 characters in the pathname.\n' + "ERROR: Solaar cannot recognize encoding of filesystem path, " + "this may happen because non UTF-8 characters in the pathname.\n" ) sys.exit(1) - prefix = _path.normpath(_path.join(_path.realpath(decoded_path), '..')) - src_lib = _path.join(prefix, 'lib') - share_lib = _path.join(prefix, 'share', 'solaar', 'lib') + prefix = _path.normpath(_path.join(_path.realpath(decoded_path), "..")) + src_lib = _path.join(prefix, "lib") + share_lib = _path.join(prefix, "share", "solaar", "lib") for location in src_lib, share_lib: - init_py = _path.join(location, 'solaar', '__init__.py') + init_py = _path.join(location, "solaar", "__init__.py") # print ("sys.path[0]: checking", init_py) if _path.exists(init_py): # print ("sys.path[0]: found", location, "replacing", sys.path[0]) @@ -49,7 +49,8 @@ def init_paths(): break -if __name__ == '__main__': +if __name__ == "__main__": init_paths() import solaar.gtk + solaar.gtk.main() diff --git a/lib/hidapi/__init__.py b/lib/hidapi/__init__.py index b29b8c2cc1..0a4fe169ac 100644 --- a/lib/hidapi/__init__.py +++ b/lib/hidapi/__init__.py @@ -19,31 +19,35 @@ import platform as _platform -if _platform.system() in ('Darwin', 'Windows'): - from hidapi.hidapi import close # noqa: F401 - from hidapi.hidapi import enumerate # noqa: F401 - from hidapi.hidapi import find_paired_node # noqa: F401 - from hidapi.hidapi import find_paired_node_wpid # noqa: F401 - from hidapi.hidapi import get_manufacturer # noqa: F401 - from hidapi.hidapi import get_product # noqa: F401 - from hidapi.hidapi import get_serial # noqa: F401 - from hidapi.hidapi import monitor_glib # noqa: F401 - from hidapi.hidapi import open # noqa: F401 - from hidapi.hidapi import open_path # noqa: F401 - from hidapi.hidapi import read # noqa: F401 - from hidapi.hidapi import write # noqa: F401 +if _platform.system() in ("Darwin", "Windows"): + from hidapi.hidapi import ( + close, # noqa: F401 + enumerate, # noqa: F401 + find_paired_node, # noqa: F401 + find_paired_node_wpid, # noqa: F401 + get_manufacturer, # noqa: F401 + get_product, # noqa: F401 + get_serial, # noqa: F401 + monitor_glib, # noqa: F401 + open, # noqa: F401 + open_path, # noqa: F401 + read, # noqa: F401 + write, # noqa: F401 + ) else: - from hidapi.udev import close # 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_wpid # noqa: F401 - from hidapi.udev import get_manufacturer # noqa: F401 - from hidapi.udev import get_product # noqa: F401 - from hidapi.udev import get_serial # noqa: F401 - from hidapi.udev import monitor_glib # noqa: F401 - from hidapi.udev import open # noqa: F401 - from hidapi.udev import open_path # noqa: F401 - from hidapi.udev import read # noqa: F401 - from hidapi.udev import write # noqa: F401 + from hidapi.udev import ( + close, # noqa: F401 + enumerate, # noqa: F401 + find_paired_node, # noqa: F401 + find_paired_node_wpid, # noqa: F401 + get_manufacturer, # noqa: F401 + get_product, # noqa: F401 + get_serial, # noqa: F401 + monitor_glib, # noqa: F401 + open, # noqa: F401 + open_path, # noqa: F401 + read, # noqa: F401 + write, # noqa: F401 + ) -__version__ = '0.9' +__version__ = "0.9" diff --git a/lib/hidapi/hidapi.py b/lib/hidapi/hidapi.py index a9cbf82f04..8078a8a935 100644 --- a/lib/hidapi/hidapi.py +++ b/lib/hidapi/hidapi.py @@ -35,30 +35,31 @@ import gi -gi.require_version('Gdk', '3.0') +gi.require_version("Gdk", "3.0") from gi.repository import GLib # NOQA: E402 logger = logging.getLogger(__name__) -native_implementation = 'hidapi' +native_implementation = "hidapi" # Device info as expected by Solaar DeviceInfo = namedtuple( - 'DeviceInfo', [ - 'path', - 'bus_id', - 'vendor_id', - 'product_id', - 'interface', - 'driver', - 'manufacturer', - 'product', - 'serial', - 'release', - 'isDevice', - 'hidpp_short', - 'hidpp_long', - ] + "DeviceInfo", + [ + "path", + "bus_id", + "vendor_id", + "product_id", + "interface", + "driver", + "manufacturer", + "product", + "serial", + "release", + "isDevice", + "hidpp_short", + "hidpp_long", + ], ) del namedtuple @@ -67,8 +68,15 @@ # hidapi binary names for various platforms _library_paths = ( - 'libhidapi-hidraw.so', 'libhidapi-hidraw.so.0', 'libhidapi-libusb.so', 'libhidapi-libusb.so.0', - 'libhidapi-iohidmanager.so', 'libhidapi-iohidmanager.so.0', 'libhidapi.dylib', 'hidapi.dll', 'libhidapi-0.dll' + "libhidapi-hidraw.so", + "libhidapi-hidraw.so.0", + "libhidapi-libusb.so", + "libhidapi-libusb.so.0", + "libhidapi-iohidmanager.so", + "libhidapi-iohidmanager.so.0", + "libhidapi.dylib", + "hidapi.dll", + "libhidapi-0.dll", ) for lib in _library_paths: @@ -84,9 +92,9 @@ # Retrieve version of hdiapi library class _cHidApiVersion(ctypes.Structure): _fields_ = [ - ('major', ctypes.c_int), - ('minor', ctypes.c_int), - ('patch', ctypes.c_int), + ("major", ctypes.c_int), + ("minor", ctypes.c_int), + ("patch", ctypes.c_int), ] @@ -97,28 +105,27 @@ class _cHidApiVersion(ctypes.Structure): # Construct device info struct based on API version class _cDeviceInfo(ctypes.Structure): - def as_dict(self): - return {name: getattr(self, name) for name, _t in self._fields_ if name != 'next'} + return {name: getattr(self, name) for name, _t in self._fields_ if name != "next"} # Low level hdiapi device info struct # See https://github.com/libusb/hidapi/blob/master/hidapi/hidapi.h#L143 _cDeviceInfo_fields = [ - ('path', ctypes.c_char_p), - ('vendor_id', ctypes.c_ushort), - ('product_id', ctypes.c_ushort), - ('serial_number', ctypes.c_wchar_p), - ('release_number', ctypes.c_ushort), - ('manufacturer_string', ctypes.c_wchar_p), - ('product_string', ctypes.c_wchar_p), - ('usage_page', ctypes.c_ushort), - ('usage', ctypes.c_ushort), - ('interface_number', ctypes.c_int), - ('next', ctypes.POINTER(_cDeviceInfo)), + ("path", ctypes.c_char_p), + ("vendor_id", ctypes.c_ushort), + ("product_id", ctypes.c_ushort), + ("serial_number", ctypes.c_wchar_p), + ("release_number", ctypes.c_ushort), + ("manufacturer_string", ctypes.c_wchar_p), + ("product_string", ctypes.c_wchar_p), + ("usage_page", ctypes.c_ushort), + ("usage", ctypes.c_ushort), + ("interface_number", ctypes.c_int), + ("next", ctypes.POINTER(_cDeviceInfo)), ] if _hid_version.contents.major >= 0 and _hid_version.contents.minor >= 13: - _cDeviceInfo_fields.append(('bus_type', ctypes.c_int)) + _cDeviceInfo_fields.append(("bus_type", ctypes.c_int)) _cDeviceInfo._fields_ = _cDeviceInfo_fields # Set up hidapi functions @@ -168,7 +175,7 @@ def as_dict(self): # Solaar opens the same device more than once which will fail unless we # allow non-exclusive opening. On windows opening with shared access is # the default, for macOS we need to set it explicitly. -if _platform.system() == 'Darwin': +if _platform.system() == "Darwin": _hidapi.hid_darwin_set_open_exclusive.argtypes = [ctypes.c_int] _hidapi.hid_darwin_set_open_exclusive.restype = None _hidapi.hid_darwin_set_open_exclusive(0) @@ -179,7 +186,7 @@ class HIDError(Exception): def _enumerate_devices(): - """ Returns all HID devices which are potentially useful to us """ + """Returns all HID devices which are potentially useful to us""" devices = [] c_devices = _hidapi.hid_enumerate(0, 0) p = c_devices @@ -188,19 +195,19 @@ def _enumerate_devices(): p = p.contents.next _hidapi.hid_free_enumeration(c_devices) - keyboard_or_mouse = {d['path'] for d in devices if d['usage_page'] == 1 and d['usage'] in (6, 2)} + keyboard_or_mouse = {d["path"] for d in devices if d["usage_page"] == 1 and d["usage"] in (6, 2)} unique_devices = {} for device in devices: # On macOS we cannot access keyboard or mouse devices without special permissions. Since # we don't need them anyway we remove them so opening them doesn't cause errors later. - if device['path'] in keyboard_or_mouse: + if device["path"] in keyboard_or_mouse: # print(f"Ignoring keyboard or mouse device: {device}") continue # hidapi returns separate entries for each usage page of a device. # Deduplicate by path to only keep one device entry. - if device['path'] not in unique_devices: - unique_devices[device['path']] = device + if device["path"] not in unique_devices: + unique_devices[device["path"]] = device unique_devices = unique_devices.values() # print("Unique devices:\n" + '\n'.join([f"{dev}" for dev in unique_devices])) @@ -209,7 +216,6 @@ def _enumerate_devices(): # Use a separate thread to check if devices have been removed or connected class _DeviceMonitor(Thread): - def __init__(self, device_callback, polling_delay=5.0): self.device_callback = device_callback self.polling_delay = polling_delay @@ -225,10 +231,10 @@ def run(self): current_devices = {tuple(dev.items()): dev for dev in _enumerate_devices()} for key, device in self.prev_devices.items(): if key not in current_devices: - self.device_callback('remove', device) + self.device_callback("remove", device) for key, device in current_devices.items(): if key not in self.prev_devices: - self.device_callback('add', device) + self.device_callback("add", device) self.prev_devices = current_devices sleep(self.polling_delay) @@ -237,29 +243,29 @@ def run(self): # It is given the bus id, vendor id, and product id and returns a dictionary # with the required hid_driver and usb_interface and whether this is a receiver or device. def _match(action, device, filterfn): - vid = device['vendor_id'] - pid = device['product_id'] + vid = device["vendor_id"] + pid = device["product_id"] # Translate hidapi bus_type to the bus_id values Solaar expects - if device.get('bus_type') == 0x01: + if device.get("bus_type") == 0x01: bus_id = 0x03 # USB - elif device.get('bus_type') == 0x02: + elif device.get("bus_type") == 0x02: bus_id = 0x05 # Bluetooth else: bus_id = None # Check for hidpp support - device['hidpp_short'] = False - device['hidpp_long'] = False + device["hidpp_short"] = False + device["hidpp_long"] = False device_handle = None try: - device_handle = open_path(device['path']) + device_handle = open_path(device["path"]) report = get_input_report(device_handle, 0x10, 32) if len(report) == 1 + 6 and report[0] == 0x10: - device['hidpp_short'] = True + device["hidpp_short"] = True report = get_input_report(device_handle, 0x11, 32) if len(report) == 1 + 19 and report[0] == 0x11: - device['hidpp_long'] = True + device["hidpp_long"] = True except HIDError as e: # noqa: F841 if logger.isEnabledFor(logging.INFO): logger.info(f"Error opening device {device['path']} ({bus_id}/{vid:04X}/{pid:04X}) for hidpp check: {e}") # noqa @@ -269,41 +275,41 @@ def _match(action, device, filterfn): if logger.isEnabledFor(logging.INFO): logger.info( - 'Found device BID %s VID %04X PID %04X HID++ %s %s', bus_id, vid, pid, device['hidpp_short'], device['hidpp_long'] + "Found device BID %s VID %04X PID %04X HID++ %s %s", bus_id, vid, pid, device["hidpp_short"], device["hidpp_long"] ) - if not device['hidpp_short'] and not device['hidpp_long']: + if not device["hidpp_short"] and not device["hidpp_long"]: return None - filter = filterfn(bus_id, vid, pid, device['hidpp_short'], device['hidpp_long']) + filter = filterfn(bus_id, vid, pid, device["hidpp_short"], device["hidpp_long"]) if not filter: return - isDevice = filter.get('isDevice') + isDevice = filter.get("isDevice") - if action == 'add': + if action == "add": d_info = DeviceInfo( - path=device['path'].decode(), + path=device["path"].decode(), bus_id=bus_id, - vendor_id=f'{vid:04X}', # noqa - product_id=f'{pid:04X}', # noqa + vendor_id=f"{vid:04X}", # noqa + product_id=f"{pid:04X}", # noqa interface=None, driver=None, - manufacturer=device['manufacturer_string'], - product=device['product_string'], - serial=device['serial_number'], - release=device['release_number'], + manufacturer=device["manufacturer_string"], + product=device["product_string"], + serial=device["serial_number"], + release=device["release_number"], isDevice=isDevice, - hidpp_short=device['hidpp_short'], - hidpp_long=device['hidpp_long'], + hidpp_short=device["hidpp_short"], + hidpp_long=device["hidpp_long"], ) return d_info - elif action == 'remove': + elif action == "remove": d_info = DeviceInfo( - path=device['path'].decode(), + path=device["path"].decode(), bus_id=None, - vendor_id=f'{vid:04X}', # noqa - product_id=f'{pid:04X}', # noqa + vendor_id=f"{vid:04X}", # noqa + product_id=f"{pid:04X}", # noqa interface=None, driver=None, manufacturer=None, @@ -328,14 +334,13 @@ def find_paired_node_wpid(receiver_path, index): def monitor_glib(callback, filterfn): - def device_callback(action, device): # print(f"device_callback({action}): {device}") - if action == 'add': + if action == "add": d_info = _match(action, device, filterfn) if d_info: GLib.idle_add(callback, action, d_info) - elif action == 'remove': + elif action == "remove": # Removed devices will be detected by Solaar directly pass @@ -352,7 +357,7 @@ def enumerate(filterfn): :returns: a list of matching ``DeviceInfo`` tuples. """ for device in _enumerate_devices(): - d_info = _match('add', device, filterfn) + d_info = _match("add", device, filterfn) if d_info: yield d_info @@ -463,7 +468,7 @@ def read(device_handle, bytes_count, timeout_ms=None): def get_input_report(device_handle, report_id, size): assert device_handle data = ctypes.create_string_buffer(size) - data[0] = bytearray((report_id, )) + data[0] = bytearray((report_id,)) size = _hidapi.hid_get_input_report(device_handle, data, size) if size < 0: raise HIDError(_hidapi.hid_error(device_handle)) @@ -475,7 +480,7 @@ def _readstring(device_handle, func, max_length=255): buf = ctypes.create_unicode_buffer(max_length) ret = func(device_handle, buf, max_length) if ret < 0: - raise HIDError('Error reading device property') + raise HIDError("Error reading device property") return buf.value diff --git a/lib/hidapi/hidconsole.py b/lib/hidapi/hidconsole.py index a37dd1fa41..46e3e9c276 100644 --- a/lib/hidapi/hidconsole.py +++ b/lib/hidapi/hidconsole.py @@ -40,10 +40,10 @@ read_packet = input interactive = os.isatty(0) -prompt = '?? Input: ' if interactive else '' +prompt = "?? Input: " if interactive else "" start_time = time.time() -strhex = lambda d: hexlify(d).decode('ascii').upper() +strhex = lambda d: hexlify(d).decode("ascii").upper() # # @@ -56,10 +56,10 @@ def _print(marker, data, scroll=False): t = time.time() - start_time if isinstance(data, str): - s = marker + ' ' + data + s = marker + " " + data else: hexs = strhex(data) - s = '%s (% 8.3f) [%s %s %s %s] %s' % (marker, t, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:], repr(data)) + s = "%s (% 8.3f) [%s %s %s %s] %s" % (marker, t, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:], repr(data)) with print_lock: # allow only one thread at a time to write to the console, otherwise @@ -68,18 +68,18 @@ def _print(marker, data, scroll=False): if interactive and scroll: # scroll the entire screen above the current line up by 1 line sys.stdout.write( - '\033[s' # save cursor position - '\033[S' # scroll up - '\033[A' # cursor up - '\033[L' # insert 1 line - '\033[G' + "\033[s" # save cursor position + "\033[S" # scroll up + "\033[A" # cursor up + "\033[L" # insert 1 line + "\033[G" ) # move cursor to column 1 sys.stdout.write(s) if interactive and scroll: # restore cursor position - sys.stdout.write('\033[u') + sys.stdout.write("\033[u") else: - sys.stdout.write('\n') + sys.stdout.write("\n") # flush stdout manually... # because trying to open stdin/out unbuffered programmatically @@ -88,7 +88,7 @@ def _print(marker, data, scroll=False): def _error(text, scroll=False): - _print('!!', text, scroll) + _print("!!", text, scroll) def _continuous_read(handle, timeout=2000): @@ -96,79 +96,78 @@ def _continuous_read(handle, timeout=2000): try: reply = _hid.read(handle, 128, timeout) except OSError as e: - _error('Read failed, aborting: ' + str(e), True) + _error("Read failed, aborting: " + str(e), True) break assert reply is not None if reply: - _print('>>', reply, True) + _print(">>", reply, True) def _validate_input(line, hidpp=False): try: - data = unhexlify(line.encode('ascii')) + data = unhexlify(line.encode("ascii")) except Exception as e: - _error('Invalid input: ' + str(e)) + _error("Invalid input: " + str(e)) return None if hidpp: if len(data) < 4: - _error('Invalid HID++ request: need at least 4 bytes') + _error("Invalid HID++ request: need at least 4 bytes") return None - if data[:1] not in b'\x10\x11': - _error('Invalid HID++ request: first byte must be 0x10 or 0x11') + if data[:1] not in b"\x10\x11": + _error("Invalid HID++ request: first byte must be 0x10 or 0x11") return None - if data[1:2] not in b'\xFF\x00\x01\x02\x03\x04\x05\x06\x07': - _error('Invalid HID++ request: second byte must be 0xFF or one of 0x00..0x07') + if data[1:2] not in b"\xFF\x00\x01\x02\x03\x04\x05\x06\x07": + _error("Invalid HID++ request: second byte must be 0xFF or one of 0x00..0x07") return None - if data[:1] == b'\x10': + if data[:1] == b"\x10": if len(data) > 7: - _error('Invalid HID++ request: maximum length of a 0x10 request is 7 bytes') + _error("Invalid HID++ request: maximum length of a 0x10 request is 7 bytes") return None while len(data) < 7: - data = (data + b'\x00' * 7)[:7] - elif data[:1] == b'\x11': + data = (data + b"\x00" * 7)[:7] + elif data[:1] == b"\x11": if len(data) > 20: - _error('Invalid HID++ request: maximum length of a 0x11 request is 20 bytes') + _error("Invalid HID++ request: maximum length of a 0x11 request is 20 bytes") return None while len(data) < 20: - data = (data + b'\x00' * 20)[:20] + data = (data + b"\x00" * 20)[:20] return data def _open(args): - def matchfn(bid, vid, pid, _a, _b): - if vid == 0x046d: - return {'vid': 0x046d} + if vid == 0x046D: + return {"vid": 0x046D} device = args.device if args.hidpp and not device: for d in _hid.enumerate(matchfn): - if d.driver == 'logitech-djreceiver': + if d.driver == "logitech-djreceiver": device = d.path break if not device: - sys.exit('!! No HID++ receiver found.') + sys.exit("!! No HID++ receiver found.") if not device: - sys.exit('!! Device path required.') + sys.exit("!! Device path required.") - print('.. Opening device', device) + print(".. Opening device", device) handle = _hid.open_path(device) if not handle: - sys.exit('!! Failed to open %s, aborting.' % device) + sys.exit("!! Failed to open %s, aborting." % device) print( - '.. Opened handle %r, vendor %r product %r serial %r.' % - (handle, _hid.get_manufacturer(handle), _hid.get_product(handle), _hid.get_serial(handle)) + ".. Opened handle %r, vendor %r product %r serial %r." + % (handle, _hid.get_manufacturer(handle), _hid.get_product(handle), _hid.get_serial(handle)) ) if args.hidpp: - if _hid.get_manufacturer(handle) is not None and _hid.get_manufacturer(handle) != b'Logitech': - sys.exit('!! Only Logitech devices support the HID++ protocol.') - print('.. HID++ validation enabled.') + if _hid.get_manufacturer(handle) is not None and _hid.get_manufacturer(handle) != b"Logitech": + sys.exit("!! Only Logitech devices support the HID++ protocol.") + print(".. HID++ validation enabled.") else: - if (_hid.get_manufacturer(handle) == b'Logitech' and b'Receiver' in _hid.get_product(handle)): + if _hid.get_manufacturer(handle) == b"Logitech" and b"Receiver" in _hid.get_product(handle): args.hidpp = True - print('.. Logitech receiver detected, HID++ validation enabled.') + print(".. Logitech receiver detected, HID++ validation enabled.") return handle @@ -180,13 +179,13 @@ def matchfn(bid, vid, pid, _a, _b): def _parse_arguments(): arg_parser = argparse.ArgumentParser() - arg_parser.add_argument('--history', help='history file (default ~/.hidconsole-history)') - arg_parser.add_argument('--hidpp', action='store_true', help='ensure input data is a valid HID++ request') + arg_parser.add_argument("--history", help="history file (default ~/.hidconsole-history)") + arg_parser.add_argument("--hidpp", action="store_true", help="ensure input data is a valid HID++ request") arg_parser.add_argument( - 'device', - nargs='?', - help='linux device to connect to (/dev/hidrawX); ' - 'may be omitted if --hidpp is given, in which case it looks for the first Logitech receiver' + "device", + nargs="?", + help="linux device to connect to (/dev/hidrawX); " + "may be omitted if --hidpp is given, in which case it looks for the first Logitech receiver", ) return arg_parser.parse_args() @@ -196,10 +195,10 @@ def main(): handle = _open(args) if interactive: - print('.. Press ^C/^D to exit, or type hex bytes to write to the device.') + print(".. Press ^C/^D to exit, or type hex bytes to write to the device.") if args.history is None: - args.history = os.path.join(os.path.expanduser('~'), '.hidconsole-history') + args.history = os.path.join(os.path.expanduser("~"), ".hidconsole-history") try: readline.read_history_file(args.history) except Exception: @@ -207,17 +206,17 @@ def main(): pass try: - t = Thread(target=_continuous_read, args=(handle, )) + t = Thread(target=_continuous_read, args=(handle,)) t.daemon = True t.start() if interactive: # move the cursor at the bottom of the screen - sys.stdout.write('\033[300B') # move cusor at most 300 lines down, don't scroll + sys.stdout.write("\033[300B") # move cusor at most 300 lines down, don't scroll while t.is_alive(): line = read_packet(prompt) - line = line.strip().replace(' ', '') + line = line.strip().replace(" ", "") # print ("line", line) if not line: continue @@ -226,12 +225,12 @@ def main(): if data is None: continue - _print('<<', data) + _print("<<", data) _hid.write(handle, data) # wait for some kind of reply if args.hidpp and not interactive: rlist, wlist, xlist = _select([handle], [], [], 1) - if data[1:2] == b'\xFF': + if data[1:2] == b"\xFF": # the receiver will reply very fast, in a few milliseconds time.sleep(0.010) else: @@ -239,16 +238,16 @@ def main(): time.sleep(0.700) except EOFError: if interactive: - print('') + print("") else: time.sleep(1) finally: - print('.. Closing handle %r' % handle) + print(".. Closing handle %r" % handle) _hid.close(handle) if interactive: readline.write_history_file(args.history) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/lib/hidapi/udev.py b/lib/hidapi/udev.py index 17b8a970d7..e25ab2f5c9 100644 --- a/lib/hidapi/udev.py +++ b/lib/hidapi/udev.py @@ -29,6 +29,7 @@ import os as _os import warnings as _warnings + # the tuple object we'll expose when enumerating devices from collections import namedtuple from select import select as _select @@ -44,30 +45,31 @@ from pyudev import Devices as _Devices from pyudev import Monitor as _Monitor -gi.require_version('Gdk', '3.0') +gi.require_version("Gdk", "3.0") from gi.repository import GLib # NOQA: E402 logger = logging.getLogger(__name__) -native_implementation = 'udev' +native_implementation = "udev" fileopen = open DeviceInfo = namedtuple( - 'DeviceInfo', [ - 'path', - 'bus_id', - 'vendor_id', - 'product_id', - 'interface', - 'driver', - 'manufacturer', - 'product', - 'serial', - 'release', - 'isDevice', - 'hidpp_short', - 'hidpp_long', - ] + "DeviceInfo", + [ + "path", + "bus_id", + "vendor_id", + "product_id", + "interface", + "driver", + "manufacturer", + "product", + "serial", + "release", + "isDevice", + "hidpp_short", + "hidpp_long", + ], ) del namedtuple @@ -100,24 +102,24 @@ def exit(): # with the required hid_driver and usb_interface and whether this is a receiver or device. def _match(action, device, filterfn): if logger.isEnabledFor(logging.DEBUG): - logger.debug(f'Dbus event {action} {device}') - hid_device = device.find_parent('hid') + logger.debug(f"Dbus event {action} {device}") + hid_device = device.find_parent("hid") if not hid_device: # only HID devices are of interest to Solaar return - hid_id = hid_device.get('HID_ID') + hid_id = hid_device.get("HID_ID") if not hid_id: return # there are reports that sometimes the id isn't set up right so be defensive - bid, vid, pid = hid_id.split(':') - hid_hid_device = hid_device.find_parent('hid') + bid, vid, pid = hid_id.split(":") + hid_hid_device = hid_device.find_parent("hid") if hid_hid_device: return # these are devices connected through a receiver so don't pick them up here try: # if report descriptor does not indicate HID++ capabilities then this device is not of interest to Solaar hidpp_short = hidpp_long = False - devfile = '/sys' + hid_device.get('DEVPATH') + '/report_descriptor' - with fileopen(devfile, 'rb') as fd: + devfile = "/sys" + hid_device.get("DEVPATH") + "/report_descriptor" + with fileopen(devfile, "rb") as fd: with _warnings.catch_warnings(): - _warnings.simplefilter('ignore') + _warnings.simplefilter("ignore") rd = _ReportDescriptor(fd.read()) hidpp_short = 0x10 in rd.input_report_ids and 6 * 8 == int(rd.get_input_report_size(0x10)) # and _Usage(0xFF00, 0x0001) in rd.get_input_items(0x10)[0].usages # be more permissive @@ -128,18 +130,18 @@ def _match(action, device, filterfn): except Exception as e: # if can't process report descriptor fall back to old scheme hidpp_short = hidpp_long = None logger.info( - 'Report Descriptor not processed for DEVICE %s BID %s VID %s PID %s: %s', device.device_node, bid, vid, pid, e + "Report Descriptor not processed for DEVICE %s BID %s VID %s PID %s: %s", device.device_node, bid, vid, pid, e ) filter = filterfn(int(bid, 16), int(vid, 16), int(pid, 16), hidpp_short, hidpp_long) if not filter: return - hid_driver = filter.get('hid_driver') - interface_number = filter.get('usb_interface') - isDevice = filter.get('isDevice') + hid_driver = filter.get("hid_driver") + interface_number = filter.get("usb_interface") + isDevice = filter.get("isDevice") - if action == 'add': - hid_driver_name = hid_device.get('DRIVER') + if action == "add": + hid_driver_name = hid_device.get("DRIVER") # print ("** found hid", action, device, "hid:", hid_device, hid_driver_name) if hid_driver: if isinstance(hid_driver, tuple): @@ -148,13 +150,20 @@ def _match(action, device, filterfn): elif hid_driver_name != hid_driver: return - intf_device = device.find_parent('usb', 'usb_interface') - usb_interface = None if intf_device is None else intf_device.attributes.asint('bInterfaceNumber') + intf_device = device.find_parent("usb", "usb_interface") + usb_interface = None if intf_device is None else intf_device.attributes.asint("bInterfaceNumber") # print('*** usb interface', action, device, 'usb_interface:', intf_device, usb_interface, interface_number) if logger.isEnabledFor(logging.INFO): logger.info( - 'Found device %s BID %s VID %s PID %s HID++ %s %s USB %s %s', device.device_node, bid, vid, pid, hidpp_short, - hidpp_long, usb_interface, interface_number + "Found device %s BID %s VID %s PID %s HID++ %s %s USB %s %s", + device.device_node, + bid, + vid, + pid, + hidpp_short, + hidpp_long, + usb_interface, + interface_number, ) if not (hidpp_short or hidpp_long or interface_number is None or interface_number == usb_interface): return @@ -167,17 +176,17 @@ def _match(action, device, filterfn): product_id=pid[-4:], interface=usb_interface, driver=hid_driver_name, - manufacturer=attrs.get('manufacturer') if attrs else None, - product=attrs.get('product') if attrs else None, - serial=hid_device.get('HID_UNIQ'), - release=attrs.get('bcdDevice') if attrs else None, + manufacturer=attrs.get("manufacturer") if attrs else None, + product=attrs.get("product") if attrs else None, + serial=hid_device.get("HID_UNIQ"), + release=attrs.get("bcdDevice") if attrs else None, isDevice=isDevice, hidpp_short=hidpp_short, hidpp_long=hidpp_long, ) return d_info - elif action == 'remove': + elif action == "remove": # print (dict(device), dict(usb_device)) d_info = DeviceInfo( @@ -201,17 +210,17 @@ def _match(action, device, filterfn): def find_paired_node(receiver_path, index, timeout): """Find the node of a device paired with a receiver""" context = _Context() - receiver_phys = _Devices.from_device_file(context, receiver_path).find_parent('hid').get('HID_PHYS') + 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}' # noqa: E231 + phys = f"{receiver_phys}:{index}" # noqa: E231 timeout += _timestamp() delta = _timestamp() while delta < timeout: - for dev in context.list_devices(subsystem='hidraw'): - dev_phys = dev.find_parent('hid').get('HID_PHYS') + for dev in context.list_devices(subsystem="hidraw"): + dev_phys = dev.find_parent("hid").get("HID_PHYS") if dev_phys and dev_phys == phys: return dev.device_node delta = _timestamp() @@ -222,17 +231,17 @@ def find_paired_node(receiver_path, index, timeout): 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') + 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}' # noqa: E231 - for dev in context.list_devices(subsystem='hidraw'): - dev_phys = dev.find_parent('hid').get('HID_PHYS') + phys = f"{receiver_phys}:{index}" # noqa: E231 + 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') + hid_id = dev.find_parent("hid").get("HID_ID") # get wpid - last 4 symbols udev_wpid = hid_id[-4:] return udev_wpid @@ -253,7 +262,7 @@ def monitor_glib(callback, filterfn): # break m = _Monitor.from_netlink(c) - m.filter_by(subsystem='hidraw') + m.filter_by(subsystem="hidraw") def _process_udev_event(monitor, condition, cb, filterfn): if condition == GLib.IO_IN: @@ -261,11 +270,11 @@ def _process_udev_event(monitor, condition, cb, filterfn): if event: action, device = event # print ("***", action, device) - if action == 'add': + if action == "add": d_info = _match(action, device, filterfn) if d_info: GLib.idle_add(cb, action, d_info) - elif action == 'remove': + elif action == "remove": # the GLib notification does _not_ match! pass return True @@ -284,7 +293,7 @@ def _process_udev_event(monitor, condition, cb, filterfn): # print ("did io_add_watch") if logger.isEnabledFor(logging.DEBUG): - logger.debug('Starting dbus monitoring') + logger.debug("Starting dbus monitoring") m.start() @@ -298,9 +307,9 @@ def enumerate(filterfn): """ if logger.isEnabledFor(logging.DEBUG): - logger.debug('Starting dbus enumeration') - for dev in _Context().list_devices(subsystem='hidraw'): - dev_info = _match('add', dev, filterfn) + logger.debug("Starting dbus enumeration") + for dev in _Context().list_devices(subsystem="hidraw"): + dev_info = _match("add", dev, filterfn) if dev_info: yield dev_info @@ -329,16 +338,16 @@ def open_path(device_path): :returns: an opaque device handle, or ``None``. """ assert device_path - assert device_path.startswith('/dev/hidraw') + assert device_path.startswith("/dev/hidraw") - logger.info('OPEN PATH %s', device_path) + logger.info("OPEN PATH %s", device_path) retrycount = 0 - while (retrycount < 3): + while retrycount < 3: retrycount += 1 try: return _os.open(device_path, _os.O_RDWR | _os.O_SYNC) except OSError as e: - logger.info('OPEN PATH FAILED %s ERROR %s %s', device_path, e.errno, e) + logger.info("OPEN PATH FAILED %s ERROR %s %s", device_path, e.errno, e) if e.errno == _errno.EACCES: sleep(0.1) else: @@ -380,7 +389,7 @@ def write(device_handle, data): assert isinstance(data, bytes), (repr(data), type(data)) retrycount = 0 bytes_written = 0 - while (retrycount < 3): + while retrycount < 3: try: retrycount += 1 bytes_written = _os.write(device_handle, data) @@ -390,7 +399,7 @@ def write(device_handle, data): else: break if bytes_written != len(data): - raise OSError(_errno.EIO, 'written %d bytes out of expected %d' % (bytes_written, len(data))) + raise OSError(_errno.EIO, "written %d bytes out of expected %d" % (bytes_written, len(data))) def read(device_handle, bytes_count, timeout_ms=-1): @@ -415,7 +424,7 @@ def read(device_handle, bytes_count, timeout_ms=-1): if xlist: assert xlist == [device_handle] - raise OSError(_errno.EIO, 'exception on file descriptor %d' % device_handle) + raise OSError(_errno.EIO, "exception on file descriptor %d" % device_handle) if rlist: assert rlist == [device_handle] @@ -424,13 +433,13 @@ def read(device_handle, bytes_count, timeout_ms=-1): assert isinstance(data, bytes), (repr(data), type(data)) return data else: - return b'' + return b"" _DEVICE_STRINGS = { - 0: 'manufacturer', - 1: 'product', - 2: 'serial', + 0: "manufacturer", + 1: "product", + 2: "serial", } @@ -477,20 +486,20 @@ def get_indexed_string(device_handle, index): assert device_handle stat = _os.fstat(device_handle) try: - dev = _Device.from_device_number(_Context(), 'char', stat.st_rdev) + dev = _Device.from_device_number(_Context(), "char", stat.st_rdev) except (DeviceNotFoundError, ValueError): return None - hid_dev = dev.find_parent('hid') + hid_dev = dev.find_parent("hid") if hid_dev: - assert 'HID_ID' in hid_dev - bus, _ignore, _ignore = hid_dev['HID_ID'].split(':') + assert "HID_ID" in hid_dev + bus, _ignore, _ignore = hid_dev["HID_ID"].split(":") - if bus == '0003': # USB - usb_dev = dev.find_parent('usb', 'usb_device') + if bus == "0003": # USB + usb_dev = dev.find_parent("usb", "usb_device") assert usb_dev return usb_dev.attributes.get(key) - elif bus == '0005': # BLUETOOTH + elif bus == "0005": # BLUETOOTH # TODO pass diff --git a/lib/keysyms/generate.py b/lib/keysyms/generate.py index 5ad68298f6..5368f0c7c4 100755 --- a/lib/keysyms/generate.py +++ b/lib/keysyms/generate.py @@ -5,39 +5,39 @@ from subprocess import run from tempfile import TemporaryDirectory -repo = 'https://github.com/freedesktop/xorg-proto-x11proto.git' -xx = 'https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/tree/master/include/X11/' -repo = 'https://gitlab.freedesktop.org/xorg/proto/xorgproto.git' -pattern = r'#define XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?' -xf86pattern = r'#define XF86XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?' +repo = "https://github.com/freedesktop/xorg-proto-x11proto.git" +xx = "https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/tree/master/include/X11/" +repo = "https://gitlab.freedesktop.org/xorg/proto/xorgproto.git" +pattern = r"#define XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?" +xf86pattern = r"#define XF86XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?" def main(): keysymdef = {} with TemporaryDirectory() as temp: - run(['git', 'clone', repo, '.'], cwd=temp) + run(["git", "clone", repo, "."], cwd=temp) # text = Path(temp, 'keysymdef.h').read_text() - text = Path(temp, 'include/X11/keysymdef.h').read_text() + text = Path(temp, "include/X11/keysymdef.h").read_text() for name, sym, uni in findall(pattern, text): sym = int(sym, 16) uni = int(uni, 16) if uni else None if keysymdef.get(name, None): - print('KEY DUP', name) + print("KEY DUP", name) keysymdef[name] = sym # text = Path(temp, 'keysymdef.h').read_text() - text = Path(temp, 'include/X11/XF86keysym.h').read_text() + text = Path(temp, "include/X11/XF86keysym.h").read_text() for name, sym, uni in findall(xf86pattern, text): sym = int(sym, 16) uni = int(uni, 16) if uni else None - if keysymdef.get('XF86_' + name, None): - print('KEY DUP', 'XF86_' + name) - keysymdef['XF86_' + name] = sym + if keysymdef.get("XF86_" + name, None): + print("KEY DUP", "XF86_" + name) + keysymdef["XF86_" + name] = sym - with open('keysymdef.py', 'w') as f: - f.write('# flake8: noqa\nkeysymdef = \\\n') + with open("keysymdef.py", "w") as f: + f.write("# flake8: noqa\nkeysymdef = \\\n") pprint(keysymdef, f) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/lib/keysyms/keysymdef.py b/lib/keysyms/keysymdef.py index c3ff1771ff..0f2ba6e7d2 100644 --- a/lib/keysyms/keysymdef.py +++ b/lib/keysyms/keysymdef.py @@ -1,2293 +1,2294 @@ # flake8: noqa -keysymdef = \ -{'0': 48, - '1': 49, - '2': 50, - '3': 51, - '3270_AltCursor': 64784, - '3270_Attn': 64782, - '3270_BackTab': 64773, - '3270_ChangeScreen': 64793, - '3270_Copy': 64789, - '3270_CursorBlink': 64783, - '3270_CursorSelect': 64796, - '3270_DeleteWord': 64794, - '3270_Duplicate': 64769, - '3270_Enter': 64798, - '3270_EraseEOF': 64774, - '3270_EraseInput': 64775, - '3270_ExSelect': 64795, - '3270_FieldMark': 64770, - '3270_Ident': 64787, - '3270_Jump': 64786, - '3270_KeyClick': 64785, - '3270_Left2': 64772, - '3270_PA1': 64778, - '3270_PA2': 64779, - '3270_PA3': 64780, - '3270_Play': 64790, - '3270_PrintScreen': 64797, - '3270_Quit': 64777, - '3270_Record': 64792, - '3270_Reset': 64776, - '3270_Right2': 64771, - '3270_Rule': 64788, - '3270_Setup': 64791, - '3270_Test': 64781, - '4': 52, - '5': 53, - '6': 54, - '7': 55, - '8': 56, - '9': 57, - 'A': 65, - 'AE': 198, - 'Aacute': 193, - 'Abelowdot': 16785056, - 'Abreve': 451, - 'Abreveacute': 16785070, - 'Abrevebelowdot': 16785078, - 'Abrevegrave': 16785072, - 'Abrevehook': 16785074, - 'Abrevetilde': 16785076, - 'AccessX_Enable': 65136, - 'AccessX_Feedback_Enable': 65137, - 'Acircumflex': 194, - 'Acircumflexacute': 16785060, - 'Acircumflexbelowdot': 16785068, - 'Acircumflexgrave': 16785062, - 'Acircumflexhook': 16785064, - 'Acircumflextilde': 16785066, - 'Adiaeresis': 196, - 'Agrave': 192, - 'Ahook': 16785058, - 'Alt_L': 65513, - 'Alt_R': 65514, - 'Amacron': 960, - 'Aogonek': 417, - 'Arabic_0': 16778848, - 'Arabic_1': 16778849, - 'Arabic_2': 16778850, - 'Arabic_3': 16778851, - 'Arabic_4': 16778852, - 'Arabic_5': 16778853, - 'Arabic_6': 16778854, - 'Arabic_7': 16778855, - 'Arabic_8': 16778856, - 'Arabic_9': 16778857, - 'Arabic_ain': 1497, - 'Arabic_alef': 1479, - 'Arabic_alefmaksura': 1513, - 'Arabic_beh': 1480, - 'Arabic_comma': 1452, - 'Arabic_dad': 1494, - 'Arabic_dal': 1487, - 'Arabic_damma': 1519, - 'Arabic_dammatan': 1516, - 'Arabic_ddal': 16778888, - 'Arabic_farsi_yeh': 16778956, - 'Arabic_fatha': 1518, - 'Arabic_fathatan': 1515, - 'Arabic_feh': 1505, - 'Arabic_fullstop': 16778964, - 'Arabic_gaf': 16778927, - 'Arabic_ghain': 1498, - 'Arabic_ha': 1511, - 'Arabic_hah': 1485, - 'Arabic_hamza': 1473, - 'Arabic_hamza_above': 16778836, - 'Arabic_hamza_below': 16778837, - 'Arabic_hamzaonalef': 1475, - 'Arabic_hamzaonwaw': 1476, - 'Arabic_hamzaonyeh': 1478, - 'Arabic_hamzaunderalef': 1477, - 'Arabic_heh': 1511, - 'Arabic_heh_doachashmee': 16778942, - 'Arabic_heh_goal': 16778945, - 'Arabic_jeem': 1484, - 'Arabic_jeh': 16778904, - 'Arabic_kaf': 1507, - 'Arabic_kasra': 1520, - 'Arabic_kasratan': 1517, - 'Arabic_keheh': 16778921, - 'Arabic_khah': 1486, - 'Arabic_lam': 1508, - 'Arabic_madda_above': 16778835, - 'Arabic_maddaonalef': 1474, - 'Arabic_meem': 1509, - 'Arabic_noon': 1510, - 'Arabic_noon_ghunna': 16778938, - 'Arabic_peh': 16778878, - 'Arabic_percent': 16778858, - 'Arabic_qaf': 1506, - 'Arabic_question_mark': 1471, - 'Arabic_ra': 1489, - 'Arabic_rreh': 16778897, - 'Arabic_sad': 1493, - 'Arabic_seen': 1491, - 'Arabic_semicolon': 1467, - 'Arabic_shadda': 1521, - 'Arabic_sheen': 1492, - 'Arabic_sukun': 1522, - 'Arabic_superscript_alef': 16778864, - 'Arabic_switch': 65406, - 'Arabic_tah': 1495, - 'Arabic_tatweel': 1504, - 'Arabic_tcheh': 16778886, - 'Arabic_teh': 1482, - 'Arabic_tehmarbuta': 1481, - 'Arabic_thal': 1488, - 'Arabic_theh': 1483, - 'Arabic_tteh': 16778873, - 'Arabic_veh': 16778916, - 'Arabic_waw': 1512, - 'Arabic_yeh': 1514, - 'Arabic_yeh_baree': 16778962, - 'Arabic_zah': 1496, - 'Arabic_zain': 1490, - 'Aring': 197, - 'Armenian_AT': 16778552, - 'Armenian_AYB': 16778545, - 'Armenian_BEN': 16778546, - 'Armenian_CHA': 16778569, - 'Armenian_DA': 16778548, - 'Armenian_DZA': 16778561, - 'Armenian_E': 16778551, - 'Armenian_FE': 16778582, - 'Armenian_GHAT': 16778562, - 'Armenian_GIM': 16778547, - 'Armenian_HI': 16778565, - 'Armenian_HO': 16778560, - 'Armenian_INI': 16778555, - 'Armenian_JE': 16778571, - 'Armenian_KE': 16778580, - 'Armenian_KEN': 16778559, - 'Armenian_KHE': 16778557, - 'Armenian_LYUN': 16778556, - 'Armenian_MEN': 16778564, - 'Armenian_NU': 16778566, - 'Armenian_O': 16778581, - 'Armenian_PE': 16778570, - 'Armenian_PYUR': 16778579, - 'Armenian_RA': 16778572, - 'Armenian_RE': 16778576, - 'Armenian_SE': 16778573, - 'Armenian_SHA': 16778567, - 'Armenian_TCHE': 16778563, - 'Armenian_TO': 16778553, - 'Armenian_TSA': 16778558, - 'Armenian_TSO': 16778577, - 'Armenian_TYUN': 16778575, - 'Armenian_VEV': 16778574, - 'Armenian_VO': 16778568, - 'Armenian_VYUN': 16778578, - 'Armenian_YECH': 16778549, - 'Armenian_ZA': 16778550, - 'Armenian_ZHE': 16778554, - 'Armenian_accent': 16778587, - 'Armenian_amanak': 16778588, - 'Armenian_apostrophe': 16778586, - 'Armenian_at': 16778600, - 'Armenian_ayb': 16778593, - 'Armenian_ben': 16778594, - 'Armenian_but': 16778589, - 'Armenian_cha': 16778617, - 'Armenian_da': 16778596, - 'Armenian_dza': 16778609, - 'Armenian_e': 16778599, - 'Armenian_exclam': 16778588, - 'Armenian_fe': 16778630, - 'Armenian_full_stop': 16778633, - 'Armenian_ghat': 16778610, - 'Armenian_gim': 16778595, - 'Armenian_hi': 16778613, - 'Armenian_ho': 16778608, - 'Armenian_hyphen': 16778634, - 'Armenian_ini': 16778603, - 'Armenian_je': 16778619, - 'Armenian_ke': 16778628, - 'Armenian_ken': 16778607, - 'Armenian_khe': 16778605, - 'Armenian_ligature_ew': 16778631, - 'Armenian_lyun': 16778604, - 'Armenian_men': 16778612, - 'Armenian_nu': 16778614, - 'Armenian_o': 16778629, - 'Armenian_paruyk': 16778590, - 'Armenian_pe': 16778618, - 'Armenian_pyur': 16778627, - 'Armenian_question': 16778590, - 'Armenian_ra': 16778620, - 'Armenian_re': 16778624, - 'Armenian_se': 16778621, - 'Armenian_separation_mark': 16778589, - 'Armenian_sha': 16778615, - 'Armenian_shesht': 16778587, - 'Armenian_tche': 16778611, - 'Armenian_to': 16778601, - 'Armenian_tsa': 16778606, - 'Armenian_tso': 16778625, - 'Armenian_tyun': 16778623, - 'Armenian_verjaket': 16778633, - 'Armenian_vev': 16778622, - 'Armenian_vo': 16778616, - 'Armenian_vyun': 16778626, - 'Armenian_yech': 16778597, - 'Armenian_yentamna': 16778634, - 'Armenian_za': 16778598, - 'Armenian_zhe': 16778602, - 'Atilde': 195, - 'AudibleBell_Enable': 65146, - 'B': 66, - 'Babovedot': 16784898, - 'BackSpace': 65288, - 'Begin': 65368, - 'BounceKeys_Enable': 65140, - 'Break': 65387, - 'Byelorussian_SHORTU': 1726, - 'Byelorussian_shortu': 1710, - 'C': 67, - 'CH': 65186, - 'C_H': 65189, - 'C_h': 65188, - 'Cabovedot': 709, - 'Cacute': 454, - 'Cancel': 65385, - 'Caps_Lock': 65509, - 'Ccaron': 456, - 'Ccedilla': 199, - 'Ccircumflex': 710, - 'Ch': 65185, - 'Clear': 65291, - 'Codeinput': 65335, - 'ColonSign': 16785569, - 'Control_L': 65507, - 'Control_R': 65508, - 'CruzeiroSign': 16785570, - 'Cyrillic_A': 1761, - 'Cyrillic_BE': 1762, - 'Cyrillic_CHE': 1790, - 'Cyrillic_CHE_descender': 16778422, - 'Cyrillic_CHE_vertstroke': 16778424, - 'Cyrillic_DE': 1764, - 'Cyrillic_DZHE': 1727, - 'Cyrillic_E': 1788, - 'Cyrillic_EF': 1766, - 'Cyrillic_EL': 1772, - 'Cyrillic_EM': 1773, - 'Cyrillic_EN': 1774, - 'Cyrillic_EN_descender': 16778402, - 'Cyrillic_ER': 1778, - 'Cyrillic_ES': 1779, - 'Cyrillic_GHE': 1767, - 'Cyrillic_GHE_bar': 16778386, - 'Cyrillic_HA': 1768, - 'Cyrillic_HARDSIGN': 1791, - 'Cyrillic_HA_descender': 16778418, - 'Cyrillic_I': 1769, - 'Cyrillic_IE': 1765, - 'Cyrillic_IO': 1715, - 'Cyrillic_I_macron': 16778466, - 'Cyrillic_JE': 1720, - 'Cyrillic_KA': 1771, - 'Cyrillic_KA_descender': 16778394, - 'Cyrillic_KA_vertstroke': 16778396, - 'Cyrillic_LJE': 1721, - 'Cyrillic_NJE': 1722, - 'Cyrillic_O': 1775, - 'Cyrillic_O_bar': 16778472, - 'Cyrillic_PE': 1776, - 'Cyrillic_SCHWA': 16778456, - 'Cyrillic_SHA': 1787, - 'Cyrillic_SHCHA': 1789, - 'Cyrillic_SHHA': 16778426, - 'Cyrillic_SHORTI': 1770, - 'Cyrillic_SOFTSIGN': 1784, - 'Cyrillic_TE': 1780, - 'Cyrillic_TSE': 1763, - 'Cyrillic_U': 1781, - 'Cyrillic_U_macron': 16778478, - 'Cyrillic_U_straight': 16778414, - 'Cyrillic_U_straight_bar': 16778416, - 'Cyrillic_VE': 1783, - 'Cyrillic_YA': 1777, - 'Cyrillic_YERU': 1785, - 'Cyrillic_YU': 1760, - 'Cyrillic_ZE': 1786, - 'Cyrillic_ZHE': 1782, - 'Cyrillic_ZHE_descender': 16778390, - 'Cyrillic_a': 1729, - 'Cyrillic_be': 1730, - 'Cyrillic_che': 1758, - 'Cyrillic_che_descender': 16778423, - 'Cyrillic_che_vertstroke': 16778425, - 'Cyrillic_de': 1732, - 'Cyrillic_dzhe': 1711, - 'Cyrillic_e': 1756, - 'Cyrillic_ef': 1734, - 'Cyrillic_el': 1740, - 'Cyrillic_em': 1741, - 'Cyrillic_en': 1742, - 'Cyrillic_en_descender': 16778403, - 'Cyrillic_er': 1746, - 'Cyrillic_es': 1747, - 'Cyrillic_ghe': 1735, - 'Cyrillic_ghe_bar': 16778387, - 'Cyrillic_ha': 1736, - 'Cyrillic_ha_descender': 16778419, - 'Cyrillic_hardsign': 1759, - 'Cyrillic_i': 1737, - 'Cyrillic_i_macron': 16778467, - 'Cyrillic_ie': 1733, - 'Cyrillic_io': 1699, - 'Cyrillic_je': 1704, - 'Cyrillic_ka': 1739, - 'Cyrillic_ka_descender': 16778395, - 'Cyrillic_ka_vertstroke': 16778397, - 'Cyrillic_lje': 1705, - 'Cyrillic_nje': 1706, - 'Cyrillic_o': 1743, - 'Cyrillic_o_bar': 16778473, - 'Cyrillic_pe': 1744, - 'Cyrillic_schwa': 16778457, - 'Cyrillic_sha': 1755, - 'Cyrillic_shcha': 1757, - 'Cyrillic_shha': 16778427, - 'Cyrillic_shorti': 1738, - 'Cyrillic_softsign': 1752, - 'Cyrillic_te': 1748, - 'Cyrillic_tse': 1731, - 'Cyrillic_u': 1749, - 'Cyrillic_u_macron': 16778479, - 'Cyrillic_u_straight': 16778415, - 'Cyrillic_u_straight_bar': 16778417, - 'Cyrillic_ve': 1751, - 'Cyrillic_ya': 1745, - 'Cyrillic_yeru': 1753, - 'Cyrillic_yu': 1728, - 'Cyrillic_ze': 1754, - 'Cyrillic_zhe': 1750, - 'Cyrillic_zhe_descender': 16778391, - 'D': 68, - 'Dabovedot': 16784906, - 'Dcaron': 463, - 'Delete': 65535, - 'DongSign': 16785579, - 'Down': 65364, - 'Dstroke': 464, - 'E': 69, - 'ENG': 957, - 'ETH': 208, - 'EZH': 16777655, - 'Eabovedot': 972, - 'Eacute': 201, - 'Ebelowdot': 16785080, - 'Ecaron': 460, - 'Ecircumflex': 202, - 'Ecircumflexacute': 16785086, - 'Ecircumflexbelowdot': 16785094, - 'Ecircumflexgrave': 16785088, - 'Ecircumflexhook': 16785090, - 'Ecircumflextilde': 16785092, - 'EcuSign': 16785568, - 'Ediaeresis': 203, - 'Egrave': 200, - 'Ehook': 16785082, - 'Eisu_Shift': 65327, - 'Eisu_toggle': 65328, - 'Emacron': 938, - 'End': 65367, - 'Eogonek': 458, - 'Escape': 65307, - 'Eth': 208, - 'Etilde': 16785084, - 'EuroSign': 8364, - 'Execute': 65378, - 'F': 70, - 'F1': 65470, - 'F10': 65479, - 'F11': 65480, - 'F12': 65481, - 'F13': 65482, - 'F14': 65483, - 'F15': 65484, - 'F16': 65485, - 'F17': 65486, - 'F18': 65487, - 'F19': 65488, - 'F2': 65471, - 'F20': 65489, - 'F21': 65490, - 'F22': 65491, - 'F23': 65492, - 'F24': 65493, - 'F25': 65494, - 'F26': 65495, - 'F27': 65496, - 'F28': 65497, - 'F29': 65498, - 'F3': 65472, - 'F30': 65499, - 'F31': 65500, - 'F32': 65501, - 'F33': 65502, - 'F34': 65503, - 'F35': 65504, - 'F4': 65473, - 'F5': 65474, - 'F6': 65475, - 'F7': 65476, - 'F8': 65477, - 'F9': 65478, - 'FFrancSign': 16785571, - 'Fabovedot': 16784926, - 'Farsi_0': 16778992, - 'Farsi_1': 16778993, - 'Farsi_2': 16778994, - 'Farsi_3': 16778995, - 'Farsi_4': 16778996, - 'Farsi_5': 16778997, - 'Farsi_6': 16778998, - 'Farsi_7': 16778999, - 'Farsi_8': 16779000, - 'Farsi_9': 16779001, - 'Farsi_yeh': 16778956, - 'Find': 65384, - 'First_Virtual_Screen': 65232, - 'G': 71, - 'Gabovedot': 725, - 'Gbreve': 683, - 'Gcaron': 16777702, - 'Gcedilla': 939, - 'Gcircumflex': 728, - 'Georgian_an': 16781520, - 'Georgian_ban': 16781521, - 'Georgian_can': 16781546, - 'Georgian_char': 16781549, - 'Georgian_chin': 16781545, - 'Georgian_cil': 16781548, - 'Georgian_don': 16781523, - 'Georgian_en': 16781524, - 'Georgian_fi': 16781558, - 'Georgian_gan': 16781522, - 'Georgian_ghan': 16781542, - 'Georgian_hae': 16781552, - 'Georgian_har': 16781556, - 'Georgian_he': 16781553, - 'Georgian_hie': 16781554, - 'Georgian_hoe': 16781557, - 'Georgian_in': 16781528, - 'Georgian_jhan': 16781551, - 'Georgian_jil': 16781547, - 'Georgian_kan': 16781529, - 'Georgian_khar': 16781541, - 'Georgian_las': 16781530, - 'Georgian_man': 16781531, - 'Georgian_nar': 16781532, - 'Georgian_on': 16781533, - 'Georgian_par': 16781534, - 'Georgian_phar': 16781540, - 'Georgian_qar': 16781543, - 'Georgian_rae': 16781536, - 'Georgian_san': 16781537, - 'Georgian_shin': 16781544, - 'Georgian_tan': 16781527, - 'Georgian_tar': 16781538, - 'Georgian_un': 16781539, - 'Georgian_vin': 16781525, - 'Georgian_we': 16781555, - 'Georgian_xan': 16781550, - 'Georgian_zen': 16781526, - 'Georgian_zhar': 16781535, - 'Greek_ALPHA': 1985, - 'Greek_ALPHAaccent': 1953, - 'Greek_BETA': 1986, - 'Greek_CHI': 2007, - 'Greek_DELTA': 1988, - 'Greek_EPSILON': 1989, - 'Greek_EPSILONaccent': 1954, - 'Greek_ETA': 1991, - 'Greek_ETAaccent': 1955, - 'Greek_GAMMA': 1987, - 'Greek_IOTA': 1993, - 'Greek_IOTAaccent': 1956, - 'Greek_IOTAdiaeresis': 1957, - 'Greek_IOTAdieresis': 1957, - 'Greek_KAPPA': 1994, - 'Greek_LAMBDA': 1995, - 'Greek_LAMDA': 1995, - 'Greek_MU': 1996, - 'Greek_NU': 1997, - 'Greek_OMEGA': 2009, - 'Greek_OMEGAaccent': 1963, - 'Greek_OMICRON': 1999, - 'Greek_OMICRONaccent': 1959, - 'Greek_PHI': 2006, - 'Greek_PI': 2000, - 'Greek_PSI': 2008, - 'Greek_RHO': 2001, - 'Greek_SIGMA': 2002, - 'Greek_TAU': 2004, - 'Greek_THETA': 1992, - 'Greek_UPSILON': 2005, - 'Greek_UPSILONaccent': 1960, - 'Greek_UPSILONdieresis': 1961, - 'Greek_XI': 1998, - 'Greek_ZETA': 1990, - 'Greek_accentdieresis': 1966, - 'Greek_alpha': 2017, - 'Greek_alphaaccent': 1969, - 'Greek_beta': 2018, - 'Greek_chi': 2039, - 'Greek_delta': 2020, - 'Greek_epsilon': 2021, - 'Greek_epsilonaccent': 1970, - 'Greek_eta': 2023, - 'Greek_etaaccent': 1971, - 'Greek_finalsmallsigma': 2035, - 'Greek_gamma': 2019, - 'Greek_horizbar': 1967, - 'Greek_iota': 2025, - 'Greek_iotaaccent': 1972, - 'Greek_iotaaccentdieresis': 1974, - 'Greek_iotadieresis': 1973, - 'Greek_kappa': 2026, - 'Greek_lambda': 2027, - 'Greek_lamda': 2027, - 'Greek_mu': 2028, - 'Greek_nu': 2029, - 'Greek_omega': 2041, - 'Greek_omegaaccent': 1979, - 'Greek_omicron': 2031, - 'Greek_omicronaccent': 1975, - 'Greek_phi': 2038, - 'Greek_pi': 2032, - 'Greek_psi': 2040, - 'Greek_rho': 2033, - 'Greek_sigma': 2034, - 'Greek_switch': 65406, - 'Greek_tau': 2036, - 'Greek_theta': 2024, - 'Greek_upsilon': 2037, - 'Greek_upsilonaccent': 1976, - 'Greek_upsilonaccentdieresis': 1978, - 'Greek_upsilondieresis': 1977, - 'Greek_xi': 2030, - 'Greek_zeta': 2022, - 'H': 72, - 'Hangul': 65329, - 'Hangul_A': 3775, - 'Hangul_AE': 3776, - 'Hangul_AraeA': 3830, - 'Hangul_AraeAE': 3831, - 'Hangul_Banja': 65337, - 'Hangul_Cieuc': 3770, - 'Hangul_Codeinput': 65335, - 'Hangul_Dikeud': 3751, - 'Hangul_E': 3780, - 'Hangul_EO': 3779, - 'Hangul_EU': 3793, - 'Hangul_End': 65331, - 'Hangul_Hanja': 65332, - 'Hangul_Hieuh': 3774, - 'Hangul_I': 3795, - 'Hangul_Ieung': 3767, - 'Hangul_J_Cieuc': 3818, - 'Hangul_J_Dikeud': 3802, - 'Hangul_J_Hieuh': 3822, - 'Hangul_J_Ieung': 3816, - 'Hangul_J_Jieuj': 3817, - 'Hangul_J_Khieuq': 3819, - 'Hangul_J_Kiyeog': 3796, - 'Hangul_J_KiyeogSios': 3798, - 'Hangul_J_KkogjiDalrinIeung': 3833, - 'Hangul_J_Mieum': 3811, - 'Hangul_J_Nieun': 3799, - 'Hangul_J_NieunHieuh': 3801, - 'Hangul_J_NieunJieuj': 3800, - 'Hangul_J_PanSios': 3832, - 'Hangul_J_Phieuf': 3821, - 'Hangul_J_Pieub': 3812, - 'Hangul_J_PieubSios': 3813, - 'Hangul_J_Rieul': 3803, - 'Hangul_J_RieulHieuh': 3810, - 'Hangul_J_RieulKiyeog': 3804, - 'Hangul_J_RieulMieum': 3805, - 'Hangul_J_RieulPhieuf': 3809, - 'Hangul_J_RieulPieub': 3806, - 'Hangul_J_RieulSios': 3807, - 'Hangul_J_RieulTieut': 3808, - 'Hangul_J_Sios': 3814, - 'Hangul_J_SsangKiyeog': 3797, - 'Hangul_J_SsangSios': 3815, - 'Hangul_J_Tieut': 3820, - 'Hangul_J_YeorinHieuh': 3834, - 'Hangul_Jamo': 65333, - 'Hangul_Jeonja': 65336, - 'Hangul_Jieuj': 3768, - 'Hangul_Khieuq': 3771, - 'Hangul_Kiyeog': 3745, - 'Hangul_KiyeogSios': 3747, - 'Hangul_KkogjiDalrinIeung': 3827, - 'Hangul_Mieum': 3761, - 'Hangul_MultipleCandidate': 65341, - 'Hangul_Nieun': 3748, - 'Hangul_NieunHieuh': 3750, - 'Hangul_NieunJieuj': 3749, - 'Hangul_O': 3783, - 'Hangul_OE': 3786, - 'Hangul_PanSios': 3826, - 'Hangul_Phieuf': 3773, - 'Hangul_Pieub': 3762, - 'Hangul_PieubSios': 3764, - 'Hangul_PostHanja': 65339, - 'Hangul_PreHanja': 65338, - 'Hangul_PreviousCandidate': 65342, - 'Hangul_Rieul': 3753, - 'Hangul_RieulHieuh': 3760, - 'Hangul_RieulKiyeog': 3754, - 'Hangul_RieulMieum': 3755, - 'Hangul_RieulPhieuf': 3759, - 'Hangul_RieulPieub': 3756, - 'Hangul_RieulSios': 3757, - 'Hangul_RieulTieut': 3758, - 'Hangul_RieulYeorinHieuh': 3823, - 'Hangul_Romaja': 65334, - 'Hangul_SingleCandidate': 65340, - 'Hangul_Sios': 3765, - 'Hangul_Special': 65343, - 'Hangul_SsangDikeud': 3752, - 'Hangul_SsangJieuj': 3769, - 'Hangul_SsangKiyeog': 3746, - 'Hangul_SsangPieub': 3763, - 'Hangul_SsangSios': 3766, - 'Hangul_Start': 65330, - 'Hangul_SunkyeongeumMieum': 3824, - 'Hangul_SunkyeongeumPhieuf': 3828, - 'Hangul_SunkyeongeumPieub': 3825, - 'Hangul_Tieut': 3772, - 'Hangul_U': 3788, - 'Hangul_WA': 3784, - 'Hangul_WAE': 3785, - 'Hangul_WE': 3790, - 'Hangul_WEO': 3789, - 'Hangul_WI': 3791, - 'Hangul_YA': 3777, - 'Hangul_YAE': 3778, - 'Hangul_YE': 3782, - 'Hangul_YEO': 3781, - 'Hangul_YI': 3794, - 'Hangul_YO': 3787, - 'Hangul_YU': 3792, - 'Hangul_YeorinHieuh': 3829, - 'Hangul_switch': 65406, - 'Hankaku': 65321, - 'Hcircumflex': 678, - 'Hebrew_switch': 65406, - 'Help': 65386, - 'Henkan': 65315, - 'Henkan_Mode': 65315, - 'Hiragana': 65317, - 'Hiragana_Katakana': 65319, - 'Home': 65360, - 'Hstroke': 673, - 'Hyper_L': 65517, - 'Hyper_R': 65518, - 'I': 73, - 'ISO_Center_Object': 65075, - 'ISO_Continuous_Underline': 65072, - 'ISO_Discontinuous_Underline': 65073, - 'ISO_Emphasize': 65074, - 'ISO_Enter': 65076, - 'ISO_Fast_Cursor_Down': 65071, - 'ISO_Fast_Cursor_Left': 65068, - 'ISO_Fast_Cursor_Right': 65069, - 'ISO_Fast_Cursor_Up': 65070, - 'ISO_First_Group': 65036, - 'ISO_First_Group_Lock': 65037, - 'ISO_Group_Latch': 65030, - 'ISO_Group_Lock': 65031, - 'ISO_Group_Shift': 65406, - 'ISO_Last_Group': 65038, - 'ISO_Last_Group_Lock': 65039, - 'ISO_Left_Tab': 65056, - 'ISO_Level2_Latch': 65026, - 'ISO_Level3_Latch': 65028, - 'ISO_Level3_Lock': 65029, - 'ISO_Level3_Shift': 65027, - 'ISO_Level5_Latch': 65042, - 'ISO_Level5_Lock': 65043, - 'ISO_Level5_Shift': 65041, - 'ISO_Lock': 65025, - 'ISO_Move_Line_Down': 65058, - 'ISO_Move_Line_Up': 65057, - 'ISO_Next_Group': 65032, - 'ISO_Next_Group_Lock': 65033, - 'ISO_Partial_Line_Down': 65060, - 'ISO_Partial_Line_Up': 65059, - 'ISO_Partial_Space_Left': 65061, - 'ISO_Partial_Space_Right': 65062, - 'ISO_Prev_Group': 65034, - 'ISO_Prev_Group_Lock': 65035, - 'ISO_Release_Both_Margins': 65067, - 'ISO_Release_Margin_Left': 65065, - 'ISO_Release_Margin_Right': 65066, - 'ISO_Set_Margin_Left': 65063, - 'ISO_Set_Margin_Right': 65064, - 'Iabovedot': 681, - 'Iacute': 205, - 'Ibelowdot': 16785098, - 'Ibreve': 16777516, - 'Icircumflex': 206, - 'Idiaeresis': 207, - 'Igrave': 204, - 'Ihook': 16785096, - 'Imacron': 975, - 'Insert': 65379, - 'Iogonek': 967, - 'Itilde': 933, - 'J': 74, - 'Jcircumflex': 684, - 'K': 75, - 'KP_0': 65456, - 'KP_1': 65457, - 'KP_2': 65458, - 'KP_3': 65459, - 'KP_4': 65460, - 'KP_5': 65461, - 'KP_6': 65462, - 'KP_7': 65463, - 'KP_8': 65464, - 'KP_9': 65465, - 'KP_Add': 65451, - 'KP_Begin': 65437, - 'KP_Decimal': 65454, - 'KP_Delete': 65439, - 'KP_Divide': 65455, - 'KP_Down': 65433, - 'KP_End': 65436, - 'KP_Enter': 65421, - 'KP_Equal': 65469, - 'KP_F1': 65425, - 'KP_F2': 65426, - 'KP_F3': 65427, - 'KP_F4': 65428, - 'KP_Home': 65429, - 'KP_Insert': 65438, - 'KP_Left': 65430, - 'KP_Multiply': 65450, - 'KP_Next': 65435, - 'KP_Page_Down': 65435, - 'KP_Page_Up': 65434, - 'KP_Prior': 65434, - 'KP_Right': 65432, - 'KP_Separator': 65452, - 'KP_Space': 65408, - 'KP_Subtract': 65453, - 'KP_Tab': 65417, - 'KP_Up': 65431, - 'Kana_Lock': 65325, - 'Kana_Shift': 65326, - 'Kanji': 65313, - 'Kanji_Bangou': 65335, - 'Katakana': 65318, - 'Kcedilla': 979, - 'Korean_Won': 3839, - 'L': 76, - 'L1': 65480, - 'L10': 65489, - 'L2': 65481, - 'L3': 65482, - 'L4': 65483, - 'L5': 65484, - 'L6': 65485, - 'L7': 65486, - 'L8': 65487, - 'L9': 65488, - 'Lacute': 453, - 'Last_Virtual_Screen': 65236, - 'Lbelowdot': 16784950, - 'Lcaron': 421, - 'Lcedilla': 934, - 'Left': 65361, - 'Linefeed': 65290, - 'LiraSign': 16785572, - 'Lstroke': 419, - 'M': 77, - 'Mabovedot': 16784960, - 'Macedonia_DSE': 1717, - 'Macedonia_GJE': 1714, - 'Macedonia_KJE': 1724, - 'Macedonia_dse': 1701, - 'Macedonia_gje': 1698, - 'Macedonia_kje': 1708, - 'Mae_Koho': 65342, - 'Massyo': 65324, - 'Menu': 65383, - 'Meta_L': 65511, - 'Meta_R': 65512, - 'MillSign': 16785573, - 'Mode_switch': 65406, - 'MouseKeys_Accel_Enable': 65143, - 'MouseKeys_Enable': 65142, - 'Muhenkan': 65314, - 'Multi_key': 65312, - 'MultipleCandidate': 65341, - 'N': 78, - 'Nacute': 465, - 'NairaSign': 16785574, - 'Ncaron': 466, - 'Ncedilla': 977, - 'NewSheqelSign': 16785578, - 'Next': 65366, - 'Next_Virtual_Screen': 65234, - 'Ntilde': 209, - 'Num_Lock': 65407, - 'O': 79, - 'OE': 5052, - 'Oacute': 211, - 'Obarred': 16777631, - 'Obelowdot': 16785100, - 'Ocaron': 16777681, - 'Ocircumflex': 212, - 'Ocircumflexacute': 16785104, - 'Ocircumflexbelowdot': 16785112, - 'Ocircumflexgrave': 16785106, - 'Ocircumflexhook': 16785108, - 'Ocircumflextilde': 16785110, - 'Odiaeresis': 214, - 'Odoubleacute': 469, - 'Ograve': 210, - 'Ohook': 16785102, - 'Ohorn': 16777632, - 'Ohornacute': 16785114, - 'Ohornbelowdot': 16785122, - 'Ohorngrave': 16785116, - 'Ohornhook': 16785118, - 'Ohorntilde': 16785120, - 'Omacron': 978, - 'Ooblique': 216, - 'Oslash': 216, - 'Otilde': 213, - 'Overlay1_Enable': 65144, - 'Overlay2_Enable': 65145, - 'P': 80, - 'Pabovedot': 16784982, - 'Page_Down': 65366, - 'Page_Up': 65365, - 'Pause': 65299, - 'PesetaSign': 16785575, - 'Pointer_Accelerate': 65274, - 'Pointer_Button1': 65257, - 'Pointer_Button2': 65258, - 'Pointer_Button3': 65259, - 'Pointer_Button4': 65260, - 'Pointer_Button5': 65261, - 'Pointer_Button_Dflt': 65256, - 'Pointer_DblClick1': 65263, - 'Pointer_DblClick2': 65264, - 'Pointer_DblClick3': 65265, - 'Pointer_DblClick4': 65266, - 'Pointer_DblClick5': 65267, - 'Pointer_DblClick_Dflt': 65262, - 'Pointer_DfltBtnNext': 65275, - 'Pointer_DfltBtnPrev': 65276, - 'Pointer_Down': 65251, - 'Pointer_DownLeft': 65254, - 'Pointer_DownRight': 65255, - 'Pointer_Drag1': 65269, - 'Pointer_Drag2': 65270, - 'Pointer_Drag3': 65271, - 'Pointer_Drag4': 65272, - 'Pointer_Drag5': 65277, - 'Pointer_Drag_Dflt': 65268, - 'Pointer_EnableKeys': 65273, - 'Pointer_Left': 65248, - 'Pointer_Right': 65249, - 'Pointer_Up': 65250, - 'Pointer_UpLeft': 65252, - 'Pointer_UpRight': 65253, - 'Prev_Virtual_Screen': 65233, - 'PreviousCandidate': 65342, - 'Print': 65377, - 'Prior': 65365, - 'Q': 81, - 'R': 82, - 'R1': 65490, - 'R10': 65499, - 'R11': 65500, - 'R12': 65501, - 'R13': 65502, - 'R14': 65503, - 'R15': 65504, - 'R2': 65491, - 'R3': 65492, - 'R4': 65493, - 'R5': 65494, - 'R6': 65495, - 'R7': 65496, - 'R8': 65497, - 'R9': 65498, - 'Racute': 448, - 'Rcaron': 472, - 'Rcedilla': 931, - 'Redo': 65382, - 'RepeatKeys_Enable': 65138, - 'Return': 65293, - 'Right': 65363, - 'Romaji': 65316, - 'RupeeSign': 16785576, - 'S': 83, - 'SCHWA': 16777615, - 'Sabovedot': 16784992, - 'Sacute': 422, - 'Scaron': 425, - 'Scedilla': 426, - 'Scircumflex': 734, - 'Scroll_Lock': 65300, - 'Select': 65376, - 'Serbian_DJE': 1713, - 'Serbian_DZE': 1727, - 'Serbian_JE': 1720, - 'Serbian_LJE': 1721, - 'Serbian_NJE': 1722, - 'Serbian_TSHE': 1723, - 'Serbian_dje': 1697, - 'Serbian_dze': 1711, - 'Serbian_je': 1704, - 'Serbian_lje': 1705, - 'Serbian_nje': 1706, - 'Serbian_tshe': 1707, - 'Shift_L': 65505, - 'Shift_Lock': 65510, - 'Shift_R': 65506, - 'SingleCandidate': 65340, - 'Sinh_a': 16780677, - 'Sinh_aa': 16780678, - 'Sinh_aa2': 16780751, - 'Sinh_ae': 16780679, - 'Sinh_ae2': 16780752, - 'Sinh_aee': 16780680, - 'Sinh_aee2': 16780753, - 'Sinh_ai': 16780691, - 'Sinh_ai2': 16780763, - 'Sinh_al': 16780746, - 'Sinh_au': 16780694, - 'Sinh_au2': 16780766, - 'Sinh_ba': 16780726, - 'Sinh_bha': 16780727, - 'Sinh_ca': 16780704, - 'Sinh_cha': 16780705, - 'Sinh_dda': 16780713, - 'Sinh_ddha': 16780714, - 'Sinh_dha': 16780719, - 'Sinh_dhha': 16780720, - 'Sinh_e': 16780689, - 'Sinh_e2': 16780761, - 'Sinh_ee': 16780690, - 'Sinh_ee2': 16780762, - 'Sinh_fa': 16780742, - 'Sinh_ga': 16780700, - 'Sinh_gha': 16780701, - 'Sinh_h2': 16780675, - 'Sinh_ha': 16780740, - 'Sinh_i': 16780681, - 'Sinh_i2': 16780754, - 'Sinh_ii': 16780682, - 'Sinh_ii2': 16780755, - 'Sinh_ja': 16780706, - 'Sinh_jha': 16780707, - 'Sinh_jnya': 16780709, - 'Sinh_ka': 16780698, - 'Sinh_kha': 16780699, - 'Sinh_kunddaliya': 16780788, - 'Sinh_la': 16780733, - 'Sinh_lla': 16780741, - 'Sinh_lu': 16780687, - 'Sinh_lu2': 16780767, - 'Sinh_luu': 16780688, - 'Sinh_luu2': 16780787, - 'Sinh_ma': 16780728, - 'Sinh_mba': 16780729, - 'Sinh_na': 16780721, - 'Sinh_ndda': 16780716, - 'Sinh_ndha': 16780723, - 'Sinh_ng': 16780674, - 'Sinh_ng2': 16780702, - 'Sinh_nga': 16780703, - 'Sinh_nja': 16780710, - 'Sinh_nna': 16780715, - 'Sinh_nya': 16780708, - 'Sinh_o': 16780692, - 'Sinh_o2': 16780764, - 'Sinh_oo': 16780693, - 'Sinh_oo2': 16780765, - 'Sinh_pa': 16780724, - 'Sinh_pha': 16780725, - 'Sinh_ra': 16780731, - 'Sinh_ri': 16780685, - 'Sinh_rii': 16780686, - 'Sinh_ru2': 16780760, - 'Sinh_ruu2': 16780786, - 'Sinh_sa': 16780739, - 'Sinh_sha': 16780737, - 'Sinh_ssha': 16780738, - 'Sinh_tha': 16780717, - 'Sinh_thha': 16780718, - 'Sinh_tta': 16780711, - 'Sinh_ttha': 16780712, - 'Sinh_u': 16780683, - 'Sinh_u2': 16780756, - 'Sinh_uu': 16780684, - 'Sinh_uu2': 16780758, - 'Sinh_va': 16780736, - 'Sinh_ya': 16780730, - 'SlowKeys_Enable': 65139, - 'StickyKeys_Enable': 65141, - 'Super_L': 65515, - 'Super_R': 65516, - 'Sys_Req': 65301, - 'T': 84, - 'THORN': 222, - 'Tab': 65289, - 'Tabovedot': 16785002, - 'Tcaron': 427, - 'Tcedilla': 478, - 'Terminate_Server': 65237, - 'Thai_baht': 3551, - 'Thai_bobaimai': 3514, - 'Thai_chochan': 3496, - 'Thai_chochang': 3498, - 'Thai_choching': 3497, - 'Thai_chochoe': 3500, - 'Thai_dochada': 3502, - 'Thai_dodek': 3508, - 'Thai_fofa': 3517, - 'Thai_fofan': 3519, - 'Thai_hohip': 3531, - 'Thai_honokhuk': 3534, - 'Thai_khokhai': 3490, - 'Thai_khokhon': 3493, - 'Thai_khokhuat': 3491, - 'Thai_khokhwai': 3492, - 'Thai_khorakhang': 3494, - 'Thai_kokai': 3489, - 'Thai_lakkhangyao': 3557, - 'Thai_lekchet': 3575, - 'Thai_lekha': 3573, - 'Thai_lekhok': 3574, - 'Thai_lekkao': 3577, - 'Thai_leknung': 3569, - 'Thai_lekpaet': 3576, - 'Thai_leksam': 3571, - 'Thai_leksi': 3572, - 'Thai_leksong': 3570, - 'Thai_leksun': 3568, - 'Thai_lochula': 3532, - 'Thai_loling': 3525, - 'Thai_lu': 3526, - 'Thai_maichattawa': 3563, - 'Thai_maiek': 3560, - 'Thai_maihanakat': 3537, - 'Thai_maihanakat_maitho': 3550, - 'Thai_maitaikhu': 3559, - 'Thai_maitho': 3561, - 'Thai_maitri': 3562, - 'Thai_maiyamok': 3558, - 'Thai_moma': 3521, - 'Thai_ngongu': 3495, - 'Thai_nikhahit': 3565, - 'Thai_nonen': 3507, - 'Thai_nonu': 3513, - 'Thai_oang': 3533, - 'Thai_paiyannoi': 3535, - 'Thai_phinthu': 3546, - 'Thai_phophan': 3518, - 'Thai_phophung': 3516, - 'Thai_phosamphao': 3520, - 'Thai_popla': 3515, - 'Thai_rorua': 3523, - 'Thai_ru': 3524, - 'Thai_saraa': 3536, - 'Thai_saraaa': 3538, - 'Thai_saraae': 3553, - 'Thai_saraaimaimalai': 3556, - 'Thai_saraaimaimuan': 3555, - 'Thai_saraam': 3539, - 'Thai_sarae': 3552, - 'Thai_sarai': 3540, - 'Thai_saraii': 3541, - 'Thai_sarao': 3554, - 'Thai_sarau': 3544, - 'Thai_saraue': 3542, - 'Thai_sarauee': 3543, - 'Thai_sarauu': 3545, - 'Thai_sorusi': 3529, - 'Thai_sosala': 3528, - 'Thai_soso': 3499, - 'Thai_sosua': 3530, - 'Thai_thanthakhat': 3564, - 'Thai_thonangmontho': 3505, - 'Thai_thophuthao': 3506, - 'Thai_thothahan': 3511, - 'Thai_thothan': 3504, - 'Thai_thothong': 3512, - 'Thai_thothung': 3510, - 'Thai_topatak': 3503, - 'Thai_totao': 3509, - 'Thai_wowaen': 3527, - 'Thai_yoyak': 3522, - 'Thai_yoying': 3501, - 'Thorn': 222, - 'Touroku': 65323, - 'Tslash': 940, - 'U': 85, - 'Uacute': 218, - 'Ubelowdot': 16785124, - 'Ubreve': 733, - 'Ucircumflex': 219, - 'Udiaeresis': 220, - 'Udoubleacute': 475, - 'Ugrave': 217, - 'Uhook': 16785126, - 'Uhorn': 16777647, - 'Uhornacute': 16785128, - 'Uhornbelowdot': 16785136, - 'Uhorngrave': 16785130, - 'Uhornhook': 16785132, - 'Uhorntilde': 16785134, - 'Ukrainian_GHE_WITH_UPTURN': 1725, - 'Ukrainian_I': 1718, - 'Ukrainian_IE': 1716, - 'Ukrainian_YI': 1719, - 'Ukrainian_ghe_with_upturn': 1709, - 'Ukrainian_i': 1702, - 'Ukrainian_ie': 1700, - 'Ukrainian_yi': 1703, - 'Ukranian_I': 1718, - 'Ukranian_JE': 1716, - 'Ukranian_YI': 1719, - 'Ukranian_i': 1702, - 'Ukranian_je': 1700, - 'Ukranian_yi': 1703, - 'Umacron': 990, - 'Undo': 65381, - 'Uogonek': 985, - 'Up': 65362, - 'Uring': 473, - 'Utilde': 989, - 'V': 86, - 'VoidSymbol': 16777215, - 'W': 87, - 'Wacute': 16785026, - 'Wcircumflex': 16777588, - 'Wdiaeresis': 16785028, - 'Wgrave': 16785024, - 'WonSign': 16785577, - 'X': 88, - 'XF86_AddFavorite': 269025081, - 'XF86_ApplicationLeft': 269025104, - 'XF86_ApplicationRight': 269025105, - 'XF86_AudioCycleTrack': 269025179, - 'XF86_AudioForward': 269025175, - 'XF86_AudioLowerVolume': 269025041, - 'XF86_AudioMedia': 269025074, - 'XF86_AudioMicMute': 269025202, - 'XF86_AudioMute': 269025042, - 'XF86_AudioNext': 269025047, - 'XF86_AudioPause': 269025073, - 'XF86_AudioPlay': 269025044, - 'XF86_AudioPreset': 269025206, - 'XF86_AudioPrev': 269025046, - 'XF86_AudioRaiseVolume': 269025043, - 'XF86_AudioRandomPlay': 269025177, - 'XF86_AudioRecord': 269025052, - 'XF86_AudioRepeat': 269025176, - 'XF86_AudioRewind': 269025086, - 'XF86_AudioStop': 269025045, - 'XF86_Away': 269025165, - 'XF86_Back': 269025062, - 'XF86_BackForward': 269025087, - 'XF86_Battery': 269025171, - 'XF86_Blue': 269025190, - 'XF86_Bluetooth': 269025172, - 'XF86_Book': 269025106, - 'XF86_BrightnessAdjust': 269025083, - 'XF86_CD': 269025107, - 'XF86_Calculater': 269025108, - 'XF86_Calculator': 269025053, - 'XF86_Calendar': 269025056, - 'XF86_Clear': 269025109, - 'XF86_ClearGrab': 269024801, - 'XF86_Close': 269025110, - 'XF86_Community': 269025085, - 'XF86_ContrastAdjust': 269025058, - 'XF86_Copy': 269025111, - 'XF86_Cut': 269025112, - 'XF86_CycleAngle': 269025180, - 'XF86_DOS': 269025114, - 'XF86_Display': 269025113, - 'XF86_Documents': 269025115, - 'XF86_Eject': 269025068, - 'XF86_Excel': 269025116, - 'XF86_Explorer': 269025117, - 'XF86_Favorites': 269025072, - 'XF86_Finance': 269025084, - 'XF86_Forward': 269025063, - 'XF86_FrameBack': 269025181, - 'XF86_FrameForward': 269025182, - 'XF86_FullScreen': 269025208, - 'XF86_Game': 269025118, - 'XF86_Go': 269025119, - 'XF86_Green': 269025188, - 'XF86_Hibernate': 269025192, - 'XF86_History': 269025079, - 'XF86_HomePage': 269025048, - 'XF86_HotLinks': 269025082, - 'XF86_KbdBrightnessDown': 269025030, - 'XF86_KbdBrightnessUp': 269025029, - 'XF86_KbdLightOnOff': 269025028, - 'XF86_Keyboard': 269025203, - 'XF86_Launch0': 269025088, - 'XF86_Launch1': 269025089, - 'XF86_Launch2': 269025090, - 'XF86_Launch3': 269025091, - 'XF86_Launch4': 269025092, - 'XF86_Launch5': 269025093, - 'XF86_Launch6': 269025094, - 'XF86_Launch7': 269025095, - 'XF86_Launch8': 269025096, - 'XF86_Launch9': 269025097, - 'XF86_LaunchA': 269025098, - 'XF86_LaunchB': 269025099, - 'XF86_LaunchC': 269025100, - 'XF86_LaunchD': 269025101, - 'XF86_LaunchE': 269025102, - 'XF86_LaunchF': 269025103, - 'XF86_LightBulb': 269025077, - 'XF86_LogGrabInfo': 269024805, - 'XF86_LogOff': 269025121, - 'XF86_LogWindowTree': 269024804, - 'XF86_MacroRecordStart': 268964528, - 'XF86_Mail': 269025049, - 'XF86_MailForward': 269025168, - 'XF86_Market': 269025122, - 'XF86_Meeting': 269025123, - 'XF86_Memo': 269025054, - 'XF86_MenuKB': 269025125, - 'XF86_MenuPB': 269025126, - 'XF86_Messenger': 269025166, - 'XF86_ModeLock': 269025025, - 'XF86_MonBrightnessCycle': 269025031, - 'XF86_MonBrightnessDown': 269025027, - 'XF86_MonBrightnessUp': 269025026, - 'XF86_Music': 269025170, - 'XF86_MyComputer': 269025075, - 'XF86_MySites': 269025127, - 'XF86_New': 269025128, - 'XF86_News': 269025129, - 'XF86_Next_VMode': 269024802, - 'XF86_OfficeHome': 269025130, - 'XF86_Open': 269025131, - 'XF86_OpenURL': 269025080, - 'XF86_Option': 269025132, - 'XF86_Paste': 269025133, - 'XF86_Phone': 269025134, - 'XF86_Pictures': 269025169, - 'XF86_PowerDown': 269025057, - 'XF86_PowerOff': 269025066, - 'XF86_Prev_VMode': 269024803, - 'XF86_Q': 269025136, - 'XF86_RFKill': 269025205, - 'XF86_Red': 269025187, - 'XF86_Refresh': 269025065, - 'XF86_Reload': 269025139, - 'XF86_Reply': 269025138, - 'XF86_RockerDown': 269025060, - 'XF86_RockerEnter': 269025061, - 'XF86_RockerUp': 269025059, - 'XF86_RotateWindows': 269025140, - 'XF86_RotationKB': 269025142, - 'XF86_RotationLockToggle': 269025207, - 'XF86_RotationPB': 269025141, - 'XF86_Save': 269025143, - 'XF86_ScreenSaver': 269025069, - 'XF86_ScrollClick': 269025146, - 'XF86_ScrollDown': 269025145, - 'XF86_ScrollUp': 269025144, - 'XF86_Search': 269025051, - 'XF86_Select': 269025184, - 'XF86_Send': 269025147, - 'XF86_Shop': 269025078, - 'XF86_Sleep': 269025071, - 'XF86_Spell': 269025148, - 'XF86_SplitScreen': 269025149, - 'XF86_Standby': 269025040, - 'XF86_Start': 269025050, - 'XF86_Stop': 269025064, - 'XF86_Subtitle': 269025178, - 'XF86_Support': 269025150, - 'XF86_Suspend': 269025191, - 'XF86_Switch_VT_1': 269024769, - 'XF86_Switch_VT_10': 269024778, - 'XF86_Switch_VT_11': 269024779, - 'XF86_Switch_VT_12': 269024780, - 'XF86_Switch_VT_2': 269024770, - 'XF86_Switch_VT_3': 269024771, - 'XF86_Switch_VT_4': 269024772, - 'XF86_Switch_VT_5': 269024773, - 'XF86_Switch_VT_6': 269024774, - 'XF86_Switch_VT_7': 269024775, - 'XF86_Switch_VT_8': 269024776, - 'XF86_Switch_VT_9': 269024777, - 'XF86_TaskPane': 269025151, - 'XF86_Terminal': 269025152, - 'XF86_Time': 269025183, - 'XF86_ToDoList': 269025055, - 'XF86_Tools': 269025153, - 'XF86_TopMenu': 269025186, - 'XF86_TouchpadOff': 269025201, - 'XF86_TouchpadOn': 269025200, - 'XF86_TouchpadToggle': 269025193, - 'XF86_Travel': 269025154, - 'XF86_UWB': 269025174, - 'XF86_Ungrab': 269024800, - 'XF86_User1KB': 269025157, - 'XF86_User2KB': 269025158, - 'XF86_UserPB': 269025156, - 'XF86_VendorHome': 269025076, - 'XF86_Video': 269025159, - 'XF86_View': 269025185, - 'XF86_WLAN': 269025173, - 'XF86_WWAN': 269025204, - 'XF86_WWW': 269025070, - 'XF86_WakeUp': 269025067, - 'XF86_WebCam': 269025167, - 'XF86_WheelButton': 269025160, - 'XF86_Word': 269025161, - 'XF86_Xfer': 269025162, - 'XF86_Yellow': 269025189, - 'XF86_ZoomIn': 269025163, - 'XF86_ZoomOut': 269025164, - 'XF86_iTouch': 269025120, - 'Xabovedot': 16785034, - 'Y': 89, - 'Yacute': 221, - 'Ybelowdot': 16785140, - 'Ycircumflex': 16777590, - 'Ydiaeresis': 5054, - 'Ygrave': 16785138, - 'Yhook': 16785142, - 'Ytilde': 16785144, - 'Z': 90, - 'Zabovedot': 431, - 'Zacute': 428, - 'Zcaron': 430, - 'Zen_Koho': 65341, - 'Zenkaku': 65320, - 'Zenkaku_Hankaku': 65322, - 'Zstroke': 16777653, - 'a': 97, - 'aacute': 225, - 'abelowdot': 16785057, - 'abovedot': 511, - 'abreve': 483, - 'abreveacute': 16785071, - 'abrevebelowdot': 16785079, - 'abrevegrave': 16785073, - 'abrevehook': 16785075, - 'abrevetilde': 16785077, - 'acircumflex': 226, - 'acircumflexacute': 16785061, - 'acircumflexbelowdot': 16785069, - 'acircumflexgrave': 16785063, - 'acircumflexhook': 16785065, - 'acircumflextilde': 16785067, - 'acute': 180, - 'adiaeresis': 228, - 'ae': 230, - 'agrave': 224, - 'ahook': 16785059, - 'amacron': 992, - 'ampersand': 38, - 'aogonek': 433, - 'apostrophe': 39, - 'approxeq': 16785992, - 'approximate': 2248, - 'aring': 229, - 'asciicircum': 94, - 'asciitilde': 126, - 'asterisk': 42, - 'at': 64, - 'atilde': 227, - 'b': 98, - 'babovedot': 16784899, - 'backslash': 92, - 'ballotcross': 2804, - 'bar': 124, - 'because': 16785973, - 'blank': 2527, - 'botintegral': 2213, - 'botleftparens': 2220, - 'botleftsqbracket': 2216, - 'botleftsummation': 2226, - 'botrightparens': 2222, - 'botrightsqbracket': 2218, - 'botrightsummation': 2230, - 'bott': 2550, - 'botvertsummationconnector': 2228, - 'braceleft': 123, - 'braceright': 125, - 'bracketleft': 91, - 'bracketright': 93, - 'braille_blank': 16787456, - 'braille_dot_1': 65521, - 'braille_dot_10': 65530, - 'braille_dot_2': 65522, - 'braille_dot_3': 65523, - 'braille_dot_4': 65524, - 'braille_dot_5': 65525, - 'braille_dot_6': 65526, - 'braille_dot_7': 65527, - 'braille_dot_8': 65528, - 'braille_dot_9': 65529, - 'braille_dots_1': 16787457, - 'braille_dots_12': 16787459, - 'braille_dots_123': 16787463, - 'braille_dots_1234': 16787471, - 'braille_dots_12345': 16787487, - 'braille_dots_123456': 16787519, - 'braille_dots_1234567': 16787583, - 'braille_dots_12345678': 16787711, - 'braille_dots_1234568': 16787647, - 'braille_dots_123457': 16787551, - 'braille_dots_1234578': 16787679, - 'braille_dots_123458': 16787615, - 'braille_dots_12346': 16787503, - 'braille_dots_123467': 16787567, - 'braille_dots_1234678': 16787695, - 'braille_dots_123468': 16787631, - 'braille_dots_12347': 16787535, - 'braille_dots_123478': 16787663, - 'braille_dots_12348': 16787599, - 'braille_dots_1235': 16787479, - 'braille_dots_12356': 16787511, - 'braille_dots_123567': 16787575, - 'braille_dots_1235678': 16787703, - 'braille_dots_123568': 16787639, - 'braille_dots_12357': 16787543, - 'braille_dots_123578': 16787671, - 'braille_dots_12358': 16787607, - 'braille_dots_1236': 16787495, - 'braille_dots_12367': 16787559, - 'braille_dots_123678': 16787687, - 'braille_dots_12368': 16787623, - 'braille_dots_1237': 16787527, - 'braille_dots_12378': 16787655, - 'braille_dots_1238': 16787591, - 'braille_dots_124': 16787467, - 'braille_dots_1245': 16787483, - 'braille_dots_12456': 16787515, - 'braille_dots_124567': 16787579, - 'braille_dots_1245678': 16787707, - 'braille_dots_124568': 16787643, - 'braille_dots_12457': 16787547, - 'braille_dots_124578': 16787675, - 'braille_dots_12458': 16787611, - 'braille_dots_1246': 16787499, - 'braille_dots_12467': 16787563, - 'braille_dots_124678': 16787691, - 'braille_dots_12468': 16787627, - 'braille_dots_1247': 16787531, - 'braille_dots_12478': 16787659, - 'braille_dots_1248': 16787595, - 'braille_dots_125': 16787475, - 'braille_dots_1256': 16787507, - 'braille_dots_12567': 16787571, - 'braille_dots_125678': 16787699, - 'braille_dots_12568': 16787635, - 'braille_dots_1257': 16787539, - 'braille_dots_12578': 16787667, - 'braille_dots_1258': 16787603, - 'braille_dots_126': 16787491, - 'braille_dots_1267': 16787555, - 'braille_dots_12678': 16787683, - 'braille_dots_1268': 16787619, - 'braille_dots_127': 16787523, - 'braille_dots_1278': 16787651, - 'braille_dots_128': 16787587, - 'braille_dots_13': 16787461, - 'braille_dots_134': 16787469, - 'braille_dots_1345': 16787485, - 'braille_dots_13456': 16787517, - 'braille_dots_134567': 16787581, - 'braille_dots_1345678': 16787709, - 'braille_dots_134568': 16787645, - 'braille_dots_13457': 16787549, - 'braille_dots_134578': 16787677, - 'braille_dots_13458': 16787613, - 'braille_dots_1346': 16787501, - 'braille_dots_13467': 16787565, - 'braille_dots_134678': 16787693, - 'braille_dots_13468': 16787629, - 'braille_dots_1347': 16787533, - 'braille_dots_13478': 16787661, - 'braille_dots_1348': 16787597, - 'braille_dots_135': 16787477, - 'braille_dots_1356': 16787509, - 'braille_dots_13567': 16787573, - 'braille_dots_135678': 16787701, - 'braille_dots_13568': 16787637, - 'braille_dots_1357': 16787541, - 'braille_dots_13578': 16787669, - 'braille_dots_1358': 16787605, - 'braille_dots_136': 16787493, - 'braille_dots_1367': 16787557, - 'braille_dots_13678': 16787685, - 'braille_dots_1368': 16787621, - 'braille_dots_137': 16787525, - 'braille_dots_1378': 16787653, - 'braille_dots_138': 16787589, - 'braille_dots_14': 16787465, - 'braille_dots_145': 16787481, - 'braille_dots_1456': 16787513, - 'braille_dots_14567': 16787577, - 'braille_dots_145678': 16787705, - 'braille_dots_14568': 16787641, - 'braille_dots_1457': 16787545, - 'braille_dots_14578': 16787673, - 'braille_dots_1458': 16787609, - 'braille_dots_146': 16787497, - 'braille_dots_1467': 16787561, - 'braille_dots_14678': 16787689, - 'braille_dots_1468': 16787625, - 'braille_dots_147': 16787529, - 'braille_dots_1478': 16787657, - 'braille_dots_148': 16787593, - 'braille_dots_15': 16787473, - 'braille_dots_156': 16787505, - 'braille_dots_1567': 16787569, - 'braille_dots_15678': 16787697, - 'braille_dots_1568': 16787633, - 'braille_dots_157': 16787537, - 'braille_dots_1578': 16787665, - 'braille_dots_158': 16787601, - 'braille_dots_16': 16787489, - 'braille_dots_167': 16787553, - 'braille_dots_1678': 16787681, - 'braille_dots_168': 16787617, - 'braille_dots_17': 16787521, - 'braille_dots_178': 16787649, - 'braille_dots_18': 16787585, - 'braille_dots_2': 16787458, - 'braille_dots_23': 16787462, - 'braille_dots_234': 16787470, - 'braille_dots_2345': 16787486, - 'braille_dots_23456': 16787518, - 'braille_dots_234567': 16787582, - 'braille_dots_2345678': 16787710, - 'braille_dots_234568': 16787646, - 'braille_dots_23457': 16787550, - 'braille_dots_234578': 16787678, - 'braille_dots_23458': 16787614, - 'braille_dots_2346': 16787502, - 'braille_dots_23467': 16787566, - 'braille_dots_234678': 16787694, - 'braille_dots_23468': 16787630, - 'braille_dots_2347': 16787534, - 'braille_dots_23478': 16787662, - 'braille_dots_2348': 16787598, - 'braille_dots_235': 16787478, - 'braille_dots_2356': 16787510, - 'braille_dots_23567': 16787574, - 'braille_dots_235678': 16787702, - 'braille_dots_23568': 16787638, - 'braille_dots_2357': 16787542, - 'braille_dots_23578': 16787670, - 'braille_dots_2358': 16787606, - 'braille_dots_236': 16787494, - 'braille_dots_2367': 16787558, - 'braille_dots_23678': 16787686, - 'braille_dots_2368': 16787622, - 'braille_dots_237': 16787526, - 'braille_dots_2378': 16787654, - 'braille_dots_238': 16787590, - 'braille_dots_24': 16787466, - 'braille_dots_245': 16787482, - 'braille_dots_2456': 16787514, - 'braille_dots_24567': 16787578, - 'braille_dots_245678': 16787706, - 'braille_dots_24568': 16787642, - 'braille_dots_2457': 16787546, - 'braille_dots_24578': 16787674, - 'braille_dots_2458': 16787610, - 'braille_dots_246': 16787498, - 'braille_dots_2467': 16787562, - 'braille_dots_24678': 16787690, - 'braille_dots_2468': 16787626, - 'braille_dots_247': 16787530, - 'braille_dots_2478': 16787658, - 'braille_dots_248': 16787594, - 'braille_dots_25': 16787474, - 'braille_dots_256': 16787506, - 'braille_dots_2567': 16787570, - 'braille_dots_25678': 16787698, - 'braille_dots_2568': 16787634, - 'braille_dots_257': 16787538, - 'braille_dots_2578': 16787666, - 'braille_dots_258': 16787602, - 'braille_dots_26': 16787490, - 'braille_dots_267': 16787554, - 'braille_dots_2678': 16787682, - 'braille_dots_268': 16787618, - 'braille_dots_27': 16787522, - 'braille_dots_278': 16787650, - 'braille_dots_28': 16787586, - 'braille_dots_3': 16787460, - 'braille_dots_34': 16787468, - 'braille_dots_345': 16787484, - 'braille_dots_3456': 16787516, - 'braille_dots_34567': 16787580, - 'braille_dots_345678': 16787708, - 'braille_dots_34568': 16787644, - 'braille_dots_3457': 16787548, - 'braille_dots_34578': 16787676, - 'braille_dots_3458': 16787612, - 'braille_dots_346': 16787500, - 'braille_dots_3467': 16787564, - 'braille_dots_34678': 16787692, - 'braille_dots_3468': 16787628, - 'braille_dots_347': 16787532, - 'braille_dots_3478': 16787660, - 'braille_dots_348': 16787596, - 'braille_dots_35': 16787476, - 'braille_dots_356': 16787508, - 'braille_dots_3567': 16787572, - 'braille_dots_35678': 16787700, - 'braille_dots_3568': 16787636, - 'braille_dots_357': 16787540, - 'braille_dots_3578': 16787668, - 'braille_dots_358': 16787604, - 'braille_dots_36': 16787492, - 'braille_dots_367': 16787556, - 'braille_dots_3678': 16787684, - 'braille_dots_368': 16787620, - 'braille_dots_37': 16787524, - 'braille_dots_378': 16787652, - 'braille_dots_38': 16787588, - 'braille_dots_4': 16787464, - 'braille_dots_45': 16787480, - 'braille_dots_456': 16787512, - 'braille_dots_4567': 16787576, - 'braille_dots_45678': 16787704, - 'braille_dots_4568': 16787640, - 'braille_dots_457': 16787544, - 'braille_dots_4578': 16787672, - 'braille_dots_458': 16787608, - 'braille_dots_46': 16787496, - 'braille_dots_467': 16787560, - 'braille_dots_4678': 16787688, - 'braille_dots_468': 16787624, - 'braille_dots_47': 16787528, - 'braille_dots_478': 16787656, - 'braille_dots_48': 16787592, - 'braille_dots_5': 16787472, - 'braille_dots_56': 16787504, - 'braille_dots_567': 16787568, - 'braille_dots_5678': 16787696, - 'braille_dots_568': 16787632, - 'braille_dots_57': 16787536, - 'braille_dots_578': 16787664, - 'braille_dots_58': 16787600, - 'braille_dots_6': 16787488, - 'braille_dots_67': 16787552, - 'braille_dots_678': 16787680, - 'braille_dots_68': 16787616, - 'braille_dots_7': 16787520, - 'braille_dots_78': 16787648, - 'braille_dots_8': 16787584, - 'breve': 418, - 'brokenbar': 166, - 'c': 99, - 'c_h': 65187, - 'cabovedot': 741, - 'cacute': 486, - 'careof': 2744, - 'caret': 2812, - 'caron': 439, - 'ccaron': 488, - 'ccedilla': 231, - 'ccircumflex': 742, - 'cedilla': 184, - 'cent': 162, - 'ch': 65184, - 'checkerboard': 2529, - 'checkmark': 2803, - 'circle': 3023, - 'club': 2796, - 'colon': 58, - 'combining_acute': 16777985, - 'combining_belowdot': 16778019, - 'combining_grave': 16777984, - 'combining_hook': 16777993, - 'combining_tilde': 16777987, - 'comma': 44, - 'containsas': 16785931, - 'copyright': 169, - 'cr': 2532, - 'crossinglines': 2542, - 'cuberoot': 16785947, - 'currency': 164, - 'cursor': 2815, - 'd': 100, - 'dabovedot': 16784907, - 'dagger': 2801, - 'dcaron': 495, - 'dead_A': 65153, - 'dead_E': 65155, - 'dead_I': 65157, - 'dead_O': 65159, - 'dead_SCHWA': 65163, - 'dead_U': 65161, - 'dead_a': 65152, - 'dead_abovecomma': 65124, - 'dead_abovedot': 65110, - 'dead_abovereversedcomma': 65125, - 'dead_abovering': 65112, - 'dead_acute': 65105, - 'dead_belowbreve': 65131, - 'dead_belowcircumflex': 65129, - 'dead_belowcomma': 65134, - 'dead_belowdiaeresis': 65132, - 'dead_belowdot': 65120, - 'dead_belowmacron': 65128, - 'dead_belowring': 65127, - 'dead_belowtilde': 65130, - 'dead_breve': 65109, - 'dead_capital_schwa': 65163, - 'dead_caron': 65114, - 'dead_cedilla': 65115, - 'dead_circumflex': 65106, - 'dead_currency': 65135, - 'dead_dasia': 65125, - 'dead_diaeresis': 65111, - 'dead_doubleacute': 65113, - 'dead_doublegrave': 65126, - 'dead_e': 65154, - 'dead_grave': 65104, - 'dead_greek': 65164, - 'dead_hamza': 65165, - 'dead_hook': 65121, - 'dead_horn': 65122, - 'dead_i': 65156, - 'dead_invertedbreve': 65133, - 'dead_iota': 65117, - 'dead_macron': 65108, - 'dead_o': 65158, - 'dead_ogonek': 65116, - 'dead_perispomeni': 65107, - 'dead_psili': 65124, - 'dead_schwa': 65162, - 'dead_semivoiced_sound': 65119, - 'dead_small_schwa': 65162, - 'dead_stroke': 65123, - 'dead_tilde': 65107, - 'dead_u': 65160, - 'dead_voiced_sound': 65118, - 'decimalpoint': 2749, - 'degree': 176, - 'diaeresis': 168, - 'diamond': 2797, - 'digitspace': 2725, - 'dintegral': 16785964, - 'division': 247, - 'dollar': 36, - 'doubbaselinedot': 2735, - 'doubleacute': 445, - 'doubledagger': 2802, - 'doublelowquotemark': 2814, - 'downarrow': 2302, - 'downcaret': 2984, - 'downshoe': 3030, - 'downstile': 3012, - 'downtack': 3010, - 'dstroke': 496, - 'e': 101, - 'eabovedot': 1004, - 'eacute': 233, - 'ebelowdot': 16785081, - 'ecaron': 492, - 'ecircumflex': 234, - 'ecircumflexacute': 16785087, - 'ecircumflexbelowdot': 16785095, - 'ecircumflexgrave': 16785089, - 'ecircumflexhook': 16785091, - 'ecircumflextilde': 16785093, - 'ediaeresis': 235, - 'egrave': 232, - 'ehook': 16785083, - 'eightsubscript': 16785544, - 'eightsuperior': 16785528, - 'elementof': 16785928, - 'ellipsis': 2734, - 'em3space': 2723, - 'em4space': 2724, - 'emacron': 954, - 'emdash': 2729, - 'emfilledcircle': 2782, - 'emfilledrect': 2783, - 'emopencircle': 2766, - 'emopenrectangle': 2767, - 'emptyset': 16785925, - 'emspace': 2721, - 'endash': 2730, - 'enfilledcircbullet': 2790, - 'enfilledsqbullet': 2791, - 'eng': 959, - 'enopencircbullet': 2784, - 'enopensquarebullet': 2785, - 'enspace': 2722, - 'eogonek': 490, - 'equal': 61, - 'eth': 240, - 'etilde': 16785085, - 'exclam': 33, - 'exclamdown': 161, - 'ezh': 16777874, - 'f': 102, - 'fabovedot': 16784927, - 'femalesymbol': 2808, - 'ff': 2531, - 'figdash': 2747, - 'filledlefttribullet': 2780, - 'filledrectbullet': 2779, - 'filledrighttribullet': 2781, - 'filledtribulletdown': 2793, - 'filledtribulletup': 2792, - 'fiveeighths': 2757, - 'fivesixths': 2743, - 'fivesubscript': 16785541, - 'fivesuperior': 16785525, - 'fourfifths': 2741, - 'foursubscript': 16785540, - 'foursuperior': 16785524, - 'fourthroot': 16785948, - 'function': 2294, - 'g': 103, - 'gabovedot': 757, - 'gbreve': 699, - 'gcaron': 16777703, - 'gcedilla': 955, - 'gcircumflex': 760, - 'grave': 96, - 'greater': 62, - 'greaterthanequal': 2238, - 'guillemetleft': 171, - 'guillemetright': 187, - 'guillemotleft': 171, - 'guillemotright': 187, - 'h': 104, - 'hairspace': 2728, - 'hcircumflex': 694, - 'heart': 2798, - 'hebrew_aleph': 3296, - 'hebrew_ayin': 3314, - 'hebrew_bet': 3297, - 'hebrew_beth': 3297, - 'hebrew_chet': 3303, - 'hebrew_dalet': 3299, - 'hebrew_daleth': 3299, - 'hebrew_doublelowline': 3295, - 'hebrew_finalkaph': 3306, - 'hebrew_finalmem': 3309, - 'hebrew_finalnun': 3311, - 'hebrew_finalpe': 3315, - 'hebrew_finalzade': 3317, - 'hebrew_finalzadi': 3317, - 'hebrew_gimel': 3298, - 'hebrew_gimmel': 3298, - 'hebrew_he': 3300, - 'hebrew_het': 3303, - 'hebrew_kaph': 3307, - 'hebrew_kuf': 3319, - 'hebrew_lamed': 3308, - 'hebrew_mem': 3310, - 'hebrew_nun': 3312, - 'hebrew_pe': 3316, - 'hebrew_qoph': 3319, - 'hebrew_resh': 3320, - 'hebrew_samech': 3313, - 'hebrew_samekh': 3313, - 'hebrew_shin': 3321, - 'hebrew_taf': 3322, - 'hebrew_taw': 3322, - 'hebrew_tet': 3304, - 'hebrew_teth': 3304, - 'hebrew_waw': 3301, - 'hebrew_yod': 3305, - 'hebrew_zade': 3318, - 'hebrew_zadi': 3318, - 'hebrew_zain': 3302, - 'hebrew_zayin': 3302, - 'hexagram': 2778, - 'horizconnector': 2211, - 'horizlinescan1': 2543, - 'horizlinescan3': 2544, - 'horizlinescan5': 2545, - 'horizlinescan7': 2546, - 'horizlinescan9': 2547, - 'hstroke': 689, - 'ht': 2530, - 'hyphen': 173, - 'i': 105, - 'iacute': 237, - 'ibelowdot': 16785099, - 'ibreve': 16777517, - 'icircumflex': 238, - 'identical': 2255, - 'idiaeresis': 239, - 'idotless': 697, - 'ifonlyif': 2253, - 'igrave': 236, - 'ihook': 16785097, - 'imacron': 1007, - 'implies': 2254, - 'includedin': 2266, - 'includes': 2267, - 'infinity': 2242, - 'integral': 2239, - 'intersection': 2268, - 'iogonek': 999, - 'itilde': 949, - 'j': 106, - 'jcircumflex': 700, - 'jot': 3018, - 'k': 107, - 'kana_A': 1201, - 'kana_CHI': 1217, - 'kana_E': 1204, - 'kana_FU': 1228, - 'kana_HA': 1226, - 'kana_HE': 1229, - 'kana_HI': 1227, - 'kana_HO': 1230, - 'kana_HU': 1228, - 'kana_I': 1202, - 'kana_KA': 1206, - 'kana_KE': 1209, - 'kana_KI': 1207, - 'kana_KO': 1210, - 'kana_KU': 1208, - 'kana_MA': 1231, - 'kana_ME': 1234, - 'kana_MI': 1232, - 'kana_MO': 1235, - 'kana_MU': 1233, - 'kana_N': 1245, - 'kana_NA': 1221, - 'kana_NE': 1224, - 'kana_NI': 1222, - 'kana_NO': 1225, - 'kana_NU': 1223, - 'kana_O': 1205, - 'kana_RA': 1239, - 'kana_RE': 1242, - 'kana_RI': 1240, - 'kana_RO': 1243, - 'kana_RU': 1241, - 'kana_SA': 1211, - 'kana_SE': 1214, - 'kana_SHI': 1212, - 'kana_SO': 1215, - 'kana_SU': 1213, - 'kana_TA': 1216, - 'kana_TE': 1219, - 'kana_TI': 1217, - 'kana_TO': 1220, - 'kana_TSU': 1218, - 'kana_TU': 1218, - 'kana_U': 1203, - 'kana_WA': 1244, - 'kana_WO': 1190, - 'kana_YA': 1236, - 'kana_YO': 1238, - 'kana_YU': 1237, - 'kana_a': 1191, - 'kana_closingbracket': 1187, - 'kana_comma': 1188, - 'kana_conjunctive': 1189, - 'kana_e': 1194, - 'kana_fullstop': 1185, - 'kana_i': 1192, - 'kana_middledot': 1189, - 'kana_o': 1195, - 'kana_openingbracket': 1186, - 'kana_switch': 65406, - 'kana_tsu': 1199, - 'kana_tu': 1199, - 'kana_u': 1193, - 'kana_ya': 1196, - 'kana_yo': 1198, - 'kana_yu': 1197, - 'kappa': 930, - 'kcedilla': 1011, - 'kra': 930, - 'l': 108, - 'lacute': 485, - 'latincross': 2777, - 'lbelowdot': 16784951, - 'lcaron': 437, - 'lcedilla': 950, - 'leftanglebracket': 2748, - 'leftarrow': 2299, - 'leftcaret': 2979, - 'leftdoublequotemark': 2770, - 'leftmiddlecurlybrace': 2223, - 'leftopentriangle': 2764, - 'leftpointer': 2794, - 'leftradical': 2209, - 'leftshoe': 3034, - 'leftsinglequotemark': 2768, - 'leftt': 2548, - 'lefttack': 3036, - 'less': 60, - 'lessthanequal': 2236, - 'lf': 2533, - 'logicaland': 2270, - 'logicalor': 2271, - 'lowleftcorner': 2541, - 'lowrightcorner': 2538, - 'lstroke': 435, - 'm': 109, - 'mabovedot': 16784961, - 'macron': 175, - 'malesymbol': 2807, - 'maltesecross': 2800, - 'marker': 2751, - 'masculine': 186, - 'minus': 45, - 'minutes': 2774, - 'mu': 181, - 'multiply': 215, - 'musicalflat': 2806, - 'musicalsharp': 2805, - 'n': 110, - 'nabla': 2245, - 'nacute': 497, - 'ncaron': 498, - 'ncedilla': 1009, - 'ninesubscript': 16785545, - 'ninesuperior': 16785529, - 'nl': 2536, - 'nobreakspace': 160, - 'notapproxeq': 16785991, - 'notelementof': 16785929, - 'notequal': 2237, - 'notidentical': 16786018, - 'notsign': 172, - 'ntilde': 241, - 'numbersign': 35, - 'numerosign': 1712, - 'o': 111, - 'oacute': 243, - 'obarred': 16777845, - 'obelowdot': 16785101, - 'ocaron': 16777682, - 'ocircumflex': 244, - 'ocircumflexacute': 16785105, - 'ocircumflexbelowdot': 16785113, - 'ocircumflexgrave': 16785107, - 'ocircumflexhook': 16785109, - 'ocircumflextilde': 16785111, - 'odiaeresis': 246, - 'odoubleacute': 501, - 'oe': 5053, - 'ogonek': 434, - 'ograve': 242, - 'ohook': 16785103, - 'ohorn': 16777633, - 'ohornacute': 16785115, - 'ohornbelowdot': 16785123, - 'ohorngrave': 16785117, - 'ohornhook': 16785119, - 'ohorntilde': 16785121, - 'omacron': 1010, - 'oneeighth': 2755, - 'onefifth': 2738, - 'onehalf': 189, - 'onequarter': 188, - 'onesixth': 2742, - 'onesubscript': 16785537, - 'onesuperior': 185, - 'onethird': 2736, - 'ooblique': 248, - 'openrectbullet': 2786, - 'openstar': 2789, - 'opentribulletdown': 2788, - 'opentribulletup': 2787, - 'ordfeminine': 170, - 'ordmasculine': 186, - 'oslash': 248, - 'otilde': 245, - 'overbar': 3008, - 'overline': 1150, - 'p': 112, - 'pabovedot': 16784983, - 'paragraph': 182, - 'parenleft': 40, - 'parenright': 41, - 'partdifferential': 16785922, - 'partialderivative': 2287, - 'percent': 37, - 'period': 46, - 'periodcentered': 183, - 'permille': 2773, - 'phonographcopyright': 2811, - 'plus': 43, - 'plusminus': 177, - 'prescription': 2772, - 'prolongedsound': 1200, - 'punctspace': 2726, - 'q': 113, - 'quad': 3020, - 'question': 63, - 'questiondown': 191, - 'quotedbl': 34, - 'quoteleft': 96, - 'quoteright': 39, - 'r': 114, - 'racute': 480, - 'radical': 2262, - 'rcaron': 504, - 'rcedilla': 947, - 'registered': 174, - 'rightanglebracket': 2750, - 'rightarrow': 2301, - 'rightcaret': 2982, - 'rightdoublequotemark': 2771, - 'rightmiddlecurlybrace': 2224, - 'rightmiddlesummation': 2231, - 'rightopentriangle': 2765, - 'rightpointer': 2795, - 'rightshoe': 3032, - 'rightsinglequotemark': 2769, - 'rightt': 2549, - 'righttack': 3068, - 's': 115, - 'sabovedot': 16784993, - 'sacute': 438, - 'scaron': 441, - 'scedilla': 442, - 'schwa': 16777817, - 'scircumflex': 766, - 'script_switch': 65406, - 'seconds': 2775, - 'section': 167, - 'semicolon': 59, - 'semivoicedsound': 1247, - 'seveneighths': 2758, - 'sevensubscript': 16785543, - 'sevensuperior': 16785527, - 'signaturemark': 2762, - 'signifblank': 2732, - 'similarequal': 2249, - 'singlelowquotemark': 2813, - 'sixsubscript': 16785542, - 'sixsuperior': 16785526, - 'slash': 47, - 'soliddiamond': 2528, - 'space': 32, - 'squareroot': 16785946, - 'ssharp': 223, - 'sterling': 163, - 'stricteq': 16786019, - 't': 116, - 'tabovedot': 16785003, - 'tcaron': 443, - 'tcedilla': 510, - 'telephone': 2809, - 'telephonerecorder': 2810, - 'therefore': 2240, - 'thinspace': 2727, - 'thorn': 254, - 'threeeighths': 2756, - 'threefifths': 2740, - 'threequarters': 190, - 'threesubscript': 16785539, - 'threesuperior': 179, - 'tintegral': 16785965, - 'topintegral': 2212, - 'topleftparens': 2219, - 'topleftradical': 2210, - 'topleftsqbracket': 2215, - 'topleftsummation': 2225, - 'toprightparens': 2221, - 'toprightsqbracket': 2217, - 'toprightsummation': 2229, - 'topt': 2551, - 'topvertsummationconnector': 2227, - 'trademark': 2761, - 'trademarkincircle': 2763, - 'tslash': 956, - 'twofifths': 2739, - 'twosubscript': 16785538, - 'twosuperior': 178, - 'twothirds': 2737, - 'u': 117, - 'uacute': 250, - 'ubelowdot': 16785125, - 'ubreve': 765, - 'ucircumflex': 251, - 'udiaeresis': 252, - 'udoubleacute': 507, - 'ugrave': 249, - 'uhook': 16785127, - 'uhorn': 16777648, - 'uhornacute': 16785129, - 'uhornbelowdot': 16785137, - 'uhorngrave': 16785131, - 'uhornhook': 16785133, - 'uhorntilde': 16785135, - 'umacron': 1022, - 'underbar': 3014, - 'underscore': 95, - 'union': 2269, - 'uogonek': 1017, - 'uparrow': 2300, - 'upcaret': 2985, - 'upleftcorner': 2540, - 'uprightcorner': 2539, - 'upshoe': 3011, - 'upstile': 3027, - 'uptack': 3022, - 'uring': 505, - 'utilde': 1021, - 'v': 118, - 'variation': 2241, - 'vertbar': 2552, - 'vertconnector': 2214, - 'voicedsound': 1246, - 'vt': 2537, - 'w': 119, - 'wacute': 16785027, - 'wcircumflex': 16777589, - 'wdiaeresis': 16785029, - 'wgrave': 16785025, - 'x': 120, - 'xabovedot': 16785035, - 'y': 121, - 'yacute': 253, - 'ybelowdot': 16785141, - 'ycircumflex': 16777591, - 'ydiaeresis': 255, - 'yen': 165, - 'ygrave': 16785139, - 'yhook': 16785143, - 'ytilde': 16785145, - 'z': 122, - 'zabovedot': 447, - 'zacute': 444, - 'zcaron': 446, - 'zerosubscript': 16785536, - 'zerosuperior': 16785520, - 'zstroke': 16777654} +keysymdef = { + "0": 48, + "1": 49, + "2": 50, + "3": 51, + "3270_AltCursor": 64784, + "3270_Attn": 64782, + "3270_BackTab": 64773, + "3270_ChangeScreen": 64793, + "3270_Copy": 64789, + "3270_CursorBlink": 64783, + "3270_CursorSelect": 64796, + "3270_DeleteWord": 64794, + "3270_Duplicate": 64769, + "3270_Enter": 64798, + "3270_EraseEOF": 64774, + "3270_EraseInput": 64775, + "3270_ExSelect": 64795, + "3270_FieldMark": 64770, + "3270_Ident": 64787, + "3270_Jump": 64786, + "3270_KeyClick": 64785, + "3270_Left2": 64772, + "3270_PA1": 64778, + "3270_PA2": 64779, + "3270_PA3": 64780, + "3270_Play": 64790, + "3270_PrintScreen": 64797, + "3270_Quit": 64777, + "3270_Record": 64792, + "3270_Reset": 64776, + "3270_Right2": 64771, + "3270_Rule": 64788, + "3270_Setup": 64791, + "3270_Test": 64781, + "4": 52, + "5": 53, + "6": 54, + "7": 55, + "8": 56, + "9": 57, + "A": 65, + "AE": 198, + "Aacute": 193, + "Abelowdot": 16785056, + "Abreve": 451, + "Abreveacute": 16785070, + "Abrevebelowdot": 16785078, + "Abrevegrave": 16785072, + "Abrevehook": 16785074, + "Abrevetilde": 16785076, + "AccessX_Enable": 65136, + "AccessX_Feedback_Enable": 65137, + "Acircumflex": 194, + "Acircumflexacute": 16785060, + "Acircumflexbelowdot": 16785068, + "Acircumflexgrave": 16785062, + "Acircumflexhook": 16785064, + "Acircumflextilde": 16785066, + "Adiaeresis": 196, + "Agrave": 192, + "Ahook": 16785058, + "Alt_L": 65513, + "Alt_R": 65514, + "Amacron": 960, + "Aogonek": 417, + "Arabic_0": 16778848, + "Arabic_1": 16778849, + "Arabic_2": 16778850, + "Arabic_3": 16778851, + "Arabic_4": 16778852, + "Arabic_5": 16778853, + "Arabic_6": 16778854, + "Arabic_7": 16778855, + "Arabic_8": 16778856, + "Arabic_9": 16778857, + "Arabic_ain": 1497, + "Arabic_alef": 1479, + "Arabic_alefmaksura": 1513, + "Arabic_beh": 1480, + "Arabic_comma": 1452, + "Arabic_dad": 1494, + "Arabic_dal": 1487, + "Arabic_damma": 1519, + "Arabic_dammatan": 1516, + "Arabic_ddal": 16778888, + "Arabic_farsi_yeh": 16778956, + "Arabic_fatha": 1518, + "Arabic_fathatan": 1515, + "Arabic_feh": 1505, + "Arabic_fullstop": 16778964, + "Arabic_gaf": 16778927, + "Arabic_ghain": 1498, + "Arabic_ha": 1511, + "Arabic_hah": 1485, + "Arabic_hamza": 1473, + "Arabic_hamza_above": 16778836, + "Arabic_hamza_below": 16778837, + "Arabic_hamzaonalef": 1475, + "Arabic_hamzaonwaw": 1476, + "Arabic_hamzaonyeh": 1478, + "Arabic_hamzaunderalef": 1477, + "Arabic_heh": 1511, + "Arabic_heh_doachashmee": 16778942, + "Arabic_heh_goal": 16778945, + "Arabic_jeem": 1484, + "Arabic_jeh": 16778904, + "Arabic_kaf": 1507, + "Arabic_kasra": 1520, + "Arabic_kasratan": 1517, + "Arabic_keheh": 16778921, + "Arabic_khah": 1486, + "Arabic_lam": 1508, + "Arabic_madda_above": 16778835, + "Arabic_maddaonalef": 1474, + "Arabic_meem": 1509, + "Arabic_noon": 1510, + "Arabic_noon_ghunna": 16778938, + "Arabic_peh": 16778878, + "Arabic_percent": 16778858, + "Arabic_qaf": 1506, + "Arabic_question_mark": 1471, + "Arabic_ra": 1489, + "Arabic_rreh": 16778897, + "Arabic_sad": 1493, + "Arabic_seen": 1491, + "Arabic_semicolon": 1467, + "Arabic_shadda": 1521, + "Arabic_sheen": 1492, + "Arabic_sukun": 1522, + "Arabic_superscript_alef": 16778864, + "Arabic_switch": 65406, + "Arabic_tah": 1495, + "Arabic_tatweel": 1504, + "Arabic_tcheh": 16778886, + "Arabic_teh": 1482, + "Arabic_tehmarbuta": 1481, + "Arabic_thal": 1488, + "Arabic_theh": 1483, + "Arabic_tteh": 16778873, + "Arabic_veh": 16778916, + "Arabic_waw": 1512, + "Arabic_yeh": 1514, + "Arabic_yeh_baree": 16778962, + "Arabic_zah": 1496, + "Arabic_zain": 1490, + "Aring": 197, + "Armenian_AT": 16778552, + "Armenian_AYB": 16778545, + "Armenian_BEN": 16778546, + "Armenian_CHA": 16778569, + "Armenian_DA": 16778548, + "Armenian_DZA": 16778561, + "Armenian_E": 16778551, + "Armenian_FE": 16778582, + "Armenian_GHAT": 16778562, + "Armenian_GIM": 16778547, + "Armenian_HI": 16778565, + "Armenian_HO": 16778560, + "Armenian_INI": 16778555, + "Armenian_JE": 16778571, + "Armenian_KE": 16778580, + "Armenian_KEN": 16778559, + "Armenian_KHE": 16778557, + "Armenian_LYUN": 16778556, + "Armenian_MEN": 16778564, + "Armenian_NU": 16778566, + "Armenian_O": 16778581, + "Armenian_PE": 16778570, + "Armenian_PYUR": 16778579, + "Armenian_RA": 16778572, + "Armenian_RE": 16778576, + "Armenian_SE": 16778573, + "Armenian_SHA": 16778567, + "Armenian_TCHE": 16778563, + "Armenian_TO": 16778553, + "Armenian_TSA": 16778558, + "Armenian_TSO": 16778577, + "Armenian_TYUN": 16778575, + "Armenian_VEV": 16778574, + "Armenian_VO": 16778568, + "Armenian_VYUN": 16778578, + "Armenian_YECH": 16778549, + "Armenian_ZA": 16778550, + "Armenian_ZHE": 16778554, + "Armenian_accent": 16778587, + "Armenian_amanak": 16778588, + "Armenian_apostrophe": 16778586, + "Armenian_at": 16778600, + "Armenian_ayb": 16778593, + "Armenian_ben": 16778594, + "Armenian_but": 16778589, + "Armenian_cha": 16778617, + "Armenian_da": 16778596, + "Armenian_dza": 16778609, + "Armenian_e": 16778599, + "Armenian_exclam": 16778588, + "Armenian_fe": 16778630, + "Armenian_full_stop": 16778633, + "Armenian_ghat": 16778610, + "Armenian_gim": 16778595, + "Armenian_hi": 16778613, + "Armenian_ho": 16778608, + "Armenian_hyphen": 16778634, + "Armenian_ini": 16778603, + "Armenian_je": 16778619, + "Armenian_ke": 16778628, + "Armenian_ken": 16778607, + "Armenian_khe": 16778605, + "Armenian_ligature_ew": 16778631, + "Armenian_lyun": 16778604, + "Armenian_men": 16778612, + "Armenian_nu": 16778614, + "Armenian_o": 16778629, + "Armenian_paruyk": 16778590, + "Armenian_pe": 16778618, + "Armenian_pyur": 16778627, + "Armenian_question": 16778590, + "Armenian_ra": 16778620, + "Armenian_re": 16778624, + "Armenian_se": 16778621, + "Armenian_separation_mark": 16778589, + "Armenian_sha": 16778615, + "Armenian_shesht": 16778587, + "Armenian_tche": 16778611, + "Armenian_to": 16778601, + "Armenian_tsa": 16778606, + "Armenian_tso": 16778625, + "Armenian_tyun": 16778623, + "Armenian_verjaket": 16778633, + "Armenian_vev": 16778622, + "Armenian_vo": 16778616, + "Armenian_vyun": 16778626, + "Armenian_yech": 16778597, + "Armenian_yentamna": 16778634, + "Armenian_za": 16778598, + "Armenian_zhe": 16778602, + "Atilde": 195, + "AudibleBell_Enable": 65146, + "B": 66, + "Babovedot": 16784898, + "BackSpace": 65288, + "Begin": 65368, + "BounceKeys_Enable": 65140, + "Break": 65387, + "Byelorussian_SHORTU": 1726, + "Byelorussian_shortu": 1710, + "C": 67, + "CH": 65186, + "C_H": 65189, + "C_h": 65188, + "Cabovedot": 709, + "Cacute": 454, + "Cancel": 65385, + "Caps_Lock": 65509, + "Ccaron": 456, + "Ccedilla": 199, + "Ccircumflex": 710, + "Ch": 65185, + "Clear": 65291, + "Codeinput": 65335, + "ColonSign": 16785569, + "Control_L": 65507, + "Control_R": 65508, + "CruzeiroSign": 16785570, + "Cyrillic_A": 1761, + "Cyrillic_BE": 1762, + "Cyrillic_CHE": 1790, + "Cyrillic_CHE_descender": 16778422, + "Cyrillic_CHE_vertstroke": 16778424, + "Cyrillic_DE": 1764, + "Cyrillic_DZHE": 1727, + "Cyrillic_E": 1788, + "Cyrillic_EF": 1766, + "Cyrillic_EL": 1772, + "Cyrillic_EM": 1773, + "Cyrillic_EN": 1774, + "Cyrillic_EN_descender": 16778402, + "Cyrillic_ER": 1778, + "Cyrillic_ES": 1779, + "Cyrillic_GHE": 1767, + "Cyrillic_GHE_bar": 16778386, + "Cyrillic_HA": 1768, + "Cyrillic_HARDSIGN": 1791, + "Cyrillic_HA_descender": 16778418, + "Cyrillic_I": 1769, + "Cyrillic_IE": 1765, + "Cyrillic_IO": 1715, + "Cyrillic_I_macron": 16778466, + "Cyrillic_JE": 1720, + "Cyrillic_KA": 1771, + "Cyrillic_KA_descender": 16778394, + "Cyrillic_KA_vertstroke": 16778396, + "Cyrillic_LJE": 1721, + "Cyrillic_NJE": 1722, + "Cyrillic_O": 1775, + "Cyrillic_O_bar": 16778472, + "Cyrillic_PE": 1776, + "Cyrillic_SCHWA": 16778456, + "Cyrillic_SHA": 1787, + "Cyrillic_SHCHA": 1789, + "Cyrillic_SHHA": 16778426, + "Cyrillic_SHORTI": 1770, + "Cyrillic_SOFTSIGN": 1784, + "Cyrillic_TE": 1780, + "Cyrillic_TSE": 1763, + "Cyrillic_U": 1781, + "Cyrillic_U_macron": 16778478, + "Cyrillic_U_straight": 16778414, + "Cyrillic_U_straight_bar": 16778416, + "Cyrillic_VE": 1783, + "Cyrillic_YA": 1777, + "Cyrillic_YERU": 1785, + "Cyrillic_YU": 1760, + "Cyrillic_ZE": 1786, + "Cyrillic_ZHE": 1782, + "Cyrillic_ZHE_descender": 16778390, + "Cyrillic_a": 1729, + "Cyrillic_be": 1730, + "Cyrillic_che": 1758, + "Cyrillic_che_descender": 16778423, + "Cyrillic_che_vertstroke": 16778425, + "Cyrillic_de": 1732, + "Cyrillic_dzhe": 1711, + "Cyrillic_e": 1756, + "Cyrillic_ef": 1734, + "Cyrillic_el": 1740, + "Cyrillic_em": 1741, + "Cyrillic_en": 1742, + "Cyrillic_en_descender": 16778403, + "Cyrillic_er": 1746, + "Cyrillic_es": 1747, + "Cyrillic_ghe": 1735, + "Cyrillic_ghe_bar": 16778387, + "Cyrillic_ha": 1736, + "Cyrillic_ha_descender": 16778419, + "Cyrillic_hardsign": 1759, + "Cyrillic_i": 1737, + "Cyrillic_i_macron": 16778467, + "Cyrillic_ie": 1733, + "Cyrillic_io": 1699, + "Cyrillic_je": 1704, + "Cyrillic_ka": 1739, + "Cyrillic_ka_descender": 16778395, + "Cyrillic_ka_vertstroke": 16778397, + "Cyrillic_lje": 1705, + "Cyrillic_nje": 1706, + "Cyrillic_o": 1743, + "Cyrillic_o_bar": 16778473, + "Cyrillic_pe": 1744, + "Cyrillic_schwa": 16778457, + "Cyrillic_sha": 1755, + "Cyrillic_shcha": 1757, + "Cyrillic_shha": 16778427, + "Cyrillic_shorti": 1738, + "Cyrillic_softsign": 1752, + "Cyrillic_te": 1748, + "Cyrillic_tse": 1731, + "Cyrillic_u": 1749, + "Cyrillic_u_macron": 16778479, + "Cyrillic_u_straight": 16778415, + "Cyrillic_u_straight_bar": 16778417, + "Cyrillic_ve": 1751, + "Cyrillic_ya": 1745, + "Cyrillic_yeru": 1753, + "Cyrillic_yu": 1728, + "Cyrillic_ze": 1754, + "Cyrillic_zhe": 1750, + "Cyrillic_zhe_descender": 16778391, + "D": 68, + "Dabovedot": 16784906, + "Dcaron": 463, + "Delete": 65535, + "DongSign": 16785579, + "Down": 65364, + "Dstroke": 464, + "E": 69, + "ENG": 957, + "ETH": 208, + "EZH": 16777655, + "Eabovedot": 972, + "Eacute": 201, + "Ebelowdot": 16785080, + "Ecaron": 460, + "Ecircumflex": 202, + "Ecircumflexacute": 16785086, + "Ecircumflexbelowdot": 16785094, + "Ecircumflexgrave": 16785088, + "Ecircumflexhook": 16785090, + "Ecircumflextilde": 16785092, + "EcuSign": 16785568, + "Ediaeresis": 203, + "Egrave": 200, + "Ehook": 16785082, + "Eisu_Shift": 65327, + "Eisu_toggle": 65328, + "Emacron": 938, + "End": 65367, + "Eogonek": 458, + "Escape": 65307, + "Eth": 208, + "Etilde": 16785084, + "EuroSign": 8364, + "Execute": 65378, + "F": 70, + "F1": 65470, + "F10": 65479, + "F11": 65480, + "F12": 65481, + "F13": 65482, + "F14": 65483, + "F15": 65484, + "F16": 65485, + "F17": 65486, + "F18": 65487, + "F19": 65488, + "F2": 65471, + "F20": 65489, + "F21": 65490, + "F22": 65491, + "F23": 65492, + "F24": 65493, + "F25": 65494, + "F26": 65495, + "F27": 65496, + "F28": 65497, + "F29": 65498, + "F3": 65472, + "F30": 65499, + "F31": 65500, + "F32": 65501, + "F33": 65502, + "F34": 65503, + "F35": 65504, + "F4": 65473, + "F5": 65474, + "F6": 65475, + "F7": 65476, + "F8": 65477, + "F9": 65478, + "FFrancSign": 16785571, + "Fabovedot": 16784926, + "Farsi_0": 16778992, + "Farsi_1": 16778993, + "Farsi_2": 16778994, + "Farsi_3": 16778995, + "Farsi_4": 16778996, + "Farsi_5": 16778997, + "Farsi_6": 16778998, + "Farsi_7": 16778999, + "Farsi_8": 16779000, + "Farsi_9": 16779001, + "Farsi_yeh": 16778956, + "Find": 65384, + "First_Virtual_Screen": 65232, + "G": 71, + "Gabovedot": 725, + "Gbreve": 683, + "Gcaron": 16777702, + "Gcedilla": 939, + "Gcircumflex": 728, + "Georgian_an": 16781520, + "Georgian_ban": 16781521, + "Georgian_can": 16781546, + "Georgian_char": 16781549, + "Georgian_chin": 16781545, + "Georgian_cil": 16781548, + "Georgian_don": 16781523, + "Georgian_en": 16781524, + "Georgian_fi": 16781558, + "Georgian_gan": 16781522, + "Georgian_ghan": 16781542, + "Georgian_hae": 16781552, + "Georgian_har": 16781556, + "Georgian_he": 16781553, + "Georgian_hie": 16781554, + "Georgian_hoe": 16781557, + "Georgian_in": 16781528, + "Georgian_jhan": 16781551, + "Georgian_jil": 16781547, + "Georgian_kan": 16781529, + "Georgian_khar": 16781541, + "Georgian_las": 16781530, + "Georgian_man": 16781531, + "Georgian_nar": 16781532, + "Georgian_on": 16781533, + "Georgian_par": 16781534, + "Georgian_phar": 16781540, + "Georgian_qar": 16781543, + "Georgian_rae": 16781536, + "Georgian_san": 16781537, + "Georgian_shin": 16781544, + "Georgian_tan": 16781527, + "Georgian_tar": 16781538, + "Georgian_un": 16781539, + "Georgian_vin": 16781525, + "Georgian_we": 16781555, + "Georgian_xan": 16781550, + "Georgian_zen": 16781526, + "Georgian_zhar": 16781535, + "Greek_ALPHA": 1985, + "Greek_ALPHAaccent": 1953, + "Greek_BETA": 1986, + "Greek_CHI": 2007, + "Greek_DELTA": 1988, + "Greek_EPSILON": 1989, + "Greek_EPSILONaccent": 1954, + "Greek_ETA": 1991, + "Greek_ETAaccent": 1955, + "Greek_GAMMA": 1987, + "Greek_IOTA": 1993, + "Greek_IOTAaccent": 1956, + "Greek_IOTAdiaeresis": 1957, + "Greek_IOTAdieresis": 1957, + "Greek_KAPPA": 1994, + "Greek_LAMBDA": 1995, + "Greek_LAMDA": 1995, + "Greek_MU": 1996, + "Greek_NU": 1997, + "Greek_OMEGA": 2009, + "Greek_OMEGAaccent": 1963, + "Greek_OMICRON": 1999, + "Greek_OMICRONaccent": 1959, + "Greek_PHI": 2006, + "Greek_PI": 2000, + "Greek_PSI": 2008, + "Greek_RHO": 2001, + "Greek_SIGMA": 2002, + "Greek_TAU": 2004, + "Greek_THETA": 1992, + "Greek_UPSILON": 2005, + "Greek_UPSILONaccent": 1960, + "Greek_UPSILONdieresis": 1961, + "Greek_XI": 1998, + "Greek_ZETA": 1990, + "Greek_accentdieresis": 1966, + "Greek_alpha": 2017, + "Greek_alphaaccent": 1969, + "Greek_beta": 2018, + "Greek_chi": 2039, + "Greek_delta": 2020, + "Greek_epsilon": 2021, + "Greek_epsilonaccent": 1970, + "Greek_eta": 2023, + "Greek_etaaccent": 1971, + "Greek_finalsmallsigma": 2035, + "Greek_gamma": 2019, + "Greek_horizbar": 1967, + "Greek_iota": 2025, + "Greek_iotaaccent": 1972, + "Greek_iotaaccentdieresis": 1974, + "Greek_iotadieresis": 1973, + "Greek_kappa": 2026, + "Greek_lambda": 2027, + "Greek_lamda": 2027, + "Greek_mu": 2028, + "Greek_nu": 2029, + "Greek_omega": 2041, + "Greek_omegaaccent": 1979, + "Greek_omicron": 2031, + "Greek_omicronaccent": 1975, + "Greek_phi": 2038, + "Greek_pi": 2032, + "Greek_psi": 2040, + "Greek_rho": 2033, + "Greek_sigma": 2034, + "Greek_switch": 65406, + "Greek_tau": 2036, + "Greek_theta": 2024, + "Greek_upsilon": 2037, + "Greek_upsilonaccent": 1976, + "Greek_upsilonaccentdieresis": 1978, + "Greek_upsilondieresis": 1977, + "Greek_xi": 2030, + "Greek_zeta": 2022, + "H": 72, + "Hangul": 65329, + "Hangul_A": 3775, + "Hangul_AE": 3776, + "Hangul_AraeA": 3830, + "Hangul_AraeAE": 3831, + "Hangul_Banja": 65337, + "Hangul_Cieuc": 3770, + "Hangul_Codeinput": 65335, + "Hangul_Dikeud": 3751, + "Hangul_E": 3780, + "Hangul_EO": 3779, + "Hangul_EU": 3793, + "Hangul_End": 65331, + "Hangul_Hanja": 65332, + "Hangul_Hieuh": 3774, + "Hangul_I": 3795, + "Hangul_Ieung": 3767, + "Hangul_J_Cieuc": 3818, + "Hangul_J_Dikeud": 3802, + "Hangul_J_Hieuh": 3822, + "Hangul_J_Ieung": 3816, + "Hangul_J_Jieuj": 3817, + "Hangul_J_Khieuq": 3819, + "Hangul_J_Kiyeog": 3796, + "Hangul_J_KiyeogSios": 3798, + "Hangul_J_KkogjiDalrinIeung": 3833, + "Hangul_J_Mieum": 3811, + "Hangul_J_Nieun": 3799, + "Hangul_J_NieunHieuh": 3801, + "Hangul_J_NieunJieuj": 3800, + "Hangul_J_PanSios": 3832, + "Hangul_J_Phieuf": 3821, + "Hangul_J_Pieub": 3812, + "Hangul_J_PieubSios": 3813, + "Hangul_J_Rieul": 3803, + "Hangul_J_RieulHieuh": 3810, + "Hangul_J_RieulKiyeog": 3804, + "Hangul_J_RieulMieum": 3805, + "Hangul_J_RieulPhieuf": 3809, + "Hangul_J_RieulPieub": 3806, + "Hangul_J_RieulSios": 3807, + "Hangul_J_RieulTieut": 3808, + "Hangul_J_Sios": 3814, + "Hangul_J_SsangKiyeog": 3797, + "Hangul_J_SsangSios": 3815, + "Hangul_J_Tieut": 3820, + "Hangul_J_YeorinHieuh": 3834, + "Hangul_Jamo": 65333, + "Hangul_Jeonja": 65336, + "Hangul_Jieuj": 3768, + "Hangul_Khieuq": 3771, + "Hangul_Kiyeog": 3745, + "Hangul_KiyeogSios": 3747, + "Hangul_KkogjiDalrinIeung": 3827, + "Hangul_Mieum": 3761, + "Hangul_MultipleCandidate": 65341, + "Hangul_Nieun": 3748, + "Hangul_NieunHieuh": 3750, + "Hangul_NieunJieuj": 3749, + "Hangul_O": 3783, + "Hangul_OE": 3786, + "Hangul_PanSios": 3826, + "Hangul_Phieuf": 3773, + "Hangul_Pieub": 3762, + "Hangul_PieubSios": 3764, + "Hangul_PostHanja": 65339, + "Hangul_PreHanja": 65338, + "Hangul_PreviousCandidate": 65342, + "Hangul_Rieul": 3753, + "Hangul_RieulHieuh": 3760, + "Hangul_RieulKiyeog": 3754, + "Hangul_RieulMieum": 3755, + "Hangul_RieulPhieuf": 3759, + "Hangul_RieulPieub": 3756, + "Hangul_RieulSios": 3757, + "Hangul_RieulTieut": 3758, + "Hangul_RieulYeorinHieuh": 3823, + "Hangul_Romaja": 65334, + "Hangul_SingleCandidate": 65340, + "Hangul_Sios": 3765, + "Hangul_Special": 65343, + "Hangul_SsangDikeud": 3752, + "Hangul_SsangJieuj": 3769, + "Hangul_SsangKiyeog": 3746, + "Hangul_SsangPieub": 3763, + "Hangul_SsangSios": 3766, + "Hangul_Start": 65330, + "Hangul_SunkyeongeumMieum": 3824, + "Hangul_SunkyeongeumPhieuf": 3828, + "Hangul_SunkyeongeumPieub": 3825, + "Hangul_Tieut": 3772, + "Hangul_U": 3788, + "Hangul_WA": 3784, + "Hangul_WAE": 3785, + "Hangul_WE": 3790, + "Hangul_WEO": 3789, + "Hangul_WI": 3791, + "Hangul_YA": 3777, + "Hangul_YAE": 3778, + "Hangul_YE": 3782, + "Hangul_YEO": 3781, + "Hangul_YI": 3794, + "Hangul_YO": 3787, + "Hangul_YU": 3792, + "Hangul_YeorinHieuh": 3829, + "Hangul_switch": 65406, + "Hankaku": 65321, + "Hcircumflex": 678, + "Hebrew_switch": 65406, + "Help": 65386, + "Henkan": 65315, + "Henkan_Mode": 65315, + "Hiragana": 65317, + "Hiragana_Katakana": 65319, + "Home": 65360, + "Hstroke": 673, + "Hyper_L": 65517, + "Hyper_R": 65518, + "I": 73, + "ISO_Center_Object": 65075, + "ISO_Continuous_Underline": 65072, + "ISO_Discontinuous_Underline": 65073, + "ISO_Emphasize": 65074, + "ISO_Enter": 65076, + "ISO_Fast_Cursor_Down": 65071, + "ISO_Fast_Cursor_Left": 65068, + "ISO_Fast_Cursor_Right": 65069, + "ISO_Fast_Cursor_Up": 65070, + "ISO_First_Group": 65036, + "ISO_First_Group_Lock": 65037, + "ISO_Group_Latch": 65030, + "ISO_Group_Lock": 65031, + "ISO_Group_Shift": 65406, + "ISO_Last_Group": 65038, + "ISO_Last_Group_Lock": 65039, + "ISO_Left_Tab": 65056, + "ISO_Level2_Latch": 65026, + "ISO_Level3_Latch": 65028, + "ISO_Level3_Lock": 65029, + "ISO_Level3_Shift": 65027, + "ISO_Level5_Latch": 65042, + "ISO_Level5_Lock": 65043, + "ISO_Level5_Shift": 65041, + "ISO_Lock": 65025, + "ISO_Move_Line_Down": 65058, + "ISO_Move_Line_Up": 65057, + "ISO_Next_Group": 65032, + "ISO_Next_Group_Lock": 65033, + "ISO_Partial_Line_Down": 65060, + "ISO_Partial_Line_Up": 65059, + "ISO_Partial_Space_Left": 65061, + "ISO_Partial_Space_Right": 65062, + "ISO_Prev_Group": 65034, + "ISO_Prev_Group_Lock": 65035, + "ISO_Release_Both_Margins": 65067, + "ISO_Release_Margin_Left": 65065, + "ISO_Release_Margin_Right": 65066, + "ISO_Set_Margin_Left": 65063, + "ISO_Set_Margin_Right": 65064, + "Iabovedot": 681, + "Iacute": 205, + "Ibelowdot": 16785098, + "Ibreve": 16777516, + "Icircumflex": 206, + "Idiaeresis": 207, + "Igrave": 204, + "Ihook": 16785096, + "Imacron": 975, + "Insert": 65379, + "Iogonek": 967, + "Itilde": 933, + "J": 74, + "Jcircumflex": 684, + "K": 75, + "KP_0": 65456, + "KP_1": 65457, + "KP_2": 65458, + "KP_3": 65459, + "KP_4": 65460, + "KP_5": 65461, + "KP_6": 65462, + "KP_7": 65463, + "KP_8": 65464, + "KP_9": 65465, + "KP_Add": 65451, + "KP_Begin": 65437, + "KP_Decimal": 65454, + "KP_Delete": 65439, + "KP_Divide": 65455, + "KP_Down": 65433, + "KP_End": 65436, + "KP_Enter": 65421, + "KP_Equal": 65469, + "KP_F1": 65425, + "KP_F2": 65426, + "KP_F3": 65427, + "KP_F4": 65428, + "KP_Home": 65429, + "KP_Insert": 65438, + "KP_Left": 65430, + "KP_Multiply": 65450, + "KP_Next": 65435, + "KP_Page_Down": 65435, + "KP_Page_Up": 65434, + "KP_Prior": 65434, + "KP_Right": 65432, + "KP_Separator": 65452, + "KP_Space": 65408, + "KP_Subtract": 65453, + "KP_Tab": 65417, + "KP_Up": 65431, + "Kana_Lock": 65325, + "Kana_Shift": 65326, + "Kanji": 65313, + "Kanji_Bangou": 65335, + "Katakana": 65318, + "Kcedilla": 979, + "Korean_Won": 3839, + "L": 76, + "L1": 65480, + "L10": 65489, + "L2": 65481, + "L3": 65482, + "L4": 65483, + "L5": 65484, + "L6": 65485, + "L7": 65486, + "L8": 65487, + "L9": 65488, + "Lacute": 453, + "Last_Virtual_Screen": 65236, + "Lbelowdot": 16784950, + "Lcaron": 421, + "Lcedilla": 934, + "Left": 65361, + "Linefeed": 65290, + "LiraSign": 16785572, + "Lstroke": 419, + "M": 77, + "Mabovedot": 16784960, + "Macedonia_DSE": 1717, + "Macedonia_GJE": 1714, + "Macedonia_KJE": 1724, + "Macedonia_dse": 1701, + "Macedonia_gje": 1698, + "Macedonia_kje": 1708, + "Mae_Koho": 65342, + "Massyo": 65324, + "Menu": 65383, + "Meta_L": 65511, + "Meta_R": 65512, + "MillSign": 16785573, + "Mode_switch": 65406, + "MouseKeys_Accel_Enable": 65143, + "MouseKeys_Enable": 65142, + "Muhenkan": 65314, + "Multi_key": 65312, + "MultipleCandidate": 65341, + "N": 78, + "Nacute": 465, + "NairaSign": 16785574, + "Ncaron": 466, + "Ncedilla": 977, + "NewSheqelSign": 16785578, + "Next": 65366, + "Next_Virtual_Screen": 65234, + "Ntilde": 209, + "Num_Lock": 65407, + "O": 79, + "OE": 5052, + "Oacute": 211, + "Obarred": 16777631, + "Obelowdot": 16785100, + "Ocaron": 16777681, + "Ocircumflex": 212, + "Ocircumflexacute": 16785104, + "Ocircumflexbelowdot": 16785112, + "Ocircumflexgrave": 16785106, + "Ocircumflexhook": 16785108, + "Ocircumflextilde": 16785110, + "Odiaeresis": 214, + "Odoubleacute": 469, + "Ograve": 210, + "Ohook": 16785102, + "Ohorn": 16777632, + "Ohornacute": 16785114, + "Ohornbelowdot": 16785122, + "Ohorngrave": 16785116, + "Ohornhook": 16785118, + "Ohorntilde": 16785120, + "Omacron": 978, + "Ooblique": 216, + "Oslash": 216, + "Otilde": 213, + "Overlay1_Enable": 65144, + "Overlay2_Enable": 65145, + "P": 80, + "Pabovedot": 16784982, + "Page_Down": 65366, + "Page_Up": 65365, + "Pause": 65299, + "PesetaSign": 16785575, + "Pointer_Accelerate": 65274, + "Pointer_Button1": 65257, + "Pointer_Button2": 65258, + "Pointer_Button3": 65259, + "Pointer_Button4": 65260, + "Pointer_Button5": 65261, + "Pointer_Button_Dflt": 65256, + "Pointer_DblClick1": 65263, + "Pointer_DblClick2": 65264, + "Pointer_DblClick3": 65265, + "Pointer_DblClick4": 65266, + "Pointer_DblClick5": 65267, + "Pointer_DblClick_Dflt": 65262, + "Pointer_DfltBtnNext": 65275, + "Pointer_DfltBtnPrev": 65276, + "Pointer_Down": 65251, + "Pointer_DownLeft": 65254, + "Pointer_DownRight": 65255, + "Pointer_Drag1": 65269, + "Pointer_Drag2": 65270, + "Pointer_Drag3": 65271, + "Pointer_Drag4": 65272, + "Pointer_Drag5": 65277, + "Pointer_Drag_Dflt": 65268, + "Pointer_EnableKeys": 65273, + "Pointer_Left": 65248, + "Pointer_Right": 65249, + "Pointer_Up": 65250, + "Pointer_UpLeft": 65252, + "Pointer_UpRight": 65253, + "Prev_Virtual_Screen": 65233, + "PreviousCandidate": 65342, + "Print": 65377, + "Prior": 65365, + "Q": 81, + "R": 82, + "R1": 65490, + "R10": 65499, + "R11": 65500, + "R12": 65501, + "R13": 65502, + "R14": 65503, + "R15": 65504, + "R2": 65491, + "R3": 65492, + "R4": 65493, + "R5": 65494, + "R6": 65495, + "R7": 65496, + "R8": 65497, + "R9": 65498, + "Racute": 448, + "Rcaron": 472, + "Rcedilla": 931, + "Redo": 65382, + "RepeatKeys_Enable": 65138, + "Return": 65293, + "Right": 65363, + "Romaji": 65316, + "RupeeSign": 16785576, + "S": 83, + "SCHWA": 16777615, + "Sabovedot": 16784992, + "Sacute": 422, + "Scaron": 425, + "Scedilla": 426, + "Scircumflex": 734, + "Scroll_Lock": 65300, + "Select": 65376, + "Serbian_DJE": 1713, + "Serbian_DZE": 1727, + "Serbian_JE": 1720, + "Serbian_LJE": 1721, + "Serbian_NJE": 1722, + "Serbian_TSHE": 1723, + "Serbian_dje": 1697, + "Serbian_dze": 1711, + "Serbian_je": 1704, + "Serbian_lje": 1705, + "Serbian_nje": 1706, + "Serbian_tshe": 1707, + "Shift_L": 65505, + "Shift_Lock": 65510, + "Shift_R": 65506, + "SingleCandidate": 65340, + "Sinh_a": 16780677, + "Sinh_aa": 16780678, + "Sinh_aa2": 16780751, + "Sinh_ae": 16780679, + "Sinh_ae2": 16780752, + "Sinh_aee": 16780680, + "Sinh_aee2": 16780753, + "Sinh_ai": 16780691, + "Sinh_ai2": 16780763, + "Sinh_al": 16780746, + "Sinh_au": 16780694, + "Sinh_au2": 16780766, + "Sinh_ba": 16780726, + "Sinh_bha": 16780727, + "Sinh_ca": 16780704, + "Sinh_cha": 16780705, + "Sinh_dda": 16780713, + "Sinh_ddha": 16780714, + "Sinh_dha": 16780719, + "Sinh_dhha": 16780720, + "Sinh_e": 16780689, + "Sinh_e2": 16780761, + "Sinh_ee": 16780690, + "Sinh_ee2": 16780762, + "Sinh_fa": 16780742, + "Sinh_ga": 16780700, + "Sinh_gha": 16780701, + "Sinh_h2": 16780675, + "Sinh_ha": 16780740, + "Sinh_i": 16780681, + "Sinh_i2": 16780754, + "Sinh_ii": 16780682, + "Sinh_ii2": 16780755, + "Sinh_ja": 16780706, + "Sinh_jha": 16780707, + "Sinh_jnya": 16780709, + "Sinh_ka": 16780698, + "Sinh_kha": 16780699, + "Sinh_kunddaliya": 16780788, + "Sinh_la": 16780733, + "Sinh_lla": 16780741, + "Sinh_lu": 16780687, + "Sinh_lu2": 16780767, + "Sinh_luu": 16780688, + "Sinh_luu2": 16780787, + "Sinh_ma": 16780728, + "Sinh_mba": 16780729, + "Sinh_na": 16780721, + "Sinh_ndda": 16780716, + "Sinh_ndha": 16780723, + "Sinh_ng": 16780674, + "Sinh_ng2": 16780702, + "Sinh_nga": 16780703, + "Sinh_nja": 16780710, + "Sinh_nna": 16780715, + "Sinh_nya": 16780708, + "Sinh_o": 16780692, + "Sinh_o2": 16780764, + "Sinh_oo": 16780693, + "Sinh_oo2": 16780765, + "Sinh_pa": 16780724, + "Sinh_pha": 16780725, + "Sinh_ra": 16780731, + "Sinh_ri": 16780685, + "Sinh_rii": 16780686, + "Sinh_ru2": 16780760, + "Sinh_ruu2": 16780786, + "Sinh_sa": 16780739, + "Sinh_sha": 16780737, + "Sinh_ssha": 16780738, + "Sinh_tha": 16780717, + "Sinh_thha": 16780718, + "Sinh_tta": 16780711, + "Sinh_ttha": 16780712, + "Sinh_u": 16780683, + "Sinh_u2": 16780756, + "Sinh_uu": 16780684, + "Sinh_uu2": 16780758, + "Sinh_va": 16780736, + "Sinh_ya": 16780730, + "SlowKeys_Enable": 65139, + "StickyKeys_Enable": 65141, + "Super_L": 65515, + "Super_R": 65516, + "Sys_Req": 65301, + "T": 84, + "THORN": 222, + "Tab": 65289, + "Tabovedot": 16785002, + "Tcaron": 427, + "Tcedilla": 478, + "Terminate_Server": 65237, + "Thai_baht": 3551, + "Thai_bobaimai": 3514, + "Thai_chochan": 3496, + "Thai_chochang": 3498, + "Thai_choching": 3497, + "Thai_chochoe": 3500, + "Thai_dochada": 3502, + "Thai_dodek": 3508, + "Thai_fofa": 3517, + "Thai_fofan": 3519, + "Thai_hohip": 3531, + "Thai_honokhuk": 3534, + "Thai_khokhai": 3490, + "Thai_khokhon": 3493, + "Thai_khokhuat": 3491, + "Thai_khokhwai": 3492, + "Thai_khorakhang": 3494, + "Thai_kokai": 3489, + "Thai_lakkhangyao": 3557, + "Thai_lekchet": 3575, + "Thai_lekha": 3573, + "Thai_lekhok": 3574, + "Thai_lekkao": 3577, + "Thai_leknung": 3569, + "Thai_lekpaet": 3576, + "Thai_leksam": 3571, + "Thai_leksi": 3572, + "Thai_leksong": 3570, + "Thai_leksun": 3568, + "Thai_lochula": 3532, + "Thai_loling": 3525, + "Thai_lu": 3526, + "Thai_maichattawa": 3563, + "Thai_maiek": 3560, + "Thai_maihanakat": 3537, + "Thai_maihanakat_maitho": 3550, + "Thai_maitaikhu": 3559, + "Thai_maitho": 3561, + "Thai_maitri": 3562, + "Thai_maiyamok": 3558, + "Thai_moma": 3521, + "Thai_ngongu": 3495, + "Thai_nikhahit": 3565, + "Thai_nonen": 3507, + "Thai_nonu": 3513, + "Thai_oang": 3533, + "Thai_paiyannoi": 3535, + "Thai_phinthu": 3546, + "Thai_phophan": 3518, + "Thai_phophung": 3516, + "Thai_phosamphao": 3520, + "Thai_popla": 3515, + "Thai_rorua": 3523, + "Thai_ru": 3524, + "Thai_saraa": 3536, + "Thai_saraaa": 3538, + "Thai_saraae": 3553, + "Thai_saraaimaimalai": 3556, + "Thai_saraaimaimuan": 3555, + "Thai_saraam": 3539, + "Thai_sarae": 3552, + "Thai_sarai": 3540, + "Thai_saraii": 3541, + "Thai_sarao": 3554, + "Thai_sarau": 3544, + "Thai_saraue": 3542, + "Thai_sarauee": 3543, + "Thai_sarauu": 3545, + "Thai_sorusi": 3529, + "Thai_sosala": 3528, + "Thai_soso": 3499, + "Thai_sosua": 3530, + "Thai_thanthakhat": 3564, + "Thai_thonangmontho": 3505, + "Thai_thophuthao": 3506, + "Thai_thothahan": 3511, + "Thai_thothan": 3504, + "Thai_thothong": 3512, + "Thai_thothung": 3510, + "Thai_topatak": 3503, + "Thai_totao": 3509, + "Thai_wowaen": 3527, + "Thai_yoyak": 3522, + "Thai_yoying": 3501, + "Thorn": 222, + "Touroku": 65323, + "Tslash": 940, + "U": 85, + "Uacute": 218, + "Ubelowdot": 16785124, + "Ubreve": 733, + "Ucircumflex": 219, + "Udiaeresis": 220, + "Udoubleacute": 475, + "Ugrave": 217, + "Uhook": 16785126, + "Uhorn": 16777647, + "Uhornacute": 16785128, + "Uhornbelowdot": 16785136, + "Uhorngrave": 16785130, + "Uhornhook": 16785132, + "Uhorntilde": 16785134, + "Ukrainian_GHE_WITH_UPTURN": 1725, + "Ukrainian_I": 1718, + "Ukrainian_IE": 1716, + "Ukrainian_YI": 1719, + "Ukrainian_ghe_with_upturn": 1709, + "Ukrainian_i": 1702, + "Ukrainian_ie": 1700, + "Ukrainian_yi": 1703, + "Ukranian_I": 1718, + "Ukranian_JE": 1716, + "Ukranian_YI": 1719, + "Ukranian_i": 1702, + "Ukranian_je": 1700, + "Ukranian_yi": 1703, + "Umacron": 990, + "Undo": 65381, + "Uogonek": 985, + "Up": 65362, + "Uring": 473, + "Utilde": 989, + "V": 86, + "VoidSymbol": 16777215, + "W": 87, + "Wacute": 16785026, + "Wcircumflex": 16777588, + "Wdiaeresis": 16785028, + "Wgrave": 16785024, + "WonSign": 16785577, + "X": 88, + "XF86_AddFavorite": 269025081, + "XF86_ApplicationLeft": 269025104, + "XF86_ApplicationRight": 269025105, + "XF86_AudioCycleTrack": 269025179, + "XF86_AudioForward": 269025175, + "XF86_AudioLowerVolume": 269025041, + "XF86_AudioMedia": 269025074, + "XF86_AudioMicMute": 269025202, + "XF86_AudioMute": 269025042, + "XF86_AudioNext": 269025047, + "XF86_AudioPause": 269025073, + "XF86_AudioPlay": 269025044, + "XF86_AudioPreset": 269025206, + "XF86_AudioPrev": 269025046, + "XF86_AudioRaiseVolume": 269025043, + "XF86_AudioRandomPlay": 269025177, + "XF86_AudioRecord": 269025052, + "XF86_AudioRepeat": 269025176, + "XF86_AudioRewind": 269025086, + "XF86_AudioStop": 269025045, + "XF86_Away": 269025165, + "XF86_Back": 269025062, + "XF86_BackForward": 269025087, + "XF86_Battery": 269025171, + "XF86_Blue": 269025190, + "XF86_Bluetooth": 269025172, + "XF86_Book": 269025106, + "XF86_BrightnessAdjust": 269025083, + "XF86_CD": 269025107, + "XF86_Calculater": 269025108, + "XF86_Calculator": 269025053, + "XF86_Calendar": 269025056, + "XF86_Clear": 269025109, + "XF86_ClearGrab": 269024801, + "XF86_Close": 269025110, + "XF86_Community": 269025085, + "XF86_ContrastAdjust": 269025058, + "XF86_Copy": 269025111, + "XF86_Cut": 269025112, + "XF86_CycleAngle": 269025180, + "XF86_DOS": 269025114, + "XF86_Display": 269025113, + "XF86_Documents": 269025115, + "XF86_Eject": 269025068, + "XF86_Excel": 269025116, + "XF86_Explorer": 269025117, + "XF86_Favorites": 269025072, + "XF86_Finance": 269025084, + "XF86_Forward": 269025063, + "XF86_FrameBack": 269025181, + "XF86_FrameForward": 269025182, + "XF86_FullScreen": 269025208, + "XF86_Game": 269025118, + "XF86_Go": 269025119, + "XF86_Green": 269025188, + "XF86_Hibernate": 269025192, + "XF86_History": 269025079, + "XF86_HomePage": 269025048, + "XF86_HotLinks": 269025082, + "XF86_KbdBrightnessDown": 269025030, + "XF86_KbdBrightnessUp": 269025029, + "XF86_KbdLightOnOff": 269025028, + "XF86_Keyboard": 269025203, + "XF86_Launch0": 269025088, + "XF86_Launch1": 269025089, + "XF86_Launch2": 269025090, + "XF86_Launch3": 269025091, + "XF86_Launch4": 269025092, + "XF86_Launch5": 269025093, + "XF86_Launch6": 269025094, + "XF86_Launch7": 269025095, + "XF86_Launch8": 269025096, + "XF86_Launch9": 269025097, + "XF86_LaunchA": 269025098, + "XF86_LaunchB": 269025099, + "XF86_LaunchC": 269025100, + "XF86_LaunchD": 269025101, + "XF86_LaunchE": 269025102, + "XF86_LaunchF": 269025103, + "XF86_LightBulb": 269025077, + "XF86_LogGrabInfo": 269024805, + "XF86_LogOff": 269025121, + "XF86_LogWindowTree": 269024804, + "XF86_MacroRecordStart": 268964528, + "XF86_Mail": 269025049, + "XF86_MailForward": 269025168, + "XF86_Market": 269025122, + "XF86_Meeting": 269025123, + "XF86_Memo": 269025054, + "XF86_MenuKB": 269025125, + "XF86_MenuPB": 269025126, + "XF86_Messenger": 269025166, + "XF86_ModeLock": 269025025, + "XF86_MonBrightnessCycle": 269025031, + "XF86_MonBrightnessDown": 269025027, + "XF86_MonBrightnessUp": 269025026, + "XF86_Music": 269025170, + "XF86_MyComputer": 269025075, + "XF86_MySites": 269025127, + "XF86_New": 269025128, + "XF86_News": 269025129, + "XF86_Next_VMode": 269024802, + "XF86_OfficeHome": 269025130, + "XF86_Open": 269025131, + "XF86_OpenURL": 269025080, + "XF86_Option": 269025132, + "XF86_Paste": 269025133, + "XF86_Phone": 269025134, + "XF86_Pictures": 269025169, + "XF86_PowerDown": 269025057, + "XF86_PowerOff": 269025066, + "XF86_Prev_VMode": 269024803, + "XF86_Q": 269025136, + "XF86_RFKill": 269025205, + "XF86_Red": 269025187, + "XF86_Refresh": 269025065, + "XF86_Reload": 269025139, + "XF86_Reply": 269025138, + "XF86_RockerDown": 269025060, + "XF86_RockerEnter": 269025061, + "XF86_RockerUp": 269025059, + "XF86_RotateWindows": 269025140, + "XF86_RotationKB": 269025142, + "XF86_RotationLockToggle": 269025207, + "XF86_RotationPB": 269025141, + "XF86_Save": 269025143, + "XF86_ScreenSaver": 269025069, + "XF86_ScrollClick": 269025146, + "XF86_ScrollDown": 269025145, + "XF86_ScrollUp": 269025144, + "XF86_Search": 269025051, + "XF86_Select": 269025184, + "XF86_Send": 269025147, + "XF86_Shop": 269025078, + "XF86_Sleep": 269025071, + "XF86_Spell": 269025148, + "XF86_SplitScreen": 269025149, + "XF86_Standby": 269025040, + "XF86_Start": 269025050, + "XF86_Stop": 269025064, + "XF86_Subtitle": 269025178, + "XF86_Support": 269025150, + "XF86_Suspend": 269025191, + "XF86_Switch_VT_1": 269024769, + "XF86_Switch_VT_10": 269024778, + "XF86_Switch_VT_11": 269024779, + "XF86_Switch_VT_12": 269024780, + "XF86_Switch_VT_2": 269024770, + "XF86_Switch_VT_3": 269024771, + "XF86_Switch_VT_4": 269024772, + "XF86_Switch_VT_5": 269024773, + "XF86_Switch_VT_6": 269024774, + "XF86_Switch_VT_7": 269024775, + "XF86_Switch_VT_8": 269024776, + "XF86_Switch_VT_9": 269024777, + "XF86_TaskPane": 269025151, + "XF86_Terminal": 269025152, + "XF86_Time": 269025183, + "XF86_ToDoList": 269025055, + "XF86_Tools": 269025153, + "XF86_TopMenu": 269025186, + "XF86_TouchpadOff": 269025201, + "XF86_TouchpadOn": 269025200, + "XF86_TouchpadToggle": 269025193, + "XF86_Travel": 269025154, + "XF86_UWB": 269025174, + "XF86_Ungrab": 269024800, + "XF86_User1KB": 269025157, + "XF86_User2KB": 269025158, + "XF86_UserPB": 269025156, + "XF86_VendorHome": 269025076, + "XF86_Video": 269025159, + "XF86_View": 269025185, + "XF86_WLAN": 269025173, + "XF86_WWAN": 269025204, + "XF86_WWW": 269025070, + "XF86_WakeUp": 269025067, + "XF86_WebCam": 269025167, + "XF86_WheelButton": 269025160, + "XF86_Word": 269025161, + "XF86_Xfer": 269025162, + "XF86_Yellow": 269025189, + "XF86_ZoomIn": 269025163, + "XF86_ZoomOut": 269025164, + "XF86_iTouch": 269025120, + "Xabovedot": 16785034, + "Y": 89, + "Yacute": 221, + "Ybelowdot": 16785140, + "Ycircumflex": 16777590, + "Ydiaeresis": 5054, + "Ygrave": 16785138, + "Yhook": 16785142, + "Ytilde": 16785144, + "Z": 90, + "Zabovedot": 431, + "Zacute": 428, + "Zcaron": 430, + "Zen_Koho": 65341, + "Zenkaku": 65320, + "Zenkaku_Hankaku": 65322, + "Zstroke": 16777653, + "a": 97, + "aacute": 225, + "abelowdot": 16785057, + "abovedot": 511, + "abreve": 483, + "abreveacute": 16785071, + "abrevebelowdot": 16785079, + "abrevegrave": 16785073, + "abrevehook": 16785075, + "abrevetilde": 16785077, + "acircumflex": 226, + "acircumflexacute": 16785061, + "acircumflexbelowdot": 16785069, + "acircumflexgrave": 16785063, + "acircumflexhook": 16785065, + "acircumflextilde": 16785067, + "acute": 180, + "adiaeresis": 228, + "ae": 230, + "agrave": 224, + "ahook": 16785059, + "amacron": 992, + "ampersand": 38, + "aogonek": 433, + "apostrophe": 39, + "approxeq": 16785992, + "approximate": 2248, + "aring": 229, + "asciicircum": 94, + "asciitilde": 126, + "asterisk": 42, + "at": 64, + "atilde": 227, + "b": 98, + "babovedot": 16784899, + "backslash": 92, + "ballotcross": 2804, + "bar": 124, + "because": 16785973, + "blank": 2527, + "botintegral": 2213, + "botleftparens": 2220, + "botleftsqbracket": 2216, + "botleftsummation": 2226, + "botrightparens": 2222, + "botrightsqbracket": 2218, + "botrightsummation": 2230, + "bott": 2550, + "botvertsummationconnector": 2228, + "braceleft": 123, + "braceright": 125, + "bracketleft": 91, + "bracketright": 93, + "braille_blank": 16787456, + "braille_dot_1": 65521, + "braille_dot_10": 65530, + "braille_dot_2": 65522, + "braille_dot_3": 65523, + "braille_dot_4": 65524, + "braille_dot_5": 65525, + "braille_dot_6": 65526, + "braille_dot_7": 65527, + "braille_dot_8": 65528, + "braille_dot_9": 65529, + "braille_dots_1": 16787457, + "braille_dots_12": 16787459, + "braille_dots_123": 16787463, + "braille_dots_1234": 16787471, + "braille_dots_12345": 16787487, + "braille_dots_123456": 16787519, + "braille_dots_1234567": 16787583, + "braille_dots_12345678": 16787711, + "braille_dots_1234568": 16787647, + "braille_dots_123457": 16787551, + "braille_dots_1234578": 16787679, + "braille_dots_123458": 16787615, + "braille_dots_12346": 16787503, + "braille_dots_123467": 16787567, + "braille_dots_1234678": 16787695, + "braille_dots_123468": 16787631, + "braille_dots_12347": 16787535, + "braille_dots_123478": 16787663, + "braille_dots_12348": 16787599, + "braille_dots_1235": 16787479, + "braille_dots_12356": 16787511, + "braille_dots_123567": 16787575, + "braille_dots_1235678": 16787703, + "braille_dots_123568": 16787639, + "braille_dots_12357": 16787543, + "braille_dots_123578": 16787671, + "braille_dots_12358": 16787607, + "braille_dots_1236": 16787495, + "braille_dots_12367": 16787559, + "braille_dots_123678": 16787687, + "braille_dots_12368": 16787623, + "braille_dots_1237": 16787527, + "braille_dots_12378": 16787655, + "braille_dots_1238": 16787591, + "braille_dots_124": 16787467, + "braille_dots_1245": 16787483, + "braille_dots_12456": 16787515, + "braille_dots_124567": 16787579, + "braille_dots_1245678": 16787707, + "braille_dots_124568": 16787643, + "braille_dots_12457": 16787547, + "braille_dots_124578": 16787675, + "braille_dots_12458": 16787611, + "braille_dots_1246": 16787499, + "braille_dots_12467": 16787563, + "braille_dots_124678": 16787691, + "braille_dots_12468": 16787627, + "braille_dots_1247": 16787531, + "braille_dots_12478": 16787659, + "braille_dots_1248": 16787595, + "braille_dots_125": 16787475, + "braille_dots_1256": 16787507, + "braille_dots_12567": 16787571, + "braille_dots_125678": 16787699, + "braille_dots_12568": 16787635, + "braille_dots_1257": 16787539, + "braille_dots_12578": 16787667, + "braille_dots_1258": 16787603, + "braille_dots_126": 16787491, + "braille_dots_1267": 16787555, + "braille_dots_12678": 16787683, + "braille_dots_1268": 16787619, + "braille_dots_127": 16787523, + "braille_dots_1278": 16787651, + "braille_dots_128": 16787587, + "braille_dots_13": 16787461, + "braille_dots_134": 16787469, + "braille_dots_1345": 16787485, + "braille_dots_13456": 16787517, + "braille_dots_134567": 16787581, + "braille_dots_1345678": 16787709, + "braille_dots_134568": 16787645, + "braille_dots_13457": 16787549, + "braille_dots_134578": 16787677, + "braille_dots_13458": 16787613, + "braille_dots_1346": 16787501, + "braille_dots_13467": 16787565, + "braille_dots_134678": 16787693, + "braille_dots_13468": 16787629, + "braille_dots_1347": 16787533, + "braille_dots_13478": 16787661, + "braille_dots_1348": 16787597, + "braille_dots_135": 16787477, + "braille_dots_1356": 16787509, + "braille_dots_13567": 16787573, + "braille_dots_135678": 16787701, + "braille_dots_13568": 16787637, + "braille_dots_1357": 16787541, + "braille_dots_13578": 16787669, + "braille_dots_1358": 16787605, + "braille_dots_136": 16787493, + "braille_dots_1367": 16787557, + "braille_dots_13678": 16787685, + "braille_dots_1368": 16787621, + "braille_dots_137": 16787525, + "braille_dots_1378": 16787653, + "braille_dots_138": 16787589, + "braille_dots_14": 16787465, + "braille_dots_145": 16787481, + "braille_dots_1456": 16787513, + "braille_dots_14567": 16787577, + "braille_dots_145678": 16787705, + "braille_dots_14568": 16787641, + "braille_dots_1457": 16787545, + "braille_dots_14578": 16787673, + "braille_dots_1458": 16787609, + "braille_dots_146": 16787497, + "braille_dots_1467": 16787561, + "braille_dots_14678": 16787689, + "braille_dots_1468": 16787625, + "braille_dots_147": 16787529, + "braille_dots_1478": 16787657, + "braille_dots_148": 16787593, + "braille_dots_15": 16787473, + "braille_dots_156": 16787505, + "braille_dots_1567": 16787569, + "braille_dots_15678": 16787697, + "braille_dots_1568": 16787633, + "braille_dots_157": 16787537, + "braille_dots_1578": 16787665, + "braille_dots_158": 16787601, + "braille_dots_16": 16787489, + "braille_dots_167": 16787553, + "braille_dots_1678": 16787681, + "braille_dots_168": 16787617, + "braille_dots_17": 16787521, + "braille_dots_178": 16787649, + "braille_dots_18": 16787585, + "braille_dots_2": 16787458, + "braille_dots_23": 16787462, + "braille_dots_234": 16787470, + "braille_dots_2345": 16787486, + "braille_dots_23456": 16787518, + "braille_dots_234567": 16787582, + "braille_dots_2345678": 16787710, + "braille_dots_234568": 16787646, + "braille_dots_23457": 16787550, + "braille_dots_234578": 16787678, + "braille_dots_23458": 16787614, + "braille_dots_2346": 16787502, + "braille_dots_23467": 16787566, + "braille_dots_234678": 16787694, + "braille_dots_23468": 16787630, + "braille_dots_2347": 16787534, + "braille_dots_23478": 16787662, + "braille_dots_2348": 16787598, + "braille_dots_235": 16787478, + "braille_dots_2356": 16787510, + "braille_dots_23567": 16787574, + "braille_dots_235678": 16787702, + "braille_dots_23568": 16787638, + "braille_dots_2357": 16787542, + "braille_dots_23578": 16787670, + "braille_dots_2358": 16787606, + "braille_dots_236": 16787494, + "braille_dots_2367": 16787558, + "braille_dots_23678": 16787686, + "braille_dots_2368": 16787622, + "braille_dots_237": 16787526, + "braille_dots_2378": 16787654, + "braille_dots_238": 16787590, + "braille_dots_24": 16787466, + "braille_dots_245": 16787482, + "braille_dots_2456": 16787514, + "braille_dots_24567": 16787578, + "braille_dots_245678": 16787706, + "braille_dots_24568": 16787642, + "braille_dots_2457": 16787546, + "braille_dots_24578": 16787674, + "braille_dots_2458": 16787610, + "braille_dots_246": 16787498, + "braille_dots_2467": 16787562, + "braille_dots_24678": 16787690, + "braille_dots_2468": 16787626, + "braille_dots_247": 16787530, + "braille_dots_2478": 16787658, + "braille_dots_248": 16787594, + "braille_dots_25": 16787474, + "braille_dots_256": 16787506, + "braille_dots_2567": 16787570, + "braille_dots_25678": 16787698, + "braille_dots_2568": 16787634, + "braille_dots_257": 16787538, + "braille_dots_2578": 16787666, + "braille_dots_258": 16787602, + "braille_dots_26": 16787490, + "braille_dots_267": 16787554, + "braille_dots_2678": 16787682, + "braille_dots_268": 16787618, + "braille_dots_27": 16787522, + "braille_dots_278": 16787650, + "braille_dots_28": 16787586, + "braille_dots_3": 16787460, + "braille_dots_34": 16787468, + "braille_dots_345": 16787484, + "braille_dots_3456": 16787516, + "braille_dots_34567": 16787580, + "braille_dots_345678": 16787708, + "braille_dots_34568": 16787644, + "braille_dots_3457": 16787548, + "braille_dots_34578": 16787676, + "braille_dots_3458": 16787612, + "braille_dots_346": 16787500, + "braille_dots_3467": 16787564, + "braille_dots_34678": 16787692, + "braille_dots_3468": 16787628, + "braille_dots_347": 16787532, + "braille_dots_3478": 16787660, + "braille_dots_348": 16787596, + "braille_dots_35": 16787476, + "braille_dots_356": 16787508, + "braille_dots_3567": 16787572, + "braille_dots_35678": 16787700, + "braille_dots_3568": 16787636, + "braille_dots_357": 16787540, + "braille_dots_3578": 16787668, + "braille_dots_358": 16787604, + "braille_dots_36": 16787492, + "braille_dots_367": 16787556, + "braille_dots_3678": 16787684, + "braille_dots_368": 16787620, + "braille_dots_37": 16787524, + "braille_dots_378": 16787652, + "braille_dots_38": 16787588, + "braille_dots_4": 16787464, + "braille_dots_45": 16787480, + "braille_dots_456": 16787512, + "braille_dots_4567": 16787576, + "braille_dots_45678": 16787704, + "braille_dots_4568": 16787640, + "braille_dots_457": 16787544, + "braille_dots_4578": 16787672, + "braille_dots_458": 16787608, + "braille_dots_46": 16787496, + "braille_dots_467": 16787560, + "braille_dots_4678": 16787688, + "braille_dots_468": 16787624, + "braille_dots_47": 16787528, + "braille_dots_478": 16787656, + "braille_dots_48": 16787592, + "braille_dots_5": 16787472, + "braille_dots_56": 16787504, + "braille_dots_567": 16787568, + "braille_dots_5678": 16787696, + "braille_dots_568": 16787632, + "braille_dots_57": 16787536, + "braille_dots_578": 16787664, + "braille_dots_58": 16787600, + "braille_dots_6": 16787488, + "braille_dots_67": 16787552, + "braille_dots_678": 16787680, + "braille_dots_68": 16787616, + "braille_dots_7": 16787520, + "braille_dots_78": 16787648, + "braille_dots_8": 16787584, + "breve": 418, + "brokenbar": 166, + "c": 99, + "c_h": 65187, + "cabovedot": 741, + "cacute": 486, + "careof": 2744, + "caret": 2812, + "caron": 439, + "ccaron": 488, + "ccedilla": 231, + "ccircumflex": 742, + "cedilla": 184, + "cent": 162, + "ch": 65184, + "checkerboard": 2529, + "checkmark": 2803, + "circle": 3023, + "club": 2796, + "colon": 58, + "combining_acute": 16777985, + "combining_belowdot": 16778019, + "combining_grave": 16777984, + "combining_hook": 16777993, + "combining_tilde": 16777987, + "comma": 44, + "containsas": 16785931, + "copyright": 169, + "cr": 2532, + "crossinglines": 2542, + "cuberoot": 16785947, + "currency": 164, + "cursor": 2815, + "d": 100, + "dabovedot": 16784907, + "dagger": 2801, + "dcaron": 495, + "dead_A": 65153, + "dead_E": 65155, + "dead_I": 65157, + "dead_O": 65159, + "dead_SCHWA": 65163, + "dead_U": 65161, + "dead_a": 65152, + "dead_abovecomma": 65124, + "dead_abovedot": 65110, + "dead_abovereversedcomma": 65125, + "dead_abovering": 65112, + "dead_acute": 65105, + "dead_belowbreve": 65131, + "dead_belowcircumflex": 65129, + "dead_belowcomma": 65134, + "dead_belowdiaeresis": 65132, + "dead_belowdot": 65120, + "dead_belowmacron": 65128, + "dead_belowring": 65127, + "dead_belowtilde": 65130, + "dead_breve": 65109, + "dead_capital_schwa": 65163, + "dead_caron": 65114, + "dead_cedilla": 65115, + "dead_circumflex": 65106, + "dead_currency": 65135, + "dead_dasia": 65125, + "dead_diaeresis": 65111, + "dead_doubleacute": 65113, + "dead_doublegrave": 65126, + "dead_e": 65154, + "dead_grave": 65104, + "dead_greek": 65164, + "dead_hamza": 65165, + "dead_hook": 65121, + "dead_horn": 65122, + "dead_i": 65156, + "dead_invertedbreve": 65133, + "dead_iota": 65117, + "dead_macron": 65108, + "dead_o": 65158, + "dead_ogonek": 65116, + "dead_perispomeni": 65107, + "dead_psili": 65124, + "dead_schwa": 65162, + "dead_semivoiced_sound": 65119, + "dead_small_schwa": 65162, + "dead_stroke": 65123, + "dead_tilde": 65107, + "dead_u": 65160, + "dead_voiced_sound": 65118, + "decimalpoint": 2749, + "degree": 176, + "diaeresis": 168, + "diamond": 2797, + "digitspace": 2725, + "dintegral": 16785964, + "division": 247, + "dollar": 36, + "doubbaselinedot": 2735, + "doubleacute": 445, + "doubledagger": 2802, + "doublelowquotemark": 2814, + "downarrow": 2302, + "downcaret": 2984, + "downshoe": 3030, + "downstile": 3012, + "downtack": 3010, + "dstroke": 496, + "e": 101, + "eabovedot": 1004, + "eacute": 233, + "ebelowdot": 16785081, + "ecaron": 492, + "ecircumflex": 234, + "ecircumflexacute": 16785087, + "ecircumflexbelowdot": 16785095, + "ecircumflexgrave": 16785089, + "ecircumflexhook": 16785091, + "ecircumflextilde": 16785093, + "ediaeresis": 235, + "egrave": 232, + "ehook": 16785083, + "eightsubscript": 16785544, + "eightsuperior": 16785528, + "elementof": 16785928, + "ellipsis": 2734, + "em3space": 2723, + "em4space": 2724, + "emacron": 954, + "emdash": 2729, + "emfilledcircle": 2782, + "emfilledrect": 2783, + "emopencircle": 2766, + "emopenrectangle": 2767, + "emptyset": 16785925, + "emspace": 2721, + "endash": 2730, + "enfilledcircbullet": 2790, + "enfilledsqbullet": 2791, + "eng": 959, + "enopencircbullet": 2784, + "enopensquarebullet": 2785, + "enspace": 2722, + "eogonek": 490, + "equal": 61, + "eth": 240, + "etilde": 16785085, + "exclam": 33, + "exclamdown": 161, + "ezh": 16777874, + "f": 102, + "fabovedot": 16784927, + "femalesymbol": 2808, + "ff": 2531, + "figdash": 2747, + "filledlefttribullet": 2780, + "filledrectbullet": 2779, + "filledrighttribullet": 2781, + "filledtribulletdown": 2793, + "filledtribulletup": 2792, + "fiveeighths": 2757, + "fivesixths": 2743, + "fivesubscript": 16785541, + "fivesuperior": 16785525, + "fourfifths": 2741, + "foursubscript": 16785540, + "foursuperior": 16785524, + "fourthroot": 16785948, + "function": 2294, + "g": 103, + "gabovedot": 757, + "gbreve": 699, + "gcaron": 16777703, + "gcedilla": 955, + "gcircumflex": 760, + "grave": 96, + "greater": 62, + "greaterthanequal": 2238, + "guillemetleft": 171, + "guillemetright": 187, + "guillemotleft": 171, + "guillemotright": 187, + "h": 104, + "hairspace": 2728, + "hcircumflex": 694, + "heart": 2798, + "hebrew_aleph": 3296, + "hebrew_ayin": 3314, + "hebrew_bet": 3297, + "hebrew_beth": 3297, + "hebrew_chet": 3303, + "hebrew_dalet": 3299, + "hebrew_daleth": 3299, + "hebrew_doublelowline": 3295, + "hebrew_finalkaph": 3306, + "hebrew_finalmem": 3309, + "hebrew_finalnun": 3311, + "hebrew_finalpe": 3315, + "hebrew_finalzade": 3317, + "hebrew_finalzadi": 3317, + "hebrew_gimel": 3298, + "hebrew_gimmel": 3298, + "hebrew_he": 3300, + "hebrew_het": 3303, + "hebrew_kaph": 3307, + "hebrew_kuf": 3319, + "hebrew_lamed": 3308, + "hebrew_mem": 3310, + "hebrew_nun": 3312, + "hebrew_pe": 3316, + "hebrew_qoph": 3319, + "hebrew_resh": 3320, + "hebrew_samech": 3313, + "hebrew_samekh": 3313, + "hebrew_shin": 3321, + "hebrew_taf": 3322, + "hebrew_taw": 3322, + "hebrew_tet": 3304, + "hebrew_teth": 3304, + "hebrew_waw": 3301, + "hebrew_yod": 3305, + "hebrew_zade": 3318, + "hebrew_zadi": 3318, + "hebrew_zain": 3302, + "hebrew_zayin": 3302, + "hexagram": 2778, + "horizconnector": 2211, + "horizlinescan1": 2543, + "horizlinescan3": 2544, + "horizlinescan5": 2545, + "horizlinescan7": 2546, + "horizlinescan9": 2547, + "hstroke": 689, + "ht": 2530, + "hyphen": 173, + "i": 105, + "iacute": 237, + "ibelowdot": 16785099, + "ibreve": 16777517, + "icircumflex": 238, + "identical": 2255, + "idiaeresis": 239, + "idotless": 697, + "ifonlyif": 2253, + "igrave": 236, + "ihook": 16785097, + "imacron": 1007, + "implies": 2254, + "includedin": 2266, + "includes": 2267, + "infinity": 2242, + "integral": 2239, + "intersection": 2268, + "iogonek": 999, + "itilde": 949, + "j": 106, + "jcircumflex": 700, + "jot": 3018, + "k": 107, + "kana_A": 1201, + "kana_CHI": 1217, + "kana_E": 1204, + "kana_FU": 1228, + "kana_HA": 1226, + "kana_HE": 1229, + "kana_HI": 1227, + "kana_HO": 1230, + "kana_HU": 1228, + "kana_I": 1202, + "kana_KA": 1206, + "kana_KE": 1209, + "kana_KI": 1207, + "kana_KO": 1210, + "kana_KU": 1208, + "kana_MA": 1231, + "kana_ME": 1234, + "kana_MI": 1232, + "kana_MO": 1235, + "kana_MU": 1233, + "kana_N": 1245, + "kana_NA": 1221, + "kana_NE": 1224, + "kana_NI": 1222, + "kana_NO": 1225, + "kana_NU": 1223, + "kana_O": 1205, + "kana_RA": 1239, + "kana_RE": 1242, + "kana_RI": 1240, + "kana_RO": 1243, + "kana_RU": 1241, + "kana_SA": 1211, + "kana_SE": 1214, + "kana_SHI": 1212, + "kana_SO": 1215, + "kana_SU": 1213, + "kana_TA": 1216, + "kana_TE": 1219, + "kana_TI": 1217, + "kana_TO": 1220, + "kana_TSU": 1218, + "kana_TU": 1218, + "kana_U": 1203, + "kana_WA": 1244, + "kana_WO": 1190, + "kana_YA": 1236, + "kana_YO": 1238, + "kana_YU": 1237, + "kana_a": 1191, + "kana_closingbracket": 1187, + "kana_comma": 1188, + "kana_conjunctive": 1189, + "kana_e": 1194, + "kana_fullstop": 1185, + "kana_i": 1192, + "kana_middledot": 1189, + "kana_o": 1195, + "kana_openingbracket": 1186, + "kana_switch": 65406, + "kana_tsu": 1199, + "kana_tu": 1199, + "kana_u": 1193, + "kana_ya": 1196, + "kana_yo": 1198, + "kana_yu": 1197, + "kappa": 930, + "kcedilla": 1011, + "kra": 930, + "l": 108, + "lacute": 485, + "latincross": 2777, + "lbelowdot": 16784951, + "lcaron": 437, + "lcedilla": 950, + "leftanglebracket": 2748, + "leftarrow": 2299, + "leftcaret": 2979, + "leftdoublequotemark": 2770, + "leftmiddlecurlybrace": 2223, + "leftopentriangle": 2764, + "leftpointer": 2794, + "leftradical": 2209, + "leftshoe": 3034, + "leftsinglequotemark": 2768, + "leftt": 2548, + "lefttack": 3036, + "less": 60, + "lessthanequal": 2236, + "lf": 2533, + "logicaland": 2270, + "logicalor": 2271, + "lowleftcorner": 2541, + "lowrightcorner": 2538, + "lstroke": 435, + "m": 109, + "mabovedot": 16784961, + "macron": 175, + "malesymbol": 2807, + "maltesecross": 2800, + "marker": 2751, + "masculine": 186, + "minus": 45, + "minutes": 2774, + "mu": 181, + "multiply": 215, + "musicalflat": 2806, + "musicalsharp": 2805, + "n": 110, + "nabla": 2245, + "nacute": 497, + "ncaron": 498, + "ncedilla": 1009, + "ninesubscript": 16785545, + "ninesuperior": 16785529, + "nl": 2536, + "nobreakspace": 160, + "notapproxeq": 16785991, + "notelementof": 16785929, + "notequal": 2237, + "notidentical": 16786018, + "notsign": 172, + "ntilde": 241, + "numbersign": 35, + "numerosign": 1712, + "o": 111, + "oacute": 243, + "obarred": 16777845, + "obelowdot": 16785101, + "ocaron": 16777682, + "ocircumflex": 244, + "ocircumflexacute": 16785105, + "ocircumflexbelowdot": 16785113, + "ocircumflexgrave": 16785107, + "ocircumflexhook": 16785109, + "ocircumflextilde": 16785111, + "odiaeresis": 246, + "odoubleacute": 501, + "oe": 5053, + "ogonek": 434, + "ograve": 242, + "ohook": 16785103, + "ohorn": 16777633, + "ohornacute": 16785115, + "ohornbelowdot": 16785123, + "ohorngrave": 16785117, + "ohornhook": 16785119, + "ohorntilde": 16785121, + "omacron": 1010, + "oneeighth": 2755, + "onefifth": 2738, + "onehalf": 189, + "onequarter": 188, + "onesixth": 2742, + "onesubscript": 16785537, + "onesuperior": 185, + "onethird": 2736, + "ooblique": 248, + "openrectbullet": 2786, + "openstar": 2789, + "opentribulletdown": 2788, + "opentribulletup": 2787, + "ordfeminine": 170, + "ordmasculine": 186, + "oslash": 248, + "otilde": 245, + "overbar": 3008, + "overline": 1150, + "p": 112, + "pabovedot": 16784983, + "paragraph": 182, + "parenleft": 40, + "parenright": 41, + "partdifferential": 16785922, + "partialderivative": 2287, + "percent": 37, + "period": 46, + "periodcentered": 183, + "permille": 2773, + "phonographcopyright": 2811, + "plus": 43, + "plusminus": 177, + "prescription": 2772, + "prolongedsound": 1200, + "punctspace": 2726, + "q": 113, + "quad": 3020, + "question": 63, + "questiondown": 191, + "quotedbl": 34, + "quoteleft": 96, + "quoteright": 39, + "r": 114, + "racute": 480, + "radical": 2262, + "rcaron": 504, + "rcedilla": 947, + "registered": 174, + "rightanglebracket": 2750, + "rightarrow": 2301, + "rightcaret": 2982, + "rightdoublequotemark": 2771, + "rightmiddlecurlybrace": 2224, + "rightmiddlesummation": 2231, + "rightopentriangle": 2765, + "rightpointer": 2795, + "rightshoe": 3032, + "rightsinglequotemark": 2769, + "rightt": 2549, + "righttack": 3068, + "s": 115, + "sabovedot": 16784993, + "sacute": 438, + "scaron": 441, + "scedilla": 442, + "schwa": 16777817, + "scircumflex": 766, + "script_switch": 65406, + "seconds": 2775, + "section": 167, + "semicolon": 59, + "semivoicedsound": 1247, + "seveneighths": 2758, + "sevensubscript": 16785543, + "sevensuperior": 16785527, + "signaturemark": 2762, + "signifblank": 2732, + "similarequal": 2249, + "singlelowquotemark": 2813, + "sixsubscript": 16785542, + "sixsuperior": 16785526, + "slash": 47, + "soliddiamond": 2528, + "space": 32, + "squareroot": 16785946, + "ssharp": 223, + "sterling": 163, + "stricteq": 16786019, + "t": 116, + "tabovedot": 16785003, + "tcaron": 443, + "tcedilla": 510, + "telephone": 2809, + "telephonerecorder": 2810, + "therefore": 2240, + "thinspace": 2727, + "thorn": 254, + "threeeighths": 2756, + "threefifths": 2740, + "threequarters": 190, + "threesubscript": 16785539, + "threesuperior": 179, + "tintegral": 16785965, + "topintegral": 2212, + "topleftparens": 2219, + "topleftradical": 2210, + "topleftsqbracket": 2215, + "topleftsummation": 2225, + "toprightparens": 2221, + "toprightsqbracket": 2217, + "toprightsummation": 2229, + "topt": 2551, + "topvertsummationconnector": 2227, + "trademark": 2761, + "trademarkincircle": 2763, + "tslash": 956, + "twofifths": 2739, + "twosubscript": 16785538, + "twosuperior": 178, + "twothirds": 2737, + "u": 117, + "uacute": 250, + "ubelowdot": 16785125, + "ubreve": 765, + "ucircumflex": 251, + "udiaeresis": 252, + "udoubleacute": 507, + "ugrave": 249, + "uhook": 16785127, + "uhorn": 16777648, + "uhornacute": 16785129, + "uhornbelowdot": 16785137, + "uhorngrave": 16785131, + "uhornhook": 16785133, + "uhorntilde": 16785135, + "umacron": 1022, + "underbar": 3014, + "underscore": 95, + "union": 2269, + "uogonek": 1017, + "uparrow": 2300, + "upcaret": 2985, + "upleftcorner": 2540, + "uprightcorner": 2539, + "upshoe": 3011, + "upstile": 3027, + "uptack": 3022, + "uring": 505, + "utilde": 1021, + "v": 118, + "variation": 2241, + "vertbar": 2552, + "vertconnector": 2214, + "voicedsound": 1246, + "vt": 2537, + "w": 119, + "wacute": 16785027, + "wcircumflex": 16777589, + "wdiaeresis": 16785029, + "wgrave": 16785025, + "x": 120, + "xabovedot": 16785035, + "y": 121, + "yacute": 253, + "ybelowdot": 16785141, + "ycircumflex": 16777591, + "ydiaeresis": 255, + "yen": 165, + "ygrave": 16785139, + "yhook": 16785143, + "ytilde": 16785145, + "z": 122, + "zabovedot": 447, + "zacute": 444, + "zcaron": 446, + "zerosubscript": 16785536, + "zerosuperior": 16785520, + "zstroke": 16777654, +} diff --git a/lib/logitech_receiver/__init__.py b/lib/logitech_receiver/__init__.py index 46aa1f0a20..7570dd918a 100644 --- a/lib/logitech_receiver/__init__.py +++ b/lib/logitech_receiver/__init__.py @@ -38,4 +38,4 @@ del logging -__version__ = '0.9' +__version__ = "0.9" diff --git a/lib/logitech_receiver/base.py b/lib/logitech_receiver/base.py index 2f06cf78b0..0a90e62bd0 100644 --- a/lib/logitech_receiver/base.py +++ b/lib/logitech_receiver/base.py @@ -45,14 +45,14 @@ # _wired_device = lambda product_id, interface: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'bus_id': 0x3, - 'usb_interface': interface, - 'isDevice': True + "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} +_bt_device = lambda product_id: {"vendor_id": 0x046D, "product_id": product_id, "bus_id": 0x5, "isDevice": True} DEVICE_IDS = [] @@ -66,13 +66,13 @@ 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 + 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): + 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): + if product_id >= 0xB012 and product_id <= 0xB0FF or product_id >= 0xB317 and product_id <= 0xB3FF: return _bt_device(product_id) @@ -80,7 +80,7 @@ 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'): + if usb_id == r.get("product_id"): return r return {} @@ -103,7 +103,7 @@ def product_information(usb_id): HIDPP_SHORT_MESSAGE_ID: _SHORT_MESSAGE_SIZE, HIDPP_LONG_MESSAGE_ID: _LONG_MESSAGE_SIZE, DJ_MESSAGE_ID: _MEDIUM_MESSAGE_SIZE, - 0x21: _MAX_READ_SIZE + 0x21: _MAX_READ_SIZE, } """Default timeout on read (in seconds).""" DEFAULT_TIMEOUT = 4 @@ -120,9 +120,11 @@ def product_information(usb_id): def match(record, bus_id, vendor_id, product_id): - return ((record.get('bus_id') is None or record.get('bus_id') == bus_id) - and (record.get('vendor_id') is None or record.get('vendor_id') == vendor_id) - and (record.get('product_id') is None or record.get('product_id') == product_id)) + return ( + (record.get("bus_id") is None or record.get("bus_id") == bus_id) + and (record.get("vendor_id") is None or record.get("vendor_id") == vendor_id) + and (record.get("product_id") is None or record.get("product_id") == product_id) + ) def filter_receivers(bus_id, vendor_id, product_id, hidpp_short=False, hidpp_long=False): @@ -131,7 +133,7 @@ def filter_receivers(bus_id, vendor_id, product_id, hidpp_short=False, hidpp_lon if match(record, bus_id, vendor_id, product_id): return record if vendor_id == 0x046D and 0xC500 <= product_id <= 0xC5FF: # unknown receiver - return {'vendor_id': vendor_id, 'product_id': product_id, 'bus_id': bus_id, 'isDevice': False} + return {"vendor_id": vendor_id, "product_id": product_id, "bus_id": bus_id, "isDevice": False} def receivers(): @@ -148,7 +150,7 @@ def filter(bus_id, vendor_id, product_id, hidpp_short=False, hidpp_long=False): 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} + 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) @@ -229,17 +231,17 @@ def write(handle, devnumber, data, long_message=False): assert data is not None assert isinstance(data, bytes), (repr(data), type(data)) - if long_message or len(data) > _SHORT_MESSAGE_SIZE - 2 or data[:1] == b'\x82': - wdata = _pack('!BB18s', HIDPP_LONG_MESSAGE_ID, devnumber, data) + if long_message or len(data) > _SHORT_MESSAGE_SIZE - 2 or data[:1] == b"\x82": + wdata = _pack("!BB18s", HIDPP_LONG_MESSAGE_ID, devnumber, data) else: - wdata = _pack('!BB5s', HIDPP_SHORT_MESSAGE_ID, devnumber, data) + wdata = _pack("!BB5s", HIDPP_SHORT_MESSAGE_ID, devnumber, data) if logger.isEnabledFor(logging.DEBUG): - logger.debug('(%s) <= w[%02X %02X %s %s]', handle, ord(wdata[:1]), devnumber, _strhex(wdata[2:4]), _strhex(wdata[4:])) + logger.debug("(%s) <= w[%02X %02X %s %s]", handle, ord(wdata[:1]), devnumber, _strhex(wdata[2:4]), _strhex(wdata[4:])) try: _hid.write(int(handle), wdata) except Exception as reason: - logger.error('write failed, assuming handle %r no longer available', handle) + logger.error("write failed, assuming handle %r no longer available", handle) close(handle) raise exceptions.NoReceiver(reason=reason) @@ -270,7 +272,7 @@ def check_message(data): if report_lengths.get(report_id) == len(data): return True else: - logger.warning('unexpected message size: report_id %02X message %s' % (report_id, _strhex(data))) + logger.warning("unexpected message size: report_id %02X message %s" % (report_id, _strhex(data))) return False @@ -288,7 +290,7 @@ def _read(handle, timeout): timeout = int(timeout * 1000) data = _hid.read(int(handle), _MAX_READ_SIZE, timeout) except Exception as reason: - logger.warning('read failed, assuming handle %r no longer available', handle) + logger.warning("read failed, assuming handle %r no longer available", handle) close(handle) raise exceptions.NoReceiver(reason=reason) @@ -296,9 +298,10 @@ def _read(handle, timeout): report_id = ord(data[:1]) devnumber = ord(data[1:2]) - if logger.isEnabledFor(logging.DEBUG - ) and (report_id != DJ_MESSAGE_ID or ord(data[2:3]) > 0x10): # ignore DJ input messages - logger.debug('(%s) => r[%02X %02X %s %s]', handle, report_id, devnumber, _strhex(data[2:4]), _strhex(data[4:])) + if logger.isEnabledFor(logging.DEBUG) and ( + report_id != DJ_MESSAGE_ID or ord(data[2:3]) > 0x10 + ): # ignore DJ input messages + logger.debug("(%s) => r[%02X %02X %s %s]", handle, report_id, devnumber, _strhex(data[2:4]), _strhex(data[4:])) return report_id, devnumber, data[2:] @@ -319,7 +322,7 @@ def _skip_incoming(handle, ihandle, notifications_hook): # read whatever is already in the buffer, if any data = _hid.read(ihandle, _MAX_READ_SIZE, 0) except Exception as reason: - logger.error('read failed, assuming receiver %s no longer available', handle) + logger.error("read failed, assuming receiver %s no longer available", handle) close(handle) raise exceptions.NoReceiver(reason=reason) @@ -355,20 +358,27 @@ def make_notification(report_id, devnumber, data): if ( # standard HID++ 1.0 notification, SubId may be 0x40 - 0x7F - (sub_id >= 0x40) or # noqa: E131 + (sub_id >= 0x40) # noqa: E131 + or # custom HID++1.0 battery events, where SubId is 0x07/0x0D - (sub_id in (0x07, 0x0D) and len(data) == 5 and data[4:5] == b'\x00') or + (sub_id in (0x07, 0x0D) and len(data) == 5 and data[4:5] == b"\x00") + or # custom HID++1.0 illumination event, where SubId is 0x17 - (sub_id == 0x17 and len(data) == 5) or + (sub_id == 0x17 and len(data) == 5) + or # HID++ 2.0 feature notifications have the SoftwareID 0 (address & 0x0F == 0x00) ): # noqa: E129 return _HIDPP_Notification(report_id, devnumber, sub_id, address, data[2:]) -_HIDPP_Notification = namedtuple('_HIDPP_Notification', ('report_id', 'devnumber', 'sub_id', 'address', 'data')) -_HIDPP_Notification.__str__ = lambda self: 'Notification(%02x,%d,%02X,%02X,%s)' % ( - self.report_id, self.devnumber, self.sub_id, self.address, _strhex(self.data) +_HIDPP_Notification = namedtuple("_HIDPP_Notification", ("report_id", "devnumber", "sub_id", "address", "data")) +_HIDPP_Notification.__str__ = lambda self: "Notification(%02x,%d,%02X,%02X,%s)" % ( + self.report_id, + self.devnumber, + self.sub_id, + self.address, + _strhex(self.data), ) del namedtuple @@ -384,7 +394,7 @@ def handle_lock(handle): with request_lock: if handles_lock.get(handle) is None: if logger.isEnabledFor(logging.INFO): - logger.info('New lock %s', repr(handle)) + logger.info("New lock %s", repr(handle)) handles_lock[handle] = _threading.Lock() # Serialize requests on the handle return handles_lock[handle] @@ -395,7 +405,7 @@ def acquire_timeout(lock, handle, timeout): result = lock.acquire(timeout=timeout) try: if not result: - logger.error('lock on handle %d not acquired, probably due to timeout', int(handle)) + logger.error("lock on handle %d not acquired, probably due to timeout", int(handle)) yield result finally: if result: @@ -415,7 +425,7 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error # import inspect as _inspect # print ('\n '.join(str(s) for s in _inspect.stack())) - with acquire_timeout(handle_lock(handle), handle, 10.): + with acquire_timeout(handle_lock(handle), handle, 10.0): assert isinstance(request_id, int) if (devnumber != 0xFF or protocol >= 2.0) and request_id < 0x8000: # For HID++ 2.0 feature requests, randomize the SoftwareId to make it @@ -431,19 +441,19 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error timeout *= 2 if params: - params = b''.join(_pack('B', p) if isinstance(p, int) else p for p in params) + params = b"".join(_pack("B", p) if isinstance(p, int) else p for p in params) else: - params = b'' + params = b"" # if logger.isEnabledFor(logging.DEBUG): # logger.debug("(%s) device %d request_id {%04X} params [%s]", handle, devnumber, request_id, _strhex(params)) - request_data = _pack('!H', request_id) + params + request_data = _pack("!H", request_id) + params ihandle = int(handle) - notifications_hook = getattr(handle, 'notifications_hook', None) + notifications_hook = getattr(handle, "notifications_hook", None) try: _skip_incoming(handle, ihandle, notifications_hook) except exceptions.NoReceiver: - logger.warning('device or receiver disconnected') + logger.warning("device or receiver disconnected") return None write(ihandle, devnumber, request_data, long_message) @@ -459,23 +469,34 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error if reply: report_id, reply_devnumber, reply_data = reply - if reply_devnumber == devnumber or reply_devnumber == devnumber ^ 0xff: # BT device returning 0x00 - if report_id == HIDPP_SHORT_MESSAGE_ID and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2 - ]: + if reply_devnumber == devnumber or reply_devnumber == devnumber ^ 0xFF: # BT device returning 0x00 + if ( + report_id == HIDPP_SHORT_MESSAGE_ID + and reply_data[:1] == b"\x8F" + and reply_data[1:3] == request_data[:2] + ): error = ord(reply_data[3:4]) if logger.isEnabledFor(logging.DEBUG): logger.debug( - '(%s) device 0x%02X error on request {%04X}: %d = %s', handle, devnumber, request_id, error, - _hidpp10_constants.ERROR[error] + "(%s) device 0x%02X error on request {%04X}: %d = %s", + handle, + devnumber, + request_id, + error, + _hidpp10_constants.ERROR[error], ) return _hidpp10_constants.ERROR[error] if return_error else None - if reply_data[:1] == b'\xFF' and reply_data[1:3] == request_data[:2]: + if reply_data[:1] == b"\xFF" and reply_data[1:3] == request_data[:2]: # a HID++ 2.0 feature call returned with an error error = ord(reply_data[3:4]) logger.error( - '(%s) device %d error on feature request {%04X}: %d = %s', handle, devnumber, request_id, error, - _hidpp20_constants.ERROR[error] + "(%s) device %d error on feature request {%04X}: %d = %s", + handle, + devnumber, + request_id, + error, + _hidpp20_constants.ERROR[error], ) raise _hidpp20.FeatureCallError(number=devnumber, request=request_id, error=error, params=params) @@ -511,8 +532,12 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error # logger.debug("(%s) still waiting for reply, delta %f", handle, delta) logger.warning( - 'timeout (%0.2f/%0.2f) on device %d request {%04X} params [%s]', delta, timeout, devnumber, request_id, - _strhex(params) + "timeout (%0.2f/%0.2f) on device %d request {%04X} params [%s]", + delta, + timeout, + devnumber, + request_id, + _strhex(params), ) # raise DeviceUnreachable(number=devnumber, request=request_id) @@ -522,20 +547,20 @@ def ping(handle, devnumber, long_message=False): :returns: The HID protocol supported by the device, as a floating point number, if the device is active. """ if logger.isEnabledFor(logging.DEBUG): - logger.debug('(%s) pinging device %d', handle, devnumber) - with acquire_timeout(handle_lock(handle), handle, 10.): - notifications_hook = getattr(handle, 'notifications_hook', None) + logger.debug("(%s) pinging device %d", handle, devnumber) + with acquire_timeout(handle_lock(handle), handle, 10.0): + notifications_hook = getattr(handle, "notifications_hook", None) try: _skip_incoming(handle, int(handle), notifications_hook) except exceptions.NoReceiver: - logger.warning('device or receiver disconnected') + logger.warning("device or receiver disconnected") return # randomize the SoftwareId and mark byte to be able to identify the ping # reply, and set most significant (0x8) bit in SoftwareId so that the reply # is always distinguishable from notifications request_id = 0x0018 | _random_bits(3) - request_data = _pack('!HBBB', request_id, 0, 0, _random_bits(8)) + request_data = _pack("!HBBB", request_id, 0, 0, _random_bits(8)) write(int(handle), devnumber, request_data, long_message) request_started = _timestamp() # we consider timeout from this point @@ -544,21 +569,26 @@ def ping(handle, devnumber, long_message=False): reply = _read(handle, _PING_TIMEOUT) if reply: report_id, reply_devnumber, reply_data = reply - if reply_devnumber == devnumber or reply_devnumber == devnumber ^ 0xff: # BT device returning 0x00 + if reply_devnumber == devnumber or reply_devnumber == devnumber ^ 0xFF: # BT device returning 0x00 if reply_data[:2] == request_data[:2] and reply_data[4:5] == request_data[-1:]: # HID++ 2.0+ device, currently connected return ord(reply_data[2:3]) + ord(reply_data[3:4]) / 10.0 - if report_id == HIDPP_SHORT_MESSAGE_ID and reply_data[:1] == b'\x8F' and \ - reply_data[1:3] == request_data[:2]: # error response + if ( + report_id == HIDPP_SHORT_MESSAGE_ID + and reply_data[:1] == b"\x8F" + and reply_data[1:3] == request_data[:2] + ): # error response error = ord(reply_data[3:4]) if error == _hidpp10_constants.ERROR.invalid_SubID__command: # a valid reply from a HID++ 1.0 device return 1.0 - if error == _hidpp10_constants.ERROR.resource_error or \ - error == _hidpp10_constants.ERROR.connection_request_failed: + if ( + error == _hidpp10_constants.ERROR.resource_error + or error == _hidpp10_constants.ERROR.connection_request_failed + ): return # device unreachable if error == _hidpp10_constants.ERROR.unknown_device: # no paired device with that number - logger.error('(%s) device %d error on ping request: unknown device', handle, devnumber) + logger.error("(%s) device %d error on ping request: unknown device", handle, devnumber) raise exceptions.NoSuchDevice(number=devnumber, request=request_id) if notifications_hook: @@ -570,4 +600,4 @@ def ping(handle, devnumber, long_message=False): delta = _timestamp() - request_started - logger.warning('(%s) timeout (%0.2f/%0.2f) on device %d ping', handle, delta, _PING_TIMEOUT, devnumber) + logger.warning("(%s) timeout (%0.2f/%0.2f) on device %d ping", handle, delta, _PING_TIMEOUT, devnumber) diff --git a/lib/logitech_receiver/base_usb.py b/lib/logitech_receiver/base_usb.py index c21dcb0aaf..6a2a6066ba 100644 --- a/lib/logitech_receiver/base_usb.py +++ b/lib/logitech_receiver/base_usb.py @@ -35,105 +35,105 @@ # 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? -_DRIVER = ('hid-generic', 'generic-usb', 'logitech-djreceiver') +_DRIVER = ("hid-generic", "generic-usb", "logitech-djreceiver") _bolt_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 2, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Bolt Receiver'), - 'receiver_kind': 'bolt', - 'max_devices': 6, - 'may_unpair': True + "vendor_id": 0x046D, + "product_id": product_id, + "usb_interface": 2, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("Bolt Receiver"), + "receiver_kind": "bolt", + "max_devices": 6, + "may_unpair": True, } _unifying_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 2, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Unifying Receiver'), - 'receiver_kind': 'unifying', - 'may_unpair': True + "vendor_id": 0x046D, + "product_id": product_id, + "usb_interface": 2, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("Unifying Receiver"), + "receiver_kind": "unifying", + "may_unpair": True, } _nano_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'may_unpair': False, - 're_pairs': True + "vendor_id": 0x046D, + "product_id": product_id, + "usb_interface": 1, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "may_unpair": False, + "re_pairs": True, } _nano_receiver_no_unpair = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'may_unpair': False, - 'unpair': False, - 're_pairs': True + "vendor_id": 0x046D, + "product_id": product_id, + "usb_interface": 1, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "may_unpair": False, + "unpair": False, + "re_pairs": True, } _nano_receiver_max2 = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'max_devices': 2, - 'may_unpair': False, - 're_pairs': True + "vendor_id": 0x046D, + "product_id": product_id, + "usb_interface": 1, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "max_devices": 2, + "may_unpair": False, + "re_pairs": True, } _nano_receiver_maxn = lambda product_id, max: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'max_devices': max, - 'may_unpair': False, - 're_pairs': True + "vendor_id": 0x046D, + "product_id": product_id, + "usb_interface": 1, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "max_devices": max, + "may_unpair": False, + "re_pairs": True, } _lenovo_receiver = lambda product_id: { - 'vendor_id': 0x17ef, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'may_unpair': False + "vendor_id": 0x17EF, + "product_id": product_id, + "usb_interface": 1, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "may_unpair": False, } _lightspeed_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 2, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Lightspeed Receiver'), - 'may_unpair': False + "vendor_id": 0x046D, + "product_id": product_id, + "usb_interface": 2, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("Lightspeed Receiver"), + "may_unpair": False, } _ex100_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('EX100 Receiver 27 Mhz'), - 'receiver_kind': '27Mhz', - 'max_devices': 4, - 'may_unpair': False, - 're_pairs': True + "vendor_id": 0x046D, + "product_id": product_id, + "usb_interface": 1, + "hid_driver": _DRIVER, # noqa: F821 + "name": _("EX100 Receiver 27 Mhz"), + "receiver_kind": "27Mhz", + "max_devices": 4, + "may_unpair": False, + "re_pairs": True, } # Receivers added here should also be listed in @@ -141,40 +141,40 @@ # Look in https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h # Bolt receivers (marked with the yellow lightning bolt logo) -BOLT_RECEIVER_C548 = _bolt_receiver(0xc548) +BOLT_RECEIVER_C548 = _bolt_receiver(0xC548) # standard Unifying receivers (marked with the orange Unifying logo) -UNIFYING_RECEIVER_C52B = _unifying_receiver(0xc52b) -UNIFYING_RECEIVER_C532 = _unifying_receiver(0xc532) +UNIFYING_RECEIVER_C52B = _unifying_receiver(0xC52B) +UNIFYING_RECEIVER_C532 = _unifying_receiver(0xC532) # Nano receivers (usually sold with low-end devices) -NANO_RECEIVER_ADVANCED = _nano_receiver_no_unpair(0xc52f) -NANO_RECEIVER_C518 = _nano_receiver(0xc518) -NANO_RECEIVER_C51A = _nano_receiver(0xc51a) -NANO_RECEIVER_C51B = _nano_receiver(0xc51b) -NANO_RECEIVER_C521 = _nano_receiver(0xc521) -NANO_RECEIVER_C525 = _nano_receiver(0xc525) -NANO_RECEIVER_C526 = _nano_receiver(0xc526) -NANO_RECEIVER_C52E = _nano_receiver_no_unpair(0xc52e) -NANO_RECEIVER_C531 = _nano_receiver(0xc531) -NANO_RECEIVER_C534 = _nano_receiver_max2(0xc534) -NANO_RECEIVER_C535 = _nano_receiver(0xc535) # branded as Dell -NANO_RECEIVER_C537 = _nano_receiver(0xc537) +NANO_RECEIVER_ADVANCED = _nano_receiver_no_unpair(0xC52F) +NANO_RECEIVER_C518 = _nano_receiver(0xC518) +NANO_RECEIVER_C51A = _nano_receiver(0xC51A) +NANO_RECEIVER_C51B = _nano_receiver(0xC51B) +NANO_RECEIVER_C521 = _nano_receiver(0xC521) +NANO_RECEIVER_C525 = _nano_receiver(0xC525) +NANO_RECEIVER_C526 = _nano_receiver(0xC526) +NANO_RECEIVER_C52E = _nano_receiver_no_unpair(0xC52E) +NANO_RECEIVER_C531 = _nano_receiver(0xC531) +NANO_RECEIVER_C534 = _nano_receiver_max2(0xC534) +NANO_RECEIVER_C535 = _nano_receiver(0xC535) # branded as Dell +NANO_RECEIVER_C537 = _nano_receiver(0xC537) # NANO_RECEIVER_C542 = _nano_receiver(0xc542) # does not use HID++ NANO_RECEIVER_6042 = _lenovo_receiver(0x6042) # Lightspeed receivers (usually sold with gaming devices) -LIGHTSPEED_RECEIVER_C539 = _lightspeed_receiver(0xc539) -LIGHTSPEED_RECEIVER_C53A = _lightspeed_receiver(0xc53a) -LIGHTSPEED_RECEIVER_C53D = _lightspeed_receiver(0xc53d) -LIGHTSPEED_RECEIVER_C53F = _lightspeed_receiver(0xc53f) -LIGHTSPEED_RECEIVER_C541 = _lightspeed_receiver(0xc541) -LIGHTSPEED_RECEIVER_C545 = _lightspeed_receiver(0xc545) -LIGHTSPEED_RECEIVER_C547 = _lightspeed_receiver(0xc547) +LIGHTSPEED_RECEIVER_C539 = _lightspeed_receiver(0xC539) +LIGHTSPEED_RECEIVER_C53A = _lightspeed_receiver(0xC53A) +LIGHTSPEED_RECEIVER_C53D = _lightspeed_receiver(0xC53D) +LIGHTSPEED_RECEIVER_C53F = _lightspeed_receiver(0xC53F) +LIGHTSPEED_RECEIVER_C541 = _lightspeed_receiver(0xC541) +LIGHTSPEED_RECEIVER_C545 = _lightspeed_receiver(0xC545) +LIGHTSPEED_RECEIVER_C547 = _lightspeed_receiver(0xC547) # EX100 old style receiver pre-unifying protocol # EX100_27MHZ_RECEIVER_C50C = _ex100_receiver(0xc50C) # in hid/hid-ids.h -EX100_27MHZ_RECEIVER_C517 = _ex100_receiver(0xc517) +EX100_27MHZ_RECEIVER_C517 = _ex100_receiver(0xC517) # EX100_27MHZ_RECEIVER_C51B = _ex100_receiver(0xc51B) # in hid/hid-ids.h ALL = ( diff --git a/lib/logitech_receiver/common.py b/lib/logitech_receiver/common.py index cab8a6506d..62f94f4f6e 100644 --- a/lib/logitech_receiver/common.py +++ b/lib/logitech_receiver/common.py @@ -31,28 +31,266 @@ def crc16(data: bytes): - ''' + """ CRC-16 (CCITT) implemented with a precomputed lookup table - ''' + """ table = [ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, - 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, - 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, - 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, - 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, - 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, - 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, - 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, - 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, - 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, - 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, - 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, - 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, - 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, - 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, - 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, - 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, - 0x1EF0 + 0x0000, + 0x1021, + 0x2042, + 0x3063, + 0x4084, + 0x50A5, + 0x60C6, + 0x70E7, + 0x8108, + 0x9129, + 0xA14A, + 0xB16B, + 0xC18C, + 0xD1AD, + 0xE1CE, + 0xF1EF, + 0x1231, + 0x0210, + 0x3273, + 0x2252, + 0x52B5, + 0x4294, + 0x72F7, + 0x62D6, + 0x9339, + 0x8318, + 0xB37B, + 0xA35A, + 0xD3BD, + 0xC39C, + 0xF3FF, + 0xE3DE, + 0x2462, + 0x3443, + 0x0420, + 0x1401, + 0x64E6, + 0x74C7, + 0x44A4, + 0x5485, + 0xA56A, + 0xB54B, + 0x8528, + 0x9509, + 0xE5EE, + 0xF5CF, + 0xC5AC, + 0xD58D, + 0x3653, + 0x2672, + 0x1611, + 0x0630, + 0x76D7, + 0x66F6, + 0x5695, + 0x46B4, + 0xB75B, + 0xA77A, + 0x9719, + 0x8738, + 0xF7DF, + 0xE7FE, + 0xD79D, + 0xC7BC, + 0x48C4, + 0x58E5, + 0x6886, + 0x78A7, + 0x0840, + 0x1861, + 0x2802, + 0x3823, + 0xC9CC, + 0xD9ED, + 0xE98E, + 0xF9AF, + 0x8948, + 0x9969, + 0xA90A, + 0xB92B, + 0x5AF5, + 0x4AD4, + 0x7AB7, + 0x6A96, + 0x1A71, + 0x0A50, + 0x3A33, + 0x2A12, + 0xDBFD, + 0xCBDC, + 0xFBBF, + 0xEB9E, + 0x9B79, + 0x8B58, + 0xBB3B, + 0xAB1A, + 0x6CA6, + 0x7C87, + 0x4CE4, + 0x5CC5, + 0x2C22, + 0x3C03, + 0x0C60, + 0x1C41, + 0xEDAE, + 0xFD8F, + 0xCDEC, + 0xDDCD, + 0xAD2A, + 0xBD0B, + 0x8D68, + 0x9D49, + 0x7E97, + 0x6EB6, + 0x5ED5, + 0x4EF4, + 0x3E13, + 0x2E32, + 0x1E51, + 0x0E70, + 0xFF9F, + 0xEFBE, + 0xDFDD, + 0xCFFC, + 0xBF1B, + 0xAF3A, + 0x9F59, + 0x8F78, + 0x9188, + 0x81A9, + 0xB1CA, + 0xA1EB, + 0xD10C, + 0xC12D, + 0xF14E, + 0xE16F, + 0x1080, + 0x00A1, + 0x30C2, + 0x20E3, + 0x5004, + 0x4025, + 0x7046, + 0x6067, + 0x83B9, + 0x9398, + 0xA3FB, + 0xB3DA, + 0xC33D, + 0xD31C, + 0xE37F, + 0xF35E, + 0x02B1, + 0x1290, + 0x22F3, + 0x32D2, + 0x4235, + 0x5214, + 0x6277, + 0x7256, + 0xB5EA, + 0xA5CB, + 0x95A8, + 0x8589, + 0xF56E, + 0xE54F, + 0xD52C, + 0xC50D, + 0x34E2, + 0x24C3, + 0x14A0, + 0x0481, + 0x7466, + 0x6447, + 0x5424, + 0x4405, + 0xA7DB, + 0xB7FA, + 0x8799, + 0x97B8, + 0xE75F, + 0xF77E, + 0xC71D, + 0xD73C, + 0x26D3, + 0x36F2, + 0x0691, + 0x16B0, + 0x6657, + 0x7676, + 0x4615, + 0x5634, + 0xD94C, + 0xC96D, + 0xF90E, + 0xE92F, + 0x99C8, + 0x89E9, + 0xB98A, + 0xA9AB, + 0x5844, + 0x4865, + 0x7806, + 0x6827, + 0x18C0, + 0x08E1, + 0x3882, + 0x28A3, + 0xCB7D, + 0xDB5C, + 0xEB3F, + 0xFB1E, + 0x8BF9, + 0x9BD8, + 0xABBB, + 0xBB9A, + 0x4A75, + 0x5A54, + 0x6A37, + 0x7A16, + 0x0AF1, + 0x1AD0, + 0x2AB3, + 0x3A92, + 0xFD2E, + 0xED0F, + 0xDD6C, + 0xCD4D, + 0xBDAA, + 0xAD8B, + 0x9DE8, + 0x8DC9, + 0x7C26, + 0x6C07, + 0x5C64, + 0x4C45, + 0x3CA2, + 0x2C83, + 0x1CE0, + 0x0CC1, + 0xEF1F, + 0xFF3E, + 0xCF5D, + 0xDF7C, + 0xAF9B, + 0xBFBA, + 0x8FD9, + 0x9FF8, + 0x6E17, + 0x7E36, + 0x4E55, + 0x5E74, + 0x2E93, + 0x3EB2, + 0x0ED1, + 0x1EF0, ] crc = 0xFFFF @@ -88,7 +326,7 @@ def __eq__(self, other): return self.name.lower() == other.lower() # this should catch comparisons with bytes in Py3 if other is not None: - raise TypeError('Unsupported type ' + str(type(other))) + raise TypeError("Unsupported type " + str(type(other))) def __ne__(self, other): return not self.__eq__(other) @@ -100,19 +338,19 @@ def __str__(self): return self.name def __repr__(self): - return 'NamedInt(%d, %r)' % (int(self), self.name) + return "NamedInt(%d, %r)" % (int(self), self.name) @classmethod def from_yaml(cls, loader, node): args = loader.construct_mapping(node) - return cls(value=args['value'], name=args['name']) + return cls(value=args["value"], name=args["name"]) @classmethod def to_yaml(cls, dumper, data): - return dumper.represent_mapping('!NamedInt', {'value': int(data), 'name': data.name}, flow_style=True) + return dumper.represent_mapping("!NamedInt", {"value": int(data), "name": data.name}, flow_style=True) -_yaml.SafeLoader.add_constructor('!NamedInt', NamedInt.from_yaml) +_yaml.SafeLoader.add_constructor("!NamedInt", NamedInt.from_yaml) _yaml.add_representer(NamedInt, NamedInt.to_yaml) @@ -129,14 +367,14 @@ class NamedInts: if the value already exists in the set (int or string), ValueError will be raised. """ - __slots__ = ('__dict__', '_values', '_indexed', '_fallback', '_is_sorted') - def __init__(self, dict=None, **kwargs): + __slots__ = ("__dict__", "_values", "_indexed", "_fallback", "_is_sorted") + def __init__(self, dict=None, **kwargs): def _readable_name(n): if not is_string(n): - raise TypeError('expected string, got ' + str(type(n))) - return n.replace('__', '/').replace('_', ' ') + raise TypeError("expected string, got " + str(type(n))) + return n.replace("__", "/").replace("_", " ") # print (repr(kwargs)) elements = dict if dict else kwargs @@ -163,13 +401,13 @@ def range(cls, from_value, to_value, name_generator=lambda x: str(x), step=1): def flag_names(self, value): unknown_bits = value for k in self._indexed: - assert bin(k).count('1') == 1 + assert bin(k).count("1") == 1 if k & value == k: unknown_bits &= ~k yield str(self._indexed[k]) if unknown_bits: - yield 'unknown:%06X' % unknown_bits + yield "unknown:%06X" % unknown_bits def _sort_values(self): self._values = sorted(self._values) @@ -190,7 +428,7 @@ def __getitem__(self, index): elif is_string(index): if index in self.__dict__: return self.__dict__[index] - return (next((x for x in self._values if str(x) == index), None)) + return next((x for x in self._values if str(x) == index), None) elif isinstance(index, slice): values = self._values if self._is_sorted else sorted(self._values) @@ -224,17 +462,17 @@ def __getitem__(self, index): def __setitem__(self, index, name): assert isinstance(index, int), type(index) if isinstance(name, NamedInt): - assert int(index) == int(name), repr(index) + ' ' + repr(name) + assert int(index) == int(name), repr(index) + " " + repr(name) value = name elif is_string(name): value = NamedInt(index, name) else: - raise TypeError('name must be a string') + raise TypeError("name must be a string") if str(value) in self.__dict__: - raise ValueError('%s (%d) already known' % (value, int(value))) + raise ValueError("%s (%d) already known" % (value, int(value))) if int(value) in self._indexed: - raise ValueError('%d (%s) already known' % (int(value), value)) + raise ValueError("%d (%s) already known" % (int(value), value)) self._values.append(value) self._is_sorted = False @@ -257,14 +495,13 @@ def __len__(self): return len(self._values) def __repr__(self): - return 'NamedInts(%s)' % ', '.join(repr(v) for v in self._values) + return "NamedInts(%s)" % ", ".join(repr(v) for v in self._values) def __or__(self, other): return NamedInts(**self.__dict__, **other.__dict__) class UnsortedNamedInts(NamedInts): - def _sort_values(self): pass @@ -276,18 +513,18 @@ def __or__(self, other): def strhex(x): assert x is not None """Produce a hex-string representation of a sequence of bytes.""" - return _hexlify(x).decode('ascii').upper() + return _hexlify(x).decode("ascii").upper() def bytes2int(x, signed=False): - return int.from_bytes(x, signed=signed, byteorder='big') + return int.from_bytes(x, signed=signed, byteorder="big") def int2bytes(x, count=None, signed=False): if count: - return x.to_bytes(length=count, byteorder='big', signed=signed) + return x.to_bytes(length=count, byteorder="big", signed=signed) else: - return x.to_bytes(length=8, byteorder='big', signed=signed).lstrip(b'\x00') + return x.to_bytes(length=8, byteorder="big", signed=signed).lstrip(b"\x00") class KwException(Exception): @@ -306,7 +543,7 @@ def __getattr__(self, k): """Firmware information.""" -FirmwareInfo = namedtuple('FirmwareInfo', ['kind', 'name', 'version', 'extras']) +FirmwareInfo = namedtuple("FirmwareInfo", ["kind", "name", "version", "extras"]) BATTERY_APPROX = NamedInts(empty=0, critical=5, low=20, good=50, full=90) diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py index 196a25411d..cca6317beb 100644 --- a/lib/logitech_receiver/descriptors.py +++ b/lib/logitech_receiver/descriptors.py @@ -33,7 +33,6 @@ class _DeviceDescriptor: - def __init__( self, name=None, @@ -44,7 +43,7 @@ def __init__( registers=None, usbid=None, interface=None, - btid=None + btid=None, ): self.name = name self.kind = kind @@ -76,21 +75,30 @@ def _D( ): if kind is None: kind = ( - _DK.mouse if 'Mouse' in name else _DK.keyboard if 'Keyboard' in name else _DK.numpad - if 'Number Pad' in name else _DK.touchpad if 'Touchpad' in name else _DK.trackball if 'Trackball' in name else None + _DK.mouse + if "Mouse" in name + else _DK.keyboard + if "Keyboard" in name + else _DK.numpad + if "Number Pad" in name + else _DK.touchpad + if "Touchpad" in name + else _DK.trackball + if "Trackball" in name + else None ) - assert kind is not None, 'descriptor for %s does not have kind set' % name + assert kind is not None, "descriptor for %s does not have kind set" % name if protocol is not None: if wpid: - for w in wpid if isinstance(wpid, tuple) else (wpid, ): + for w in wpid if isinstance(wpid, tuple) else (wpid,): if protocol > 1.0: - assert w[0:1] == '4', '%s has protocol %0.1f, wpid %s' % (name, protocol, w) + assert w[0:1] == "4", "%s has protocol %0.1f, wpid %s" % (name, protocol, w) else: - if w[0:1] == '1': - assert kind == _DK.mouse, '%s has protocol %0.1f, wpid %s' % (name, protocol, w) - elif w[0:1] == '2': - assert kind in (_DK.keyboard, _DK.numpad), '%s has protocol %0.1f, wpid %s' % (name, protocol, w) + if w[0:1] == "1": + assert kind == _DK.mouse, "%s has protocol %0.1f, wpid %s" % (name, protocol, w) + elif w[0:1] == "2": + assert kind in (_DK.keyboard, _DK.numpad), "%s has protocol %0.1f, wpid %s" % (name, protocol, w) device_descriptor = _DeviceDescriptor( name=name, @@ -101,23 +109,23 @@ def _D( registers=registers, usbid=usbid, interface=interface, - btid=btid + btid=btid, ) if usbid: found = get_usbid(usbid) - assert found is None, 'duplicate usbid in device descriptors: %s' % (found, ) + assert found is None, "duplicate usbid in device descriptors: %s" % (found,) if btid: found = get_btid(btid) - assert found is None, 'duplicate btid in device descriptors: %s' % (found, ) + assert found is None, "duplicate btid in device descriptors: %s" % (found,) - assert codename not in DEVICES, 'duplicate codename in device descriptors: %s' % (DEVICES[codename], ) + assert codename not in DEVICES, "duplicate codename in device descriptors: %s" % (DEVICES[codename],) if codename: DEVICES[codename] = device_descriptor if wpid: - for w in wpid if isinstance(wpid, tuple) else (wpid, ): - assert w not in DEVICES_WPID, 'duplicate wpid in device descriptors: %s' % (DEVICES_WPID[w], ) + for w in wpid if isinstance(wpid, tuple) else (wpid,): + assert w not in DEVICES_WPID, "duplicate wpid in device descriptors: %s" % (DEVICES_WPID[w],) DEVICES_WPID[w] = device_descriptor diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index 1834caf46e..c5ad3745ff 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -64,7 +64,7 @@ def __init__( long=None, product_id=None, bus_id=None, - setting_callback=None + setting_callback=None, ): assert receiver or handle Device.instances.append(self) @@ -125,15 +125,15 @@ def __init__( # assert link_notification.address == (0x04 if unifying else 0x03) kind = ord(link_notification.data[0:1]) & 0x0F # get 27Mhz wpid and set kind based on index - if receiver.receiver_kind == '27Mhz': # 27 Mhz receiver - self.wpid = '00' + _strhex(link_notification.data[2:3]) + if receiver.receiver_kind == "27Mhz": # 27 Mhz receiver + self.wpid = "00" + _strhex(link_notification.data[2:3]) kind = receiver.get_kind_from_index(number) self._kind = _hidpp10_constants.DEVICE_KIND[kind] - elif receiver.receiver_kind == '27Mhz': # 27 Mhz receiver doesn't have pairing registers + elif receiver.receiver_kind == "27Mhz": # 27 Mhz receiver doesn't have pairing registers self.wpid = _hid.find_paired_node_wpid(receiver.path, number) if not self.wpid: - logger.error('Unable to get wpid from udev for device %d of %s', number, receiver) - raise exceptions.NoSuchDevice(number=number, receiver=receiver, error='Not present 27Mhz device') + logger.error("Unable to get wpid from udev for device %d of %s", number, receiver) + raise exceptions.NoSuchDevice(number=number, receiver=receiver, error="Not present 27Mhz device") kind = receiver.get_kind_from_index(number) self._kind = _hidpp10_constants.DEVICE_KIND[kind] else: # get information from pairing registers @@ -141,10 +141,10 @@ def __init__( self.update_pairing_information() self.update_extended_pairing_information() if not self.wpid and not self._serial: # if neither then the device almost certainly wasn't found - raise exceptions.NoSuchDevice(number=number, receiver=receiver, error='no wpid or serial') + raise exceptions.NoSuchDevice(number=number, receiver=receiver, error="no wpid or serial") # the wpid is set to None on this object when the device is unpaired - assert self.wpid is not None, 'failed to read wpid: device %d of %s' % (number, receiver) + assert self.wpid is not None, "failed to read wpid: device %d of %s" % (number, receiver) self.descriptor = _descriptors.get_wpid(self.wpid) if self.descriptor is None: @@ -155,8 +155,9 @@ def __init__( self.descriptor = _descriptors.get_codename(self._codename) else: self.online = None # a direct connected device might not be online (as reported by user) - self.descriptor = _descriptors.get_btid(self.product_id) if self.bluetooth else \ - _descriptors.get_usbid(self.product_id) + self.descriptor = ( + _descriptors.get_btid(self.product_id) if self.bluetooth else _descriptors.get_usbid(self.product_id) + ) if self.number is None: # for direct-connected devices get 'number' from descriptor protocol else use 0xFF self.number = 0x00 if self.descriptor and self.descriptor.protocol and self.descriptor.protocol < 2.0 else 0xFF @@ -176,7 +177,7 @@ def __init__( self.features = _hidpp20.FeaturesArray(self) 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' + assert serial, "need serial number or unit ID to find a device" result = None for device in Device.instances: if device.online and (device.unitId == serial or device.serial == serial): @@ -197,14 +198,14 @@ def codename(self): if self.online and self.protocol >= 2.0: self._codename = _hidpp20.get_friendly_name(self) if not self._codename: - self._codename = self.name.split(' ', 1)[0] if self.name else None + self._codename = self.name.split(" ", 1)[0] if self.name else None if not self._codename and self.receiver: codename = self.receiver.device_codename(self.number) if codename: self._codename = codename elif self.protocol < 2.0: - self._codename = '? (%s)' % (self.wpid or self.product_id) - return self._codename or '?? (%s)' % (self.wpid or self.product_id) + self._codename = "? (%s)" % (self.wpid or self.product_id) + return self._codename or "?? (%s)" % (self.wpid or self.product_id) @property def name(self): @@ -216,14 +217,14 @@ def name(self): pass if self.online and self.protocol >= 2.0: self._name = _hidpp20.get_name(self) - return self._name or self._codename or ('Unknown device %s' % (self.wpid or self.product_id)) + return self._name or self._codename or ("Unknown device %s" % (self.wpid or self.product_id)) def get_ids(self): ids = _hidpp20.get_ids(self) if ids: self._unitId, self._modelId, self._tid_map = ids if logger.isEnabledFor(logging.INFO) and self._serial and self._serial != self._unitId: - logger.info('%s: unitId %s does not match serial %s', self, self._unitId, self._serial) + logger.info("%s: unitId %s does not match serial %s", self, self._unitId, self._serial) @property def unitId(self): @@ -251,7 +252,7 @@ def update_pairing_information(self): if not self._kind: self._kind = kind if not self._polling_rate: - self._polling_rate = str(polling_rate) + 'ms' + self._polling_rate = str(polling_rate) + "ms" def update_extended_pairing_information(self): if self.receiver: @@ -268,7 +269,7 @@ def kind(self): if not self._kind and self.protocol >= 2.0: kind = _hidpp20.get_kind(self) self._kind = KIND_MAP[kind] if kind else None - return self._kind or '?' + return self._kind or "?" @property def firmware(self): @@ -283,13 +284,13 @@ def firmware(self): def serial(self): if not self._serial: self.update_extended_pairing_information() - return self._serial or '' + return self._serial or "" @property def id(self): if not self.serial: - if self.persister and self.persister.get('_serial', None): - self._serial = self.persister.get('_serial', None) + if self.persister and self.persister.get("_serial", None): + self._serial = self.persister.get("_serial", None) return self.unitId or self.serial @property @@ -399,17 +400,17 @@ def battery(self): # None or level, next, status, voltage if self.protocol < 2.0: return _hidpp10.get_battery(self) else: - battery_feature = self.persister.get('_battery', None) if self.persister else None + battery_feature = self.persister.get("_battery", None) if self.persister else None if battery_feature != 0: result = _hidpp20.get_battery(self, battery_feature) try: feature, level, next, status, voltage = result if self.persister and battery_feature is None: - self.persister['_battery'] = feature + self.persister["_battery"] = feature return level, next, status, voltage except Exception: if self.persister and battery_feature is None: - self.persister['_battery'] = result + self.persister["_battery"] = result def enable_connection_notifications(self, enable=True): """Enable or disable device (dis)connection notifications on this @@ -428,12 +429,12 @@ def enable_connection_notifications(self, enable=True): set_flag_bits = 0 ok = _hidpp10.set_notification_flags(self, set_flag_bits) if not ok: - logger.warning('%s: failed to %s device notifications', self, 'enable' if enable else 'disable') + logger.warning("%s: failed to %s device notifications", self, "enable" if enable else "disable") flag_bits = _hidpp10.get_notification_flags(self) flag_names = None if flag_bits is None else tuple(_hidpp10_constants.NOTIFICATION_FLAG.flag_names(flag_bits)) if logger.isEnabledFor(logging.INFO): - logger.info('%s: device notifications %s %s', self, 'enabled' if enable else 'disabled', flag_names) + logger.info("%s: device notifications %s %s", self, "enabled" if enable else "disabled", flag_names) return flag_bits if ok else None def add_notification_handler(self, id: str, fn): @@ -454,7 +455,7 @@ def remove_notification_handler(self, id: str): """Unregisters the notification handler under name `id`.""" if id not in self._notification_handlers and logger.isEnabledFor(logging.INFO): - logger.info(f'Tried to remove nonexistent notification handler {id} from device {self}.') + logger.info(f"Tried to remove nonexistent notification handler {id} from device {self}.") else: del self._notification_handlers[id] @@ -477,7 +478,7 @@ def request(self, request_id, *params, no_reply=False): *params, no_reply=no_reply, long_message=long, - protocol=self.protocol + protocol=self.protocol, ) def feature_request(self, feature, function=0x00, *params, no_reply=False): @@ -517,10 +518,10 @@ def __bool__(self): def __str__(self): try: - name = self.name or self.codename or '?' + name = self.name or self.codename or "?" except exceptions.NoSuchDevice: - name = 'name not available' - return '' % (self.number, self.wpid or self.product_id, name, self.serial) + name = "name not available" + return "" % (self.number, self.wpid or self.product_id, name, self.serial) __repr__ = __str__ @@ -544,20 +545,20 @@ def open(self, device_info, setting_callback=None): long=device_info.hidpp_long, product_id=device_info.product_id, bus_id=device_info.bus_id, - setting_callback=setting_callback + setting_callback=setting_callback, ) except OSError as e: - logger.exception('open %s', device_info) + logger.exception("open %s", device_info) if e.errno == _errno.EACCES: raise except Exception: - logger.exception('open %s', device_info) + logger.exception("open %s", device_info) def close(self): handle, self.handle = self.handle, None if self in Device.instances: Device.instances.remove(self) - return (handle and _base.close(handle)) + return handle and _base.close(handle) def __del__(self): self.close() diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py index 97d13709de..d3c509ed8a 100644 --- a/lib/logitech_receiver/diversion.py +++ b/lib/logitech_receiver/diversion.py @@ -35,7 +35,7 @@ # There is no evdev on macOS or Windows. Diversion will not work without # it but other Solaar functionality is available. -if _platform.system() in ('Darwin', 'Windows'): +if _platform.system() in ("Darwin", "Windows"): evdev = None else: import evdev @@ -51,7 +51,7 @@ from .hidpp20 import FEATURE as _F from .special_keys import CONTROL as _CONTROL -gi.require_version('Gdk', '3.0') # isort:skip +gi.require_version("Gdk", "3.0") # isort:skip from gi.repository import Gdk, GLib # NOQA: E402 # isort:skip logger = logging.getLogger(__name__) @@ -94,22 +94,23 @@ _BUTTON_RELEASE = 2 _BUTTON_PRESS = 3 -CLICK, DEPRESS, RELEASE = 'click', 'depress', 'release' +CLICK, DEPRESS, RELEASE = "click", "depress", "release" gdisplay = Gdk.Display.get_default() # can be None if Solaar is run without a full window system gkeymap = Gdk.Keymap.get_for_display(gdisplay) if gdisplay else None if logger.isEnabledFor(logging.INFO): - logger.info('GDK Keymap %sset up', '' if gkeymap else 'not ') + logger.info("GDK Keymap %sset up", "" if gkeymap else "not ") -wayland = _os.getenv('WAYLAND_DISPLAY') # is this Wayland? +wayland = _os.getenv("WAYLAND_DISPLAY") # is this Wayland? if wayland: logger.warning( - 'rules cannot access modifier keys in Wayland, ' - 'accessing process only works on GNOME with Solaar Gnome extension installed' + "rules cannot access modifier keys in Wayland, " + "accessing process only works on GNOME with Solaar Gnome extension installed" ) try: import Xlib + _x11 = None # X11 might be available except Exception: _x11 = False # X11 is not available @@ -124,16 +125,26 @@ class XkbDisplay(_ctypes.Structure): - """ opaque struct """ + """opaque struct""" class XkbStateRec(_ctypes.Structure): - _fields_ = [('group', _ctypes.c_ubyte), ('locked_group', _ctypes.c_ubyte), ('base_group', _ctypes.c_ushort), - ('latched_group', _ctypes.c_ushort), ('mods', _ctypes.c_ubyte), ('base_mods', _ctypes.c_ubyte), - ('latched_mods', _ctypes.c_ubyte), ('locked_mods', _ctypes.c_ubyte), ('compat_state', _ctypes.c_ubyte), - ('grab_mods', _ctypes.c_ubyte), ('compat_grab_mods', _ctypes.c_ubyte), ('lookup_mods', _ctypes.c_ubyte), - ('compat_lookup_mods', _ctypes.c_ubyte), - ('ptr_buttons', _ctypes.c_ushort)] # something strange is happening here but it is not being used + _fields_ = [ + ("group", _ctypes.c_ubyte), + ("locked_group", _ctypes.c_ubyte), + ("base_group", _ctypes.c_ushort), + ("latched_group", _ctypes.c_ushort), + ("mods", _ctypes.c_ubyte), + ("base_mods", _ctypes.c_ubyte), + ("latched_mods", _ctypes.c_ubyte), + ("locked_mods", _ctypes.c_ubyte), + ("compat_state", _ctypes.c_ubyte), + ("grab_mods", _ctypes.c_ubyte), + ("compat_grab_mods", _ctypes.c_ubyte), + ("lookup_mods", _ctypes.c_ubyte), + ("compat_lookup_mods", _ctypes.c_ubyte), + ("ptr_buttons", _ctypes.c_ushort), + ] # something strange is happening here but it is not being used def x11_setup(): @@ -142,16 +153,17 @@ def x11_setup(): return _x11 try: from Xlib.display import Display + xdisplay = Display() modifier_keycodes = xdisplay.get_modifier_mapping() # there should be a way to do this in Gdk - NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW') - NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID') - WM_CLASS = xdisplay.intern_atom('WM_CLASS') + NET_ACTIVE_WINDOW = xdisplay.intern_atom("_NET_ACTIVE_WINDOW") + NET_WM_PID = xdisplay.intern_atom("_NET_WM_PID") + WM_CLASS = xdisplay.intern_atom("WM_CLASS") _x11 = True # X11 available if logger.isEnabledFor(logging.INFO): - logger.info('X11 library loaded and display set up') + logger.info("X11 library loaded and display set up") except Exception: - logger.warning('X11 not available - some rule capabilities inoperable', exc_info=_sys.exc_info()) + logger.warning("X11 not available - some rule capabilities inoperable", exc_info=_sys.exc_info()) _x11 = False xtest_available = False return _x11 @@ -163,10 +175,10 @@ def gnome_dbus_interface_setup(): return _dbus_interface try: bus = dbus.SessionBus() - remote_object = bus.get_object('org.gnome.Shell', '/io/github/pwr_solaar/solaar') - _dbus_interface = dbus.Interface(remote_object, 'io.github.pwr_solaar.solaar') + remote_object = bus.get_object("org.gnome.Shell", "/io/github/pwr_solaar/solaar") + _dbus_interface = dbus.Interface(remote_object, "io.github.pwr_solaar.solaar") except dbus.exceptions.DBusException: - logger.warning('Solaar Gnome extension not installed - some rule capabilities inoperable', exc_info=_sys.exc_info()) + logger.warning("Solaar Gnome extension not installed - some rule capabilities inoperable", exc_info=_sys.exc_info()) _dbus_interface = False return _dbus_interface @@ -176,35 +188,35 @@ def xkb_setup(): if Xkbdisplay is not None: return Xkbdisplay try: # set up to get keyboard state using ctypes interface to libx11 - X11Lib = _ctypes.cdll.LoadLibrary('libX11.so') + X11Lib = _ctypes.cdll.LoadLibrary("libX11.so") X11Lib.XOpenDisplay.restype = _ctypes.POINTER(XkbDisplay) X11Lib.XkbGetState.argtypes = [_ctypes.POINTER(XkbDisplay), _ctypes.c_uint, _ctypes.POINTER(XkbStateRec)] Xkbdisplay = X11Lib.XOpenDisplay(None) if logger.isEnabledFor(logging.INFO): - logger.info('XKB display set up') + logger.info("XKB display set up") except Exception: - logger.warning('XKB display not available - rules cannot access keyboard group', exc_info=_sys.exc_info()) + logger.warning("XKB display not available - rules cannot access keyboard group", exc_info=_sys.exc_info()) Xkbdisplay = False return Xkbdisplay if evdev: buttons = { - 'unknown': (None, None), - 'left': (1, evdev.ecodes.ecodes['BTN_LEFT']), - 'middle': (2, evdev.ecodes.ecodes['BTN_MIDDLE']), - 'right': (3, evdev.ecodes.ecodes['BTN_RIGHT']), - 'scroll_up': (4, evdev.ecodes.ecodes['BTN_4']), - 'scroll_down': (5, evdev.ecodes.ecodes['BTN_5']), - 'scroll_left': (6, evdev.ecodes.ecodes['BTN_6']), - 'scroll_right': (7, evdev.ecodes.ecodes['BTN_7']), - 'button8': (8, evdev.ecodes.ecodes['BTN_8']), - 'button9': (9, evdev.ecodes.ecodes['BTN_9']), + "unknown": (None, None), + "left": (1, evdev.ecodes.ecodes["BTN_LEFT"]), + "middle": (2, evdev.ecodes.ecodes["BTN_MIDDLE"]), + "right": (3, evdev.ecodes.ecodes["BTN_RIGHT"]), + "scroll_up": (4, evdev.ecodes.ecodes["BTN_4"]), + "scroll_down": (5, evdev.ecodes.ecodes["BTN_5"]), + "scroll_left": (6, evdev.ecodes.ecodes["BTN_6"]), + "scroll_right": (7, evdev.ecodes.ecodes["BTN_7"]), + "button8": (8, evdev.ecodes.ecodes["BTN_8"]), + "button9": (9, evdev.ecodes.ecodes["BTN_9"]), } # uinput capability for keyboard keys, mouse buttons, and scrolling - key_events = [c for n, c in evdev.ecodes.ecodes.items() if n.startswith('KEY') and n != 'KEY_CNT'] - for (_, evcode) in buttons.values(): + key_events = [c for n, c in evdev.ecodes.ecodes.items() if n.startswith("KEY") and n != "KEY_CNT"] + for _, evcode in buttons.values(): if evcode: key_events.append(evcode) devicecap = {evdev.ecodes.EV_KEY: key_events, evdev.ecodes.EV_REL: [evdev.ecodes.REL_WHEEL, evdev.ecodes.REL_HWHEEL]} @@ -222,12 +234,12 @@ def setup_uinput(): if udevice is not None: return udevice try: - udevice = evdev.uinput.UInput(events=devicecap, name='solaar-keyboard') + udevice = evdev.uinput.UInput(events=devicecap, name="solaar-keyboard") if logger.isEnabledFor(logging.INFO): - logger.info('uinput device set up') + logger.info("uinput device set up") return True except Exception as e: - logger.warning('cannot create uinput device: %s', e) + logger.warning("cannot create uinput device: %s", e) if wayland: # Wayland can't use xtest so may as well set up uinput now @@ -256,34 +268,34 @@ def modifier_code(keycode): def signed(bytes): - return int.from_bytes(bytes, 'big', signed=True) + return int.from_bytes(bytes, "big", signed=True) def xy_direction(_x, _y): # normalize x and y m = _sqrt((_x * _x) + (_y * _y)) if m == 0: - return 'noop' + return "noop" x = round(_x / m) y = round(_y / m) if x < 0 and y < 0: - return 'Mouse Up-left' + return "Mouse Up-left" elif x > 0 and y < 0: - return 'Mouse Up-right' + return "Mouse Up-right" elif x < 0 and y > 0: - return 'Mouse Down-left' + return "Mouse Down-left" elif x > 0 and y > 0: - return 'Mouse Down-right' + return "Mouse Down-right" elif x > 0: - return 'Mouse Right' + return "Mouse Right" elif x < 0: - return 'Mouse Left' + return "Mouse Left" elif y > 0: - return 'Mouse Down' + return "Mouse Down" elif y < 0: - return 'Mouse Up' + return "Mouse Up" else: - return 'noop' + return "noop" def simulate_xtest(code, event): @@ -291,17 +303,24 @@ def simulate_xtest(code, event): if x11_setup() and xtest_available: try: event = ( - Xlib.X.KeyPress if event == _KEY_PRESS else Xlib.X.KeyRelease if event == _KEY_RELEASE else - Xlib.X.ButtonPress if event == _BUTTON_PRESS else Xlib.X.ButtonRelease if event == _BUTTON_RELEASE else None + Xlib.X.KeyPress + if event == _KEY_PRESS + else Xlib.X.KeyRelease + if event == _KEY_RELEASE + else Xlib.X.ButtonPress + if event == _BUTTON_PRESS + else Xlib.X.ButtonRelease + if event == _BUTTON_RELEASE + else None ) Xlib.ext.xtest.fake_input(xdisplay, event, code) xdisplay.sync() if logger.isEnabledFor(logging.DEBUG): - logger.debug('xtest simulated input %s %s %s', xdisplay, event, code) + logger.debug("xtest simulated input %s %s %s", xdisplay, event, code) return True except Exception as e: xtest_available = False - logger.warning('xtest fake input failed: %s', e) + logger.warning("xtest fake input failed: %s", e) def simulate_uinput(what, code, arg): @@ -311,11 +330,11 @@ def simulate_uinput(what, code, arg): udevice.write(what, code, arg) udevice.syn() if logger.isEnabledFor(logging.DEBUG): - logger.debug('uinput simulated input %s %s %s', what, code, arg) + logger.debug("uinput simulated input %s %s %s", what, code, arg) return True except Exception as e: udevice = None - logger.warning('uinput write failed: %s', e) + logger.warning("uinput write failed: %s", e) def simulate_key(code, event): # X11 keycode but Solaar event code @@ -323,7 +342,7 @@ def simulate_key(code, event): # X11 keycode but Solaar event code return True if simulate_uinput(evdev.ecodes.EV_KEY, code - 8, event): return True - logger.warning('no way to simulate key input') + logger.warning("no way to simulate key input") def click_xtest(button, count): @@ -365,7 +384,7 @@ def click(button, count): return True if click_uinput(button, count): return True - logger.warning('no way to simulate mouse click') + logger.warning("no way to simulate mouse click") return False @@ -373,9 +392,9 @@ def simulate_scroll(dx, dy): if not wayland and xtest_available: success = True if dx: - success = click_xtest(buttons['scroll_right' if dx > 0 else 'scroll_left'], count=abs(dx)) + success = click_xtest(buttons["scroll_right" if dx > 0 else "scroll_left"], count=abs(dx)) if dy and success: - success = click_xtest(buttons['scroll_up' if dy > 0 else 'scroll_down'], count=abs(dy)) + success = click_xtest(buttons["scroll_up" if dy > 0 else "scroll_down"], count=abs(dy)) if success: return True if setup_uinput(): @@ -386,7 +405,7 @@ def simulate_scroll(dx, dy): success = simulate_uinput(evdev.ecodes.EV_REL, evdev.ecodes.REL_WHEEL, dy) if success: return True - logger.warning('no way to simulate scrolling') + logger.warning("no way to simulate scrolling") def thumb_wheel_up(f, r, d, a): @@ -416,47 +435,48 @@ def thumb_wheel_down(f, r, d, a): def charging(f, r, d, a): - if (f == _F.BATTERY_STATUS and r == 0 and 1 <= d[2] <= 4) or \ - (f == _F.BATTERY_VOLTAGE and r == 0 and d[2] & (1 << 7)) or \ - (f == _F.UNIFIED_BATTERY and r == 0 and 1 <= d[2] <= 3): + if ( + (f == _F.BATTERY_STATUS and r == 0 and 1 <= d[2] <= 4) + or (f == _F.BATTERY_VOLTAGE and r == 0 and d[2] & (1 << 7)) + or (f == _F.UNIFIED_BATTERY and r == 0 and 1 <= d[2] <= 3) + ): return 1 else: return False TESTS = { - 'crown_right': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[1] < 128 and d[1], False], - 'crown_left': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[1] >= 128 and 256 - d[1], False], - 'crown_right_ratchet': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[2] < 128 and d[2], False], - 'crown_left_ratchet': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[2] >= 128 and 256 - d[2], False], - 'crown_tap': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[5] == 0x01 and d[5], False], - 'crown_start_press': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] == 0x01 and d[6], False], - 'crown_end_press': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] == 0x05 and d[6], False], - 'crown_pressed': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] >= 0x01 and d[6] <= 0x04 and d[6], False], - 'thumb_wheel_up': [thumb_wheel_up, True], - 'thumb_wheel_down': [thumb_wheel_down, True], - 'lowres_wheel_up': [lambda f, r, d, a: f == _F.LOWRES_WHEEL and r == 0 and signed(d[0:1]) > 0 and signed(d[0:1]), False], - 'lowres_wheel_down': [lambda f, r, d, a: f == _F.LOWRES_WHEEL and r == 0 and signed(d[0:1]) < 0 and signed(d[0:1]), False], - 'hires_wheel_up': [lambda f, r, d, a: f == _F.HIRES_WHEEL and r == 0 and signed(d[1:3]) > 0 and signed(d[1:3]), False], - 'hires_wheel_down': [lambda f, r, d, a: f == _F.HIRES_WHEEL and r == 0 and signed(d[1:3]) < 0 and signed(d[1:3]), False], - 'charging': [charging, False], - 'False': [lambda f, r, d, a: False, False], - 'True': [lambda f, r, d, a: True, False], + "crown_right": [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[1] < 128 and d[1], False], + "crown_left": [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[1] >= 128 and 256 - d[1], False], + "crown_right_ratchet": [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[2] < 128 and d[2], False], + "crown_left_ratchet": [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[2] >= 128 and 256 - d[2], False], + "crown_tap": [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[5] == 0x01 and d[5], False], + "crown_start_press": [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] == 0x01 and d[6], False], + "crown_end_press": [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] == 0x05 and d[6], False], + "crown_pressed": [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] >= 0x01 and d[6] <= 0x04 and d[6], False], + "thumb_wheel_up": [thumb_wheel_up, True], + "thumb_wheel_down": [thumb_wheel_down, True], + "lowres_wheel_up": [lambda f, r, d, a: f == _F.LOWRES_WHEEL and r == 0 and signed(d[0:1]) > 0 and signed(d[0:1]), False], + "lowres_wheel_down": [lambda f, r, d, a: f == _F.LOWRES_WHEEL and r == 0 and signed(d[0:1]) < 0 and signed(d[0:1]), False], + "hires_wheel_up": [lambda f, r, d, a: f == _F.HIRES_WHEEL and r == 0 and signed(d[1:3]) > 0 and signed(d[1:3]), False], + "hires_wheel_down": [lambda f, r, d, a: f == _F.HIRES_WHEEL and r == 0 and signed(d[1:3]) < 0 and signed(d[1:3]), False], + "charging": [charging, False], + "False": [lambda f, r, d, a: False, False], + "True": [lambda f, r, d, a: True, False], } MOUSE_GESTURE_TESTS = { - 'mouse-down': ['Mouse Down'], - 'mouse-up': ['Mouse Up'], - 'mouse-left': ['Mouse Left'], - 'mouse-right': ['Mouse Right'], - 'mouse-noop': [], + "mouse-down": ["Mouse Down"], + "mouse-up": ["Mouse Up"], + "mouse-left": ["Mouse Left"], + "mouse-right": ["Mouse Right"], + "mouse-noop": [], } COMPONENTS = {} class RuleComponent: - def compile(self, c): if isinstance(c, RuleComponent): return c @@ -464,23 +484,22 @@ def compile(self, c): k, v = next(iter(c.items())) if k in COMPONENTS: return COMPONENTS[k](v) - logger.warning('illegal component in rule: %s', c) + logger.warning("illegal component in rule: %s", c) return Condition() class Rule(RuleComponent): - def __init__(self, args, source=None, warn=True): self.components = [self.compile(a) for a in args] self.source = source def __str__(self): - source = '(' + self.source + ')' if self.source else '' - return 'Rule%s[%s]' % (source, ', '.join([c.__str__() for c in self.components])) + source = "(" + self.source + ")" if self.source else "" + return "Rule%s[%s]" % (source, ", ".join([c.__str__() for c in self.components])) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate rule: %s', self) + logger.debug("evaluate rule: %s", self) result = True for component in self.components: result = component.evaluate(feature, notification, device, status, result) @@ -495,25 +514,23 @@ def once(self, feature, notification, device, status, last_result): return False def data(self): - return {'Rule': [c.data() for c in self.components]} + return {"Rule": [c.data() for c in self.components]} class Condition(RuleComponent): - def __init__(self, *args): pass def __str__(self): - return 'CONDITION' + return "CONDITION" def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) return False class Not(Condition): - def __init__(self, op, warn=True): if isinstance(op, list) and len(op) == 1: op = op[0] @@ -521,29 +538,28 @@ def __init__(self, op, warn=True): self.component = self.compile(op) def __str__(self): - return 'Not: ' + str(self.component) + return "Not: " + str(self.component) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) result = self.component.evaluate(feature, notification, device, status, last_result) return None if result is None else not result def data(self): - return {'Not': self.component.data()} + return {"Not": self.component.data()} class Or(Condition): - def __init__(self, args, warn=True): self.components = [self.compile(a) for a in args] def __str__(self): - return 'Or: [' + ', '.join(str(c) for c in self.components) + ']' + return "Or: [" + ", ".join(str(c) for c in self.components) + "]" def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) result = False for component in self.components: result = component.evaluate(feature, notification, device, status, last_result) @@ -554,20 +570,19 @@ def evaluate(self, feature, notification, device, status, last_result): return result def data(self): - return {'Or': [c.data() for c in self.components]} + return {"Or": [c.data() for c in self.components]} class And(Condition): - def __init__(self, args, warn=True): self.components = [self.compile(a) for a in args] def __str__(self): - return 'And: [' + ', '.join(str(c) for c in self.components) + ']' + return "And: [" + ", ".join(str(c) for c in self.components) + "]" def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) result = True for component in self.components: result = component.evaluate(feature, notification, device, status, last_result) @@ -578,7 +593,7 @@ def evaluate(self, feature, notification, device, status, last_result): return result def data(self): - return {'And': [c.data() for c in self.components]} + return {"And": [c.data() for c in self.components]} def x11_focus_prog(): @@ -593,10 +608,10 @@ def x11_focus_prog(): break window = window.query_tree().parent try: - name = psutil.Process(pid.value[0]).name() if pid else '' + name = psutil.Process(pid.value[0]).name() if pid else "" except Exception: - name = '' - return (wm_class[0], wm_class[1], name) if wm_class else (name, ) + name = "" + return (wm_class[0], wm_class[1], name) if wm_class else (name,) def x11_pointer_prog(): @@ -609,45 +624,44 @@ def x11_pointer_prog(): wm_class = child.get_wm_class() if wm_class: break - name = psutil.Process(pid.value[0]).name() if pid else '' - return (wm_class[0], wm_class[1], name) if wm_class else (name, ) + name = psutil.Process(pid.value[0]).name() if pid else "" + return (wm_class[0], wm_class[1], name) if wm_class else (name,) def gnome_dbus_focus_prog(): if not gnome_dbus_interface_setup(): return None wm_class = _dbus_interface.ActiveWindow() - return (wm_class, ) if wm_class else None + return (wm_class,) if wm_class else None def gnome_dbus_pointer_prog(): if not gnome_dbus_interface_setup(): return None wm_class = _dbus_interface.PointerOverWindow() - return (wm_class, ) if wm_class else None + return (wm_class,) if wm_class else None class Process(Condition): - def __init__(self, process, warn=True): self.process = process if (not wayland and not x11_setup()) or (wayland and not gnome_dbus_interface_setup()): if warn: logger.warning( - 'rules can only access active process in X11 or in Wayland under GNOME with Solaar Gnome extension - %s', - self + "rules can only access active process in X11 or in Wayland under GNOME with Solaar Gnome extension - %s", + self, ) if not isinstance(process, str): if warn: - logger.warning('rule Process argument not a string: %s', process) + logger.warning("rule Process argument not a string: %s", process) self.process = str(process) def __str__(self): - return 'Process: ' + str(self.process) + return "Process: " + str(self.process) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) if not isinstance(self.process, str): return False focus = x11_focus_prog() if not wayland else gnome_dbus_focus_prog() @@ -655,30 +669,30 @@ def evaluate(self, feature, notification, device, status, last_result): return result def data(self): - return {'Process': str(self.process)} + return {"Process": str(self.process)} class MouseProcess(Condition): - def __init__(self, process, warn=True): self.process = process if (not wayland and not x11_setup()) or (wayland and not gnome_dbus_interface_setup()): if warn: logger.warning( - 'rules cannot access active mouse process ' - 'in X11 or in Wayland under GNOME with Solaar Extension for GNOME - %s', self + "rules cannot access active mouse process " + "in X11 or in Wayland under GNOME with Solaar Extension for GNOME - %s", + self, ) if not isinstance(process, str): if warn: - logger.warning('rule MouseProcess argument not a string: %s', process) + logger.warning("rule MouseProcess argument not a string: %s", process) self.process = str(process) def __str__(self): - return 'MouseProcess: ' + str(self.process) + return "MouseProcess: " + str(self.process) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) if not isinstance(self.process, str): return False pointer_focus = x11_pointer_prog() if not wayland else gnome_dbus_pointer_prog() @@ -686,103 +700,99 @@ def evaluate(self, feature, notification, device, status, last_result): return result def data(self): - return {'MouseProcess': str(self.process)} + return {"MouseProcess": str(self.process)} class Feature(Condition): - def __init__(self, feature, warn=True): if not (isinstance(feature, str) and feature in _F): if warn: - logger.warning('rule Feature argument not name of a feature: %s', feature) + logger.warning("rule Feature argument not name of a feature: %s", feature) self.feature = None self.feature = _F[feature] def __str__(self): - return 'Feature: ' + str(self.feature) + return "Feature: " + str(self.feature) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) return feature == self.feature def data(self): - return {'Feature': str(self.feature)} + return {"Feature": str(self.feature)} class Report(Condition): - def __init__(self, report, warn=True): if not (isinstance(report, int)): if warn: - logger.warning('rule Report argument not an integer: %s', report) + logger.warning("rule Report argument not an integer: %s", report) self.report = -1 else: self.report = report def __str__(self): - return 'Report: ' + str(self.report) + return "Report: " + str(self.report) def evaluate(self, report, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) return (notification.address >> 4) == self.report def data(self): - return {'Report': self.report} + return {"Report": self.report} # Setting(device, setting, [key], value...) class Setting(Condition): - def __init__(self, args, warn=True): if not (isinstance(args, list) and len(args) > 2): if warn: - logger.warning('rule Setting argument not list with minimum length 3: %s', args) + logger.warning("rule Setting argument not list with minimum length 3: %s", args) self.args = [] else: self.args = args def __str__(self): - return 'Setting: ' + ' '.join([str(a) for a in self.args]) + return "Setting: " + " ".join([str(a) for a in self.args]) def evaluate(self, report, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + 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 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 setting = next((s for s in dev.settings if s.name == self.args[1]), None) if setting is None: - logger.warning('Setting condition: setting %s is not the name of a setting for %s', self.args[1], dev.name) + logger.warning("Setting condition: setting %s is not the name of a setting for %s", self.args[1], dev.name) return None # should the value argument be checked to be sure it is acceptable?? needs to be careful about boolean toggle # TODO add compare methods for more validators try: result = setting.compare(self.args[2:], setting.read()) except Exception as e: - logger.warning('Setting condition: error when checking setting %s: %s', self.args, e) + logger.warning("Setting condition: error when checking setting %s: %s", self.args, e) result = False return result def data(self): - return {'Setting': self.args[:]} + return {"Setting": self.args[:]} MODIFIERS = { - 'Shift': int(Gdk.ModifierType.SHIFT_MASK), - 'Control': int(Gdk.ModifierType.CONTROL_MASK), - 'Alt': int(Gdk.ModifierType.MOD1_MASK), - 'Super': int(Gdk.ModifierType.MOD4_MASK) + "Shift": int(Gdk.ModifierType.SHIFT_MASK), + "Control": int(Gdk.ModifierType.CONTROL_MASK), + "Alt": int(Gdk.ModifierType.MOD1_MASK), + "Super": int(Gdk.ModifierType.MOD4_MASK), } -MODIFIER_MASK = MODIFIERS['Shift'] + MODIFIERS['Control'] + MODIFIERS['Alt'] + MODIFIERS['Super'] +MODIFIER_MASK = MODIFIERS["Shift"] + MODIFIERS["Control"] + MODIFIERS["Alt"] + MODIFIERS["Super"] class Modifiers(Condition): - def __init__(self, modifiers, warn=True): modifiers = [modifiers] if isinstance(modifiers, str) else modifiers self.desired = 0 @@ -793,28 +803,28 @@ def __init__(self, modifiers, warn=True): self.modifiers.append(k) else: if warn: - logger.warning('unknown rule Modifier value: %s', k) + logger.warning("unknown rule Modifier value: %s", k) def __str__(self): - return 'Modifiers: ' + str(self.desired) + return "Modifiers: " + str(self.desired) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) if gkeymap: current = gkeymap.get_modifier_state() # get the current keyboard modifier return self.desired == (current & MODIFIER_MASK) else: - logger.warning('no keymap so cannot determine modifier keys') + logger.warning("no keymap so cannot determine modifier keys") return False def data(self): - return {'Modifiers': [str(m) for m in self.modifiers]} + return {"Modifiers": [str(m) for m in self.modifiers]} class Key(Condition): - DOWN = 'pressed' - UP = 'released' + DOWN = "pressed" + UP = "released" def __init__(self, args, warn=True): default_key = 0 @@ -824,7 +834,7 @@ def __init__(self, args, warn=True): if not args or not isinstance(args, (list, str)): if warn: - logger.warning('rule Key arguments unknown: %s' % args) + logger.warning("rule Key arguments unknown: %s" % args) key = default_key action = default_action elif isinstance(args, str): @@ -842,30 +852,29 @@ def __init__(self, args, warn=True): self.key = _CONTROL[key] else: if warn: - logger.warning('rule Key key name not name of a Logitech key: %s' % key) + logger.warning("rule Key key name not name of a Logitech key: %s" % key) self.key = default_key if isinstance(action, str) and action in (self.DOWN, self.UP): self.action = action else: if warn: - logger.warning('rule Key action unknown: %s, assuming %s' % (action, default_action)) + logger.warning("rule Key action unknown: %s, assuming %s" % (action, default_action)) self.action = default_action def __str__(self): - return 'Key: %s (%s)' % ((str(self.key) if self.key else 'None'), self.action) + return "Key: %s (%s)" % ((str(self.key) if self.key else "None"), self.action) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) return bool(self.key and self.key == (key_down if self.action == self.DOWN else key_up)) def data(self): - return {'Key': [str(self.key), self.action]} + return {"Key": [str(self.key), self.action]} class KeyIsDown(Condition): - def __init__(self, args, warn=True): default_key = 0 @@ -873,7 +882,7 @@ def __init__(self, args, warn=True): if not args or not isinstance(args, str): if warn: - logger.warning('rule KeyDown arguments unknown: %s' % args) + logger.warning("rule KeyDown arguments unknown: %s" % args) key = default_key elif isinstance(args, str): key = args @@ -882,50 +891,48 @@ def __init__(self, args, warn=True): self.key = _CONTROL[key] else: if warn: - logger.warning('rule Key key name not name of a Logitech key: %s' % key) + logger.warning("rule Key key name not name of a Logitech key: %s" % key) self.key = default_key def __str__(self): - return 'KeyIsDown: %s' % (str(self.key) if self.key else 'None') + return "KeyIsDown: %s" % (str(self.key) if self.key else "None") def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) return key_is_down(self.key) def data(self): - return {'KeyIsDown': str(self.key)} + return {"KeyIsDown": str(self.key)} def bit_test(start, end, bits): - return lambda f, r, d: int.from_bytes(d[start:end], byteorder='big', signed=True) & bits + return lambda f, r, d: int.from_bytes(d[start:end], byteorder="big", signed=True) & bits def range_test(start, end, min, max): - def range_test_helper(f, r, d): - value = int.from_bytes(d[start:end], byteorder='big', signed=True) + value = int.from_bytes(d[start:end], byteorder="big", signed=True) return min <= value <= max and (value if value else True) return range_test_helper class Test(Condition): - def __init__(self, test, warn=True): - self.test = '' + self.test = "" self.parameter = None if isinstance(test, str): test = [test] if isinstance(test, list) and all(isinstance(t, int) for t in test): if warn: - logger.warning('Test rules consisting of numbers are deprecated, converting to a TestBytes condition') + logger.warning("Test rules consisting of numbers are deprecated, converting to a TestBytes condition") self.__class__ = TestBytes self.__init__(test, warn=warn) elif isinstance(test, list): if test[0] in MOUSE_GESTURE_TESTS: if warn: - logger.warning('mouse movement test %s deprecated, converting to a MouseGesture', test) + logger.warning("mouse movement test %s deprecated, converting to a MouseGesture", test) self.__class__ = MouseGesture self.__init__(MOUSE_GESTURE_TESTS[0][test], warn=warn) elif test[0] in TESTS: @@ -934,54 +941,65 @@ def __init__(self, test, warn=True): self.parameter = test[1] if len(test) > 1 else None else: if warn: - logger.warning('rule Test name not name of a test: %s', test) - self.test = 'False' - self.function = TESTS['False'][0] + logger.warning("rule Test name not name of a test: %s", test) + self.test = "False" + self.function = TESTS["False"][0] else: if warn: - logger.warning('rule Test argument not valid %s', test) + logger.warning("rule Test argument not valid %s", test) def __str__(self): - return 'Test: ' + str(self.test) + return "Test: " + str(self.test) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) return self.function(feature, notification.address, notification.data, self.parameter) def data(self): - return {'Test': ([self.test, self.parameter] if self.parameter is not None else [self.test])} + return {"Test": ([self.test, self.parameter] if self.parameter is not None else [self.test])} class TestBytes(Condition): - def __init__(self, test, warn=True): self.test = test if ( - isinstance(test, list) and 2 < len(test) <= 4 and all(isinstance(t, int) for t in test) and test[0] >= 0 - and test[0] <= 16 and test[1] >= 0 and test[1] <= 16 and test[0] < test[1] + isinstance(test, list) + and 2 < len(test) <= 4 + and all(isinstance(t, int) for t in test) + and test[0] >= 0 + and test[0] <= 16 + and test[1] >= 0 + and test[1] <= 16 + and test[0] < test[1] ): self.function = bit_test(*test) if len(test) == 3 else range_test(*test) else: if warn: - logger.warning('rule TestBytes argument not valid %s', test) + logger.warning("rule TestBytes argument not valid %s", test) def __str__(self): - return 'TestBytes: ' + str(self.test) + return "TestBytes: " + str(self.test) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) return self.function(feature, notification.address, notification.data) def data(self): - return {'TestBytes': self.test[:]} + return {"TestBytes": self.test[:]} class MouseGesture(Condition): MOVEMENTS = [ - 'Mouse Up', 'Mouse Down', 'Mouse Left', 'Mouse Right', 'Mouse Up-left', 'Mouse Up-right', 'Mouse Down-left', - 'Mouse Down-right' + "Mouse Up", + "Mouse Down", + "Mouse Left", + "Mouse Right", + "Mouse Up-left", + "Mouse Up-right", + "Mouse Down-left", + "Mouse Down-right", ] def __init__(self, movements, warn=True): @@ -990,18 +1008,18 @@ def __init__(self, movements, warn=True): for x in movements: if x not in self.MOVEMENTS and x not in _CONTROL: if warn: - logger.warning('rule Mouse Gesture argument not direction or name of a Logitech key: %s', x) + logger.warning("rule Mouse Gesture argument not direction or name of a Logitech key: %s", x) self.movements = movements def __str__(self): - return 'MouseGesture: ' + ' '.join(self.movements) + return "MouseGesture: " + " ".join(self.movements) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) if feature == _F.MOUSE_GESTURE: d = notification.data - data = _unpack('!' + (int(len(d) / 2) * 'h'), d) + data = _unpack("!" + (int(len(d) / 2) * "h"), d) data_offset = 1 movement_offset = 0 if self.movements and self.movements[0] not in self.MOVEMENTS: # matching against initiating key @@ -1024,76 +1042,72 @@ def evaluate(self, feature, notification, device, status, last_result): return False def data(self): - return {'MouseGesture': [str(m) for m in self.movements]} + return {"MouseGesture": [str(m) for m in self.movements]} class Active(Condition): - def __init__(self, devID, warn=True): if not (isinstance(devID, str)): if warn: - logger.warning('rule Active argument not a string: %s', devID) - self.devID = '' + logger.warning("rule Active argument not a string: %s", devID) + self.devID = "" self.devID = devID def __str__(self): - return 'Active: ' + str(self.devID) + return "Active: " + str(self.devID) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) dev = device.find(self.devID) return bool(dev and dev.ping()) def data(self): - return {'Active': self.devID} + return {"Active": self.devID} class Device(Condition): - def __init__(self, devID, warn=True): if not (isinstance(devID, str)): if warn: - logger.warning('rule Device argument not a string: %s', devID) - self.devID = '' + logger.warning("rule Device argument not a string: %s", devID) + self.devID = "" self.devID = devID def __str__(self): - return 'Device: ' + str(self.devID) + return "Device: " + str(self.devID) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) return device.unitId == self.devID or device.serial == self.devID def data(self): - return {'Device': self.devID} + return {"Device": self.devID} class Host(Condition): - def __init__(self, host, warn=True): if not (isinstance(host, str)): if warn: - logger.warning('rule Host Name argument not a string: %s', host) - self.host = '' + logger.warning("rule Host Name argument not a string: %s", host) + self.host = "" self.host = host def __str__(self): - return 'Host: ' + str(self.host) + return "Host: " + str(self.host) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluate condition: %s', self) + logger.debug("evaluate condition: %s", self) hostname = socket.getfqdn() return hostname.startswith(self.host) def data(self): - return {'Host': self.host} + return {"Host": self.host} class Action(RuleComponent): - def __init__(self, *args): pass @@ -1102,18 +1116,17 @@ def evaluate(self, feature, notification, device, status, last_result): class KeyPress(Action): - def __init__(self, args, warn=True): self.key_names, self.action = self.regularize_args(args) if not isinstance(self.key_names, list): if warn: - logger.warning('rule KeyPress keys not key names %s', self.keys_names) + logger.warning("rule KeyPress keys not key names %s", self.keys_names) self.key_symbols = [] else: self.key_symbols = [XK_KEYS.get(k, None) for k in self.key_names] if not all(self.key_symbols): if warn: - logger.warning('rule KeyPress keys not key names %s', self.key_names) + logger.warning("rule KeyPress keys not key names %s", self.key_names) self.key_symbols = [] def regularize_args(self, args): @@ -1146,7 +1159,7 @@ def keysym_to_keycode(self, keysym, modifiers): # maybe should take shift into return (keycode, level) def __str__(self): - return 'KeyPress: ' + ' '.join(self.key_names) + ' ' + self.action + return "KeyPress: " + " ".join(self.key_names) + " " + self.action def needed(self, k, modifiers): code = modifier_code(k) @@ -1154,11 +1167,11 @@ def needed(self, k, modifiers): def mods(self, level, modifiers, direction): if level == 2 or level == 3: - (sk, _) = self.keysym_to_keycode(XK_KEYS.get('ISO_Level3_Shift', None), modifiers) + (sk, _) = self.keysym_to_keycode(XK_KEYS.get("ISO_Level3_Shift", None), modifiers) if sk and self.needed(sk, modifiers): simulate_key(sk, direction) if level == 1 or level == 3: - (sk, _) = self.keysym_to_keycode(XK_KEYS.get('Shift_L', None), modifiers) + (sk, _) = self.keysym_to_keycode(XK_KEYS.get("Shift_L", None), modifiers) if sk and self.needed(sk, modifiers): simulate_key(sk, direction) @@ -1166,7 +1179,7 @@ def keyDown(self, keysyms, modifiers): for k in keysyms: (keycode, level) = self.keysym_to_keycode(k, modifiers) if keycode is None: - logger.warning('rule KeyPress key symbol not currently available %s', self) + logger.warning("rule KeyPress key symbol not currently available %s", self) elif self.action != CLICK or self.needed(keycode, modifiers): # only check needed when clicking self.mods(level, modifiers, _KEY_PRESS) simulate_key(keycode, _KEY_PRESS) @@ -1182,18 +1195,18 @@ def evaluate(self, feature, notification, device, status, last_result): if gkeymap: current = gkeymap.get_modifier_state() if logger.isEnabledFor(logging.INFO): - logger.info('KeyPress action: %s %s, group %s, modifiers %s', self.key_names, self.action, kbdgroup(), current) + logger.info("KeyPress action: %s %s, group %s, modifiers %s", self.key_names, self.action, kbdgroup(), current) if self.action != RELEASE: self.keyDown(self.key_symbols, current) if self.action != DEPRESS: self.keyUp(reversed(self.key_symbols), current) _time.sleep(0.01) else: - logger.warning('no keymap so cannot determine which keycode to send') + logger.warning("no keymap so cannot determine which keycode to send") return None def data(self): - return {'KeyPress': [[str(k) for k in self.key_names], self.action]} + return {"KeyPress": [[str(k) for k in self.key_names], self.action]} # KeyDown is dangerous as the key can auto-repeat and make your system unusable @@ -1206,36 +1219,34 @@ def data(self): class MouseScroll(Action): - def __init__(self, amounts, warn=True): if len(amounts) == 1 and isinstance(amounts[0], list): amounts = amounts[0] if not (len(amounts) == 2 and all([isinstance(a, numbers.Number) for a in amounts])): if warn: - logger.warning('rule MouseScroll argument not two numbers %s', amounts) + logger.warning("rule MouseScroll argument not two numbers %s", amounts) amounts = [0, 0] self.amounts = amounts def __str__(self): - return 'MouseScroll: ' + ' '.join([str(a) for a in self.amounts]) + return "MouseScroll: " + " ".join([str(a) for a in self.amounts]) def evaluate(self, feature, notification, device, status, last_result): amounts = self.amounts if isinstance(last_result, numbers.Number): amounts = [math.floor(last_result * a) for a in self.amounts] if logger.isEnabledFor(logging.INFO): - logger.info('MouseScroll action: %s %s %s', self.amounts, last_result, amounts) + logger.info("MouseScroll action: %s %s %s", self.amounts, last_result, amounts) dx, dy = amounts simulate_scroll(dx, dy) _time.sleep(0.01) return None def data(self): - return {'MouseScroll': self.amounts[:]} + return {"MouseScroll": self.amounts[:]} class MouseClick(Action): - def __init__(self, args, warn=True): if len(args) == 1 and isinstance(args[0], list): args = args[0] @@ -1244,7 +1255,7 @@ def __init__(self, args, warn=True): self.button = str(args[0]) if len(args) >= 0 else None if self.button not in buttons: if warn: - logger.warning('rule MouseClick action: button %s not known', self.button) + logger.warning("rule MouseClick action: button %s not known", self.button) self.button = None count = args[1] if len(args) >= 2 else 1 try: @@ -1253,53 +1264,52 @@ def __init__(self, args, warn=True): if count in [CLICK, DEPRESS, RELEASE]: self.count = count elif warn: - logger.warning('rule MouseClick action: argument %s should be an integer or CLICK, PRESS, or RELEASE', count) + logger.warning("rule MouseClick action: argument %s should be an integer or CLICK, PRESS, or RELEASE", count) self.count = 1 def __str__(self): - return 'MouseClick: %s (%d)' % (self.button, self.count) + return "MouseClick: %s (%d)" % (self.button, self.count) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.INFO): - logger.info('MouseClick action: %d %s' % (self.count, self.button)) + logger.info("MouseClick action: %d %s" % (self.count, self.button)) if self.button and self.count: click(buttons[self.button], self.count) _time.sleep(0.01) return None def data(self): - return {'MouseClick': [self.button, self.count]} + return {"MouseClick": [self.button, self.count]} class Set(Action): - def __init__(self, args, warn=True): if not (isinstance(args, list) and len(args) > 2): if warn: - logger.warning('rule Set argument not list with minimum length 3: %s', args) + logger.warning("rule Set argument not list with minimum length 3: %s", args) self.args = [] else: self.args = args def __str__(self): - return 'Set: ' + ' '.join([str(a) for a in self.args]) + return "Set: " + " ".join([str(a) for a in self.args]) def evaluate(self, feature, notification, device, status, last_result): if len(self.args) < 3: return None 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 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 setting = next((s for s in dev.settings if s.name == self.args[1]), None) if setting is None: - logger.warning('Set action: setting %s is not the name of a setting for %s', self.args[1], dev.name) + logger.warning("Set action: setting %s is not the name of a setting for %s", self.args[1], dev.name) return None args = setting.acceptable(self.args[2:], setting.read()) if args is None: - logger.warning('Set Action: invalid args %s for setting %s of %s', self.args[2:], self.args[1], self.args[0]) + logger.warning("Set Action: invalid args %s for setting %s of %s", self.args[2:], self.args[1], self.args[0]) return None if len(args) > 1: setting.write_key_value(args[0], args[1]) @@ -1310,36 +1320,34 @@ def evaluate(self, feature, notification, device, status, last_result): return None def data(self): - return {'Set': self.args[:]} + return {"Set": self.args[:]} class Execute(Action): - def __init__(self, args, warn=True): if isinstance(args, str): args = [args] if not (isinstance(args, list) and all(isinstance(arg), str) for arg in args): if warn: - logger.warning('rule Execute argument not list of strings: %s', args) + logger.warning("rule Execute argument not list of strings: %s", args) self.args = [] else: self.args = args def __str__(self): - return 'Execute: ' + ' '.join([a for a in self.args]) + return "Execute: " + " ".join([a for a in self.args]) def evaluate(self, feature, notification, device, status, last_result): if logger.isEnabledFor(logging.INFO): - logger.info('Execute action: %s', self.args) + logger.info("Execute action: %s", self.args) subprocess.Popen(self.args) return None def data(self): - return {'Execute': self.args[:]} + return {"Execute": self.args[:]} class Later(Action): - def __init__(self, args, warn=True): self.delay = 0 self.rule = Rule([]) @@ -1348,17 +1356,17 @@ def __init__(self, args, warn=True): args = [args] if not (isinstance(args, list) and len(args) >= 1): if warn: - logger.warning('rule Later argument not list with minimum length 1: %s', args) + logger.warning("rule Later argument not list with minimum length 1: %s", args) elif not (isinstance(args[0], int)) or not 0 < args[0] < 101: if warn: - logger.warning('rule Later argument delay not integer between 1 and 100: %s', args) + logger.warning("rule Later argument delay not integer between 1 and 100: %s", args) else: self.delay = args[0] self.rule = Rule(args[1:], warn=warn) self.components = self.rule.components def __str__(self): - return 'Later: [' + str(self.delay) + ', ' + ', '.join(str(c) for c in self.components) + ']' + return "Later: [" + str(self.delay) + ", " + ", ".join(str(c) for c in self.components) + "]" def evaluate(self, feature, notification, device, status, last_result): if self.delay and self.rule: @@ -1368,60 +1376,64 @@ def evaluate(self, feature, notification, device, status, last_result): def data(self): data = [c.data() for c in self.components] data.insert(0, self.delay) - return {'Later': data} + return {"Later": data} COMPONENTS = { - 'Rule': Rule, - 'Not': Not, - 'Or': Or, - 'And': And, - 'Process': Process, - 'MouseProcess': MouseProcess, - 'Feature': Feature, - 'Report': Report, - 'Setting': Setting, - 'Modifiers': Modifiers, - 'Key': Key, - 'KeyIsDown': KeyIsDown, - 'Test': Test, - 'TestBytes': TestBytes, - 'MouseGesture': MouseGesture, - 'Active': Active, - 'Device': Device, - 'Host': Host, - 'KeyPress': KeyPress, - 'MouseScroll': MouseScroll, - 'MouseClick': MouseClick, - 'Set': Set, - 'Execute': Execute, - 'Later': Later, + "Rule": Rule, + "Not": Not, + "Or": Or, + "And": And, + "Process": Process, + "MouseProcess": MouseProcess, + "Feature": Feature, + "Report": Report, + "Setting": Setting, + "Modifiers": Modifiers, + "Key": Key, + "KeyIsDown": KeyIsDown, + "Test": Test, + "TestBytes": TestBytes, + "MouseGesture": MouseGesture, + "Active": Active, + "Device": Device, + "Host": Host, + "KeyPress": KeyPress, + "MouseScroll": MouseScroll, + "MouseClick": MouseClick, + "Set": Set, + "Execute": Execute, + "Later": Later, } built_in_rules = Rule([]) if True: - built_in_rules = Rule([ - {'Rule': [ # Implement problematic keys for Craft and MX Master - {'Rule': [{'Key': ['Brightness Down', 'pressed']}, {'KeyPress': 'XF86_MonBrightnessDown'}]}, - {'Rule': [{'Key': ['Brightness Up', 'pressed']}, {'KeyPress': 'XF86_MonBrightnessUp'}]}, - ]}, - # {'Rule': [ # In firefox, crown emits keys that move up and down if not pressed, rotate through tabs otherwise - # {'Process': 'firefox'}, - # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': ['Control_R', 'Tab']}]}, - # {'Rule': [{'Test': 'crown_pressed'}, - # {'Test': 'crown_left_ratchet'}, - # {'KeyPress': ['Control_R', 'Shift_R', 'Tab']}]}, - # {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'Down'}]}, - # {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'Up'}]}, - # ]}, - # {'Rule': [ # Otherwise, crown movements emit keys that modify volume if not pressed, move between tracks otherwise - # {'Feature': 'CROWN'}, {'Report': 0x0}, - # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioNext'}]}, - # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioPrev'}]}, - # {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioRaiseVolume'}]}, - # {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioLowerVolume'}]} - # ]}, - ]) + built_in_rules = Rule( + [ + { + "Rule": [ # Implement problematic keys for Craft and MX Master + {"Rule": [{"Key": ["Brightness Down", "pressed"]}, {"KeyPress": "XF86_MonBrightnessDown"}]}, + {"Rule": [{"Key": ["Brightness Up", "pressed"]}, {"KeyPress": "XF86_MonBrightnessUp"}]}, + ] + }, + # {'Rule': [ # In firefox, crown emits keys that move up and down if not pressed, rotate through tabs otherwise + # {'Process': 'firefox'}, + # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': ['Control_R', 'Tab']}]}, + # {'Rule': [{'Test': 'crown_pressed'}, + # {'Test': 'crown_left_ratchet'}, + # {'KeyPress': ['Control_R', 'Shift_R', 'Tab']}]}, + # {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'Down'}]}, + # {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'Up'}]}, + # ]}, + # {'Rule': [ # Otherwise, crown movements emit keys that modify volume if not pressed, move between tracks otherwise + # {'Feature': 'CROWN'}, {'Report': 0x0}, + # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioNext'}]}, + # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioPrev'}]}, + # {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioRaiseVolume'}]}, + # {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioLowerVolume'}]} + # ]}, + ] + ) keys_down = [] g_keys_down = 0 @@ -1443,7 +1455,7 @@ def key_is_down(key): def evaluate_rules(feature, notification, device, status): if logger.isEnabledFor(logging.DEBUG): - logger.debug('evaluating rules on %s', notification) + logger.debug("evaluating rules on %s", notification) rules.evaluate(feature, notification, device, status, True) @@ -1453,7 +1465,7 @@ def process_notification(device, status, notification, feature): key_down, key_up = None, None # need to keep track of keys that are down to find a new key down if feature == _F.REPROG_CONTROLS_V4 and notification.address == 0x00: - new_keys_down = _unpack('!4H', notification.data[:8]) + new_keys_down = _unpack("!4H", notification.data[:8]) for key in new_keys_down: if key and key not in keys_down: key_down = key @@ -1463,29 +1475,29 @@ def process_notification(device, status, notification, feature): keys_down = new_keys_down # and also G keys down elif feature == _F.GKEY and notification.address == 0x00: - new_g_keys_down = _unpack('> 1) - v2 |= (v2 >> 1) + v1 |= v1 >> 1 + v2 |= v2 >> 1 elif charging: # blink all green v1, v2 = 0x30, 0x33 diff --git a/lib/logitech_receiver/hidpp10_constants.py b/lib/logitech_receiver/hidpp10_constants.py index eb1c3ad807..46950634d4 100644 --- a/lib/logitech_receiver/hidpp10_constants.py +++ b/lib/logitech_receiver/hidpp10_constants.py @@ -16,7 +16,7 @@ touchpad=0x09, headset=0x0D, # not from Logitech documentation remote_control=0x0E, # for compatibility with HID++ 2.0 - receiver=0x0F # for compatibility with HID++ 2.0 + receiver=0x0F, # for compatibility with HID++ 2.0 ) POWER_SWITCH_LOCATION = NamedInts( @@ -30,7 +30,7 @@ top_edge=0x09, right_edge=0x0A, left_edge=0x0B, - bottom_edge=0x0C + bottom_edge=0x0C, ) # Some flags are used both by devices and receivers. The Logitech documentation @@ -79,7 +79,7 @@ resource_error=0x09, request_unavailable=0x0A, unsupported_parameter_value=0x0B, - wrong_pin_code=0x0C + wrong_pin_code=0x0C, ) PAIRING_ERRORS = NamedInts(device_timeout=0x01, device_not_supported=0x02, too_many_devices=0x03, sequence_timeout=0x06) @@ -96,7 +96,6 @@ bolt_device_discovery=0xC0, bolt_pairing=0x2C1, bolt_uniqueId=0x02FB, - # only apply to devices mouse_button_flags=0x01, keyboard_hand_detection=0x01, @@ -106,11 +105,9 @@ keyboard_illumination=0x17, three_leds=0x51, mouse_dpi=0x63, - # apply to both notifications=0x00, firmware=0xF1, - # notifications passkey_request_notification=0x4D, passkey_pressed_notification=0x4E, diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index c95c082f4a..81c2b15fc2 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -37,9 +37,8 @@ from .common import bytes2int as _bytes2int from .common import crc16 as _crc16 from .common import int2bytes as _int2bytes -from .hidpp20_constants import BATTERY_STATUS, CHARGE_LEVEL, CHARGE_STATUS, CHARGE_TYPE, DEVICE_KIND, ERROR, FEATURE +from .hidpp20_constants import BATTERY_STATUS, CHARGE_LEVEL, CHARGE_STATUS, CHARGE_TYPE, DEVICE_KIND, ERROR, FEATURE, GESTURE from .hidpp20_constants import FIRMWARE_KIND as _FIRMWARE_KIND -from .hidpp20_constants import GESTURE from .i18n import _ logger = logging.getLogger(__name__) @@ -57,7 +56,6 @@ def hexint_presenter(dumper, data): class FeaturesArray(dict): - def __init__(self, device): assert device is not None self.supported = True # Actually don't know whether it is supported yet @@ -76,13 +74,13 @@ def _check(self) -> bool: return False if self.count > 0: return True - reply = self.device.request(0x0000, _pack('!H', FEATURE.FEATURE_SET)) + reply = self.device.request(0x0000, _pack("!H", FEATURE.FEATURE_SET)) if reply is not None: fs_index = reply[0] if fs_index: count = self.device.request(fs_index << 8) if count is None: - logger.warn('FEATURE_SET found, but failed to read features count') + logger.warn("FEATURE_SET found, but failed to read features count") return False else: self.count = count[0] + 1 # ROOT feature not included in count @@ -100,7 +98,7 @@ def get_feature(self, index: int) -> Optional[_NamedInt]: elif self._check(): response = self.device.feature_request(FEATURE.FEATURE_SET, 0x10, index) if response: - feature = FEATURE[_unpack('!H', response[:2])[0]] + feature = FEATURE[_unpack("!H", response[:2])[0]] self[feature] = index self.version[feature] = response[3] return feature @@ -124,7 +122,7 @@ def __getitem__(self, feature: _NamedInt) -> Optional[int]: if index is not None: return index elif self._check(): - response = self.device.request(0x0000, _pack('!H', feature)) + response = self.device.request(0x0000, _pack("!H", feature)) if response: index = response[0] self[feature] = index if index else False @@ -261,26 +259,26 @@ def remap(self, to: _NamedInt): def _getCidReporting(self): try: - mapped_data = feature_request(self._device, FEATURE.REPROG_CONTROLS_V4, 0x20, *tuple(_pack('!H', self._cid))) + mapped_data = feature_request(self._device, FEATURE.REPROG_CONTROLS_V4, 0x20, *tuple(_pack("!H", self._cid))) if mapped_data: - cid, mapping_flags_1, mapped_to = _unpack('!HBH', mapped_data[:5]) + cid, mapping_flags_1, mapped_to = _unpack("!HBH", mapped_data[:5]) if cid != self._cid and logger.isEnabledFor(logging.WARNING): logger.warn( - f'REPROG_CONTROLS_V4 endpoint getCidReporting on device {self._device} replied ' + - f'with a different control ID ({cid}) than requested ({self._cid}).' + f"REPROG_CONTROLS_V4 endpoint getCidReporting on device {self._device} replied " + + f"with a different control ID ({cid}) than requested ({self._cid})." ) self._mapped_to = mapped_to if mapped_to != 0 else self._cid if len(mapped_data) > 5: - mapping_flags_2, = _unpack('!B', mapped_data[5:6]) + (mapping_flags_2,) = _unpack("!B", mapped_data[5:6]) else: mapping_flags_2 = 0 self._mapping_flags = mapping_flags_1 | (mapping_flags_2 << 8) else: - raise exceptions.FeatureCallError(msg='No reply from device.') + raise exceptions.FeatureCallError(msg="No reply from device.") except exceptions.FeatureCallError: # if the key hasn't ever been configured only produce a warning if logger.isEnabledFor(logging.WARNING): logger.warn( - f'Feature Call Error in _getCidReporting on device {self._device} for cid {self._cid} - use defaults' + f"Feature Call Error in _getCidReporting on device {self._device} for cid {self._cid} - use defaults" ) # Clear flags and set mapping target to self self._mapping_flags = 0 @@ -306,15 +304,15 @@ def _setCidReporting(self, flags=None, remap=0): special_keys.MAPPING_FLAG.persistently_diverted: special_keys.KEY_FLAG.persistently_divertable, special_keys.MAPPING_FLAG.analytics_key_events_reporting: special_keys.KEY_FLAG.analytics_key_events, special_keys.MAPPING_FLAG.force_raw_XY_diverted: special_keys.KEY_FLAG.force_raw_XY, - special_keys.MAPPING_FLAG.raw_XY_diverted: special_keys.KEY_FLAG.raw_XY + special_keys.MAPPING_FLAG.raw_XY_diverted: special_keys.KEY_FLAG.raw_XY, } bfield = 0 for f, v in flags.items(): if v and FLAG_TO_CAPABILITY[f] not in self.flags: raise exceptions.FeatureNotSupported( - msg=f'Tried to set mapping flag "{f}" on control "{self.key}" ' + - f'which does not support "{FLAG_TO_CAPABILITY[f]}" on device {self._device}.' + msg=f'Tried to set mapping flag "{f}" on control "{self.key}" ' + + f'which does not support "{FLAG_TO_CAPABILITY[f]}" on device {self._device}.' ) bfield |= int(f) if v else 0 bfield |= int(f) << 1 # The 'Xvalid' bit @@ -326,22 +324,21 @@ def _setCidReporting(self, flags=None, remap=0): if remap != 0 and remap not in self.remappable_to: raise exceptions.FeatureNotSupported( - msg=f'Tried to remap control "{self.key}" to a control ID {remap} which it is not remappable to ' + - f'on device {self._device}.' + msg=f'Tried to remap control "{self.key}" to a control ID {remap} which it is not remappable to ' + + f"on device {self._device}." ) if remap != 0: # update mapping if changing (even if not already read) self._mapped_to = remap - pkt = tuple(_pack('!HBH', self._cid, bfield & 0xff, remap)) + pkt = tuple(_pack("!HBH", self._cid, bfield & 0xFF, remap)) # TODO: to fully support version 4 of REPROG_CONTROLS_V4, append `(bfield >> 8) & 0xff` here. # But older devices might behave oddly given that byte, so we don't send it. ret = feature_request(self._device, FEATURE.REPROG_CONTROLS_V4, 0x30, *pkt) - if ret is None or _unpack('!BBBBB', ret[:5]) != pkt and logger.isEnabledFor(logging.DEBUG): + if ret is None or _unpack("!BBBBB", ret[:5]) != pkt and logger.isEnabledFor(logging.DEBUG): logger.debug(f"REPROG_CONTROLS_v4 setCidReporting on device {self._device} didn't echo request packet.") -class PersistentRemappableAction(): - +class PersistentRemappableAction: def __init__(self, device, index, cid, actionId, remapped, modifierMask, cidStatus): self._device = device self.index = index @@ -364,25 +361,25 @@ def action(self): if self.actionId == special_keys.ACTIONID.Empty: return None elif self.actionId == special_keys.ACTIONID.Key: - return 'Key: ' + str(self.modifiers) + str(self.remapped) + return "Key: " + str(self.modifiers) + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Mouse: - return 'Mouse Button: ' + str(self.remapped) + return "Mouse Button: " + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Xdisp: - return 'X Displacement ' + str(self.remapped) + return "X Displacement " + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Ydisp: - return 'Y Displacement ' + str(self.remapped) + return "Y Displacement " + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Vscroll: - return 'Vertical Scroll ' + str(self.remapped) + return "Vertical Scroll " + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Hscroll: - return 'Horizontal Scroll: ' + str(self.remapped) + return "Horizontal Scroll: " + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Consumer: - return 'Consumer: ' + str(self.remapped) + return "Consumer: " + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Internal: - return 'Internal Action ' + str(self.remapped) + return "Internal Action " + str(self.remapped) elif self.actionId == special_keys.ACTIONID.Internal: - return 'Power ' + str(self.remapped) + return "Power " + str(self.remapped) else: - return 'Unknown' + return "Unknown" @property def modifiers(self): @@ -399,7 +396,7 @@ def remap(self, data_bytes): self._device.remap_keys._query_key(self.index) return self._device.remap_keys.keys[self.index].data_bytes else: - self._actionId, self._code, self._modifierMask = _unpack('!BHB', data_bytes) + self._actionId, self._code, self._modifierMask = _unpack("!BHB", data_bytes) self.cidStatus = 0x01 feature_request(self._device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x40, cid, 0xFF, data_bytes) return True @@ -418,7 +415,7 @@ def __init__(self, device, count, version): self.keyversion = FEATURE.REPROG_CONTROLS_V2 else: if logger.isEnabledFor(logging.ERROR): - logger.error(f'Trying to read keys on device {device} which has no REPROG_CONTROLS(_VX) support.') + logger.error(f"Trying to read keys on device {device} which has no REPROG_CONTROLS(_VX) support.") self.keyversion = None self.keys = [None] * count @@ -431,13 +428,13 @@ def _query_key(self, index: int): if self.keyversion == FEATURE.REPROG_CONTROLS_V2: keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V2, 0x10, index) if keydata: - cid, tid, flags = _unpack('!HHB', keydata[:5]) + cid, tid, flags = _unpack("!HHB", keydata[:5]) self.keys[index] = ReprogrammableKey(self.device, index, cid, tid, flags) self.cid_to_tid[cid] = tid elif self.keyversion == FEATURE.REPROG_CONTROLS_V4: keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x10, index) if keydata: - cid, tid, flags1, pos, group, gmask, flags2 = _unpack('!HHBBBBB', keydata[:9]) + cid, tid, flags1, pos, group, gmask, flags2 = _unpack("!HHBBBBB", keydata[:9]) flags = flags1 | (flags2 << 8) self.keys[index] = ReprogrammableKeyV4(self.device, index, cid, tid, flags, pos, group, gmask) self.cid_to_tid[cid] = tid @@ -450,7 +447,7 @@ def _ensure_all_keys_queried(self): """The retrieval of key information is lazy, but for certain functionality we need to know all keys. This function makes sure that's the case.""" with self.lock: # don't want two threads doing this - for (i, k) in enumerate(self.keys): + for i, k in enumerate(self.keys): if k is None: self._query_key(i) @@ -483,7 +480,6 @@ def __len__(self): class KeysArrayV1(KeysArray): - def __init__(self, device, count, version=1): super().__init__(device, count, version) """The mapping from Control IDs to their native Task IDs. @@ -503,7 +499,7 @@ def _query_key(self, index: int): raise IndexError(index) keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS, 0x10, index) if keydata: - cid, tid, flags = _unpack('!HHB', keydata[:5]) + cid, tid, flags = _unpack("!HHB", keydata[:5]) self.keys[index] = ReprogrammableKey(self.device, index, cid, tid, flags) self.cid_to_tid[cid] = tid elif logger.isEnabledFor(logging.WARNING): @@ -511,7 +507,6 @@ def _query_key(self, index: int): class KeysArrayV4(KeysArrayV1): - def __init__(self, device, count): super().__init__(device, count, 4) @@ -520,7 +515,7 @@ def _query_key(self, index: int): raise IndexError(index) keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x10, index) if keydata: - cid, tid, flags1, pos, group, gmask, flags2 = _unpack('!HHBBBBB', keydata[:9]) + cid, tid, flags1, pos, group, gmask, flags2 = _unpack("!HHBBBBB", keydata[:9]) flags = flags1 | (flags2 << 8) self.keys[index] = ReprogrammableKeyV4(self.device, index, cid, tid, flags, pos, group, gmask) self.cid_to_tid[cid] = tid @@ -532,7 +527,6 @@ def _query_key(self, index: int): # we are only interested in the current host, so use 0xFF for the host throughout class KeysArrayPersistent(KeysArray): - def __init__(self, device, count): super().__init__(device, count, 5) self._capabilities = None @@ -541,22 +535,22 @@ def __init__(self, device, count): def capabilities(self): if self._capabilities is None and self.device.online: capabilities = self.device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x00) - assert capabilities, 'Oops, persistent remappable key capabilities cannot be retrieved!' - self._capabilities = _unpack('!H', capabilities[:2])[0] # flags saying what the mappings are possible + assert capabilities, "Oops, persistent remappable key capabilities cannot be retrieved!" + self._capabilities = _unpack("!H", capabilities[:2])[0] # flags saying what the mappings are possible return self._capabilities def _query_key(self, index: int): if index < 0 or index >= len(self.keys): raise IndexError(index) - keydata = feature_request(self.device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x20, index, 0xff) + keydata = feature_request(self.device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x20, index, 0xFF) if keydata: - key = _unpack('!H', keydata[:2])[0] + key = _unpack("!H", keydata[:2])[0] try: mapped_data = feature_request( - self.device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x30, key & 0xff00, key & 0xff, 0xff + self.device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x30, key & 0xFF00, key & 0xFF, 0xFF ) if mapped_data: - _ignore, _ignore, actionId, remapped, modifiers, status = _unpack('!HBBHBB', mapped_data[:8]) + _ignore, _ignore, actionId, remapped, modifiers, status = _unpack("!HBBHBB", mapped_data[:8]) except Exception: actionId = remapped = modifiers = status = 0 actionId = special_keys.ACTIONID[actionId] @@ -582,18 +576,18 @@ def _query_key(self, index: int): RatioZone=3, # 4 bytes, left, bottom, width, height; unit 1/240 pad size ScaleFactor=4, # 2-byte integer, with 256 as normal scale ) -PARAM._fallback = lambda x: 'unknown:%04X' % x +PARAM._fallback = lambda x: "unknown:%04X" % x class SubParam: - __slots__ = ('id', 'length', 'minimum', 'maximum', 'widget') + __slots__ = ("id", "length", "minimum", "maximum", "widget") def __init__(self, id, length, minimum=None, maximum=None, widget=None): self.id = id self.length = length self.minimum = minimum if minimum is not None else 0 self.maximum = maximum if maximum is not None else ((1 << 8 * length) - 1) - self.widget = widget if widget is not None else 'Scale' + self.widget = widget if widget is not None else "Scale" def __str__(self): return self.id @@ -602,20 +596,21 @@ def __repr__(self): return self.id -SUB_PARAM = { # (byte count, minimum, maximum) - PARAM['ExtraCapabilities']: None, # ignore - PARAM['PixelZone']: ( # TODO: replace min and max with the correct values - SubParam('left', 2, 0x0000, 0xFFFF, 'SpinButton'), - SubParam('bottom', 2, 0x0000, 0xFFFF, 'SpinButton'), - SubParam('width', 2, 0x0000, 0xFFFF, 'SpinButton'), - SubParam('height', 2, 0x0000, 0xFFFF, 'SpinButton')), - PARAM['RatioZone']: ( # TODO: replace min and max with the correct values - SubParam('left', 1, 0x00, 0xFF, 'SpinButton'), - SubParam('bottom', 1, 0x00, 0xFF, 'SpinButton'), - SubParam('width', 1, 0x00, 0xFF, 'SpinButton'), - SubParam('height', 1, 0x00, 0xFF, 'SpinButton')), - PARAM['ScaleFactor']: ( - SubParam('scale', 2, 0x002E, 0x01FF, 'Scale'), ) +SUB_PARAM = { # (byte count, minimum, maximum) + PARAM["ExtraCapabilities"]: None, # ignore + PARAM["PixelZone"]: ( # TODO: replace min and max with the correct values + SubParam("left", 2, 0x0000, 0xFFFF, "SpinButton"), + SubParam("bottom", 2, 0x0000, 0xFFFF, "SpinButton"), + SubParam("width", 2, 0x0000, 0xFFFF, "SpinButton"), + SubParam("height", 2, 0x0000, 0xFFFF, "SpinButton"), + ), + PARAM["RatioZone"]: ( # TODO: replace min and max with the correct values + SubParam("left", 1, 0x00, 0xFF, "SpinButton"), + SubParam("bottom", 1, 0x00, 0xFF, "SpinButton"), + SubParam("width", 1, 0x00, 0xFF, "SpinButton"), + SubParam("height", 1, 0x00, 0xFF, "SpinButton"), + ), + PARAM["ScaleFactor"]: (SubParam("scale", 2, 0x002E, 0x01FF, "Scale"),), } # Spec Ids for feature GESTURE_2 @@ -629,9 +624,9 @@ def __repr__(self): finger_width_and_height=7, finger_major_minor_axis=8, finger_force=9, - zone=10 + zone=10, ) -SPEC._fallback = lambda x: 'unknown:%04X' % x +SPEC._fallback = lambda x: "unknown:%04X" % x # Action Ids for feature GESTURE_2 ACTION_ID = _NamedInts( @@ -646,13 +641,12 @@ def __repr__(self): SecondaryDrag=9, Zoom=10, ScrollHorizontalOnly=11, - ScrollVerticalOnly=12 + ScrollVerticalOnly=12, ) -ACTION_ID._fallback = lambda x: 'unknown:%04X' % x +ACTION_ID._fallback = lambda x: "unknown:%04X" % x class Gesture: - def __init__(self, device, low, high, next_index, next_diversion_index): self._device = device self.id = low @@ -719,7 +713,7 @@ def __int__(self): return self.id def __repr__(self): - return f'' + return f"" # allow a gesture to be used as a settings reader/writer to enable and disable the gesture read = enabled @@ -751,7 +745,7 @@ def value(self): def read(self): # returns the bytes for the parameter result = feature_request(self._device, FEATURE.GESTURE_2, 0x70, self.index, 0xFF) if result: - self._value = _bytes2int(result[:self.size]) + self._value = _bytes2int(result[: self.size]) return self._value @property @@ -763,7 +757,7 @@ def default_value(self): def _read_default(self): result = feature_request(self._device, FEATURE.GESTURE_2, 0x60, self.index, 0xFF) if result: - self._default_value = _bytes2int(result[:self.size]) + self._default_value = _bytes2int(result[: self.size]) return self._default_value def write(self, bytes): @@ -778,7 +772,6 @@ def __int__(self): class Spec: - def __init__(self, device, low, high): self._device = device self.id = low @@ -797,12 +790,12 @@ def read(self): value = feature_request(self._device, FEATURE.GESTURE_2, 0x50, self.id, 0xFF) except exceptions.FeatureCallError: # some calls produce an error (notably spec 5 multiplier on K400Plus) if logger.isEnabledFor(logging.WARNING): - logger.warn(f'Feature Call Error reading Gesture Spec on device {self._device} for spec {self.id} - use None') + logger.warn(f"Feature Call Error reading Gesture Spec on device {self._device} for spec {self.id} - use None") return None - return _bytes2int(value[:self.byte_count]) + return _bytes2int(value[: self.byte_count]) def __repr__(self): - return f'[{self.spec}={self.value}]' + return f"[{self.spec}={self.value}]" class Gestures: @@ -839,12 +832,12 @@ def __init__(self, device): self.params[param.param] = param elif field_high == 0x04: if field_low != 0x00: - logger.error(f'Unimplemented GESTURE_2 grouping {field_low} {field_high} found.') + logger.error(f"Unimplemented GESTURE_2 grouping {field_low} {field_high} found.") elif field_high & 0xF0 == 0x40: spec = Spec(device, field_low, field_high) self.specs[spec.spec] = spec else: - logger.warn(f'Unimplemented GESTURE_2 field {field_low} {field_high} found.') + logger.warn(f"Unimplemented GESTURE_2 field {field_low} {field_high} found.") index += 1 def gesture(self, gesture): @@ -880,10 +873,10 @@ class Backlight: def __init__(self, device): response = device.feature_request(FEATURE.BACKLIGHT2, 0x00) if not response: - raise exceptions.FeatureCallError(msg='No reply from device.') + raise exceptions.FeatureCallError(msg="No reply from device.") self.device = device self.enabled, self.options, supported, effects, self.level, self.dho, self.dhi, self.dpow = _unpack( - ' 0x04: return - count, oob, buttons, sectors, size, shift = _unpack('!BBBBHB', response[3:10]) + count, oob, buttons, sectors, size, shift = _unpack("!BBBBHB", response[3:10]) gbuttons = buttons if (shift & 0x3 == 0x2) else 0 headers = OnboardProfiles.get_profile_headers(device) profiles = {} @@ -1289,29 +1279,29 @@ def from_device(cls, device): gbuttons=gbuttons, sectors=sectors, size=size, - profiles=profiles + profiles=profiles, ) def to_bytes(self): - bytes = b'' + bytes = b"" for i in range(1, len(self.profiles) + 1): - bytes += _int2bytes(self.profiles[i].sector, 2) + _int2bytes(self.profiles[i].enabled, 1) + b'\x00' - bytes += b'\xff\xff\x00\x00' # marker after last profile + bytes += _int2bytes(self.profiles[i].sector, 2) + _int2bytes(self.profiles[i].enabled, 1) + b"\x00" + bytes += b"\xff\xff\x00\x00" # marker after last profile while len(bytes) < self.size - 2: # leave room for CRC - bytes += b'\xff' + bytes += b"\xff" bytes += _int2bytes(_crc16(bytes), 2) return bytes @classmethod def read_sector(cls, dev, sector, s): # doesn't check for valid sector or size - bytes = b'' + bytes = b"" o = 0 while o < s - 15: chunk = dev.feature_request(FEATURE.ONBOARD_PROFILES, 0x50, sector >> 8, sector & 0xFF, o >> 8, o & 0xFF) bytes += chunk o += 16 chunk = dev.feature_request(FEATURE.ONBOARD_PROFILES, 0x50, sector >> 8, sector & 0xFF, (s - 16) >> 8, (s - 16) & 0xFF) - bytes += chunk[16 + o - s:] # the last chunk has to be read in an awkward way + bytes += chunk[16 + o - s :] # the last chunk has to be read in an awkward way return bytes @classmethod @@ -1322,7 +1312,7 @@ def write_sector(cls, device, s, bs): # doesn't check for valid sector or size device.feature_request(FEATURE.ONBOARD_PROFILES, 0x60, s >> 8, s & 0xFF, 0, 0, len(bs) >> 8, len(bs) & 0xFF) o = 0 while o < len(bs) - 1: - device.feature_request(FEATURE.ONBOARD_PROFILES, 0x70, bs[o:o + 16]) + device.feature_request(FEATURE.ONBOARD_PROFILES, 0x70, bs[o : o + 16]) o += 16 device.feature_request(FEATURE.ONBOARD_PROFILES, 0x80) return True @@ -1331,15 +1321,15 @@ def write(self, device): try: written = 1 if OnboardProfiles.write_sector(device, 0, self.to_bytes()) else 0 except Exception as e: - logger.warn('Exception writing onboard profile control sector') + logger.warn("Exception writing onboard profile control sector") raise e for p in self.profiles.values(): try: if p.sector >= self.sectors: - raise Exception(f'Sector {p.sector} not a writable sector') + raise Exception(f"Sector {p.sector} not a writable sector") written += 1 if OnboardProfiles.write_sector(device, p.sector, p.to_bytes(self.size)) else 0 except Exception as e: - logger.warn(f'Exception writing onboard profile sector {p.sector}') + logger.warn(f"Exception writing onboard profile sector {p.sector}") raise e return written @@ -1347,7 +1337,7 @@ def show(self): print(_yaml.dump(self)) -_yaml.SafeLoader.add_constructor('!OnboardProfiles', OnboardProfiles.from_yaml) +_yaml.SafeLoader.add_constructor("!OnboardProfiles", OnboardProfiles.from_yaml) _yaml.add_representer(OnboardProfiles, OnboardProfiles.to_yaml) # @@ -1377,16 +1367,16 @@ def get_firmware(device): if fw_info: level = ord(fw_info[:1]) & 0x0F if level == 0 or level == 1: - name, version_major, version_minor, build = _unpack('!3sBBH', fw_info[1:8]) - version = '%02X.%02X' % (version_major, version_minor) + name, version_major, version_minor, build = _unpack("!3sBBH", fw_info[1:8]) + version = "%02X.%02X" % (version_major, version_minor) if build: - version += '.B%04X' % build - extras = fw_info[9:].rstrip(b'\x00') or None - fw_info = _FirmwareInfo(_FIRMWARE_KIND[level], name.decode('ascii'), version, extras) + version += ".B%04X" % build + extras = fw_info[9:].rstrip(b"\x00") or None + fw_info = _FirmwareInfo(_FIRMWARE_KIND[level], name.decode("ascii"), version, extras) elif level == _FIRMWARE_KIND.Hardware: - fw_info = _FirmwareInfo(_FIRMWARE_KIND.Hardware, '', str(ord(fw_info[1:2])), None) + fw_info = _FirmwareInfo(_FIRMWARE_KIND.Hardware, "", str(ord(fw_info[1:2])), None) else: - fw_info = _FirmwareInfo(_FIRMWARE_KIND.Other, '', '', None) + fw_info = _FirmwareInfo(_FIRMWARE_KIND.Other, "", "", None) fw.append(fw_info) # if logger.isEnabledFor(logging.DEBUG): @@ -1403,9 +1393,9 @@ def get_ids(device): transport_bits = ord(ids[6:7]) offset = 0 tid_map = {} - for transport, flag in [('btid', 0x1), ('btleid', 0x02), ('wpid', 0x04), ('usbid', 0x08)]: + for transport, flag in [("btid", 0x1), ("btleid", 0x02), ("wpid", 0x04), ("usbid", 0x08)]: if transport_bits & flag: - tid_map[transport] = modelId[offset:offset + 2].hex().upper() + tid_map[transport] = modelId[offset : offset + 2].hex().upper() offset = offset + 2 return (unitId.hex().upper(), modelId.hex().upper(), tid_map) @@ -1435,16 +1425,16 @@ def get_name(device): if name_length: name_length = ord(name_length[:1]) - name = b'' + name = b"" while len(name) < name_length: fragment = feature_request(device, FEATURE.DEVICE_NAME, 0x10, len(name)) if fragment: - name += fragment[:name_length - len(name)] + name += fragment[: name_length - len(name)] else: - logger.error('failed to read whole name of %s (expected %d chars)', device, name_length) + logger.error("failed to read whole name of %s (expected %d chars)", device, name_length) return None - return name.decode('utf-8') + return name.decode("utf-8") def get_friendly_name(device): @@ -1457,17 +1447,17 @@ def get_friendly_name(device): if name_length: name_length = ord(name_length[:1]) - name = b'' + name = b"" while len(name) < name_length: fragment = feature_request(device, FEATURE.DEVICE_FRIENDLY_NAME, 0x10, len(name)) if fragment: initial_null = 0 if fragment[0] else 1 # initial null actually seen on a device - name += fragment[initial_null:name_length + initial_null - len(name)] + name += fragment[initial_null : name_length + initial_null - len(name)] else: - logger.error('failed to read whole name of %s (expected %d chars)', device, name_length) + logger.error("failed to read whole name of %s (expected %d chars)", device, name_length) return None - return name.decode('utf-8') + return name.decode("utf-8") def get_battery_status(device): @@ -1477,11 +1467,11 @@ def get_battery_status(device): def decipher_battery_status(report): - discharge, next, status = _unpack('!BBB', report[:3]) + discharge, next, status = _unpack("!BBB", report[:3]) discharge = None if discharge == 0 else discharge status = BATTERY_STATUS[status] if logger.isEnabledFor(logging.DEBUG): - logger.debug('battery status %s%% charged, next %s%%, status %s', discharge, next, status) + logger.debug("battery status %s%% charged, next %s%%, status %s", discharge, next, status) return FEATURE.BATTERY_STATUS, discharge, next, status, None @@ -1492,15 +1482,19 @@ def get_battery_unified(device): def decipher_battery_unified(report): - discharge, level, status, _ignore = _unpack('!BBBB', report[:4]) + discharge, level, status, _ignore = _unpack("!BBBB", report[:4]) status = BATTERY_STATUS[status] if logger.isEnabledFor(logging.DEBUG): - logger.debug('battery unified %s%% charged, level %s, charging %s', discharge, level, status) + logger.debug("battery unified %s%% charged, level %s, charging %s", discharge, level, status) level = ( - _BATTERY_APPROX.full if level == 8 # full - else _BATTERY_APPROX.good if level == 4 # good - else _BATTERY_APPROX.low if level == 2 # low - else _BATTERY_APPROX.critical if level == 1 # critical + _BATTERY_APPROX.full + if level == 8 # full + else _BATTERY_APPROX.good + if level == 4 # good + else _BATTERY_APPROX.low + if level == 2 # low + else _BATTERY_APPROX.critical + if level == 1 # critical else _BATTERY_APPROX.empty ) return FEATURE.UNIFIED_BATTERY, discharge if discharge else level, None, status, None @@ -1532,7 +1526,7 @@ def get_battery_voltage(device): def decipher_battery_voltage(report): - voltage, flags = _unpack('>HB', report[:3]) + voltage, flags = _unpack(">HB", report[:3]) status = BATTERY_STATUS.discharging charge_sts = ERROR.unknown charge_lvl = CHARGE_LEVEL.average @@ -1545,12 +1539,12 @@ def decipher_battery_voltage(report): elif charge_sts == CHARGE_STATUS.full: charge_lvl = CHARGE_LEVEL.full status = BATTERY_STATUS.full - if (flags & (1 << 3)): + if flags & (1 << 3): charge_type = CHARGE_TYPE.fast - elif (flags & (1 << 4)): + elif flags & (1 << 4): charge_type = CHARGE_TYPE.slow status = BATTERY_STATUS.slow_recharge - elif (flags & (1 << 5)): + elif flags & (1 << 5): charge_lvl = CHARGE_LEVEL.critical for level in battery_voltage_remaining: if level[0] < voltage: @@ -1558,8 +1552,13 @@ def decipher_battery_voltage(report): break if logger.isEnabledFor(logging.DEBUG): logger.debug( - 'battery voltage %d mV, charging %s, status %d = %s, level %s, type %s', voltage, status, (flags & 0x03), - charge_sts, charge_lvl, charge_type + "battery voltage %d mV, charging %s, status %d = %s, level %s, type %s", + voltage, + status, + (flags & 0x03), + charge_sts, + charge_lvl, + charge_type, ) return FEATURE.BATTERY_VOLTAGE, charge_lvl, None, status, voltage @@ -1575,7 +1574,7 @@ def get_adc_measurement(device): def decipher_adc_measurement(report): # partial implementation - needs mapping to levels - adc, flags = _unpack('!HB', report[:3]) + adc, flags = _unpack("!HB", report[:3]) for level in battery_voltage_remaining: if level[0] < adc: charge_level = level[1] @@ -1629,21 +1628,21 @@ def get_remap_keys(device): def get_gestures(device): - if getattr(device, '_gestures', None) is not None: + if getattr(device, "_gestures", None) is not None: return device._gestures if FEATURE.GESTURE_2 in device.features: return Gestures(device) def get_backlight(device): - if getattr(device, '_backlight', None) is not None: + if getattr(device, "_backlight", None) is not None: return device._backlight if FEATURE.BACKLIGHT2 in device.features: return Backlight(device) def get_profiles(device): - if getattr(device, '_profiles', None) is not None: + if getattr(device, "_profiles", None) is not None: return device._profiles if FEATURE.ONBOARD_PROFILES in device.features: return OnboardProfiles.from_device(device) @@ -1652,39 +1651,46 @@ def get_profiles(device): def get_mouse_pointer_info(device): pointer_info = feature_request(device, FEATURE.MOUSE_POINTER) if pointer_info: - dpi, flags = _unpack('!HB', pointer_info[:3]) - acceleration = ('none', 'low', 'med', 'high')[flags & 0x3] + dpi, flags = _unpack("!HB", pointer_info[:3]) + acceleration = ("none", "low", "med", "high")[flags & 0x3] suggest_os_ballistics = (flags & 0x04) != 0 suggest_vertical_orientation = (flags & 0x08) != 0 return { - 'dpi': dpi, - 'acceleration': acceleration, - 'suggest_os_ballistics': suggest_os_ballistics, - 'suggest_vertical_orientation': suggest_vertical_orientation + "dpi": dpi, + "acceleration": acceleration, + "suggest_os_ballistics": suggest_os_ballistics, + "suggest_vertical_orientation": suggest_vertical_orientation, } def get_vertical_scrolling_info(device): vertical_scrolling_info = feature_request(device, FEATURE.VERTICAL_SCROLLING) if vertical_scrolling_info: - roller, ratchet, lines = _unpack('!BBB', vertical_scrolling_info[:3]) + roller, ratchet, lines = _unpack("!BBB", vertical_scrolling_info[:3]) roller_type = ( - 'reserved', 'standard', 'reserved', '3G', 'micro', 'normal touch pad', 'inverted touch pad', 'reserved' + "reserved", + "standard", + "reserved", + "3G", + "micro", + "normal touch pad", + "inverted touch pad", + "reserved", )[roller] - return {'roller': roller_type, 'ratchet': ratchet, 'lines': lines} + return {"roller": roller_type, "ratchet": ratchet, "lines": lines} def get_hi_res_scrolling_info(device): hi_res_scrolling_info = feature_request(device, FEATURE.HI_RES_SCROLLING) if hi_res_scrolling_info: - mode, resolution = _unpack('!BB', hi_res_scrolling_info[:2]) + mode, resolution = _unpack("!BB", hi_res_scrolling_info[:2]) return mode, resolution def get_pointer_speed_info(device): pointer_speed_info = feature_request(device, FEATURE.POINTER_SPEED) if pointer_speed_info: - pointer_speed_hi, pointer_speed_lo = _unpack('!BB', pointer_speed_info[:2]) + pointer_speed_hi, pointer_speed_lo = _unpack("!BB", pointer_speed_info[:2]) # if pointer_speed_lo > 0: # pointer_speed_lo = pointer_speed_lo return pointer_speed_hi + pointer_speed_lo / 256 @@ -1693,8 +1699,8 @@ def get_pointer_speed_info(device): def get_lowres_wheel_status(device): lowres_wheel_status = feature_request(device, FEATURE.LOWRES_WHEEL) if lowres_wheel_status: - wheel_flag = _unpack('!B', lowres_wheel_status[:1])[0] - wheel_reporting = ('HID', 'HID++')[wheel_flag & 0x01] + wheel_flag = _unpack("!B", lowres_wheel_status[:1])[0] + wheel_reporting = ("HID", "HID++")[wheel_flag & 0x01] return wheel_reporting @@ -1705,20 +1711,20 @@ def get_hires_wheel(device): if caps and mode and ratchet: # Parse caps - multi, flags = _unpack('!BB', caps[:2]) + multi, flags = _unpack("!BB", caps[:2]) has_invert = (flags & 0x08) != 0 has_ratchet = (flags & 0x04) != 0 # Parse mode - wheel_mode, reserved = _unpack('!BB', mode[:2]) + wheel_mode, reserved = _unpack("!BB", mode[:2]) target = (wheel_mode & 0x01) != 0 res = (wheel_mode & 0x02) != 0 inv = (wheel_mode & 0x04) != 0 # Parse Ratchet switch - ratchet_mode, reserved = _unpack('!BB', ratchet[:2]) + ratchet_mode, reserved = _unpack("!BB", ratchet[:2]) ratchet = (ratchet_mode & 0x01) != 0 @@ -1728,7 +1734,7 @@ def get_hires_wheel(device): def get_new_fn_inversion(device): state = feature_request(device, FEATURE.NEW_FN_INVERSION, 0x00) if state: - inverted, default_inverted = _unpack('!BB', state[:2]) + inverted, default_inverted = _unpack("!BB", state[:2]) inverted = (inverted & 0x01) != 0 default_inverted = (default_inverted & 0x01) != 0 return inverted, default_inverted @@ -1738,46 +1744,46 @@ def get_host_names(device): state = feature_request(device, FEATURE.HOSTS_INFO, 0x00) host_names = {} if state: - capability_flags, _ignore, numHosts, currentHost = _unpack('!BBBB', state[:4]) + capability_flags, _ignore, numHosts, currentHost = _unpack("!BBBB", state[:4]) if capability_flags & 0x01: # device can get host names for host in range(0, numHosts): hostinfo = feature_request(device, FEATURE.HOSTS_INFO, 0x10, host) - _ignore, status, _ignore, _ignore, nameLen, _ignore = _unpack('!BBBBBB', hostinfo[:6]) - name = '' + _ignore, status, _ignore, _ignore, nameLen, _ignore = _unpack("!BBBBBB", hostinfo[:6]) + name = "" remaining = nameLen while remaining > 0: name_piece = feature_request(device, FEATURE.HOSTS_INFO, 0x30, host, nameLen - remaining) if name_piece: - name += name_piece[2:2 + min(remaining, 14)].decode() + name += name_piece[2 : 2 + min(remaining, 14)].decode() remaining = max(0, remaining - 14) else: remaining = 0 host_names[host] = (bool(status), name) # update the current host's name if it doesn't match the system name - hostname = socket.gethostname().partition('.')[0] + hostname = socket.gethostname().partition(".")[0] if host_names[currentHost][1] != hostname: set_host_name(device, hostname, host_names[currentHost][1]) host_names[currentHost] = (host_names[currentHost][0], hostname) return host_names -def set_host_name(device, name, currentName=''): - name = bytearray(name, 'utf-8') - currentName = bytearray(currentName, 'utf-8') +def set_host_name(device, name, currentName=""): + name = bytearray(name, "utf-8") + currentName = bytearray(currentName, "utf-8") if logger.isEnabledFor(logging.INFO): - logger.info('Setting host name to %s', name) + logger.info("Setting host name to %s", name) state = feature_request(device, FEATURE.HOSTS_INFO, 0x00) if state: - flags, _ignore, _ignore, currentHost = _unpack('!BBBB', state[:4]) + flags, _ignore, _ignore, currentHost = _unpack("!BBBB", state[:4]) if flags & 0x02: hostinfo = feature_request(device, FEATURE.HOSTS_INFO, 0x10, currentHost) - _ignore, _ignore, _ignore, _ignore, _ignore, maxNameLen = _unpack('!BBBBBB', hostinfo[:6]) + _ignore, _ignore, _ignore, _ignore, _ignore, maxNameLen = _unpack("!BBBBBB", hostinfo[:6]) if name[:maxNameLen] == currentName[:maxNameLen] and False: return True length = min(maxNameLen, len(name)) chunk = 0 while chunk < length: - response = feature_request(device, FEATURE.HOSTS_INFO, 0x40, currentHost, chunk, name[chunk:chunk + 14]) + response = feature_request(device, FEATURE.HOSTS_INFO, 0x40, currentHost, chunk, name[chunk : chunk + 14]) if not response: return False chunk += 14 @@ -1788,7 +1794,7 @@ def get_onboard_mode(device): state = feature_request(device, FEATURE.ONBOARD_PROFILES, 0x20) if state: - mode = _unpack('!B', state[:1])[0] + mode = _unpack("!B", state[:1])[0] return mode @@ -1800,20 +1806,20 @@ def set_onboard_mode(device, mode): def get_polling_rate(device): state = feature_request(device, FEATURE.REPORT_RATE, 0x10) if state: - rate = _unpack('!B', state[:1])[0] - return str(rate) + 'ms' + rate = _unpack("!B", state[:1])[0] + return str(rate) + "ms" else: - rates = ['8ms', '4ms', '2ms', '1ms', '500us', '250us', '125us'] + rates = ["8ms", "4ms", "2ms", "1ms", "500us", "250us", "125us"] state = feature_request(device, FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE, 0x20) if state: - rate = _unpack('!B', state[:1])[0] + rate = _unpack("!B", state[:1])[0] return rates[rate] def get_remaining_pairing(device): result = feature_request(device, FEATURE.REMAINING_PAIRING, 0x0) if result: - result = _unpack('!B', result[:1])[0] + result = _unpack("!B", result[:1])[0] return result diff --git a/lib/logitech_receiver/hidpp20_constants.py b/lib/logitech_receiver/hidpp20_constants.py index 72cc5e272b..c99df4bdc5 100644 --- a/lib/logitech_receiver/hidpp20_constants.py +++ b/lib/logitech_receiver/hidpp20_constants.py @@ -128,7 +128,7 @@ # Fake features for Solaar internal use MOUSE_GESTURE=0xFE00, ) -FEATURE._fallback = lambda x: 'unknown:%04X' % x +FEATURE._fallback = lambda x: "unknown:%04X" % x FEATURE_FLAG = NamedInts(internal=0x20, hidden=0x40, obsolete=0x80) @@ -150,7 +150,7 @@ def BATTERY_OK(status): full=0x03, slow_recharge=0x04, invalid_battery=0x05, - thermal_error=0x06 + thermal_error=0x06, ) ONBOARD_MODES = NamedInts(MODE_NO_CHANGE=0x00, MODE_ONBOARD=0x01, MODE_HOST=0x02) @@ -170,7 +170,7 @@ def BATTERY_OK(status): invalid_feature_index=0x06, invalid_function=0x07, busy=0x08, - unsupported=0x09 + unsupported=0x09, ) # Gesture Ids for feature GESTURE_2 @@ -236,7 +236,7 @@ def BATTERY_OK(status): Finger10=99, DeviceSpecificRawData=100, ) -GESTURE._fallback = lambda x: 'unknown:%04X' % x +GESTURE._fallback = lambda x: "unknown:%04X" % x # Param Ids for feature GESTURE_2 PARAM = NamedInts( @@ -245,4 +245,4 @@ def BATTERY_OK(status): RatioZone=3, # 4 bytes, left, bottom, width, height; unit 1/240 pad size ScaleFactor=4, # 2-byte integer, with 256 as normal scale ) -PARAM._fallback = lambda x: 'unknown:%04X' % x +PARAM._fallback = lambda x: "unknown:%04X" % x diff --git a/lib/logitech_receiver/i18n.py b/lib/logitech_receiver/i18n.py index c2d51cce8f..896c56cfb6 100644 --- a/lib/logitech_receiver/i18n.py +++ b/lib/logitech_receiver/i18n.py @@ -27,61 +27,56 @@ _DUMMY = ( # approximative battery levels - _('empty'), - _('critical'), - _('low'), - _('average'), - _('good'), - _('full'), - + _("empty"), + _("critical"), + _("low"), + _("average"), + _("good"), + _("full"), # battery charging statuses - _('discharging'), - _('recharging'), - _('charging'), - _('not charging'), - _('almost full'), - _('charged'), - _('slow recharge'), - _('invalid battery'), - _('thermal error'), - _('error'), - _('standard'), - _('fast'), - _('slow'), - + _("discharging"), + _("recharging"), + _("charging"), + _("not charging"), + _("almost full"), + _("charged"), + _("slow recharge"), + _("invalid battery"), + _("thermal error"), + _("error"), + _("standard"), + _("fast"), + _("slow"), # pairing errors - _('device timeout'), - _('device not supported'), - _('too many devices'), - _('sequence timeout'), - + _("device timeout"), + _("device not supported"), + _("too many devices"), + _("sequence timeout"), # firmware kinds - _('Firmware'), - _('Bootloader'), - _('Hardware'), - _('Other'), - + _("Firmware"), + _("Bootloader"), + _("Hardware"), + _("Other"), # common button and task names (from special_keys.py) - _('Left Button'), - _('Right Button'), - _('Middle Button'), - _('Back Button'), - _('Forward Button'), - _('Mouse Gesture Button'), - _('Smart Shift'), - _('DPI Switch'), - _('Left Tilt'), - _('Right Tilt'), - _('Left Click'), - _('Right Click'), - _('Mouse Middle Button'), - _('Mouse Back Button'), - _('Mouse Forward Button'), - _('Gesture Button Navigation'), - _('Mouse Scroll Left Button'), - _('Mouse Scroll Right Button'), - + _("Left Button"), + _("Right Button"), + _("Middle Button"), + _("Back Button"), + _("Forward Button"), + _("Mouse Gesture Button"), + _("Smart Shift"), + _("DPI Switch"), + _("Left Tilt"), + _("Right Tilt"), + _("Left Click"), + _("Right Click"), + _("Mouse Middle Button"), + _("Mouse Back Button"), + _("Mouse Forward Button"), + _("Gesture Button Navigation"), + _("Mouse Scroll Left Button"), + _("Mouse Scroll Right Button"), # key/button statuses - _('pressed'), - _('released'), + _("pressed"), + _("released"), ) diff --git a/lib/logitech_receiver/listener.py b/lib/logitech_receiver/listener.py index 8d10789c72..f571ae0181 100644 --- a/lib/logitech_receiver/listener.py +++ b/lib/logitech_receiver/listener.py @@ -43,7 +43,7 @@ class _ThreadedHandle: Closing a ThreadedHandle will close all handles. """ - __slots__ = ('path', '_local', '_handles', '_listener') + __slots__ = ("path", "_local", "_handles", "_listener") def __init__(self, listener, path, handle): assert listener is not None @@ -61,7 +61,7 @@ def __init__(self, listener, path, handle): def _open(self): handle = _base.open_path(self.path) if handle is None: - logger.error('%r failed to open new handle', self) + logger.error("%r failed to open new handle", self) else: # if logger.isEnabledFor(logging.DEBUG): # logger.debug("%r opened new handle %d", self, handle) @@ -74,7 +74,7 @@ def close(self): self._local = None handles, self._handles = self._handles, [] if logger.isEnabledFor(logging.DEBUG): - logger.debug('%r closing %s', self, handles) + logger.debug("%r closing %s", self, handles) for h in handles: _base.close(h) @@ -105,7 +105,7 @@ def __str__(self): return str(int(self)) def __repr__(self): - return '<_ThreadedHandle(%s)>' % self.path + return "<_ThreadedHandle(%s)>" % self.path def __bool__(self): return bool(self._local) @@ -123,7 +123,7 @@ def __bool__(self): # a while for it to acknowledge it. # Forcibly closing the file handle on another thread does _not_ interrupt the # read on Linux systems. -_EVENT_READ_TIMEOUT = 1. # in seconds +_EVENT_READ_TIMEOUT = 1.0 # in seconds # After this many reads that did not produce a packet, call the tick() method. # This only happens if tick_period is enabled (>0) for the Listener instance. @@ -138,10 +138,10 @@ class EventsListener(_threading.Thread): def __init__(self, receiver, notifications_callback): try: - path_name = receiver.path.split('/')[2] + path_name = receiver.path.split("/")[2] except IndexError: path_name = receiver.path - super().__init__(name=self.__class__.__name__ + ':' + path_name) + super().__init__(name=self.__class__.__name__ + ":" + path_name) self.daemon = True self._active = False self.receiver = receiver @@ -153,19 +153,19 @@ def run(self): # replace the handle with a threaded one self.receiver.handle = _ThreadedHandle(self, self.receiver.path, self.receiver.handle) if logger.isEnabledFor(logging.INFO): - logger.info('started with %s (%d)', self.receiver, int(self.receiver.handle)) + logger.info("started with %s (%d)", self.receiver, int(self.receiver.handle)) self.has_started() if self.receiver.isDevice: # ping (wired or BT) devices to see if they are really online if self.receiver.ping(): - self.receiver.status.changed(True, reason='initialization') + self.receiver.status.changed(True, reason="initialization") while self._active: if self._queued_notifications.empty(): try: n = _base.read(self.receiver.handle, _EVENT_READ_TIMEOUT) except exceptions.NoReceiver: - logger.warning('%s disconnected', self.receiver.name) + logger.warning("%s disconnected", self.receiver.name) self.receiver.close() break if n: @@ -176,7 +176,7 @@ def run(self): try: self._notifications_callback(n) except Exception: - logger.exception('processing %s', n) + logger.exception("processing %s", n) del self._queued_notifications self.has_stopped() diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 4031552069..9fa2f1eac1 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -49,7 +49,7 @@ def process(device, notification): assert device assert notification - assert hasattr(device, 'status') + assert hasattr(device, "status") status = device.status assert status is not None @@ -70,9 +70,9 @@ def _process_receiver_notification(receiver, status, n): if n.sub_id == 0x4A: # pairing lock notification status.lock_open = bool(n.address & 0x01) - reason = (_('pairing lock is open') if status.lock_open else _('pairing lock is closed')) + reason = _("pairing lock is open") if status.lock_open else _("pairing lock is closed") if logger.isEnabledFor(logging.INFO): - logger.info('%s: %s', receiver, reason) + logger.info("%s: %s", receiver, reason) status[_K.ERROR] = None if status.lock_open: status.new_device = None @@ -80,16 +80,16 @@ def _process_receiver_notification(receiver, status, n): if pair_error: status[_K.ERROR] = error_string = _hidpp10.PAIRING_ERRORS[pair_error] status.new_device = None - logger.warning('pairing error %d: %s', pair_error, error_string) + logger.warning("pairing error %d: %s", pair_error, error_string) status.changed(reason=reason) return True elif n.sub_id == _R.discovery_status_notification: # Bolt pairing with notification_lock: status.discovering = n.address == 0x00 - reason = (_('discovery lock is open') if status.discovering else _('discovery lock is closed')) + reason = _("discovery lock is open") if status.discovering else _("discovery lock is closed") if logger.isEnabledFor(logging.INFO): - logger.info('%s: %s', receiver, reason) + logger.info("%s: %s", receiver, reason) status[_K.ERROR] = None if status.discovering: status.counter = status.device_address = status.device_authentication = status.device_name = None @@ -97,7 +97,7 @@ def _process_receiver_notification(receiver, status, n): discover_error = ord(n.data[:1]) if discover_error: status[_K.ERROR] = discover_string = _hidpp10.BOLT_PAIRING_ERRORS[discover_error] - logger.warning('bolt discovering error %d: %s', discover_error, discover_string) + logger.warning("bolt discovering error %d: %s", discover_error, discover_string) status.changed(reason=reason) return True @@ -114,16 +114,16 @@ def _process_receiver_notification(receiver, status, n): status.device_address = n.data[6:12] status.device_authentication = n.data[14] elif n.data[1] == 1: - status.device_name = n.data[3:3 + n.data[2]].decode('utf-8') + status.device_name = n.data[3 : 3 + n.data[2]].decode("utf-8") return True elif n.sub_id == _R.pairing_status_notification: # Bolt pairing with notification_lock: status.device_passkey = None status.lock_open = n.address == 0x00 - reason = (_('pairing lock is open') if status.lock_open else _('pairing lock is closed')) + reason = _("pairing lock is open") if status.lock_open else _("pairing lock is closed") if logger.isEnabledFor(logging.INFO): - logger.info('%s: %s', receiver, reason) + logger.info("%s: %s", receiver, reason) status[_K.ERROR] = None if not status.lock_open: status.counter = status.device_address = status.device_authentication = status.device_name = None @@ -135,19 +135,19 @@ def _process_receiver_notification(receiver, status, n): if pair_error: status[_K.ERROR] = error_string = _hidpp10.BOLT_PAIRING_ERRORS[pair_error] status.new_device = None - logger.warning('pairing error %d: %s', pair_error, error_string) + logger.warning("pairing error %d: %s", pair_error, error_string) status.changed(reason=reason) return True elif n.sub_id == _R.passkey_request_notification: # Bolt pairing with notification_lock: - status.device_passkey = n.data[0:6].decode('utf-8') + status.device_passkey = n.data[0:6].decode("utf-8") return True elif n.sub_id == _R.passkey_pressed_notification: # Bolt pairing return True - logger.warning('%s: unhandled notification %s', receiver, n) + logger.warning("%s: unhandled notification %s", receiver, n) # @@ -188,12 +188,12 @@ def _process_device_notification(device, status, n): # assuming 0x00 to 0x3F are feature (HID++ 2.0) notifications if not device.features: - logger.warning('%s: feature notification but features not set up: %02X %s', device, n.sub_id, n) + logger.warning("%s: feature notification but features not set up: %02X %s", device, n.sub_id, n) return False try: feature = device.features.get_feature(n.sub_id) except IndexError: - logger.warning('%s: notification from invalid feature index %02X: %s', device, n.sub_id, n) + logger.warning("%s: notification from invalid feature index %02X: %s", device, n.sub_id, n) return False return _process_feature_notification(device, status, n, feature) @@ -201,37 +201,37 @@ def _process_device_notification(device, status, n): def _process_dj_notification(device, status, n): if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s (%s) DJ %s', device, device.protocol, n) + logger.debug("%s (%s) DJ %s", device, device.protocol, n) if n.sub_id == 0x40: # do all DJ paired notifications also show up as HID++ 1.0 notifications? if logger.isEnabledFor(logging.INFO): - logger.info('%s: ignoring DJ unpaired: %s', device, n) + logger.info("%s: ignoring DJ unpaired: %s", device, n) return True if n.sub_id == 0x41: # do all DJ paired notifications also show up as HID++ 1.0 notifications? if logger.isEnabledFor(logging.INFO): - logger.info('%s: ignoring DJ paired: %s', device, n) + logger.info("%s: ignoring DJ paired: %s", device, n) return True if n.sub_id == 0x42: connected = not n.address & 0x01 if logger.isEnabledFor(logging.INFO): - logger.info('%s: DJ connection: %s %s', device, connected, n) - status.changed(active=connected, alert=_ALERT.NONE, reason=_('connected') if connected else _('disconnected')) + logger.info("%s: DJ connection: %s %s", device, connected, n) + status.changed(active=connected, alert=_ALERT.NONE, reason=_("connected") if connected else _("disconnected")) return True - logger.warning('%s: unrecognized DJ %s', device, n) + logger.warning("%s: unrecognized DJ %s", device, n) def _process_hidpp10_custom_notification(device, status, n): if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s (%s) custom notification %s', device, device.protocol, n) + logger.debug("%s (%s) custom notification %s", device, device.protocol, n) if n.sub_id in (_R.battery_status, _R.battery_charge): # message layout: 10 ix <00> - assert n.data[-1:] == b'\x00' + assert n.data[-1:] == b"\x00" data = chr(n.address).encode() + n.data charge, next_charge, status_text, voltage = _hidpp10.parse_battery_status(n.sub_id, data) status.set_battery_info(charge, next_charge, status_text, voltage) @@ -241,10 +241,10 @@ def _process_hidpp10_custom_notification(device, status, n): # message layout: 10 ix 17("address") # TODO anything we can do with this? if logger.isEnabledFor(logging.INFO): - logger.info('illumination event: %s', n) + logger.info("illumination event: %s", n) return True - logger.warning('%s: unrecognized %s', device, n) + logger.warning("%s: unrecognized %s", device, n) def _process_hidpp10_notification(device, status, n): @@ -257,16 +257,16 @@ def _process_hidpp10_notification(device, status, n): device.status = None if device.number in device.receiver: del device.receiver[device.number] - status.changed(active=False, alert=_ALERT.ALL, reason=_('unpaired')) + status.changed(active=False, alert=_ALERT.ALL, reason=_("unpaired")) else: - logger.warning('%s: disconnection with unknown type %02X: %s', device, n.address, n) + logger.warning("%s: disconnection with unknown type %02X: %s", device, n.address, n) return True # device connection (and disconnection) if n.sub_id == 0x41: flags = ord(n.data[:1]) & 0xF0 if n.address == 0x02: # very old 27 MHz protocol - wpid = '00' + _strhex(n.data[2:3]) + wpid = "00" + _strhex(n.data[2:3]) link_established = True link_encrypted = bool(flags & 0x80) elif n.address > 0x00: # all other protocols are supposed to be almost the same @@ -274,14 +274,19 @@ def _process_hidpp10_notification(device, status, n): link_established = not (flags & 0x40) link_encrypted = bool(flags & 0x20) or n.address == 0x10 # Bolt protocol always encrypted else: - logger.warning('%s: connection notification with unknown protocol %02X: %s', device.number, n.address, n) + logger.warning("%s: connection notification with unknown protocol %02X: %s", device.number, n.address, n) return True if wpid != device.wpid: - logger.warning('%s wpid mismatch, got %s', device, wpid) + logger.warning("%s wpid mismatch, got %s", device, wpid) if logger.isEnabledFor(logging.DEBUG): logger.debug( - '%s: protocol %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s', device, n.address, - bool(flags & 0x10), link_encrypted, link_established, bool(flags & 0x80) + "%s: protocol %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s", + device, + n.address, + bool(flags & 0x10), + link_encrypted, + link_established, + bool(flags & 0x80), ) status[_K.LINK_ENCRYPTED] = link_encrypted status.changed(active=link_established) @@ -298,19 +303,19 @@ def _process_hidpp10_notification(device, status, n): if n.sub_id == 0x4B: if n.address == 0x01: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: device powered on', device) - reason = status.to_string() or _('powered on') + logger.debug("%s: device powered on", device) + reason = status.to_string() or _("powered on") status.changed(active=True, alert=_ALERT.NOTIFICATION, reason=reason) else: - logger.warning('%s: unknown %s', device, n) + logger.warning("%s: unknown %s", device, n) return True - logger.warning('%s: unrecognized %s', device, n) + logger.warning("%s: unrecognized %s", device, n) def _process_feature_notification(device, status, n, feature): if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: notification for feature %s, report %s, data %s', device, feature, n.address >> 4, _strhex(n.data)) + logger.debug("%s: notification for feature %s, report %s, data %s", device, feature, n.address >> 4, _strhex(n.data)) if feature == _F.BATTERY_STATUS: if n.address == 0x00: @@ -318,23 +323,23 @@ def _process_feature_notification(device, status, n, feature): status.set_battery_info(discharge_level, discharge_next_level, battery_status, voltage) elif n.address == 0x10: if logger.isEnabledFor(logging.INFO): - logger.info('%s: spurious BATTERY status %s', device, n) + logger.info("%s: spurious BATTERY status %s", device, n) else: - logger.warning('%s: unknown BATTERY %s', device, n) + logger.warning("%s: unknown BATTERY %s", device, n) elif feature == _F.BATTERY_VOLTAGE: if n.address == 0x00: _ignore, level, nextl, battery_status, voltage = _hidpp20.decipher_battery_voltage(n.data) status.set_battery_info(level, nextl, battery_status, voltage) else: - logger.warning('%s: unknown VOLTAGE %s', device, n) + logger.warning("%s: unknown VOLTAGE %s", device, n) elif feature == _F.UNIFIED_BATTERY: if n.address == 0x00: _ignore, level, nextl, battery_status, voltage = _hidpp20.decipher_battery_unified(n.data) status.set_battery_info(level, nextl, battery_status, voltage) else: - logger.warning('%s: unknown UNIFIED BATTERY %s', device, n) + logger.warning("%s: unknown UNIFIED BATTERY %s", device, n) elif feature == _F.ADC_MEASUREMENT: if n.address == 0x00: @@ -345,11 +350,11 @@ def _process_feature_notification(device, status, n, feature): else: # this feature is used to signal device becoming inactive status.changed(active=False) else: - logger.warning('%s: unknown ADC MEASUREMENT %s', device, n) + logger.warning("%s: unknown ADC MEASUREMENT %s", device, n) elif feature == _F.SOLAR_DASHBOARD: - if n.data[5:9] == b'GOOD': - charge, lux, adc = _unpack('!BHH', n.data[:5]) + if n.data[5:9] == b"GOOD": + charge, lux, adc = _unpack("!BHH", n.data[:5]) # guesstimate the battery voltage, emphasis on 'guess' # status_text = '%1.2fV' % (adc * 2.67793237653 / 0x0672) status_text = _hidpp20_constants.BATTERY_STATUS.discharging @@ -363,7 +368,7 @@ def _process_feature_notification(device, status, n, feature): status.set_battery_info(charge, None, status_text, None) elif n.address == 0x20: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: Light Check button pressed', device) + logger.debug("%s: Light Check button pressed", device) status.changed(alert=_ALERT.SHOW_WINDOW) # first cancel any reporting # device.feature_request(_F.SOLAR_DASHBOARD) @@ -372,93 +377,93 @@ def _process_feature_notification(device, status, n, feature): reports_period = 2 # seconds device.feature_request(_F.SOLAR_DASHBOARD, 0x00, reports_count, reports_period) else: - logger.warning('%s: unknown SOLAR CHARGE %s', device, n) + logger.warning("%s: unknown SOLAR CHARGE %s", device, n) else: - logger.warning('%s: SOLAR CHARGE not GOOD? %s', device, n) + logger.warning("%s: SOLAR CHARGE not GOOD? %s", device, n) elif feature == _F.WIRELESS_DEVICE_STATUS: if n.address == 0x00: if logger.isEnabledFor(logging.DEBUG): - logger.debug('wireless status: %s', n) - reason = 'powered on' if n.data[2] == 1 else None + logger.debug("wireless status: %s", n) + reason = "powered on" if n.data[2] == 1 else None if n.data[1] == 1: # device is asking for software reconfiguration so need to change status alert = _ALERT.NONE status.changed(active=True, alert=alert, reason=reason, push=True) else: - logger.warning('%s: unknown WIRELESS %s', device, n) + logger.warning("%s: unknown WIRELESS %s", device, n) elif feature == _F.TOUCHMOUSE_RAW_POINTS: if n.address == 0x00: if logger.isEnabledFor(logging.INFO): - logger.info('%s: TOUCH MOUSE points %s', device, n) + logger.info("%s: TOUCH MOUSE points %s", device, n) elif n.address == 0x10: touch = ord(n.data[:1]) button_down = bool(touch & 0x02) mouse_lifted = bool(touch & 0x01) if logger.isEnabledFor(logging.INFO): - logger.info('%s: TOUCH MOUSE status: button_down=%s mouse_lifted=%s', device, button_down, mouse_lifted) + logger.info("%s: TOUCH MOUSE status: button_down=%s mouse_lifted=%s", device, button_down, mouse_lifted) else: - logger.warning('%s: unknown TOUCH MOUSE %s', device, n) + logger.warning("%s: unknown TOUCH MOUSE %s", device, n) # TODO: what are REPROG_CONTROLS_V{2,3}? elif feature == _F.REPROG_CONTROLS: if n.address == 0x00: if logger.isEnabledFor(logging.INFO): - logger.info('%s: reprogrammable key: %s', device, n) + logger.info("%s: reprogrammable key: %s", device, n) else: - logger.warning('%s: unknown REPROG_CONTROLS %s', device, n) + logger.warning("%s: unknown REPROG_CONTROLS %s", device, n) elif feature == _F.BACKLIGHT2: - if (n.address == 0x00): - level = _unpack('!B', n.data[1:2])[0] + if n.address == 0x00: + level = _unpack("!B", n.data[1:2])[0] if device.setting_callback: device.setting_callback(device, _st.Backlight2Level, [level]) elif feature == _F.REPROG_CONTROLS_V4: if n.address == 0x00: if logger.isEnabledFor(logging.DEBUG): - cid1, cid2, cid3, cid4 = _unpack('!HHHH', n.data[:8]) - logger.debug('%s: diverted controls pressed: 0x%x, 0x%x, 0x%x, 0x%x', device, cid1, cid2, cid3, cid4) + cid1, cid2, cid3, cid4 = _unpack("!HHHH", n.data[:8]) + logger.debug("%s: diverted controls pressed: 0x%x, 0x%x, 0x%x, 0x%x", device, cid1, cid2, cid3, cid4) elif n.address == 0x10: if logger.isEnabledFor(logging.DEBUG): - dx, dy = _unpack('!hh', n.data[:4]) - logger.debug('%s: rawXY dx=%i dy=%i', device, dx, dy) + dx, dy = _unpack("!hh", n.data[:4]) + logger.debug("%s: rawXY dx=%i dy=%i", device, dx, dy) elif n.address == 0x20: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: received analyticsKeyEvents', device) + logger.debug("%s: received analyticsKeyEvents", device) elif logger.isEnabledFor(logging.INFO): - logger.info('%s: unknown REPROG_CONTROLS_V4 %s', device, n) + logger.info("%s: unknown REPROG_CONTROLS_V4 %s", device, n) elif feature == _F.HIRES_WHEEL: - if (n.address == 0x00): + if n.address == 0x00: if logger.isEnabledFor(logging.INFO): - flags, delta_v = _unpack('>bh', n.data[:3]) + flags, delta_v = _unpack(">bh", n.data[:3]) high_res = (flags & 0x10) != 0 - periods = flags & 0x0f - logger.info('%s: WHEEL: res: %d periods: %d delta V:%-3d', device, high_res, periods, delta_v) - elif (n.address == 0x10): + periods = flags & 0x0F + logger.info("%s: WHEEL: res: %d periods: %d delta V:%-3d", device, high_res, periods, delta_v) + elif n.address == 0x10: ratchet = n.data[0] if logger.isEnabledFor(logging.INFO): - logger.info('%s: WHEEL: ratchet: %d', device, ratchet) + logger.info("%s: WHEEL: ratchet: %d", device, ratchet) if ratchet < 2: # don't process messages with unusual ratchet values if device.setting_callback: device.setting_callback(device, _st.ScrollRatchet, [2 if ratchet else 1]) else: if logger.isEnabledFor(logging.INFO): - logger.info('%s: unknown WHEEL %s', device, n) + logger.info("%s: unknown WHEEL %s", device, n) elif feature == _F.ONBOARD_PROFILES: - if (n.address > 0x10): + if n.address > 0x10: if logger.isEnabledFor(logging.INFO): - logger.info('%s: unknown ONBOARD PROFILES %s', device, n) + logger.info("%s: unknown ONBOARD PROFILES %s", device, n) else: - if (n.address == 0x00): - profile_sector = _unpack('!H', n.data[:2])[0] + if n.address == 0x00: + profile_sector = _unpack("!H", n.data[:2])[0] if 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] + 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] if device.setting_callback: for profile in device.profiles.profiles.values() if device.profiles else []: if profile.sector == profile_sector: diff --git a/lib/logitech_receiver/notify.py b/lib/logitech_receiver/notify.py index 939c25e08f..83a659b9f6 100644 --- a/lib/logitech_receiver/notify.py +++ b/lib/logitech_receiver/notify.py @@ -22,9 +22,11 @@ try: import gi - gi.require_version('Notify', '0.7') - gi.require_version('Gtk', '3.0') + + gi.require_version("Notify", "0.7") + gi.require_version("Gtk", "3.0") from gi.repository import GLib, Gtk, Notify # this import is allowed to fail making the entire feature unavailable + available = True except (ValueError, ImportError): available = False @@ -39,18 +41,18 @@ def init(): if available: if not Notify.is_initted(): if logger.isEnabledFor(logging.INFO): - logger.info('starting desktop notifications') + logger.info("starting desktop notifications") try: - return Notify.init('solaar') # replace with better name later + return Notify.init("solaar") # replace with better name later except Exception: - logger.exception('initializing desktop notifications') + logger.exception("initializing desktop notifications") available = False return available and Notify.is_initted() def uninit(): if available and Notify.is_initted(): if logger.isEnabledFor(logging.INFO): - logger.info('stopping desktop notifications') + logger.info("stopping desktop notifications") _notifications.clear() Notify.uninit() @@ -64,32 +66,32 @@ def show(dev, message, icon=None): icon_name = device_icon_name(dev.name, dev.kind) if icon is None else icon n.update(summary, message, icon_name) n.set_urgency(Notify.Urgency.NORMAL) - n.set_hint('desktop-entry', GLib.Variant('s', 'solaar')) # replace with better name late + n.set_hint("desktop-entry", GLib.Variant("s", "solaar")) # replace with better name late try: # if logger.isEnabledFor(logging.DEBUG): # logger.debug("showing %s", n) n.show() except Exception: - logger.exception('showing %s', n) + logger.exception("showing %s", n) _ICON_LISTS = {} - def device_icon_list(name='_', kind=None): + def device_icon_list(name="_", kind=None): icon_list = _ICON_LISTS.get(name) if icon_list is None: # names of possible icons, in reverse order of likelihood # the theme will hopefully pick up the most appropriate - icon_list = ['preferences-desktop-peripherals'] + icon_list = ["preferences-desktop-peripherals"] if kind: - if str(kind) == 'numpad': - icon_list += ('input-keyboard', 'input-dialpad') - elif str(kind) == 'touchpad': - icon_list += ('input-mouse', 'input-tablet') - elif str(kind) == 'trackball': - icon_list += ('input-mouse', ) - elif str(kind) == 'headset': - icon_list += ('audio-headphones', 'audio-headset') - icon_list += ('input-' + str(kind), ) + if str(kind) == "numpad": + icon_list += ("input-keyboard", "input-dialpad") + elif str(kind) == "touchpad": + icon_list += ("input-mouse", "input-tablet") + elif str(kind) == "trackball": + icon_list += ("input-mouse",) + elif str(kind) == "headset": + icon_list += ("audio-headphones", "audio-headset") + icon_list += ("input-" + str(kind),) _ICON_LISTS[name] = icon_list return icon_list diff --git a/lib/logitech_receiver/receiver.py b/lib/logitech_receiver/receiver.py index 7b593880e5..9b08193273 100644 --- a/lib/logitech_receiver/receiver.py +++ b/lib/logitech_receiver/receiver.py @@ -44,6 +44,7 @@ class Receiver: The paired devices are available through the sequence interface. """ + number = 0xFF kind = None @@ -56,33 +57,36 @@ def __init__(self, handle, path, product_id, setting_callback=None): self.setting_callback = setting_callback product_info = _product_information(self.product_id) if not product_info: - logger.warning('Unknown receiver type: %s', self.product_id) + logger.warning("Unknown receiver type: %s", self.product_id) product_info = {} - self.receiver_kind = product_info.get('receiver_kind', 'unknown') + self.receiver_kind = product_info.get("receiver_kind", "unknown") # read the serial immediately, so we can find out max_devices - if self.receiver_kind == 'bolt': + if self.receiver_kind == "bolt": serial_reply = self.read_register(_R.bolt_uniqueId) self.serial = _strhex(serial_reply) - self.max_devices = product_info.get('max_devices', 1) - self.may_unpair = product_info.get('may_unpair', False) + self.max_devices = product_info.get("max_devices", 1) + self.may_unpair = product_info.get("may_unpair", False) else: serial_reply = self.read_register(_R.receiver_info, _IR.receiver_information) if serial_reply: self.serial = _strhex(serial_reply[1:5]) self.max_devices = ord(serial_reply[6:7]) if self.max_devices <= 0 or self.max_devices > 6: - self.max_devices = product_info.get('max_devices', 1) - self.may_unpair = product_info.get('may_unpair', False) + self.max_devices = product_info.get("max_devices", 1) + self.may_unpair = product_info.get("may_unpair", False) else: # handle receivers that don't have a serial number specially (i.e., c534 and Bolt receivers) self.serial = None - self.max_devices = product_info.get('max_devices', 1) - self.may_unpair = product_info.get('may_unpair', False) - - self.name = product_info.get('name', 'Receiver') - self.re_pairs = product_info.get('re_pairs', False) - self._str = '<%s(%s,%s%s)>' % ( - self.name.replace(' ', ''), self.path, '' if isinstance(self.handle, int) else 'T', self.handle + self.max_devices = product_info.get("max_devices", 1) + self.may_unpair = product_info.get("may_unpair", False) + + self.name = product_info.get("name", "Receiver") + self.re_pairs = product_info.get("re_pairs", False) + self._str = "<%s(%s,%s%s)>" % ( + self.name.replace(" ", ""), + self.path, + "" if isinstance(self.handle, int) else "T", + self.handle, ) self._firmware = None @@ -95,7 +99,7 @@ def close(self): if d: d.close() self._devices.clear() - return (handle and _base.close(handle)) + return handle and _base.close(handle) def __del__(self): self.close() @@ -131,36 +135,36 @@ def enable_connection_notifications(self, enable=True): set_flag_bits = 0 ok = _hidpp10.set_notification_flags(self, set_flag_bits) if ok is None: - logger.warning('%s: failed to %s receiver notifications', self, 'enable' if enable else 'disable') + logger.warning("%s: failed to %s receiver notifications", self, "enable" if enable else "disable") return None flag_bits = _hidpp10.get_notification_flags(self) flag_names = None if flag_bits is None else tuple(_hidpp10_constants.NOTIFICATION_FLAG.flag_names(flag_bits)) if logger.isEnabledFor(logging.INFO): - logger.info('%s: receiver notifications %s => %s', self, 'enabled' if enable else 'disabled', flag_names) + logger.info("%s: receiver notifications %s => %s", self, "enabled" if enable else "disabled", flag_names) return flag_bits def device_codename(self, n): - if self.receiver_kind == 'bolt': + if self.receiver_kind == "bolt": codename = self.read_register(_R.receiver_info, _IR.bolt_device_name + n, 0x01) if codename: - codename = codename[3:3 + min(14, ord(codename[2:3]))] - return codename.decode('ascii') + codename = codename[3 : 3 + min(14, ord(codename[2:3]))] + return codename.decode("ascii") return codename = self.read_register(_R.receiver_info, _IR.device_name + n - 1) if codename: - codename = codename[2:2 + ord(codename[1:2])] - return codename.decode('ascii') + codename = codename[2 : 2 + ord(codename[1:2])] + return codename.decode("ascii") def device_pairing_information(self, n): - if self.receiver_kind == 'bolt': + if self.receiver_kind == "bolt": pair_info = self.read_register(_R.receiver_info, _IR.bolt_pairing_information + n) if pair_info: wpid = _strhex(pair_info[3:4]) + _strhex(pair_info[2:3]) kind = _hidpp10_constants.DEVICE_KIND[ord(pair_info[1:2]) & 0x0F] return wpid, kind, 0 else: - raise exceptions.NoSuchDevice(number=n, receiver=self, error='read Bolt wpid') + raise exceptions.NoSuchDevice(number=n, receiver=self, error="read Bolt wpid") wpid = 0 kind = None polling_rate = None @@ -168,35 +172,35 @@ def device_pairing_information(self, n): if pair_info: # may be either a Unifying receiver, or an Unifying-ready receiver wpid = _strhex(pair_info[3:5]) kind = _hidpp10_constants.DEVICE_KIND[ord(pair_info[7:8]) & 0x0F] - polling_rate = str(ord(pair_info[2:3])) + 'ms' - elif self.receiver_kind == '27Mz': # 27Mhz receiver, fill extracting WPID from udev path + polling_rate = str(ord(pair_info[2:3])) + "ms" + elif self.receiver_kind == "27Mz": # 27Mhz receiver, fill extracting WPID from udev path wpid = _hid.find_paired_node_wpid(self.path, n) if not wpid: - logger.error('Unable to get wpid from udev for device %d of %s', n, self) - raise exceptions.NoSuchDevice(number=n, receiver=self, error='Not present 27Mhz device') + logger.error("Unable to get wpid from udev for device %d of %s", n, self) + raise exceptions.NoSuchDevice(number=n, receiver=self, error="Not present 27Mhz device") kind = _hidpp10_constants.DEVICE_KIND[self.get_kind_from_index(n)] - elif not self.receiver_kind == 'unifying': # unifying protocol not supported, may be an old Nano receiver + elif not self.receiver_kind == "unifying": # unifying protocol not supported, may be an old Nano receiver device_info = self.read_register(_R.receiver_info, 0x04) if device_info: wpid = _strhex(device_info[3:5]) kind = _hidpp10_constants.DEVICE_KIND[0x00] # unknown kind else: - raise exceptions.NoSuchDevice(number=n, receiver=self, error='read pairing information - non-unifying') + raise exceptions.NoSuchDevice(number=n, receiver=self, error="read pairing information - non-unifying") else: - raise exceptions.NoSuchDevice(number=n, receiver=self, error='read pairing information') + raise exceptions.NoSuchDevice(number=n, receiver=self, error="read pairing information") return wpid, kind, polling_rate def device_extended_pairing_information(self, n): serial = None - power_switch = '(unknown)' - if self.receiver_kind == 'bolt': + power_switch = "(unknown)" + if self.receiver_kind == "bolt": pair_info = self.read_register(_R.receiver_info, _IR.bolt_pairing_information + n) if pair_info: serial = _strhex(pair_info[4:8]) return serial, power_switch else: - return '?', power_switch + return "?", power_switch pair_info = self.read_register(_R.receiver_info, _IR.extended_pairing_information + n - 1) if pair_info: power_switch = _hidpp10_constants.POWER_SWITCH_LOCATION[ord(pair_info[9:10]) & 0x0F] @@ -220,19 +224,19 @@ def get_kind_from_index(self, index): elif index == 4: # numpad kind = 3 else: # unknown device number on 27Mhz receiver - logger.error('failed to calculate device kind for device %d of %s', index, self) - raise exceptions.NoSuchDevice(number=index, receiver=self, error='Unknown 27Mhz device number') + logger.error("failed to calculate device kind for device %d of %s", index, self) + raise exceptions.NoSuchDevice(number=index, receiver=self, error="Unknown 27Mhz device number") return kind def notify_devices(self): """Scan all devices.""" if self.handle: if not self.write_register(_R.receiver_connection, 0x02): - logger.warning('%s: failed to trigger device link notifications', self) + logger.warning("%s: failed to trigger device link notifications", self) def register_new_device(self, number, notification=None): if self._devices.get(number) is not None: - raise IndexError('%s: device number %d already registered' % (self, number)) + raise IndexError("%s: device number %d already registered" % (self, number)) assert notification is None or notification.devnumber == number assert notification is None or notification.sub_id == 0x41 @@ -240,13 +244,13 @@ def register_new_device(self, number, notification=None): try: dev = Device(self, number, notification, setting_callback=self.setting_callback) if logger.isEnabledFor(logging.INFO): - logger.info('%s: found new device %d (%s)', self, number, dev.wpid) + logger.info("%s: found new device %d (%s)", self, number, dev.wpid) self._devices[number] = dev return dev except exceptions.NoSuchDevice as e: - logger.warning('register new device failed for %s device %d error %s', e.receiver, e.number, e.error) + logger.warning("register new device failed for %s device %d error %s", e.receiver, e.number, e.error) - logger.warning('%s: looked for device %d, not found', self, number) + logger.warning("%s: looked for device %d, not found", self, number) self._devices[number] = None def set_lock(self, lock_closed=True, device=0, timeout=0): @@ -255,25 +259,25 @@ def set_lock(self, lock_closed=True, device=0, timeout=0): reply = self.write_register(_R.receiver_pairing, action, device, timeout) if reply: return True - logger.warning('%s: failed to %s the receiver lock', self, 'close' if lock_closed else 'open') + logger.warning("%s: failed to %s the receiver lock", self, "close" if lock_closed else "open") def discover(self, cancel=False, timeout=30): # Bolt device discovery - assert self.receiver_kind == 'bolt' + assert self.receiver_kind == "bolt" if self.handle: action = 0x02 if cancel else 0x01 reply = self.write_register(_R.bolt_device_discovery, timeout, action) if reply: return True - logger.warning('%s: failed to %s device discovery', self, 'cancel' if cancel else 'start') + logger.warning("%s: failed to %s device discovery", self, "cancel" if cancel else "start") - def pair_device(self, pair=True, slot=0, address=b'\0\0\0\0\0\0', authentication=0x00, entropy=20): # Bolt pairing - assert self.receiver_kind == 'bolt' + def pair_device(self, pair=True, slot=0, address=b"\0\0\0\0\0\0", authentication=0x00, entropy=20): # Bolt pairing + assert self.receiver_kind == "bolt" if self.handle: action = 0x01 if pair is True else 0x03 if pair is False else 0x02 reply = self.write_register(_R.bolt_pairing, action, slot, address, authentication, entropy) if reply: return True - logger.warning('%s: failed to %s device %s', self, 'pair' if pair else 'unpair', address) + logger.warning("%s: failed to %s device %s", self, "pair" if pair else "unpair", address) def count(self): count = self.read_register(_R.receiver_connection) @@ -312,7 +316,7 @@ def __getitem__(self, key): return dev if not isinstance(key, int): - raise TypeError('key must be an integer') + raise TypeError("key must be an integer") if key < 1 or key > 15: # some receivers have devices past their max # devices raise IndexError(key) @@ -339,9 +343,9 @@ def _unpair_device(self, key, force=False): dev.wpid = None if key in self._devices: del self._devices[key] - logger.warning('%s removed device %s', self, dev) + logger.warning("%s removed device %s", self, dev) else: - if self.receiver_kind == 'bolt': + if self.receiver_kind == "bolt": reply = self.write_register(_R.bolt_pairing, 0x03, key) else: reply = self.write_register(_R.receiver_pairing, 0x03, key) @@ -352,10 +356,10 @@ def _unpair_device(self, key, force=False): if key in self._devices: del self._devices[key] if logger.isEnabledFor(logging.INFO): - logger.info('%s unpaired device %s', self, dev) + logger.info("%s unpaired device %s", self, dev) else: - logger.error('%s failed to unpair device %s', self, dev) - raise Exception('failed to unpair device %s: %s' % (dev.name, key)) + logger.error("%s failed to unpair device %s", self, dev) + raise Exception("failed to unpair device %s: %s" % (dev.name, key)) def __len__(self): return len([d for d in self._devices.values() if d is not None]) @@ -393,8 +397,8 @@ def open(self, device_info, setting_callback=None): if handle: return Receiver(handle, device_info.path, device_info.product_id, setting_callback) except OSError as e: - logger.exception('open %s', device_info) + logger.exception("open %s", device_info) if e.errno == _errno.EACCES: raise except Exception: - logger.exception('open %s', device_info) + logger.exception("open %s", device_info) diff --git a/lib/logitech_receiver/settings.py b/lib/logitech_receiver/settings.py index 7ef1f98b4f..8a7b9417f5 100644 --- a/lib/logitech_receiver/settings.py +++ b/lib/logitech_receiver/settings.py @@ -35,7 +35,7 @@ # # -SENSITIVITY_IGNORE = 'ignore' +SENSITIVITY_IGNORE = "ignore" KIND = _NamedInts( toggle=0x01, choice=0x02, @@ -44,7 +44,7 @@ multiple_toggle=0x10, packed_range=0x20, multiple_range=0x40, - hetero=0x80 + hetero=0x80, ) @@ -55,25 +55,24 @@ def bool_or_toggle(current, new): return bool(int(new)) except (TypeError, ValueError): new = str(new).lower() - if new in ('true', 'yes', 'on', 't', 'y'): + if new in ("true", "yes", "on", "t", "y"): return True - if new in ('false', 'no', 'off', 'f', 'n'): + if new in ("false", "no", "off", "f", "n"): return False - if new in ('~', 'toggle'): + if new in ("~", "toggle"): return not current return None # moved first for dependency reasons class Validator: - @classmethod def build(cls, setting_class, device, **kwargs): return cls(**kwargs) @classmethod def to_string(cls, value): - return (str(value)) + return str(value) def compare(self, args, current): if len(args) != 1: @@ -82,7 +81,7 @@ def compare(self, args, current): class BooleanValidator(Validator): - __slots__ = ('true_value', 'false_value', 'read_skip_byte_count', 'write_prefix_bytes', 'mask', 'needs_current_value') + __slots__ = ("true_value", "false_value", "read_skip_byte_count", "write_prefix_bytes", "mask", "needs_current_value") kind = KIND.toggle default_true = 0x01 @@ -96,7 +95,7 @@ def __init__( false_value=default_false, mask=default_mask, read_skip_byte_count=0, - write_prefix_bytes=b'' + write_prefix_bytes=b"", ): if isinstance(true_value, int): assert isinstance(false_value, int) @@ -107,14 +106,14 @@ def __init__( assert true_value & false_value == 0 assert true_value & mask == true_value assert false_value & mask == false_value - self.needs_current_value = (mask != self.default_mask) + self.needs_current_value = mask != self.default_mask elif isinstance(true_value, bytes): if false_value is None or false_value == self.default_false: - false_value = b'\x00' * len(true_value) + false_value = b"\x00" * len(true_value) else: assert isinstance(false_value, bytes) if mask is None or mask == self.default_mask: - mask = b'\xFF' * len(true_value) + mask = b"\xFF" * len(true_value) else: assert isinstance(mask, bytes) assert len(mask) == len(true_value) == len(false_value) @@ -124,7 +123,7 @@ def __init__( assert tv != fv # true and false might be something other than bit values assert tv & mv == tv assert fv & mv == fv - self.needs_current_value = any(m != 0xff for m in mask) + self.needs_current_value = any(m != 0xFF for m in mask) else: raise Exception("invalid mask '%r', type %s" % (mask, type(mask))) @@ -135,18 +134,21 @@ def __init__( self.write_prefix_bytes = write_prefix_bytes def validate_read(self, reply_bytes): - reply_bytes = reply_bytes[self.read_skip_byte_count:] + reply_bytes = reply_bytes[self.read_skip_byte_count :] if isinstance(self.mask, int): reply_value = ord(reply_bytes[:1]) & self.mask if logger.isEnabledFor(logging.DEBUG): - logger.debug('BooleanValidator: validate read %r => %02X', reply_bytes, reply_value) + logger.debug("BooleanValidator: validate read %r => %02X", reply_bytes, reply_value) if reply_value == self.true_value: return True if reply_value == self.false_value: return False logger.warning( - 'BooleanValidator: reply %02X mismatched %02X/%02X/%02X', reply_value, self.true_value, self.false_value, - self.mask + "BooleanValidator: reply %02X mismatched %02X/%02X/%02X", + reply_value, + self.true_value, + self.false_value, + self.mask, ) return False @@ -163,7 +165,7 @@ def validate_read(self, reply_bytes): return False logger.warning( - 'BooleanValidator: reply %r mismatched %r/%r/%r', reply_bytes, self.true_value, self.false_value, self.mask + "BooleanValidator: reply %r mismatched %r/%r/%r", reply_bytes, self.true_value, self.false_value, self.mask ) return False @@ -171,7 +173,7 @@ def prepare_write(self, new_value, current_value=None): if new_value is None: new_value = False else: - assert isinstance(new_value, bool), 'New value %s for boolean setting is not a boolean' % new_value + assert isinstance(new_value, bool), "New value %s for boolean setting is not a boolean" % new_value to_write = self.true_value if new_value else self.false_value @@ -185,20 +187,20 @@ def prepare_write(self, new_value, current_value=None): to_write = bytearray(to_write) count = len(self.mask) for i in range(0, count): - b = ord(to_write[i:i + 1]) - m = ord(self.mask[i:i + 1]) + b = ord(to_write[i : i + 1]) + m = ord(self.mask[i : i + 1]) assert b & m == b # b &= m if current_value is not None and self.needs_current_value: - b |= ord(current_value[i:i + 1]) & (0xFF ^ m) + b |= ord(current_value[i : i + 1]) & (0xFF ^ m) to_write[i] = b to_write = bytes(to_write) - if current_value is not None and to_write == current_value[:len(to_write)]: + if current_value is not None and to_write == current_value[: len(to_write)]: return None if logger.isEnabledFor(logging.DEBUG): - logger.debug('BooleanValidator: prepare_write(%s, %s) => %r', new_value, current_value, to_write) + logger.debug("BooleanValidator: prepare_write(%s, %s) => %r", new_value, current_value, to_write) return self.write_prefix_bytes + to_write @@ -211,7 +213,8 @@ def acceptable(self, args, current): class Setting: """A setting descriptor. Needs to be instantiated for each specific device.""" - name = label = description = '' + + name = label = description = "" feature = register = kind = None min_version = 0 persist = True @@ -223,13 +226,13 @@ def __init__(self, device, rw, validator): self._device = device self._rw = rw self._validator = validator - self.kind = getattr(self._validator, 'kind', None) + self.kind = getattr(self._validator, "kind", None) self._value = None @classmethod def build(cls, device): - assert cls.feature or cls.register, 'Settings require either a feature or a register' - rw_class = cls.rw_class if hasattr(cls, 'rw_class') else FeatureRW if cls.feature else RegisterRW + assert cls.feature or cls.register, "Settings require either a feature or a register" + rw_class = cls.rw_class if hasattr(cls, "rw_class") else FeatureRW if cls.feature else RegisterRW rw = rw_class(cls.feature if cls.feature else cls.register, **cls.rw_options) p = device.protocol if p == 1.0: # HID++ 1.0 devices do not support features @@ -247,38 +250,38 @@ def val_to_string(self, value): @property def choices(self): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") return self._validator.choices if self._validator and self._validator.kind & KIND.choice else None @property def range(self): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") if self._validator.kind == KIND.range: return (self._validator.min_value, self._validator.max_value) def _pre_read(self, cached, key=None): - if self.persist and self._value is None and getattr(self._device, 'persister', None): + if self.persist and self._value is None and getattr(self._device, "persister", None): # We haven't read a value from the device yet, # maybe we have something in the configuration. self._value = self._device.persister.get(self.name) if cached and self._value is not None: - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # If this is a new device (or a new setting for an old device), # make sure to save its current value for the next time. self._device.persister[self.name] = self._value if self.persist else None def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") self._pre_read(cached) if cached and self._value is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: cached value %r on %s', self.name, self._value, self._device) + logger.debug("%s: cached value %r on %s", self.name, self._value, self._device) return self._value if self._device.online: @@ -290,7 +293,7 @@ def read(self, cached=True): # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: read value %r on %s', self.name, self._value, self._device) + logger.debug("%s: read value %r on %s", self.name, self._value, self._device) return self._value def _pre_write(self, save=True): @@ -305,12 +308,12 @@ def update(self, value, save=True): self._pre_write(save) def write(self, value, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert value is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: write %r to %s', self.name, value, self._device) + logger.debug("%s: write %r to %s", self.name, value, self._device) if self._device.online: if self._value != value: @@ -321,12 +324,12 @@ def write(self, value, save=True): # the _validator needs the current value, possibly to merge flag values current_value = self._rw.read(self._device) if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: current value %r on %s', self.name, current_value, self._device) + logger.debug("%s: current value %r on %s", self.name, current_value, self._device) data_bytes = self._validator.prepare_write(value, current_value) if data_bytes is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: prepare write(%s) => %r', self.name, value, data_bytes) + logger.debug("%s: prepare write(%s) => %r", self.name, value, data_bytes) reply = self._rw.write(self._device, data_bytes) if not reply: @@ -342,10 +345,10 @@ def compare(self, args, current): return self._validator.compare(args, current) if self._validator else None def apply(self): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: apply (%s)', self.name, self._device) + logger.debug("%s: apply (%s)", self.name, self._device) value = self.read(self.persist) # Don't use persisted value if setting doesn't persist if self.persist and value is not None: # If setting doesn't persist no need to write value just read try: @@ -353,16 +356,20 @@ def apply(self): except Exception as e: if logger.isEnabledFor(logging.WARNING): logger.warning( - '%s: error applying value %s so ignore it (%s): %s', self.name, self._value, self._device, repr(e) + "%s: error applying value %s so ignore it (%s): %s", self.name, self._value, self._device, repr(e) ) def __str__(self): - if hasattr(self, '_value'): - assert hasattr(self, '_device') - return '' % ( - self._rw.kind, self._validator.kind if self._validator else None, self._device.codename, self.name, self._value + if hasattr(self, "_value"): + assert hasattr(self, "_device") + return "" % ( + self._rw.kind, + self._validator.kind if self._validator else None, + self._device.codename, + self.name, + self._value, ) - return '' % (self._rw.kind, self._validator.kind if self._validator else None, self.name) + return "" % (self._rw.kind, self._validator.kind if self._validator else None, self.name) __repr__ = __str__ @@ -372,10 +379,10 @@ class Settings(Setting): Needs to be instantiated for each specific device.""" def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings read %r from %s', self.name, self._value, self._device) + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) self._pre_read(cached) @@ -389,18 +396,18 @@ def read(self, cached=True): if reply: reply_map[int(key)] = self._validator.validate_read(reply, key) self._value = reply_map - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None return self._value def read_key(self, key, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert key is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings read %r key %r from %s', self.name, self._value, key, self._device) + logger.debug("%s: settings read %r key %r from %s", self.name, self._value, key, self._device) self._pre_read(cached) if cached and self._value is not None: @@ -410,17 +417,17 @@ def read_key(self, key, cached=True): reply = self._rw.read(self._device, key) if reply: self._value[int(key)] = self._validator.validate_read(reply, key) - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: self._device.persister[self.name] = self._value if self.persist else None return self._value[int(key)] def write(self, map, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert map is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings write %r to %s', self.name, map, self._device) + logger.debug("%s: settings write %r to %s", self.name, map, self._device) if self._device.online: self.update(map, save) @@ -428,7 +435,7 @@ def write(self, map, save=True): data_bytes = self._validator.prepare_write(int(key), value) if data_bytes is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings prepare map write(%s,%s) => %r', self.name, key, value, data_bytes) + logger.debug("%s: settings prepare map write(%s,%s) => %r", self.name, key, value, data_bytes) reply = self._rw.write(self._device, int(key), data_bytes) if not reply: return None @@ -439,13 +446,13 @@ def update_key_value(self, key, value, save=True): self._pre_write(save) def write_key_value(self, key, value, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert key is not None assert value is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings write key %r value %r to %s', self.name, key, value, self._device) + logger.debug("%s: settings write key %r value %r to %s", self.name, key, value, self._device) if self._device.online: if not self._value: @@ -458,7 +465,7 @@ def write_key_value(self, key, value, save=True): data_bytes = value = None if data_bytes is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings prepare key value write(%s,%s) => %r', self.name, key, value, data_bytes) + logger.debug("%s: settings prepare key value write(%s,%s) => %r", self.name, key, value, data_bytes) reply = self._rw.write(self._device, int(key), data_bytes) if not reply: return None @@ -472,10 +479,10 @@ class LongSettings(Setting): Needs to be instantiated for each specific device.""" def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings read %r from %s', self.name, self._value, self._device) + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) self._pre_read(cached) @@ -491,18 +498,18 @@ def read(self, cached=True): if reply: reply_map[int(item)] = self._validator.validate_read_item(reply, item) self._value = reply_map - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None return self._value def read_item(self, item, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert item is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings read %r item %r from %s', self.name, self._value, item, self._device) + logger.debug("%s: settings read %r item %r from %s", self.name, self._value, item, self._device) self._pre_read(cached) if cached and self._value is not None: @@ -513,17 +520,17 @@ def read_item(self, item, cached=True): reply = self._rw.read(self._device, r) if reply: self._value[int(item)] = self._validator.validate_read_item(reply, item) - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: self._device.persister[self.name] = self._value if self.persist else None return self._value[int(item)] def write(self, map, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert map is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: long settings write %r to %s', self.name, map, self._device) + logger.debug("%s: long settings write %r to %s", self.name, map, self._device) if self._device.online: self.update(map, save) for item, value in map.items(): @@ -532,7 +539,7 @@ def write(self, map, save=True): for data_bytes in data_bytes_list: if data_bytes is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings prepare map write(%s,%s) => %r', self.name, item, value, data_bytes) + logger.debug("%s: settings prepare map write(%s,%s) => %r", self.name, item, value, data_bytes) reply = self._rw.write(self._device, data_bytes) if not reply: return None @@ -543,13 +550,13 @@ def update_key_value(self, key, value, save=True): self._pre_write(save) def write_key_value(self, item, value, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert item is not None assert value is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: long settings write item %r value %r to %s', self.name, item, value, self._device) + logger.debug("%s: long settings write item %r value %r to %s", self.name, item, value, self._device) if self._device.online: if not self._value: @@ -558,7 +565,7 @@ def write_key_value(self, item, value, save=True): self.update_key_value(item, value, save) if data_bytes is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings prepare item value write(%s,%s) => %r', self.name, item, value, data_bytes) + logger.debug("%s: settings prepare item value write(%s,%s) => %r", self.name, item, value, data_bytes) reply = self._rw.write(self._device, data_bytes) if not reply: return None @@ -570,10 +577,10 @@ class BitFieldSetting(Setting): Needs to be instantiated for each specific device.""" def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings read %r from %s', self.name, self._value, self._device) + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) self._pre_read(cached) @@ -586,7 +593,7 @@ def read(self, cached=True): if reply: reply_map = self._validator.validate_read(reply) self._value = reply_map - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None @@ -596,11 +603,11 @@ def _do_read(self): return self._rw.read(self._device) def read_key(self, key, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert key is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings read %r key %r from %s', self.name, self._value, key, self._device) + logger.debug("%s: settings read %r key %r from %s", self.name, self._value, key, self._device) self._pre_read(cached) @@ -611,7 +618,7 @@ def read_key(self, key, cached=True): reply = self._do_read_key(key) if reply: self._value = self._validator.validate_read(reply) - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: self._device.persister[self.name] = self._value if self.persist else None return self._value[int(key)] @@ -619,18 +626,18 @@ def _do_read_key(self, key): return self._rw.read(self._device, key) def write(self, map, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert map is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: bit field settings write %r to %s', self.name, map, self._device) + logger.debug("%s: bit field settings write %r to %s", self.name, map, self._device) if self._device.online: self.update(map, save) data_bytes = self._validator.prepare_write(self._value) if data_bytes is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings prepare map write(%s) => %r', self.name, self._value, data_bytes) + logger.debug("%s: settings prepare map write(%s) => %r", self.name, self._value, data_bytes) # if prepare_write returns a list, write one item at a time seq = data_bytes if isinstance(data_bytes, list) else [data_bytes] for b in seq: @@ -644,13 +651,13 @@ def update_key_value(self, key, value, save=True): self._pre_write(save) def write_key_value(self, key, value, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert key is not None assert value is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: bit field settings write key %r value %r to %s', self.name, key, value, self._device) + logger.debug("%s: bit field settings write key %r value %r to %s", self.name, key, value, self._device) if self._device.online: if not self._value: @@ -661,7 +668,7 @@ def write_key_value(self, key, value, save=True): data_bytes = self._validator.prepare_write(self._value) if data_bytes is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings prepare key value write(%s,%s) => %r', self.name, key, str(value), data_bytes) + logger.debug("%s: settings prepare key value write(%s,%s) => %r", self.name, key, str(value), data_bytes) # if prepare_write returns a list, write one item at a time seq = data_bytes if isinstance(data_bytes, list) else [data_bytes] for b in seq: @@ -690,10 +697,10 @@ class RangeFieldSetting(Setting): Needs to be instantiated for each specific device.""" def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: settings read %r from %s', self.name, self._value, self._device) + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) self._pre_read(cached) if cached and self._value is not None: return self._value @@ -703,7 +710,7 @@ def read(self, cached=True): if reply: reply_map = self._validator.validate_read(reply) self._value = reply_map - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None @@ -716,29 +723,29 @@ def read_key(self, key, cached=True): return self.read(cached)[int(key)] def write(self, map, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert map is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: range field setting write %r to %s', self.name, map, self._device) + logger.debug("%s: range field setting write %r to %s", self.name, map, self._device) if self._device.online: self.update(map, save) data_bytes = self._validator.prepare_write(self._value) if data_bytes is not None: if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: range field setting prepare map write(%s) => %r', self.name, self._value, data_bytes) + logger.debug("%s: range field setting prepare map write(%s) => %r", self.name, self._value, data_bytes) reply = self._rw.write(self._device, data_bytes) if not reply: return None elif logger.isEnabledFor(logging.WARNING): - logger.warning('%s: range field setting no data to write', self.name) + logger.warning("%s: range field setting no data to write", self.name) return map def write_key_value(self, key, value, save=True): assert key is not None assert value is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: range field setting write key %r value %r to %s', self.name, key, value, self._device) + logger.debug("%s: range field setting write key %r value %r to %s", self.name, key, value, self._device) if self._device.online: if not self._value: self.read() @@ -754,9 +761,9 @@ def write_key_value(self, key, value, save=True): class RegisterRW: - __slots__ = ('register', ) + __slots__ = ("register",) - kind = _NamedInt(0x01, _('register')) + kind = _NamedInt(0x01, _("register")) def __init__(self, register): assert isinstance(register, int) @@ -770,11 +777,11 @@ def write(self, device, data_bytes): class FeatureRW: - kind = _NamedInt(0x02, _('feature')) + kind = _NamedInt(0x02, _("feature")) default_read_fnid = 0x00 default_write_fnid = 0x10 - def __init__(self, feature, read_fnid=0x00, write_fnid=0x10, prefix=b'', suffix=b'', read_prefix=b'', no_reply=False): + def __init__(self, feature, read_fnid=0x00, write_fnid=0x10, prefix=b"", suffix=b"", read_prefix=b"", no_reply=False): assert isinstance(feature, _NamedInt) self.feature = feature self.read_fnid = read_fnid @@ -784,7 +791,7 @@ def __init__(self, feature, read_fnid=0x00, write_fnid=0x10, prefix=b'', suffix= self.suffix = suffix self.read_prefix = read_prefix - def read(self, device, data_bytes=b''): + def read(self, device, data_bytes=b""): assert self.feature is not None return device.feature_request(self.feature, self.read_fnid, self.prefix, self.read_prefix, data_bytes) @@ -797,7 +804,7 @@ def write(self, device, data_bytes): class FeatureRWMap(FeatureRW): - kind = _NamedInt(0x02, _('feature')) + kind = _NamedInt(0x02, _("feature")) default_read_fnid = 0x00 default_write_fnid = 0x10 default_key_byte_count = 1 @@ -808,7 +815,7 @@ def __init__( read_fnid=default_read_fnid, write_fnid=default_write_fnid, key_byte_count=default_key_byte_count, - no_reply=False + no_reply=False, ): assert isinstance(feature, _NamedInt) self.feature = feature @@ -830,28 +837,27 @@ def write(self, device, key, data_bytes): class BitFieldValidator(Validator): - __slots__ = ('byte_count', 'options') + __slots__ = ("byte_count", "options") kind = KIND.multiple_toggle def __init__(self, options, byte_count=None): - assert (isinstance(options, list)) + assert isinstance(options, list) self.options = options self.byte_count = (max(x.bit_length() for x in options) + 7) // 8 if byte_count: - assert (isinstance(byte_count, int) and byte_count >= self.byte_count) + assert isinstance(byte_count, int) and byte_count >= self.byte_count self.byte_count = byte_count def to_string(self, value): - def element_to_string(key, val): k = next((k for k in self.options if int(key) == k), None) - return str(k) + ':' + str(val) if k is not None else '?' + return str(k) + ":" + str(val) if k is not None else "?" - return '{' + ', '.join([element_to_string(k, value[k]) for k in value]) + '}' + return "{" + ", ".join([element_to_string(k, value[k]) for k in value]) + "}" def validate_read(self, reply_bytes): - r = _bytes2int(reply_bytes[:self.byte_count]) + r = _bytes2int(reply_bytes[: self.byte_count]) value = {int(k): False for k in self.options} m = 1 for _ignore in range(8 * self.byte_count): @@ -861,7 +867,7 @@ def validate_read(self, reply_bytes): return value def prepare_write(self, new_value): - assert (isinstance(new_value, dict)) + assert isinstance(new_value, dict) w = 0 for k, v in new_value.items(): if v: @@ -890,13 +896,13 @@ def compare(self, args, current): class BitFieldWithOffsetAndMaskValidator(Validator): - __slots__ = ('byte_count', 'options', '_option_from_key', '_mask_from_offset', '_option_from_offset_mask') + __slots__ = ("byte_count", "options", "_option_from_key", "_mask_from_offset", "_option_from_offset_mask") kind = KIND.multiple_toggle sep = 0x01 def __init__(self, options, om_method=None, byte_count=None): - assert (isinstance(options, list)) + assert isinstance(options, list) # each element of options is an instance of a class # that has an id (which is used as an index in other dictionaries) # and where om_method is a method that returns a byte offset and byte mask @@ -922,13 +928,13 @@ def __init__(self, options, om_method=None, byte_count=None): mask_to_opt[mask] = opt self.byte_count = (max(om_method(x)[1].bit_length() for x in options) + 7) // 8 # is this correct?? if byte_count: - assert (isinstance(byte_count, int) and byte_count >= self.byte_count) + assert isinstance(byte_count, int) and byte_count >= self.byte_count self.byte_count = byte_count def prepare_read(self): r = [] for offset, mask in self._mask_from_offset.items(): - b = (offset << (8 * (self.byte_count + 1))) + b = offset << (8 * (self.byte_count + 1)) b |= (self.sep << (8 * self.byte_count)) | mask r.append(_int2bytes(b, self.byte_count + 2)) return r @@ -946,8 +952,8 @@ def validate_read(self, reply_bytes_dict): values = {int(k): False for k in self.options} for query, b in reply_bytes_dict.items(): offset = _bytes2int(query[0:1]) - b += (self.byte_count - len(b)) * b'\x00' - value = _bytes2int(b[:self.byte_count]) + b += (self.byte_count - len(b)) * b"\x00" + value = _bytes2int(b[: self.byte_count]) mask_to_opt = self._option_from_offset_mask.get(offset, {}) m = 1 for _ignore in range(8 * self.byte_count): @@ -957,7 +963,7 @@ def validate_read(self, reply_bytes_dict): return values def prepare_write(self, new_value): - assert (isinstance(new_value, dict)) + assert isinstance(new_value, dict) w = {} for k, v in new_value.items(): option = self._option_from_key[int(k)] @@ -967,10 +973,14 @@ def prepare_write(self, new_value): if v: w[offset] |= mask return [ - _int2bytes((offset << (8 * (2 * self.byte_count + 1))) - | (self.sep << (16 * self.byte_count)) - | (self._mask_from_offset[offset] << (8 * self.byte_count)) - | value, 2 * self.byte_count + 2) for offset, value in w.items() + _int2bytes( + (offset << (8 * (2 * self.byte_count + 1))) + | (self.sep << (16 * self.byte_count)) + | (self._mask_from_offset[offset] << (8 * self.byte_count)) + | value, + 2 * self.byte_count + 2, + ) + for offset, value in w.items() ] def get_options(self): @@ -999,9 +1009,10 @@ class ChoicesValidator(Validator): :param choices: a list of NamedInts :param byte_count: the size of the derived byte sequence. If None, it will be calculated from the choices.""" + kind = KIND.choice - def __init__(self, choices=None, byte_count=None, read_skip_byte_count=0, write_prefix_bytes=b''): + def __init__(self, choices=None, byte_count=None, read_skip_byte_count=0, write_prefix_bytes=b""): assert choices is not None assert isinstance(choices, _NamedInts) assert len(choices) > 1 @@ -1015,7 +1026,7 @@ def __init__(self, choices=None, byte_count=None, read_skip_byte_count=0, write_ self._byte_count = byte_count assert self._byte_count < 8 self._read_skip_byte_count = read_skip_byte_count - self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b'' + self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b"" assert self._byte_count + self._read_skip_byte_count <= 14 assert self._byte_count + len(self._write_prefix_bytes) <= 14 @@ -1023,9 +1034,9 @@ def to_string(self, value): return str(self.choices[value]) if isinstance(value, int) else str(value) def validate_read(self, reply_bytes): - reply_value = _bytes2int(reply_bytes[self._read_skip_byte_count:self._read_skip_byte_count + self._byte_count]) + reply_value = _bytes2int(reply_bytes[self._read_skip_byte_count : self._read_skip_byte_count + self._byte_count]) valid_value = self.choices[reply_value] - assert valid_value is not None, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) + assert valid_value is not None, "%s: failed to validate read value %02X" % (self.__class__.__name__, reply_value) return valid_value def prepare_write(self, new_value, current_value=None): @@ -1034,7 +1045,7 @@ def prepare_write(self, new_value, current_value=None): else: value = self.choice(new_value) if value is None: - raise ValueError('invalid choice %r' % new_value) + raise ValueError("invalid choice %r" % new_value) assert isinstance(value, _NamedInt) return self._write_prefix_bytes + value.bytes(self._byte_count) @@ -1064,13 +1075,13 @@ def __init__( self, choices_map, key_byte_count=0, - key_postfix_bytes=b'', + key_postfix_bytes=b"", byte_count=0, read_skip_byte_count=0, - write_prefix_bytes=b'', + write_prefix_bytes=b"", extra_default=None, mask=-1, - activate=0 + activate=0, ): assert choices_map is not None assert isinstance(choices_map, dict) @@ -1097,19 +1108,18 @@ def __init__( self.extra_default = extra_default self._key_postfix_bytes = key_postfix_bytes self._read_skip_byte_count = read_skip_byte_count if read_skip_byte_count else 0 - self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b'' + self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b"" self.activate = activate self.mask = mask assert self._byte_count + self._read_skip_byte_count + self._key_byte_count <= 14 assert self._byte_count + len(self._write_prefix_bytes) + self._key_byte_count <= 14 def to_string(self, value): - def element_to_string(key, val): k, c = next(((k, c) for k, c in self.choices.items() if int(key) == k), (None, None)) - return str(k) + ':' + str(c[val]) if k is not None else '?' + return str(k) + ":" + str(c[val]) if k is not None else "?" - return '{' + ', '.join([element_to_string(k, value[k]) for k in sorted(value)]) + '}' + return "{" + ", ".join([element_to_string(k, value[k]) for k in sorted(value)]) + "}" def validate_read(self, reply_bytes, key): start = self._key_byte_count + self._read_skip_byte_count @@ -1119,20 +1129,22 @@ def validate_read(self, reply_bytes, key): if self.extra_default is not None and self.extra_default == reply_value: return int(self.choices[key][0]) if reply_value not in self.choices[key]: - assert reply_value in self.choices[ - key], '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) + assert reply_value in self.choices[key], "%s: failed to validate read value %02X" % ( + self.__class__.__name__, + reply_value, + ) return reply_value def prepare_key(self, key): - return key.to_bytes(self._key_byte_count, 'big') + self._key_postfix_bytes + return key.to_bytes(self._key_byte_count, "big") + self._key_postfix_bytes def prepare_write(self, key, new_value): choices = self.choices.get(key) if choices is None or (new_value not in choices and new_value != self.extra_default): - logger.error('invalid choice %r for %s', new_value, key) + logger.error("invalid choice %r for %s", new_value, key) return None new_value = new_value | self.activate - return self._write_prefix_bytes + new_value.to_bytes(self._byte_count, 'big') + return self._write_prefix_bytes + new_value.to_bytes(self._byte_count, "big") def acceptable(self, args, current): if len(args) != 2: @@ -1164,8 +1176,8 @@ class RangeValidator(Validator): @classmethod def build(cls, setting_class, device, **kwargs): - kwargs['min_value'] = setting_class.min_value - kwargs['max_value'] = setting_class.max_value + kwargs["min_value"] = setting_class.min_value + kwargs["max_value"] = setting_class.max_value return cls(**kwargs) def __init__(self, min_value=0, max_value=255, byte_count=1): @@ -1181,14 +1193,14 @@ def __init__(self, min_value=0, max_value=255, byte_count=1): assert self._byte_count < 8 def validate_read(self, reply_bytes): - reply_value = _bytes2int(reply_bytes[:self._byte_count]) - assert reply_value >= self.min_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) - assert reply_value <= self.max_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) + reply_value = _bytes2int(reply_bytes[: self._byte_count]) + assert reply_value >= self.min_value, "%s: failed to validate read value %02X" % (self.__class__.__name__, reply_value) + assert reply_value <= self.max_value, "%s: failed to validate read value %02X" % (self.__class__.__name__, reply_value) return reply_value def prepare_write(self, new_value, current_value=None): if new_value < self.min_value or new_value > self.max_value: - raise ValueError('invalid choice %r' % new_value) + raise ValueError("invalid choice %r" % new_value) current_value = self.validate_read(current_value) if current_value is not None else None to_write = _int2bytes(new_value, self._byte_count) # current value is known and same as value to be written return None to signal not to write it @@ -1242,10 +1254,10 @@ class PackedRangeValidator(Validator): max_value = 255 count = 1 rsbc = 0 - write_prefix_bytes = b'' + write_prefix_bytes = b"" def __init__( - self, keys, min_value=0, max_value=255, count=1, byte_count=1, read_skip_byte_count=0, write_prefix_bytes=b'' + self, keys, min_value=0, max_value=255, count=1, byte_count=1, read_skip_byte_count=0, write_prefix_bytes=b"" ): assert max_value > min_value self.needs_current_value = True @@ -1263,21 +1275,21 @@ def __init__( def validate_read(self, reply_bytes): rvs = { - n: _bytes2int(reply_bytes[self.rsbc + n * self.bc:self.rsbc + (n + 1) * self.bc], signed=True) + n: _bytes2int(reply_bytes[self.rsbc + n * self.bc : self.rsbc + (n + 1) * self.bc], signed=True) for n in range(self.count) } for n in range(self.count): - assert rvs[n] >= self.min_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, rvs[n]) - assert rvs[n] <= self.max_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, rvs[n]) + assert rvs[n] >= self.min_value, "%s: failed to validate read value %02X" % (self.__class__.__name__, rvs[n]) + assert rvs[n] <= self.max_value, "%s: failed to validate read value %02X" % (self.__class__.__name__, rvs[n]) return rvs def prepare_write(self, new_values): if len(new_values) != self.count: - raise ValueError('wrong number of values %r' % new_values) + raise ValueError("wrong number of values %r" % new_values) for new_value in new_values: if new_value < self.min_value or new_value > self.max_value: - raise ValueError('invalid value %r' % new_value) - bytes = self.write_prefix_bytes + b''.join(_int2bytes(new_values[n], self.bc, signed=True) for n in range(self.count)) + raise ValueError("invalid value %r" % new_value) + bytes = self.write_prefix_bytes + b"".join(_int2bytes(new_values[n], self.bc, signed=True) for n in range(self.count)) return bytes def acceptable(self, args, current): @@ -1286,7 +1298,7 @@ def acceptable(self, args, current): return None if type(args[1]) != int or args[1] < self.min_value or args[1] > self.max_value else args def compare(self, args, current): - logger.warning('compare not implemented for packed range settings') + logger.warning("compare not implemented for packed range settings") return False @@ -1310,14 +1322,14 @@ def validate_read_item(self, reply_bytes, item): start = 0 value = {} for sub_item in self.sub_items[item]: - r = reply_bytes[start:start + sub_item.length] + r = reply_bytes[start : start + sub_item.length] if len(r) < sub_item.length: - r += b'\x00' * (sub_item.length - len(value)) + r += b"\x00" * (sub_item.length - len(value)) v = _bytes2int(r) if not (sub_item.minimum < v < sub_item.maximum): logger.warning( - f'{self.__class__.__name__}: failed to validate read value for {item}.{sub_item}: ' + - f'{v} not in [{sub_item.minimum}..{sub_item.maximum}]' + f"{self.__class__.__name__}: failed to validate read value for {item}.{sub_item}: " + + f"{v} not in [{sub_item.minimum}..{sub_item.maximum}]" ) value[str(sub_item)] = v start += sub_item.length @@ -1325,7 +1337,7 @@ def validate_read_item(self, reply_bytes, item): def prepare_write(self, value): seq = [] - w = b'' + w = b"" for item in value.keys(): _item = self._item_from_id[int(item)] b = _int2bytes(_item.index, 1) @@ -1336,14 +1348,14 @@ def prepare_write(self, value): return None if not (sub_item.minimum <= v <= sub_item.maximum): raise ValueError( - f'invalid choice for {item}.{sub_item}: {v} not in [{sub_item.minimum}..{sub_item.maximum}]' + f"invalid choice for {item}.{sub_item}: {v} not in [{sub_item.minimum}..{sub_item.maximum}]" ) b += _int2bytes(v, sub_item.length) if len(w) + len(b) > 15: - seq.append(b + b'\xFF') - w = b'' + seq.append(b + b"\xFF") + w = b"" w += b - seq.append(w + b'\xFF') + seq.append(w + b"\xFF") return seq def prepare_write_item(self, item, value): @@ -1355,9 +1367,9 @@ def prepare_write_item(self, item, value): except KeyError: return None if not (sub_item.minimum <= v <= sub_item.maximum): - raise ValueError(f'invalid choice for {item}.{sub_item}: {v} not in [{sub_item.minimum}..{sub_item.maximum}]') + raise ValueError(f"invalid choice for {item}.{sub_item}: {v} not in [{sub_item.minimum}..{sub_item.maximum}]") w += _int2bytes(v, sub_item.length) - return w + b'\xFF' + return w + b"\xFF" def acceptable(self, args, current): # just one item, with at least one sub-item @@ -1375,14 +1387,14 @@ def acceptable(self, args, current): return [int(item), {**args[1]}] def compare(self, args, current): - logger.warning('compare not implemented for multiple range settings') + logger.warning("compare not implemented for multiple range settings") return False class ActionSettingRW: """Special RW class for settings that turn on and off special processing when a key or button is depressed""" - def __init__(self, feature, name='', divert_setting_name='divert-keys'): + def __init__(self, feature, name="", divert_setting_name="divert-keys"): self.feature = feature # not used? self.name = name self.divert_setting_name = divert_setting_name @@ -1411,14 +1423,13 @@ def key_action(self, key): # acction to take when some other diverted key is pr pass def read(self, device): # need to return bytes, as if read from device - return _int2bytes(self.key.key, 2) if self.active and self.key else b'\x00\x00' + return _int2bytes(self.key.key, 2) if self.active and self.key else b"\x00\x00" def write(self, device, data_bytes): - def handler(device, n): # Called on notification events from the device if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == _hidpp20_constants.FEATURE.REPROG_CONTROLS_V4: if n.address == 0x00: - cids = _unpack('!HHHH', n.data[:8]) + cids = _unpack("!HHHH", n.data[:8]) if not self.pressed and int(self.key.key) in cids: # trigger key pressed self.pressed = True self.press_action() @@ -1432,12 +1443,12 @@ def handler(device, n): # Called on notification events from the device self.key_action(key) elif n.address == 0x10: if self.pressed: - dx, dy = _unpack('!hh', n.data[:4]) + dx, dy = _unpack("!hh", n.data[:4]) self.move_action(dx, dy) divertSetting = next(filter(lambda s: s.name == self.divert_setting_name, device.settings), None) if divertSetting is None: - logger.warning('setting %s not found on %s', self.divert_setting_name, device.name) + logger.warning("setting %s not found on %s", self.divert_setting_name, device.name) return None self.device = device key = _bytes2int(data_bytes) @@ -1452,7 +1463,7 @@ def handler(device, n): # Called on notification events from the device device.add_notification_handler(self.name, handler) self.activate_action() else: - logger.error('cannot enable %s on %s for key %s', self.name, device, key) + logger.error("cannot enable %s on %s for key %s", self.name, device, key) else: # Disable if self.active: self.active = False @@ -1464,7 +1475,7 @@ def handler(device, n): # Called on notification events from the device device.remove_notification_handler(self.name) except Exception: if logger.isEnabledFor(logging.WARNING): - logger.warning('cannot disable %s on %s', self.name, device) + logger.warning("cannot disable %s on %s", self.name, device) self.deactivate_action() return True @@ -1472,7 +1483,7 @@ def handler(device, n): # Called on notification events from the device class RawXYProcessing: """Special class for processing RawXY action messages initiated by pressing a key with rawXY diversion capability""" - def __init__(self, device, name=''): + def __init__(self, device, name=""): self.device = device self.name = name self.keys = [] # the keys that can initiate processing @@ -1484,7 +1495,7 @@ def __init__(self, device, name=''): def handler(self, device, n): # Called on notification events from the device if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == _hidpp20_constants.FEATURE.REPROG_CONTROLS_V4: if n.address == 0x00: - cids = _unpack('!HHHH', n.data[:8]) + cids = _unpack("!HHHH", n.data[:8]) ## generalize to list of keys if not self.initiating_key: # no initiating key pressed for k in self.keys: @@ -1502,7 +1513,7 @@ def handler(self, device, n): # Called on notification events from the device self.key_action(key) elif n.address == 0x10: if self.initiating_key: - dx, dy = _unpack('!hh', n.data[:4]) + dx, dy = _unpack("!hh", n.data[:4]) self.move_action(dx, dy) def start(self, key): @@ -1526,7 +1537,7 @@ def stop(self, key): # only stop if this is the active key self.device.remove_notification_handler(self.name) except Exception: if logger.isEnabledFor(logging.WARNING): - logger.warning('cannot disable %s on %s', self.name, self.device) + logger.warning("cannot disable %s on %s", self.name, self.device) self.deactivate_action() self.active = False @@ -1552,8 +1563,8 @@ def key_action(self, key): # acction to take when some other diverted key is pr def apply_all_settings(device): if device.features and _hidpp20_constants.FEATURE.HIRES_WHEEL in device.features: _sleep(0.2) # delay to try to get out of race condition with Linux HID++ driver - persister = getattr(device, 'persister', None) - sensitives = persister.get('_sensitive', {}) if persister else {} + persister = getattr(device, "persister", None) + sensitives = persister.get("_sensitive", {}) if persister else {} for s in device.settings: ignore = sensitives.get(s.name, False) if ignore != SENSITIVITY_IGNORE: diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 44618d3435..e97a7096fe 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -152,84 +152,84 @@ class FnSwapVirtual(_Setting): # virtual setting to hold fn swap strings class RegisterHandDetection(_Setting): - name = 'hand-detection' - label = _('Hand Detection') - description = _('Turn on illumination when the hands hover over the keyboard.') + name = "hand-detection" + label = _("Hand Detection") + description = _("Turn on illumination when the hands hover over the keyboard.") register = _R.keyboard_hand_detection - validator_options = {'true_value': b'\x00\x00\x00', 'false_value': b'\x00\x00\x30', 'mask': b'\x00\x00\xFF'} + validator_options = {"true_value": b"\x00\x00\x00", "false_value": b"\x00\x00\x30", "mask": b"\x00\x00\xFF"} class RegisterSmoothScroll(_Setting): - name = 'smooth-scroll' - label = _('Scroll Wheel Smooth Scrolling') - description = _('High-sensitivity mode for vertical scroll with the wheel.') + name = "smooth-scroll" + label = _("Scroll Wheel Smooth Scrolling") + description = _("High-sensitivity mode for vertical scroll with the wheel.") register = _R.mouse_button_flags - validator_options = {'true_value': 0x40, 'mask': 0x40} + validator_options = {"true_value": 0x40, "mask": 0x40} class RegisterSideScroll(_Setting): - name = 'side-scroll' - label = _('Side Scrolling') + name = "side-scroll" + label = _("Side Scrolling") description = _( - 'When disabled, pushing the wheel sideways sends custom button events\n' - 'instead of the standard side-scrolling events.' + "When disabled, pushing the wheel sideways sends custom button events\n" + "instead of the standard side-scrolling events." ) register = _R.mouse_button_flags - validator_options = {'true_value': 0x02, 'mask': 0x02} + validator_options = {"true_value": 0x02, "mask": 0x02} # different devices have different sets of permissible dpis, so this should be subclassed class RegisterDpi(_Setting): - name = 'dpi-old' - label = _('Sensitivity (DPI - older mice)') - description = _('Mouse movement sensitivity') + name = "dpi-old" + label = _("Sensitivity (DPI - older mice)") + description = _("Mouse movement sensitivity") register = _R.mouse_dpi choices_universe = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + validator_options = {"choices": choices_universe} class RegisterFnSwap(FnSwapVirtual): register = _R.keyboard_fn_swap - validator_options = {'true_value': b'\x00\x01', 'mask': b'\x00\x01'} + validator_options = {"true_value": b"\x00\x01", "mask": b"\x00\x01"} class _PerformanceMXDpi(RegisterDpi): choices_universe = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) - validator_options = {'choices': choices_universe} + validator_options = {"choices": choices_universe} # 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] +_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 # just use the current host (first byte = 0xFF) part of the feature to read and set the Fn state class K375sFnSwap(FnSwapVirtual): feature = _F.K375S_FN_INVERSION - rw_options = {'prefix': b'\xFF'} - validator_options = {'true_value': b'\x01', 'false_value': b'\x00', 'read_skip_byte_count': 1} + rw_options = {"prefix": b"\xFF"} + validator_options = {"true_value": b"\x01", "false_value": b"\x00", "read_skip_byte_count": 1} class FnSwap(FnSwapVirtual): @@ -241,26 +241,25 @@ class NewFnSwap(FnSwapVirtual): class Backlight(_Setting): - name = 'backlight-qualitative' - label = _('Backlight') - description = _('Set illumination time for keyboard.') + name = "backlight-qualitative" + label = _("Backlight") + description = _("Set illumination time for keyboard.") feature = _F.BACKLIGHT choices_universe = _NamedInts(Off=0, Varying=2, VeryShort=5, Short=10, Medium=20, Long=60, VeryLong=180) validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + validator_options = {"choices": choices_universe} # MX Keys S requires some extra values, as in 11 02 0c1a 000dff000b000b003c00000000000000 # on/off options (from current) effect (FF-no change) level (from current) durations[6] (from current) class Backlight2(_Setting): - name = 'backlight' - label = _('Backlight') - description = _('Illumination level on keyboard. Changes made are only applied in Manual mode.') + name = "backlight" + label = _("Backlight") + description = _("Illumination level on keyboard. Changes made are only applied in Manual mode.") feature = _F.BACKLIGHT2 min_version = 0 class rw_class: - def __init__(self, feature): self.feature = feature self.kind = _FeatureRW.kind @@ -281,30 +280,28 @@ def write(self, device, data_bytes): return True class validator_class(_ChoicesV): - @classmethod def build(cls, setting_class, device): backlight = device.backlight choices = _NamedInts() - choices[0xFF] = _('Disabled') + choices[0xFF] = _("Disabled") if backlight.auto_supported: - choices[0x1] = _('Automatic') + choices[0x1] = _("Automatic") if backlight.perm_supported: - choices[0x3] = _('Manual') + choices[0x3] = _("Manual") if not (backlight.auto_supported or backlight.temp_supported or backlight.perm_supported): - choices[0x0] = _('Enabled') + choices[0x0] = _("Enabled") return cls(choices=choices, byte_count=1) class Backlight2Level(_Setting): - name = 'backlight_level' - label = _('Backlight Level') - description = _('Illumination level on keyboard when in Manual mode.') + name = "backlight_level" + label = _("Backlight Level") + description = _("Illumination level on keyboard when in Manual mode.") feature = _F.BACKLIGHT2 min_version = 3 class rw_class: - def __init__(self, feature): self.feature = feature self.kind = _FeatureRW.kind @@ -320,11 +317,10 @@ def write(self, device, data_bytes): return True class validator_class(_RangeV): - @classmethod def build(cls, setting_class, device): reply = device.feature_request(_F.BACKLIGHT2, 0x20) - assert reply, 'Oops, backlight range cannot be retrieved!' + assert reply, "Oops, backlight range cannot be retrieved!" if reply[0] > 1: return cls(min_value=0, max_value=reply[0] - 1, byte_count=1) @@ -335,10 +331,9 @@ class Backlight2Duration(_Setting): validator_class = _RangeV min_value = 1 max_value = 120 # actual maximum is 2 hours - validator_options = {'byte_count': 2} + validator_options = {"byte_count": 2} class rw_class: - def __init__(self, feature, field): self.feature = feature self.kind = _FeatureRW.kind @@ -358,124 +353,126 @@ def write(self, device, data_bytes): class Backlight2DurationHandsOut(Backlight2Duration): - name = 'backlight_duration_hands_out' - label = _('Backlight Delay Hands Out') - description = _('Delay in seconds until backlight fades out with hands away from keyboard.') + name = "backlight_duration_hands_out" + label = _("Backlight Delay Hands Out") + description = _("Delay in seconds until backlight fades out with hands away from keyboard.") feature = _F.BACKLIGHT2 validator_class = _RangeV - rw_options = {'field': 'dho'} + rw_options = {"field": "dho"} class Backlight2DurationHandsIn(Backlight2Duration): - name = 'backlight_duration_hands_in' - label = _('Backlight Delay Hands In') - description = _('Delay in seconds until backlight fades out with hands near keyboard.') + name = "backlight_duration_hands_in" + label = _("Backlight Delay Hands In") + description = _("Delay in seconds until backlight fades out with hands near keyboard.") feature = _F.BACKLIGHT2 validator_class = _RangeV - rw_options = {'field': 'dhi'} + rw_options = {"field": "dhi"} class Backlight2DurationPowered(Backlight2Duration): - name = 'backlight_duration_powered' - label = _('Backlight Delay Powered') - description = _('Delay in seconds until backlight fades out with external power.') + name = "backlight_duration_powered" + label = _("Backlight Delay Powered") + description = _("Delay in seconds until backlight fades out with external power.") feature = _F.BACKLIGHT2 validator_class = _RangeV - rw_options = {'field': 'dpow'} + rw_options = {"field": "dpow"} class Backlight3(_Setting): - name = 'backlight-timed' - label = _('Backlight') - description = _('Set illumination time for keyboard.') + name = "backlight-timed" + label = _("Backlight") + description = _("Set illumination time for keyboard.") feature = _F.BACKLIGHT3 - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20, 'suffix': 0x09} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20, "suffix": 0x09} validator_class = _RangeV min_value = 0 max_value = 1000 - validator_options = {'byte_count': 2} + validator_options = {"byte_count": 2} class HiResScroll(_Setting): - name = 'hi-res-scroll' - label = _('Scroll Wheel High Resolution') + name = "hi-res-scroll" + label = _("Scroll Wheel High Resolution") description = ( - _('High-sensitivity mode for vertical scroll with the wheel.') + '\n' + - _('Set to ignore if scrolling is abnormally fast or slow') + _("High-sensitivity mode for vertical scroll with the wheel.") + + "\n" + + _("Set to ignore if scrolling is abnormally fast or slow") ) feature = _F.HI_RES_SCROLLING class LowresMode(_Setting): - name = 'lowres-scroll-mode' - label = _('Scroll Wheel Diversion') + name = "lowres-scroll-mode" + label = _("Scroll Wheel Diversion") description = _( - 'Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored).' + "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored)." ) feature = _F.LOWRES_WHEEL class HiresSmoothInvert(_Setting): - name = 'hires-smooth-invert' - label = _('Scroll Wheel Direction') - description = _('Invert direction for vertical scroll with wheel.') + name = "hires-smooth-invert" + label = _("Scroll Wheel Direction") + description = _("Invert direction for vertical scroll with wheel.") feature = _F.HIRES_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x04, 'mask': 0x04} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x04, "mask": 0x04} class HiresSmoothResolution(_Setting): - name = 'hires-smooth-resolution' - label = _('Scroll Wheel Resolution') + name = "hires-smooth-resolution" + label = _("Scroll Wheel Resolution") description = ( - _('High-sensitivity mode for vertical scroll with the wheel.') + '\n' + - _('Set to ignore if scrolling is abnormally fast or slow') + _("High-sensitivity mode for vertical scroll with the wheel.") + + "\n" + + _("Set to ignore if scrolling is abnormally fast or slow") ) feature = _F.HIRES_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x02, 'mask': 0x02} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x02, "mask": 0x02} class HiresMode(_Setting): - name = 'hires-scroll-mode' - label = _('Scroll Wheel Diversion') + name = "hires-scroll-mode" + label = _("Scroll Wheel Diversion") description = _( - 'Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored).' + "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored)." ) feature = _F.HIRES_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x01, 'mask': 0x01} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x01, "mask": 0x01} class PointerSpeed(_Setting): - name = 'pointer_speed' - label = _('Sensitivity (Pointer Speed)') - description = _('Speed multiplier for mouse (256 is normal multiplier).') + name = "pointer_speed" + label = _("Sensitivity (Pointer Speed)") + description = _("Speed multiplier for mouse (256 is normal multiplier).") feature = _F.POINTER_SPEED validator_class = _RangeV - min_value = 0x002e - max_value = 0x01ff - validator_options = {'byte_count': 2} + min_value = 0x002E + max_value = 0x01FF + validator_options = {"byte_count": 2} class ThumbMode(_Setting): - name = 'thumb-scroll-mode' - label = _('Thumb Wheel Diversion') + name = "thumb-scroll-mode" + label = _("Thumb Wheel Diversion") description = _( - 'Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored).' + "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored)." ) feature = _F.THUMB_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': b'\x01\x00', 'false_value': b'\x00\x00', 'mask': b'\x01\x00'} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": b"\x01\x00", "false_value": b"\x00\x00", "mask": b"\x01\x00"} class ThumbInvert(_Setting): - name = 'thumb-scroll-invert' - label = _('Thumb Wheel Direction') - description = _('Invert thumb wheel scroll direction.') + name = "thumb-scroll-invert" + label = _("Thumb Wheel Direction") + description = _("Invert thumb wheel scroll direction.") feature = _F.THUMB_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': b'\x00\x01', 'false_value': b'\x00\x00', 'mask': b'\x00\x01'} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + 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 @@ -491,17 +488,16 @@ def profile_change(device, profile_sector): class OnboardProfiles(_Setting): - name = 'onboard_profiles' - label = _('Onboard Profiles') - description = _('Enable an onboard profile, which controls report rate, sensitivity, and button actions') + name = "onboard_profiles" + label = _("Onboard Profiles") + description = _("Enable an onboard profile, which controls report rate, sensitivity, and button actions") feature = _F.ONBOARD_PROFILES choices_universe = _NamedInts(Disabled=0) for i in range(1, 6): - choices_universe[i] = f'Profile {i}' + choices_universe[i] = f"Profile {i}" validator_class = _ChoicesV class rw_class: - def __init__(self, feature): self.feature = feature self.kind = _FeatureRW.kind @@ -512,50 +508,48 @@ def read(self, device): active = device.feature_request(_F.ONBOARD_PROFILES, 0x40) return active[:2] else: - return b'\x00\x00' + return b"\x00\x00" def write(self, device, data_bytes): - if data_bytes == b'\x00\x00': - result = device.feature_request(_F.ONBOARD_PROFILES, 0x10, b'\x02') + 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') + device.feature_request(_F.ONBOARD_PROFILES, 0x10, b"\x01") result = device.feature_request(_F.ONBOARD_PROFILES, 0x30, data_bytes) profile_change(device, _bytes2int(data_bytes)) return result class validator_class(_ChoicesV): - @classmethod def build(cls, setting_class, device): headers = _hidpp20.OnboardProfiles.get_profile_headers(device) profiles_list = [setting_class.choices_universe[0]] if headers: - for (sector, enabled) in headers: + for sector, enabled in headers: if enabled: profiles_list.append(setting_class.choices_universe[sector]) return cls(choices=_NamedInts.list(profiles_list), byte_count=2) if len(profiles_list) > 1 else None class ReportRate(_Setting): - name = 'report_rate' - label = _('Report Rate') + name = "report_rate" + label = _("Report Rate") description = ( - _('Frequency of device movement reports') + '\n' + _('May need Onboard Profiles set to Disable to be effective.') + _("Frequency of device movement reports") + "\n" + _("May need Onboard Profiles set to Disable to be effective.") ) feature = _F.REPORT_RATE - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} choices_universe = _NamedInts() - choices_universe[1] = '1ms' - choices_universe[2] = '2ms' - choices_universe[3] = '3ms' - choices_universe[4] = '4ms' - choices_universe[5] = '5ms' - choices_universe[6] = '6ms' - choices_universe[7] = '7ms' - choices_universe[8] = '8ms' + choices_universe[1] = "1ms" + choices_universe[2] = "2ms" + choices_universe[3] = "3ms" + choices_universe[4] = "4ms" + choices_universe[5] = "5ms" + choices_universe[6] = "6ms" + choices_universe[7] = "7ms" + choices_universe[8] = "8ms" class _rw_class(_FeatureRW): # no longer needed - set Onboard Profiles to disable - def write(self, device, data_bytes): # Host mode is required for report rate to be adjustable if _hidpp20.get_onboard_mode(device) != _hidpp20_constants.ONBOARD_MODES.MODE_HOST: @@ -563,13 +557,12 @@ def write(self, device, data_bytes): return super().write(device, data_bytes) class validator_class(_ChoicesV): - @classmethod def build(cls, setting_class, device): # if device.wpid == '408E': # return None # host mode borks the function keys on the G915 TKL keyboard reply = device.feature_request(_F.REPORT_RATE, 0x00) - assert reply, 'Oops, report rate choices cannot be retrieved!' + assert reply, "Oops, report rate choices cannot be retrieved!" rate_list = [] rate_flags = _bytes2int(reply[0:1]) for i in range(0, 8): @@ -579,27 +572,26 @@ def build(cls, setting_class, device): class ExtendedReportRate(_Setting): - name = 'report_rate_extended' - label = _('Report Rate') + name = "report_rate_extended" + label = _("Report Rate") description = ( - _('Frequency of device movement reports') + '\n' + _('May need Onboard Profiles set to Disable to be effective.') + _("Frequency of device movement reports") + "\n" + _("May need Onboard Profiles set to Disable to be effective.") ) feature = _F.EXTENDED_ADJUSTABLE_REPORT_RATE - rw_options = {'read_fnid': 0x20, 'write_fnid': 0x30} + rw_options = {"read_fnid": 0x20, "write_fnid": 0x30} choices_universe = _NamedInts() - choices_universe[0] = '8ms' - choices_universe[1] = '4ms' - choices_universe[2] = '2ms' - choices_universe[3] = '1ms' - choices_universe[4] = '500us' - choices_universe[5] = '250us' - choices_universe[6] = '125us' + choices_universe[0] = "8ms" + choices_universe[1] = "4ms" + choices_universe[2] = "2ms" + choices_universe[3] = "1ms" + choices_universe[4] = "500us" + choices_universe[5] = "250us" + choices_universe[6] = "125us" class _rw_class(_FeatureRW): - - def read(self, device, data_bytes=b''): + def read(self, device, data_bytes=b""): # need connection type from device to get actual report rate - self.read_prefix = b'\x00' if device.receiver else b'\x01' + self.read_prefix = b"\x00" if device.receiver else b"\x01" super().read(device, data_bytes) def write(self, device, data_bytes): @@ -609,72 +601,70 @@ def write(self, device, data_bytes): return super().write(device, data_bytes) class validator_class(_ChoicesV): - @classmethod def build(cls, setting_class, device): reply = device.feature_request(_F.EXTENDED_ADJUSTABLE_REPORT_RATE, 0x10) - assert reply, 'Oops, report rate choices cannot be retrieved!' + assert reply, "Oops, report rate choices cannot be retrieved!" rate_list = [] rate_flags = _bytes2int(reply[0:2]) for i in range(0, 6): - if (rate_flags & (0x01 << i)): + if rate_flags & (0x01 << i): rate_list.append(setting_class.choices_universe[i]) return cls(choices=_NamedInts.list(rate_list), byte_count=1) if rate_list else None class DivertCrown(_Setting): - name = 'divert-crown' - label = _('Divert crown events') - description = _('Make crown send CROWN HID++ notifications (which trigger Solaar rules but are otherwise ignored).') + name = "divert-crown" + label = _("Divert crown events") + description = _("Make crown send CROWN HID++ notifications (which trigger Solaar rules but are otherwise ignored).") feature = _F.CROWN - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x02, 'false_value': 0x01, 'mask': 0xff} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x02, "false_value": 0x01, "mask": 0xFF} class CrownSmooth(_Setting): - name = 'crown-smooth' - label = _('Crown smooth scroll') - description = _('Set crown smooth scroll') + name = "crown-smooth" + label = _("Crown smooth scroll") + description = _("Set crown smooth scroll") feature = _F.CROWN - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x01, 'false_value': 0x02, 'read_skip_byte_count': 1, 'write_prefix_bytes': b'\x00'} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x01, "false_value": 0x02, "read_skip_byte_count": 1, "write_prefix_bytes": b"\x00"} class DivertGkeys(_Setting): - name = 'divert-gkeys' - label = _('Divert G and M Keys') - description = (_('Make G and M keys send HID++ notifications (which trigger Solaar rules but are otherwise ignored).')) + name = "divert-gkeys" + label = _("Divert G and M Keys") + description = _("Make G and M keys send HID++ notifications (which trigger Solaar rules but are otherwise ignored).") feature = _F.GKEY - validator_options = {'true_value': 0x01, 'false_value': 0x00, 'mask': 0xff} + validator_options = {"true_value": 0x01, "false_value": 0x00, "mask": 0xFF} class rw_class(_FeatureRW): - def __init__(self, feature): super().__init__(feature, write_fnid=0x20) def read(self, device): # no way to read, so just assume not diverted - return b'\x00' + return b"\x00" class ScrollRatchet(_Setting): - name = 'scroll-ratchet' - label = _('Scroll Wheel Ratcheted') - description = _('Switch the mouse wheel between speed-controlled ratcheting and always freespin.') + name = "scroll-ratchet" + label = _("Scroll Wheel Ratcheted") + description = _("Switch the mouse wheel between speed-controlled ratcheting and always freespin.") feature = _F.SMART_SHIFT - choices_universe = _NamedInts(**{_('Freespinning'): 1, _('Ratcheted'): 2}) + choices_universe = _NamedInts(**{_("Freespinning"): 1, _("Ratcheted"): 2}) validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + validator_options = {"choices": choices_universe} class SmartShift(_Setting): - name = 'smart-shift' - label = _('Scroll Wheel Ratchet Speed') + name = "smart-shift" + label = _("Scroll Wheel Ratchet Speed") description = _( - 'Use the mouse wheel speed to switch between ratcheted and freespinning.\n' - 'The mouse wheel is always ratcheted at 50.' + "Use the mouse wheel speed to switch between ratcheted and freespinning.\n" + "The mouse wheel is always ratcheted at 50." ) feature = _F.SMART_SHIFT - rw_options = {'read_fnid': 0x00, 'write_fnid': 0x10} + rw_options = {"read_fnid": 0x00, "write_fnid": 0x10} class rw_class(_FeatureRW): MIN_VALUE = 1 @@ -710,7 +700,7 @@ def write(self, device, data_bytes): class SmartShiftEnhanced(SmartShift): feature = _F.SMART_SHIFT_ENHANCED - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} # the keys for the choice map are Logitech controls (from special_keys) @@ -718,18 +708,20 @@ class SmartShiftEnhanced(SmartShift): # and the integer being the control number for that task (to be written to the device) # Solaar only remaps keys (controlled by key gmask and group), not other key reprogramming class ReprogrammableKeys(_Settings): - name = 'reprogrammable-keys' - label = _('Key/Button Actions') + name = "reprogrammable-keys" + label = _("Key/Button Actions") description = ( - _('Change the action for the key or button.') + ' ' + _('Overridden by diversion.') + '\n' + - _('Changing important actions (such as for the left mouse button) can result in an unusable system.') + _("Change the action for the key or button.") + + " " + + _("Overridden by diversion.") + + "\n" + + _("Changing important actions (such as for the left mouse button) can result in an unusable system.") ) feature = _F.REPROG_CONTROLS_V4 keys_universe = _special_keys.CONTROL choices_universe = _special_keys.CONTROL class rw_class: - def __init__(self, feature): self.feature = feature self.kind = _FeatureRW.kind @@ -737,7 +729,7 @@ def __init__(self, feature): def read(self, device, key): key_index = device.keys.index(key) key_struct = device.keys[key_index] - return b'\x00\x00' + _int2bytes(int(key_struct.mapped_to), 2) + return b"\x00\x00" + _int2bytes(int(key_struct.mapped_to), 2) def write(self, device, key, data_bytes): key_index = device.keys.index(key) @@ -746,7 +738,6 @@ def write(self, device, key, data_bytes): return True class validator_class(_ChoicesMapV): - @classmethod def build(cls, setting_class, device): choices = {} @@ -759,15 +750,14 @@ def build(cls, setting_class, device): class DpiSlidingXY(_RawXYProcessing): - def activate_action(self): - self.dpiSetting = next(filter(lambda s: s.name == 'dpi', self.device.settings), None) + self.dpiSetting = next(filter(lambda s: s.name == "dpi", self.device.settings), None) self.dpiChoices = list(self.dpiSetting.choices) - self.otherDpiIdx = self.device.persister.get('_dpi-sliding', -1) if self.device.persister else -1 + self.otherDpiIdx = self.device.persister.get("_dpi-sliding", -1) if self.device.persister else -1 if not isinstance(self.otherDpiIdx, int) or self.otherDpiIdx < 0 or self.otherDpiIdx >= len(self.dpiChoices): self.otherDpiIdx = self.dpiChoices.index(self.dpiSetting.read()) - self.fsmState = 'idle' - self.dx = 0. + self.fsmState = "idle" + self.dx = 0.0 self.movingDpiIdx = None def setNewDpi(self, newDpiIdx): @@ -778,40 +768,40 @@ def setNewDpi(self, newDpiIdx): def displayNewDpi(self, newDpiIdx): if _notify.available: - reason = 'DPI %d [min %d, max %d]' % (self.dpiChoices[newDpiIdx], self.dpiChoices[0], self.dpiChoices[-1]) + reason = "DPI %d [min %d, max %d]" % (self.dpiChoices[newDpiIdx], self.dpiChoices[0], self.dpiChoices[-1]) _notify.show(self.device, reason) def press_action(self, key): # start tracking self.starting = True - if self.fsmState == 'idle': - self.fsmState = 'pressed' - self.dx = 0. + if self.fsmState == "idle": + self.fsmState = "pressed" + self.dx = 0.0 # While in 'moved' state, the index into 'dpiChoices' of the currently selected DPI setting self.movingDpiIdx = None def release_action(self): # adjust DPI and stop tracking - if self.fsmState == 'pressed': # Swap with other DPI + if self.fsmState == "pressed": # Swap with other DPI thisIdx = self.dpiChoices.index(self.dpiSetting.read()) newDpiIdx, self.otherDpiIdx = self.otherDpiIdx, thisIdx if self.device.persister: - self.device.persister['_dpi-sliding'] = self.otherDpiIdx + self.device.persister["_dpi-sliding"] = self.otherDpiIdx self.setNewDpi(newDpiIdx) self.displayNewDpi(newDpiIdx) - elif self.fsmState == 'moved': # Set DPI according to displacement + elif self.fsmState == "moved": # Set DPI according to displacement self.setNewDpi(self.movingDpiIdx) - self.fsmState = 'idle' + self.fsmState = "idle" def move_action(self, dx, dy): if self.device.features.get_feature_version(_F.REPROG_CONTROLS_V4) >= 5 and self.starting: self.starting = False # hack to ignore strange first movement report from MX Master 3S return currDpi = self.dpiSetting.read() - self.dx += float(dx) / float(currDpi) * 15. # yields a more-or-less DPI-independent dx of about 5/cm - if self.fsmState == 'pressed': - if abs(self.dx) >= 1.: - self.fsmState = 'moved' + self.dx += float(dx) / float(currDpi) * 15.0 # yields a more-or-less DPI-independent dx of about 5/cm + if self.fsmState == "pressed": + if abs(self.dx) >= 1.0: + self.fsmState = "moved" self.movingDpiIdx = self.dpiChoices.index(currDpi) - elif self.fsmState == 'moved': + elif self.fsmState == "moved": currIdx = self.dpiChoices.index(self.dpiSetting.read()) newMovingDpiIdx = min(max(currIdx + int(self.dx), 0), len(self.dpiChoices) - 1) if newMovingDpiIdx != self.movingDpiIdx: @@ -820,48 +810,47 @@ def move_action(self, dx, dy): class MouseGesturesXY(_RawXYProcessing): - def activate_action(self): - self.dpiSetting = next(filter(lambda s: s.name == 'dpi', self.device.settings), None) - self.fsmState = 'idle' + self.dpiSetting = next(filter(lambda s: s.name == "dpi", self.device.settings), None) + self.fsmState = "idle" self.initialize_data() def initialize_data(self): - self.dx = 0. - self.dy = 0. + self.dx = 0.0 + self.dy = 0.0 self.lastEv = None self.data = [] def press_action(self, key): self.starting = True - if self.fsmState == 'idle': - self.fsmState = 'pressed' + if self.fsmState == "idle": + self.fsmState = "pressed" self.initialize_data() self.data = [key.key] def release_action(self): - if self.fsmState == 'pressed': + if self.fsmState == "pressed": # emit mouse gesture notification self.push_mouse_event() if logger.isEnabledFor(logging.INFO): - logger.info('mouse gesture notification %s', self.data) - payload = _pack('!' + (len(self.data) * 'h'), *self.data) + logger.info("mouse gesture notification %s", self.data) + payload = _pack("!" + (len(self.data) * "h"), *self.data) notification = _HIDPP_Notification(0, 0, 0, 0, payload) _process_notification(self.device, self.device.status, notification, _F.MOUSE_GESTURE) - self.fsmState = 'idle' + self.fsmState = "idle" def move_action(self, dx, dy): - if self.fsmState == 'pressed': + if self.fsmState == "pressed": now = _time() * 1000 # _time_ns() / 1e6 if self.device.features.get_feature_version(_F.REPROG_CONTROLS_V4) >= 5 and self.starting: self.starting = False # hack to ignore strange first movement report from MX Master 3S return - if self.lastEv is not None and now - self.lastEv > 200.: + if self.lastEv is not None and now - self.lastEv > 200.0: self.push_mouse_event() dpi = self.dpiSetting.read() if self.dpiSetting else 1000 - dx = float(dx) / float(dpi) * 15. # This multiplier yields a more-or-less DPI-independent dx of about 5/cm + dx = float(dx) / float(dpi) * 15.0 # This multiplier yields a more-or-less DPI-independent dx of about 5/cm self.dx += dx - dy = float(dy) / float(dpi) * 15. # This multiplier yields a more-or-less DPI-independent dx of about 5/cm + dy = float(dy) / float(dpi) * 15.0 # This multiplier yields a more-or-less DPI-independent dx of about 5/cm self.dy += dy self.lastEv = now @@ -871,7 +860,7 @@ def key_action(self, key): self.data.append(key) self.lastEv = _time() * 1000 # _time_ns() / 1e6 if logger.isEnabledFor(logging.DEBUG): - logger.debug('mouse gesture key event %d %s', key, self.data) + logger.debug("mouse gesture key event %d %s", key, self.data) def push_mouse_event(self): x = int(self.dx) @@ -881,24 +870,23 @@ def push_mouse_event(self): self.data.append(0) self.data.append(x) self.data.append(y) - self.dx = 0. - self.dy = 0. + self.dx = 0.0 + self.dy = 0.0 if logger.isEnabledFor(logging.DEBUG): - logger.debug('mouse gesture move event %d %d %s', x, y, self.data) + logger.debug("mouse gesture move event %d %d %s", x, y, self.data) class DivertKeys(_Settings): - name = 'divert-keys' - label = _('Key/Button Diversion') - description = _('Make the key or button send HID++ notifications (Diverted) or initiate Mouse Gestures or Sliding DPI') + name = "divert-keys" + label = _("Key/Button Diversion") + description = _("Make the key or button send HID++ notifications (Diverted) or initiate Mouse Gestures or Sliding DPI") feature = _F.REPROG_CONTROLS_V4 keys_universe = _special_keys.CONTROL - choices_universe = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1, _('Mouse Gestures'): 2, _('Sliding DPI'): 3}) - choices_gesture = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1, _('Mouse Gestures'): 2}) - choices_divert = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1}) + choices_universe = _NamedInts(**{_("Regular"): 0, _("Diverted"): 1, _("Mouse Gestures"): 2, _("Sliding DPI"): 3}) + choices_gesture = _NamedInts(**{_("Regular"): 0, _("Diverted"): 1, _("Mouse Gestures"): 2}) + choices_divert = _NamedInts(**{_("Regular"): 0, _("Diverted"): 1}) class rw_class: - def __init__(self, feature): self.feature = feature self.kind = _FeatureRW.kind @@ -906,7 +894,7 @@ def __init__(self, feature): def read(self, device, key): key_index = device.keys.index(key) key_struct = device.keys[key_index] - return b'\x00\x00\x01' if 'diverted' in key_struct.mapping_flags else b'\x00\x00\x00' + return b"\x00\x00\x01" if "diverted" in key_struct.mapping_flags else b"\x00\x00\x00" def write(self, device, key, data_bytes): key_index = device.keys.index(key) @@ -915,7 +903,6 @@ def write(self, device, key, data_bytes): return True class validator_class(_ChoicesMapV): - def __init__(self, choices, key_byte_count=2, byte_count=1, mask=0x01): super().__init__(choices, key_byte_count, byte_count, mask) @@ -936,15 +923,15 @@ def build(cls, setting_class, device): choices = {} if device.keys: for k in device.keys: - if 'divertable' in k.flags and 'virtual' not in k.flags: - if 'raw XY' in k.flags: + if "divertable" in k.flags and "virtual" not in k.flags: + if "raw XY" in k.flags: choices[k.key] = setting_class.choices_gesture if gestures is None: - gestures = MouseGesturesXY(device, name='MouseGestures') + gestures = MouseGesturesXY(device, name="MouseGestures") if _F.ADJUSTABLE_DPI in device.features: choices[k.key] = setting_class.choices_universe if sliding is None: - sliding = DpiSlidingXY(device, name='DpiSlding') + sliding = DpiSlidingXY(device, name="DpiSlding") else: choices[k.key] = setting_class.choices_divert if not choices: @@ -957,36 +944,35 @@ def build(cls, setting_class, device): class AdjustableDpi(_Setting): """Pointer Speed feature""" + # Assume sensorIdx 0 (there is only one sensor) # [2] getSensorDpi(sensorIdx) -> sensorIdx, dpiMSB, dpiLSB # [3] setSensorDpi(sensorIdx, dpi) - name = 'dpi' - label = _('Sensitivity (DPI)') - description = _('Mouse movement sensitivity') + name = "dpi" + label = _("Sensitivity (DPI)") + description = _("Mouse movement sensitivity") feature = _F.ADJUSTABLE_DPI - rw_options = {'read_fnid': 0x20, 'write_fnid': 0x30} + rw_options = {"read_fnid": 0x20, "write_fnid": 0x30} choices_universe = _NamedInts.range(200, 4000, str, 50) class validator_class(_ChoicesV): - @classmethod def build(cls, setting_class, device): # [1] getSensorDpiList(sensorIdx) reply = device.feature_request(_F.ADJUSTABLE_DPI, 0x10) - assert reply, 'Oops, DPI list cannot be retrieved!' + assert reply, "Oops, DPI list cannot be retrieved!" dpi_list = [] step = None - for val in _unpack('!7H', reply[1:1 + 14]): + for val in _unpack("!7H", reply[1 : 1 + 14]): if val == 0: break if val >> 13 == 0b111: - assert step is None and len(dpi_list) == 1, \ - 'Invalid DPI list item: %r' % val - step = val & 0x1fff + assert step is None and len(dpi_list) == 1, "Invalid DPI list item: %r" % val + step = val & 0x1FFF else: dpi_list.append(val) if step: - assert len(dpi_list) == 2, 'Invalid DPI list range: %r' % dpi_list + assert len(dpi_list) == 2, "Invalid DPI list range: %r" % dpi_list dpi_list = range(dpi_list[0], dpi_list[1] + 1, step) return cls(choices=_NamedInts.list(dpi_list), byte_count=3) if dpi_list else None @@ -995,61 +981,59 @@ def validate_read(self, reply_bytes): # special validator to use default DPI if if reply_value == 0: # use default value instead reply_value = _bytes2int(reply_bytes[3:5]) valid_value = self.choices[reply_value] - assert valid_value is not None, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) + assert valid_value is not None, "%s: failed to validate read value %02X" % (self.__class__.__name__, reply_value) return valid_value class SpeedChange(_Setting): """Implements the ability to switch Sensitivity by clicking on the DPI_Change button.""" - name = 'speed-change' - label = _('Sensitivity Switching') + + name = "speed-change" + label = _("Sensitivity Switching") description = _( - 'Switch the current sensitivity and the remembered sensitivity when the key or button is pressed.\n' - 'If there is no remembered sensitivity, just remember the current sensitivity' + "Switch the current sensitivity and the remembered sensitivity when the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current sensitivity" ) choices_universe = _special_keys.CONTROL - choices_extra = _NamedInt(0, _('Off')) + choices_extra = _NamedInt(0, _("Off")) feature = _F.POINTER_SPEED - rw_options = {'name': 'speed change'} + rw_options = {"name": "speed change"} class rw_class(_ActionSettingRW): - def press_action(self): # switch sensitivity - currentSpeed = self.device.persister.get('pointer_speed', None) if self.device.persister else None - newSpeed = self.device.persister.get('_speed-change', None) if self.device.persister else None - speed_setting = next(filter(lambda s: s.name == 'pointer_speed', self.device.settings), None) + currentSpeed = self.device.persister.get("pointer_speed", None) if self.device.persister else None + newSpeed = self.device.persister.get("_speed-change", None) if self.device.persister else None + speed_setting = next(filter(lambda s: s.name == "pointer_speed", self.device.settings), None) if newSpeed is not None: if speed_setting: speed_setting.write(newSpeed) if self.device.setting_callback: self.device.setting_callback(self.device, type(speed_setting), [newSpeed]) else: - logger.error('cannot save sensitivity setting on %s', self.device) + logger.error("cannot save sensitivity setting on %s", self.device) if self.device.persister: - self.device.persister['_speed-change'] = currentSpeed + self.device.persister["_speed-change"] = currentSpeed class validator_class(_ChoicesV): - @classmethod def build(cls, setting_class, device): key_index = device.keys.index(_special_keys.CONTROL.DPI_Change) key = device.keys[key_index] if key_index is not None else None - if key is not None and 'divertable' in key.flags: + if key is not None and "divertable" in key.flags: keys = [setting_class.choices_extra, key.key] return cls(choices=_NamedInts.list(keys), byte_count=2) class DisableKeyboardKeys(_BitFieldSetting): - name = 'disable-keyboard-keys' - label = _('Disable keys') - description = _('Disable specific keyboard keys.') + name = "disable-keyboard-keys" + label = _("Disable keys") + description = _("Disable specific keyboard keys.") feature = _F.KEYBOARD_DISABLE_KEYS - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - _labels = {k: (None, _('Disables the %s key.') % k) for k in _DKEY} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + _labels = {k: (None, _("Disables the %s key.") % k) for k in _DKEY} choices_universe = _DKEY class validator_class(_BitFieldV): - @classmethod def build(cls, setting_class, device): mask = device.feature_request(_F.KEYBOARD_DISABLE_KEYS, 0x00)[0] @@ -1058,46 +1042,52 @@ def build(cls, setting_class, device): class Multiplatform(_Setting): - name = 'multiplatform' - label = _('Set OS') - description = _('Change keys to match OS.') + name = "multiplatform" + label = _("Set OS") + description = _("Change keys to match OS.") feature = _F.MULTIPLATFORM - rw_options = {'read_fnid': 0x00, 'write_fnid': 0x30} - choices_universe = _NamedInts(**{'OS ' + str(i + 1): i for i in range(8)}) + rw_options = {"read_fnid": 0x00, "write_fnid": 0x30} + choices_universe = _NamedInts(**{"OS " + str(i + 1): i for i in range(8)}) # multiplatform OS bits - OSS = [('Linux', 0x0400), ('MacOS', 0x2000), ('Windows', 0x0100), ('iOS', 0x4000), ('Android', 0x1000), ('WebOS', 0x8000), - ('Chrome', 0x0800), ('WinEmb', 0x0200), ('Tizen', 0x0001)] + OSS = [ + ("Linux", 0x0400), + ("MacOS", 0x2000), + ("Windows", 0x0100), + ("iOS", 0x4000), + ("Android", 0x1000), + ("WebOS", 0x8000), + ("Chrome", 0x0800), + ("WinEmb", 0x0200), + ("Tizen", 0x0001), + ] # the problem here is how to construct the right values for the rules Set GUI, # as, for example, the integer value for 'Windows' can be different on different devices class validator_class(_ChoicesV): - @classmethod def build(cls, setting_class, device): - def _str_os_versions(low, high): - def _str_os_version(version): if version == 0: - return '' + return "" elif version & 0xFF: - return str(version >> 8) + '.' + str(version & 0xFF) + return str(version >> 8) + "." + str(version & 0xFF) else: return str(version >> 8) - return '' if low == 0 and high == 0 else ' ' + _str_os_version(low) + '-' + _str_os_version(high) + return "" if low == 0 and high == 0 else " " + _str_os_version(low) + "-" + _str_os_version(high) infos = device.feature_request(_F.MULTIPLATFORM) - assert infos, 'Oops, multiplatform count cannot be retrieved!' - flags, _ignore, num_descriptors = _unpack('!BBB', infos[:3]) + assert infos, "Oops, multiplatform count cannot be retrieved!" + flags, _ignore, num_descriptors = _unpack("!BBB", infos[:3]) if not (flags & 0x02): # can't set platform so don't create setting return [] descriptors = [] for index in range(0, num_descriptors): descriptor = device.feature_request(_F.MULTIPLATFORM, 0x10, index) - platform, _ignore, os_flags, low, high = _unpack('!BBHHH', descriptor[:8]) + platform, _ignore, os_flags, low, high = _unpack("!BBHHH", descriptor[:8]) descriptors.append((platform, os_flags, low, high)) choices = _NamedInts() for os_name, os_bit in setting_class.OSS: @@ -1105,142 +1095,144 @@ def _str_os_version(version): os = os_name + _str_os_versions(low, high) if os_bit & os_flags and platform not in choices and os not in choices: choices[platform] = os - return cls(choices=choices, read_skip_byte_count=6, write_prefix_bytes=b'\xff') if choices else None + return cls(choices=choices, read_skip_byte_count=6, write_prefix_bytes=b"\xff") if choices else None class DualPlatform(_Setting): - name = 'multiplatform' - label = _('Set OS') - description = _('Change keys to match OS.') + name = "multiplatform" + label = _("Set OS") + description = _("Change keys to match OS.") choices_universe = _NamedInts() - choices_universe[0x00] = 'iOS, MacOS' - choices_universe[0x01] = 'Android, Windows' + choices_universe[0x00] = "iOS, MacOS" + choices_universe[0x01] = "Android, Windows" feature = _F.DUALPLATFORM - rw_options = {'read_fnid': 0x00, 'write_fnid': 0x20} + rw_options = {"read_fnid": 0x00, "write_fnid": 0x20} validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + validator_options = {"choices": choices_universe} class ChangeHost(_Setting): - name = 'change-host' - label = _('Change Host') - description = _('Switch connection to a different host') + name = "change-host" + label = _("Change Host") + description = _("Switch connection to a different host") persist = False # persisting this setting is harmful feature = _F.CHANGE_HOST - rw_options = {'read_fnid': 0x00, 'write_fnid': 0x10, 'no_reply': True} - choices_universe = _NamedInts(**{'Host ' + str(i + 1): i for i in range(3)}) + rw_options = {"read_fnid": 0x00, "write_fnid": 0x10, "no_reply": True} + choices_universe = _NamedInts(**{"Host " + str(i + 1): i for i in range(3)}) class validator_class(_ChoicesV): - @classmethod def build(cls, setting_class, device): infos = device.feature_request(_F.CHANGE_HOST) - assert infos, 'Oops, host count cannot be retrieved!' - numHosts, currentHost = _unpack('!BB', infos[:2]) + assert infos, "Oops, host count cannot be retrieved!" + numHosts, currentHost = _unpack("!BB", infos[:2]) hostNames = _hidpp20.get_host_names(device) hostNames = hostNames if hostNames is not None else {} - if currentHost not in hostNames or hostNames[currentHost][1] == '': - hostNames[currentHost] = (True, _socket.gethostname().partition('.')[0]) + if currentHost not in hostNames or hostNames[currentHost][1] == "": + hostNames[currentHost] = (True, _socket.gethostname().partition(".")[0]) choices = _NamedInts() for host in range(0, numHosts): - paired, hostName = hostNames.get(host, (True, '')) - choices[host] = str(host + 1) + ':' + hostName if hostName else str(host + 1) + paired, hostName = hostNames.get(host, (True, "")) + choices[host] = str(host + 1) + ":" + hostName if hostName else str(host + 1) return cls(choices=choices, read_skip_byte_count=1) if choices and len(choices) > 1 else None _GESTURE2_GESTURES_LABELS = { - _GG['Tap1Finger']: (_('Single tap'), _('Performs a left click.')), - _GG['Tap2Finger']: (_('Single tap with two fingers'), _('Performs a right click.')), - _GG['Tap3Finger']: (_('Single tap with three fingers'), None), - _GG['Click1Finger']: (None, None), - _GG['Click2Finger']: (None, None), - _GG['Click3Finger']: (None, None), - _GG['DoubleTap1Finger']: (_('Double tap'), _('Performs a double click.')), - _GG['DoubleTap2Finger']: (_('Double tap with two fingers'), None), - _GG['DoubleTap3Finger']: (_('Double tap with three fingers'), None), - _GG['Track1Finger']: (None, None), - _GG['TrackingAcceleration']: (None, None), - _GG['TapDrag1Finger']: (_('Tap and drag'), _('Drags items by dragging the finger after double tapping.')), - _GG['TapDrag2Finger']: - (_('Tap and drag with two fingers'), _('Drags items by dragging the fingers after double tapping.')), - _GG['Drag3Finger']: (_('Tap and drag with three fingers'), None), - _GG['TapGestures']: (None, None), - _GG['FnClickGestureSuppression']: - (_('Suppress tap and edge gestures'), _('Disables tap and edge gestures (equivalent to pressing Fn+LeftClick).')), - _GG['Scroll1Finger']: (_('Scroll with one finger'), _('Scrolls.')), - _GG['Scroll2Finger']: (_('Scroll with two fingers'), _('Scrolls.')), - _GG['Scroll2FingerHoriz']: (_('Scroll horizontally with two fingers'), _('Scrolls horizontally.')), - _GG['Scroll2FingerVert']: (_('Scroll vertically with two fingers'), _('Scrolls vertically.')), - _GG['Scroll2FingerStateless']: (_('Scroll with two fingers'), _('Scrolls.')), - _GG['NaturalScrolling']: (_('Natural scrolling'), _('Inverts the scrolling direction.')), - _GG['Thumbwheel']: (_('Thumbwheel'), _('Enables the thumbwheel.')), - _GG['VScrollInertia']: (None, None), - _GG['VScrollBallistics']: (None, None), - _GG['Swipe2FingerHoriz']: (None, None), - _GG['Swipe3FingerHoriz']: (None, None), - _GG['Swipe4FingerHoriz']: (None, None), - _GG['Swipe3FingerVert']: (None, None), - _GG['Swipe4FingerVert']: (None, None), - _GG['LeftEdgeSwipe1Finger']: (None, None), - _GG['RightEdgeSwipe1Finger']: (None, None), - _GG['BottomEdgeSwipe1Finger']: (None, None), - _GG['TopEdgeSwipe1Finger']: (_('Swipe from the top edge'), None), - _GG['LeftEdgeSwipe1Finger2']: (_('Swipe from the left edge'), None), - _GG['RightEdgeSwipe1Finger2']: (_('Swipe from the right edge'), None), - _GG['BottomEdgeSwipe1Finger2']: (_('Swipe from the bottom edge'), None), - _GG['TopEdgeSwipe1Finger2']: (_('Swipe from the top edge'), None), - _GG['LeftEdgeSwipe2Finger']: (_('Swipe two fingers from the left edge'), None), - _GG['RightEdgeSwipe2Finger']: (_('Swipe two fingers from the right edge'), None), - _GG['BottomEdgeSwipe2Finger']: (_('Swipe two fingers from the bottom edge'), None), - _GG['TopEdgeSwipe2Finger']: (_('Swipe two fingers from the top edge'), None), - _GG['Zoom2Finger']: (_('Zoom with two fingers.'), _('Pinch to zoom out; spread to zoom in.')), - _GG['Zoom2FingerPinch']: (_('Pinch to zoom out.'), _('Pinch to zoom out.')), - _GG['Zoom2FingerSpread']: (_('Spread to zoom in.'), _('Spread to zoom in.')), - _GG['Zoom3Finger']: (_('Zoom with three fingers.'), None), - _GG['Zoom2FingerStateless']: (_('Zoom with two fingers'), _('Pinch to zoom out; spread to zoom in.')), - _GG['TwoFingersPresent']: (None, None), - _GG['Rotate2Finger']: (None, None), - _GG['Finger1']: (None, None), - _GG['Finger2']: (None, None), - _GG['Finger3']: (None, None), - _GG['Finger4']: (None, None), - _GG['Finger5']: (None, None), - _GG['Finger6']: (None, None), - _GG['Finger7']: (None, None), - _GG['Finger8']: (None, None), - _GG['Finger9']: (None, None), - _GG['Finger10']: (None, None), - _GG['DeviceSpecificRawData']: (None, None), + _GG["Tap1Finger"]: (_("Single tap"), _("Performs a left click.")), + _GG["Tap2Finger"]: (_("Single tap with two fingers"), _("Performs a right click.")), + _GG["Tap3Finger"]: (_("Single tap with three fingers"), None), + _GG["Click1Finger"]: (None, None), + _GG["Click2Finger"]: (None, None), + _GG["Click3Finger"]: (None, None), + _GG["DoubleTap1Finger"]: (_("Double tap"), _("Performs a double click.")), + _GG["DoubleTap2Finger"]: (_("Double tap with two fingers"), None), + _GG["DoubleTap3Finger"]: (_("Double tap with three fingers"), None), + _GG["Track1Finger"]: (None, None), + _GG["TrackingAcceleration"]: (None, None), + _GG["TapDrag1Finger"]: (_("Tap and drag"), _("Drags items by dragging the finger after double tapping.")), + _GG["TapDrag2Finger"]: ( + _("Tap and drag with two fingers"), + _("Drags items by dragging the fingers after double tapping."), + ), + _GG["Drag3Finger"]: (_("Tap and drag with three fingers"), None), + _GG["TapGestures"]: (None, None), + _GG["FnClickGestureSuppression"]: ( + _("Suppress tap and edge gestures"), + _("Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)."), + ), + _GG["Scroll1Finger"]: (_("Scroll with one finger"), _("Scrolls.")), + _GG["Scroll2Finger"]: (_("Scroll with two fingers"), _("Scrolls.")), + _GG["Scroll2FingerHoriz"]: (_("Scroll horizontally with two fingers"), _("Scrolls horizontally.")), + _GG["Scroll2FingerVert"]: (_("Scroll vertically with two fingers"), _("Scrolls vertically.")), + _GG["Scroll2FingerStateless"]: (_("Scroll with two fingers"), _("Scrolls.")), + _GG["NaturalScrolling"]: (_("Natural scrolling"), _("Inverts the scrolling direction.")), + _GG["Thumbwheel"]: (_("Thumbwheel"), _("Enables the thumbwheel.")), + _GG["VScrollInertia"]: (None, None), + _GG["VScrollBallistics"]: (None, None), + _GG["Swipe2FingerHoriz"]: (None, None), + _GG["Swipe3FingerHoriz"]: (None, None), + _GG["Swipe4FingerHoriz"]: (None, None), + _GG["Swipe3FingerVert"]: (None, None), + _GG["Swipe4FingerVert"]: (None, None), + _GG["LeftEdgeSwipe1Finger"]: (None, None), + _GG["RightEdgeSwipe1Finger"]: (None, None), + _GG["BottomEdgeSwipe1Finger"]: (None, None), + _GG["TopEdgeSwipe1Finger"]: (_("Swipe from the top edge"), None), + _GG["LeftEdgeSwipe1Finger2"]: (_("Swipe from the left edge"), None), + _GG["RightEdgeSwipe1Finger2"]: (_("Swipe from the right edge"), None), + _GG["BottomEdgeSwipe1Finger2"]: (_("Swipe from the bottom edge"), None), + _GG["TopEdgeSwipe1Finger2"]: (_("Swipe from the top edge"), None), + _GG["LeftEdgeSwipe2Finger"]: (_("Swipe two fingers from the left edge"), None), + _GG["RightEdgeSwipe2Finger"]: (_("Swipe two fingers from the right edge"), None), + _GG["BottomEdgeSwipe2Finger"]: (_("Swipe two fingers from the bottom edge"), None), + _GG["TopEdgeSwipe2Finger"]: (_("Swipe two fingers from the top edge"), None), + _GG["Zoom2Finger"]: (_("Zoom with two fingers."), _("Pinch to zoom out; spread to zoom in.")), + _GG["Zoom2FingerPinch"]: (_("Pinch to zoom out."), _("Pinch to zoom out.")), + _GG["Zoom2FingerSpread"]: (_("Spread to zoom in."), _("Spread to zoom in.")), + _GG["Zoom3Finger"]: (_("Zoom with three fingers."), None), + _GG["Zoom2FingerStateless"]: (_("Zoom with two fingers"), _("Pinch to zoom out; spread to zoom in.")), + _GG["TwoFingersPresent"]: (None, None), + _GG["Rotate2Finger"]: (None, None), + _GG["Finger1"]: (None, None), + _GG["Finger2"]: (None, None), + _GG["Finger3"]: (None, None), + _GG["Finger4"]: (None, None), + _GG["Finger5"]: (None, None), + _GG["Finger6"]: (None, None), + _GG["Finger7"]: (None, None), + _GG["Finger8"]: (None, None), + _GG["Finger9"]: (None, None), + _GG["Finger10"]: (None, None), + _GG["DeviceSpecificRawData"]: (None, None), } _GESTURE2_PARAMS_LABELS = { - _GP['ExtraCapabilities']: (None, None), # not supported - _GP['PixelZone']: (_('Pixel zone'), None), # TO DO: replace None with a short description - _GP['RatioZone']: (_('Ratio zone'), None), # TO DO: replace None with a short description - _GP['ScaleFactor']: (_('Scale factor'), _('Sets the cursor speed.')), + _GP["ExtraCapabilities"]: (None, None), # not supported + _GP["PixelZone"]: (_("Pixel zone"), None), # TO DO: replace None with a short description + _GP["RatioZone"]: (_("Ratio zone"), None), # TO DO: replace None with a short description + _GP["ScaleFactor"]: (_("Scale factor"), _("Sets the cursor speed.")), } _GESTURE2_PARAMS_LABELS_SUB = { - 'left': (_('Left'), _('Left-most coordinate.')), - 'bottom': (_('Bottom'), _('Bottom coordinate.')), - 'width': (_('Width'), _('Width.')), - 'height': (_('Height'), _('Height.')), - 'scale': (_('Scale'), _('Cursor speed.')), + "left": (_("Left"), _("Left-most coordinate.")), + "bottom": (_("Bottom"), _("Bottom coordinate.")), + "width": (_("Width"), _("Width.")), + "height": (_("Height"), _("Height.")), + "scale": (_("Scale"), _("Cursor speed.")), } class Gesture2Gestures(_BitFieldOMSetting): - name = 'gesture2-gestures' - label = _('Gestures') - description = _('Tweak the mouse/touchpad behaviour.') + name = "gesture2-gestures" + label = _("Gestures") + description = _("Tweak the mouse/touchpad behaviour.") feature = _F.GESTURE_2 - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'om_method': _hidpp20.Gesture.enable_offset_mask} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"om_method": _hidpp20.Gesture.enable_offset_mask} choices_universe = _hidpp20_constants.GESTURE _labels = _GESTURE2_GESTURES_LABELS class validator_class(_BitFieldOMV): - @classmethod def build(cls, setting_class, device, om_method=None): options = [g for g in device.gestures.gestures.values() if g.can_be_enabled or g.default_enabled] @@ -1248,17 +1240,16 @@ def build(cls, setting_class, device, om_method=None): class Gesture2Divert(_BitFieldOMSetting): - name = 'gesture2-divert' - label = _('Gestures Diversion') - description = _('Divert mouse/touchpad gestures.') + name = "gesture2-divert" + label = _("Gestures Diversion") + description = _("Divert mouse/touchpad gestures.") feature = _F.GESTURE_2 - rw_options = {'read_fnid': 0x30, 'write_fnid': 0x40} - validator_options = {'om_method': _hidpp20.Gesture.diversion_offset_mask} + rw_options = {"read_fnid": 0x30, "write_fnid": 0x40} + validator_options = {"om_method": _hidpp20.Gesture.diversion_offset_mask} choices_universe = _hidpp20_constants.GESTURE _labels = _GESTURE2_GESTURES_LABELS class validator_class(_BitFieldOMV): - @classmethod def build(cls, setting_class, device, om_method=None): options = [g for g in device.gestures.gestures.values() if g.can_be_diverted] @@ -1266,11 +1257,11 @@ def build(cls, setting_class, device, om_method=None): class Gesture2Params(_LongSettings): - name = 'gesture2-params' - label = _('Gesture params') - description = _('Change numerical parameters of a mouse/touchpad.') + name = "gesture2-params" + label = _("Gesture params") + description = _("Change numerical parameters of a mouse/touchpad.") feature = _F.GESTURE_2 - rw_options = {'read_fnid': 0x70, 'write_fnid': 0x80} + rw_options = {"read_fnid": 0x70, "write_fnid": 0x80} choices_universe = _hidpp20.PARAM sub_items_universe = _hidpp20.SUB_PARAM # item (NamedInt) -> list/tuple of objects that have the following attributes @@ -1280,7 +1271,6 @@ class Gesture2Params(_LongSettings): _labels_sub = _GESTURE2_PARAMS_LABELS_SUB class validator_class(_MultipleRangeV): - @classmethod def build(cls, setting_class, device): params = _hidpp20.get_gestures(device).params.values() @@ -1292,28 +1282,29 @@ def build(cls, setting_class, device): class MKeyLEDs(_BitFieldSetting): - name = 'm-key-leds' - label = _('M-Key LEDs') + name = "m-key-leds" + label = _("M-Key LEDs") description = ( - _('Control the M-Key LEDs.') + '\n' + _('May need Onboard Profiles set to Disable to be effective.') + '\n' + - _('May need G Keys diverted to be effective.') + _("Control the M-Key LEDs.") + + "\n" + + _("May need Onboard Profiles set to Disable to be effective.") + + "\n" + + _("May need G Keys diverted to be effective.") ) feature = _F.MKEYS choices_universe = _NamedInts() for i in range(8): - choices_universe[1 << i] = 'M' + str(i + 1) - _labels = {k: (None, _('Lights up the %s key.') % k) for k in choices_universe} + choices_universe[1 << i] = "M" + str(i + 1) + _labels = {k: (None, _("Lights up the %s key.") % k) for k in choices_universe} class rw_class(_FeatureRW): - def __init__(self, feature): super().__init__(feature, write_fnid=0x10) def read(self, device): # no way to read, so just assume off - return b'\x00' + return b"\x00" class validator_class(_BitFieldV): - @classmethod def build(cls, setting_class, device): number = device.feature_request(setting_class.feature, 0x00)[0] @@ -1322,32 +1313,35 @@ def build(cls, setting_class, device): class MRKeyLED(_Setting): - name = 'mr-key-led' - label = _('MR-Key LED') + name = "mr-key-led" + label = _("MR-Key LED") description = ( - _('Control the MR-Key LED.') + '\n' + _('May need Onboard Profiles set to Disable to be effective.') + '\n' + - _('May need G Keys diverted to be effective.') + _("Control the MR-Key LED.") + + "\n" + + _("May need Onboard Profiles set to Disable to be effective.") + + "\n" + + _("May need G Keys diverted to be effective.") ) feature = _F.MR class rw_class(_FeatureRW): - def __init__(self, feature): super().__init__(feature, write_fnid=0x00) def read(self, device): # no way to read, so just assume off - return b'\x00' + return b"\x00" ## Only implemented for devices that can produce Key and Consumer Codes (e.g., Craft) ## and devices that can produce Key, Mouse, and Horizontal Scroll (e.g., M720) ## Only interested in current host, so use 0xFF for it class PersistentRemappableAction(_Settings): - name = 'persistent-remappable-keys' - label = _('Persistent Key/Button Mapping') + name = "persistent-remappable-keys" + label = _("Persistent Key/Button Mapping") description = ( - _('Permanently change the mapping for the key or button.') + '\n' + - _('Changing important keys or buttons (such as for the left mouse button) can result in an unusable system.') + _("Permanently change the mapping for the key or button.") + + "\n" + + _("Changing important keys or buttons (such as for the left mouse button) can result in an unusable system.") ) persist = False # This setting is persistent in the device so no need to persist it here feature = _F.PERSISTENT_REMAPPABLE_ACTION @@ -1355,14 +1349,13 @@ class PersistentRemappableAction(_Settings): choices_universe = _special_keys.KEYS class rw_class: - def __init__(self, feature): self.feature = feature self.kind = _FeatureRW.kind def read(self, device, key): ks = device.remap_keys[device.remap_keys.index(key)] - return b'\x00\x00' + ks.data_bytes + return b"\x00\x00" + ks.data_bytes def write(self, device, key, data_bytes): ks = device.remap_keys[device.remap_keys.index(key)] @@ -1370,7 +1363,6 @@ def write(self, device, key, data_bytes): return v class validator_class(_ChoicesMapV): - @classmethod def build(cls, setting_class, device): remap_keys = device.remap_keys @@ -1383,7 +1375,7 @@ def build(cls, setting_class, device): keys = _special_keys.KEYS_KEYS_MOUSE_HSCROLL else: if logger.isEnabledFor(_WARN): - logger.warning('%s: unimplemented Persistent Remappable capability %s', device.name, hex(capabilities)) + logger.warning("%s: unimplemented Persistent Remappable capability %s", device.name, hex(capabilities)) return None choices = {} for k in remap_keys: @@ -1399,15 +1391,15 @@ def validate_read(self, reply_bytes, key): # Craft keyboard has a value that isn't valid so fudge these values if reply_value not in self.choices[key]: if logger.isEnabledFor(_WARN): - logger.warning('unusual persistent remappable action mapping %x: use Default', reply_value) + logger.warning("unusual persistent remappable action mapping %x: use Default", reply_value) reply_value = _special_keys.KEYS_Default return reply_value class Sidetone(_Setting): - name = 'sidetone' - label = _('Sidetone') - description = _('Set sidetone level.') + name = "sidetone" + label = _("Sidetone") + description = _("Set sidetone level.") feature = _F.SIDETONE validator_class = _RangeV min_value = 0 @@ -1415,21 +1407,20 @@ class Sidetone(_Setting): class Equalizer(_RangeFieldSetting): - name = 'equalizer' - label = _('Equalizer') - description = _('Set equalizer levels.') + name = "equalizer" + label = _("Equalizer") + description = _("Set equalizer levels.") feature = _F.EQUALIZER - rw_options = {'read_fnid': 0x20, 'write_fnid': 0x30, 'read_prefix': b'\x00'} + rw_options = {"read_fnid": 0x20, "write_fnid": 0x30, "read_prefix": b"\x00"} keys_universe = [] class validator_class(_PackedRangeV): - @classmethod def build(cls, setting_class, device): data = device.feature_request(_F.EQUALIZER, 0x00) if not data: return None - count, dbRange, _x, dbMin, dbMax = _unpack('!BBBBB', data[:5]) + count, dbRange, _x, dbMin, dbMax = _unpack("!BBBBB", data[:5]) if dbMin == 0: dbMin = -dbRange if dbMax == 0: @@ -1440,31 +1431,31 @@ def build(cls, setting_class, device): for b in range(7): if g * 7 + b >= count: break - map[g * 7 + b] = str(int.from_bytes(freqs[2 * b + 1:2 * b + 3], 'big')) + _('Hz') - return cls(map, min_value=dbMin, max_value=dbMax, count=count, write_prefix_bytes=b'\x02') + map[g * 7 + b] = str(int.from_bytes(freqs[2 * b + 1 : 2 * b + 3], "big")) + _("Hz") + return cls(map, min_value=dbMin, max_value=dbMax, count=count, write_prefix_bytes=b"\x02") class ADCPower(_Setting): - name = 'adc_power_management' - label = _('Power Management') - description = _('Power off in minutes (0 for never).') + name = "adc_power_management" + label = _("Power Management") + description = _("Power off in minutes (0 for never).") feature = _F.ADC_MEASUREMENT - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} validator_class = _RangeV min_value = 0x00 - max_value = 0xff - validator_options = {'byte_count': 1} + max_value = 0xFF + validator_options = {"byte_count": 1} class LEDControl(_Setting): - name = 'led_control' - label = _('LED Control') - description = _('Switch control of LEDs between device and Solaar') + name = "led_control" + label = _("LED Control") + description = _("Switch control of LEDs between device and Solaar") feature = _F.COLOR_LED_EFFECTS - rw_options = {'read_fnid': 0x70, 'write_fnid': 0x80} + rw_options = {"read_fnid": 0x70, "write_fnid": 0x80} choices_universe = _NamedInts(Device=0, Solaar=1) validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + validator_options = {"choices": choices_universe} colors = _special_keys.COLORS @@ -1473,15 +1464,15 @@ class LEDControl(_Setting): # an LED Zone has an index, a set of possible LED effects, and an LED effect setting class LEDZoneSetting(_Setting): - name = 'led_zone_' - label = _('LED Zone Effects') - description = _('Set effect for LED Zone') + name = "led_zone_" + label = _("LED Zone Effects") + description = _("Set effect for LED Zone") feature = _F.COLOR_LED_EFFECTS - color_field = {'name': _LEDP.color, 'kind': _KIND.choice, 'label': None, 'choices': colors} - speed_field = {'name': _LEDP.speed, 'kind': _KIND.range, 'label': _('Speed'), 'min': 0, 'max': 255} - period_field = {'name': _LEDP.period, 'kind': _KIND.range, 'label': _('Period'), 'min': 100, 'max': 5000} - intensity_field = {'name': _LEDP.intensity, 'kind': _KIND.range, 'label': _('Intensity'), 'min': 0, 'max': 100} - ramp_field = {'name': _LEDP.ramp, 'kind': _KIND.choice, 'label': _('Ramp'), 'choices': _hidpp20.LEDRampChoices} + color_field = {"name": _LEDP.color, "kind": _KIND.choice, "label": None, "choices": colors} + speed_field = {"name": _LEDP.speed, "kind": _KIND.range, "label": _("Speed"), "min": 0, "max": 255} + period_field = {"name": _LEDP.period, "kind": _KIND.range, "label": _("Period"), "min": 100, "max": 5000} + intensity_field = {"name": _LEDP.intensity, "kind": _KIND.range, "label": _("Intensity"), "min": 0, "max": 100} + ramp_field = {"name": _LEDP.ramp, "kind": _KIND.choice, "label": _("Ramp"), "choices": _hidpp20.LEDRampChoices} # form_field = { 'name': _LEDP.form, 'kind': _KIND.choice, 'label': _('Form'), 'choices': _hidpp20.LEDFormChoices } possible_fields = [color_field, speed_field, period_field, intensity_field, ramp_field] @@ -1495,9 +1486,9 @@ def build(cls, device): validator = _HeteroV(data_class=_hidpp20.LEDEffectSetting, options=zone.effects, readable=infos.readable) setting = cls(device, rw, validator) setting.name = cls.name + str(int(zone.location)) - setting.label = _('LEDs') + ' ' + str(_hidpp20.LEDZoneLocations[zone.location]) + setting.label = _("LEDs") + " " + str(_hidpp20.LEDZoneLocations[zone.location]) choices = [_hidpp20.LEDEffects[e.ID][0] for e in zone.effects] - ID_field = {'name': 'ID', 'kind': _KIND.choice, 'label': None, 'choices': choices} + ID_field = {"name": "ID", "kind": _KIND.choice, "label": None, "choices": choices} setting.possible_fields = [ID_field] + cls.possible_fields setting.fields_map = _hidpp20.LEDEffects settings.append(setting) @@ -1571,10 +1562,10 @@ def check_feature(device, sclass): try: detected = sclass.build(device) if logger.isEnabledFor(logging.DEBUG): - 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 except Exception as e: - 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 @@ -1585,7 +1576,7 @@ def check_feature_settings(device, already_known): return False if device.protocol and device.protocol < 2.0: return False - absent = device.persister.get('_absent', []) if device.persister else [] + absent = device.persister.get("_absent", []) if device.persister else [] newAbsent = [] for sclass in SETTINGS: if sclass.feature: @@ -1606,7 +1597,7 @@ def check_feature_settings(device, already_known): newAbsent.append(sclass.name) if device.persister and newAbsent: absent.extend(newAbsent) - device.persister['_absent'] = absent + device.persister["_absent"] = absent return True diff --git a/lib/logitech_receiver/special_keys.py b/lib/logitech_receiver/special_keys.py index d3a52a4dd4..e847e30331 100644 --- a/lib/logitech_receiver/special_keys.py +++ b/lib/logitech_receiver/special_keys.py @@ -23,296 +23,298 @@ from .common import UnsortedNamedInts as _UnsortedNamedInts # > 24) in action_ids: keys[int(key)] = str(key) @@ -1250,141 +1254,143 @@ def persistent_keys(action_ids): KEYS_KEYS_CONSUMER = persistent_keys([ACTIONID.Key, ACTIONID.Consumer]) KEYS_KEYS_MOUSE_HSCROLL = persistent_keys([ACTIONID.Key, ACTIONID.Mouse, ACTIONID.Hscroll]) -COLORS = _UnsortedNamedInts({ - # from Xorg rgb.txt,v 1.3 2000/08/17 - 'red': 0xff0000, - 'orange': 0xffa500, - 'yellow': 0xffff00, - 'green': 0x00ff00, - 'blue': 0x0000ff, - 'purple': 0xa020f0, - 'violet': 0xee82ee, - 'black': 0x000000, - 'white': 0xffffff, - 'gray': 0xbebebe, - 'brown': 0xa52a2a, - 'cyan': 0x00ffff, - 'magenta': 0xff00ff, - 'pink': 0xffc0cb, - 'maroon': 0xb03060, - 'turquoise': 0x40e0d0, - 'gold': 0xffd700, - 'tan': 0xd2b48c, - 'snow': 0xfffafa, - 'ghost white': 0xf8f8ff, - 'white smoke': 0xf5f5f5, - 'gainsboro': 0xdcdcdc, - 'floral white': 0xfffaf0, - 'old lace': 0xfdf5e6, - 'linen': 0xfaf0e6, - 'antique white': 0xfaebd7, - 'papaya whip': 0xffefd5, - 'blanched almond': 0xffebcd, - 'bisque': 0xffe4c4, - 'peach puff': 0xffdab9, - 'navajo white': 0xffdead, - 'moccasin': 0xffe4b5, - 'cornsilk': 0xfff8dc, - 'ivory': 0xfffff0, - 'lemon chiffon': 0xfffacd, - 'seashell': 0xfff5ee, - 'honeydew': 0xf0fff0, - 'mint cream': 0xf5fffa, - 'azure': 0xf0ffff, - 'alice blue': 0xf0f8ff, - 'lavender': 0xe6e6fa, - 'lavender blush': 0xfff0f5, - 'misty rose': 0xffe4e1, - 'dark slate gray': 0x2f4f4f, - 'dim gray': 0x696969, - 'slate gray': 0x708090, - 'light slate gray': 0x778899, - 'light gray': 0xd3d3d3, - 'midnight blue': 0x191970, - 'navy blue': 0x000080, - 'cornflower blue': 0x6495ed, - 'dark slate blue': 0x483d8b, - 'slate blue': 0x6a5acd, - 'medium slate blue': 0x7b68ee, - 'light slate blue': 0x8470ff, - 'medium blue': 0x0000cd, - 'royal blue': 0x4169e1, - 'dodger blue': 0x1e90ff, - 'deep sky blue': 0x00bfff, - 'sky blue': 0x87ceeb, - 'light sky blue': 0x87cefa, - 'steel blue': 0x4682b4, - 'light steel blue': 0xb0c4de, - 'light blue': 0xadd8e6, - 'powder blue': 0xb0e0e6, - 'pale turquoise': 0xafeeee, - 'dark turquoise': 0x00ced1, - 'medium turquoise': 0x48d1cc, - 'light cyan': 0xe0ffff, - 'cadet blue': 0x5f9ea0, - 'medium aquamarine': 0x66cdaa, - 'aquamarine': 0x7fffd4, - 'dark green': 0x006400, - 'dark olive green': 0x556b2f, - 'dark sea green': 0x8fbc8f, - 'sea green': 0x2e8b57, - 'medium sea green': 0x3cb371, - 'light sea green': 0x20b2aa, - 'pale green': 0x98fb98, - 'spring green': 0x00ff7f, - 'lawn green': 0x7cfc00, - 'chartreuse': 0x7fff00, - 'medium spring green': 0x00fa9a, - 'green yellow': 0xadff2f, - 'lime green': 0x32cd32, - 'yellow green': 0x9acd32, - 'forest green': 0x228b22, - 'olive drab': 0x6b8e23, - 'dark khaki': 0xbdb76b, - 'khaki': 0xf0e68c, - 'pale goldenrod': 0xeee8aa, - 'light goldenrod yellow': 0xfafad2, - 'light yellow': 0xffffe0, - 'light goldenrod': 0xeedd82, - 'goldenrod': 0xdaa520, - 'dark goldenrod': 0xb8860b, - 'rosy brown': 0xbc8f8f, - 'indian red': 0xcd5c5c, - 'saddle brown': 0x8b4513, - 'sienna': 0xa0522d, - 'peru': 0xcd853f, - 'burlywood': 0xdeb887, - 'beige': 0xf5f5dc, - 'wheat': 0xf5deb3, - 'sandy brown': 0xf4a460, - 'chocolate': 0xd2691e, - 'firebrick': 0xb22222, - 'dark salmon': 0xe9967a, - 'salmon': 0xfa8072, - 'light salmon': 0xffa07a, - 'dark orange': 0xff8c00, - 'coral': 0xff7f50, - 'light coral': 0xf08080, - 'tomato': 0xff6347, - 'orange red': 0xff4500, - 'hot pink': 0xff69b4, - 'deep pink': 0xff1493, - 'light pink': 0xffb6c1, - 'pale violet red': 0xdb7093, - 'medium violet red': 0xc71585, - 'violet red': 0xd02090, - 'plum': 0xdda0dd, - 'orchid': 0xda70d6, - 'medium orchid': 0xba55d3, - 'dark orchid': 0x9932cc, - 'dark violet': 0x9400d3, - 'blue violet': 0x8a2be2, - 'medium purple': 0x9370db, - 'thistle': 0xd8bfd8, - 'dark gray': 0xa9a9a9, - 'dark blue': 0x00008b, - 'dark cyan': 0x008b8b, - 'dark magenta': 0x8b008b, - 'dark red': 0x8b0000, - 'light green': 0x90ee90, -}) +COLORS = _UnsortedNamedInts( + { + # from Xorg rgb.txt,v 1.3 2000/08/17 + "red": 0xFF0000, + "orange": 0xFFA500, + "yellow": 0xFFFF00, + "green": 0x00FF00, + "blue": 0x0000FF, + "purple": 0xA020F0, + "violet": 0xEE82EE, + "black": 0x000000, + "white": 0xFFFFFF, + "gray": 0xBEBEBE, + "brown": 0xA52A2A, + "cyan": 0x00FFFF, + "magenta": 0xFF00FF, + "pink": 0xFFC0CB, + "maroon": 0xB03060, + "turquoise": 0x40E0D0, + "gold": 0xFFD700, + "tan": 0xD2B48C, + "snow": 0xFFFAFA, + "ghost white": 0xF8F8FF, + "white smoke": 0xF5F5F5, + "gainsboro": 0xDCDCDC, + "floral white": 0xFFFAF0, + "old lace": 0xFDF5E6, + "linen": 0xFAF0E6, + "antique white": 0xFAEBD7, + "papaya whip": 0xFFEFD5, + "blanched almond": 0xFFEBCD, + "bisque": 0xFFE4C4, + "peach puff": 0xFFDAB9, + "navajo white": 0xFFDEAD, + "moccasin": 0xFFE4B5, + "cornsilk": 0xFFF8DC, + "ivory": 0xFFFFF0, + "lemon chiffon": 0xFFFACD, + "seashell": 0xFFF5EE, + "honeydew": 0xF0FFF0, + "mint cream": 0xF5FFFA, + "azure": 0xF0FFFF, + "alice blue": 0xF0F8FF, + "lavender": 0xE6E6FA, + "lavender blush": 0xFFF0F5, + "misty rose": 0xFFE4E1, + "dark slate gray": 0x2F4F4F, + "dim gray": 0x696969, + "slate gray": 0x708090, + "light slate gray": 0x778899, + "light gray": 0xD3D3D3, + "midnight blue": 0x191970, + "navy blue": 0x000080, + "cornflower blue": 0x6495ED, + "dark slate blue": 0x483D8B, + "slate blue": 0x6A5ACD, + "medium slate blue": 0x7B68EE, + "light slate blue": 0x8470FF, + "medium blue": 0x0000CD, + "royal blue": 0x4169E1, + "dodger blue": 0x1E90FF, + "deep sky blue": 0x00BFFF, + "sky blue": 0x87CEEB, + "light sky blue": 0x87CEFA, + "steel blue": 0x4682B4, + "light steel blue": 0xB0C4DE, + "light blue": 0xADD8E6, + "powder blue": 0xB0E0E6, + "pale turquoise": 0xAFEEEE, + "dark turquoise": 0x00CED1, + "medium turquoise": 0x48D1CC, + "light cyan": 0xE0FFFF, + "cadet blue": 0x5F9EA0, + "medium aquamarine": 0x66CDAA, + "aquamarine": 0x7FFFD4, + "dark green": 0x006400, + "dark olive green": 0x556B2F, + "dark sea green": 0x8FBC8F, + "sea green": 0x2E8B57, + "medium sea green": 0x3CB371, + "light sea green": 0x20B2AA, + "pale green": 0x98FB98, + "spring green": 0x00FF7F, + "lawn green": 0x7CFC00, + "chartreuse": 0x7FFF00, + "medium spring green": 0x00FA9A, + "green yellow": 0xADFF2F, + "lime green": 0x32CD32, + "yellow green": 0x9ACD32, + "forest green": 0x228B22, + "olive drab": 0x6B8E23, + "dark khaki": 0xBDB76B, + "khaki": 0xF0E68C, + "pale goldenrod": 0xEEE8AA, + "light goldenrod yellow": 0xFAFAD2, + "light yellow": 0xFFFFE0, + "light goldenrod": 0xEEDD82, + "goldenrod": 0xDAA520, + "dark goldenrod": 0xB8860B, + "rosy brown": 0xBC8F8F, + "indian red": 0xCD5C5C, + "saddle brown": 0x8B4513, + "sienna": 0xA0522D, + "peru": 0xCD853F, + "burlywood": 0xDEB887, + "beige": 0xF5F5DC, + "wheat": 0xF5DEB3, + "sandy brown": 0xF4A460, + "chocolate": 0xD2691E, + "firebrick": 0xB22222, + "dark salmon": 0xE9967A, + "salmon": 0xFA8072, + "light salmon": 0xFFA07A, + "dark orange": 0xFF8C00, + "coral": 0xFF7F50, + "light coral": 0xF08080, + "tomato": 0xFF6347, + "orange red": 0xFF4500, + "hot pink": 0xFF69B4, + "deep pink": 0xFF1493, + "light pink": 0xFFB6C1, + "pale violet red": 0xDB7093, + "medium violet red": 0xC71585, + "violet red": 0xD02090, + "plum": 0xDDA0DD, + "orchid": 0xDA70D6, + "medium orchid": 0xBA55D3, + "dark orchid": 0x9932CC, + "dark violet": 0x9400D3, + "blue violet": 0x8A2BE2, + "medium purple": 0x9370DB, + "thistle": 0xD8BFD8, + "dark gray": 0xA9A9A9, + "dark blue": 0x00008B, + "dark cyan": 0x008B8B, + "dark magenta": 0x8B008B, + "dark red": 0x8B0000, + "light green": 0x90EE90, + } +) diff --git a/lib/logitech_receiver/status.py b/lib/logitech_receiver/status.py index dd293f8223..f484922947 100644 --- a/lib/logitech_receiver/status.py +++ b/lib/logitech_receiver/status.py @@ -58,7 +58,7 @@ def attach_to(device, changed_callback): assert device assert changed_callback - if not hasattr(device, 'status') or device.status is None: + if not hasattr(device, "status") or device.status is None: if not device.isDevice: device.status = ReceiverStatus(device, changed_callback) else: @@ -97,10 +97,9 @@ def __init__(self, receiver, changed_callback): def to_string(self): count = len(self._receiver) return ( - _('No paired devices.') - if count == 0 else ngettext('%(count)s paired device.', '%(count)s paired devices.', count) % { - 'count': count - } + _("No paired devices.") + if count == 0 + else ngettext("%(count)s paired device.", "%(count)s paired devices.", count) % {"count": count} ) def __str__(self): @@ -129,21 +128,20 @@ def __init__(self, device, changed_callback): self._active = None # is the device active? def to_string(self): - - status = '' + status = "" battery_level = self.get(KEYS.BATTERY_LEVEL) if battery_level is not None: if isinstance(battery_level, _NamedInt): - status = _('Battery: %(level)s') % {'level': _(str(battery_level))} + status = _("Battery: %(level)s") % {"level": _(str(battery_level))} else: - status = _('Battery: %(percent)d%%') % {'percent': battery_level} + status = _("Battery: %(percent)d%%") % {"percent": battery_level} battery_status = self.get(KEYS.BATTERY_STATUS) if battery_status is not None: - status += ' (%s)' % _(str(battery_status)) + status += " (%s)" % _(str(battery_status)) return status def __repr__(self): - return '{' + ', '.join('\'%s\': %r' % (k, v) for k, v in self.items()) + '}' + return "{" + ", ".join("'%s': %r" % (k, v) for k, v in self.items()) + "}" def __bool__(self): return bool(self._active) @@ -152,7 +150,7 @@ def __bool__(self): def set_battery_info(self, level, nextLevel, status, voltage): if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s: battery %s, %s', self._device, level, status) + logger.debug("%s: battery %s, %s", self._device, level, status) if level is None: # Some notifications may come with no battery level info, just @@ -176,8 +174,10 @@ def set_battery_info(self, level, nextLevel, status, voltage): old_voltage, self[KEYS.BATTERY_VOLTAGE] = self.get(KEYS.BATTERY_VOLTAGE), voltage charging = status in ( - _hidpp20_constants.BATTERY_STATUS.recharging, _hidpp20_constants.BATTERY_STATUS.almost_full, - _hidpp20_constants.BATTERY_STATUS.full, _hidpp20_constants.BATTERY_STATUS.slow_recharge + _hidpp20_constants.BATTERY_STATUS.recharging, + _hidpp20_constants.BATTERY_STATUS.almost_full, + _hidpp20_constants.BATTERY_STATUS.full, + _hidpp20_constants.BATTERY_STATUS.slow_recharge, ) old_charging, self[KEYS.BATTERY_CHARGING] = self.get(KEYS.BATTERY_CHARGING), charging @@ -187,15 +187,15 @@ def set_battery_info(self, level, nextLevel, status, voltage): if _hidpp20_constants.BATTERY_OK(status) and (level is None or level > _BATTERY_ATTENTION_LEVEL): self[KEYS.ERROR] = None else: - logger.warning('%s: battery %d%%, ALERT %s', self._device, level, status) + logger.warning("%s: battery %d%%, ALERT %s", self._device, level, status) if self.get(KEYS.ERROR) != status: self[KEYS.ERROR] = status # only show the notification once alert = ALERT.NOTIFICATION | ALERT.ATTENTION if isinstance(level, _NamedInt): - reason = _('Battery: %(level)s (%(status)s)') % {'level': _(level), 'status': _(status)} + reason = _("Battery: %(level)s (%(status)s)") % {"level": _(level), "status": _(status)} else: - reason = _('Battery: %(percent)d%% (%(status)s)') % {'percent': level, 'status': status.name} + reason = _("Battery: %(percent)d%% (%(status)s)") % {"percent": level, "status": status.name} if changed or reason or not self._active: # a battery response means device is active # update the leds on the device, if any @@ -241,11 +241,14 @@ def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False): # Push settings for new devices (was_active is None), # when devices request software reconfiguration # and when devices become active if they don't have wireless device status feature, - if was_active is None or push or not was_active and ( - not d.features or _hidpp20_constants.FEATURE.WIRELESS_DEVICE_STATUS not in d.features + if ( + was_active is None + or push + or not was_active + and (not d.features or _hidpp20_constants.FEATURE.WIRELESS_DEVICE_STATUS not in d.features) ): if logger.isEnabledFor(logging.INFO): - logger.info('%s pushing device settings %s', d, d.settings) + logger.info("%s pushing device settings %s", d, d.settings) _settings.apply_all_settings(d) else: diff --git a/lib/solaar/__init__.py b/lib/solaar/__init__.py index 5d443f56e7..ee1ea2cff8 100644 --- a/lib/solaar/__init__.py +++ b/lib/solaar/__init__.py @@ -20,13 +20,16 @@ import subprocess as _subprocess import sys as _sys -NAME = 'solaar' +NAME = "solaar" try: - __version__ = _subprocess.check_output(['git', 'describe', '--always'], cwd=_sys.path[0], - stderr=_subprocess.DEVNULL).strip().decode() + __version__ = ( + _subprocess.check_output(["git", "describe", "--always"], cwd=_sys.path[0], stderr=_subprocess.DEVNULL) + .strip() + .decode() + ) except Exception: try: - __version__ = _pkgutil.get_data('solaar', 'commit').strip().decode() + __version__ = _pkgutil.get_data("solaar", "commit").strip().decode() except Exception: - __version__ = _pkgutil.get_data('solaar', 'version').strip().decode() + __version__ = _pkgutil.get_data("solaar", "version").strip().decode() diff --git a/lib/solaar/cli/__init__.py b/lib/solaar/cli/__init__.py index 5fc68f0dae..c78c645a14 100644 --- a/lib/solaar/cli/__init__.py +++ b/lib/solaar/cli/__init__.py @@ -27,6 +27,7 @@ import logitech_receiver.receiver as _receiver from logitech_receiver.base import receivers, receivers_and_devices + from solaar import NAME logger = logging.getLogger(__name__) @@ -38,70 +39,66 @@ def _create_parser(): parser = _argparse.ArgumentParser( - prog=NAME.lower(), - add_help=False, - epilog='For details on individual actions, run `%s --help`.' % NAME.lower() + prog=NAME.lower(), add_help=False, epilog="For details on individual actions, run `%s --help`." % NAME.lower() ) - subparsers = parser.add_subparsers(title='actions', help='optional action to perform') + subparsers = parser.add_subparsers(title="actions", help="optional action to perform") - sp = subparsers.add_parser('show', help='show information about devices') + sp = subparsers.add_parser("show", help="show information about devices") sp.add_argument( - 'device', - nargs='?', - default='all', - help='device to show information about; may be a device number (1..6), a serial number, ' - 'a substring of a device\'s name, or "all" (the default)' + "device", + nargs="?", + default="all", + help="device to show information about; may be a device number (1..6), a serial number, " + 'a substring of a device\'s name, or "all" (the default)', ) - sp.set_defaults(action='show') + sp.set_defaults(action="show") - sp = subparsers.add_parser('probe', help='probe a receiver (debugging use only)') + sp = subparsers.add_parser("probe", help="probe a receiver (debugging use only)") sp.add_argument( - 'receiver', nargs='?', help='select receiver by name substring or serial number when more than one is present' + "receiver", nargs="?", help="select receiver by name substring or serial number when more than one is present" ) - sp.set_defaults(action='probe') + sp.set_defaults(action="probe") - sp = subparsers.add_parser('profiles', help='read or write onboard profiles', epilog='Only works on active devices.') + sp = subparsers.add_parser("profiles", help="read or write onboard profiles", epilog="Only works on active devices.") sp.add_argument( - 'device', - help='device to read or write profiles of; may be a device number (1..6), a serial number, ' - 'a substring of a device\'s name' + "device", + help="device to read or write profiles of; may be a device number (1..6), a serial number, " + "a substring of a device's name", ) - sp.add_argument('profiles', nargs='?', help='file containing YAML dump of profiles') - sp.set_defaults(action='profiles') + sp.add_argument("profiles", nargs="?", help="file containing YAML dump of profiles") + sp.set_defaults(action="profiles") sp = subparsers.add_parser( - 'config', - help='read/write device-specific settings', - epilog='Please note that configuration only works on active devices.' + "config", + help="read/write device-specific settings", + epilog="Please note that configuration only works on active devices.", ) sp.add_argument( - 'device', - help='device to configure; may be a device number (1..6), a serial number, ' - 'or a substring of a device\'s name' + "device", + help="device to configure; may be a device number (1..6), a serial number, " "or a substring of a device's name", ) - sp.add_argument('setting', nargs='?', help='device-specific setting; leave empty to list available settings') - sp.add_argument('value_key', nargs='?', help='new value for the setting or key for keyed settings') - sp.add_argument('extra_subkey', nargs='?', help='value for keyed or subkey for subkeyed settings') - sp.add_argument('extra2', nargs='?', help='value for subkeyed settings') - sp.set_defaults(action='config') + sp.add_argument("setting", nargs="?", help="device-specific setting; leave empty to list available settings") + sp.add_argument("value_key", nargs="?", help="new value for the setting or key for keyed settings") + sp.add_argument("extra_subkey", nargs="?", help="value for keyed or subkey for subkeyed settings") + sp.add_argument("extra2", nargs="?", help="value for subkeyed settings") + sp.set_defaults(action="config") sp = subparsers.add_parser( - 'pair', - help='pair a new device', - epilog='The Logitech Unifying Receiver supports up to 6 paired devices at the same time.' + "pair", + help="pair a new device", + epilog="The Logitech Unifying Receiver supports up to 6 paired devices at the same time.", ) sp.add_argument( - 'receiver', nargs='?', help='select receiver by name substring or serial number when more than one is present' + "receiver", nargs="?", help="select receiver by name substring or serial number when more than one is present" ) - sp.set_defaults(action='pair') + sp.set_defaults(action="pair") - sp = subparsers.add_parser('unpair', help='unpair a device') + sp = subparsers.add_parser("unpair", help="unpair a device") sp.add_argument( - 'device', - help='device to unpair; may be a device number (1..6), a serial number, ' - 'or a substring of a device\'s name.' + "device", + help="device to unpair; may be a device number (1..6), a serial number, " "or a substring of a device's name.", ) - sp.set_defaults(action='unpair') + sp.set_defaults(action="unpair") return parser, subparsers.choices @@ -117,12 +114,12 @@ def _receivers(dev_path=None): try: r = _receiver.Receiver.open(dev_info) if logger.isEnabledFor(logging.DEBUG): - logger.debug('[%s] => %s', dev_info.path, r) + logger.debug("[%s] => %s", dev_info.path, r) if r: yield r except Exception as e: - logger.exception('opening ' + str(dev_info)) - _sys.exit('%s: error: %s' % (NAME, str(e))) + logger.exception("opening " + str(dev_info)) + _sys.exit("%s: error: %s" % (NAME, str(e))) def _receivers_and_devices(dev_path=None): @@ -132,12 +129,12 @@ def _receivers_and_devices(dev_path=None): try: d = _device.Device.open(dev_info) if dev_info.isDevice else _receiver.Receiver.open(dev_info) if logger.isEnabledFor(logging.DEBUG): - logger.debug('[%s] => %s', dev_info.path, d) + logger.debug("[%s] => %s", dev_info.path, d) if d is not None: yield d except Exception as e: - logger.exception('opening ' + str(dev_info)) - _sys.exit('%s: error: %s' % (NAME, str(e))) + logger.exception("opening " + str(dev_info)) + _sys.exit("%s: error: %s" % (NAME, str(e))) def _find_receiver(receivers, name): @@ -178,7 +175,9 @@ def _find_device(receivers, name): for dev in r: if ( - name == dev.serial.lower() or name == dev.codename.lower() or name == str(dev.kind).lower() + name == dev.serial.lower() + or name == dev.codename.lower() + or name == str(dev.kind).lower() or name in dev.name.lower() ): yield dev @@ -191,7 +190,6 @@ def _find_device(receivers, name): def run(cli_args=None, hidraw_path=None): - if cli_args: action = cli_args[0] args = _cli_parser.parse_args(cli_args) @@ -199,15 +197,15 @@ def run(cli_args=None, hidraw_path=None): args = _cli_parser.parse_args() # Python 3 has an undocumented 'feature' that breaks parsing empty args # http://bugs.python.org/issue16308 - if 'cmd' not in args: + if "cmd" not in args: _cli_parser.print_usage(_sys.stderr) - _sys.stderr.write('%s: error: too few arguments\n' % NAME.lower()) + _sys.stderr.write("%s: error: too few arguments\n" % NAME.lower()) _sys.exit(2) action = args.action assert action in actions try: - if action == 'show' or action == 'probe' or action == 'config' or action == 'profiles': + if action == "show" or action == "probe" or action == "config" or action == "profiles": c = list(_receivers_and_devices(hidraw_path)) else: c = list(_receivers(hidraw_path)) @@ -215,10 +213,10 @@ def run(cli_args=None, hidraw_path=None): raise Exception( 'No supported device found. Use "lsusb" and "bluetoothctl devices Connected" to list connected devices.' ) - m = import_module('.' + action, package=__name__) + m = import_module("." + action, package=__name__) m.run(c, args, _find_receiver, _find_device) except AssertionError: tb_last = extract_tb(_sys.exc_info()[2])[-1] - _sys.exit('%s: assertion failed: %s line %d' % (NAME.lower(), tb_last[0], tb_last[1])) + _sys.exit("%s: assertion failed: %s line %d" % (NAME.lower(), tb_last[0], tb_last[1])) except Exception: - _sys.exit('%s: error: %s' % (NAME.lower(), format_exc())) + _sys.exit("%s: error: %s" % (NAME.lower(), format_exc())) diff --git a/lib/solaar/cli/config.py b/lib/solaar/cli/config.py index 3230a1b266..0c74fc916b 100644 --- a/lib/solaar/cli/config.py +++ b/lib/solaar/cli/config.py @@ -21,56 +21,58 @@ from logitech_receiver import settings as _settings from logitech_receiver import settings_templates as _settings_templates from logitech_receiver.common import NamedInts as _NamedInts + from solaar import configuration as _configuration def _print_setting(s, verbose=True): - print('#', s.label) + print("#", s.label) if verbose: if s.description: - print('#', s.description.replace('\n', ' ')) + print("#", s.description.replace("\n", " ")) if s.kind == _settings.KIND.toggle: - print('# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~') + print("# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~") elif s.kind == _settings.KIND.choice: print( - '# possible values: one of [', ', '.join(str(v) for v in s.choices), - '], or higher/lower/highest/max/lowest/min' + "# possible values: one of [", + ", ".join(str(v) for v in s.choices), + "], or higher/lower/highest/max/lowest/min", ) else: # wtf? pass value = s.read(cached=False) if value is None: - print(s.name, '= ? (failed to read from device)') + print(s.name, "= ? (failed to read from device)") else: - print(s.name, '=', s.val_to_string(value)) + print(s.name, "=", s.val_to_string(value)) def _print_setting_keyed(s, key, verbose=True): - print('#', s.label) + print("#", s.label) if verbose: if s.description: - print('#', s.description.replace('\n', ' ')) + print("#", s.description.replace("\n", " ")) if s.kind == _settings.KIND.multiple_toggle: k = next((k for k in s._labels if key == k), None) if k is None: - print(s.name, '=? (key not found)') + print(s.name, "=? (key not found)") else: - print('# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~') + print("# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~") value = s.read(cached=False) if value is None: - print(s.name, '= ? (failed to read from device)') + print(s.name, "= ? (failed to read from device)") else: print(s.name, s.val_to_string({k: value[str(int(k))]})) elif s.kind == _settings.KIND.map_choice: k = next((k for k in s.choices.keys() if key == k), None) if k is None: - print(s.name, '=? (key not found)') + print(s.name, "=? (key not found)") else: - print('# possible values: one of [', ', '.join(str(v) for v in s.choices[k]), ']') + print("# possible values: one of [", ", ".join(str(v) for v in s.choices[k]), "]") value = s.read(cached=False) if value is None: - print(s.name, '= ? (failed to read from device)') + print(s.name, "= ? (failed to read from device)") else: print(s.name, s.val_to_string({k: value[int(k)]})) @@ -94,35 +96,35 @@ def select_choice(value, choices, setting, key): value = val elif ivalue is not None and ivalue >= 1 and ivalue <= len(choices): value = choices[ivalue - 1] - elif lvalue in ('higher', 'lower'): + elif lvalue in ("higher", "lower"): old_value = setting.read() if key is None else setting.read_key(key) if old_value is None: - raise Exception('%s: could not read current value' % setting.name) - if lvalue == 'lower': + raise Exception("%s: could not read current value" % setting.name) + if lvalue == "lower": lower_values = choices[:old_value] value = lower_values[-1] if lower_values else choices[:][0] - elif lvalue == 'higher': - higher_values = choices[old_value + 1:] + elif lvalue == "higher": + higher_values = choices[old_value + 1 :] value = higher_values[0] if higher_values else choices[:][-1] - elif lvalue in ('highest', 'max', 'first'): + elif lvalue in ("highest", "max", "first"): value = choices[:][-1] - elif lvalue in ('lowest', 'min', 'last'): + elif lvalue in ("lowest", "min", "last"): value = choices[:][0] else: - raise Exception('%s: possible values are [%s]' % (setting.name, ', '.join(str(v) for v in choices))) + raise Exception("%s: possible values are [%s]" % (setting.name, ", ".join(str(v) for v in choices))) return value def select_toggle(value, setting, key=None): - if value.lower() in ('toggle', '~'): + if value.lower() in ("toggle", "~"): value = not (setting.read() if key is None else setting.read()[str(int(key))]) else: try: value = bool(int(value)) except Exception: - if value.lower() in ('true', 'yes', 'on', 't', 'y'): + if value.lower() in ("true", "yes", "on", "t", "y"): value = True - elif value.lower() in ('false', 'no', 'off', 'f', 'n'): + elif value.lower() in ("false", "no", "off", "f", "n"): value = False else: raise Exception("%s: don't know how to interpret '%s' as boolean" % (setting.name, value)) @@ -157,12 +159,12 @@ def run(receivers, args, find_receiver, find_device): if not args.setting: # print all settings, so first set them all up if not dev.settings: - raise Exception('no settings for %s' % dev.name) + raise Exception("no settings for %s" % dev.name) _configuration.attach_to(dev) # _settings.apply_all_settings(dev) - print(dev.name, '(%s) [%s:%s]' % (dev.codename, dev.wpid, dev.serial)) + print(dev.name, "(%s) [%s:%s]" % (dev.codename, dev.wpid, dev.serial)) for s in dev.settings: - print('') + print("") _print_setting(s) return @@ -186,10 +188,12 @@ def run(receivers, args, find_receiver, find_device): remote = False try: import gi - gi.require_version('Gtk', '3.0') + + gi.require_version("Gtk", "3.0") from gi.repository import Gio, Gtk + if Gtk.init_check()[0]: # can Gtk be initialized? - APP_ID = 'io.github.pwr_solaar.solaar' + APP_ID = "io.github.pwr_solaar.solaar" application = Gtk.Application.new(APP_ID, Gio.ApplicationFlags.HANDLES_COMMAND_LINE) application.register() remote = application.get_is_remote() @@ -205,7 +209,7 @@ def run(receivers, args, find_receiver, find_device): # if the Solaar UI is running tell it to also perform the set, otherwise save the change in the configuration file if remote: - argl = ['config', dev.serial or dev.unitId, setting.name] + argl = ["config", dev.serial or dev.unitId, setting.name] argl.extend([a for a in [args.value_key, args.extra_subkey, args.extra2] if a is not None]) application.run(_yaml.safe_dump(argl)) else: @@ -217,19 +221,19 @@ def set(dev, setting, args, save): if setting.kind == _settings.KIND.toggle: value = select_toggle(args.value_key, setting) args.value_key = value - message = 'Setting %s of %s to %s' % (setting.name, dev.name, value) + message = "Setting %s of %s to %s" % (setting.name, dev.name, value) result = setting.write(value, save=save) elif setting.kind == _settings.KIND.range: value = select_range(args.value_key, setting) args.value_key = value - message = 'Setting %s of %s to %s' % (setting.name, dev.name, value) + message = "Setting %s of %s to %s" % (setting.name, dev.name, value) result = setting.write(value, save=save) elif setting.kind == _settings.KIND.choice: value = select_choice(args.value_key, setting.choices, setting, None) args.value_key = int(value) - message = 'Setting %s of %s to %s' % (setting.name, dev.name, value) + message = "Setting %s of %s to %s" % (setting.name, dev.name, value) result = setting.write(value, save=save) elif setting.kind == _settings.KIND.map_choice: @@ -247,7 +251,7 @@ def set(dev, setting, args, save): args.value_key = str(int(k)) else: raise Exception("%s: key '%s' not in setting" % (setting.name, key)) - message = 'Setting %s of %s key %r to %r' % (setting.name, dev.name, k, value) + message = "Setting %s of %s key %r to %r" % (setting.name, dev.name, k, value) result = setting.write_key_value(int(k), value, save=save) elif setting.kind == _settings.KIND.multiple_toggle: @@ -255,7 +259,7 @@ def set(dev, setting, args, save): _print_setting_keyed(setting, args.value_key) return (None, None, None) key = args.value_key - all_keys = getattr(setting, 'choices_universe', None) + all_keys = getattr(setting, "choices_universe", None) ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, _NamedInts) else to_int(key) k = next((k for k in setting._labels if key == k), None) if k is None and ikey is not None: @@ -266,17 +270,17 @@ def set(dev, setting, args, save): args.value_key = str(int(k)) else: raise Exception("%s: key '%s' not in setting" % (setting.name, key)) - message = 'Setting %s key %r to %r' % (setting.name, k, value) + message = "Setting %s key %r to %r" % (setting.name, k, value) result = setting.write_key_value(str(int(k)), value, save=save) elif setting.kind == _settings.KIND.multiple_range: if args.extra_subkey is None: - raise Exception('%s: setting needs both key and value to set' % (setting.name)) + raise Exception("%s: setting needs both key and value to set" % (setting.name)) key = args.value_key - all_keys = getattr(setting, 'choices_universe', None) + all_keys = getattr(setting, "choices_universe", None) ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, _NamedInts) else to_int(key) if args.extra2 is None or to_int(args.extra2) is None: - raise Exception('%s: setting needs an integer value, not %s' % (setting.name, args.extra2)) + raise Exception("%s: setting needs an integer value, not %s" % (setting.name, args.extra2)) if not setting._value: # ensure that there are values to look through setting.read() k = next((k for k in setting._value if key == ikey or key.isdigit() and ikey == int(key)), None) @@ -288,11 +292,11 @@ def set(dev, setting, args, save): args.value_key = str(int(k)) else: raise Exception("%s: key '%s' not in setting" % (setting.name, key)) - message = 'Setting %s key %s parameter %s to %r' % (setting.name, k, args.extra_subkey, item[args.extra_subkey]) + message = "Setting %s key %s parameter %s to %r" % (setting.name, k, args.extra_subkey, item[args.extra_subkey]) result = setting.write_key_value(int(k), item, save=save) value = item else: - raise Exception('NotImplemented') + raise Exception("NotImplemented") return result, message, value diff --git a/lib/solaar/cli/pair.py b/lib/solaar/cli/pair.py index a12ae555f6..a73efca2a6 100644 --- a/lib/solaar/cli/pair.py +++ b/lib/solaar/cli/pair.py @@ -50,7 +50,6 @@ def run(receivers, args, find_receiver, _ignore): known_devices = [dev.number for dev in receiver] class _HandleWithNotificationHook(int): - def notifications_hook(self, n): nonlocal known_devices assert n @@ -68,9 +67,9 @@ def notifications_hook(self, n): timeout = 30 # seconds receiver.handle = _HandleWithNotificationHook(receiver.handle) - if receiver.receiver_kind == 'bolt': # Bolt receivers require authentication to pair a device + if receiver.receiver_kind == "bolt": # Bolt receivers require authentication to pair a device receiver.discover(timeout=timeout) - print('Bolt Pairing: long-press the pairing key or button on your device (timing out in', timeout, 'seconds).') + print("Bolt Pairing: long-press the pairing key or button on your device (timing out in", timeout, "seconds).") pairing_start = _timestamp() patience = 5 # the discovering notification may come slightly later, so be patient while receiver.status.discovering or _timestamp() - pairing_start < patience: @@ -84,11 +83,11 @@ def notifications_hook(self, n): name = receiver.status.device_name authentication = receiver.status.device_authentication kind = receiver.status.device_kind - print(f'Bolt Pairing: discovered {name}') + print(f"Bolt Pairing: discovered {name}") receiver.pair_device( address=address, authentication=authentication, - entropy=20 if kind == _hidpp10_constants.DEVICE_KIND.keyboard else 10 + entropy=20 if kind == _hidpp10_constants.DEVICE_KIND.keyboard else 10, ) pairing_start = _timestamp() patience = 5 # the discovering notification may come slightly later, so be patient @@ -100,12 +99,12 @@ def notifications_hook(self, n): if n: receiver.handle.notifications_hook(n) if authentication & 0x01: - print(f'Bolt Pairing: type passkey {receiver.status.device_passkey} and then press the enter key') + print(f"Bolt Pairing: type passkey {receiver.status.device_passkey} and then press the enter key") else: - passkey = f'{int(receiver.status.device_passkey):010b}' - passkey = ', '.join(['right' if bit == '1' else 'left' for bit in passkey]) - print(f'Bolt Pairing: press {passkey}') - print('and then press left and right buttons simultaneously') + passkey = f"{int(receiver.status.device_passkey):010b}" + passkey = ", ".join(["right" if bit == "1" else "left" for bit in passkey]) + print(f"Bolt Pairing: press {passkey}") + print("and then press left and right buttons simultaneously") while receiver.status.lock_open: n = _base.read(receiver.handle) n = _base.make_notification(*n) if n else None @@ -114,7 +113,7 @@ def notifications_hook(self, n): else: receiver.set_lock(False, timeout=timeout) - print('Pairing: turn your new device on (timing out in', timeout, 'seconds).') + print("Pairing: turn your new device on (timing out in", timeout, "seconds).") pairing_start = _timestamp() patience = 5 # the lock-open notification may come slightly later, wait for it a bit while receiver.status.lock_open or _timestamp() - pairing_start < patience: @@ -131,10 +130,10 @@ def notifications_hook(self, n): if receiver.status.new_device: dev = receiver.status.new_device - print('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial)) + print("Paired device %d: %s (%s) [%s:%s]" % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial)) else: error = receiver.status.get(_status.KEYS.ERROR) if error: - raise Exception('pairing failed: %s' % error) + raise Exception("pairing failed: %s" % error) else: - print('Paired device') # this is better than an error + print("Paired device") # this is better than an error diff --git a/lib/solaar/cli/probe.py b/lib/solaar/cli/probe.py index 977a87f554..076eae0788 100644 --- a/lib/solaar/cli/probe.py +++ b/lib/solaar/cli/probe.py @@ -19,6 +19,7 @@ from logitech_receiver import base as _base from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver.common import strhex as _strhex + from solaar.cli.show import _print_device, _print_receiver _R = _hidpp10_constants.REGISTERS @@ -43,49 +44,53 @@ def run(receivers, args, find_receiver, _ignore): _print_receiver(receiver) - print('') - print(' Register Dump') + print("") + print(" Register Dump") rgst = receiver.read_register(_R.notifications) - print(' Notifications %#04x: %s' % (_R.notifications % 0x100, '0x' + _strhex(rgst) if rgst else 'None')) + print(" Notifications %#04x: %s" % (_R.notifications % 0x100, "0x" + _strhex(rgst) if rgst else "None")) rgst = receiver.read_register(_R.receiver_connection) - print(' Connection State %#04x: %s' % (_R.receiver_connection % 0x100, '0x' + _strhex(rgst) if rgst else 'None')) + print(" Connection State %#04x: %s" % (_R.receiver_connection % 0x100, "0x" + _strhex(rgst) if rgst else "None")) rgst = receiver.read_register(_R.devices_activity) - print(' Device Activity %#04x: %s' % (_R.devices_activity % 0x100, '0x' + _strhex(rgst) if rgst else 'None')) + print(" Device Activity %#04x: %s" % (_R.devices_activity % 0x100, "0x" + _strhex(rgst) if rgst else "None")) for sub_reg in range(0, 16): rgst = receiver.read_register(_R.receiver_info, sub_reg) print( - ' Pairing Register %#04x %#04x: %s' % - (_R.receiver_info % 0x100, sub_reg, '0x' + _strhex(rgst) if rgst else 'None') + " Pairing Register %#04x %#04x: %s" + % (_R.receiver_info % 0x100, sub_reg, "0x" + _strhex(rgst) if rgst else "None") ) for device in range(0, 7): for sub_reg in [0x10, 0x20, 0x30, 0x50]: rgst = receiver.read_register(_R.receiver_info, sub_reg + device) print( - ' Pairing Register %#04x %#04x: %s' % - (_R.receiver_info % 0x100, sub_reg + device, '0x' + _strhex(rgst) if rgst else 'None') + " Pairing Register %#04x %#04x: %s" + % (_R.receiver_info % 0x100, sub_reg + device, "0x" + _strhex(rgst) if rgst else "None") ) rgst = receiver.read_register(_R.receiver_info, 0x40 + device) print( - ' Pairing Name %#04x %#02x: %s' % - (_R.receiver_info % 0x100, 0x40 + device, rgst[2:2 + ord(rgst[1:2])] if rgst else 'None') + " Pairing Name %#04x %#02x: %s" + % (_R.receiver_info % 0x100, 0x40 + device, rgst[2 : 2 + ord(rgst[1:2])] if rgst else "None") ) for part in range(1, 4): rgst = receiver.read_register(_R.receiver_info, 0x60 + device, part) print( - ' Pairing Name %#04x %#02x %#02x: %2d %s' % ( - _R.receiver_info % 0x100, 0x60 + device, part, ord(rgst[2:3]) if rgst else 0, - rgst[3:3 + ord(rgst[2:3])] if rgst else 'None' + " Pairing Name %#04x %#02x %#02x: %2d %s" + % ( + _R.receiver_info % 0x100, + 0x60 + device, + part, + ord(rgst[2:3]) if rgst else 0, + rgst[3 : 3 + ord(rgst[2:3])] if rgst else "None", ) ) for sub_reg in range(0, 5): rgst = receiver.read_register(_R.firmware, sub_reg) print( - ' Firmware %#04x %#04x: %s' % - (_R.firmware % 0x100, sub_reg, '0x' + _strhex(rgst) if rgst is not None else 'None') + " Firmware %#04x %#04x: %s" + % (_R.firmware % 0x100, sub_reg, "0x" + _strhex(rgst) if rgst is not None else "None") ) - print('') + print("") for reg in range(0, 0xFF): last = None for sub in range(0, 0xFF): @@ -97,8 +102,8 @@ def run(receivers, args, find_receiver, _ignore): else: if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst: print( - ' Register Short %#04x %#04x: %s' % - (reg, sub, '0x' + _strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) + " Register Short %#04x %#04x: %s" + % (reg, sub, "0x" + _strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) ) last = rgst last = None @@ -111,7 +116,7 @@ def run(receivers, args, find_receiver, _ignore): else: if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst: print( - ' Register Long %#04x %#04x: %s' % - (reg, sub, '0x' + _strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) + " Register Long %#04x %#04x: %s" + % (reg, sub, "0x" + _strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) ) last = rgst diff --git a/lib/solaar/cli/profiles.py b/lib/solaar/cli/profiles.py index 4456314a80..a4de3c2d1c 100644 --- a/lib/solaar/cli/profiles.py +++ b/lib/solaar/cli/profiles.py @@ -41,27 +41,27 @@ def run(receivers, args, find_receiver, find_device): raise Exception("no online device found matching '%s'" % device_name) if not (dev.online and dev.profiles): - print(f'Device {dev.name} is either offline or has no onboard profiles') + print(f"Device {dev.name} is either offline or has no onboard profiles") elif not profiles_file: - print(f'#Dumping profiles from {dev.name}') + print(f"#Dumping profiles from {dev.name}") print(_yaml.dump(dev.profiles)) else: try: - with open(profiles_file, 'r') as f: - print(f'Reading profiles from {profiles_file}') + with open(profiles_file, "r") as f: + print(f"Reading profiles from {profiles_file}") profiles = _yaml.safe_load(f) if not isinstance(profiles, _OnboardProfiles): - print('Profiles file does not contain current onboard profiles') - elif getattr(profiles, 'version', None) != _OnboardProfilesVersion: - version = getattr(profiles, 'version', None) - print(f'Missing or incorrect profile version {version} in loaded profile') - elif getattr(profiles, 'name', None) != dev.name: - name = getattr(profiles, 'name', None) - print(f'Different device name {name} in loaded profile') + print("Profiles file does not contain current onboard profiles") + elif getattr(profiles, "version", None) != _OnboardProfilesVersion: + version = getattr(profiles, "version", None) + print(f"Missing or incorrect profile version {version} in loaded profile") + elif getattr(profiles, "name", None) != dev.name: + name = getattr(profiles, "name", None) + print(f"Different device name {name} in loaded profile") else: - print(f'Loading profiles into {dev.name}') + print(f"Loading profiles into {dev.name}") written = profiles.write(dev) - print(f'Wrote {written} sectors to {dev.name}') + print(f"Wrote {written} sectors to {dev.name}") except Exception as exc: - print('Profiles not written:', exc) + print("Profiles not written:", exc) print(_traceback.format_exc()) diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py index c69e91d41b..0a558c61a2 100644 --- a/lib/solaar/cli/show.py +++ b/lib/solaar/cli/show.py @@ -25,6 +25,7 @@ from logitech_receiver import settings_templates as _settings_templates from logitech_receiver.common import NamedInt as _NamedInt from logitech_receiver.common import strhex as _strhex + from solaar import NAME, __version__ _F = _hidpp20_constants.FEATURE @@ -34,39 +35,39 @@ def _print_receiver(receiver): paired_count = receiver.count() print(receiver.name) - print(' Device path :', receiver.path) - print(' USB id : 046d:%s' % receiver.product_id) - print(' Serial :', receiver.serial) + print(" Device path :", receiver.path) + print(" USB id : 046d:%s" % receiver.product_id) + print(" Serial :", receiver.serial) if receiver.firmware: for f in receiver.firmware: - print(' %-11s: %s' % (f.kind, f.version)) + print(" %-11s: %s" % (f.kind, f.version)) - print(' Has', paired_count, 'paired device(s) out of a maximum of %d.' % receiver.max_devices) + print(" Has", paired_count, "paired device(s) out of a maximum of %d." % receiver.max_devices) if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0: - print(' Has %d successful pairing(s) remaining.' % receiver.remaining_pairings()) + print(" Has %d successful pairing(s) remaining." % receiver.remaining_pairings()) notification_flags = _hidpp10.get_notification_flags(receiver) if notification_flags is not None: if notification_flags: notification_names = _hidpp10_constants.NOTIFICATION_FLAG.flag_names(notification_flags) - print(' Notifications: %s (0x%06X)' % (', '.join(notification_names), notification_flags)) + print(" Notifications: %s (0x%06X)" % (", ".join(notification_names), notification_flags)) else: - print(' Notifications: (none)') + print(" Notifications: (none)") activity = receiver.read_register(_hidpp10_constants.REGISTERS.devices_activity) if activity: - activity = [(d, ord(activity[d - 1:d])) for d in range(1, receiver.max_devices)] - activity_text = ', '.join(('%d=%d' % (d, a)) for d, a in activity if a > 0) - print(' Device activity counters:', activity_text or '(empty)') + activity = [(d, ord(activity[d - 1 : d])) for d in range(1, receiver.max_devices)] + activity_text = ", ".join(("%d=%d" % (d, a)) for d, a in activity if a > 0) + print(" Device activity counters:", activity_text or "(empty)") def _battery_text(level): if level is None: - return 'N/A' + return "N/A" elif isinstance(level, _NamedInt): return str(level) else: - return '%d%%' % level + return "%d%%" % level def _battery_line(dev): @@ -75,11 +76,11 @@ def _battery_line(dev): level, nextLevel, status, voltage = battery text = _battery_text(level) if voltage is not None: - text = text + (' %smV ' % voltage) - nextText = '' if nextLevel is None else ', next level ' + _battery_text(nextLevel) - print(' Battery: %s, %s%s.' % (text, status, nextText)) + text = text + (" %smV " % voltage) + nextText = "" if nextLevel is None else ", next level " + _battery_text(nextLevel) + print(" Battery: %s, %s%s." % (text, status, nextText)) else: - print(' Battery status unavailable.') + print(" Battery status unavailable.") def _print_device(dev, num=None): @@ -88,56 +89,56 @@ def _print_device(dev, num=None): try: dev.ping() except exceptions.NoSuchDevice: - print(' %s: Device not found' % num or dev.number) + print(" %s: Device not found" % num or dev.number) return if num or dev.number < 8: - print(' %d: %s' % (num or dev.number, dev.name)) + print(" %d: %s" % (num or dev.number, dev.name)) else: - print('%s' % dev.name) - print(' Device path :', dev.path) + print("%s" % dev.name) + print(" Device path :", dev.path) if dev.wpid: - print(' WPID : %s' % dev.wpid) + print(" WPID : %s" % dev.wpid) if dev.product_id: - print(' USB id : 046d:%s' % dev.product_id) - print(' Codename :', dev.codename) - print(' Kind :', dev.kind) + print(" USB id : 046d:%s" % dev.product_id) + print(" Codename :", dev.codename) + print(" Kind :", dev.kind) if dev.protocol: - print(' Protocol : HID++ %1.1f' % dev.protocol) + print(" Protocol : HID++ %1.1f" % dev.protocol) else: - print(' Protocol : unknown (device is offline)') + print(" Protocol : unknown (device is offline)") if dev.polling_rate: - print(' Report Rate :', dev.polling_rate) - print(' Serial number:', dev.serial) + print(" Report Rate :", dev.polling_rate) + print(" Serial number:", dev.serial) if dev.modelId: - print(' Model ID: ', dev.modelId) + print(" Model ID: ", dev.modelId) if dev.unitId: - print(' Unit ID: ', dev.unitId) + print(" Unit ID: ", dev.unitId) if dev.firmware: for fw in dev.firmware: - print(' %11s:' % fw.kind, (fw.name + ' ' + fw.version).strip()) + print(" %11s:" % fw.kind, (fw.name + " " + fw.version).strip()) if dev.power_switch_location: - print(' The power switch is located on the %s.' % dev.power_switch_location) + print(" The power switch is located on the %s." % dev.power_switch_location) if dev.online: notification_flags = _hidpp10.get_notification_flags(dev) if notification_flags is not None: if notification_flags: notification_names = _hidpp10_constants.NOTIFICATION_FLAG.flag_names(notification_flags) - print(' Notifications: %s (0x%06X).' % (', '.join(notification_names), notification_flags)) + print(" Notifications: %s (0x%06X)." % (", ".join(notification_names), notification_flags)) else: - print(' Notifications: (none).') + print(" Notifications: (none).") device_features = _hidpp10.get_device_features(dev) if device_features is not None: if device_features: device_features_names = _hidpp10_constants.DEVICE_FEATURES.flag_names(device_features) - print(' Features: %s (0x%06X)' % (', '.join(device_features_names), device_features)) + print(" Features: %s (0x%06X)" % (", ".join(device_features_names), device_features)) else: - print(' Features: (none)') + print(" Features: (none)") if dev.online and dev.features: - print(' Supports %d HID++ 2.0 features:' % len(dev.features)) + print(" Supports %d HID++ 2.0 features:" % len(dev.features)) dev_settings = [] _settings_templates.check_feature_settings(dev, dev_settings) for feature, index in dev.features.enumerate(): @@ -146,172 +147,177 @@ def _print_device(dev, num=None): flags = _hidpp20_constants.FEATURE_FLAG.flag_names(flags) version = dev.features.get_feature_version(int(feature)) version = version if version else 0 - print(' %2d: %-22s {%04X} V%s %s ' % (index, feature, feature, version, ', '.join(flags))) + print(" %2d: %-22s {%04X} V%s %s " % (index, feature, feature, version, ", ".join(flags))) if feature == _hidpp20_constants.FEATURE.HIRES_WHEEL: wheel = _hidpp20.get_hires_wheel(dev) if wheel: multi, has_invert, has_switch, inv, res, target, ratchet = wheel - print(' Multiplier: %s' % multi) + print(" Multiplier: %s" % multi) if has_invert: - print(' Has invert:', 'Inverse wheel motion' if inv else 'Normal wheel motion') + print(" Has invert:", "Inverse wheel motion" if inv else "Normal wheel motion") if has_switch: - print(' Has ratchet switch:', 'Normal wheel mode' if ratchet else 'Free wheel mode') + print(" Has ratchet switch:", "Normal wheel mode" if ratchet else "Free wheel mode") if res: - print(' High resolution mode') + print(" High resolution mode") else: - print(' Low resolution mode') + print(" Low resolution mode") if target: - print(' HID++ notification') + print(" HID++ notification") else: - print(' HID notification') + print(" HID notification") elif feature == _hidpp20_constants.FEATURE.MOUSE_POINTER: mouse_pointer = _hidpp20.get_mouse_pointer_info(dev) if mouse_pointer: - print(' DPI: %s' % mouse_pointer['dpi']) - print(' Acceleration: %s' % mouse_pointer['acceleration']) - if mouse_pointer['suggest_os_ballistics']: - print(' Use OS ballistics') + print(" DPI: %s" % mouse_pointer["dpi"]) + print(" Acceleration: %s" % mouse_pointer["acceleration"]) + if mouse_pointer["suggest_os_ballistics"]: + print(" Use OS ballistics") else: - print(' Override OS ballistics') - if mouse_pointer['suggest_vertical_orientation']: - print(' Provide vertical tuning, trackball') + print(" Override OS ballistics") + if mouse_pointer["suggest_vertical_orientation"]: + print(" Provide vertical tuning, trackball") else: - print(' No vertical tuning, standard mice') + print(" No vertical tuning, standard mice") elif feature == _hidpp20_constants.FEATURE.VERTICAL_SCROLLING: vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev) if vertical_scrolling_info: - print(' Roller type: %s' % vertical_scrolling_info['roller']) - print(' Ratchet per turn: %s' % vertical_scrolling_info['ratchet']) - print(' Scroll lines: %s' % vertical_scrolling_info['lines']) + print(" Roller type: %s" % vertical_scrolling_info["roller"]) + print(" Ratchet per turn: %s" % vertical_scrolling_info["ratchet"]) + print(" Scroll lines: %s" % vertical_scrolling_info["lines"]) elif feature == _hidpp20_constants.FEATURE.HI_RES_SCROLLING: scrolling_mode, scrolling_resolution = _hidpp20.get_hi_res_scrolling_info(dev) if scrolling_mode: - print(' Hi-res scrolling enabled') + print(" Hi-res scrolling enabled") else: - print(' Hi-res scrolling disabled') + print(" Hi-res scrolling disabled") if scrolling_resolution: - print(' Hi-res scrolling multiplier: %s' % scrolling_resolution) + print(" Hi-res scrolling multiplier: %s" % scrolling_resolution) elif feature == _hidpp20_constants.FEATURE.POINTER_SPEED: pointer_speed = _hidpp20.get_pointer_speed_info(dev) if pointer_speed: - print(' Pointer Speed: %s' % pointer_speed) + print(" Pointer Speed: %s" % pointer_speed) elif feature == _hidpp20_constants.FEATURE.LOWRES_WHEEL: wheel_status = _hidpp20.get_lowres_wheel_status(dev) if wheel_status: - print(' Wheel Reports: %s' % wheel_status) + print(" Wheel Reports: %s" % wheel_status) elif feature == _hidpp20_constants.FEATURE.NEW_FN_INVERSION: inversion = _hidpp20.get_new_fn_inversion(dev) if inversion: inverted, default_inverted = inversion - print(' Fn-swap:', 'enabled' if inverted else 'disabled') - print(' Fn-swap default:', 'enabled' if default_inverted else 'disabled') + print(" Fn-swap:", "enabled" if inverted else "disabled") + print(" Fn-swap default:", "enabled" if default_inverted else "disabled") elif feature == _hidpp20_constants.FEATURE.HOSTS_INFO: host_names = _hidpp20.get_host_names(dev) for host, (paired, name) in host_names.items(): - print(' Host %s (%s): %s' % (host, 'paired' if paired else 'unpaired', name)) + print(" Host %s (%s): %s" % (host, "paired" if paired else "unpaired", name)) elif feature == _hidpp20_constants.FEATURE.DEVICE_NAME: - print(' Name: %s' % _hidpp20.get_name(dev)) - print(' Kind: %s' % _hidpp20.get_kind(dev)) + print(" Name: %s" % _hidpp20.get_name(dev)) + print(" Kind: %s" % _hidpp20.get_kind(dev)) elif feature == _hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME: - print(' Friendly Name: %s' % _hidpp20.get_friendly_name(dev)) + print(" Friendly Name: %s" % _hidpp20.get_friendly_name(dev)) elif feature == _hidpp20_constants.FEATURE.DEVICE_FW_VERSION: for fw in _hidpp20.get_firmware(dev): - extras = _strhex(fw.extras) if fw.extras else '' - print(' Firmware: %s %s %s %s' % (fw.kind, fw.name, fw.version, extras)) + extras = _strhex(fw.extras) if fw.extras else "" + print(" Firmware: %s %s %s %s" % (fw.kind, fw.name, fw.version, extras)) ids = _hidpp20.get_ids(dev) if ids: unitId, modelId, tid_map = ids - print(' Unit ID: %s Model ID: %s Transport IDs: %s' % (unitId, modelId, tid_map)) - elif feature == _hidpp20_constants.FEATURE.REPORT_RATE or \ - feature == _hidpp20_constants.FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE: - print(' Report Rate: %s' % _hidpp20.get_polling_rate(dev)) + print(" Unit ID: %s Model ID: %s Transport IDs: %s" % (unitId, modelId, tid_map)) + elif ( + feature == _hidpp20_constants.FEATURE.REPORT_RATE + or feature == _hidpp20_constants.FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE + ): + print(" Report Rate: %s" % _hidpp20.get_polling_rate(dev)) elif feature == _hidpp20_constants.FEATURE.REMAINING_PAIRING: - print(' Remaining Pairings: %d' % _hidpp20.get_remaining_pairing(dev)) + print(" Remaining Pairings: %d" % _hidpp20.get_remaining_pairing(dev)) elif feature == _hidpp20_constants.FEATURE.ONBOARD_PROFILES: if _hidpp20.get_onboard_mode(dev) == _hidpp20_constants.ONBOARD_MODES.MODE_HOST: - mode = 'Host' + mode = "Host" else: - mode = 'On-Board' - print(' Device Mode: %s' % mode) + mode = "On-Board" + print(" Device Mode: %s" % mode) elif _hidpp20.battery_functions.get(feature, None): - print('', end=' ') + print("", end=" ") _battery_line(dev) for setting in dev_settings: if setting.feature == feature: - if setting._device and getattr(setting._device, 'persister', None) and \ - setting._device.persister.get(setting.name) is not None: + if ( + setting._device + and getattr(setting._device, "persister", None) + and setting._device.persister.get(setting.name) is not None + ): v = setting.val_to_string(setting._device.persister.get(setting.name)) - print(' %s (saved): %s' % (setting.label, v)) + print(" %s (saved): %s" % (setting.label, v)) try: v = setting.val_to_string(setting.read(False)) except _hidpp20.FeatureCallError as e: - v = 'HID++ error ' + str(e) + v = "HID++ error " + str(e) except AssertionError as e: - v = 'AssertionError ' + str(e) - print(' %s : %s' % (setting.label, v)) + v = "AssertionError " + str(e) + print(" %s : %s" % (setting.label, v)) if dev.online and dev.keys: - print(' Has %d reprogrammable keys:' % len(dev.keys)) + print(" Has %d reprogrammable keys:" % len(dev.keys)) for k in dev.keys: # TODO: add here additional variants for other REPROG_CONTROLS if dev.keys.keyversion == _hidpp20_constants.FEATURE.REPROG_CONTROLS_V2: - print(' %2d: %-26s => %-27s %s' % (k.index, k.key, k.default_task, ', '.join(k.flags))) + print(" %2d: %-26s => %-27s %s" % (k.index, k.key, k.default_task, ", ".join(k.flags))) if dev.keys.keyversion == _hidpp20_constants.FEATURE.REPROG_CONTROLS_V4: - print(' %2d: %-26s, default: %-27s => %-26s' % (k.index, k.key, k.default_task, k.mapped_to)) - gmask_fmt = ','.join(k.group_mask) - gmask_fmt = gmask_fmt if gmask_fmt else 'empty' - print(' %s, pos:%d, group:%1d, group mask:%s' % (', '.join(k.flags), k.pos, k.group, gmask_fmt)) - report_fmt = ', '.join(k.mapping_flags) - report_fmt = report_fmt if report_fmt else 'default' - print(' reporting: %s' % (report_fmt)) + print(" %2d: %-26s, default: %-27s => %-26s" % (k.index, k.key, k.default_task, k.mapped_to)) + gmask_fmt = ",".join(k.group_mask) + gmask_fmt = gmask_fmt if gmask_fmt else "empty" + print(" %s, pos:%d, group:%1d, group mask:%s" % (", ".join(k.flags), k.pos, k.group, gmask_fmt)) + report_fmt = ", ".join(k.mapping_flags) + report_fmt = report_fmt if report_fmt else "default" + print(" reporting: %s" % (report_fmt)) if dev.online and dev.remap_keys: - print(' Has %d persistent remappable keys:' % len(dev.remap_keys)) + print(" Has %d persistent remappable keys:" % len(dev.remap_keys)) for k in dev.remap_keys: - print(' %2d: %-26s => %s%s' % (k.index, k.key, k.action, ' (remapped)' if k.cidStatus else '')) + print(" %2d: %-26s => %s%s" % (k.index, k.key, k.action, " (remapped)" if k.cidStatus else "")) if dev.online and dev.gestures: print( - ' Has %d gesture(s), %d param(s) and %d spec(s):' % - (len(dev.gestures.gestures), len(dev.gestures.params), len(dev.gestures.specs)) + " Has %d gesture(s), %d param(s) and %d spec(s):" + % (len(dev.gestures.gestures), len(dev.gestures.params), len(dev.gestures.specs)) ) for k in dev.gestures.gestures.values(): print( - ' %-26s Enabled(%4s): %-5s Diverted:(%4s) %s' % - (k.gesture, k.index, k.enabled(), k.diversion_index, k.diverted()) + " %-26s Enabled(%4s): %-5s Diverted:(%4s) %s" + % (k.gesture, k.index, k.enabled(), k.diversion_index, k.diverted()) ) for k in dev.gestures.params.values(): - print(' %-26s Value (%4s): %s [Default: %s]' % (k.param, k.index, k.value, k.default_value)) + print(" %-26s Value (%4s): %s [Default: %s]" % (k.param, k.index, k.value, k.default_value)) for k in dev.gestures.specs.values(): - print(' %-26s Spec (%4s): %s' % (k.spec, k.id, k.value)) + print(" %-26s Spec (%4s): %s" % (k.spec, k.id, k.value)) if dev.online: _battery_line(dev) else: - print(' Battery: unknown (device is offline).') + print(" Battery: unknown (device is offline).") def run(devices, args, find_receiver, find_device): assert devices assert args.device - print('%s version %s' % (NAME, __version__)) - print('') + print("%s version %s" % (NAME, __version__)) + print("") device_name = args.device.lower() - if device_name == 'all': + if device_name == "all": for d in devices: if isinstance(d, _receiver.Receiver): _print_receiver(d) count = d.count() if count: for dev in d: - print('') + print("") _print_device(dev, dev.number) count -= 1 if not count: break - print('') + print("") else: - print('') + print("") _print_device(d) return diff --git a/lib/solaar/cli/unpair.py b/lib/solaar/cli/unpair.py index f4ca2b61d2..62f0f06a5f 100644 --- a/lib/solaar/cli/unpair.py +++ b/lib/solaar/cli/unpair.py @@ -28,13 +28,13 @@ def run(receivers, args, find_receiver, find_device): if not dev.receiver.may_unpair: print( - 'Receiver with USB id %s for %s [%s:%s] does not unpair, but attempting anyway.' % - (dev.receiver.product_id, dev.name, dev.wpid, dev.serial) + "Receiver with USB id %s for %s [%s:%s] does not unpair, but attempting anyway." + % (dev.receiver.product_id, dev.name, dev.wpid, dev.serial) ) try: # query these now, it's last chance to get them number, codename, wpid, serial = dev.number, dev.codename, dev.wpid, dev.serial dev.receiver._unpair_device(number, True) # force an unpair - print('Unpaired %d: %s (%s) [%s:%s]' % (number, dev.name, codename, wpid, serial)) + print("Unpaired %d: %s (%s) [%s:%s]" % (number, dev.name, codename, wpid, serial)) except Exception as e: raise e diff --git a/lib/solaar/configuration.py b/lib/solaar/configuration.py index 7066a0b984..92bcfa58db 100644 --- a/lib/solaar/configuration.py +++ b/lib/solaar/configuration.py @@ -28,22 +28,23 @@ from gi.repository import GLib from logitech_receiver.common import NamedInt as _NamedInt + from solaar import __version__ logger = logging.getLogger(__name__) -_XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser(_path.join('~', '.config')) -_yaml_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.yaml') -_json_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json') - -_KEY_VERSION = '_version' -_KEY_NAME = '_NAME' -_KEY_WPID = '_wpid' -_KEY_SERIAL = '_serial' -_KEY_MODEL_ID = '_modelId' -_KEY_UNIT_ID = '_unitId' -_KEY_ABSENT = '_absent' -_KEY_SENSITIVE = '_sensitive' +_XDG_CONFIG_HOME = _os.environ.get("XDG_CONFIG_HOME") or _path.expanduser(_path.join("~", ".config")) +_yaml_file_path = _path.join(_XDG_CONFIG_HOME, "solaar", "config.yaml") +_json_file_path = _path.join(_XDG_CONFIG_HOME, "solaar", "config.json") + +_KEY_VERSION = "_version" +_KEY_NAME = "_NAME" +_KEY_WPID = "_wpid" +_KEY_SERIAL = "_serial" +_KEY_MODEL_ID = "_modelId" +_KEY_UNIT_ID = "_unitId" +_KEY_ABSENT = "_absent" +_KEY_SENSITIVE = "_sensitive" _config = [] @@ -55,19 +56,19 @@ def _load(): with open(_yaml_file_path) as config_file: loaded_config = _yaml.safe_load(config_file) except Exception as e: - logger.error('failed to load from %s: %s', _yaml_file_path, e) + logger.error("failed to load from %s: %s", _yaml_file_path, e) elif _path.isfile(_json_file_path): path = _json_file_path try: with open(_json_file_path) as config_file: loaded_config = _json.load(config_file) except Exception as e: - logger.error('failed to load from %s: %s', _json_file_path, e) + logger.error("failed to load from %s: %s", _json_file_path, e) loaded_config = _convert_json(loaded_config) else: path = None if logger.isEnabledFor(logging.DEBUG): - logger.debug('load => %s', loaded_config) + logger.debug("load => %s", loaded_config) global _config _config = _parse_config(loaded_config, path) @@ -84,41 +85,43 @@ def _parse_config(loaded_config, config_path): if discard_derived_properties: if logger.isEnabledFor(logging.INFO): logger.info( - 'config file \'%s\' was generated by another version of solaar ' - '(config: %s, current: %s). refreshing detected device capabilities', config_path, loaded_version, - current_version + "config file '%s' was generated by another version of solaar " + "(config: %s, current: %s). refreshing detected device capabilities", + config_path, + loaded_version, + current_version, ) for device in loaded_config[1:]: assert isinstance(device, dict) parsed_config.append(_device_entry_from_config_dict(device, discard_derived_properties)) except Exception as e: - logger.warning('Exception processing config file \'%s\', ignoring contents: %s', config_path, e) + logger.warning("Exception processing config file '%s', ignoring contents: %s", config_path, e) return parsed_config def _device_entry_from_config_dict(data, discard_derived_properties): - divert = data.get('divert-keys') + divert = data.get("divert-keys") if divert: - sliding = data.get('dpi-sliding') + sliding = data.get("dpi-sliding") if sliding: # convert old-style dpi-sliding setting to divert-keys entry divert[int(sliding)] = 3 - data.pop('dpi-sliding', None) - gestures = data.get('mouse-gestures') + data.pop("dpi-sliding", None) + gestures = data.get("mouse-gestures") if gestures: # convert old-style mouse-gestures setting to divert-keys entry divert[int(gestures)] = 2 - data.pop('mouse-gestures', None) + data.pop("mouse-gestures", None) # remove any string entries (from bad conversions) - data['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)} - if data.get('_sensitive', None) is None: # make scroll wheel settings default to ignore - data['_sensitive'] = { - 'hires-smooth-resolution': 'ignore', - 'hires-smooth-invert': 'ignore', - 'hires-scroll-mode': 'ignore' + data["divert-keys"] = {k: v for k, v in divert.items() if isinstance(k, int)} + if data.get("_sensitive", None) is None: # make scroll wheel settings default to ignore + data["_sensitive"] = { + "hires-smooth-resolution": "ignore", + "hires-smooth-invert": "ignore", + "hires-scroll-mode": "ignore", } if discard_derived_properties: - data.pop('_absent', None) - data.pop('_battery', None) + data.pop("_absent", None) + data.pop("_battery", None) return _DeviceEntry(**data) @@ -136,7 +139,7 @@ def save(defer=False): try: _os.makedirs(dirname) except Exception: - logger.error('failed to create %s', dirname) + logger.error("failed to create %s", dirname) return if not defer or not defer_saves: do_save() @@ -154,38 +157,37 @@ def do_save(): save_timer.cancel() save_timer = None try: - with open(_yaml_file_path, 'w') as config_file: + with open(_yaml_file_path, "w") as config_file: _yaml.dump(_config, config_file, default_flow_style=None, width=150) if logger.isEnabledFor(logging.INFO): - logger.info('saved %s to %s', _config, _yaml_file_path) + logger.info("saved %s to %s", _config, _yaml_file_path) except Exception as e: - logger.error('failed to save to %s: %s', _yaml_file_path, e) + logger.error("failed to save to %s: %s", _yaml_file_path, e) def _convert_json(json_dict): config = [json_dict.get(_KEY_VERSION)] for key, dev in json_dict.items(): - key = key.split(':') + key = key.split(":") if len(key) == 2: dev[_KEY_WPID] = dev.get(_KEY_WPID) if dev.get(_KEY_WPID) else key[0] dev[_KEY_SERIAL] = dev.get(_KEY_SERIAL) if dev.get(_KEY_SERIAL) else key[1] for k, v in dev.items(): - if type(k) == str and not k.startswith('_') and type(v) == dict: # convert string keys to ints + if type(k) == str and not k.startswith("_") and type(v) == dict: # convert string keys to ints v = {int(dk) if type(dk) == str else dk: dv for dk, dv in v.items()} dev[k] = v - for k in ['mouse-gestures', 'dpi-sliding']: + for k in ["mouse-gestures", "dpi-sliding"]: v = dev.get(k, None) if v is True or v is False: dev.pop(k) - if '_name' in dev: - dev[_KEY_NAME] = dev['_name'] - dev.pop('_name') + if "_name" in dev: + dev[_KEY_NAME] = dev["_name"] + dev.pop("_name") config.append(dev) return config class _DeviceEntry(dict): - def __init__(self, **kwargs): super().__init__(**kwargs) @@ -198,7 +200,7 @@ def update(self, device, modelId): super().__setitem__(_KEY_NAME, device.name) if device.wpid and device.wpid != self.get(_KEY_WPID): super().__setitem__(_KEY_WPID, device.wpid) - if device.serial and device.serial != '?' and device.serial != self.get(_KEY_SERIAL): + if device.serial and device.serial != "?" and device.serial != self.get(_KEY_SERIAL): super().__setitem__(_KEY_SERIAL, device.serial) if modelId and modelId != self.get(_KEY_MODEL_ID): super().__setitem__(_KEY_MODEL_ID, modelId) @@ -216,14 +218,14 @@ def set_sensitivity(self, name, value): def device_representer(dumper, data): - return dumper.represent_mapping('tag:yaml.org,2002:map', data) + return dumper.represent_mapping("tag:yaml.org,2002:map", data) _yaml.add_representer(_DeviceEntry, device_representer) def named_int_representer(dumper, data): - return dumper.represent_scalar('tag:yaml.org,2002:int', str(int(data))) + return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data))) _yaml.add_representer(_NamedInt, named_int_representer) @@ -236,17 +238,19 @@ def named_int_representer(dumper, data): # that is directly connected. Here there is no way to realize that the two devices are the same. # So new entries are not created for unseen off-line receiver-connected devices except for those with protocol 1.0 def persister(device): - def match(wpid, serial, modelId, unitId, c): - return ((wpid and wpid == c.get(_KEY_WPID) and serial and serial == c.get(_KEY_SERIAL)) or ( - modelId and modelId != '000000000000' and modelId == c.get(_KEY_MODEL_ID) and unitId + return (wpid and wpid == c.get(_KEY_WPID) and serial and serial == c.get(_KEY_SERIAL)) or ( + modelId + and modelId != "000000000000" + and modelId == c.get(_KEY_MODEL_ID) + and unitId and unitId == c.get(_KEY_UNIT_ID) - )) + ) if not _config: _load() entry = None - modelId = device.modelId if device.modelId != '000000000000' else device.name if device.modelId else None + modelId = device.modelId if device.modelId != "000000000000" else device.name if device.modelId else None for c in _config: if isinstance(c, _DeviceEntry) and match(device.wpid, device.serial, modelId, device.unitId, c): entry = c @@ -254,10 +258,10 @@ def match(wpid, serial, modelId, unitId, c): if not entry: if not device.online and not device.serial: # don't create entry for offline devices without serial number if logger.isEnabledFor(logging.INFO): - logger.info('not setting up persister for offline device %s with missing serial number', device.name) + logger.info("not setting up persister for offline device %s with missing serial number", device.name) return if logger.isEnabledFor(logging.INFO): - logger.info('setting up persister for device %s', device.name) + logger.info("setting up persister for device %s", device.name) entry = _DeviceEntry() _config.append(entry) entry.update(device, modelId) diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py index 21b4732dcb..3f31bf889a 100755 --- a/lib/solaar/gtk.py +++ b/lib/solaar/gtk.py @@ -53,46 +53,46 @@ def _require(module, os_package, gi=None, gi_package=None, gi_version=None): gi.require_version(gi_package, gi_version) return importlib.import_module(module) except (ImportError, ValueError): - sys.exit('%s: missing required system package %s' % (NAME, os_package)) + sys.exit("%s: missing required system package %s" % (NAME, os_package)) -battery_icons_style = 'regular' -temp = tempfile.NamedTemporaryFile(prefix='Solaar_', mode='w', delete=True) +battery_icons_style = "regular" +temp = tempfile.NamedTemporaryFile(prefix="Solaar_", mode="w", delete=True) def _parse_arguments(): arg_parser = argparse.ArgumentParser( - prog=NAME.lower(), epilog='For more information see https://pwr-solaar.github.io/Solaar' + prog=NAME.lower(), epilog="For more information see https://pwr-solaar.github.io/Solaar" ) arg_parser.add_argument( - '-d', - '--debug', - action='count', + "-d", + "--debug", + action="count", default=0, - help='print logging messages, for debugging purposes (may be repeated for extra verbosity)' + help="print logging messages, for debugging purposes (may be repeated for extra verbosity)", ) arg_parser.add_argument( - '-D', - '--hidraw', - action='store', - dest='hidraw_path', - metavar='PATH', - help='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2' + "-D", + "--hidraw", + action="store", + dest="hidraw_path", + metavar="PATH", + help="unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2", ) - arg_parser.add_argument('--restart-on-wake-up', action='store_true', help='restart Solaar on sleep wake-up (experimental)') + arg_parser.add_argument("--restart-on-wake-up", action="store_true", help="restart Solaar on sleep wake-up (experimental)") arg_parser.add_argument( - '-w', '--window', choices=('show', 'hide', 'only'), help='start with window showing / hidden / only (no tray icon)' + "-w", "--window", choices=("show", "hide", "only"), help="start with window showing / hidden / only (no tray icon)" ) arg_parser.add_argument( - '-b', - '--battery-icons', - choices=('regular', 'symbolic', 'solaar'), - help='prefer regular battery / symbolic battery / solaar icons' + "-b", + "--battery-icons", + choices=("regular", "symbolic", "solaar"), + help="prefer regular battery / symbolic battery / solaar icons", ) - arg_parser.add_argument('--tray-icon-size', type=int, help='explicit size for tray icons') - arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) - arg_parser.add_argument('--help-actions', action='store_true', help='print help for the optional actions') - arg_parser.add_argument('action', nargs=argparse.REMAINDER, choices=_cli.actions, help='optional actions to perform') + arg_parser.add_argument("--tray-icon-size", type=int, help="explicit size for tray icons") + arg_parser.add_argument("-V", "--version", action="version", version="%(prog)s " + __version__) + arg_parser.add_argument("--help-actions", action="store_true", help="print help for the optional actions") + arg_parser.add_argument("action", nargs=argparse.REMAINDER, choices=_cli.actions, help="optional actions to perform") args = arg_parser.parse_args() @@ -101,29 +101,29 @@ def _parse_arguments(): return if args.window is None: - args.window = 'show' # default behaviour is to show main window + args.window = "show" # default behaviour is to show main window global battery_icons_style - battery_icons_style = args.battery_icons if args.battery_icons is not None else 'regular' + battery_icons_style = args.battery_icons if args.battery_icons is not None else "regular" global tray_icon_size tray_icon_size = args.tray_icon_size - log_format = '%(asctime)s,%(msecs)03d %(levelname)8s [%(threadName)s] %(name)s: %(message)s' + log_format = "%(asctime)s,%(msecs)03d %(levelname)8s [%(threadName)s] %(name)s: %(message)s" log_level = logging.ERROR - 10 * args.debug - logging.getLogger('').setLevel(min(log_level, logging.WARNING)) + logging.getLogger("").setLevel(min(log_level, logging.WARNING)) file_handler = logging.StreamHandler(temp) file_handler.setLevel(max(min(log_level, logging.WARNING), logging.INFO)) file_handler.setFormatter(logging.Formatter(log_format)) - logging.getLogger('').addHandler(file_handler) + logging.getLogger("").addHandler(file_handler) if args.debug > 0: stream_handler = logging.StreamHandler() stream_handler.setFormatter(logging.Formatter(log_format)) stream_handler.setLevel(log_level) - logging.getLogger('').addHandler(stream_handler) + logging.getLogger("").addHandler(stream_handler) if not args.action: if logger.isEnabledFor(logging.INFO): - logger.info('version %s, language %s (%s)', __version__, _i18n.language, _i18n.encoding) + logger.info("version %s, language %s (%s)", __version__, _i18n.language, _i18n.encoding) return args @@ -136,14 +136,14 @@ def _handlesig(signl, stack): if signl == int(signal.SIGINT): if logger.isEnabledFor(logging.INFO): faulthandler.dump_traceback() - sys.exit('%s: exit due to keyboard interrupt' % (NAME.lower())) + sys.exit("%s: exit due to keyboard interrupt" % (NAME.lower())) else: sys.exit(0) def main(): - if platform.system() not in ('Darwin', 'Windows'): - _require('pyudev', 'python3-pyudev') + if platform.system() not in ("Darwin", "Windows"): + _require("pyudev", "python3-pyudev") args = _parse_arguments() if not args: @@ -152,21 +152,23 @@ def main(): # if any argument, run comandline and exit return _cli.run(args.action, args.hidraw_path) - gi = _require('gi', 'python3-gi (in Ubuntu) or python3-gobject (in Fedora)') - _require('gi.repository.Gtk', 'gir1.2-gtk-3.0', gi, 'Gtk', '3.0') + gi = _require("gi", "python3-gi (in Ubuntu) or python3-gobject (in Fedora)") + _require("gi.repository.Gtk", "gir1.2-gtk-3.0", gi, "Gtk", "3.0") # handle ^C in console signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, _handlesig) signal.signal(signal.SIGTERM, _handlesig) - udev_file = '42-logitech-unify-permissions.rules' - if logger.isEnabledFor(logging.WARNING) \ - and not os.path.isfile('/etc/udev/rules.d/' + udev_file) \ - and not os.path.isfile('/usr/lib/udev/rules.d/' + udev_file) \ - and not os.path.isfile('/usr/local/lib/udev/rules.d/' + udev_file): - logger.warning('Solaar udev file not found in expected location') - logger.warning('See https://pwr-solaar.github.io/Solaar/installation for more information') + udev_file = "42-logitech-unify-permissions.rules" + if ( + logger.isEnabledFor(logging.WARNING) + and not os.path.isfile("/etc/udev/rules.d/" + udev_file) + and not os.path.isfile("/usr/lib/udev/rules.d/" + udev_file) + and not os.path.isfile("/usr/local/lib/udev/rules.d/" + udev_file) + ): + logger.warning("Solaar udev file not found in expected location") + logger.warning("See https://pwr-solaar.github.io/Solaar/installation for more information") try: _listener.setup_scanner(_ui.status_changed, _ui.setting_changed, _common.error_dialog) @@ -178,12 +180,12 @@ def main(): _configuration.defer_saves = True # allow configuration saves to be deferred # main UI event loop - _ui.run_loop(_listener.start_all, _listener.stop_all, args.window != 'only', args.window != 'hide') + _ui.run_loop(_listener.start_all, _listener.stop_all, args.window != "only", args.window != "hide") except Exception: - sys.exit('%s: error: %s' % (NAME.lower(), format_exc())) + sys.exit("%s: error: %s" % (NAME.lower(), format_exc())) temp.close() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/lib/solaar/i18n.py b/lib/solaar/i18n.py index 162ff8ed22..77a519b86d 100644 --- a/lib/solaar/i18n.py +++ b/lib/solaar/i18n.py @@ -31,19 +31,19 @@ def _find_locale_path(lc_domain): - prefix_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..')) - src_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..', 'share')) + prefix_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), "..")) + src_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), "..", "share")) for location in prefix_share, src_share: - mo_files = _glob(_path.join(location, 'locale', '*', 'LC_MESSAGES', lc_domain + '.mo')) + mo_files = _glob(_path.join(location, "locale", "*", "LC_MESSAGES", lc_domain + ".mo")) if mo_files: - return _path.join(location, 'locale') + return _path.join(location, "locale") # del _path try: - locale.setlocale(locale.LC_ALL, '') + locale.setlocale(locale.LC_ALL, "") except Exception: pass diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index eabf0f8d11..e7db83748d 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -36,7 +36,7 @@ from . import configuration -gi.require_version('Gtk', '3.0') # NOQA: E402 +gi.require_version("Gtk", "3.0") # NOQA: E402 from gi.repository import GLib # NOQA: E402 # isort:skip # from solaar.i18n import _ @@ -50,7 +50,7 @@ # # -_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ('receiver', 'number', 'name', 'kind', 'status', 'online')) +_GHOST_DEVICE = namedtuple("_GHOST_DEVICE", ("receiver", "number", "name", "kind", "status", "online")) _GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ del namedtuple @@ -72,8 +72,7 @@ def _ghost(device): class ReceiverListener(_listener.EventsListener): - """Keeps the status of a Receiver. - """ + """Keeps the status of a Receiver.""" def __init__(self, receiver, status_changed_callback): super().__init__(receiver, self._notifications_handler) @@ -87,13 +86,13 @@ def __init__(self, receiver, status_changed_callback): def has_started(self): if logger.isEnabledFor(logging.INFO): - logger.info('%s: notifications listener has started (%s)', self.receiver, self.receiver.handle) + logger.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle) nfs = self.receiver.enable_connection_notifications() if logger.isEnabledFor(logging.WARNING): if not self.receiver.isDevice and not ((nfs if nfs else 0) & _hidpp10_constants.NOTIFICATION_FLAG.wireless): logger.warning( - 'Receiver on %s might not support connection notifications, GUI might not show its devices', - self.receiver.path + "Receiver on %s might not support connection notifications, GUI might not show its devices", + self.receiver.path, ) self.receiver.status[_status.KEYS.NOTIFICATION_FLAGS] = nfs self.receiver.notify_devices() @@ -103,7 +102,7 @@ def has_stopped(self): r, self.receiver = self.receiver, None assert r is not None if logger.isEnabledFor(logging.INFO): - logger.info('%s: notifications listener has stopped', r) + logger.info("%s: notifications listener has stopped", r) # because udev is not notifying us about device removal, # make sure to clean up in _all_listeners @@ -114,7 +113,7 @@ def has_stopped(self): try: r.close() except Exception: - logger.exception('closing receiver %s' % r.path) + logger.exception("closing receiver %s" % r.path) self.status_changed_callback(r) # , _status.ALERT.NOTIFICATION) # def tick(self, timestamp): @@ -161,16 +160,25 @@ def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None): device.ping() if device.kind is None: logger.info( - 'status_changed %r: %s, %s (%X) %s', device, 'present' if bool(device) else 'removed', device.status, - alert, reason or '' + "status_changed %r: %s, %s (%X) %s", + device, + "present" if bool(device) else "removed", + device.status, + alert, + reason or "", ) else: logger.info( - 'status_changed %r: %s %s, %s (%X) %s', device, 'paired' if bool(device) else 'unpaired', - 'online' if device.online else 'offline', device.status, alert, reason or '' + "status_changed %r: %s %s, %s (%X) %s", + device, + "paired" if bool(device) else "unpaired", + "online" if device.online else "offline", + device.status, + alert, + reason or "", ) except Exception: - logger.info('status_changed for unknown device') + logger.info("status_changed for unknown device") if device.kind is None: assert device == self.receiver @@ -184,7 +192,7 @@ def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None): # We replace it with a ghost so that the UI has something to work # with while cleaning up. if logger.isEnabledFor(logging.INFO): - logger.info('device %s was unpaired, ghosting', device) + logger.info("device %s was unpaired, ghosting", device) device = _ghost(device) self.status_changed_callback(device, alert, reason) @@ -206,19 +214,19 @@ def _notifications_handler(self, n): # a notification that came in to the device listener - strange, but nothing needs to be done here if self.receiver.isDevice: if logger.isEnabledFor(logging.DEBUG): - logger.debug('Notification %s via device %s being ignored.', n, self.receiver) + logger.debug("Notification %s via device %s being ignored.", n, self.receiver) return # DJ pairing notification - ignore - hid++ 1.0 pairing notification is all that is needed if n.sub_id == 0x41 and n.report_id == _base.DJ_MESSAGE_ID: if logger.isEnabledFor(logging.INFO): - logger.info('ignoring DJ pairing notification %s', n) + logger.info("ignoring DJ pairing notification %s", n) return # a device notification if not (0 < n.devnumber <= 16): # some receivers have devices past their max # devices if logger.isEnabledFor(logging.WARNING): - logger.warning('Unexpected device number (%s) in notification %s.', n.devnumber, n) + logger.warning("Unexpected device number (%s) in notification %s.", n.devnumber, n) return already_known = n.devnumber in self.receiver @@ -237,7 +245,7 @@ def _notifications_handler(self, n): if n.sub_id == 0x41: if not already_known: - if n.address == 0x0A and not self.receiver.receiver_kind == 'bolt': + if n.address == 0x0A and not self.receiver.receiver_kind == "bolt": # some Nanos send a notification even if no new pairing - check that there really is a device there if self.receiver.read_register(_R.receiver_info, _IR.pairing_information + n.devnumber - 1) is None: return @@ -254,7 +262,7 @@ def _notifications_handler(self, n): dev = self.receiver[n.devnumber] if not dev: - logger.warning('%s: received %s for invalid device %d: %r', self.receiver, n, n.devnumber, dev) + logger.warning("%s: received %s for invalid device %d: %r", self.receiver, n, n.devnumber, dev) return # Apply settings every time the device connects @@ -262,9 +270,9 @@ def _notifications_handler(self, n): if logger.isEnabledFor(logging.INFO): try: dev.ping() - logger.info('connection %s for %r', n, dev) + logger.info("connection %s for %r", n, dev) except Exception: - logger.info('connection %s for unknown device, number %s', n, n.devnumber) + logger.info("connection %s for unknown device, number %s", n, n.devnumber) # If there are saved configs, bring the device's settings up-to-date. # They will be applied when the device is marked as online. configuration.attach_to(dev) @@ -272,23 +280,23 @@ def _notifications_handler(self, n): # the receiver changed status as well self._status_changed(self.receiver) - if not hasattr(dev, 'status') or dev.status is None: + if not hasattr(dev, "status") or dev.status is None: # notification before device status set up - don't process it - logger.warning('%s before device %s has status', n, dev) + logger.warning("%s before device %s has status", n, dev) else: _notifications.process(dev, n) if self.receiver.status.lock_open and not already_known: # this should be the first notification after a device was paired - assert n.sub_id == 0x41, 'first notification was not a connection notification' + assert n.sub_id == 0x41, "first notification was not a connection notification" if logger.isEnabledFor(logging.INFO): - logger.info('%s: pairing detected new device', self.receiver) + logger.info("%s: pairing detected new device", self.receiver) self.receiver.status.new_device = dev elif dev.online is None: dev.ping() def __str__(self): - return '' % (self.receiver.path, self.receiver.handle) + return "" % (self.receiver.path, self.receiver.handle) # @@ -315,7 +323,7 @@ def _start(device_info): _all_listeners[device_info.path] = rl return rl - logger.warning('failed to open %s', device_info) + logger.warning("failed to open %s", device_info) def start_all(): @@ -323,9 +331,9 @@ def start_all(): stop_all() if logger.isEnabledFor(logging.INFO): - logger.info('starting receiver listening threads') + logger.info("starting receiver listening threads") for device_info in _base.receivers_and_devices(): - _process_receiver_event('add', device_info) + _process_receiver_event("add", device_info) def stop_all(): @@ -334,7 +342,7 @@ def stop_all(): if listeners: if logger.isEnabledFor(logging.INFO): - logger.info('stopping receiver listening threads %s', listeners) + logger.info("stopping receiver listening threads %s", listeners) for l in listeners: l.stop() @@ -351,10 +359,10 @@ def stop_all(): # so mark its saved status to ensure that the status is pushed to the device when it comes back def ping_all(resuming=False): if logger.isEnabledFor(logging.INFO): - logger.info('ping all devices%s', ' when resuming' if resuming else '') + logger.info("ping all devices%s", " when resuming" if resuming else "") for l in _all_listeners.values(): if l.receiver.isDevice: - if resuming and hasattr(l.receiver, 'status'): + if resuming and hasattr(l.receiver, "status"): l.receiver.status._active = None # ensure that settings are pushed if l.receiver.ping(): l.receiver.status.changed(active=True, push=True) @@ -363,7 +371,7 @@ def ping_all(resuming=False): count = l.receiver.count() if count: for dev in l.receiver: - if resuming and hasattr(dev, 'status'): + if resuming and hasattr(dev, "status"): dev.status._active = None # ensure that settings are pushed if dev.ping(): dev.status.changed(active=True, push=True) @@ -380,7 +388,7 @@ def ping_all(resuming=False): def setup_scanner(status_changed_callback, setting_changed_callback, error_callback): global _status_callback, _error_callback, _setting_callback - assert _status_callback is None, 'scanner was already set-up' + assert _status_callback is None, "scanner was already set-up" _status_callback = status_changed_callback _setting_callback = setting_changed_callback @@ -395,19 +403,19 @@ def _process_add(device_info, retry): except OSError as e: if e.errno == _errno.EACCES: try: - output = subprocess.check_output(['/usr/bin/getfacl', '-p', device_info.path], text=True) + output = subprocess.check_output(["/usr/bin/getfacl", "-p", device_info.path], text=True) if logger.isEnabledFor(logging.WARNING): - logger.warning('Missing permissions on %s\n%s.', device_info.path, output) + logger.warning("Missing permissions on %s\n%s.", device_info.path, output) except Exception: pass if retry: GLib.timeout_add(2000.0, _process_add, device_info, retry - 1) else: - _error_callback('permissions', device_info.path) + _error_callback("permissions", device_info.path) else: - _error_callback('nodevice', device_info.path) + _error_callback("nodevice", device_info.path) except exceptions.NoReceiver: - _error_callback('nodevice', device_info.path) + _error_callback("nodevice", device_info.path) # receiver add/remove events will start/stop listener threads @@ -417,7 +425,7 @@ def _process_receiver_event(action, device_info): assert _error_callback if logger.isEnabledFor(logging.INFO): - logger.info('receiver event %s %s', action, device_info) + logger.info("receiver event %s %s", action, device_info) # whatever the action, stop any previous receivers at this path l = _all_listeners.pop(device_info.path, None) @@ -425,7 +433,7 @@ def _process_receiver_event(action, device_info): assert isinstance(l, ReceiverListener) l.stop() - if action == 'add': # a new device was detected + if action == "add": # a new device was detected _process_add(device_info, 3) return False diff --git a/lib/solaar/tasks.py b/lib/solaar/tasks.py index 333de1761c..7e9682d67a 100644 --- a/lib/solaar/tasks.py +++ b/lib/solaar/tasks.py @@ -35,7 +35,6 @@ class TaskRunner(_Thread): - def __init__(self, name): super().__init__(name=name) self.daemon = True @@ -54,7 +53,7 @@ def run(self): self.alive = True if logger.isEnabledFor(logging.DEBUG): - logger.debug('started') + logger.debug("started") while self.alive: task = self.queue.get() @@ -64,7 +63,7 @@ def run(self): try: function(*args, **kwargs) except Exception: - logger.exception('calling %s', function) + logger.exception("calling %s", function) if logger.isEnabledFor(logging.DEBUG): - logger.debug('stopped') + logger.debug("stopped") diff --git a/lib/solaar/ui/__init__.py b/lib/solaar/ui/__init__.py index 67cf3624c5..e383dfcf37 100644 --- a/lib/solaar/ui/__init__.py +++ b/lib/solaar/ui/__init__.py @@ -22,13 +22,14 @@ import yaml as _yaml from logitech_receiver.status import ALERT + from solaar.i18n import _ from solaar.ui.config_panel import change_setting, record_setting from solaar.ui.window import find_device from . import common, diversion_rules, notify, tray, window -gi.require_version('Gtk', '3.0') +gi.require_version("Gtk", "3.0") from gi.repository import Gio, GLib, Gtk # NOQA: E402 logger = logging.getLogger(__name__) @@ -37,7 +38,7 @@ # # -assert Gtk.get_major_version() > 2, 'Solaar requires Gtk 3 python bindings' +assert Gtk.get_major_version() > 2, "Solaar requires Gtk 3 python bindings" GLib.threads_init() @@ -48,7 +49,7 @@ def _startup(app, startup_hook, use_tray, show_window): if logger.isEnabledFor(logging.DEBUG): - logger.debug('startup registered=%s, remote=%s', app.get_is_registered(), app.get_is_remote()) + logger.debug("startup registered=%s, remote=%s", app.get_is_registered(), app.get_is_remote()) common.start_async() notify.init() if use_tray: @@ -59,7 +60,7 @@ def _startup(app, startup_hook, use_tray, show_window): def _activate(app): if logger.isEnabledFor(logging.DEBUG): - logger.debug('activate') + logger.debug("activate") if app.get_windows(): window.popup() else: @@ -68,12 +69,12 @@ def _activate(app): def _command_line(app, command_line): args = command_line.get_arguments() - args = _yaml.safe_load(''.join(args)) if args else args + args = _yaml.safe_load("".join(args)) if args else args if not args: _activate(app) - elif args[0] == 'config': # config call from remote instance + elif args[0] == "config": # config call from remote instance if logger.isEnabledFor(logging.INFO): - logger.info('remote command line %s', args) + logger.info("remote command line %s", args) dev = find_device(args[1]) if dev: setting = next((s for s in dev.settings if s.name == args[2]), None) @@ -84,7 +85,7 @@ def _command_line(app, command_line): def _shutdown(app, shutdown_hook): if logger.isEnabledFor(logging.DEBUG): - logger.debug('shutdown') + logger.debug("shutdown") shutdown_hook() common.stop_async() tray.destroy() @@ -92,18 +93,18 @@ def _shutdown(app, shutdown_hook): def run_loop(startup_hook, shutdown_hook, use_tray, show_window): - assert use_tray or show_window, 'need either tray or visible window' - APP_ID = 'io.github.pwr_solaar.solaar' + assert use_tray or show_window, "need either tray or visible window" + APP_ID = "io.github.pwr_solaar.solaar" application = Gtk.Application.new(APP_ID, Gio.ApplicationFlags.HANDLES_COMMAND_LINE) - application.connect('startup', lambda app, startup_hook: _startup(app, startup_hook, use_tray, show_window), startup_hook) - application.connect('command-line', _command_line) - application.connect('activate', _activate) - application.connect('shutdown', _shutdown, shutdown_hook) + application.connect("startup", lambda app, startup_hook: _startup(app, startup_hook, use_tray, show_window), startup_hook) + application.connect("command-line", _command_line) + application.connect("activate", _activate) + application.connect("shutdown", _shutdown, shutdown_hook) application.register() if application.get_is_remote(): - print(_('Another Solaar process is already running so just expose its window')) + print(_("Another Solaar process is already running so just expose its window")) application.run() @@ -115,7 +116,7 @@ def run_loop(startup_hook, shutdown_hook, use_tray, show_window): def _status_changed(device, alert, reason, refresh=False): assert device is not None if logger.isEnabledFor(logging.DEBUG): - logger.debug('status changed: %s (%s) %s', device, alert, reason) + logger.debug("status changed: %s (%s) %s", device, alert, reason) tray.update(device) if alert & ALERT.ATTENTION: diff --git a/lib/solaar/ui/about.py b/lib/solaar/ui/about.py index 1c081bf182..427c2dfac7 100644 --- a/lib/solaar/ui/about.py +++ b/lib/solaar/ui/about.py @@ -20,6 +20,7 @@ import logging from gi.repository import Gtk + from solaar import NAME, __version__ from solaar.i18n import _ @@ -35,60 +36,64 @@ def _create(): about.set_program_name(NAME) about.set_version(__version__) - about.set_comments(_('Manages Logitech receivers,\nkeyboards, mice, and tablets.')) + about.set_comments(_("Manages Logitech receivers,\nkeyboards, mice, and tablets.")) about.set_icon_name(NAME.lower()) - about.set_copyright('© 2012-2023 Daniel Pavel and contributors to the Solaar project') + about.set_copyright("© 2012-2023 Daniel Pavel and contributors to the Solaar project") about.set_license_type(Gtk.License.GPL_2_0) - about.set_authors(('Daniel Pavel http://github.com/pwr', )) + about.set_authors(("Daniel Pavel http://github.com/pwr",)) try: - about.add_credit_section(_('Additional Programming'), ('Filipe Laíns', 'Peter F. Patel-Schneider')) - about.add_credit_section(_('GUI design'), ('Julien Gascard', 'Daniel Pavel')) + about.add_credit_section(_("Additional Programming"), ("Filipe Laíns", "Peter F. Patel-Schneider")) + about.add_credit_section(_("GUI design"), ("Julien Gascard", "Daniel Pavel")) about.add_credit_section( - _('Testing'), ( - 'Douglas Wagner', - 'Julien Gascard', - 'Peter Wu http://www.lekensteyn.nl/logitech-unifying.html', - ) + _("Testing"), + ( + "Douglas Wagner", + "Julien Gascard", + "Peter Wu http://www.lekensteyn.nl/logitech-unifying.html", + ), ) about.add_credit_section( - _('Logitech documentation'), ( - 'Julien Danjou http://julien.danjou.info/blog/2012/logitech-unifying-upower', - 'Nestor Lopez Casado http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28', - ) + _("Logitech documentation"), + ( + "Julien Danjou http://julien.danjou.info/blog/2012/logitech-unifying-upower", + "Nestor Lopez Casado http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28", + ), ) except TypeError: # gtk3 < ~3.6.4 has incorrect gi bindings - logging.exception('failed to fully create the about dialog') + logging.exception("failed to fully create the about dialog") except Exception: # the Gtk3 version may be too old, and the function does not exist - logging.exception('failed to fully create the about dialog') + logging.exception("failed to fully create the about dialog") about.set_translator_credits( - '\n'.join(( - 'gogo (croatian)', - 'Papoteur, David Geiger, Damien Lallement (français)', - 'Michele Olivo (italiano)', - 'Adrian Piotrowicz (polski)', - 'Drovetto, JrBenito (Portuguese-BR)', - 'Daniel Pavel (română)', - 'Daniel Zippert, Emelie Snecker (svensk)', - 'Dimitriy Ryazantcev (Russian)', - 'El Jinete Sin Cabeza (Español)', - )) + "\n".join( + ( + "gogo (croatian)", + "Papoteur, David Geiger, Damien Lallement (français)", + "Michele Olivo (italiano)", + "Adrian Piotrowicz (polski)", + "Drovetto, JrBenito (Portuguese-BR)", + "Daniel Pavel (română)", + "Daniel Zippert, Emelie Snecker (svensk)", + "Dimitriy Ryazantcev (Russian)", + "El Jinete Sin Cabeza (Español)", + ) + ) ) - about.set_website('https://pwr-solaar.github.io/Solaar') + about.set_website("https://pwr-solaar.github.io/Solaar") about.set_website_label(NAME) - about.connect('response', lambda x, y: x.hide()) + about.connect("response", lambda x, y: x.hide()) def _hide(dialog, event): dialog.hide() return True - about.connect('delete-event', _hide) + about.connect("delete-event", _hide) return about diff --git a/lib/solaar/ui/action.py b/lib/solaar/ui/action.py index 18aad695b4..bc96eab02e 100644 --- a/lib/solaar/ui/action.py +++ b/lib/solaar/ui/action.py @@ -17,6 +17,7 @@ ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from gi.repository import Gdk, Gtk + from solaar.i18n import _ from . import pair_window @@ -36,7 +37,7 @@ def make(name, label, function, stock_id=None, *args): if stock_id is not None: action.set_stock_id(stock_id) if function: - action.connect('activate', function, *args) + action.connect("activate", function, *args) return action @@ -45,7 +46,7 @@ def make_toggle(name, label, function, stock_id=None, *args): action.set_icon_name(name) if stock_id is not None: action.set_stock_id(stock_id) - action.connect('activate', function, *args) + action.connect("activate", function, *args) return action @@ -80,12 +81,11 @@ def unpair(window, device): assert device.kind is not None qdialog = Gtk.MessageDialog( - window, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, - _('Unpair') + ' ' + device.name + ' ?' + window, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, _("Unpair") + " " + device.name + " ?" ) - qdialog.set_icon_name('remove') - qdialog.add_button(_('Cancel'), Gtk.ResponseType.CANCEL) - qdialog.add_button(_('Unpair'), Gtk.ResponseType.ACCEPT) + qdialog.set_icon_name("remove") + qdialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL) + qdialog.add_button(_("Unpair"), Gtk.ResponseType.ACCEPT) choice = qdialog.run() qdialog.destroy() if choice == Gtk.ResponseType.ACCEPT: @@ -97,4 +97,4 @@ def unpair(window, device): del receiver[device_number] except Exception: # logger.exception("unpairing %s", device) - error_dialog('unpair', device) + error_dialog("unpair", device) diff --git a/lib/solaar/ui/common.py b/lib/solaar/ui/common.py index 457fa3dfdb..1e223cbf62 100644 --- a/lib/solaar/ui/common.py +++ b/lib/solaar/ui/common.py @@ -23,32 +23,35 @@ from solaar.i18n import _ from solaar.tasks import TaskRunner as _TaskRunner -gi.require_version('Gtk', '3.0') +gi.require_version("Gtk", "3.0") from gi.repository import GLib, Gtk # NOQA: E402 logger = logging.getLogger(__name__) def _error_dialog(reason, object): - logger.error('error: %s %s', reason, object) + logger.error("error: %s %s", reason, object) - if reason == 'permissions': - title = _('Permissions error') + if reason == "permissions": + title = _("Permissions error") text = ( - _('Found a Logitech receiver or device (%s), but did not have permission to open it.') % object + '\n\n' + - _("If you've just installed Solaar, try disconnecting the receiver or device and then reconnecting it.") + _("Found a Logitech receiver or device (%s), but did not have permission to open it.") % object + + "\n\n" + + _("If you've just installed Solaar, try disconnecting the receiver or device and then reconnecting it.") ) - elif reason == 'nodevice': - title = _('Cannot connect to device error') + elif reason == "nodevice": + title = _("Cannot connect to device error") text = ( - _('Found a Logitech receiver or device at %s, but encountered an error connecting to it.') % object + '\n\n' + - _('Try disconnecting the device and then reconnecting it or turning it off and then on.') + _("Found a Logitech receiver or device at %s, but encountered an error connecting to it.") % object + + "\n\n" + + _("Try disconnecting the device and then reconnecting it or turning it off and then on.") ) - elif reason == 'unpair': - title = _('Unpairing failed') + elif reason == "unpair": + title = _("Unpairing failed") text = ( - _('Failed to unpair %{device} from %{receiver}.').format(device=object.name, receiver=object.receiver.name) + - '\n\n' + _('The receiver returned an error, with no further details.') + _("Failed to unpair %{device} from %{receiver}.").format(device=object.name, receiver=object.receiver.name) + + "\n\n" + + _("The receiver returned an error, with no further details.") ) else: raise Exception("ui.error_dialog: don't know how to handle (%s, %s)", reason, object) @@ -76,7 +79,7 @@ def error_dialog(reason, object): def start_async(): global _task_runner - _task_runner = _TaskRunner('AsyncUI') + _task_runner = _TaskRunner("AsyncUI") _task_runner.start() diff --git a/lib/solaar/ui/config_panel.py b/lib/solaar/ui/config_panel.py index d97f2e5a93..24bbe0f193 100644 --- a/lib/solaar/ui/config_panel.py +++ b/lib/solaar/ui/config_panel.py @@ -26,11 +26,12 @@ from logitech_receiver.hidpp20 import LEDEffectSetting as _LEDEffectSetting from logitech_receiver.settings import KIND as _SETTING_KIND from logitech_receiver.settings import SENSITIVITY_IGNORE as _SENSITIVITY_IGNORE + from solaar.i18n import _, ngettext from .common import ui_async as _ui_async -gi.require_version('Gtk', '3.0') +gi.require_version("Gtk", "3.0") from gi.repository import Gdk, GLib, Gtk # NOQA: E402 logger = logging.getLogger(__name__) @@ -41,7 +42,6 @@ def _read_async(setting, force_read, sbox, device_is_online, sensitive): - def _do_read(s, force, sb, online, sensitive): v = s.read(not force) GLib.idle_add(_update_setting_item, sb, v, online, sensitive, True, priority=99) @@ -50,7 +50,6 @@ def _do_read(s, force, sb, online, sensitive): def _write_async(setting, value, sbox, sensitive=True, key=None): - def _do_write(s, v, sb, key): try: if key is None: @@ -78,7 +77,6 @@ def _do_write(s, v, sb, key): class ComboBoxText(Gtk.ComboBoxText): - def get_value(self): return int(self.get_active_id()) @@ -87,13 +85,11 @@ def set_value(self, value): class Scale(Gtk.Scale): - def get_value(self): return int(super().get_value()) -class Control(): - +class Control: def __init__(**kwargs): pass @@ -119,11 +115,10 @@ def layout(self, sbox, label, change, spinner, failed): class ToggleControl(Gtk.Switch, Control): - def __init__(self, sbox, delegate=None): super().__init__(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER) self.init(sbox, delegate) - self.connect('notify::active', self.changed) + self.connect("notify::active", self.changed) def set_value(self, value): if value is not None: @@ -134,7 +129,6 @@ def get_value(self): class SliderControl(Gtk.Scale, Control): - def __init__(self, sbox, delegate=None): super().__init__(halign=Gtk.Align.FILL) self.init(sbox, delegate) @@ -143,7 +137,7 @@ def __init__(self, sbox, delegate=None): self.set_round_digits(0) self.set_digits(0) self.set_increments(1, 5) - self.connect('value-changed', self.changed) + self.connect("value-changed", self.changed) def get_value(self): return int(super().get_value()) @@ -169,14 +163,13 @@ def _create_choice_control(sbox, delegate=None, choices=None): # GTK boxes have property lists, but the keys must be strings class ChoiceControlLittle(Gtk.ComboBoxText, Control): - def __init__(self, sbox, delegate=None, choices=None): super().__init__(halign=Gtk.Align.FILL) self.init(sbox, delegate) self.choices = choices if choices is not None else sbox.setting.choices for entry in self.choices: self.append(str(int(entry)), str(entry)) - self.connect('changed', self.changed) + self.connect("changed", self.changed) def get_value(self): return int(self.get_active_id()) if self.get_active_id() is not None else None @@ -196,7 +189,6 @@ def set_choices(self, choices): class ChoiceControlBig(Gtk.Entry, Control): - def __init__(self, sbox, delegate=None, choices=None): super().__init__(halign=Gtk.Align.FILL) self.init(sbox, delegate) @@ -208,13 +200,13 @@ def __init__(self, sbox, delegate=None, choices=None): liststore.append((int(v), str(v))) completion = Gtk.EntryCompletion() completion.set_model(liststore) - norm = lambda s: s.replace('_', '').replace(' ', '').lower() + norm = lambda s: s.replace("_", "").replace(" ", "").lower() completion.set_match_func(lambda completion, key, it: norm(key) in norm(completion.get_model()[it][1])) completion.set_text_column(1) self.set_completion(completion) - self.connect('changed', self.changed) - self.connect('activate', self.activate) - completion.connect('match_selected', self.select) + self.connect("changed", self.changed) + self.connect("activate", self.activate) + completion.connect("match_selected", self.select) def get_value(self): choice = self.get_choice() @@ -230,25 +222,24 @@ def get_choice(self): def changed(self, *args): self.value = self.get_choice() - icon = 'dialog-warning' if self.value is None else 'dialog-question' if self.get_sensitive() else '' + icon = "dialog-warning" if self.value is None else "dialog-question" if self.get_sensitive() else "" self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - tooltip = _('Incomplete') if self.value is None else _('Complete - ENTER to change') + tooltip = _("Incomplete") if self.value is None else _("Complete - ENTER to change") self.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, tooltip) def activate(self, *args): if self.value is not None and self.get_sensitive(): - self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, '') + self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "") self.delegate.update() def select(self, completion, model, iter): self.set_value(model.get(iter, 0)[0]) if self.value and self.get_sensitive(): - self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, '') + self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "") self.delegate.update() class MapChoiceControl(Gtk.HBox, Control): - def __init__(self, sbox, delegate=None): super().__init__(homogeneous=False, spacing=6) self.init(sbox, delegate) @@ -261,7 +252,7 @@ def __init__(self, sbox, delegate=None): self.valueBox = _create_choice_control(sbox.setting, choices=self.value_choices, delegate=self) self.pack_start(self.keyBox, False, False, 0) self.pack_end(self.valueBox, False, False, 0) - self.keyBox.connect('changed', self.map_value_notify_key) + self.keyBox.connect("changed", self.map_value_notify_key) def get_value(self): key_choice = int(self.keyBox.get_active_id()) @@ -301,8 +292,7 @@ def update(self): class MultipleControl(Gtk.ListBox, Control): - - def __init__(self, sbox, change, button_label='...', delegate=None): + def __init__(self, sbox, change, button_label="...", delegate=None): super().__init__() self.init(sbox, delegate) self.set_selection_mode(Gtk.SelectionMode.NONE) @@ -311,7 +301,7 @@ def __init__(self, sbox, change, button_label='...', delegate=None): self.setup(sbox.setting) # set up the data and boxes for the sub-controls btn = Gtk.Button(button_label) btn.set_alignment(1.0, 0.5) - btn.connect('clicked', self.toggle_display) + btn.connect("clicked", self.toggle_display) self._button = btn hbox = Gtk.HBox(homogeneous=False, spacing=6) hbox.pack_end(change, False, False, 0) @@ -345,22 +335,21 @@ def toggle_display(self, *args): class MultipleToggleControl(MultipleControl): - def setup(self, setting): self._label_control_pairs = [] for k in setting._validator.get_options(): h = Gtk.HBox(homogeneous=False, spacing=0) lbl_text = str(k) lbl_tooltip = None - if hasattr(setting, '_labels'): + if hasattr(setting, "_labels"): l1, l2 = setting._labels.get(k, (None, None)) lbl_text = l1 if l1 else lbl_text lbl_tooltip = l2 if l2 else lbl_tooltip lbl = Gtk.Label(lbl_text) - h.set_tooltip_text(lbl_tooltip or ' ') + h.set_tooltip_text(lbl_tooltip or " ") control = Gtk.Switch() control._setting_key = int(k) - control.connect('notify::active', self.toggle_notify) + control.connect("notify::active", self.toggle_notify) h.pack_start(lbl, False, False, 0) h.pack_end(control, False, False, 0) lbl.set_alignment(0.0, 0.5) @@ -388,26 +377,25 @@ def set_value(self, value): elem.set_state(v) if elem.get_state(): active += 1 - to_join.append(lbl.get_text() + ': ' + str(elem.get_state())) - b = ', '.join(to_join) - self._button.set_label(f'{active} / {total}') + to_join.append(lbl.get_text() + ": " + str(elem.get_state())) + b = ", ".join(to_join) + self._button.set_label(f"{active} / {total}") self._button.set_tooltip_text(b) class MultipleRangeControl(MultipleControl): - def setup(self, setting): self._items = [] for item in setting._validator.items: lbl_text = str(item) lbl_tooltip = None - if hasattr(setting, '_labels'): + if hasattr(setting, "_labels"): l1, l2 = setting._labels.get(int(item), (None, None)) lbl_text = l1 if l1 else lbl_text lbl_tooltip = l2 if l2 else lbl_tooltip item_lbl = Gtk.Label(lbl_text) self.add(item_lbl) - self.set_tooltip_text(lbl_tooltip or ' ') + self.set_tooltip_text(lbl_tooltip or " ") item_lb = Gtk.ListBox() item_lb.set_selection_mode(Gtk.SelectionMode.NONE) item_lb._sub_items = [] @@ -415,27 +403,27 @@ def setup(self, setting): h = Gtk.HBox(homogeneous=False, spacing=20) lbl_text = str(sub_item) lbl_tooltip = None - if hasattr(setting, '_labels_sub'): + if hasattr(setting, "_labels_sub"): l1, l2 = setting._labels_sub.get(str(sub_item), (None, None)) lbl_text = l1 if l1 else lbl_text lbl_tooltip = l2 if l2 else lbl_tooltip sub_item_lbl = Gtk.Label(lbl_text) - h.set_tooltip_text(lbl_tooltip or ' ') + h.set_tooltip_text(lbl_tooltip or " ") h.pack_start(sub_item_lbl, False, False, 0) sub_item_lbl.set_margin_left(30) sub_item_lbl.set_alignment(0.0, 0.5) - if sub_item.widget == 'Scale': + if sub_item.widget == "Scale": control = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, sub_item.minimum, sub_item.maximum, 1) control.set_round_digits(0) control.set_digits(0) h.pack_end(control, True, True, 0) - elif sub_item.widget == 'SpinButton': + elif sub_item.widget == "SpinButton": control = Gtk.SpinButton.new_with_range(sub_item.minimum, sub_item.maximum, 1) control.set_digits(0) h.pack_end(control, False, False, 0) else: raise NotImplementedError - control.connect('value-changed', self.changed, item, sub_item) + control.connect("value-changed", self.changed, item, sub_item) item_lb.add(h) h._setting_sub_item = sub_item h._label, h._control = sub_item_lbl, control @@ -447,14 +435,14 @@ def setup(self, setting): def changed(self, control, item, sub_item): if control.get_sensitive(): - if hasattr(control, '_timer'): + if hasattr(control, "_timer"): control._timer.cancel() control._timer = _Timer(0.5, lambda: GLib.idle_add(self._write, control, item, sub_item)) control._timer.start() def _write(self, control, item, sub_item): control._timer.cancel() - delattr(control, '_timer') + delattr(control, "_timer") new_state = int(control.get_value()) if self.sbox.setting._value[int(item)][str(sub_item)] != new_state: self.sbox.setting._value[int(item)][str(sub_item)] = new_state @@ -463,13 +451,13 @@ def _write(self, control, item, sub_item): def set_value(self, value): if value is None: return - b = '' + b = "" n = 0 for ch in self._items: item = ch._setting_item v = value.get(int(item), None) if v is not None: - b += str(item) + ': (' + b += str(item) + ": (" to_join = [] for c in ch._sub_items: sub_item = c._setting_sub_item @@ -479,15 +467,14 @@ def set_value(self, value): sub_item_value = c._control.get_value() c._control.set_value(sub_item_value) n += 1 - to_join.append(str(sub_item) + f'={sub_item_value}') - b += ', '.join(to_join) + ') ' - lbl_text = ngettext('%d value', '%d values', n) % n + to_join.append(str(sub_item) + f"={sub_item_value}") + b += ", ".join(to_join) + ") " + lbl_text = ngettext("%d value", "%d values", n) % n self._button.set_label(lbl_text) self._button.set_tooltip_text(b) class PackedRangeControl(MultipleRangeControl): - def setup(self, setting): validator = setting._validator self._items = [] @@ -497,7 +484,7 @@ def setup(self, setting): control = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, validator.min_value, validator.max_value, 1) control.set_round_digits(0) control.set_digits(0) - control.connect('value-changed', self.changed, validator.keys[item]) + control.connect("value-changed", self.changed, validator.keys[item]) h.pack_start(lbl, False, False, 0) h.pack_end(control, True, True, 0) h._setting_item = validator.keys[item] @@ -509,14 +496,14 @@ def setup(self, setting): def changed(self, control, item): if control.get_sensitive(): - if hasattr(control, '_timer'): + if hasattr(control, "_timer"): control._timer.cancel() control._timer = _Timer(0.5, lambda: GLib.idle_add(self._write, control, item)) control._timer.start() def _write(self, control, item): control._timer.cancel() - delattr(control, '_timer') + delattr(control, "_timer") new_state = int(control.get_value()) if self.sbox.setting._value[int(item)] != new_state: self.sbox.setting._value[int(item)] = new_state @@ -525,7 +512,7 @@ def _write(self, control, item): def set_value(self, value): if value is None: return - b = '' + b = "" n = len(self._items) for h in self._items: item = h._setting_item @@ -534,43 +521,42 @@ def set_value(self, value): h.control.set_value(v) else: v = self.sbox.setting._value[int(item)] - b += str(item) + ': (' + str(v) + ') ' - lbl_text = ngettext('%d value', '%d values', n) % n + b += str(item) + ": (" + str(v) + ") " + lbl_text = ngettext("%d value", "%d values", n) % n self._button.set_label(lbl_text) self._button.set_tooltip_text(b) # control with an ID key that determines what else to show class HeteroKeyControl(Gtk.HBox, Control): - def __init__(self, sbox, delegate=None): super().__init__(homogeneous=False, spacing=6) self.init(sbox, delegate) self._items = {} for item in sbox.setting.possible_fields: - if item['label']: - item_lblbox = Gtk.Label(item['label']) + if item["label"]: + item_lblbox = Gtk.Label(item["label"]) self.pack_start(item_lblbox, False, False, 0) item_lblbox.set_visible(False) else: item_lblbox = None - if item['kind'] == _SETTING_KIND.choice: + if item["kind"] == _SETTING_KIND.choice: item_box = ComboBoxText() - for entry in item['choices']: + for entry in item["choices"]: item_box.append(str(int(entry)), str(entry)) item_box.set_active(0) - item_box.connect('changed', self.changed) + item_box.connect("changed", self.changed) self.pack_start(item_box, False, False, 0) - elif item['kind'] == _SETTING_KIND.range: + elif item["kind"] == _SETTING_KIND.range: item_box = Scale() - item_box.set_range(item['min'], item['max']) + item_box.set_range(item["min"], item["max"]) item_box.set_round_digits(0) item_box.set_digits(0) item_box.set_increments(1, 5) - item_box.connect('value-changed', self.changed) + item_box.connect("value-changed", self.changed) self.pack_start(item_box, True, True, 0) item_box.set_visible(False) - self._items[str(item['name'])] = (item_lblbox, item_box) + self._items[str(item["name"])] = (item_lblbox, item_box) def get_value(self): result = {} @@ -591,23 +577,23 @@ def set_value(self, value): def setup_visibles(self, ID): fields = self.sbox.setting.fields_map[ID][1] if ID in self.sbox.setting.fields_map else {} for name, (lblbox, box) in self._items.items(): - visible = name in fields or name == 'ID' + visible = name in fields or name == "ID" if lblbox: lblbox.set_visible(visible) box.set_visible(visible) def changed(self, control): if self.get_sensitive() and control.get_sensitive(): - if 'ID' in self._items and control == self._items['ID'][1]: - self.setup_visibles(int(self._items['ID'][1].get_value())) - if hasattr(control, '_timer'): + if "ID" in self._items and control == self._items["ID"][1]: + self.setup_visibles(int(self._items["ID"][1].get_value())) + if hasattr(control, "_timer"): control._timer.cancel() control._timer = _Timer(0.3, lambda: GLib.idle_add(self._write, control)) control._timer.start() def _write(self, control): control._timer.cancel() - delattr(control, '_timer') + delattr(control, "_timer") new_state = self.get_value() if self.sbox.setting._value != new_state: _write_async(self.sbox.setting, new_state, self.sbox) @@ -617,11 +603,11 @@ def _write(self, control): # # -_allowables_icons = {True: 'changes-allow', False: 'changes-prevent', _SENSITIVITY_IGNORE: 'dialog-error'} +_allowables_icons = {True: "changes-allow", False: "changes-prevent", _SENSITIVITY_IGNORE: "dialog-error"} _allowables_tooltips = { - True: _('Changes allowed'), - False: _('No changes allowed'), - _SENSITIVITY_IGNORE: _('Ignore this setting') + True: _("Changes allowed"), + False: _("No changes allowed"), + _SENSITIVITY_IGNORE: _("Ignore this setting"), } _next_allowable = {True: False, False: _SENSITIVITY_IGNORE, _SENSITIVITY_IGNORE: True} _icons_allowables = {v: k for k, v in _allowables_icons.items()} @@ -666,19 +652,19 @@ def _create_sbox(s, device): label = Gtk.EventBox() label.add(lbl) spinner = Gtk.Spinner() - spinner.set_tooltip_text(_('Working') + '...') + spinner.set_tooltip_text(_("Working") + "...") sbox._spinner = spinner - failed = Gtk.Image.new_from_icon_name('dialog-warning', Gtk.IconSize.SMALL_TOOLBAR) - failed.set_tooltip_text(_('Read/write operation failed.')) + failed = Gtk.Image.new_from_icon_name("dialog-warning", Gtk.IconSize.SMALL_TOOLBAR) + failed.set_tooltip_text(_("Read/write operation failed.")) sbox._failed = failed - change_icon = Gtk.Image.new_from_icon_name('changes-prevent', Gtk.IconSize.LARGE_TOOLBAR) + change_icon = Gtk.Image.new_from_icon_name("changes-prevent", Gtk.IconSize.LARGE_TOOLBAR) sbox._change_icon = change_icon _change_icon(False, change_icon) change = Gtk.Button() change.set_relief(Gtk.ReliefStyle.NONE) change.add(change_icon) change.set_sensitive(True) - change.connect('clicked', _change_click, sbox) + change.connect("clicked", _change_click, sbox) if s.kind == _SETTING_KIND.toggle: control = ToggleControl(sbox) @@ -698,7 +684,7 @@ def _create_sbox(s, device): control = HeteroKeyControl(sbox, change) else: if logger.isEnabledFor(logging.WARNING): - logger.warning('setting %s display not implemented', s.label) + logger.warning("setting %s display not implemented", s.label) return None control.set_sensitive(False) # the first read will enable it @@ -728,7 +714,7 @@ def _update_setting_item(sbox, value, is_online=True, sensitive=True, nullOK=Fal def _disable_listbox_highlight_bg(lb): colour = Gdk.RGBA() - colour.parse('rgba(0,0,0,0)') + colour.parse("rgba(0,0,0,0)") for child in lb.get_children(): child.override_background_color(Gtk.StateFlags.PRELIGHT, colour) @@ -830,10 +816,10 @@ def record_setting(device, setting, values): def _record_setting(device, setting_class, values): if logger.isEnabledFor(logging.DEBUG): - logger.debug('on %s changing setting %s to %s', device, setting_class.name, values) + logger.debug("on %s changing setting %s to %s", device, setting_class.name, values) setting = next((s for s in device.settings if s.name == setting_class.name), None) if setting is None and logger.isEnabledFor(logging.DEBUG): - logger.debug('No setting for %s found on %s when trying to record a change made elsewhere', setting_class.name, device) + logger.debug("No setting for %s found on %s when trying to record a change made elsewhere", setting_class.name, device) if setting: assert device == setting._device if len(values) > 1: diff --git a/lib/solaar/ui/diversion_rules.py b/lib/solaar/ui/diversion_rules.py index fc71a0ea78..eee464fdf7 100644 --- a/lib/solaar/ui/diversion_rules.py +++ b/lib/solaar/ui/diversion_rules.py @@ -38,6 +38,7 @@ from logitech_receiver.settings import Setting as _Setting from logitech_receiver.settings_templates import SETTINGS as _SETTINGS from logitech_receiver.special_keys import CONTROL as _CONTROL + from solaar.i18n import _ logger = logging.getLogger(__name__) @@ -51,7 +52,6 @@ class RuleComponentWrapper(GObject.GObject): - def __init__(self, component, level=0, editable=False): self.component = component self.level = level @@ -61,24 +61,24 @@ def __init__(self, component, level=0, editable=False): def display_left(self): if isinstance(self.component, _DIV.Rule): if self.level == 0: - return _('Built-in rules') if not self.editable else _('User-defined rules') + return _("Built-in rules") if not self.editable else _("User-defined rules") if self.level == 1: - return ' ' + _('Rule') - return ' ' + _('Sub-rule') + return " " + _("Rule") + return " " + _("Sub-rule") if self.component is None: - return _('[empty]') - return ' ' + self.__component_ui().left_label(self.component) + return _("[empty]") + return " " + self.__component_ui().left_label(self.component) def display_right(self): if self.component is None: - return '' + return "" return self.__component_ui().right_label(self.component) def display_icon(self): if self.component is None: - return '' + return "" if isinstance(self.component, _DIV.Rule) and self.level == 0: - return 'emblem-system' if not self.editable else 'avatar-default' + return "emblem-system" if not self.editable else "avatar-default" return self.__component_ui().icon_name() def __component_ui(self): @@ -86,12 +86,10 @@ def __component_ui(self): class DiversionDialog: - def __init__(self): - window = Gtk.Window() - window.set_title(_('Solaar Rule Editor')) - window.connect('delete-event', self._closing) + window.set_title(_("Solaar Rule Editor")) + window.connect("delete-event", self._closing) vbox = Gtk.VBox() self.top_panel, self.view = self._create_top_panel() @@ -105,10 +103,12 @@ def __init__(self): self.update_ui = {} self.bottom_panel = self._create_bottom_panel() self.ui = defaultdict(lambda: UnsupportedRuleComponentUI(self.bottom_panel)) - self.ui.update({ # one instance per type - rc_class: rc_ui_class(self.bottom_panel, on_update=self.on_update) - for rc_class, rc_ui_class in COMPONENT_UI.items() - }) + self.ui.update( + { # one instance per type + rc_class: rc_ui_class(self.bottom_panel, on_update=self.on_update) + for rc_class, rc_ui_class in COMPONENT_UI.items() + } + ) vbox.pack_start(self.bottom_panel, False, False, 10) self.model = self._create_model() @@ -125,10 +125,10 @@ def __init__(self): window.show_all() - window.connect('delete-event', lambda w, e: w.hide_on_delete() or True) + window.connect("delete-event", lambda w, e: w.hide_on_delete() or True) style = window.get_style_context() - style.add_class('solaar') + style.add_class("solaar") self.window = window self._editing_component = None @@ -137,19 +137,19 @@ def _closing(self, w, e): dialog = Gtk.MessageDialog( self.window, type=Gtk.MessageType.QUESTION, - title=_('Make changes permanent?'), + title=_("Make changes permanent?"), flags=Gtk.DialogFlags.MODAL, ) dialog.set_default_size(400, 100) dialog.add_buttons( - _('Yes'), + _("Yes"), Gtk.ResponseType.YES, - _('No'), + _("No"), Gtk.ResponseType.NO, - _('Cancel'), + _("Cancel"), Gtk.ResponseType.CANCEL, ) - dialog.set_markup(_('If you choose No, changes will be lost when Solaar is closed.')) + dialog.set_markup(_("If you choose No, changes will be lost when Solaar is closed.")) dialog.show_all() response = dialog.run() dialog.destroy() @@ -189,25 +189,25 @@ def _create_top_panel(self): view.set_enable_tree_lines(True) view.set_reorderable(False) - view.connect('key-press-event', self._event_key_pressed) - view.connect('button-release-event', self._event_button_released) - view.get_selection().connect('changed', self._selection_changed) + view.connect("key-press-event", self._event_key_pressed) + view.connect("button-release-event", self._event_button_released) + view.get_selection().connect("changed", self._selection_changed) sw.add(view) sw.set_size_request(0, 300) # don't ask for so much height button_box = Gtk.HBox(spacing=20) - self.save_btn = Gtk.Button.new_from_icon_name('document-save', Gtk.IconSize.BUTTON) - self.save_btn.set_label(_('Save changes')) + self.save_btn = Gtk.Button.new_from_icon_name("document-save", Gtk.IconSize.BUTTON) + self.save_btn.set_label(_("Save changes")) self.save_btn.set_always_show_image(True) self.save_btn.set_sensitive(False) self.save_btn.set_valign(Gtk.Align.CENTER) - self.discard_btn = Gtk.Button.new_from_icon_name('document-revert', Gtk.IconSize.BUTTON) - self.discard_btn.set_label(_('Discard changes')) + self.discard_btn = Gtk.Button.new_from_icon_name("document-revert", Gtk.IconSize.BUTTON) + self.discard_btn.set_label(_("Discard changes")) self.discard_btn.set_always_show_image(True) self.discard_btn.set_sensitive(False) self.discard_btn.set_valign(Gtk.Align.CENTER) - self.save_btn.connect('clicked', lambda *_args: self._save_yaml_file()) - self.discard_btn.connect('clicked', lambda *_args: self._reload_yaml_file()) + self.save_btn.connect("clicked", lambda *_args: self._save_yaml_file()) + self.discard_btn.connect("clicked", lambda *_args: self._reload_yaml_file()) button_box.pack_start(self.save_btn, False, False, 0) button_box.pack_start(self.discard_btn, False, False, 0) button_box.set_halign(Gtk.Align.CENTER) @@ -231,17 +231,16 @@ def _create_model(self): def _create_view_columns(self): cell_icon = Gtk.CellRendererPixbuf() cell1 = Gtk.CellRendererText() - col1 = Gtk.TreeViewColumn('Type') + col1 = Gtk.TreeViewColumn("Type") col1.pack_start(cell_icon, False) col1.pack_start(cell1, True) - col1.set_cell_data_func(cell1, lambda _c, c, m, it, _d: c.set_property('text', m.get_value(it, 0).display_left())) + col1.set_cell_data_func(cell1, lambda _c, c, m, it, _d: c.set_property("text", m.get_value(it, 0).display_left())) cell2 = Gtk.CellRendererText() - col2 = Gtk.TreeViewColumn('Summary') + col2 = Gtk.TreeViewColumn("Summary") col2.pack_start(cell2, True) - col2.set_cell_data_func(cell2, lambda _c, c, m, it, _d: c.set_property('text', m.get_value(it, 0).display_right())) + col2.set_cell_data_func(cell2, lambda _c, c, m, it, _d: c.set_property("text", m.get_value(it, 0).display_right())) col2.set_cell_data_func( - cell_icon, lambda _c, c, m, it, _d: c.set_property('icon-name', - m.get_value(it, 0).display_icon()) + cell_icon, lambda _c, c, m, it, _d: c.set_property("icon-name", m.get_value(it, 0).display_icon()) ) return col1, col2 @@ -257,7 +256,7 @@ def _populate_model(self, model, it, rule_component, level=0, pos=-1, editable=N if isinstance(rule_component, _DIV.Rule): editable = editable or (rule_component.source is not None) wrapped = RuleComponentWrapper(rule_component, level, editable=editable) - piter = model.insert(it, pos, (wrapped, )) + piter = model.insert(it, pos, (wrapped,)) if isinstance(rule_component, (_DIV.Rule, _DIV.And, _DIV.Or, _DIV.Later)): for c in rule_component.components: ed = editable or (isinstance(c, _DIV.Rule) and c.source is not None) @@ -296,7 +295,7 @@ def _selection_changed(self, selection): self.bottom_panel.set_sensitive(wrapped.editable) def _event_key_pressed(self, v, e): - ''' + """ Shortcuts: Ctrl + I insert component Ctrl + Delete delete row @@ -310,7 +309,7 @@ def _event_key_pressed(self, v, e): Ctrl + Shift + V paste above * flatten Ctrl + S save changes - ''' + """ state = e.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) m, it = v.get_selection().get_selected() wrapped = m[it][0] @@ -321,9 +320,13 @@ def _event_key_pressed(self, v, e): can_delete = wrapped.editable and not isinstance(parent_c, _DIV.Not) and c is not None and wrapped.level >= 1 can_insert = wrapped.editable and not isinstance(parent_c, _DIV.Not) and wrapped.level >= 2 can_insert_only_rule = wrapped.editable and wrapped.level == 1 - can_flatten = wrapped.editable and not isinstance(parent_c, _DIV.Not) and isinstance( - c, (_DIV.Rule, _DIV.And, _DIV.Or) - ) and wrapped.level >= 2 and len(c.components) + can_flatten = ( + wrapped.editable + and not isinstance(parent_c, _DIV.Not) + and isinstance(c, (_DIV.Rule, _DIV.And, _DIV.Or)) + and wrapped.level >= 2 + and len(c.components) + ) can_copy = wrapped.level >= 1 can_insert_root = wrapped.editable and wrapped.level == 0 if state & Gdk.ModifierType.CONTROL_MASK: @@ -333,8 +336,11 @@ def _event_key_pressed(self, v, e): self._menu_do_copy(None, m, it) elif can_insert and _rule_component_clipboard is not None and e.keyval in [Gdk.KEY_v, Gdk.KEY_V]: self._menu_do_paste(None, m, it, below=c is not None and not (state & Gdk.ModifierType.SHIFT_MASK)) - elif can_insert_only_rule and isinstance(_rule_component_clipboard, - _DIV.Rule) and e.keyval in [Gdk.KEY_v, Gdk.KEY_V]: + elif ( + can_insert_only_rule + and isinstance(_rule_component_clipboard, _DIV.Rule) + and e.keyval in [Gdk.KEY_v, Gdk.KEY_V] + ): self._menu_do_paste(None, m, it, below=c is not None and not (state & Gdk.ModifierType.SHIFT_MASK)) elif can_insert_root and isinstance(_rule_component_clipboard, _DIV.Rule) and e.keyval in [Gdk.KEY_v, Gdk.KEY_V]: self._menu_do_paste(None, m, m.iter_nth_child(it, 0)) @@ -368,21 +374,21 @@ def __get_insert_menus(self, m, it, c, can_insert, can_insert_only_rule, can_ins ins = self._menu_insert(m, it) items.append(ins) if c is None: # just a placeholder - ins.set_label(_('Insert here')) + ins.set_label(_("Insert here")) else: - ins.set_label(_('Insert above')) + ins.set_label(_("Insert above")) ins2 = self._menu_insert(m, it, below=True) - ins2.set_label(_('Insert below')) + ins2.set_label(_("Insert below")) items.append(ins2) elif can_insert_only_rule: ins = self._menu_create_rule(m, it) items.append(ins) if c is None: - ins.set_label(_('Insert new rule here')) + ins.set_label(_("Insert new rule here")) else: - ins.set_label(_('Insert new rule above')) + ins.set_label(_("Insert new rule above")) ins2 = self._menu_create_rule(m, it, below=True) - ins2.set_label(_('Insert new rule below')) + ins2.set_label(_("Insert new rule below")) items.append(ins2) elif can_insert_root: ins = self._menu_create_rule(m, m.iter_nth_child(it, 0)) @@ -401,9 +407,13 @@ def _event_button_released(self, v, e): can_delete = wrapped.editable and not isinstance(parent_c, _DIV.Not) and c is not None and wrapped.level >= 1 can_insert = wrapped.editable and not isinstance(parent_c, _DIV.Not) and wrapped.level >= 2 can_insert_only_rule = wrapped.editable and wrapped.level == 1 - can_flatten = wrapped.editable and not isinstance(parent_c, _DIV.Not) and isinstance( - c, (_DIV.Rule, _DIV.And, _DIV.Or) - ) and wrapped.level >= 2 and len(c.components) + can_flatten = ( + wrapped.editable + and not isinstance(parent_c, _DIV.Not) + and isinstance(c, (_DIV.Rule, _DIV.And, _DIV.Or)) + and wrapped.level >= 2 + and len(c.components) + ) can_copy = wrapped.level >= 1 can_insert_root = wrapped.editable and wrapped.level == 0 for item in self.__get_insert_menus(m, it, c, can_insert, can_insert_only_rule, can_insert_root): @@ -423,25 +433,25 @@ def _event_button_released(self, v, e): p = self._menu_paste(m, it) menu.append(p) if c is None: # just a placeholder - p.set_label(_('Paste here')) + p.set_label(_("Paste here")) else: - p.set_label(_('Paste above')) + p.set_label(_("Paste above")) p2 = self._menu_paste(m, it, below=True) - p2.set_label(_('Paste below')) + p2.set_label(_("Paste below")) menu.append(p2) elif can_insert_only_rule and isinstance(_rule_component_clipboard, _DIV.Rule): p = self._menu_paste(m, it) menu.append(p) if c is None: - p.set_label(_('Paste rule here')) + p.set_label(_("Paste rule here")) else: - p.set_label(_('Paste rule above')) + p.set_label(_("Paste rule above")) p2 = self._menu_paste(m, it, below=True) - p2.set_label(_('Paste rule below')) + p2.set_label(_("Paste rule below")) menu.append(p2) elif can_insert_root and isinstance(_rule_component_clipboard, _DIV.Rule): p = self._menu_paste(m, m.iter_nth_child(it, 0)) - p.set_label(_('Paste rule')) + p.set_label(_("Paste rule")) menu.append(p) if menu.get_children() and can_delete: menu.append(Gtk.SeparatorMenuItem(visible=True)) @@ -457,10 +467,10 @@ def _menu_do_flatten(self, _mitem, m, it): parent_c = m[parent_it][0].component idx = parent_c.components.index(c) if isinstance(c, _DIV.Not): - parent_c.components = [*parent_c.components[:idx], c.component, *parent_c.components[idx + 1:]] + parent_c.components = [*parent_c.components[:idx], c.component, *parent_c.components[idx + 1 :]] children = [next(m[it].iterchildren())[0].component] else: - parent_c.components = [*parent_c.components[:idx], *c.components, *parent_c.components[idx + 1:]] + parent_c.components = [*parent_c.components[:idx], *c.components, *parent_c.components[idx + 1 :]] children = [child[0].component for child in m[it].iterchildren()] m.remove(it) self._populate_model(m, parent_it, children, level=wrapped.level, pos=idx) @@ -470,8 +480,8 @@ def _menu_do_flatten(self, _mitem, m, it): self.on_update() def _menu_flatten(self, m, it): - menu_flatten = Gtk.MenuItem(_('Flatten')) - menu_flatten.connect('activate', self._menu_do_flatten, m, it) + menu_flatten = Gtk.MenuItem(_("Flatten")) + menu_flatten.connect("activate", self._menu_do_flatten, m, it) menu_flatten.show() return menu_flatten @@ -503,42 +513,42 @@ def _menu_do_insert_new(self, _mitem, m, it, cls, initial_value, below=False): def _menu_insert(self, m, it, below=False): elements = [ - _('Insert'), + _("Insert"), [ - (_('Sub-rule'), _DIV.Rule, []), - (_('Or'), _DIV.Or, []), - (_('And'), _DIV.And, []), + (_("Sub-rule"), _DIV.Rule, []), + (_("Or"), _DIV.Or, []), + (_("And"), _DIV.And, []), [ - _('Condition'), + _("Condition"), [ - (_('Feature'), _DIV.Feature, FeatureUI.FEATURES_WITH_DIVERSION[0]), - (_('Report'), _DIV.Report, 0), - (_('Process'), _DIV.Process, ''), - (_('Mouse process'), _DIV.MouseProcess, ''), - (_('Modifiers'), _DIV.Modifiers, []), - (_('Key'), _DIV.Key, ''), - (_('KeyIsDown'), _DIV.KeyIsDown, ''), - (_('Active'), _DIV.Active, ''), - (_('Device'), _DIV.Device, ''), - (_('Host'), _DIV.Host, ''), - (_('Setting'), _DIV.Setting, [None, '', None]), - (_('Test'), _DIV.Test, next(iter(_DIV.TESTS))), - (_('Test bytes'), _DIV.TestBytes, [0, 1, 0]), - (_('Mouse Gesture'), _DIV.MouseGesture, ''), - ] + (_("Feature"), _DIV.Feature, FeatureUI.FEATURES_WITH_DIVERSION[0]), + (_("Report"), _DIV.Report, 0), + (_("Process"), _DIV.Process, ""), + (_("Mouse process"), _DIV.MouseProcess, ""), + (_("Modifiers"), _DIV.Modifiers, []), + (_("Key"), _DIV.Key, ""), + (_("KeyIsDown"), _DIV.KeyIsDown, ""), + (_("Active"), _DIV.Active, ""), + (_("Device"), _DIV.Device, ""), + (_("Host"), _DIV.Host, ""), + (_("Setting"), _DIV.Setting, [None, "", None]), + (_("Test"), _DIV.Test, next(iter(_DIV.TESTS))), + (_("Test bytes"), _DIV.TestBytes, [0, 1, 0]), + (_("Mouse Gesture"), _DIV.MouseGesture, ""), + ], ], [ - _('Action'), + _("Action"), [ - (_('Key press'), _DIV.KeyPress, 'space'), - (_('Mouse scroll'), _DIV.MouseScroll, [0, 0]), - (_('Mouse click'), _DIV.MouseClick, ['left', 1]), - (_('Set'), _DIV.Set, [None, '', None]), - (_('Execute'), _DIV.Execute, ['']), - (_('Later'), _DIV.Later, [1]), - ] + (_("Key press"), _DIV.KeyPress, "space"), + (_("Mouse scroll"), _DIV.MouseScroll, [0, 0]), + (_("Mouse click"), _DIV.MouseClick, ["left", 1]), + (_("Set"), _DIV.Set, [None, "", None]), + (_("Execute"), _DIV.Execute, [""]), + (_("Later"), _DIV.Later, [1]), + ], ], - ] + ], ] def build(spec): @@ -554,7 +564,7 @@ def build(spec): label, feature, *args = spec item = Gtk.MenuItem(label) args = [a.copy() if isinstance(a, list) else a for a in args] - item.connect('activate', self._menu_do_insert_new, m, it, feature, *args, below) + item.connect("activate", self._menu_do_insert_new, m, it, feature, *args, below) return item else: return None @@ -564,8 +574,8 @@ def build(spec): return menu_insert def _menu_create_rule(self, m, it, below=False): - menu_create_rule = Gtk.MenuItem(_('Insert new rule')) - menu_create_rule.connect('activate', self._menu_do_insert_new, m, it, _DIV.Rule, [], below) + menu_create_rule = Gtk.MenuItem(_("Insert new rule")) + menu_create_rule.connect("activate", self._menu_do_insert_new, m, it, _DIV.Rule, [], below) menu_create_rule.show() return menu_create_rule @@ -584,8 +594,8 @@ def _menu_do_delete(self, _mitem, m, it): return c def _menu_delete(self, m, it): - menu_delete = Gtk.MenuItem(_('Delete')) - menu_delete.connect('activate', self._menu_do_delete, m, it) + menu_delete = Gtk.MenuItem(_("Delete")) + menu_delete.connect("activate", self._menu_do_delete, m, it) menu_delete.show() return menu_delete @@ -606,8 +616,8 @@ def _menu_do_negate(self, _mitem, m, it): self.on_update() def _menu_negate(self, m, it): - menu_negate = Gtk.MenuItem(_('Negate')) - menu_negate.connect('activate', self._menu_do_negate, m, it) + menu_negate = Gtk.MenuItem(_("Negate")) + menu_negate.connect("activate", self._menu_do_negate, m, it) menu_negate.show() return menu_negate @@ -630,14 +640,14 @@ def _menu_do_wrap(self, _mitem, m, it, cls): self.on_update() def _menu_wrap(self, m, it): - menu_wrap = Gtk.MenuItem(_('Wrap with')) + menu_wrap = Gtk.MenuItem(_("Wrap with")) submenu_wrap = Gtk.Menu() - menu_sub_rule = Gtk.MenuItem(_('Sub-rule')) - menu_and = Gtk.MenuItem(_('And')) - menu_or = Gtk.MenuItem(_('Or')) - menu_sub_rule.connect('activate', self._menu_do_wrap, m, it, _DIV.Rule) - menu_and.connect('activate', self._menu_do_wrap, m, it, _DIV.And) - menu_or.connect('activate', self._menu_do_wrap, m, it, _DIV.Or) + menu_sub_rule = Gtk.MenuItem(_("Sub-rule")) + menu_and = Gtk.MenuItem(_("And")) + menu_or = Gtk.MenuItem(_("Or")) + menu_sub_rule.connect("activate", self._menu_do_wrap, m, it, _DIV.Rule) + menu_and.connect("activate", self._menu_do_wrap, m, it, _DIV.And) + menu_or.connect("activate", self._menu_do_wrap, m, it, _DIV.Or) submenu_wrap.append(menu_sub_rule) submenu_wrap.append(menu_and) submenu_wrap.append(menu_or) @@ -652,8 +662,8 @@ def _menu_do_cut(self, _mitem, m, it): _rule_component_clipboard = c def _menu_cut(self, m, it): - menu_cut = Gtk.MenuItem(_('Cut')) - menu_cut.connect('activate', self._menu_do_cut, m, it) + menu_cut = Gtk.MenuItem(_("Cut")) + menu_cut.connect("activate", self._menu_do_cut, m, it) menu_cut.show() return menu_cut @@ -667,8 +677,8 @@ def _menu_do_paste(self, _mitem, m, it, below=False): self.on_update() def _menu_paste(self, m, it, below=False): - menu_paste = Gtk.MenuItem(_('Paste')) - menu_paste.connect('activate', self._menu_do_paste, m, it, below) + menu_paste = Gtk.MenuItem(_("Paste")) + menu_paste.connect("activate", self._menu_do_paste, m, it, below) menu_paste.show() return menu_paste @@ -679,8 +689,8 @@ def _menu_do_copy(self, _mitem, m, it): _rule_component_clipboard = _DIV.RuleComponent().compile(c.data()) def _menu_copy(self, m, it): - menu_copy = Gtk.MenuItem(_('Copy')) - menu_copy.connect('activate', self._menu_do_copy, m, it) + menu_copy = Gtk.MenuItem(_("Copy")) + menu_copy.connect("activate", self._menu_do_copy, m, it) menu_copy.show() return menu_copy @@ -705,7 +715,6 @@ def update_devices(self): class CompletionEntry(Gtk.Entry): - def __init__(self, values, *args, **kwargs): super().__init__(*args, **kwargs) CompletionEntry.add_completion_to_entry(self, values) @@ -717,7 +726,7 @@ def add_completion_to_entry(cls, entry, values): liststore = Gtk.ListStore(str) completion = Gtk.EntryCompletion() completion.set_model(liststore) - norm = lambda s: s.replace('_', '').replace(' ', '').lower() + norm = lambda s: s.replace("_", "").replace(" ", "").lower() completion.set_match_func(lambda completion, key, it: norm(key) in norm(completion.get_model()[it][0])) completion.set_text_column(0) entry.set_completion(completion) @@ -725,7 +734,7 @@ def add_completion_to_entry(cls, entry, values): liststore = completion.get_model() liststore.clear() for v in sorted(set(values), key=str.casefold): - liststore.append((v, )) + liststore.append((v,)) class SmartComboBox(Gtk.ComboBox): @@ -755,7 +764,7 @@ class SmartComboBox(Gtk.ComboBox): """ def __init__( - self, all_values, blank='', completion=False, case_insensitive=False, replace_with_default_name=False, **kwargs + self, all_values, blank="", completion=False, case_insensitive=False, replace_with_default_name=False, **kwargs ): super().__init__(**kwargs) self._name_to_idx = {} @@ -776,7 +785,7 @@ def replace_with(value): if name != self.get_child().get_text(): self.get_child().set_text(name) - self.connect('changed', lambda *a: replace_with(self.get_value(invalid_as_str=False))) + self.connect("changed", lambda *a: replace_with(self.get_value(invalid_as_str=False))) self.set_id_column(0) if self.get_has_entry(): @@ -784,9 +793,9 @@ def replace_with(value): else: renderer = Gtk.CellRendererText() self.pack_start(renderer, True) - self.add_attribute(renderer, 'text', 1) + self.add_attribute(renderer, "text", 1) self.set_all_values(all_values) - self.set_active_id('') + self.set_active_id("") @classmethod def new_model(cls): @@ -801,16 +810,16 @@ def set_all_values(self, all_values, visible_fn=(lambda value: True)): self._name_to_idx = {} self._value_to_idx = {} self._hidden_idx = set() - self._all_values = [v if isinstance(v, tuple) else (v, ) for v in all_values] + self._all_values = [v if isinstance(v, tuple) else (v,) for v in all_values] model, filtered_model = SmartComboBox.new_model() # creating a new model seems to be necessary to avoid firing 'changed' event once per inserted item - model.append(('', self._blank, True)) + model.append(("", self._blank, True)) self._model = model to_complete = [self._blank] for idx, item in enumerate(self._all_values): - value, *names = item if isinstance(item, tuple) else (item, ) + value, *names = item if isinstance(item, tuple) else (item,) visible = visible_fn(value) self._include(model, idx, value, visible, *names) if visible: @@ -850,7 +859,7 @@ def get_value(self, invalid_as_str=True, accept_hidden=True): if tree_iter is not None: t = self.get_model()[tree_iter] number = t[0] - return self._all_values[int(number)][0] if number != '' and (accept_hidden or t[2]) else None + return self._all_values[int(number)][0] if number != "" and (accept_hidden or t[2]) else None elif self.get_has_entry(): text = self.get_child().get_text().strip() if text == self._blank: @@ -885,14 +894,14 @@ def set_value(self, value, accept_invalid=True): If `value` is invalid, then the entry text is set to the provided value if the widget has an entry and `accept_invalid` is True, or else the blank value is set. """ - idx = self._find_idx(value) if value != self._blank else '' + idx = self._find_idx(value) if value != self._blank else "" if idx is not None: self.set_active_id(str(idx)) else: if self.get_has_entry() and accept_invalid: - self.get_child().set_text(str(value or '') if value != '' else self._blank) + self.get_child().set_text(str(value or "") if value != "" else self._blank) else: - self.set_active_id('') + self.set_active_id("") def show_only(self, only, include_new=False): """Hide items not present in `only`. @@ -913,15 +922,14 @@ def show_only(self, only, include_new=False): @dataclass class DeviceInfo: - - serial: str = '' - unitId: str = '' - codename: str = '' + serial: str = "" + unitId: str = "" + codename: str = "" settings: Dict[str, _Setting] = field(default_factory=dict) @property def id(self): - return self.serial or self.unitId or '' + return self.serial or self.unitId or "" @property def identifiers(self): @@ -929,23 +937,23 @@ def identifiers(self): @property def display_name(self): - return f'{self.codename} ({self.id})' + return f"{self.codename} ({self.id})" def __post_init__(self): - if self.serial is None or self.serial == '?': - self.serial = '' - if self.unitId is None or self.unitId == '?': - self.unitId = '' + if self.serial is None or self.serial == "?": + self.serial = "" + if self.unitId is None or self.unitId == "?": + self.unitId = "" def matches(self, search): return search and search in (self.serial, self.unitId, self.display_name) def update(self, device): - for k in ('serial', 'unitId', 'codename', 'settings'): + for k in ("serial", "unitId", "codename", "settings"): if not getattr(self, k, None): v = getattr(device, k, None) - if v and v != '?': - setattr(self, k, copy(v) if k != 'settings' else {s.name: s for s in v}) + if v and v != "?": + setattr(self, k, copy(v) if k != "settings" else {s.name: s for s in v}) @classmethod def from_device(cls, device): @@ -955,7 +963,6 @@ def from_device(cls, device): class AllDevicesInfo: - def __init__(self): self._devices = [] self._lock = threading.Lock() @@ -976,7 +983,7 @@ def refresh(self): def dev_in_row(_store, _treepath, row): nonlocal updated device = _dev_model.get_value(row, 7) - if device and device.kind and (device.serial and device.serial != '?' or device.unitId and device.unitId != '?'): + if device and device.kind and (device.serial and device.serial != "?" or device.unitId and device.unitId != "?"): existing = self[device.serial] or self[device.unitId] if not existing: updated = True @@ -991,7 +998,6 @@ def dev_in_row(_store, _treepath, row): class RuleComponentUI: - CLASS = _DIV.RuleComponent def __init__(self, panel, on_update=None): @@ -1039,11 +1045,11 @@ def left_label(cls, component): @classmethod def right_label(cls, _component): - return '' + return "" @classmethod def icon_name(cls): - return '' + return "" def _remove_panel_items(self): for c in self.panel.get_children(): @@ -1054,12 +1060,11 @@ def update_devices(self): class UnsupportedRuleComponentUI(RuleComponentUI): - CLASS = None def create_widgets(self): self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('This editor does not support the selected rule component yet.')) + self.label.set_text(_("This editor does not support the selected rule component yet.")) self.widgets[self.label] = (0, 0, 1, 1) @classmethod @@ -1068,7 +1073,6 @@ def right_label(cls, component): class RuleUI(RuleComponentUI): - CLASS = _DIV.Rule def create_widgets(self): @@ -1079,24 +1083,22 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Rule') + return _("Rule") @classmethod def icon_name(cls): - return 'format-justify-fill' + return "format-justify-fill" class ConditionUI(RuleComponentUI): - CLASS = _DIV.Condition @classmethod def icon_name(cls): - return 'dialog-question' + return "dialog-question" class AndUI(RuleComponentUI): - CLASS = _DIV.And def create_widgets(self): @@ -1107,11 +1109,10 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('And') + return _("And") class OrUI(RuleComponentUI): - CLASS = _DIV.Or def create_widgets(self): @@ -1122,11 +1123,10 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Or') + return _("Or") class LaterUI(RuleComponentUI): - CLASS = _DIV.Later MIN_VALUE = 1 MAX_VALUE = 100 @@ -1134,14 +1134,14 @@ class LaterUI(RuleComponentUI): def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Number of seconds to delay.')) + self.label.set_text(_("Number of seconds to delay.")) self.widgets[self.label] = (0, 0, 1, 1) self.field = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) self.field.set_halign(Gtk.Align.CENTER) self.field.set_valign(Gtk.Align.CENTER) self.field.set_hexpand(True) # self.field.set_vexpand(True) - self.field.connect('changed', self._on_update) + self.field.connect("changed", self._on_update) self.widgets[self.field] = (0, 1, 1, 1) def show(self, component, editable): @@ -1154,7 +1154,7 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Later') + return _("Later") @classmethod def right_label(cls, component): @@ -1162,7 +1162,6 @@ def right_label(cls, component): class NotUI(RuleComponentUI): - CLASS = _DIV.Not def create_widgets(self): @@ -1173,21 +1172,20 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Not') + return _("Not") class ProcessUI(ConditionUI): - CLASS = _DIV.Process def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('X11 active process. For use in X11 only.')) + self.label.set_text(_("X11 active process. For use in X11 only.")) self.widgets[self.label] = (0, 0, 1, 1) self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) self.field.set_size_request(600, 0) - self.field.connect('changed', self._on_update) + self.field.connect("changed", self._on_update) self.widgets[self.field] = (0, 1, 1, 1) def show(self, component, editable): @@ -1200,7 +1198,7 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Process') + return _("Process") @classmethod def right_label(cls, component): @@ -1208,17 +1206,16 @@ def right_label(cls, component): class MouseProcessUI(ConditionUI): - CLASS = _DIV.MouseProcess def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('X11 mouse process. For use in X11 only.')) + self.label.set_text(_("X11 mouse process. For use in X11 only.")) self.widgets[self.label] = (0, 0, 1, 1) self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) self.field.set_size_request(600, 0) - self.field.connect('changed', self._on_update) + self.field.connect("changed", self._on_update) self.widgets[self.field] = (0, 1, 1, 1) def show(self, component, editable): @@ -1231,7 +1228,7 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('MouseProcess') + return _("MouseProcess") @classmethod def right_label(cls, component): @@ -1239,7 +1236,6 @@ def right_label(cls, component): class FeatureUI(ConditionUI): - CLASS = _DIV.Feature FEATURES_WITH_DIVERSION = [ str(_ALL_FEATURES.CROWN), @@ -1256,16 +1252,16 @@ class FeatureUI(ConditionUI): def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Feature name of notification triggering rule processing.')) + self.label.set_text(_("Feature name of notification triggering rule processing.")) self.widgets[self.label] = (0, 0, 1, 1) self.field = Gtk.ComboBoxText.new_with_entry() - self.field.append('', '') + self.field.append("", "") for feature in self.FEATURES_WITH_DIVERSION: self.field.append(feature, feature) self.field.set_valign(Gtk.Align.CENTER) # self.field.set_vexpand(True) self.field.set_size_request(600, 0) - self.field.connect('changed', self._on_update) + self.field.connect("changed", self._on_update) all_features = [str(f) for f in _ALL_FEATURES] CompletionEntry.add_completion_to_entry(self.field.get_child(), all_features) self.widgets[self.field] = (0, 1, 1, 1) @@ -1273,30 +1269,29 @@ def create_widgets(self): def show(self, component, editable): super().show(component, editable) with self.ignore_changes(): - f = str(component.feature) if component.feature else '' + f = str(component.feature) if component.feature else "" self.field.set_active_id(f) if f not in self.FEATURES_WITH_DIVERSION: self.field.get_child().set_text(f) def collect_value(self): - return (self.field.get_active_text() or '').strip() + return (self.field.get_active_text() or "").strip() def _on_update(self, *args): super()._on_update(*args) - icon = 'dialog-warning' if not self.component.feature else '' + icon = "dialog-warning" if not self.component.feature else "" self.field.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) @classmethod def left_label(cls, component): - return _('Feature') + return _("Feature") @classmethod def right_label(cls, component): - return '%s (%04X)' % (str(component.feature), int(component.feature or 0)) + return "%s (%04X)" % (str(component.feature), int(component.feature or 0)) class ReportUI(ConditionUI): - CLASS = _DIV.Report MIN_VALUE = -1 # for invalid values MAX_VALUE = 15 @@ -1304,14 +1299,14 @@ class ReportUI(ConditionUI): def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Report number of notification triggering rule processing.')) + self.label.set_text(_("Report number of notification triggering rule processing.")) self.widgets[self.label] = (0, 0, 1, 1) self.field = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) self.field.set_halign(Gtk.Align.CENTER) self.field.set_valign(Gtk.Align.CENTER) self.field.set_hexpand(True) # self.field.set_vexpand(True) - self.field.connect('changed', self._on_update) + self.field.connect("changed", self._on_update) self.widgets[self.field] = (0, 1, 1, 1) def show(self, component, editable): @@ -1324,7 +1319,7 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Report') + return _("Report") @classmethod def right_label(cls, component): @@ -1332,13 +1327,12 @@ def right_label(cls, component): class ModifiersUI(ConditionUI): - CLASS = _DIV.Modifiers def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Active keyboard modifiers. Not always available in Wayland.')) + self.label.set_text(_("Active keyboard modifiers. Not always available in Wayland.")) self.widgets[self.label] = (0, 0, 5, 1) self.labels = {} self.switches = {} @@ -1349,7 +1343,7 @@ def create_widgets(self): self.widgets[switch] = (i, 2, 1, 1) self.labels[m] = label self.switches[m] = switch - switch.connect('notify::active', self._on_update) + switch.connect("notify::active", self._on_update) def show(self, component, editable): super().show(component, editable) @@ -1362,15 +1356,14 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Modifiers') + return _("Modifiers") @classmethod def right_label(cls, component): - return '+'.join(component.modifiers) or 'None' + return "+".join(component.modifiers) or "None" class KeyUI(ConditionUI): - CLASS = _DIV.Key KEY_NAMES = map(str, _CONTROL) @@ -1379,26 +1372,26 @@ def create_widgets(self): self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) self.label.set_text( _( - 'Diverted key or button depressed or released.\n' - 'Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons.' + "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons." ) ) self.widgets[self.label] = (0, 0, 5, 1) self.key_field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) self.key_field.set_size_request(600, 0) - self.key_field.connect('changed', self._on_update) + self.key_field.connect("changed", self._on_update) self.widgets[self.key_field] = (0, 1, 2, 1) - self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(None, _('Key down')) - self.action_pressed_radio.connect('toggled', self._on_update, _Key.DOWN) + self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(None, _("Key down")) + self.action_pressed_radio.connect("toggled", self._on_update, _Key.DOWN) self.widgets[self.action_pressed_radio] = (2, 1, 1, 1) - self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _('Key up')) - self.action_released_radio.connect('toggled', self._on_update, _Key.UP) + self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _("Key up")) + self.action_released_radio.connect("toggled", self._on_update, _Key.UP) self.widgets[self.action_released_radio] = (3, 1, 1, 1) def show(self, component, editable): super().show(component, editable) with self.ignore_changes(): - self.key_field.set_text(str(component.key) if self.component.key else '') + self.key_field.set_text(str(component.key) if self.component.key else "") if not component.action or component.action == _Key.DOWN: self.action_pressed_radio.set_active(True) else: @@ -1410,20 +1403,19 @@ def collect_value(self): def _on_update(self, *args): super()._on_update(*args) - icon = 'dialog-warning' if not self.component.key or not self.component.action else '' + icon = "dialog-warning" if not self.component.key or not self.component.action else "" self.key_field.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) @classmethod def left_label(cls, component): - return _('Key') + return _("Key") @classmethod def right_label(cls, component): - return '%s (%04X) (%s)' % (str(component.key), int(component.key), _(component.action)) if component.key else 'None' + return "%s (%04X) (%s)" % (str(component.key), int(component.key), _(component.action)) if component.key else "None" class KeyIsDownUI(ConditionUI): - CLASS = _DIV.KeyIsDown KEY_NAMES = map(str, _CONTROL) @@ -1432,54 +1424,53 @@ def create_widgets(self): self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) self.label.set_text( _( - 'Diverted key or button is currently down.\n' - 'Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons.' + "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons." ) ) self.widgets[self.label] = (0, 0, 5, 1) self.key_field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) self.key_field.set_size_request(600, 0) - self.key_field.connect('changed', self._on_update) + self.key_field.connect("changed", self._on_update) self.widgets[self.key_field] = (0, 1, 1, 1) def show(self, component, editable): super().show(component, editable) with self.ignore_changes(): - self.key_field.set_text(str(component.key) if self.component.key else '') + self.key_field.set_text(str(component.key) if self.component.key else "") def collect_value(self): return self.key_field.get_text() def _on_update(self, *args): super()._on_update(*args) - icon = 'dialog-warning' if not self.component.key else '' + icon = "dialog-warning" if not self.component.key else "" self.key_field.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) @classmethod def left_label(cls, component): - return _('KeyIsDown') + return _("KeyIsDown") @classmethod def right_label(cls, component): - return '%s (%04X)' % (str(component.key), int(component.key)) if component.key else 'None' + return "%s (%04X)" % (str(component.key), int(component.key)) if component.key else "None" class TestUI(ConditionUI): - CLASS = _DIV.Test def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Test condition on notification triggering rule processing.')) + self.label.set_text(_("Test condition on notification triggering rule processing.")) self.widgets[self.label] = (0, 0, 4, 1) - lbl = Gtk.Label(_('Test'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=False, vexpand=False) + lbl = Gtk.Label(_("Test"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=False, vexpand=False) self.widgets[lbl] = (0, 1, 1, 1) - lbl = Gtk.Label(_('Parameter'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=False, vexpand=False) + lbl = Gtk.Label(_("Parameter"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=False, vexpand=False) self.widgets[lbl] = (2, 1, 1, 1) self.test = Gtk.ComboBoxText.new_with_entry() - self.test.append('', '') + self.test.append("", "") for t in _DIV.TESTS: self.test.append(t, t) self.test.set_halign(Gtk.Align.END) @@ -1487,19 +1478,19 @@ def create_widgets(self): self.test.set_hexpand(False) self.test.set_size_request(300, 0) CompletionEntry.add_completion_to_entry(self.test.get_child(), _DIV.TESTS) - self.test.connect('changed', self._on_update) + self.test.connect("changed", self._on_update) self.widgets[self.test] = (1, 1, 1, 1) self.parameter = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) self.parameter.set_size_request(150, 0) - self.parameter.connect('changed', self._on_update) + self.parameter.connect("changed", self._on_update) self.widgets[self.parameter] = (3, 1, 1, 1) def show(self, component, editable): super().show(component, editable) with self.ignore_changes(): self.test.set_active_id(component.test) - self.parameter.set_text(str(component.parameter) if component.parameter is not None else '') + self.parameter.set_text(str(component.parameter) if component.parameter is not None else "") if component.test not in _DIV.TESTS: self.test.get_child().set_text(component.test) self._change_status_icon() @@ -1509,7 +1500,7 @@ def collect_value(self): param = int(self.parameter.get_text()) if self.parameter.get_text() else None except Exception: param = self.parameter.get_text() - test = (self.test.get_active_text() or '').strip() + test = (self.test.get_active_text() or "").strip() return [test, param] if param is not None else [test] def _on_update(self, *args): @@ -1517,51 +1508,48 @@ def _on_update(self, *args): self._change_status_icon() def _change_status_icon(self): - icon = 'dialog-warning' if (self.test.get_active_text() or '').strip() not in _DIV.TESTS else '' + icon = "dialog-warning" if (self.test.get_active_text() or "").strip() not in _DIV.TESTS else "" self.test.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) @classmethod def left_label(cls, component): - return _('Test') + return _("Test") @classmethod def right_label(cls, component): - return component.test + (' ' + repr(component.parameter) if component.parameter is not None else '') + return component.test + (" " + repr(component.parameter) if component.parameter is not None else "") -_TestBytesElement = namedtuple('TestBytesElement', ['id', 'label', 'min', 'max']) -_TestBytesMode = namedtuple('TestBytesMode', ['label', 'elements', 'label_fn']) +_TestBytesElement = namedtuple("TestBytesElement", ["id", "label", "min", "max"]) +_TestBytesMode = namedtuple("TestBytesMode", ["label", "elements", "label_fn"]) class TestBytesUI(ConditionUI): - CLASS = _DIV.TestBytes _common_elements = [ - _TestBytesElement('begin', _('begin (inclusive)'), 0, 16), - _TestBytesElement('end', _('end (exclusive)'), 0, 16) + _TestBytesElement("begin", _("begin (inclusive)"), 0, 16), + _TestBytesElement("end", _("end (exclusive)"), 0, 16), ] - _global_min = -2**31 + _global_min = -(2**31) _global_max = 2**31 - 1 _modes = { - 'range': - _TestBytesMode( - _('range'), - _common_elements + [ - _TestBytesElement('minimum', _('minimum'), _global_min, _global_max), # uint32 - _TestBytesElement('maximum', _('maximum'), _global_min, _global_max), + "range": _TestBytesMode( + _("range"), + _common_elements + + [ + _TestBytesElement("minimum", _("minimum"), _global_min, _global_max), # uint32 + _TestBytesElement("maximum", _("maximum"), _global_min, _global_max), ], - lambda e: _('bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d' % {str(i): v - for i, v in enumerate(e)}) + lambda e: _("bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" % {str(i): v for i, v in enumerate(e)}), + ), + "mask": _TestBytesMode( + _("mask"), + _common_elements + [_TestBytesElement("mask", _("mask"), _global_min, _global_max)], + lambda e: _("bytes %(0)d to %(1)d, mask %(2)d" % {str(i): v for i, v in enumerate(e)}), ), - 'mask': - _TestBytesMode( - _('mask'), _common_elements + [_TestBytesElement('mask', _('mask'), _global_min, _global_max)], - lambda e: _('bytes %(0)d to %(1)d, mask %(2)d' % {str(i): v - for i, v in enumerate(e)}) - ) } def create_widgets(self): @@ -1569,7 +1557,7 @@ def create_widgets(self): self.field_labels = {} self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Bit or range test on bytes in notification message triggering rule processing.')) + self.label.set_text(_("Bit or range test on bytes in notification message triggering rule processing.")) self.widgets[self.label] = (0, 0, 5, 1) col = 0 mode_col = 2 @@ -1577,9 +1565,9 @@ def create_widgets(self): mode_renderer = Gtk.CellRendererText() self.mode_field.set_id_column(0) self.mode_field.pack_start(mode_renderer, True) - self.mode_field.add_attribute(mode_renderer, 'text', 1) + self.mode_field.add_attribute(mode_renderer, "text", 1) self.widgets[self.mode_field] = (mode_col, 2, 1, 1) - mode_label = Gtk.Label(_('type'), margin_top=20) + mode_label = Gtk.Label(_("type"), margin_top=20) self.widgets[mode_label] = (mode_col, 1, 1, 1) for mode_id, mode in TestBytesUI._modes.items(): self.mode_field.get_model().append([mode_id, mode.label]) @@ -1588,21 +1576,21 @@ def create_widgets(self): field = Gtk.SpinButton.new_with_range(element.min, element.max, 1) field.set_value(0) field.set_size_request(150, 0) - field.connect('value-changed', self._on_update) + field.connect("value-changed", self._on_update) label = Gtk.Label(element.label, margin_top=20) self.fields[element.id] = field self.field_labels[element.id] = label self.widgets[label] = (col, 1, 1, 1) self.widgets[field] = (col, 2, 1, 1) col += 1 if col != mode_col - 1 else 2 - self.mode_field.connect('changed', lambda cb: (self._on_update(), self._only_mode(cb.get_active_id()))) - self.mode_field.set_active_id('range') + self.mode_field.connect("changed", lambda cb: (self._on_update(), self._only_mode(cb.get_active_id()))) + self.mode_field.set_active_id("range") def show(self, component, editable): super().show(component, editable) with self.ignore_changes(): - mode_id = {3: 'mask', 4: 'range'}.get(len(component.test), None) + mode_id = {3: "mask", 4: "range"}.get(len(component.test), None) self._only_mode(mode_id) if not mode_id: return @@ -1630,31 +1618,36 @@ def _on_update(self, *args): if not self.component: return begin, end, *etc = self.component.test - icon = 'dialog-warning' if end <= begin else '' - self.fields['end'].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + icon = "dialog-warning" if end <= begin else "" + self.fields["end"].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) if len(self.component.test) == 4: *etc, minimum, maximum = self.component.test - icon = 'dialog-warning' if maximum < minimum else '' - self.fields['maximum'].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + icon = "dialog-warning" if maximum < minimum else "" + self.fields["maximum"].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) @classmethod def left_label(cls, component): - return _('Test bytes') + return _("Test bytes") @classmethod def right_label(cls, component): - mode_id = {3: 'mask', 4: 'range'}.get(len(component.test), None) + mode_id = {3: "mask", 4: "range"}.get(len(component.test), None) if not mode_id: return str(component.test) return TestBytesUI._modes[mode_id].label_fn(component.test) class MouseGestureUI(ConditionUI): - CLASS = _DIV.MouseGesture MOUSE_GESTURE_NAMES = [ - 'Mouse Up', 'Mouse Down', 'Mouse Left', 'Mouse Right', 'Mouse Up-left', 'Mouse Up-right', 'Mouse Down-left', - 'Mouse Down-right' + "Mouse Up", + "Mouse Down", + "Mouse Left", + "Mouse Right", + "Mouse Up-left", + "Mouse Up-right", + "Mouse Down-left", + "Mouse Down-right", ] MOVE_NAMES = list(map(str, _CONTROL)) + MOUSE_GESTURE_NAMES @@ -1662,13 +1655,13 @@ def create_widgets(self): self.widgets = {} self.fields = [] self.label = Gtk.Label( - _('Mouse gesture with optional initiating button followed by zero or more mouse movements.'), - halign=Gtk.Align.CENTER + _("Mouse gesture with optional initiating button followed by zero or more mouse movements."), + halign=Gtk.Align.CENTER, ) self.widgets[self.label] = (0, 0, 5, 1) self.del_btns = [] - self.add_btn = Gtk.Button(_('Add movement'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - self.add_btn.connect('clicked', self._clicked_add) + self.add_btn = Gtk.Button(_("Add movement"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + self.add_btn.connect("clicked", self._clicked_add) self.widgets[self.add_btn] = (1, 1, 1, 1) def _create_field(self): @@ -1676,20 +1669,20 @@ def _create_field(self): for g in self.MOUSE_GESTURE_NAMES: field.append(g, g) CompletionEntry.add_completion_to_entry(field.get_child(), self.MOVE_NAMES) - field.connect('changed', self._on_update) + field.connect("changed", self._on_update) self.fields.append(field) self.widgets[field] = (len(self.fields) - 1, 1, 1, 1) return field def _create_del_btn(self): - btn = Gtk.Button(_('Delete'), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) + btn = Gtk.Button(_("Delete"), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) self.del_btns.append(btn) self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) - btn.connect('clicked', self._clicked_del, len(self.del_btns) - 1) + btn.connect("clicked", self._clicked_del, len(self.del_btns) - 1) return btn def _clicked_add(self, _btn): - self.component.__init__(self.collect_value() + [''], warn=False) + self.component.__init__(self.collect_value() + [""], warn=False) self.show(self.component, editable=True) self.fields[len(self.component.movements) - 1].grab_focus() @@ -1704,8 +1697,11 @@ def _on_update(self, *args): super()._on_update(*args) for i, f in enumerate(self.fields): if f.get_visible(): - icon = 'dialog-warning' if i < len(self.component.movements - ) and self.component.movements[i] not in self.MOVE_NAMES else '' + icon = ( + "dialog-warning" + if i < len(self.component.movements) and self.component.movements[i] not in self.MOVE_NAMES + else "" + ) f.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) def show(self, component, editable): @@ -1732,69 +1728,67 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Mouse Gesture') + return _("Mouse Gesture") @classmethod def right_label(cls, component): if len(component.movements) == 0: - return 'No-op' + return "No-op" else: - return ' -> '.join(component.movements) + return " -> ".join(component.movements) class ActionUI(RuleComponentUI): - CLASS = _DIV.Action @classmethod def icon_name(cls): - return 'go-next' + return "go-next" class KeyPressUI(ActionUI): - CLASS = _DIV.KeyPress - KEY_NAMES = [k[3:] if k.startswith('XK_') else k for k, v in _XK_KEYS.items() if isinstance(v, int)] + KEY_NAMES = [k[3:] if k.startswith("XK_") else k for k, v in _XK_KEYS.items() if isinstance(v, int)] def create_widgets(self): self.widgets = {} self.fields = [] self.label = Gtk.Label( - _('Simulate a chorded key click or depress or release.\nOn Wayland requires write access to /dev/uinput.'), - halign=Gtk.Align.CENTER + _("Simulate a chorded key click or depress or release.\nOn Wayland requires write access to /dev/uinput."), + halign=Gtk.Align.CENTER, ) self.widgets[self.label] = (0, 0, 5, 1) self.del_btns = [] - self.add_btn = Gtk.Button(_('Add key'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - self.add_btn.connect('clicked', self._clicked_add) + self.add_btn = Gtk.Button(_("Add key"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + self.add_btn.connect("clicked", self._clicked_add) self.widgets[self.add_btn] = (1, 1, 1, 1) - self.action_clicked_radio = Gtk.RadioButton.new_with_label_from_widget(None, _('Click')) - self.action_clicked_radio.connect('toggled', self._on_update, CLICK) + self.action_clicked_radio = Gtk.RadioButton.new_with_label_from_widget(None, _("Click")) + self.action_clicked_radio.connect("toggled", self._on_update, CLICK) self.widgets[self.action_clicked_radio] = (0, 3, 1, 1) - self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_clicked_radio, _('Depress')) - self.action_pressed_radio.connect('toggled', self._on_update, DEPRESS) + self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_clicked_radio, _("Depress")) + self.action_pressed_radio.connect("toggled", self._on_update, DEPRESS) self.widgets[self.action_pressed_radio] = (1, 3, 1, 1) - self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _('Release')) - self.action_released_radio.connect('toggled', self._on_update, RELEASE) + self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _("Release")) + self.action_released_radio.connect("toggled", self._on_update, RELEASE) self.widgets[self.action_released_radio] = (2, 3, 1, 1) def _create_field(self): field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - field.connect('changed', self._on_update) + field.connect("changed", self._on_update) self.fields.append(field) self.widgets[field] = (len(self.fields) - 1, 1, 1, 1) return field def _create_del_btn(self): - btn = Gtk.Button(_('Delete'), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) + btn = Gtk.Button(_("Delete"), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) self.del_btns.append(btn) self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) - btn.connect('clicked', self._clicked_del, len(self.del_btns) - 1) + btn.connect("clicked", self._clicked_del, len(self.del_btns) - 1) return btn def _clicked_add(self, _btn): keys, action = self.component.regularize_args(self.collect_value()) - self.component.__init__([keys + [''], action], warn=False) + self.component.__init__([keys + [""], action], warn=False) self.show(self.component, editable=True) self.fields[len(self.component.key_names) - 1].grab_focus() @@ -1809,8 +1803,11 @@ def _on_update(self, *args): super()._on_update(*args) for i, f in enumerate(self.fields): if f.get_visible(): - icon = 'dialog-warning' if i < len(self.component.key_names - ) and self.component.key_names[i] not in self.KEY_NAMES else '' + icon = ( + "dialog-warning" + if i < len(self.component.key_names) and self.component.key_names[i] not in self.KEY_NAMES + else "" + ) f.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) def show(self, component, editable=True): @@ -1819,8 +1816,7 @@ def show(self, component, editable=True): self._create_field() self._create_del_btn() - -# self.widgets[self.add_btn] = (n + 1, 0, 1, 1) + # self.widgets[self.add_btn] = (n + 1, 0, 1, 1) self.widgets[self.add_btn] = (n, 1, 1, 1) super().show(component, editable) for i in range(n): @@ -1835,21 +1831,21 @@ def show(self, component, editable=True): self.del_btns[i].hide() def collect_value(self): - action = CLICK if self.action_clicked_radio.get_active() else \ - DEPRESS if self.action_pressed_radio.get_active() else RELEASE + action = ( + CLICK if self.action_clicked_radio.get_active() else DEPRESS if self.action_pressed_radio.get_active() else RELEASE + ) return [[f.get_text().strip() for f in self.fields if f.get_visible()], action] @classmethod def left_label(cls, component): - return _('Key press') + return _("Key press") @classmethod def right_label(cls, component): - return ' + '.join(component.key_names) + (' (' + component.action + ')' if component.action != CLICK else '') + return " + ".join(component.key_names) + (" (" + component.action + ")" if component.action != CLICK else "") class MouseScrollUI(ActionUI): - CLASS = _DIV.MouseScroll MIN_VALUE = -2000 MAX_VALUE = 2000 @@ -1857,18 +1853,18 @@ class MouseScrollUI(ActionUI): def create_widgets(self): self.widgets = {} self.label = Gtk.Label( - _('Simulate a mouse scroll.\nOn Wayland requires write access to /dev/uinput.'), halign=Gtk.Align.CENTER + _("Simulate a mouse scroll.\nOn Wayland requires write access to /dev/uinput."), halign=Gtk.Align.CENTER ) self.widgets[self.label] = (0, 0, 4, 1) - self.label_x = Gtk.Label(label='x', halign=Gtk.Align.END, valign=Gtk.Align.END, hexpand=True) - self.label_y = Gtk.Label(label='y', halign=Gtk.Align.END, valign=Gtk.Align.END, hexpand=True) + self.label_x = Gtk.Label(label="x", halign=Gtk.Align.END, valign=Gtk.Align.END, hexpand=True) + self.label_y = Gtk.Label(label="y", halign=Gtk.Align.END, valign=Gtk.Align.END, hexpand=True) self.field_x = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) self.field_y = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) for f in [self.field_x, self.field_y]: f.set_halign(Gtk.Align.CENTER) f.set_valign(Gtk.Align.START) - self.field_x.connect('changed', self._on_update) - self.field_y.connect('changed', self._on_update) + self.field_x.connect("changed", self._on_update) + self.field_y.connect("changed", self._on_update) self.widgets[self.label_x] = (0, 1, 1, 1) self.widgets[self.field_x] = (1, 1, 1, 1) self.widgets[self.label_y] = (2, 1, 1, 1) @@ -1893,18 +1889,17 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Mouse scroll') + return _("Mouse scroll") @classmethod def right_label(cls, component): x = y = 0 x = cls.__parse(component.amounts[0] if len(component.amounts) >= 1 else 0) y = cls.__parse(component.amounts[1] if len(component.amounts) >= 2 else 0) - return f'{x}, {y}' + return f"{x}, {y}" class MouseClickUI(ActionUI): - CLASS = _DIV.MouseClick MIN_VALUE = 1 MAX_VALUE = 9 @@ -1914,20 +1909,20 @@ class MouseClickUI(ActionUI): def create_widgets(self): self.widgets = {} self.label = Gtk.Label( - _('Simulate a mouse click.\nOn Wayland requires write access to /dev/uinput.'), halign=Gtk.Align.CENTER + _("Simulate a mouse click.\nOn Wayland requires write access to /dev/uinput."), halign=Gtk.Align.CENTER ) self.widgets[self.label] = (0, 0, 4, 1) - self.label_b = Gtk.Label(label=_('Button'), halign=Gtk.Align.END, valign=Gtk.Align.CENTER, hexpand=True) - self.label_c = Gtk.Label(label=_('Count and Action'), halign=Gtk.Align.END, valign=Gtk.Align.CENTER, hexpand=True) + self.label_b = Gtk.Label(label=_("Button"), halign=Gtk.Align.END, valign=Gtk.Align.CENTER, hexpand=True) + self.label_c = Gtk.Label(label=_("Count and Action"), halign=Gtk.Align.END, valign=Gtk.Align.CENTER, hexpand=True) self.field_b = CompletionEntry(self.BUTTONS) self.field_c = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) self.field_d = CompletionEntry(self.ACTIONS) for f in [self.field_b, self.field_c]: f.set_halign(Gtk.Align.CENTER) f.set_valign(Gtk.Align.START) - self.field_b.connect('changed', self._on_update) - self.field_c.connect('changed', self._on_update) - self.field_d.connect('changed', self._on_update) + self.field_b.connect("changed", self._on_update) + self.field_c.connect("changed", self._on_update) + self.field_d.connect("changed", self._on_update) self.widgets[self.label_b] = (0, 1, 1, 1) self.widgets[self.field_b] = (1, 1, 1, 1) self.widgets[self.label_c] = (2, 1, 1, 1) @@ -1948,14 +1943,14 @@ def show(self, component, editable): def collect_value(self): b, c, d = self.field_b.get_text(), int(self.field_c.get_value()), self.field_d.get_text() if b not in self.BUTTONS: - b = 'unknown' + b = "unknown" if d != CLICK: c = d return [b, c] @classmethod def left_label(cls, component): - return _('Mouse click') + return _("Mouse click") @classmethod def right_label(cls, component): @@ -1963,37 +1958,36 @@ def right_label(cls, component): class ExecuteUI(ActionUI): - CLASS = _DIV.Execute def create_widgets(self): self.widgets = {} - self.label = Gtk.Label(_('Execute a command with arguments.'), halign=Gtk.Align.CENTER) + self.label = Gtk.Label(_("Execute a command with arguments."), halign=Gtk.Align.CENTER) self.widgets[self.label] = (0, 0, 5, 1) self.fields = [] - self.add_btn = Gtk.Button(_('Add argument'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + self.add_btn = Gtk.Button(_("Add argument"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) self.del_btns = [] - self.add_btn.connect('clicked', self._clicked_add) + self.add_btn.connect("clicked", self._clicked_add) self.widgets[self.add_btn] = (1, 1, 1, 1) def _create_field(self): field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) field.set_size_request(150, 0) - field.connect('changed', self._on_update) + field.connect("changed", self._on_update) self.fields.append(field) self.widgets[field] = (len(self.fields) - 1, 1, 1, 1) return field def _create_del_btn(self): - btn = Gtk.Button(_('Delete'), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) + btn = Gtk.Button(_("Delete"), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) btn.set_size_request(150, 0) self.del_btns.append(btn) self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) - btn.connect('clicked', self._clicked_del, len(self.del_btns) - 1) + btn.connect("clicked", self._clicked_del, len(self.del_btns) - 1) return btn def _clicked_add(self, *_args): - self.component.__init__(self.collect_value() + [''], warn=False) + self.component.__init__(self.collect_value() + [""], warn=False) self.show(self.component, editable=True) self.fields[len(self.component.args) - 1].grab_focus() @@ -2026,11 +2020,11 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Execute') + return _("Execute") @classmethod def right_label(cls, component): - return ' '.join([shlex_quote(a) for a in component.args]) + return " ".join([shlex_quote(a) for a in component.args]) def _from_named_ints(v, all_values): @@ -2041,27 +2035,27 @@ def _from_named_ints(v, all_values): class SetValueControl(Gtk.HBox): - def __init__(self, on_change, *args, accept_toggle=True, **kwargs): super().__init__(*args, **kwargs) self.on_change = on_change - self.toggle_widget = SmartComboBox([ - *([('Toggle', _('Toggle'), '~')] if accept_toggle else []), (True, _('True'), 'True', 'yes', 'on', 't', 'y'), - (False, _('False'), 'False', 'no', 'off', 'f', 'n') - ], - case_insensitive=True) - self.toggle_widget.connect('changed', self._changed) + self.toggle_widget = SmartComboBox( + [ + *([("Toggle", _("Toggle"), "~")] if accept_toggle else []), + (True, _("True"), "True", "yes", "on", "t", "y"), + (False, _("False"), "False", "no", "off", "f", "n"), + ], + case_insensitive=True, + ) + self.toggle_widget.connect("changed", self._changed) self.range_widget = Gtk.SpinButton.new_with_range(0, 0xFFFF, 1) - self.range_widget.connect('value-changed', self._changed) - self.choice_widget = SmartComboBox([], - completion=True, - has_entry=True, - case_insensitive=True, - replace_with_default_name=True) - self.choice_widget.connect('changed', self._changed) + self.range_widget.connect("value-changed", self._changed) + self.choice_widget = SmartComboBox( + [], completion=True, has_entry=True, case_insensitive=True, replace_with_default_name=True + ) + self.choice_widget.connect("changed", self._changed) self.sub_key_widget = SmartComboBox([]) - self.sub_key_widget.connect('changed', self._changed) - self.unsupported_label = Gtk.Label(_('Unsupported setting')) + self.sub_key_widget.connect("changed", self._changed) + self.unsupported_label = Gtk.Label(_("Unsupported setting")) self.pack_start(self.sub_key_widget, False, False, 0) self.sub_key_widget.set_hexpand(False) self.sub_key_widget.set_size_request(120, 0) @@ -2076,14 +2070,17 @@ def __init__(self, on_change, *args, accept_toggle=True, **kwargs): def _changed(self, widget, *args): if widget.get_visible(): value = self.get_value() - if self.current_kind == 'choice': + if self.current_kind == "choice": value = widget.get_value() - icon = 'dialog-warning' if widget._allowed_values and (value not in widget._allowed_values) else '' + icon = "dialog-warning" if widget._allowed_values and (value not in widget._allowed_values) else "" widget.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - elif self.current_kind == 'range_with_key' and widget == self.sub_key_widget: + elif self.current_kind == "range_with_key" and widget == self.sub_key_widget: key = self.sub_key_widget.get_value() - selected_item = next((item for item in self.sub_key_range_items - if key == item.id), None) if self.sub_key_range_items else None + selected_item = ( + next((item for item in self.sub_key_range_items if key == item.id), None) + if self.sub_key_range_items + else None + ) (minimum, maximum) = (selected_item.minimum, selected_item.maximum) if selected_item else (0, 0xFFFF) self.range_widget.set_range(minimum, maximum) self.on_change(value) @@ -2093,63 +2090,64 @@ def _hide_all(self): w.hide() def get_value(self): - if self.current_kind == 'toggle': + if self.current_kind == "toggle": return self.toggle_widget.get_value() - if self.current_kind == 'range': + if self.current_kind == "range": return int(self.range_widget.get_value()) - if self.current_kind == 'range_with_key': + if self.current_kind == "range_with_key": return {self.sub_key_widget.get_value(): int(self.range_widget.get_value())} - if self.current_kind == 'choice': + if self.current_kind == "choice": return self.choice_widget.get_value() return self.unsupp_value def set_value(self, value): - if self.current_kind == 'toggle': - self.toggle_widget.set_value(value if value is not None else '') - elif self.current_kind == 'range': + if self.current_kind == "toggle": + self.toggle_widget.set_value(value if value is not None else "") + elif self.current_kind == "range": minimum, maximum = self.range_widget.get_range() try: v = round(float(value)) except (ValueError, TypeError): v = minimum self.range_widget.set_value(max(minimum, min(maximum, v))) - elif self.current_kind == 'range_with_key': + elif self.current_kind == "range_with_key": if not (isinstance(value, dict) and len(value) == 1): value = {None: None} key = next(iter(value.keys())) - selected_item = next((item for item in self.sub_key_range_items - if key == item.id), None) if self.sub_key_range_items else None + selected_item = ( + next((item for item in self.sub_key_range_items if key == item.id), None) if self.sub_key_range_items else None + ) (minimum, maximum) = (selected_item.minimum, selected_item.maximum) if selected_item else (0, 0xFFFF) try: v = round(float(next(iter(value.values())))) except (ValueError, TypeError): v = minimum - self.sub_key_widget.set_value(key or '') + self.sub_key_widget.set_value(key or "") self.range_widget.set_value(max(minimum, min(maximum, v))) - elif self.current_kind == 'choice': + elif self.current_kind == "choice": self.choice_widget.set_value(value) else: self.unsupp_value = value - if value is None or value == '': # reset all + if value is None or value == "": # reset all self.range_widget.set_range(0x0000, 0xFFFF) self.range_widget.set_value(0) - self.toggle_widget.set_active_id('') - self.sub_key_widget.set_value('') - self.choice_widget.set_value('') + self.toggle_widget.set_active_id("") + self.sub_key_widget.set_value("") + self.choice_widget.set_value("") def make_toggle(self): - self.current_kind = 'toggle' + self.current_kind = "toggle" self._hide_all() self.toggle_widget.show() def make_range(self, minimum, maximum): - self.current_kind = 'range' + self.current_kind = "range" self._hide_all() self.range_widget.set_range(minimum, maximum) self.range_widget.show() def make_range_with_key(self, items, labels=None): - self.current_kind = 'range_with_key' + self.current_kind = "range_with_key" self._hide_all() self.sub_key_range_items = items or None if not labels: @@ -2162,7 +2160,7 @@ def make_range_with_key(self, items, labels=None): def make_choice(self, values, extra=None): # if extra is not in values, it is ignored - self.current_kind = 'choice' + self.current_kind = "choice" self._hide_all() sort_key = int if all((v == extra or str(v).isdigit()) for v in values) else str if extra is not None and extra in values: @@ -2182,7 +2180,6 @@ def make_unsupported(self): def _all_settings(): settings = {} for s in sorted(_SETTINGS, key=lambda setting: setting.label): - if s.name not in settings: settings[s.name] = [s] else: @@ -2190,7 +2187,7 @@ def _all_settings(): prev_kind = prev_setting.validator_class.kind if prev_kind != s.validator_class.kind: logger.warning( - 'ignoring setting {} - same name of {}, but different kind ({} != {})'.format( + "ignoring setting {} - same name of {}, but different kind ({} != {})".format( s.__name__, prev_setting.__name__, prev_kind, s.validator_class.kind ) ) @@ -2200,33 +2197,35 @@ def _all_settings(): class _DeviceUI: - label_text = '' + label_text = "" def show(self, component, editable): super().show(component, editable) with self.ignore_changes(): same = not component.devID device = _all_devices[component.devID] - self.device_field.set_value(device.id if device else '' if same else component.devID or '') + self.device_field.set_value(device.id if device else "" if same else component.devID or "") def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) self.label.set_text(self.label_text) self.widgets[self.label] = (0, 0, 5, 1) - lbl = Gtk.Label(_('Device'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) + lbl = Gtk.Label(_("Device"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) self.widgets[lbl] = (0, 1, 1, 1) - self.device_field = SmartComboBox([], - completion=True, - has_entry=True, - blank=_('Originating device'), - case_insensitive=True, - replace_with_default_name=True) - self.device_field.set_value('') + self.device_field = SmartComboBox( + [], + completion=True, + has_entry=True, + blank=_("Originating device"), + case_insensitive=True, + replace_with_default_name=True, + ) + self.device_field.set_value("") self.device_field.set_valign(Gtk.Align.CENTER) self.device_field.set_size_request(400, 0) # self.device_field.connect('changed', self._changed_device) - self.device_field.connect('changed', self._on_update) + self.device_field.connect("changed", self._on_update) self.widgets[self.device_field] = (1, 1, 1, 1) def update_devices(self): @@ -2238,7 +2237,7 @@ def _update_device_list(self): def collect_value(self): device_str = self.device_field.get_value() - same = device_str in ['', _('Originating device')] + same = device_str in ["", _("Originating device")] device = None if same else _all_devices[device_str] device_value = device.id if device else None if same else device_str return device_value @@ -2250,37 +2249,34 @@ def right_label(cls, component): class ActiveUI(_DeviceUI, ConditionUI): - CLASS = _DIV.Active - label_text = _('Device is active and its settings can be changed.') + label_text = _("Device is active and its settings can be changed.") @classmethod def left_label(cls, component): - return _('Active') + return _("Active") class DeviceUI(_DeviceUI, ConditionUI): - CLASS = _DIV.Device - label_text = _('Device that originated the current notification.') + label_text = _("Device that originated the current notification.") @classmethod def left_label(cls, component): - return _('Device') + return _("Device") class HostUI(ConditionUI): - CLASS = _DIV.Host def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Name of host computer.')) + self.label.set_text(_("Name of host computer.")) self.widgets[self.label] = (0, 0, 1, 1) self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) self.field.set_size_request(600, 0) - self.field.connect('changed', self._on_update) + self.field.connect("changed", self._on_update) self.widgets[self.field] = (0, 1, 1, 1) def show(self, component, editable): @@ -2293,7 +2289,7 @@ def collect_value(self): @classmethod def left_label(cls, component): - return _('Host') + return _("Host") @classmethod def right_label(cls, component): @@ -2301,17 +2297,15 @@ def right_label(cls, component): class _SettingWithValueUI: - ALL_SETTINGS = _all_settings() MULTIPLE = [_SKIND.multiple_toggle, _SKIND.map_choice, _SKIND.multiple_range] ACCEPT_TOGGLE = True - label_text = '' + label_text = "" def create_widgets(self): - self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) @@ -2319,31 +2313,33 @@ def create_widgets(self): self.widgets[self.label] = (0, 0, 5, 1) m = 20 - lbl = Gtk.Label(_('Device'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, margin_top=m) + lbl = Gtk.Label(_("Device"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, margin_top=m) self.widgets[lbl] = (0, 1, 1, 1) - self.device_field = SmartComboBox([], - completion=True, - has_entry=True, - blank=_('Originating device'), - case_insensitive=True, - replace_with_default_name=True) - self.device_field.set_value('') + self.device_field = SmartComboBox( + [], + completion=True, + has_entry=True, + blank=_("Originating device"), + case_insensitive=True, + replace_with_default_name=True, + ) + self.device_field.set_value("") self.device_field.set_valign(Gtk.Align.CENTER) self.device_field.set_size_request(400, 0) self.device_field.set_margin_top(m) - self.device_field.connect('changed', self._changed_device) - self.device_field.connect('changed', self._on_update) + self.device_field.connect("changed", self._changed_device) + self.device_field.connect("changed", self._on_update) self.widgets[self.device_field] = (1, 1, 1, 1) - lbl = Gtk.Label(_('Setting'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False) + lbl = Gtk.Label(_("Setting"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False) self.widgets[lbl] = (0, 2, 1, 1) self.setting_field = SmartComboBox([(s[0].name, s[0].label) for s in self.ALL_SETTINGS.values()]) self.setting_field.set_valign(Gtk.Align.CENTER) - self.setting_field.connect('changed', self._changed_setting) - self.setting_field.connect('changed', self._on_update) + self.setting_field.connect("changed", self._changed_setting) + self.setting_field.connect("changed", self._on_update) self.widgets[self.setting_field] = (1, 2, 1, 1) - self.value_lbl = Gtk.Label(_('Value'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False) + self.value_lbl = Gtk.Label(_("Value"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False) self.widgets[self.value_lbl] = (2, 2, 1, 1) self.value_field = SetValueControl(self._on_update, accept_toggle=self.ACCEPT_TOGGLE) self.value_field.set_valign(Gtk.Align.CENTER) @@ -2351,20 +2347,18 @@ def create_widgets(self): self.widgets[self.value_field] = (3, 2, 1, 1) self.key_lbl = Gtk.Label( - _('Item'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False, margin_top=m + _("Item"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False, margin_top=m ) self.key_lbl.hide() self.widgets[self.key_lbl] = (2, 1, 1, 1) - self.key_field = SmartComboBox([], - has_entry=True, - completion=True, - case_insensitive=True, - replace_with_default_name=True) + self.key_field = SmartComboBox( + [], has_entry=True, completion=True, case_insensitive=True, replace_with_default_name=True + ) self.key_field.set_margin_top(m) self.key_field.hide() self.key_field.set_valign(Gtk.Align.CENTER) - self.key_field.connect('changed', self._changed_key) - self.key_field.connect('changed', self._on_update) + self.key_field.connect("changed", self._changed_key) + self.key_field.connect("changed", self._on_update) self.widgets[self.key_field] = (3, 1, 1, 1) @classmethod @@ -2383,10 +2377,10 @@ def _all_choices(cls, setting): # choice and map-choice setting = type(setting) if isinstance(setting, type) and issubclass(setting, _Setting): choices = UnsortedNamedInts() - universe = getattr(setting, 'choices_universe', None) + universe = getattr(setting, "choices_universe", None) if universe: choices |= universe - extra = getattr(setting, 'choices_extra', None) + extra = getattr(setting, "choices_extra", None) if extra is not None: choices |= NamedInts(**{str(extra): int(extra)}) return choices, extra @@ -2413,7 +2407,7 @@ def _setting_attributes(cls, setting_name, device=None): if kind in cls.MULTIPLE: keys = UnsortedNamedInts() for s in settings: - universe = getattr(s, 'keys_universe' if kind == _SKIND.map_choice else 'choices_universe', None) + universe = getattr(s, "keys_universe" if kind == _SKIND.map_choice else "choices_universe", None) if universe: keys |= universe # only one key per number is used @@ -2468,7 +2462,7 @@ def _update_key_list(self, setting_name, device=None): self.key_lbl.set_visible(multiple) if not multiple: return - labels = getattr(setting, '_labels', {}) + labels = getattr(setting, "_labels", {}) def item(k): lbl = labels.get(k, None) @@ -2503,7 +2497,7 @@ def _update_value_list(self, setting_name, device=None, key=None): supported_values = None if device_setting: val = device_setting._validator - choices = getattr(val, 'choices', None) or None + choices = getattr(val, "choices", None) or None if kind == _SKIND.choice: supported_values = choices elif kind == _SKIND.map_choice and isinstance(choices, dict): @@ -2514,8 +2508,8 @@ def _update_value_list(self, setting_name, device=None, key=None): self.value_field.make_range(val_class.min_value, val_class.max_value) elif kind == _SKIND.multiple_range: self.value_field.make_range_with_key( - getattr(setting, 'sub_items_universe', {}).get(key, {}) if setting else {}, - getattr(setting, '_labels_sub', None) if setting else None + getattr(setting, "sub_items_universe", {}).get(key, {}) if setting else {}, + getattr(setting, "_labels_sub", None) if setting else None, ) else: self.value_field.make_unsupported() @@ -2528,22 +2522,24 @@ def _update_validation(self): device_str = self.device_field.get_value() device = _all_devices[device_str] if device_str and not device: - icon = 'dialog-question' if len(device_str) == 8 and all( - c in string.hexdigits for c in device_str - ) else 'dialog-warning' + icon = ( + "dialog-question" + if len(device_str) == 8 and all(c in string.hexdigits for c in device_str) + else "dialog-warning" + ) else: - icon = '' + icon = "" self.device_field.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) setting_name = self.setting_field.get_value() setting, val_class, kind, keys = self._setting_attributes(setting_name, device) multiple = kind in self.MULTIPLE if multiple: key = self.key_field.get_value(invalid_as_str=False, accept_hidden=False) - icon = 'dialog-warning' if key is None else '' + icon = "dialog-warning" if key is None else "" self.key_field.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) if kind in (_SKIND.choice, _SKIND.map_choice): value = self.value_field.choice_widget.get_value(invalid_as_str=False, accept_hidden=False) - icon = 'dialog-warning' if value is None else '' + icon = "dialog-warning" if value is None else "" self.value_field.choice_widget.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) def show(self, component, editable): @@ -2552,21 +2548,21 @@ def show(self, component, editable): device_str = next(a, None) same = not device_str device = _all_devices[device_str] - self.device_field.set_value(device.id if device else '' if same else device_str or '') - setting_name = next(a, '') + self.device_field.set_value(device.id if device else "" if same else device_str or "") + setting_name = next(a, "") setting, _v, kind, keys = self._setting_attributes(setting_name, device) - self.setting_field.set_value(setting.name if setting else '') + self.setting_field.set_value(setting.name if setting else "") self._changed_setting() key = None if kind in self.MULTIPLE or kind is None and len(self.component.args) > 3: - key = _from_named_ints(next(a, ''), keys) + key = _from_named_ints(next(a, ""), keys) self.key_field.set_value(key) - self.value_field.set_value(next(a, '')) + self.value_field.set_value(next(a, "")) self._update_validation() def collect_value(self): device_str = self.device_field.get_value() - same = device_str in ['', _('Originating device')] + same = device_str in ["", _("Originating device")] device = None if same else _all_devices[device_str] device_value = device.id if device else None if same else device_str setting_name = self.setting_field.get_value() @@ -2584,7 +2580,7 @@ def right_label(cls, component): a = iter(component.args) device_str = next(a, None) device = None if not device_str else _all_devices[device_str] - device_disp = _('Originating device') if not device_str else device.display_name if device else shlex_quote(device_str) + device_disp = _("Originating device") if not device_str else device.display_name if device else shlex_quote(device_str) setting_name = next(a, None) setting, val_class, kind, keys = cls._setting_attributes(setting_name, device) device_setting = (device.settings if device else {}).get(setting_name, None) @@ -2592,7 +2588,7 @@ def right_label(cls, component): if kind in cls.MULTIPLE: key = next(a, None) key = _from_named_ints(key, keys) if keys else key - key_label = getattr(setting, '_labels', {}).get(key, [None])[0] if setting else None + key_label = getattr(setting, "_labels", {}).get(key, [None])[0] if setting else None disp.append(key_label or key) value = next(a, None) if setting and (kind in (_SKIND.choice, _SKIND.map_choice)): @@ -2600,7 +2596,7 @@ def right_label(cls, component): supported_values = None if device_setting: val = device_setting._validator - choices = getattr(val, 'choices', None) or None + choices = getattr(val, "choices", None) or None if kind == _SKIND.choice: supported_values = choices elif kind == _SKIND.map_choice and isinstance(choices, dict): @@ -2612,21 +2608,20 @@ def right_label(cls, component): disp.append(value) elif kind == _SKIND.multiple_range and isinstance(value, dict) and len(value) == 1: k, v = next(iter(value.items())) - k = (getattr(setting, '_labels_sub', {}).get(k, (None, ))[0] if setting else None) or k - disp.append(f'{k}={v}') + k = (getattr(setting, "_labels_sub", {}).get(k, (None,))[0] if setting else None) or k + disp.append(f"{k}={v}") elif kind in (_SKIND.toggle, _SKIND.multiple_toggle): disp.append(_(str(value))) else: disp.append(value) - return device_disp + ' ' + ' '.join(map(lambda s: shlex_quote(str(s)), [*disp, *a])) + return device_disp + " " + " ".join(map(lambda s: shlex_quote(str(s)), [*disp, *a])) class SetUI(_SettingWithValueUI, ActionUI): - CLASS = _DIV.Set ACCEPT_TOGGLE = True - label_text = _('Change setting on device') + label_text = _("Change setting on device") def show(self, component, editable): ActionUI.show(self, component, editable) @@ -2639,11 +2634,10 @@ def _on_update(self, *_args): class SettingUI(_SettingWithValueUI, ConditionUI): - CLASS = _DIV.Setting ACCEPT_TOGGLE = False - label_text = _('Setting on device') + label_text = _("Setting on device") def show(self, component, editable): ConditionUI.show(self, component, editable) diff --git a/lib/solaar/ui/icons.py b/lib/solaar/ui/icons.py index 4c258cc94a..9be7e6ab80 100644 --- a/lib/solaar/ui/icons.py +++ b/lib/solaar/ui/icons.py @@ -18,10 +18,10 @@ import logging -import solaar.gtk as gtk - from gi.repository import Gtk +import solaar.gtk as gtk + logger = logging.getLogger(__name__) # @@ -29,12 +29,12 @@ # _LARGE_SIZE = 64 -Gtk.IconSize.LARGE = Gtk.icon_size_register('large', _LARGE_SIZE, _LARGE_SIZE) +Gtk.IconSize.LARGE = Gtk.icon_size_register("large", _LARGE_SIZE, _LARGE_SIZE) # Gtk.IconSize.XLARGE = Gtk.icon_size_register('x-large', _LARGE_SIZE * 2, _LARGE_SIZE * 2) -TRAY_INIT = 'solaar-init' -TRAY_OKAY = 'solaar' -TRAY_ATTENTION = 'solaar-attention' +TRAY_INIT = "solaar-init" +TRAY_OKAY = "solaar" +TRAY_ATTENTION = "solaar-attention" _default_theme = None @@ -46,18 +46,18 @@ def _init_icon_paths(): _default_theme = Gtk.IconTheme.get_default() if logger.isEnabledFor(logging.DEBUG): - logger.debug('icon theme paths: %s', _default_theme.get_search_path()) + logger.debug("icon theme paths: %s", _default_theme.get_search_path()) - if gtk.battery_icons_style == 'symbolic': + if gtk.battery_icons_style == "symbolic": global TRAY_OKAY TRAY_OKAY = TRAY_INIT # use monochrome tray icon - if not _default_theme.has_icon('battery-good-symbolic'): - logger.warning('failed to detect symbolic icons') - gtk.battery_icons_style = 'regular' - if gtk.battery_icons_style == 'regular': - if not _default_theme.has_icon('battery-good'): - logger.warning('failed to detect icons') - gtk.battery_icons_style = 'solaar' + if not _default_theme.has_icon("battery-good-symbolic"): + logger.warning("failed to detect symbolic icons") + gtk.battery_icons_style = "regular" + if gtk.battery_icons_style == "regular": + if not _default_theme.has_icon("battery-good"): + logger.warning("failed to detect icons") + gtk.battery_icons_style = "solaar" # @@ -68,10 +68,10 @@ def _init_icon_paths(): def battery(level=None, charging=False): icon_name = _battery_icon_name(level, charging) if not _default_theme.has_icon(icon_name): - logger.warning('icon %s not found in current theme', icon_name) + logger.warning("icon %s not found in current theme", icon_name) return TRAY_OKAY # use Solaar icon if battery icon not available elif logger.isEnabledFor(logging.DEBUG): - logger.debug('battery icon for %s:%s = %s', level, charging, icon_name) + logger.debug("battery icon for %s:%s = %s", level, charging, icon_name) return icon_name @@ -85,11 +85,13 @@ def _battery_icon_name(level, charging): _init_icon_paths() if level is None or level < 0: - return 'battery-missing' + ('-symbolic' if gtk.battery_icons_style == 'symbolic' else '') + return "battery-missing" + ("-symbolic" if gtk.battery_icons_style == "symbolic" else "") - level_name = _first_res(level, ((90, 'full'), (30, 'good'), (20, 'low'), (5, 'caution'), (0, 'empty'))) - return 'battery-%s%s%s' % ( - level_name, '-charging' if charging else '', '-symbolic' if gtk.battery_icons_style == 'symbolic' else '' + level_name = _first_res(level, ((90, "full"), (30, "good"), (20, "low"), (5, "caution"), (0, "empty"))) + return "battery-%s%s%s" % ( + level_name, + "-charging" if charging else "", + "-symbolic" if gtk.battery_icons_style == "symbolic" else "", ) @@ -100,8 +102,8 @@ def _battery_icon_name(level, charging): def lux(level=None): if level is None or level < 0: - return 'light_unknown' - return 'solaar-light_%03d' % (20 * ((level + 50) // 100)) + return "light_unknown" + return "solaar-light_%03d" % (20 * ((level + 50) // 100)) # @@ -111,7 +113,7 @@ def lux(level=None): _ICON_SETS = {} -def device_icon_set(name='_', kind=None): +def device_icon_set(name="_", kind=None): icon_set = _ICON_SETS.get(name) if icon_set is None: icon_set = Gtk.IconSet.new() @@ -119,17 +121,17 @@ def device_icon_set(name='_', kind=None): # names of possible icons, in reverse order of likelihood # the theme will hopefully pick up the most appropriate - names = ['preferences-desktop-peripherals'] + names = ["preferences-desktop-peripherals"] if kind: - if str(kind) == 'numpad': - names += ('input-keyboard', 'input-dialpad') - elif str(kind) == 'touchpad': - names += ('input-mouse', 'input-tablet') - elif str(kind) == 'trackball': - names += ('input-mouse', ) - elif str(kind) == 'headset': - names += ('audio-headphones', 'audio-headset') - names += ('input-' + str(kind), ) + if str(kind) == "numpad": + names += ("input-keyboard", "input-dialpad") + elif str(kind) == "touchpad": + names += ("input-mouse", "input-tablet") + elif str(kind) == "trackball": + names += ("input-mouse",) + elif str(kind) == "headset": + names += ("audio-headphones", "audio-headset") + names += ("input-" + str(kind),) # names += (name.replace(' ', '-'),) source = Gtk.IconSource.new() @@ -174,4 +176,4 @@ def icon_file(name, size=_LARGE_SIZE): # logger.debug("icon %s(%d) => %s", name, size, file_name) return file_name - logger.warning('icon %s(%d) not found in current theme', name, size) + logger.warning("icon %s(%d) not found in current theme", name, size) diff --git a/lib/solaar/ui/notify.py b/lib/solaar/ui/notify.py index c858a45cc8..9edb3903b8 100644 --- a/lib/solaar/ui/notify.py +++ b/lib/solaar/ui/notify.py @@ -33,7 +33,8 @@ try: import gi - gi.require_version('Notify', '0.7') + + gi.require_version("Notify", "0.7") # this import is allowed to fail, in which case the entire feature is unavailable from gi.repository import GLib, Notify @@ -44,7 +45,6 @@ available = False if available: - # cache references to shown notifications here, so if another status comes # while its notification is still visible we don't create another one _notifications = {} @@ -55,18 +55,18 @@ def init(): if available: if not Notify.is_initted(): if logger.isEnabledFor(logging.INFO): - logger.info('starting desktop notifications') + logger.info("starting desktop notifications") try: return Notify.init(NAME) except Exception: - logger.exception('initializing desktop notifications') + logger.exception("initializing desktop notifications") available = False return available and Notify.is_initted() def uninit(): if available and Notify.is_initted(): if logger.isEnabledFor(logging.INFO): - logger.info('stopping desktop notifications') + logger.info("stopping desktop notifications") _notifications.clear() Notify.uninit() @@ -92,14 +92,14 @@ def alert(reason, icon=None): n.update(NAME, reason, icon_file) n.set_urgency(Notify.Urgency.NORMAL) - n.set_hint('desktop-entry', GLib.Variant('s', NAME.lower())) + n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower())) try: # if logger.isEnabledFor(logging.DEBUG): # logger.debug("showing %s", n) n.show() except Exception: - logger.exception('showing %s', n) + logger.exception("showing %s", n) def show(dev, reason=None, icon=None, progress=None): """Show a notification with title and text. @@ -116,11 +116,11 @@ def show(dev, reason=None, icon=None, progress=None): if reason: message = reason elif dev.status is None: - message = _('unpaired') + message = _("unpaired") elif bool(dev.status): - message = dev.status.to_string() or _('connected') + message = dev.status.to_string() or _("connected") else: - message = _('offline') + message = _("offline") # we need to use the filename here because the notifications daemon # is an external application that does not know about our icon sets @@ -129,16 +129,16 @@ def show(dev, reason=None, icon=None, progress=None): n.update(summary, message, icon_file) urgency = Notify.Urgency.LOW if dev.status else Notify.Urgency.NORMAL n.set_urgency(urgency) - n.set_hint('desktop-entry', GLib.Variant('s', NAME.lower())) + n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower())) if progress: - n.set_hint('value', GLib.Variant('i', progress)) + n.set_hint("value", GLib.Variant("i", progress)) try: # if logger.isEnabledFor(logging.DEBUG): # logger.debug("showing %s", n) n.show() except Exception: - logger.exception('showing %s', n) + logger.exception("showing %s", n) else: init = lambda: False diff --git a/lib/solaar/ui/pair_window.py b/lib/solaar/ui/pair_window.py index 4664a8d5de..92bce672dd 100644 --- a/lib/solaar/ui/pair_window.py +++ b/lib/solaar/ui/pair_window.py @@ -21,6 +21,7 @@ from gi.repository import GLib, Gtk from logitech_receiver import hidpp10 as _hidpp10 from logitech_receiver.status import KEYS as _K + from solaar.i18n import _, ngettext from . import icons as _icons @@ -70,7 +71,7 @@ def _check_lock_state(assistant, receiver, count=2): if not assistant.is_drawable(): if logger.isEnabledFor(logging.DEBUG): - logger.debug('assistant %s destroyed, bailing out', assistant) + logger.debug("assistant %s destroyed, bailing out", assistant) return False if receiver.status.get(_K.ERROR): @@ -94,7 +95,7 @@ def _check_lock_state(assistant, receiver, count=2): ): return True else: - _pairing_failed(assistant, receiver, 'failed to open pairing lock') + _pairing_failed(assistant, receiver, "failed to open pairing lock") return False elif address and receiver.status.device_passkey and not passcode: passcode = receiver.status.device_passkey @@ -106,7 +107,7 @@ def _check_lock_state(assistant, receiver, count=2): # the actual device notification may arrive later so have a little patience GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, receiver, count - 1) else: - _pairing_failed(assistant, receiver, 'failed to open pairing lock') + _pairing_failed(assistant, receiver, "failed to open pairing lock") return False return True @@ -114,20 +115,20 @@ def _check_lock_state(assistant, receiver, count=2): def _show_passcode(assistant, receiver, passkey): if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s show passkey: %s', receiver, passkey) + logger.debug("%s show passkey: %s", receiver, passkey) name = receiver.status.device_name authentication = receiver.status.device_authentication - intro_text = _('%(receiver_name)s: pair new device') % {'receiver_name': receiver.name} - page_text = _('Enter passcode on %(name)s.') % {'name': name} - page_text += '\n' + intro_text = _("%(receiver_name)s: pair new device") % {"receiver_name": receiver.name} + page_text = _("Enter passcode on %(name)s.") % {"name": name} + page_text += "\n" if authentication & 0x01: - page_text += _('Type %(passcode)s and then press the enter key.') % {'passcode': receiver.status.device_passkey} + page_text += _("Type %(passcode)s and then press the enter key.") % {"passcode": receiver.status.device_passkey} else: - passcode = ', '.join([ - _('right') if bit == '1' else _('left') for bit in f'{int(receiver.status.device_passkey):010b}' - ]) - page_text += _('Press %(code)s\nand then press left and right buttons simultaneously.') % {'code': passcode} - page = _create_page(assistant, Gtk.AssistantPageType.PROGRESS, intro_text, 'preferences-desktop-peripherals', page_text) + passcode = ", ".join( + [_("right") if bit == "1" else _("left") for bit in f"{int(receiver.status.device_passkey):010b}"] + ) + page_text += _("Press %(code)s\nand then press left and right buttons simultaneously.") % {"code": passcode} + page = _create_page(assistant, Gtk.AssistantPageType.PROGRESS, intro_text, "preferences-desktop-peripherals", page_text) assistant.set_page_complete(page, True) assistant.next_page() @@ -135,10 +136,10 @@ def _show_passcode(assistant, receiver, passkey): def _prepare(assistant, page, receiver): index = assistant.get_current_page() if logger.isEnabledFor(logging.DEBUG): - logger.debug('prepare %s %d %s', assistant, index, page) + logger.debug("prepare %s %d %s", assistant, index, page) if index == 0: - if receiver.receiver_kind == 'bolt': + if receiver.receiver_kind == "bolt": if receiver.discover(timeout=_PAIRING_TIMEOUT): assert receiver.status.new_device is None assert receiver.status.get(_K.ERROR) is None @@ -147,7 +148,7 @@ def _prepare(assistant, page, receiver): GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, receiver) assistant.set_page_complete(page, True) else: - GLib.idle_add(_pairing_failed, assistant, receiver, 'discovery did not start') + GLib.idle_add(_pairing_failed, assistant, receiver, "discovery did not start") elif receiver.set_lock(False, timeout=_PAIRING_TIMEOUT): assert receiver.status.new_device is None assert receiver.status.get(_K.ERROR) is None @@ -156,19 +157,19 @@ def _prepare(assistant, page, receiver): GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, receiver) assistant.set_page_complete(page, True) else: - GLib.idle_add(_pairing_failed, assistant, receiver, 'the pairing lock did not open') + GLib.idle_add(_pairing_failed, assistant, receiver, "the pairing lock did not open") else: assistant.remove_page(0) def _finish(assistant, receiver): if logger.isEnabledFor(logging.DEBUG): - logger.debug('finish %s', assistant) + logger.debug("finish %s", assistant) assistant.destroy() receiver.status.new_device = None if receiver.status.lock_open: - if receiver.receiver_kind == 'bolt': - receiver.pair_device('cancel') + if receiver.receiver_kind == "bolt": + receiver.pair_device("cancel") else: receiver.set_lock() if receiver.status.discovering: @@ -179,20 +180,20 @@ def _finish(assistant, receiver): def _pairing_failed(assistant, receiver, error): if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s fail: %s', receiver, error) + logger.debug("%s fail: %s", receiver, error) assistant.commit() - header = _('Pairing failed') + ': ' + _(str(error)) + '.' - if 'timeout' in str(error): - text = _('Make sure your device is within range, and has a decent battery charge.') - elif str(error) == 'device not supported': - text = _('A new device was detected, but it is not compatible with this receiver.') - elif 'many' in str(error): - text = _('More paired devices than receiver can support.') + header = _("Pairing failed") + ": " + _(str(error)) + "." + if "timeout" in str(error): + text = _("Make sure your device is within range, and has a decent battery charge.") + elif str(error) == "device not supported": + text = _("A new device was detected, but it is not compatible with this receiver.") + elif "many" in str(error): + text = _("More paired devices than receiver can support.") else: - text = _('No further details are available about the error.') - _create_page(assistant, Gtk.AssistantPageType.SUMMARY, header, 'dialog-error', text) + text = _("No further details are available about the error.") + _create_page(assistant, Gtk.AssistantPageType.SUMMARY, header, "dialog-error", text) assistant.next_page() assistant.commit() @@ -201,11 +202,11 @@ def _pairing_failed(assistant, receiver, error): def _pairing_succeeded(assistant, receiver, device): assert device if logger.isEnabledFor(logging.DEBUG): - logger.debug('%s success: %s', receiver, device) + logger.debug("%s success: %s", receiver, device) page = _create_page(assistant, Gtk.AssistantPageType.SUMMARY) - header = Gtk.Label(_('Found a new device:')) + header = Gtk.Label(_("Found a new device:")) header.set_alignment(0.5, 0) page.pack_start(header, False, False, 0) @@ -216,21 +217,21 @@ def _pairing_succeeded(assistant, receiver, device): page.pack_start(device_icon, True, True, 0) device_label = Gtk.Label() - device_label.set_markup('%s' % device.name) + device_label.set_markup("%s" % device.name) device_label.set_alignment(0.5, 0) page.pack_start(device_label, True, True, 0) hbox = Gtk.HBox(False, 8) - hbox.pack_start(Gtk.Label(' '), False, False, 0) - hbox.set_property('expand', False) - hbox.set_property('halign', Gtk.Align.CENTER) + hbox.pack_start(Gtk.Label(" "), False, False, 0) + hbox.set_property("expand", False) + hbox.set_property("halign", Gtk.Align.CENTER) page.pack_start(hbox, False, False, 0) def _check_encrypted(dev): if assistant.is_drawable(): if device.status.get(_K.LINK_ENCRYPTED) is False: - hbox.pack_start(Gtk.Image.new_from_icon_name('security-low', Gtk.IconSize.MENU), False, False, 0) - hbox.pack_start(Gtk.Label(_('The wireless link is not encrypted') + '!'), False, False, 0) + hbox.pack_start(Gtk.Image.new_from_icon_name("security-low", Gtk.IconSize.MENU), False, False, 0) + hbox.pack_start(Gtk.Label(_("The wireless link is not encrypted") + "!"), False, False, 0) hbox.show_all() else: return True @@ -251,49 +252,53 @@ def create(receiver): address = name = kind = authentication = passcode = None assistant = Gtk.Assistant() - assistant.set_title(_('%(receiver_name)s: pair new device') % {'receiver_name': receiver.name}) - assistant.set_icon_name('list-add') + assistant.set_title(_("%(receiver_name)s: pair new device") % {"receiver_name": receiver.name}) + assistant.set_icon_name("list-add") assistant.set_size_request(400, 240) assistant.set_resizable(False) - assistant.set_role('pair-device') + assistant.set_role("pair-device") - if receiver.receiver_kind == 'unifying': - page_text = _('Unifying receivers are only compatible with Unifying devices.') - elif receiver.receiver_kind == 'bolt': - page_text = _('Bolt receivers are only compatible with Bolt devices.') + if receiver.receiver_kind == "unifying": + page_text = _("Unifying receivers are only compatible with Unifying devices.") + elif receiver.receiver_kind == "bolt": + page_text = _("Bolt receivers are only compatible with Bolt devices.") else: - page_text = _('Other receivers are only compatible with a few devices.') - page_text += '\n' - page_text += _('The device must not be paired with a nearby powered-on receiver.') - page_text += '\n\n' - - if receiver.receiver_kind == 'bolt': - page_text += _('Press a pairing button or key until the pairing light flashes quickly.') - page_text += '\n' - page_text += _('You may have to first turn the device off and on again.') + page_text = _("Other receivers are only compatible with a few devices.") + page_text += "\n" + page_text += _("The device must not be paired with a nearby powered-on receiver.") + page_text += "\n\n" + + if receiver.receiver_kind == "bolt": + page_text += _("Press a pairing button or key until the pairing light flashes quickly.") + page_text += "\n" + page_text += _("You may have to first turn the device off and on again.") else: - page_text += _('Turn on the device you want to pair.') - page_text += '\n' - page_text += _('If the device is already turned on, turn it off and on again.') + page_text += _("Turn on the device you want to pair.") + page_text += "\n" + page_text += _("If the device is already turned on, turn it off and on again.") if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0: - page_text += ngettext( - '\n\nThis receiver has %d pairing remaining.', '\n\nThis receiver has %d pairings remaining.', - receiver.remaining_pairings() - ) % receiver.remaining_pairings() - page_text += _('\nCancelling at this point will not use up a pairing.') - - intro_text = _('%(receiver_name)s: pair new device') % {'receiver_name': receiver.name} + page_text += ( + ngettext( + "\n\nThis receiver has %d pairing remaining.", + "\n\nThis receiver has %d pairings remaining.", + receiver.remaining_pairings(), + ) + % receiver.remaining_pairings() + ) + page_text += _("\nCancelling at this point will not use up a pairing.") + + intro_text = _("%(receiver_name)s: pair new device") % {"receiver_name": receiver.name} page_intro = _create_page( - assistant, Gtk.AssistantPageType.PROGRESS, intro_text, 'preferences-desktop-peripherals', page_text + assistant, Gtk.AssistantPageType.PROGRESS, intro_text, "preferences-desktop-peripherals", page_text ) spinner = Gtk.Spinner() spinner.set_visible(True) page_intro.pack_end(spinner, True, True, 24) - assistant.connect('prepare', _prepare, receiver) - assistant.connect('cancel', _finish, receiver) - assistant.connect('close', _finish, receiver) + assistant.connect("prepare", _prepare, receiver) + assistant.connect("cancel", _finish, receiver) + assistant.connect("close", _finish, receiver) return assistant diff --git a/lib/solaar/ui/tray.py b/lib/solaar/ui/tray.py index 01f32049aa..3dab465050 100644 --- a/lib/solaar/ui/tray.py +++ b/lib/solaar/ui/tray.py @@ -22,11 +22,13 @@ from time import time as _timestamp import gi -import solaar.gtk as gtk from gi.repository import GLib, Gtk from gi.repository.Gdk import ScrollDirection from logitech_receiver.status import KEYS as _K + +import solaar.gtk as gtk + from solaar import NAME from solaar.i18n import _ @@ -55,13 +57,13 @@ def _create_menu(quit_handler): # per-device menu entries will be generated as-needed - no_receiver = Gtk.MenuItem.new_with_label(_('No supported device found')) + no_receiver = Gtk.MenuItem.new_with_label(_("No supported device found")) no_receiver.set_sensitive(False) menu.append(no_receiver) menu.append(Gtk.SeparatorMenuItem.new()) - menu.append(_make('help-about', _('About %s') % NAME, _show_about_window, stock_id='help-about').create_menu_item()) - menu.append(_make('application-exit', _('Quit %s') % NAME, quit_handler, stock_id='application-exit').create_menu_item()) + menu.append(_make("help-about", _("About %s") % NAME, _show_about_window, stock_id="help-about").create_menu_item()) + menu.append(_make("application-exit", _("Quit %s") % NAME, quit_handler, stock_id="application-exit").create_menu_item()) menu.show_all() @@ -140,26 +142,28 @@ def _scroll(tray_icon, event, direction=None): _picked_device = candidate or _picked_device if logger.isEnabledFor(logging.DEBUG): - logger.debug('scroll: picked %s', _picked_device) + logger.debug("scroll: picked %s", _picked_device) _update_tray_icon() try: try: - gi.require_version('AyatanaAppIndicator3', '0.1') + gi.require_version("AyatanaAppIndicator3", "0.1") from gi.repository import AyatanaAppIndicator3 as AppIndicator3 + ayatana_appindicator_found = True except ValueError: try: - gi.require_version('AppIndicator3', '0.1') + gi.require_version("AppIndicator3", "0.1") from gi.repository import AppIndicator3 + ayatana_appindicator_found = False except ValueError: # treat unavailable versions the same as unavailable packages raise ImportError if logger.isEnabledFor(logging.DEBUG): - logger.debug('using %sAppIndicator3' % ('Ayatana ' if ayatana_appindicator_found else '')) + logger.debug("using %sAppIndicator3" % ("Ayatana " if ayatana_appindicator_found else "")) # Defense against AppIndicator3 bug that treats files in current directory as icon files # https://bugs.launchpad.net/ubuntu/+source/libappindicator/+bug/1363277 @@ -175,7 +179,7 @@ def _icon_file(icon_name): def _create(menu): _icons._init_icon_paths() ind = AppIndicator3.Indicator.new( - 'indicator-solaar', _icon_file(_icons.TRAY_INIT), AppIndicator3.IndicatorCategory.HARDWARE + "indicator-solaar", _icon_file(_icons.TRAY_INIT), AppIndicator3.IndicatorCategory.HARDWARE ) ind.set_title(NAME) ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE) @@ -183,7 +187,7 @@ def _create(menu): # ind.set_label(NAME, NAME) ind.set_menu(menu) - ind.connect('scroll-event', _scroll) + ind.connect("scroll-event", _scroll) return ind @@ -194,19 +198,19 @@ def _show(indicator): indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE) def _update_tray_icon(): - if _picked_device and gtk.battery_icons_style != 'solaar': + if _picked_device and gtk.battery_icons_style != "solaar": _ignore, _ignore, name, device_status = _picked_device battery_level = device_status.get(_K.BATTERY_LEVEL) battery_charging = device_status.get(_K.BATTERY_CHARGING) tray_icon_name = _icons.battery(battery_level, battery_charging) - description = '%s: %s' % (name, device_status.to_string()) + description = "%s: %s" % (name, device_status.to_string()) else: # there may be a receiver, but no peripherals tray_icon_name = _icons.TRAY_OKAY if _devices_info else _icons.TRAY_INIT description_lines = _generate_description_lines() - description = '\n'.join(description_lines).rstrip('\n') + description = "\n".join(description_lines).rstrip("\n") # icon_file = _icons.icon_file(icon_name, _TRAY_ICON_SIZE) _icon.set_icon_full(_icon_file(tray_icon_name), description) @@ -224,18 +228,17 @@ def attention(reason=None): GLib.timeout_add(10 * 1000, _icon.set_status, AppIndicator3.IndicatorStatus.ACTIVE) except ImportError: - if logger.isEnabledFor(logging.DEBUG): - logger.debug('using StatusIcon') + logger.debug("using StatusIcon") def _create(menu): icon = Gtk.StatusIcon.new_from_icon_name(_icons.TRAY_INIT) icon.set_name(NAME) icon.set_title(NAME) icon.set_tooltip_text(NAME) - icon.connect('activate', _window_toggle) - icon.connect('scroll-event', _scroll) - icon.connect('popup-menu', lambda icon, button, time: menu.popup(None, None, icon.position_menu, icon, button, time)) + icon.connect("activate", _window_toggle) + icon.connect("scroll-event", _scroll) + icon.connect("popup-menu", lambda icon, button, time: menu.popup(None, None, icon.position_menu, icon, button, time)) return icon @@ -247,10 +250,10 @@ def _show(icon): def _update_tray_icon(): tooltip_lines = _generate_tooltip_lines() - tooltip = '\n'.join(tooltip_lines).rstrip('\n') + tooltip = "\n".join(tooltip_lines).rstrip("\n") _icon.set_tooltip_markup(tooltip) - if _picked_device and gtk.battery_icons_style != 'solaar': + if _picked_device and gtk.battery_icons_style != "solaar": _ignore, _ignore, name, device_status = _picked_device battery_level = device_status.get(_K.BATTERY_LEVEL) battery_charging = device_status.get(_K.BATTERY_CHARGING) @@ -291,7 +294,7 @@ def attention(reason=None): def _generate_tooltip_lines(): if not _devices_info: - yield '%s: ' % NAME + _('no receiver') + yield "%s: " % NAME + _("no receiver") return yield from _generate_description_lines() @@ -299,7 +302,7 @@ def _generate_tooltip_lines(): def _generate_description_lines(): if not _devices_info: - yield _('no receiver') + yield _("no receiver") return for _ignore, number, name, status in _devices_info: @@ -308,16 +311,16 @@ def _generate_description_lines(): p = status.to_string() if p: # does it have any properties to print? - yield '%s' % name + yield "%s" % name if status: - yield '\t%s' % p + yield "\t%s" % p else: - yield '\t%s (' % p + _('offline') + ')' + yield "\t%s (" % p + _("offline") + ")" else: if status: - yield '%s (' % name + _('no status') + ')' + yield "%s (" % name + _("no status") + ")" else: - yield '%s (' % name + _('offline') + ')' + yield "%s (" % name + _("offline") + ")" def _pick_device_with_lowest_battery(): @@ -337,7 +340,7 @@ def _pick_device_with_lowest_battery(): picked_level = level or 0 if logger.isEnabledFor(logging.DEBUG): - logger.debug('picked device with lowest battery: %s', picked) + logger.debug("picked device with lowest battery: %s", picked) return picked @@ -369,11 +372,11 @@ def _add_device(device): new_device_info = (receiver_path, device.number, device.name, device.status) _devices_info.insert(index, new_device_info) - label_prefix = ' ' - new_menu_item = Gtk.ImageMenuItem.new_with_label((label_prefix if device.number else '') + device.name) + label_prefix = " " + new_menu_item = Gtk.ImageMenuItem.new_with_label((label_prefix if device.number else "") + device.name) new_menu_item.set_image(Gtk.Image()) new_menu_item.show_all() - new_menu_item.connect('activate', _window_popup, receiver_path, device.number) + new_menu_item.connect("activate", _window_popup, receiver_path, device.number) _menu.insert(new_menu_item, index) return index @@ -402,7 +405,7 @@ def _add_receiver(receiver): icon_set = _icons.device_icon_set(receiver.name) new_menu_item.set_image(Gtk.Image().new_from_icon_name(icon_set.names[0], _MENU_ICON_SIZE)) new_menu_item.show_all() - new_menu_item.connect('activate', _window_popup, receiver.path) + new_menu_item.connect("activate", _window_popup, receiver.path) _menu.insert(new_menu_item, index) return 0 @@ -421,7 +424,7 @@ def _remove_receiver(receiver): def _update_menu_item(index, device): if device is None or device.status is None: - logger.warning('updating an inactive device %s, assuming disconnected', device) + logger.warning("updating an inactive device %s, assuming disconnected", device) return None menu_items = _menu.get_children() @@ -431,7 +434,7 @@ def _update_menu_item(index, device): charging = device.status.get(_K.BATTERY_CHARGING) icon_name = _icons.battery(level, charging) - menu_item.set_label((' ' if 0 < device.number <= 6 else '') + device.name + ': ' + device.status.to_string()) + menu_item.set_label((" " if 0 < device.number <= 6 else "") + device.name + ": " + device.status.to_string()) image_widget = menu_item.get_image() image_widget.set_sensitive(bool(device.online)) _update_menu_icon(image_widget, icon_name) diff --git a/lib/solaar/ui/window.py b/lib/solaar/ui/window.py index b94b00f65a..f54afceb75 100644 --- a/lib/solaar/ui/window.py +++ b/lib/solaar/ui/window.py @@ -25,6 +25,7 @@ from logitech_receiver.common import NamedInt as _NamedInt from logitech_receiver.common import NamedInts as _NamedInts from logitech_receiver.status import KEYS as _K + from solaar import NAME from solaar.i18n import _, ngettext @@ -37,7 +38,7 @@ # from solaar import __version__ as VERSION -gi.require_version('Gdk', '3.0') +gi.require_version("Gdk", "3.0") from gi.repository import Gdk, GLib, Gtk # NOQA: E402 logger = logging.getLogger(__name__) @@ -52,10 +53,10 @@ _INFO_ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR _DEVICE_ICON_SIZE = Gtk.IconSize.DND try: - gi.check_version('3.7.4') + gi.check_version("3.7.4") _CAN_SET_ROW_NONE = None except (ValueError, AttributeError): - _CAN_SET_ROW_NONE = '' + _CAN_SET_ROW_NONE = "" # tree model columns _COLUMN = _NamedInts(PATH=0, NUMBER=1, ACTIVE=2, NAME=3, ICON=4, STATUS_TEXT=5, STATUS_ICON=6, DEVICE=7) @@ -78,7 +79,7 @@ def _new_button(label, icon_name=None, icon_size=_NORMAL_BUTTON_ICON_SIZE, toolt c.pack_start(Gtk.Label(label), True, True, 0) b.add(c) if clicked is not None: - b.connect('clicked', clicked) + b.connect("clicked", clicked) if tooltip: b.set_tooltip_text(tooltip) if not label and icon_size < _NORMAL_BUTTON_ICON_SIZE: @@ -95,11 +96,11 @@ def _create_receiver_panel(): p._count.set_alignment(0, 0.5) p.pack_start(p._count, True, True, 0) - p._scanning = Gtk.Label(_('Scanning') + '...') + p._scanning = Gtk.Label(_("Scanning") + "...") p._spinner = Gtk.Spinner() bp = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 8) - bp.pack_start(Gtk.Label(' '), True, True, 0) + bp.pack_start(Gtk.Label(" "), True, True, 0) bp.pack_start(p._scanning, False, False, 0) bp.pack_end(p._spinner, False, False, 0) p.pack_end(bp, False, False, 0) @@ -128,14 +129,14 @@ def _status_line(label_text): return b - p._battery = _status_line(_('Battery')) + p._battery = _status_line(_("Battery")) p.pack_start(p._battery, False, False, 0) - p._secure = _status_line(_('Wireless Link')) - p._secure._icon.set_from_icon_name('dialog-warning', _INFO_ICON_SIZE) + p._secure = _status_line(_("Wireless Link")) + p._secure._icon.set_from_icon_name("dialog-warning", _INFO_ICON_SIZE) p.pack_start(p._secure, False, False, 0) - p._lux = _status_line(_('Lighting')) + p._lux = _status_line(_("Lighting")) p.pack_start(p._lux, False, False, 0) p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, 0) # spacer @@ -167,11 +168,11 @@ def _create_buttons_box(): bb._details = _new_button( None, - 'dialog-information', + "dialog-information", _SMALL_BUTTON_ICON_SIZE, - tooltip=_('Show Technical Details'), + tooltip=_("Show Technical Details"), toggle=True, - clicked=_update_details + clicked=_update_details, ) bb.add(bb._details) bb.set_child_secondary(bb._details, True) @@ -185,7 +186,7 @@ def _pair_new_device(trigger): assert receiver.kind is None _action.pair(_window, receiver) - bb._pair = _new_button(_('Pair new device'), 'list-add', clicked=_pair_new_device) + bb._pair = _new_button(_("Pair new device"), "list-add", clicked=_pair_new_device) bb.add(bb._pair) def _unpair_current_device(trigger): @@ -196,7 +197,7 @@ def _unpair_current_device(trigger): assert device.kind is not None _action.unpair(_window, device) - bb._unpair = _new_button(_('Unpair'), 'edit-delete', clicked=_unpair_current_device) + bb._unpair = _new_button(_("Unpair"), "edit-delete", clicked=_unpair_current_device) bb.add(bb._unpair) return bb @@ -204,7 +205,7 @@ def _unpair_current_device(trigger): def _create_empty_panel(): p = Gtk.Label() - p.set_markup('' + _('Select a device') + '') + p.set_markup("" + _("Select a device") + "") p.set_sensitive(False) return p @@ -213,7 +214,7 @@ def _create_empty_panel(): def _create_info_panel(): p = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) - p._title = Gtk.Label(' ') + p._title = Gtk.Label(" ") p._title.set_alignment(0, 0.5) p._icon = Gtk.Image() @@ -256,34 +257,34 @@ def _is_separator(model, item, _ignore=None): tree.set_row_separator_func(_is_separator, None) icon_cell_renderer = Gtk.CellRendererPixbuf() - icon_cell_renderer.set_property('stock-size', _TREE_ICON_SIZE) - icon_column = Gtk.TreeViewColumn('Icon', icon_cell_renderer) - icon_column.add_attribute(icon_cell_renderer, 'sensitive', _COLUMN.ACTIVE) - icon_column.add_attribute(icon_cell_renderer, 'icon-name', _COLUMN.ICON) + icon_cell_renderer.set_property("stock-size", _TREE_ICON_SIZE) + icon_column = Gtk.TreeViewColumn("Icon", icon_cell_renderer) + icon_column.add_attribute(icon_cell_renderer, "sensitive", _COLUMN.ACTIVE) + icon_column.add_attribute(icon_cell_renderer, "icon-name", _COLUMN.ICON) tree.append_column(icon_column) name_cell_renderer = Gtk.CellRendererText() - name_column = Gtk.TreeViewColumn('device name', name_cell_renderer) - name_column.add_attribute(name_cell_renderer, 'sensitive', _COLUMN.ACTIVE) - name_column.add_attribute(name_cell_renderer, 'text', _COLUMN.NAME) + name_column = Gtk.TreeViewColumn("device name", name_cell_renderer) + name_column.add_attribute(name_cell_renderer, "sensitive", _COLUMN.ACTIVE) + name_column.add_attribute(name_cell_renderer, "text", _COLUMN.NAME) name_column.set_expand(True) tree.append_column(name_column) tree.set_expander_column(name_column) status_cell_renderer = Gtk.CellRendererText() - status_cell_renderer.set_property('scale', 0.85) - status_cell_renderer.set_property('xalign', 1) - status_column = Gtk.TreeViewColumn('status text', status_cell_renderer) - status_column.add_attribute(status_cell_renderer, 'sensitive', _COLUMN.ACTIVE) - status_column.add_attribute(status_cell_renderer, 'text', _COLUMN.STATUS_TEXT) + status_cell_renderer.set_property("scale", 0.85) + status_cell_renderer.set_property("xalign", 1) + status_column = Gtk.TreeViewColumn("status text", status_cell_renderer) + status_column.add_attribute(status_cell_renderer, "sensitive", _COLUMN.ACTIVE) + status_column.add_attribute(status_cell_renderer, "text", _COLUMN.STATUS_TEXT) status_column.set_expand(True) tree.append_column(status_column) battery_cell_renderer = Gtk.CellRendererPixbuf() - battery_cell_renderer.set_property('stock-size', _TREE_ICON_SIZE) - battery_column = Gtk.TreeViewColumn('status icon', battery_cell_renderer) - battery_column.add_attribute(battery_cell_renderer, 'sensitive', _COLUMN.ACTIVE) - battery_column.add_attribute(battery_cell_renderer, 'icon-name', _COLUMN.STATUS_ICON) + battery_cell_renderer.set_property("stock-size", _TREE_ICON_SIZE) + battery_column = Gtk.TreeViewColumn("status icon", battery_cell_renderer) + battery_column.add_attribute(battery_cell_renderer, "sensitive", _COLUMN.ACTIVE) + battery_column.add_attribute(battery_cell_renderer, "icon-name", _COLUMN.STATUS_ICON) tree.append_column(battery_column) return tree @@ -296,7 +297,7 @@ def _create_window_layout(): assert _empty is not None assert _tree.get_selection().get_mode() == Gtk.SelectionMode.SINGLE - _tree.get_selection().connect('changed', _device_selected) + _tree.get_selection().connect("changed", _device_selected) tree_scroll = Gtk.ScrolledWindow() tree_scroll.add(_tree) @@ -316,12 +317,12 @@ def _create_window_layout(): bottom_buttons_box = Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL) bottom_buttons_box.set_layout(Gtk.ButtonBoxStyle.START) bottom_buttons_box.set_spacing(20) - quit_button = _new_button(_('Quit %s') % NAME, 'application-exit', _SMALL_BUTTON_ICON_SIZE, clicked=destroy) + quit_button = _new_button(_("Quit %s") % NAME, "application-exit", _SMALL_BUTTON_ICON_SIZE, clicked=destroy) bottom_buttons_box.add(quit_button) - about_button = _new_button(_('About %s') % NAME, 'help-about', _SMALL_BUTTON_ICON_SIZE, clicked=_show_about_window) + about_button = _new_button(_("About %s") % NAME, "help-about", _SMALL_BUTTON_ICON_SIZE, clicked=_show_about_window) bottom_buttons_box.add(about_button) diversion_button = _new_button( - _('Rule Editor'), '', _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: _show_diversion_window(_model) + _("Rule Editor"), "", _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: _show_diversion_window(_model) ) bottom_buttons_box.add(diversion_button) bottom_buttons_box.set_child_secondary(diversion_button, True) @@ -345,12 +346,12 @@ def _create_window_layout(): def _create(delete_action): window = Gtk.Window() window.set_title(NAME) - window.set_role('status-window') + window.set_role("status-window") # window.set_type_hint(Gdk.WindowTypeHint.UTILITY) # window.set_skip_taskbar_hint(True) # window.set_skip_pager_hint(True) - window.connect('delete-event', delete_action) + window.connect("delete-event", delete_action) vbox = _create_window_layout() window.add(vbox) @@ -362,7 +363,7 @@ def _create(delete_action): window.set_position(Gtk.WindowPosition.CENTER) style = window.get_style_context() - style.add_class('solaar') + style.add_class("solaar") return window @@ -421,7 +422,7 @@ def _receiver_row(receiver_path, receiver=None): row_data = (receiver_path, 0, True, receiver.name, icon_name, status_text, status_icon, receiver) assert len(row_data) == len(_TREE_SEPATATOR) if logger.isEnabledFor(logging.DEBUG): - logger.debug('new receiver row %s', row_data) + logger.debug("new receiver row %s", row_data) item = _model.append(None, row_data) if _TREE_SEPATATOR: _model.append(None, _TREE_SEPATATOR) @@ -446,8 +447,9 @@ def _device_row(receiver_path, device_number, device=None): while item: if _model.get_value(item, _COLUMN.PATH) != receiver_path: logger.warning( - 'path for device row %s different from path for receiver %s', _model.get_value(item, _COLUMN.PATH), - receiver_path + "path for device row %s different from path for receiver %s", + _model.get_value(item, _COLUMN.PATH), + receiver_path, ) item_number = _model.get_value(item, _COLUMN.NUMBER) if item_number == device_number: @@ -463,11 +465,18 @@ def _device_row(receiver_path, device_number, device=None): status_text = None status_icon = None row_data = ( - receiver_path, device_number, bool(device.online), device.codename, icon_name, status_text, status_icon, device + receiver_path, + device_number, + bool(device.online), + device.codename, + icon_name, + status_text, + status_icon, + device, ) assert len(row_data) == len(_TREE_SEPATATOR) if logger.isEnabledFor(logging.DEBUG): - logger.debug('new device row %s at index %d', row_data, new_child_index) + logger.debug("new device row %s at index %d", row_data, new_child_index) item = _model.insert(receiver_row, new_child_index, row_data) return item or None @@ -489,7 +498,7 @@ def select(receiver_path, device_number=None): selection = _tree.get_selection() selection.select_iter(item) else: - logger.warning('select(%s, %s) failed to find an item', receiver_path, device_number) + logger.warning("select(%s, %s) failed to find an item", receiver_path, device_number) def _hide(w, _ignore=None): @@ -532,57 +541,57 @@ def _details_items(device, read_all=False): # If read_all is False, only return stuff that is ~100% already # cached, and involves no HID++ calls. - yield (_('Path'), device.path) + yield (_("Path"), device.path) if device.kind is None: # 046d is the Logitech vendor id - yield (_('USB ID'), '046d:' + device.product_id) + yield (_("USB ID"), "046d:" + device.product_id) if read_all: - yield (_('Serial'), device.serial) + yield (_("Serial"), device.serial) else: - yield (_('Serial'), '...') + yield (_("Serial"), "...") else: # yield ('Codename', device.codename) - yield (_('Index'), device.number) + yield (_("Index"), device.number) if device.wpid: - yield (_('Wireless PID'), device.wpid) + yield (_("Wireless PID"), device.wpid) if device.product_id: - yield (_('Product ID'), '046d:' + device.product_id) + yield (_("Product ID"), "046d:" + device.product_id) hid_version = device.protocol - yield (_('Protocol'), 'HID++ %1.1f' % hid_version if hid_version else _('Unknown')) + yield (_("Protocol"), "HID++ %1.1f" % hid_version if hid_version else _("Unknown")) if read_all and device.polling_rate: - yield (_('Polling rate'), device.polling_rate) + yield (_("Polling rate"), device.polling_rate) if read_all or not device.online: - yield (_('Serial'), device.serial) + yield (_("Serial"), device.serial) else: - yield (_('Serial'), '...') + yield (_("Serial"), "...") if read_all and device.unitId and device.unitId != device.serial: - yield (_('Unit ID'), device.unitId) + yield (_("Unit ID"), device.unitId) if read_all: if device.firmware: for fw in list(device.firmware): - yield (' ' + _(str(fw.kind)), (fw.name + ' ' + fw.version).strip()) + yield (" " + _(str(fw.kind)), (fw.name + " " + fw.version).strip()) elif device.kind is None or device.online: - yield (' %s' % _('Firmware'), '...') + yield (" %s" % _("Firmware"), "...") flag_bits = device.status.get(_K.NOTIFICATION_FLAGS) if flag_bits is not None: - flag_names = ('(%s)' % _('none'), ) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits) - yield (_('Notifications'), ('\n%15s' % ' ').join(flag_names)) + flag_names = ("(%s)" % _("none"),) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits) + yield (_("Notifications"), ("\n%15s" % " ").join(flag_names)) def _set_details(text): _details._text.set_markup(text) def _make_text(items): - text = '\n'.join('%-13s: %s' % (name, value) for name, value in items) - return '' + text + '' + text = "\n".join("%-13s: %s" % (name, value) for name, value in items) + return "" + text + "" def _displayable_items(items): for name, value in items: - value = GLib.markup_escape_text(str(value).replace('\x00', '')).strip() + value = GLib.markup_escape_text(str(value).replace("\x00", "")).strip() if value: yield name, value @@ -614,26 +623,29 @@ def _update_receiver_panel(receiver, panel, buttons, full=False): devices_count = len(receiver) - paired_text = _( - _('No device paired.') - ) if devices_count == 0 else ngettext('%(count)s paired device.', '%(count)s paired devices.', devices_count) % { - 'count': devices_count - } - - if (receiver.max_devices > 0): - paired_text += '\n\n%s' % ngettext( - 'Up to %(max_count)s device can be paired to this receiver.', - 'Up to %(max_count)s devices can be paired to this receiver.', receiver.max_devices - ) % { - 'max_count': receiver.max_devices - } + paired_text = ( + _(_("No device paired.")) + if devices_count == 0 + else ngettext("%(count)s paired device.", "%(count)s paired devices.", devices_count) % {"count": devices_count} + ) + + if receiver.max_devices > 0: + paired_text += ( + "\n\n%s" + % ngettext( + "Up to %(max_count)s device can be paired to this receiver.", + "Up to %(max_count)s devices can be paired to this receiver.", + receiver.max_devices, + ) + % {"max_count": receiver.max_devices} + ) elif devices_count > 0: - paired_text += '\n\n%s' % _('Only one device can be paired to this receiver.') + paired_text += "\n\n%s" % _("Only one device can be paired to this receiver.") pairings = receiver.remaining_pairings() - if (pairings is not None and pairings >= 0): - paired_text += '\n%s' % ( - ngettext('This receiver has %d pairing remaining.', 'This receiver has %d pairings remaining.', pairings) % - pairings + if pairings is not None and pairings >= 0: + paired_text += "\n%s" % ( + ngettext("This receiver has %d pairing remaining.", "This receiver has %d pairings remaining.", pairings) + % pairings ) panel._count.set_markup(paired_text) @@ -655,8 +667,11 @@ def _update_receiver_panel(receiver, panel, buttons, full=False): # b._insecure.set_visible(False) buttons._unpair.set_visible(False) - if not is_pairing and (receiver.remaining_pairings() is None or receiver.remaining_pairings() != 0) and \ - (receiver.re_pairs or devices_count < receiver.max_devices): + if ( + not is_pairing + and (receiver.remaining_pairings() is None or receiver.remaining_pairings() != 0) + and (receiver.re_pairs or devices_count < receiver.max_devices) + ): buttons._pair.set_sensitive(True) else: buttons._pair.set_sensitive(False) @@ -686,28 +701,28 @@ def _update_device_panel(device, panel, buttons, full=False): panel._battery._text.set_sensitive(is_online) if battery_voltage is not None: - panel._battery._label.set_text(_('Battery Voltage')) - text = '%dmV' % battery_voltage - tooltip_text = _('Voltage reported by battery') + panel._battery._label.set_text(_("Battery Voltage")) + text = "%dmV" % battery_voltage + tooltip_text = _("Voltage reported by battery") else: - panel._battery._label.set_text(_('Battery Level')) - text = '' - tooltip_text = _('Approximate level reported by battery') + panel._battery._label.set_text(_("Battery Level")) + text = "" + tooltip_text = _("Approximate level reported by battery") if battery_voltage is not None and battery_level is not None: - text += ', ' + text += ", " if battery_level is not None: - text += _(str(battery_level)) if isinstance(battery_level, _NamedInt) else '%d%%' % battery_level + text += _(str(battery_level)) if isinstance(battery_level, _NamedInt) else "%d%%" % battery_level if battery_next_level is not None and not charging: if isinstance(battery_next_level, _NamedInt): - text += ' (' + _('next reported ') + _(str(battery_next_level)) + ')' + text += " (" + _("next reported ") + _(str(battery_next_level)) + ")" else: - text += ' (' + _('next reported ') + ('%d%%' % battery_next_level) + ')' - tooltip_text = tooltip_text + _(' and next level to be reported.') + text += " (" + _("next reported ") + ("%d%%" % battery_next_level) + ")" + tooltip_text = tooltip_text + _(" and next level to be reported.") if is_online: if charging: - text += ' (%s)' % _('charging') + text += " (%s)" % _("charging") else: - text += ' (%s)' % _('last known') + text += " (%s)" % _("last known") panel._battery._text.set_markup(text) panel._battery.set_tooltip_text(tooltip_text) @@ -718,23 +733,23 @@ def _update_device_panel(device, panel, buttons, full=False): panel._secure.set_visible(True) panel._secure._icon.set_visible(True) if device.status.get(_K.LINK_ENCRYPTED) is True: - panel._secure._text.set_text(_('encrypted')) - panel._secure._icon.set_from_icon_name('security-high', _INFO_ICON_SIZE) - panel._secure.set_tooltip_text(_('The wireless link between this device and its receiver is encrypted.')) + panel._secure._text.set_text(_("encrypted")) + panel._secure._icon.set_from_icon_name("security-high", _INFO_ICON_SIZE) + panel._secure.set_tooltip_text(_("The wireless link between this device and its receiver is encrypted.")) else: - panel._secure._text.set_text(_('not encrypted')) - panel._secure._icon.set_from_icon_name('security-low', _INFO_ICON_SIZE) + panel._secure._text.set_text(_("not encrypted")) + panel._secure._icon.set_from_icon_name("security-low", _INFO_ICON_SIZE) panel._secure.set_tooltip_text( _( - 'The wireless link between this device and its receiver is not encrypted.\n' - 'This is a security issue for pointing devices, and a major security issue for text-input devices.' + "The wireless link between this device and its receiver is not encrypted.\n" + "This is a security issue for pointing devices, and a major security issue for text-input devices." ) ) else: panel._secure.set_visible(True) panel._secure._icon.set_visible(False) - panel._secure._text.set_markup('%s' % _('offline')) - panel._secure.set_tooltip_text('') + panel._secure._text.set_markup("%s" % _("offline")) + panel._secure.set_tooltip_text("") if is_online: light_level = device.status.get(_K.LIGHT_LEVEL) @@ -742,7 +757,7 @@ def _update_device_panel(device, panel, buttons, full=False): panel._lux.set_visible(False) else: panel._lux._icon.set_from_icon_name(_icons.lux(light_level), _INFO_ICON_SIZE) - panel._lux._text.set_text(_('%(light_level)d lux') % {'light_level': light_level}) + panel._lux._text.set_text(_("%(light_level)d lux") % {"light_level": light_level}) panel._lux.set_visible(True) else: panel._lux.set_visible(False) @@ -769,7 +784,7 @@ def _update_info_panel(device, full=False): # a device must be paired assert device - _info._title.set_markup('%s' % device.name) + _info._title.set_markup("%s" % device.name) icon_name = _icons.device_icon_name(device.name, device.kind) _info._icon.set_from_icon_name(icon_name, _DEVICE_ICON_SIZE) @@ -860,7 +875,7 @@ def update(device, need_popup=False, refresh=False): if is_alive and item: was_pairing = bool(_model.get_value(item, _COLUMN.STATUS_ICON)) is_pairing = (not device.isDevice) and bool(device.status.lock_open) - _model.set_value(item, _COLUMN.STATUS_ICON, 'network-wireless' if is_pairing else _CAN_SET_ROW_NONE) + _model.set_value(item, _COLUMN.STATUS_ICON, "network-wireless" if is_pairing else _CAN_SET_ROW_NONE) if selected_device_id == (device.path, 0): full_update = need_popup or was_pairing != is_pairing @@ -875,7 +890,7 @@ def update(device, need_popup=False, refresh=False): else: path = device.receiver.path if device.receiver is not None else device.path - assert device.number is not None and device.number >= 0, 'invalid device number' + str(device.number) + assert device.number is not None and device.number >= 0, "invalid device number" + str(device.number) item = _device_row(path, device.number, device if bool(device) else None) if bool(device) and item: @@ -900,11 +915,11 @@ def update_device(device, item, selected_device_id, need_popup, full=False): _model.set_value(item, _COLUMN.STATUS_ICON, _CAN_SET_ROW_NONE) else: if battery_voltage is not None and False: # Use levels instead of voltage here - status_text = '%(battery_voltage)dmV' % {'battery_voltage': battery_voltage} + status_text = "%(battery_voltage)dmV" % {"battery_voltage": battery_voltage} elif isinstance(battery_level, _NamedInt): status_text = _(str(battery_level)) else: - status_text = '%(battery_percent)d%%' % {'battery_percent': battery_level} + status_text = "%(battery_percent)d%%" % {"battery_percent": battery_level} _model.set_value(item, _COLUMN.STATUS_TEXT, status_text) charging = device.status.get(_K.BATTERY_CHARGING) @@ -921,7 +936,7 @@ def update_device(device, item, selected_device_id, need_popup, full=False): def find_device(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 def check(_store, _treepath, row): diff --git a/lib/solaar/upower.py b/lib/solaar/upower.py index 61517b4004..17b6709c3c 100644 --- a/lib/solaar/upower.py +++ b/lib/solaar/upower.py @@ -30,7 +30,7 @@ def _suspend(): if _suspend_callback: if logger.isEnabledFor(logging.INFO): - logger.info('received suspend event') + logger.info("received suspend event") _suspend_callback() @@ -40,7 +40,7 @@ def _suspend(): def _resume(): if _resume_callback: if logger.isEnabledFor(logging.INFO): - logger.info('received resume event') + logger.info("received resume event") _resume_callback() @@ -59,24 +59,25 @@ def watch(on_resume_callback=None, on_suspend_callback=None): try: import dbus - _LOGIND_BUS = 'org.freedesktop.login1' - _LOGIND_INTERFACE = 'org.freedesktop.login1.Manager' + _LOGIND_BUS = "org.freedesktop.login1" + _LOGIND_INTERFACE = "org.freedesktop.login1.Manager" # integration into the main GLib loop from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() assert bus - bus.add_signal_receiver(_suspend_or_resume, 'PrepareForSleep', dbus_interface=_LOGIND_INTERFACE, bus_name=_LOGIND_BUS) + bus.add_signal_receiver(_suspend_or_resume, "PrepareForSleep", dbus_interface=_LOGIND_INTERFACE, bus_name=_LOGIND_BUS) if logger.isEnabledFor(logging.INFO): - logger.info('connected to system dbus, watching for suspend/resume events') + logger.info("connected to system dbus, watching for suspend/resume events") except Exception: # Either: # - the dbus library is not available # - the system dbus is not running - logger.warning('failed to register suspend/resume callbacks') + logger.warning("failed to register suspend/resume callbacks") pass diff --git a/setup.py b/setup.py index b7ed3ef347..9b40ddce68 100755 --- a/setup.py +++ b/setup.py @@ -9,88 +9,89 @@ except ImportError: from distutils.core import setup -NAME = 'Solaar' +NAME = "Solaar" -with open('lib/solaar/version', 'r') as vfile: +with open("lib/solaar/version", "r") as vfile: version = vfile.read().strip() try: # get commit from git describe - commit = subprocess.check_output(['git', 'describe', '--always'], stderr=subprocess.DEVNULL).strip().decode() - with open('lib/solaar/commit', 'w') as vfile: - vfile.write(f'{commit}\n') + commit = subprocess.check_output(["git", "describe", "--always"], stderr=subprocess.DEVNULL).strip().decode() + with open("lib/solaar/commit", "w") as vfile: + vfile.write(f"{commit}\n") except Exception: # get commit from Ubuntu dpkg-parsechangelog try: - commit = subprocess.check_output(['dpkg-parsechangelog', '--show-field', 'Version'], - stderr=subprocess.DEVNULL).strip().decode() - commit = commit.split('~') - with open('lib/solaar/commit', 'w') as vfile: - vfile.write(f'{commit[0]}\n') + commit = ( + subprocess.check_output(["dpkg-parsechangelog", "--show-field", "Version"], stderr=subprocess.DEVNULL) + .strip() + .decode() + ) + commit = commit.split("~") + with open("lib/solaar/commit", "w") as vfile: + vfile.write(f"{commit[0]}\n") except Exception as e: - print('Exception using dpkg-parsechangelog', e) + print("Exception using dpkg-parsechangelog", e) def _data_files(): + yield "share/icons/hicolor/scalable/apps", _glob("share/solaar/icons/solaar*.svg") + yield "share/icons/hicolor/32x32/apps", _glob("share/solaar/icons/solaar-light_*.png") - yield 'share/icons/hicolor/scalable/apps', _glob('share/solaar/icons/solaar*.svg') - yield 'share/icons/hicolor/32x32/apps', _glob('share/solaar/icons/solaar-light_*.png') - - for mo in _glob('share/locale/*/LC_MESSAGES/solaar.mo'): + for mo in _glob("share/locale/*/LC_MESSAGES/solaar.mo"): yield _dirname(mo), [mo] - yield 'share/applications', ['share/applications/solaar.desktop'] - yield 'lib/udev/rules.d', ['rules.d/42-logitech-unify-permissions.rules'] - yield 'share/metainfo', ['share/solaar/io.github.pwr_solaar.solaar.metainfo.xml'] + yield "share/applications", ["share/applications/solaar.desktop"] + yield "lib/udev/rules.d", ["rules.d/42-logitech-unify-permissions.rules"] + yield "share/metainfo", ["share/solaar/io.github.pwr_solaar.solaar.metainfo.xml"] setup( name=NAME.lower(), version=version, - description='Linux device manager for Logitech receivers, keyboards, mice, and tablets.', - long_description=''' + description="Linux device manager for Logitech receivers, keyboards, mice, and tablets.", + long_description=""" Solaar is a Linux device manager for many Logitech peripherals that connect through Unifying and other receivers or via USB or Bluetooth. Solaar is able to pair/unpair devices with receivers and show and modify some of the modifiable features of devices. -For instructions on installing Solaar see https://pwr-solaar.github.io/Solaar/installation'''.strip(), - author='Daniel Pavel', - license='GPLv2', - url='http://pwr-solaar.github.io/Solaar/', +For instructions on installing Solaar see https://pwr-solaar.github.io/Solaar/installation""".strip(), + author="Daniel Pavel", + license="GPLv2", + url="http://pwr-solaar.github.io/Solaar/", classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: X11 Applications :: GTK', - 'Environment :: Console', - 'Intended Audience :: End Users/Desktop', - 'License :: DFSG approved', - 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', - 'Natural Language :: English', - 'Programming Language :: Python :: 3 :: Only', - 'Operating System :: POSIX :: Linux', - 'Topic :: Utilities', + "Development Status :: 4 - Beta", + "Environment :: X11 Applications :: GTK", + "Environment :: Console", + "Intended Audience :: End Users/Desktop", + "License :: DFSG approved", + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + "Natural Language :: English", + "Programming Language :: Python :: 3 :: Only", + "Operating System :: POSIX :: Linux", + "Topic :: Utilities", ], - platforms=['linux'], - + platforms=["linux"], # sudo apt install python-gi python3-gi \ # gir1.2-gtk-3.0 gir1.2-notify-0.7 gir1.2-ayatanaappindicator3-0.1 # os_requires=['gi.repository.GObject (>= 2.0)', 'gi.repository.Gtk (>= 3.0)'], - python_requires='>=3.7', + python_requires=">=3.7", install_requires=[ 'evdev (>= 1.1.2) ; platform_system=="Linux"', - 'pyudev (>= 0.13)', - 'PyYAML (>= 3.12)', - 'python-xlib (>= 0.27)', - 'psutil (>= 5.4.3)', + "pyudev (>= 0.13)", + "PyYAML (>= 3.12)", + "python-xlib (>= 0.27)", + "psutil (>= 5.4.3)", 'dbus-python ; platform_system=="Linux"', ], extras_require={ - 'report-descriptor': ['hid-parser'], - 'desktop-notifications': ['Notify (>= 0.7)'], - 'git-commit': ['python-git-info'], - 'test': ['pytest', 'pytest-cov'], - 'dev': ['ruff'], + "report-descriptor": ["hid-parser"], + "desktop-notifications": ["Notify (>= 0.7)"], + "git-commit": ["python-git-info"], + "test": ["pytest", "pytest-cov"], + "dev": ["ruff"], }, - package_dir={'': 'lib'}, - packages=['keysyms', 'hidapi', 'logitech_receiver', 'solaar', 'solaar.ui', 'solaar.cli'], + package_dir={"": "lib"}, + packages=["keysyms", "hidapi", "logitech_receiver", "solaar", "solaar.ui", "solaar.cli"], data_files=list(_data_files()), include_package_data=True, - scripts=_glob('bin/*'), + scripts=_glob("bin/*"), ) diff --git a/tests/logitech_receiver/test_common.py b/tests/logitech_receiver/test_common.py index 46776115ef..4ad594e62f 100644 --- a/tests/logitech_receiver/test_common.py +++ b/tests/logitech_receiver/test_common.py @@ -4,7 +4,7 @@ def test_crc16(): - value = b'123456789' + value = b"123456789" expected = 0x29B1 result = common.crc16(value) @@ -13,21 +13,21 @@ def test_crc16(): def test_named_int(): - named_int = common.NamedInt(0x2, 'pulse') + named_int = common.NamedInt(0x2, "pulse") - assert named_int.name == 'pulse' + assert named_int.name == "pulse" assert named_int == 2 def test_named_int_comparison(): default_value = 0 - default_name = 'entry' + default_name = "entry" named_int = common.NamedInt(default_value, default_name) named_int_equal = common.NamedInt(default_value, default_name) - named_int_unequal_name = common.NamedInt(default_value, 'unequal') + named_int_unequal_name = common.NamedInt(default_value, "unequal") named_int_unequal_value = common.NamedInt(5, default_name) - named_int_unequal = common.NamedInt(2, 'unequal') + named_int_unequal = common.NamedInt(2, "unequal") assert named_int == named_int_equal assert named_int != named_int_unequal_name @@ -42,23 +42,26 @@ def named_ints(): def test_named_ints(named_ints): assert named_ints.empty == 0 - assert named_ints.empty.name == 'empty' + assert named_ints.empty.name == "empty" assert named_ints.critical == 5 - assert named_ints.critical.name == 'critical' + assert named_ints.critical.name == "critical" assert named_ints.low == 20 - assert named_ints.low.name == 'low' + assert named_ints.low.name == "low" assert named_ints.good == 50 - assert named_ints.good.name == 'good' + assert named_ints.good.name == "good" assert named_ints.full == 90 - assert named_ints.full.name == 'full' + assert named_ints.full.name == "full" assert len(named_ints) == 5 -@pytest.mark.parametrize('bytes_input, expected_output', [ - (b'\x01\x02\x03\x04', '01020304'), - (b'', ''), -]) +@pytest.mark.parametrize( + "bytes_input, expected_output", + [ + (b"\x01\x02\x03\x04", "01020304"), + (b"", ""), + ], +) def test_strhex(bytes_input, expected_output): result = common.strhex(bytes_input) @@ -66,7 +69,7 @@ def test_strhex(bytes_input, expected_output): def test_bytest2int(): - value = b'\x12\x34\x56\x78' + value = b"\x12\x34\x56\x78" expected = 0x12345678 result = common.bytes2int(value) @@ -76,7 +79,7 @@ def test_bytest2int(): def test_int2bytes(): value = 0x12345678 - expected = b'\x12\x34\x56\x78' + expected = b"\x12\x34\x56\x78" result = common.int2bytes(value) diff --git a/tools/hidconsole b/tools/hidconsole index d270aa2716..88ad3831f4 100755 --- a/tools/hidconsole +++ b/tools/hidconsole @@ -10,13 +10,14 @@ def init_paths(): import os.path as _path import sys - src_lib = _path.normpath(_path.join(_path.realpath(sys.path[0]), '..', 'lib')) - init_py = _path.join(src_lib, 'hidapi', '__init__.py') + src_lib = _path.normpath(_path.join(_path.realpath(sys.path[0]), "..", "lib")) + init_py = _path.join(src_lib, "hidapi", "__init__.py") if _path.exists(init_py): sys.path[0] = src_lib -if __name__ == '__main__': +if __name__ == "__main__": init_paths() from hidapi import hidconsole + hidconsole.main()