Apply ruff format
Run ruff auto formatting using: ruff format . Related #2295
This commit is contained in:
parent
35f63edcd8
commit
7774569971
15
bin/solaar
15
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()
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -35,30 +35,31 @@ from time import sleep
|
|||
|
||||
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 = None
|
|||
|
||||
# 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 @@ else:
|
|||
# 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 @@ _hid_version = _hidapi.hid_version()
|
|||
|
||||
# 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 @@ atexit.register(_hidapi.hid_exit)
|
|||
# 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 @@ class _DeviceMonitor(Thread):
|
|||
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 @@ class _DeviceMonitor(Thread):
|
|||
# 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
|
||||
|
||||
|
||||
|
|
|
@ -40,10 +40,10 @@ except NameError:
|
|||
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 @@ del Lock
|
|||
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 _open(args):
|
|||
|
||||
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()
|
||||
|
|
|
@ -29,6 +29,7 @@ import logging
|
|||
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 DeviceNotFoundError
|
|||
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 monitor_glib(callback, 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 monitor_glib(callback, 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
|
||||
|
|
|
@ -5,39 +5,39 @@ from re import findall
|
|||
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()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -38,4 +38,4 @@ logger.setLevel(logging.root.level)
|
|||
|
||||
del logging
|
||||
|
||||
__version__ = '0.9'
|
||||
__version__ = "0.9"
|
||||
|
|
|
@ -45,14 +45,14 @@ logger = logging.getLogger(__name__)
|
|||
#
|
||||
|
||||
_wired_device = lambda product_id, interface: {
|
||||
'vendor_id': 0x046d,
|
||||
'product_id': product_id,
|
||||
'bus_id': 0x3,
|
||||
'usb_interface': interface,
|
||||
'isDevice': True
|
||||
"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 @@ for _ignore, d in _DEVICES.items():
|
|||
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 @@ report_lengths = {
|
|||
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 @@ _PING_TIMEOUT = DEFAULT_TIMEOUT
|
|||
|
||||
|
||||
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)
|
||||
|
|
|
@ -35,105 +35,105 @@ from .i18n import _
|
|||
# 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 @@ _ex100_receiver = lambda product_id: {
|
|||
# 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 = (
|
||||
|
|
|
@ -31,28 +31,266 @@ is_string = lambda d: isinstance(d, str)
|
|||
|
||||
|
||||
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 @@ class NamedInt(int):
|
|||
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 @@ class NamedInt(int):
|
|||
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')
|
||||
|
||||
__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 @@ class NamedInts:
|
|||
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 @@ class NamedInts:
|
|||
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 @@ class NamedInts:
|
|||
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 @@ class NamedInts:
|
|||
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 @@ class UnsortedNamedInts(NamedInts):
|
|||
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 @@ class KwException(Exception):
|
|||
|
||||
|
||||
"""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)
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ from .hidpp10_constants import REGISTERS as _R
|
|||
|
||||
|
||||
class _DeviceDescriptor:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None,
|
||||
|
@ -44,7 +43,7 @@ class _DeviceDescriptor:
|
|||
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
|
||||
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ class Device:
|
|||
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 @@ class Device:
|
|||
# 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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
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 @@ class Device:
|
|||
"""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 @@ class Device:
|
|||
*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 @@ class Device:
|
|||
|
||||
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 '<Device(%d,%s,%s,%s)>' % (self.number, self.wpid or self.product_id, name, self.serial)
|
||||
name = "name not available"
|
||||
return "<Device(%d,%s,%s,%s)>" % (self.number, self.wpid or self.product_id, name, self.serial)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
@ -544,20 +545,20 @@ class Device:
|
|||
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()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,24 +10,29 @@ class NoReceiver(_KwException):
|
|||
receiver is no longer available. Should only happen if the receiver is
|
||||
physically disconnected from the machine, or its kernel driver module is
|
||||
unloaded."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NoSuchDevice(_KwException):
|
||||
"""Raised when trying to reach a device number not paired to the receiver."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DeviceUnreachable(_KwException):
|
||||
"""Raised when a request is made to an unreachable (turned off) device."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FeatureNotSupported(_KwException):
|
||||
"""Raised when trying to request a feature not supported by the device."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FeatureCallError(_KwException):
|
||||
"""Raised if the device replied to a feature call with an error."""
|
||||
|
||||
pass
|
||||
|
|
|
@ -34,14 +34,14 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def read_register(device, register_number, *params):
|
||||
assert device is not None, 'tried to read register %02X from invalid device %s' % (register_number, device)
|
||||
assert device is not None, "tried to read register %02X from invalid device %s" % (register_number, device)
|
||||
# support long registers by adding a 2 in front of the register number
|
||||
request_id = 0x8100 | (int(register_number) & 0x2FF)
|
||||
return device.request(request_id, *params)
|
||||
|
||||
|
||||
def write_register(device, register_number, *value):
|
||||
assert device is not None, 'tried to write register %02X to invalid device %s' % (register_number, device)
|
||||
assert device is not None, "tried to write register %02X to invalid device %s" % (register_number, device)
|
||||
# support long registers by adding a 2 in front of the register number
|
||||
request_id = 0x8000 | (int(register_number) & 0x2FF)
|
||||
return device.request(request_id, *value)
|
||||
|
@ -83,18 +83,27 @@ def parse_battery_status(register, reply):
|
|||
charge = ord(reply[:1])
|
||||
status_byte = ord(reply[2:3]) & 0xF0
|
||||
status_text = (
|
||||
BATTERY_STATUS.discharging if status_byte == 0x30 else
|
||||
BATTERY_STATUS.recharging if status_byte == 0x50 else BATTERY_STATUS.full if status_byte == 0x90 else None
|
||||
BATTERY_STATUS.discharging
|
||||
if status_byte == 0x30
|
||||
else BATTERY_STATUS.recharging
|
||||
if status_byte == 0x50
|
||||
else BATTERY_STATUS.full
|
||||
if status_byte == 0x90
|
||||
else None
|
||||
)
|
||||
return charge, None, status_text, None
|
||||
|
||||
if register == REGISTERS.battery_status:
|
||||
status_byte = ord(reply[:1])
|
||||
charge = (
|
||||
_BATTERY_APPROX.full if status_byte == 7 # full
|
||||
else _BATTERY_APPROX.good if status_byte == 5 # good
|
||||
else _BATTERY_APPROX.low if status_byte == 3 # low
|
||||
else _BATTERY_APPROX.critical if status_byte == 1 # critical
|
||||
_BATTERY_APPROX.full
|
||||
if status_byte == 7 # full
|
||||
else _BATTERY_APPROX.good
|
||||
if status_byte == 5 # good
|
||||
else _BATTERY_APPROX.low
|
||||
if status_byte == 3 # low
|
||||
else _BATTERY_APPROX.critical
|
||||
if status_byte == 1 # critical
|
||||
# pure 'charging' notifications may come without a status
|
||||
else _BATTERY_APPROX.empty
|
||||
)
|
||||
|
@ -107,7 +116,7 @@ def parse_battery_status(register, reply):
|
|||
elif charging_byte & 0x22 == 0x22:
|
||||
status_text = BATTERY_STATUS.full
|
||||
else:
|
||||
logger.warning('could not parse 0x07 battery status: %02X (level %02X)', charging_byte, status_byte)
|
||||
logger.warning("could not parse 0x07 battery status: %02X (level %02X)", charging_byte, status_byte)
|
||||
status_text = None
|
||||
|
||||
if charging_byte & 0x03 and status_byte == 0:
|
||||
|
@ -129,25 +138,25 @@ def get_firmware(device):
|
|||
return
|
||||
|
||||
fw_version = _strhex(reply[1:3])
|
||||
fw_version = '%s.%s' % (fw_version[0:2], fw_version[2:4])
|
||||
fw_version = "%s.%s" % (fw_version[0:2], fw_version[2:4])
|
||||
reply = read_register(device, REGISTERS.firmware, 0x02)
|
||||
if reply:
|
||||
fw_version += '.B' + _strhex(reply[1:3])
|
||||
fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, '', fw_version, None)
|
||||
fw_version += ".B" + _strhex(reply[1:3])
|
||||
fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, "", fw_version, None)
|
||||
firmware[0] = fw
|
||||
|
||||
reply = read_register(device, REGISTERS.firmware, 0x04)
|
||||
if reply:
|
||||
bl_version = _strhex(reply[1:3])
|
||||
bl_version = '%s.%s' % (bl_version[0:2], bl_version[2:4])
|
||||
bl = _FirmwareInfo(FIRMWARE_KIND.Bootloader, '', bl_version, None)
|
||||
bl_version = "%s.%s" % (bl_version[0:2], bl_version[2:4])
|
||||
bl = _FirmwareInfo(FIRMWARE_KIND.Bootloader, "", bl_version, None)
|
||||
firmware[1] = bl
|
||||
|
||||
reply = read_register(device, REGISTERS.firmware, 0x03)
|
||||
if reply:
|
||||
o_version = _strhex(reply[1:3])
|
||||
o_version = '%s.%s' % (o_version[0:2], o_version[2:4])
|
||||
o = _FirmwareInfo(FIRMWARE_KIND.Other, '', o_version, None)
|
||||
o_version = "%s.%s" % (o_version[0:2], o_version[2:4])
|
||||
o = _FirmwareInfo(FIRMWARE_KIND.Other, "", o_version, None)
|
||||
firmware[2] = o
|
||||
|
||||
if any(firmware):
|
||||
|
@ -182,8 +191,8 @@ def set_3leds(device, battery_level=None, charging=None, warning=None):
|
|||
v1, v2 = 0x20, 0x22
|
||||
if warning:
|
||||
# set the blinking flag for the leds already set
|
||||
v1 |= (v1 >> 1)
|
||||
v2 |= (v2 >> 1)
|
||||
v1 |= v1 >> 1
|
||||
v2 |= v2 >> 1
|
||||
elif charging:
|
||||
# blink all green
|
||||
v1, v2 = 0x30, 0x33
|
||||
|
|
|
@ -16,7 +16,7 @@ DEVICE_KIND = NamedInts(
|
|||
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 @@ POWER_SWITCH_LOCATION = NamedInts(
|
|||
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 @@ ERROR = NamedInts(
|
|||
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 @@ REGISTERS = NamedInts(
|
|||
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 @@ REGISTERS = NamedInts(
|
|||
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,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -128,7 +128,7 @@ FEATURE = NamedInts(
|
|||
# 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 @@ BATTERY_STATUS = NamedInts(
|
|||
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 @@ ERROR = NamedInts(
|
|||
invalid_feature_index=0x06,
|
||||
invalid_function=0x07,
|
||||
busy=0x08,
|
||||
unsupported=0x09
|
||||
unsupported=0x09,
|
||||
)
|
||||
|
||||
# Gesture Ids for feature GESTURE_2
|
||||
|
@ -236,7 +236,7 @@ GESTURE = NamedInts(
|
|||
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 @@ PARAM = NamedInts(
|
|||
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
|
||||
|
|
|
@ -27,61 +27,56 @@ ngettext = _gettext.ngettext
|
|||
|
||||
_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"),
|
||||
)
|
||||
|
|
|
@ -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 @@ class _ThreadedHandle:
|
|||
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 @@ class _ThreadedHandle:
|
|||
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 @@ class _ThreadedHandle:
|
|||
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 @@ class _ThreadedHandle:
|
|||
# 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 @@ class EventsListener(_threading.Thread):
|
|||
# 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 @@ class EventsListener(_threading.Thread):
|
|||
try:
|
||||
self._notifications_callback(n)
|
||||
except Exception:
|
||||
logger.exception('processing %s', n)
|
||||
logger.exception("processing %s", n)
|
||||
|
||||
del self._queued_notifications
|
||||
self.has_stopped()
|
||||
|
|
|
@ -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 <register> <xx> <yy> <zz> <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") <??> <?> <??> <light level 1=off..5=max>
|
||||
# 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:
|
||||
|
|
|
@ -22,9 +22,11 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
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 @@ if available:
|
|||
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 @@ if available:
|
|||
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
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ class Receiver:
|
|||
|
||||
The paired devices are available through the sequence interface.
|
||||
"""
|
||||
|
||||
number = 0xFF
|
||||
kind = None
|
||||
|
||||
|
@ -56,33 +57,36 @@ class Receiver:
|
|||
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.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.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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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 @@ class Receiver:
|
|||
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)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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 @@ class ReceiverStatus(dict):
|
|||
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 @@ class DeviceStatus(dict):
|
|||
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 @@ class DeviceStatus(dict):
|
|||
|
||||
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 @@ class DeviceStatus(dict):
|
|||
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 @@ class DeviceStatus(dict):
|
|||
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 @@ class DeviceStatus(dict):
|
|||
# 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:
|
||||
|
|
|
@ -20,13 +20,16 @@ import pkgutil as _pkgutil
|
|||
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()
|
||||
|
|
|
@ -27,6 +27,7 @@ import logitech_receiver.device as _device
|
|||
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 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
def _create_parser():
|
||||
parser = _argparse.ArgumentParser(
|
||||
prog=NAME.lower(),
|
||||
add_help=False,
|
||||
epilog='For details on individual actions, run `%s <action> --help`.' % NAME.lower()
|
||||
prog=NAME.lower(), add_help=False, epilog="For details on individual actions, run `%s <action> --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()))
|
||||
|
|
|
@ -21,56 +21,58 @@ import yaml as _yaml
|
|||
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
|
||||
|
|
|
@ -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 run(receivers, args, find_receiver, _ignore):
|
|||
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 run(receivers, args, find_receiver, _ignore):
|
|||
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 run(receivers, args, find_receiver, _ignore):
|
|||
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 run(receivers, args, find_receiver, _ignore):
|
|||
|
||||
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 run(receivers, args, find_receiver, _ignore):
|
|||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -25,6 +25,7 @@ from logitech_receiver import receiver as _receiver
|
|||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -28,22 +28,23 @@ import yaml as _yaml
|
|||
|
||||
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')
|
||||
_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'
|
||||
_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 @@ class _DeviceEntry(dict):
|
|||
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 @@ class _DeviceEntry(dict):
|
|||
|
||||
|
||||
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 @@ _yaml.add_representer(_NamedInt, named_int_representer)
|
|||
# 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 persister(device):
|
|||
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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -31,19 +31,19 @@ from solaar import NAME as _NAME
|
|||
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ from logitech_receiver import status as _status
|
|||
|
||||
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 @@ _IR = _hidpp10_constants.INFO_SUBREGISTERS
|
|||
#
|
||||
#
|
||||
|
||||
_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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
|
||||
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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
# 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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
# 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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
|
||||
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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
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 @@ class ReceiverListener(_listener.EventsListener):
|
|||
# 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 '<ReceiverListener(%s,%s)>' % (self.receiver.path, self.receiver.handle)
|
||||
return "<ReceiverListener(%s,%s)>" % (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 @@ _error_callback = None
|
|||
|
||||
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
|
||||
|
|
|
@ -35,7 +35,6 @@ except ImportError:
|
|||
|
||||
|
||||
class TaskRunner(_Thread):
|
||||
|
||||
def __init__(self, name):
|
||||
super().__init__(name=name)
|
||||
self.daemon = True
|
||||
|
@ -54,7 +53,7 @@ class TaskRunner(_Thread):
|
|||
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 @@ class TaskRunner(_Thread):
|
|||
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")
|
||||
|
|
|
@ -22,13 +22,14 @@ import gi
|
|||
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 @@ logger = logging.getLogger(__name__)
|
|||
#
|
||||
#
|
||||
|
||||
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 @@ GLib.threads_init()
|
|||
|
||||
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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -23,32 +23,35 @@ import gi
|
|||
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 @@ _task_runner = None
|
|||
|
||||
def start_async():
|
||||
global _task_runner
|
||||
_task_runner = _TaskRunner('AsyncUI')
|
||||
_task_runner = _TaskRunner("AsyncUI")
|
||||
_task_runner.start()
|
||||
|
||||
|
||||
|
|
|
@ -26,11 +26,12 @@ import gi
|
|||
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 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
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 _read_async(setting, force_read, sbox, device_is_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 _write_async(setting, value, sbox, sensitive=True, key=None):
|
|||
|
||||
|
||||
class ComboBoxText(Gtk.ComboBoxText):
|
||||
|
||||
def get_value(self):
|
||||
return int(self.get_active_id())
|
||||
|
||||
|
@ -87,13 +85,11 @@ class ComboBoxText(Gtk.ComboBoxText):
|
|||
|
||||
|
||||
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 @@ class Control():
|
|||
|
||||
|
||||
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 @@ class ToggleControl(Gtk.Switch, Control):
|
|||
|
||||
|
||||
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 @@ class SliderControl(Gtk.Scale, Control):
|
|||
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 @@ class ChoiceControlLittle(Gtk.ComboBoxText, Control):
|
|||
|
||||
|
||||
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 @@ class ChoiceControlBig(Gtk.Entry, Control):
|
|||
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 @@ class ChoiceControlBig(Gtk.Entry, Control):
|
|||
|
||||
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 @@ class MapChoiceControl(Gtk.HBox, Control):
|
|||
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 @@ class MapChoiceControl(Gtk.HBox, Control):
|
|||
|
||||
|
||||
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 @@ class MultipleControl(Gtk.ListBox, Control):
|
|||
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 @@ class MultipleControl(Gtk.ListBox, Control):
|
|||
|
||||
|
||||
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 @@ class MultipleToggleControl(MultipleControl):
|
|||
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 @@ class MultipleRangeControl(MultipleControl):
|
|||
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 @@ class MultipleRangeControl(MultipleControl):
|
|||
|
||||
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 @@ class MultipleRangeControl(MultipleControl):
|
|||
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 @@ class MultipleRangeControl(MultipleControl):
|
|||
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 @@ class PackedRangeControl(MultipleRangeControl):
|
|||
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 @@ class PackedRangeControl(MultipleRangeControl):
|
|||
|
||||
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 @@ class PackedRangeControl(MultipleRangeControl):
|
|||
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 @@ class PackedRangeControl(MultipleRangeControl):
|
|||
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 @@ class HeteroKeyControl(Gtk.HBox, Control):
|
|||
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 @@ class HeteroKeyControl(Gtk.HBox, 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:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 @@ logger = logging.getLogger(__name__)
|
|||
#
|
||||
|
||||
_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)
|
||||
|
|
|
@ -33,7 +33,8 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
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 @@ except (ValueError, ImportError):
|
|||
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 @@ if available:
|
|||
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 @@ if available:
|
|||
|
||||
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 @@ if available:
|
|||
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 @@ if available:
|
|||
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
|
||||
|
|
|
@ -21,6 +21,7 @@ import logging
|
|||
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('<b>%s</b>' % device.name)
|
||||
device_label.set_markup("<b>%s</b>" % 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'
|
||||
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.')
|
||||
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.')
|
||||
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}
|
||||
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
|
||||
|
|
|
@ -22,11 +22,13 @@ import os
|
|||
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 @@ try:
|
|||
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 @@ try:
|
|||
# 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 @@ try:
|
|||
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 @@ try:
|
|||
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 @@ except ImportError:
|
|||
|
||||
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 @@ except ImportError:
|
|||
|
||||
def _generate_tooltip_lines():
|
||||
if not _devices_info:
|
||||
yield '<b>%s</b>: ' % NAME + _('no receiver')
|
||||
yield "<b>%s</b>: " % 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 '<b>%s</b>' % name
|
||||
yield "<b>%s</b>" % name
|
||||
if status:
|
||||
yield '\t%s' % p
|
||||
yield "\t%s" % p
|
||||
else:
|
||||
yield '\t%s <small>(' % p + _('offline') + ')</small>'
|
||||
yield "\t%s <small>(" % p + _("offline") + ")</small>"
|
||||
else:
|
||||
if status:
|
||||
yield '<b>%s</b> <small>(' % name + _('no status') + ')</small>'
|
||||
yield "<b>%s</b> <small>(" % name + _("no status") + ")</small>"
|
||||
else:
|
||||
yield '<b>%s</b> <small>(' % name + _('offline') + ')</small>'
|
||||
yield "<b>%s</b> <small>(" % name + _("offline") + ")</small>"
|
||||
|
||||
|
||||
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)
|
||||
|
|
|
@ -25,6 +25,7 @@ from logitech_receiver import hidpp10 as _hidpp10
|
|||
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 .diversion_rules import show_window as _show_diversion_window
|
|||
|
||||
# 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 @@ _TREE_ICON_SIZE = Gtk.IconSize.BUTTON
|
|||
_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 _create_device_panel():
|
|||
|
||||
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 _create_buttons_box():
|
|||
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 _create_buttons_box():
|
|||
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 _create_buttons_box():
|
|||
|
||||
def _create_empty_panel():
|
||||
p = Gtk.Label()
|
||||
p.set_markup('<small>' + _('Select a device') + '</small>')
|
||||
p.set_markup("<small>" + _("Select a device") + "</small>")
|
||||
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 _create_tree(model):
|
|||
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 _update_details(button):
|
|||
# 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 '<small><tt>' + text + '</tt></small>'
|
||||
text = "\n".join("%-13s: %s" % (name, value) for name, value in items)
|
||||
return "<small><tt>" + text + "</tt></small>"
|
||||
|
||||
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
|
||||
}
|
||||
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<small>%s</small>' % 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
|
||||
}
|
||||
if receiver.max_devices > 0:
|
||||
paired_text += (
|
||||
"\n\n<small>%s</small>"
|
||||
% 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<small>%s</small>' % _('Only one device can be paired to this receiver.')
|
||||
paired_text += "\n\n<small>%s</small>" % _("Only one device can be paired to this receiver.")
|
||||
pairings = receiver.remaining_pairings()
|
||||
if (pairings is not None and pairings >= 0):
|
||||
paired_text += '\n<small>%s</small>' % (
|
||||
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<small>%s</small>" % (
|
||||
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 += '<small> (' + _('next reported ') + _(str(battery_next_level)) + ')</small>'
|
||||
text += "<small> (" + _("next reported ") + _(str(battery_next_level)) + ")</small>"
|
||||
else:
|
||||
text += '<small> (' + _('next reported ') + ('%d%%' % battery_next_level) + ')</small>'
|
||||
tooltip_text = tooltip_text + _(' and next level to be reported.')
|
||||
text += "<small> (" + _("next reported ") + ("%d%%" % battery_next_level) + ")</small>"
|
||||
tooltip_text = tooltip_text + _(" and next level to be reported.")
|
||||
if is_online:
|
||||
if charging:
|
||||
text += ' <small>(%s)</small>' % _('charging')
|
||||
text += " <small>(%s)</small>" % _("charging")
|
||||
else:
|
||||
text += ' <small>(%s)</small>' % _('last known')
|
||||
text += " <small>(%s)</small>" % _("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('<small>%s</small>' % _('offline'))
|
||||
panel._secure.set_tooltip_text('')
|
||||
panel._secure._text.set_markup("<small>%s</small>" % _("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('<b>%s</b>' % device.name)
|
||||
_info._title.set_markup("<b>%s</b>" % 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):
|
||||
|
|
|
@ -30,7 +30,7 @@ _suspend_callback = None
|
|||
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 @@ _resume_callback = None
|
|||
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
|
||||
|
|
99
setup.py
99
setup.py
|
@ -9,88 +9,89 @@ try:
|
|||
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/*"),
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ from lib.logitech_receiver import common
|
|||
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue