yapf: set max line lenght to 127

Signed-off-by: Filipe Laíns <lains@archlinux.org>
This commit is contained in:
Filipe Laíns 2020-07-02 14:44:15 +01:00 committed by Filipe Laíns
parent 627185079f
commit 8e89aa0038
38 changed files with 536 additions and 1208 deletions

View File

@ -37,9 +37,7 @@ def init_paths():
if __name__ == '__main__': if __name__ == '__main__':
print( print('WARNING: solaar-cli is deprecated; use solaar with the usual arguments')
'WARNING: solaar-cli is deprecated; use solaar with the usual arguments'
)
init_paths() init_paths()
import solaar.cli import solaar.cli
solaar.cli.run() solaar.cli.run()

View File

@ -72,8 +72,7 @@ def _print(marker, data, scroll=False):
s = marker + ' ' + data s = marker + ' ' + data
else: else:
hexs = strhex(data) hexs = strhex(data)
s = '%s (% 8.3f) [%s %s %s %s] %s' % (marker, t, hexs[0:2], hexs[2:4], s = '%s (% 8.3f) [%s %s %s %s] %s' % (marker, t, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:], repr(data))
hexs[4:8], hexs[8:], repr(data))
with print_lock: with print_lock:
# allow only one thread at a time to write to the console, otherwise # allow only one thread at a time to write to the console, otherwise
@ -130,23 +129,17 @@ def _validate_input(line, hidpp=False):
_error('Invalid HID++ request: first byte must be 0x10 or 0x11') _error('Invalid HID++ request: first byte must be 0x10 or 0x11')
return None return None
if data[1:2] not in b'\xFF\x01\x02\x03\x04\x05\x06': if data[1:2] not in b'\xFF\x01\x02\x03\x04\x05\x06':
_error( _error('Invalid HID++ request: second byte must be 0xFF or one of 0x01..0x06')
'Invalid HID++ request: second byte must be 0xFF or one of 0x01..0x06'
)
return None return None
if data[:1] == b'\x10': if data[:1] == b'\x10':
if len(data) > 7: if len(data) > 7:
_error( _error('Invalid HID++ request: maximum length of a 0x10 request is 7 bytes')
'Invalid HID++ request: maximum length of a 0x10 request is 7 bytes'
)
return None return None
while len(data) < 7: while len(data) < 7:
data = (data + b'\x00' * 7)[:7] data = (data + b'\x00' * 7)[:7]
elif data[:1] == b'\x11': elif data[:1] == b'\x11':
if len(data) > 20: if len(data) > 20:
_error( _error('Invalid HID++ request: maximum length of a 0x11 request is 20 bytes')
'Invalid HID++ request: maximum length of a 0x11 request is 20 bytes'
)
return None return None
while len(data) < 20: while len(data) < 20:
data = (data + b'\x00' * 20)[:20] data = (data + b'\x00' * 20)[:20]
@ -172,15 +165,13 @@ def _open(args):
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.' % print('.. Opened handle %r, vendor %r product %r serial %r.' %
(handle, _hid.get_manufacturer(handle), _hid.get_product(handle), (handle, _hid.get_manufacturer(handle), _hid.get_product(handle), _hid.get_serial(handle)))
_hid.get_serial(handle)))
if args.hidpp: if args.hidpp:
if _hid.get_manufacturer(handle) != b'Logitech': if _hid.get_manufacturer(handle) != b'Logitech':
sys.exit('!! Only Logitech devices support the HID++ protocol.') sys.exit('!! Only Logitech devices support the HID++ protocol.')
print('.. HID++ validation enabled.') print('.. HID++ validation enabled.')
else: else:
if (_hid.get_manufacturer(handle) == b'Logitech' if (_hid.get_manufacturer(handle) == b'Logitech' and b'Receiver' in _hid.get_product(handle)):
and b'Receiver' in _hid.get_product(handle)):
args.hidpp = True args.hidpp = True
print('.. Logitech receiver detected, HID++ validation enabled.') print('.. Logitech receiver detected, HID++ validation enabled.')
@ -195,17 +186,12 @@ def _open(args):
def _parse_arguments(): def _parse_arguments():
import argparse import argparse
arg_parser = argparse.ArgumentParser() arg_parser = argparse.ArgumentParser()
arg_parser.add_argument( arg_parser.add_argument('--history', help='history file (default ~/.hidconsole-history)')
'--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('--hidpp', arg_parser.add_argument('device',
action='store_true', nargs='?',
help='ensure input data is a valid HID++ request') help='linux device to connect to (/dev/hidrawX); '
arg_parser.add_argument( '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() return arg_parser.parse_args()
@ -214,15 +200,12 @@ def main():
handle = _open(args) handle = _open(args)
if interactive: if interactive:
print( print('.. Press ^C/^D to exit, or type hex bytes to write to the device.')
'.. Press ^C/^D to exit, or type hex bytes to write to the device.'
)
import readline import readline
if args.history is None: if args.history is None:
import os.path import os.path
args.history = os.path.join(os.path.expanduser('~'), args.history = os.path.join(os.path.expanduser('~'), '.hidconsole-history')
'.hidconsole-history')
try: try:
readline.read_history_file(args.history) readline.read_history_file(args.history)
except Exception: except Exception:
@ -237,8 +220,7 @@ def main():
if interactive: if interactive:
# move the cursor at the bottom of the screen # move the cursor at the bottom of the screen
sys.stdout.write( sys.stdout.write('\033[300B') # move cusor at most 300 lines down, don't scroll
'\033[300B') # move cusor at most 300 lines down, don't scroll
while t.is_alive(): while t.is_alive():
line = read_packet(prompt) line = read_packet(prompt)

View File

@ -95,8 +95,7 @@ def _match(action, device, filter):
pid = usb_device.get('ID_MODEL_ID') pid = usb_device.get('ID_MODEL_ID')
if vid is None or pid is None: if vid is None or pid is None:
return # there are reports that sometimes the usb_device isn't set up right so be defensive return # there are reports that sometimes the usb_device isn't set up right so be defensive
if not ((vendor_id is None or vendor_id == int(vid, 16)) and if not ((vendor_id is None or vendor_id == int(vid, 16)) and (product_id is None or product_id == int(pid, 16))):
(product_id is None or product_id == int(pid, 16))):
return return
if action == 'add': if action == 'add':
@ -115,11 +114,9 @@ def _match(action, device, filter):
intf_device = device.find_parent('usb', 'usb_interface') intf_device = device.find_parent('usb', 'usb_interface')
# print ("*** usb interface", action, device, "usb_interface:", intf_device) # print ("*** usb interface", action, device, "usb_interface:", intf_device)
if interface_number is None: if interface_number is None:
usb_interface = None if intf_device is None else intf_device.attributes.asint( usb_interface = None if intf_device is None else intf_device.attributes.asint('bInterfaceNumber')
'bInterfaceNumber')
else: else:
usb_interface = None if intf_device is None else intf_device.attributes.asint( usb_interface = None if intf_device is None else intf_device.attributes.asint('bInterfaceNumber')
'bInterfaceNumber')
if usb_interface is None or interface_number != usb_interface: if usb_interface is None or interface_number != usb_interface:
return return
@ -186,18 +183,15 @@ def monitor_glib(callback, *device_filters):
try: try:
# io_add_watch_full may not be available... # io_add_watch_full may not be available...
GLib.io_add_watch_full(m, GLib.PRIORITY_LOW, GLib.IO_IN, GLib.io_add_watch_full(m, GLib.PRIORITY_LOW, GLib.IO_IN, _process_udev_event, callback, device_filters)
_process_udev_event, callback, device_filters)
# print ("did io_add_watch_full") # print ("did io_add_watch_full")
except AttributeError: except AttributeError:
try: try:
# and the priority parameter appeared later in the API # and the priority parameter appeared later in the API
GLib.io_add_watch(m, GLib.PRIORITY_LOW, GLib.IO_IN, GLib.io_add_watch(m, GLib.PRIORITY_LOW, GLib.IO_IN, _process_udev_event, callback, device_filters)
_process_udev_event, callback, device_filters)
# print ("did io_add_watch with priority") # print ("did io_add_watch with priority")
except Exception: except Exception:
GLib.io_add_watch(m, GLib.IO_IN, _process_udev_event, callback, GLib.io_add_watch(m, GLib.IO_IN, _process_udev_event, callback, device_filters)
device_filters)
# print ("did io_add_watch") # print ("did io_add_watch")
m.start() m.start()
@ -288,9 +282,7 @@ def write(device_handle, data):
else: else:
break break
if bytes_written != len(data): if bytes_written != len(data):
raise IOError( raise IOError(_errno.EIO, 'written %d bytes out of expected %d' % (bytes_written, len(data)))
_errno.EIO,
'written %d bytes out of expected %d' % (bytes_written, len(data)))
def read(device_handle, bytes_count, timeout_ms=-1): def read(device_handle, bytes_count, timeout_ms=-1):
@ -311,13 +303,11 @@ def read(device_handle, bytes_count, timeout_ms=-1):
""" """
assert device_handle assert device_handle
timeout = None if timeout_ms < 0 else timeout_ms / 1000.0 timeout = None if timeout_ms < 0 else timeout_ms / 1000.0
rlist, wlist, xlist = _select([device_handle], [], [device_handle], rlist, wlist, xlist = _select([device_handle], [], [device_handle], timeout)
timeout)
if xlist: if xlist:
assert xlist == [device_handle] assert xlist == [device_handle]
raise IOError(_errno.EIO, raise IOError(_errno.EIO, 'exception on file descriptor %d' % device_handle)
'exception on file descriptor %d' % device_handle)
if rlist: if rlist:
assert rlist == [device_handle] assert rlist == [device_handle]

View File

@ -50,12 +50,7 @@ _MEDIUM_MESSAGE_SIZE = 15
_MAX_READ_SIZE = 32 _MAX_READ_SIZE = 32
# mapping from report_id to message length # mapping from report_id to message length
report_lengths = { report_lengths = {0x10: _SHORT_MESSAGE_SIZE, 0x11: _LONG_MESSAGE_SIZE, 0x20: _MEDIUM_MESSAGE_SIZE, 0x21: _MAX_READ_SIZE}
0x10: _SHORT_MESSAGE_SIZE,
0x11: _LONG_MESSAGE_SIZE,
0x20: _MEDIUM_MESSAGE_SIZE,
0x21: _MAX_READ_SIZE
}
"""Default timeout on read (in seconds).""" """Default timeout on read (in seconds)."""
DEFAULT_TIMEOUT = 4 DEFAULT_TIMEOUT = 4
# the receiver itself should reply very fast, within 500ms # the receiver itself should reply very fast, within 500ms
@ -176,14 +171,12 @@ def write(handle, devnumber, data):
else: else:
wdata = _pack('!BB5s', 0x10, devnumber, data) wdata = _pack('!BB5s', 0x10, devnumber, data)
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('(%s) <= w[%02X %02X %s %s]', handle, ord(wdata[:1]), _log.debug('(%s) <= w[%02X %02X %s %s]', handle, ord(wdata[:1]), devnumber, _strhex(wdata[2:4]), _strhex(wdata[4:]))
devnumber, _strhex(wdata[2:4]), _strhex(wdata[4:]))
try: try:
_hid.write(int(handle), wdata) _hid.write(int(handle), wdata)
except Exception as reason: except Exception as reason:
_log.error('write failed, assuming handle %r no longer available', _log.error('write failed, assuming handle %r no longer available', handle)
handle)
close(handle) close(handle)
raise NoReceiver(reason=reason) raise NoReceiver(reason=reason)
@ -214,8 +207,7 @@ def check_message(data):
if report_lengths.get(report_id) == len(data): if report_lengths.get(report_id) == len(data):
return True return True
else: else:
_log.warn('unexpected message size: report_id %02X message %s' % _log.warn('unexpected message size: report_id %02X message %s' % (report_id, _strhex(data)))
(report_id, _strhex(data)))
return False return False
@ -233,8 +225,7 @@ def _read(handle, timeout):
timeout = int(timeout * 1000) timeout = int(timeout * 1000)
data = _hid.read(int(handle), _MAX_READ_SIZE, timeout) data = _hid.read(int(handle), _MAX_READ_SIZE, timeout)
except Exception as reason: except Exception as reason:
_log.error('read failed, assuming handle %r no longer available', _log.error('read failed, assuming handle %r no longer available', handle)
handle)
close(handle) close(handle)
raise NoReceiver(reason=reason) raise NoReceiver(reason=reason)
@ -243,8 +234,7 @@ def _read(handle, timeout):
devnumber = ord(data[1:2]) devnumber = ord(data[1:2])
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('(%s) => r[%02X %02X %s %s]', handle, report_id, _log.debug('(%s) => r[%02X %02X %s %s]', handle, report_id, devnumber, _strhex(data[2:4]), _strhex(data[4:]))
devnumber, _strhex(data[2:4]), _strhex(data[4:]))
return report_id, devnumber, data[2:] return report_id, devnumber, data[2:]
@ -265,8 +255,7 @@ def _skip_incoming(handle, ihandle, notifications_hook):
# read whatever is already in the buffer, if any # read whatever is already in the buffer, if any
data = _hid.read(ihandle, _MAX_READ_SIZE, 0) data = _hid.read(ihandle, _MAX_READ_SIZE, 0)
except Exception as reason: except Exception as reason:
_log.error('read failed, assuming receiver %s no longer available', _log.error('read failed, assuming receiver %s no longer available', handle)
handle)
close(handle) close(handle)
raise NoReceiver(reason=reason) raise NoReceiver(reason=reason)
@ -309,10 +298,9 @@ def make_notification(devnumber, data):
return _HIDPP_Notification(devnumber, sub_id, address, data[2:]) return _HIDPP_Notification(devnumber, sub_id, address, data[2:])
_HIDPP_Notification = namedtuple('_HIDPP_Notification', _HIDPP_Notification = namedtuple('_HIDPP_Notification', ('devnumber', 'sub_id', 'address', 'data'))
('devnumber', 'sub_id', 'address', 'data')) _HIDPP_Notification.__str__ = lambda self: 'Notification(%d,%02X,%02X,%s)' % (self.devnumber, self.sub_id, self.address,
_HIDPP_Notification.__str__ = lambda self: 'Notification(%d,%02X,%02X,%s)' % ( _strhex(self.data))
self.devnumber, self.sub_id, self.address, _strhex(self.data))
_HIDPP_Notification.__unicode__ = _HIDPP_Notification.__str__ _HIDPP_Notification.__unicode__ = _HIDPP_Notification.__str__
DJ_NOTIFICATION_LENGTH = _MEDIUM_MESSAGE_SIZE - 4 # to allow easy distinguishing of DJ notifications DJ_NOTIFICATION_LENGTH = _MEDIUM_MESSAGE_SIZE - 4 # to allow easy distinguishing of DJ notifications
del namedtuple del namedtuple
@ -352,8 +340,7 @@ def request(handle, devnumber, request_id, *params):
timeout *= 2 timeout *= 2
if params: if params:
params = b''.join( params = b''.join(_pack('B', p) if isinstance(p, int) else p for p in params)
_pack('B', p) if isinstance(p, int) else p for p in params)
else: else:
params = b'' params = b''
# if _log.isEnabledFor(_DEBUG): # if _log.isEnabledFor(_DEBUG):
@ -375,8 +362,7 @@ def request(handle, devnumber, request_id, *params):
if reply: if reply:
report_id, reply_devnumber, reply_data = reply report_id, reply_devnumber, reply_data = reply
if reply_devnumber == devnumber: if reply_devnumber == devnumber:
if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[ if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2]:
1:3] == request_data[:2]:
error = ord(reply_data[3:4]) error = ord(reply_data[3:4])
# if error == _hidpp10.ERROR.resource_error: # device unreachable # if error == _hidpp10.ERROR.resource_error: # device unreachable
@ -388,24 +374,16 @@ def request(handle, devnumber, request_id, *params):
# raise NoSuchDevice(number=devnumber, request=request_id) # raise NoSuchDevice(number=devnumber, request=request_id)
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug( _log.debug('(%s) device 0x%02X error on request {%04X}: %d = %s', handle, devnumber, request_id, error,
'(%s) device 0x%02X error on request {%04X}: %d = %s', _hidpp10.ERROR[error])
handle, devnumber, request_id, error,
_hidpp10.ERROR[error])
return return
if reply_data[:1] == b'\xFF' and reply_data[ if reply_data[:1] == b'\xFF' and reply_data[1:3] == request_data[:2]:
1:3] == request_data[:2]:
# a HID++ 2.0 feature call returned with an error # a HID++ 2.0 feature call returned with an error
error = ord(reply_data[3:4]) error = ord(reply_data[3:4])
_log.error( _log.error('(%s) device %d error on feature request {%04X}: %d = %s', handle, devnumber, request_id, error,
'(%s) device %d error on feature request {%04X}: %d = %s', _hidpp20.ERROR[error])
handle, devnumber, request_id, error, raise _hidpp20.FeatureCallError(number=devnumber, request=request_id, error=error, params=params)
_hidpp20.ERROR[error])
raise _hidpp20.FeatureCallError(number=devnumber,
request=request_id,
error=error,
params=params)
if reply_data[:2] == request_data[:2]: if reply_data[:2] == request_data[:2]:
if request_id & 0xFE00 == 0x8200: if request_id & 0xFE00 == 0x8200:
@ -445,8 +423,8 @@ def request(handle, devnumber, request_id, *params):
# if _log.isEnabledFor(_DEBUG): # if _log.isEnabledFor(_DEBUG):
# _log.debug("(%s) still waiting for reply, delta %f", handle, delta) # _log.debug("(%s) still waiting for reply, delta %f", handle, delta)
_log.warn('timeout (%0.2f/%0.2f) on device %d request {%04X} params [%s]', _log.warn('timeout (%0.2f/%0.2f) on device %d request {%04X} params [%s]', delta, timeout, devnumber, request_id,
delta, timeout, devnumber, request_id, _strhex(params)) _strhex(params))
# raise DeviceUnreachable(number=devnumber, request=request_id) # raise DeviceUnreachable(number=devnumber, request=request_id)
@ -486,13 +464,11 @@ def ping(handle, devnumber):
if reply: if reply:
report_id, reply_devnumber, reply_data = reply report_id, reply_devnumber, reply_data = reply
if reply_devnumber == devnumber: if reply_devnumber == devnumber:
if reply_data[:2] == request_data[:2] and reply_data[ if reply_data[:2] == request_data[:2] and reply_data[4:5] == request_data[-1:]:
4:5] == request_data[-1:]:
# HID++ 2.0+ device, currently connected # HID++ 2.0+ device, currently connected
return ord(reply_data[2:3]) + ord(reply_data[3:4]) / 10.0 return ord(reply_data[2:3]) + ord(reply_data[3:4]) / 10.0
if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[ if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2]:
1:3] == request_data[:2]:
assert reply_data[-1:] == b'\x00' assert reply_data[-1:] == b'\x00'
error = ord(reply_data[3:4]) error = ord(reply_data[3:4])
@ -503,11 +479,8 @@ def ping(handle, devnumber):
return return
if error == _hidpp10.ERROR.unknown_device: # no paired device with that number if error == _hidpp10.ERROR.unknown_device: # no paired device with that number
_log.error( _log.error('(%s) device %d error on ping request: unknown device', handle, devnumber)
'(%s) device %d error on ping request: unknown device', raise NoSuchDevice(number=devnumber, request=request_id)
handle, devnumber)
raise NoSuchDevice(number=devnumber,
request=request_id)
if notifications_hook: if notifications_hook:
n = make_notification(reply_devnumber, reply_data) n = make_notification(reply_devnumber, reply_data)
@ -518,6 +491,5 @@ def ping(handle, devnumber):
delta = _timestamp() - request_started delta = _timestamp() - request_started
_log.warn('(%s) timeout (%0.2f/%0.2f) on device %d ping', handle, delta, _log.warn('(%s) timeout (%0.2f/%0.2f) on device %d ping', handle, delta, _PING_TIMEOUT, devnumber)
_PING_TIMEOUT, devnumber)
# raise DeviceUnreachable(number=devnumber, request=request_id) # raise DeviceUnreachable(number=devnumber, request=request_id)

View File

@ -107,15 +107,11 @@ class NamedInts(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
def _readable_name(n): def _readable_name(n):
if not is_string(n): if not is_string(n):
raise TypeError('expected (unicode) string, got ' + raise TypeError('expected (unicode) string, got ' + str(type(n)))
str(type(n)))
return n.replace('__', '/').replace('_', ' ') return n.replace('__', '/').replace('_', ' ')
# print (repr(kwargs)) # print (repr(kwargs))
values = { values = {k: NamedInt(v, _readable_name(k)) for (k, v) in kwargs.items()}
k: NamedInt(v, _readable_name(k))
for (k, v) in kwargs.items()
}
self.__dict__ = values self.__dict__ = values
self._values = sorted(list(values.values())) self._values = sorted(list(values.values()))
self._indexed = {int(v): v for v in self._values} self._indexed = {int(v): v for v in self._values}
@ -129,15 +125,8 @@ class NamedInts(object):
return NamedInts(**values) return NamedInts(**values)
@classmethod @classmethod
def range(cls, def range(cls, from_value, to_value, name_generator=lambda x: str(x), step=1):
from_value, values = {name_generator(x): x for x in range(from_value, to_value + 1, step)}
to_value,
name_generator=lambda x: str(x),
step=1):
values = {
name_generator(x): x
for x in range(from_value, to_value + 1, step)
}
return NamedInts(**values) return NamedInts(**values)
def flag_names(self, value): def flag_names(self, value):
@ -169,13 +158,10 @@ class NamedInts(object):
if index.start is None and index.stop is None: if index.start is None and index.stop is None:
return self._values[:] return self._values[:]
v_start = int(self._values[0]) if index.start is None else int( v_start = int(self._values[0]) if index.start is None else int(index.start)
index.start) v_stop = (self._values[-1] + 1) if index.stop is None else int(index.stop)
v_stop = (self._values[-1] +
1) if index.stop is None else int(index.stop)
if v_start > v_stop or v_start > self._values[ if v_start > v_stop or v_start > self._values[-1] or v_stop <= self._values[0]:
-1] or v_stop <= self._values[0]:
return [] return []
if v_start <= self._values[0] and v_stop > self._values[-1]: if v_start <= self._values[0] and v_stop > self._values[-1]:
@ -282,14 +268,11 @@ class KwException(Exception):
"""Firmware information.""" """Firmware information."""
FirmwareInfo = namedtuple('FirmwareInfo', FirmwareInfo = namedtuple('FirmwareInfo', ['kind', 'name', 'version', 'extras'])
['kind', 'name', 'version', 'extras'])
"""Reprogrammable keys information.""" """Reprogrammable keys information."""
ReprogrammableKeyInfo = namedtuple('ReprogrammableKeyInfo', ReprogrammableKeyInfo = namedtuple('ReprogrammableKeyInfo', ['index', 'key', 'task', 'flags'])
['index', 'key', 'task', 'flags'])
ReprogrammableKeyInfoV4 = namedtuple('ReprogrammableKeyInfoV4', [ ReprogrammableKeyInfoV4 = namedtuple('ReprogrammableKeyInfoV4',
'index', 'key', 'task', 'flags', 'pos', 'group', 'group_mask', 'remapped' ['index', 'key', 'task', 'flags', 'pos', 'group', 'group_mask', 'remapped'])
])
del namedtuple del namedtuple

View File

@ -32,28 +32,18 @@ from .settings_templates import RegisterSettings as _RS
# #
_DeviceDescriptor = namedtuple('_DeviceDescriptor', _DeviceDescriptor = namedtuple('_DeviceDescriptor',
('name', 'kind', 'wpid', 'codename', 'protocol', ('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings', 'persister'))
'registers', 'settings', 'persister'))
del namedtuple del namedtuple
DEVICES = {} DEVICES = {}
def _D(name, def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, settings=None, persister=None):
codename=None,
kind=None,
wpid=None,
protocol=None,
registers=None,
settings=None,
persister=None):
assert name assert name
if kind is None: if kind is None:
kind = (_DK.mouse if 'Mouse' in name else kind = (_DK.mouse if 'Mouse' in name else _DK.keyboard if 'Keyboard' in name else _DK.numpad if 'Number Pad' in name
_DK.keyboard if 'Keyboard' in name else _DK.numpad else _DK.touchpad if 'Touchpad' in name else _DK.trackball if 'Trackball' in name else None)
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
# heuristic: the codename is the last word in the device name # heuristic: the codename is the last word in the device name
@ -73,17 +63,12 @@ def _D(name,
if wpid: 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: if protocol > 1.0:
assert w[0:1] == '4', '%s has protocol %0.1f, wpid %s' % ( assert w[0:1] == '4', '%s has protocol %0.1f, wpid %s' % (name, protocol, w)
name, protocol, w)
else: else:
if w[0:1] == '1': if w[0:1] == '1':
assert kind == _DK.mouse, '%s has protocol %0.1f, wpid %s' % ( assert kind == _DK.mouse, '%s has protocol %0.1f, wpid %s' % (name, protocol, w)
name, protocol, w)
elif w[0:1] == '2': elif w[0:1] == '2':
assert kind in ( assert kind in (_DK.keyboard, _DK.numpad), '%s has protocol %0.1f, wpid %s' % (name, protocol, w)
_DK.keyboard,
_DK.numpad), '%s has protocol %0.1f, wpid %s' % (
name, protocol, w)
device_descriptor = _DeviceDescriptor(name=name, device_descriptor = _DeviceDescriptor(name=name,
kind=kind, kind=kind,
@ -94,8 +79,7 @@ def _D(name,
settings=settings, settings=settings,
persister=persister) persister=persister)
assert codename not in DEVICES, 'duplicate codename in device descriptors: %s' % ( assert codename not in DEVICES, 'duplicate codename in device descriptors: %s' % (DEVICES[codename], )
DEVICES[codename], )
DEVICES[codename] = device_descriptor DEVICES[codename] = device_descriptor
if wpid: if wpid:
@ -103,8 +87,7 @@ def _D(name,
wpid = (wpid, ) wpid = (wpid, )
for w in wpid: for w in wpid:
assert w not in DEVICES, 'duplicate wpid in device descriptors: %s' % ( assert w not in DEVICES, 'duplicate wpid in device descriptors: %s' % (DEVICES[w], )
DEVICES[w], )
DEVICES[w] = device_descriptor DEVICES[w] = device_descriptor
@ -112,8 +95,7 @@ def _D(name,
# #
# #
_PERFORMANCE_MX_DPIS = _NamedInts.range(0x81, 0x8F, lambda x: str( _PERFORMANCE_MX_DPIS = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100))
(x - 0x80) * 100))
# #
# #
@ -349,12 +331,9 @@ _D(
) )
_D('Wireless Mouse M315') _D('Wireless Mouse M315')
_D('Wireless Mouse M317') _D('Wireless Mouse M317')
_D('Wireless Mouse M325', _D('Wireless Mouse M325', protocol=2.0, wpid='400A', settings=[
protocol=2.0, _FS.hi_res_scroll(),
wpid='400A', ])
settings=[
_FS.hi_res_scroll(),
])
_D('Wireless Mouse M345', protocol=2.0, wpid='4017') _D('Wireless Mouse M345', protocol=2.0, wpid='4017')
_D( _D(
'Wireless Mouse M350', 'Wireless Mouse M350',
@ -384,13 +363,9 @@ _D(
_RS.side_scroll(), _RS.side_scroll(),
], ],
) )
_D('Wireless Mouse M510', _D('Wireless Mouse M510', codename='M510v2', protocol=2.0, wpid='4051', settings=[
codename='M510v2', _FS.lowres_smooth_scroll(),
protocol=2.0, ])
wpid='4051',
settings=[
_FS.lowres_smooth_scroll(),
])
_D('Couch Mouse M515', protocol=2.0, wpid='4007') _D('Couch Mouse M515', protocol=2.0, wpid='4007')
_D('Wireless Mouse M525', protocol=2.0, wpid='4013') _D('Wireless Mouse M525', protocol=2.0, wpid='4013')
_D( _D(
@ -486,10 +461,7 @@ _D(
], ],
) )
_D('Wireless Mouse MX Vertical', _D('Wireless Mouse MX Vertical', codename='MX Vertical', protocol=4.5, wpid='407B')
codename='MX Vertical',
protocol=4.5,
wpid='407B')
_D( _D(
'G7 Cordless Laser Mouse', 'G7 Cordless Laser Mouse',

View File

@ -36,12 +36,7 @@ del getLogger
# documentation, some of them guessed. # documentation, some of them guessed.
# #
DEVICE_KIND = _NamedInts(keyboard=0x01, DEVICE_KIND = _NamedInts(keyboard=0x01, mouse=0x02, numpad=0x03, presenter=0x04, trackball=0x08, touchpad=0x09)
mouse=0x02,
numpad=0x03,
presenter=0x04,
trackball=0x08,
touchpad=0x09)
POWER_SWITCH_LOCATION = _NamedInts(base=0x01, POWER_SWITCH_LOCATION = _NamedInts(base=0x01,
top_case=0x02, top_case=0x02,
@ -70,12 +65,10 @@ POWER_SWITCH_LOCATION = _NamedInts(base=0x01,
NOTIFICATION_FLAG = _NamedInts( NOTIFICATION_FLAG = _NamedInts(
battery_status=0x100000, # send battery charge notifications (0x07 or 0x0D) battery_status=0x100000, # send battery charge notifications (0x07 or 0x0D)
keyboard_sleep_raw=0x020000, # system control keys such as Sleep keyboard_sleep_raw=0x020000, # system control keys such as Sleep
keyboard_multimedia_raw= keyboard_multimedia_raw=0x010000, # consumer controls such as Mute and Calculator
0x010000, # consumer controls such as Mute and Calculator
# reserved_r1b4= 0x001000, # unknown, seen on a unifying receiver # reserved_r1b4= 0x001000, # unknown, seen on a unifying receiver
software_present=0x000800, # .. no idea software_present=0x000800, # .. no idea
keyboard_illumination= keyboard_illumination=0x000200, # illumination brightness level changes (by pressing keys)
0x000200, # illumination brightness level changes (by pressing keys)
wireless=0x000100, # notify when the device wireless goes on/off-line wireless=0x000100, # notify when the device wireless goes on/off-line
) )
@ -92,10 +85,7 @@ ERROR = _NamedInts(invalid_SubID__command=0x01,
unsupported_parameter_value=0x0B, unsupported_parameter_value=0x0B,
wrong_pin_code=0x0C) wrong_pin_code=0x0C)
PAIRING_ERRORS = _NamedInts(device_timeout=0x01, PAIRING_ERRORS = _NamedInts(device_timeout=0x01, device_not_supported=0x02, too_many_devices=0x03, sequence_timeout=0x06)
device_not_supported=0x02,
too_many_devices=0x03,
sequence_timeout=0x06)
BATTERY_APPOX = _NamedInts(empty=0, critical=5, low=20, good=50, full=90) BATTERY_APPOX = _NamedInts(empty=0, critical=5, low=20, good=50, full=90)
"""Known registers. """Known registers.
@ -129,16 +119,14 @@ REGISTERS = _NamedInts(
def read_register(device, register_number, *params): def read_register(device, register_number, *params):
assert device, 'tried to read register %02X from invalid device %s' % ( assert device, 'tried to read register %02X from invalid device %s' % (register_number, device)
register_number, device)
# support long registers by adding a 2 in front of the register number # support long registers by adding a 2 in front of the register number
request_id = 0x8100 | (int(register_number) & 0x2FF) request_id = 0x8100 | (int(register_number) & 0x2FF)
return device.request(request_id, *params) return device.request(request_id, *params)
def write_register(device, register_number, *value): def write_register(device, register_number, *value):
assert device, 'tried to write register %02X to invalid device %s' % ( assert device, 'tried to write register %02X to invalid device %s' % (register_number, device)
register_number, device)
# support long registers by adding a 2 in front of the register number # support long registers by adding a 2 in front of the register number
request_id = 0x8000 | (int(register_number) & 0x2FF) request_id = 0x8000 | (int(register_number) & 0x2FF)
return device.request(request_id, *value) return device.request(request_id, *value)
@ -179,9 +167,8 @@ def parse_battery_status(register, reply):
if register == REGISTERS.battery_charge: if register == REGISTERS.battery_charge:
charge = ord(reply[:1]) charge = ord(reply[:1])
status_byte = ord(reply[2:3]) & 0xF0 status_byte = ord(reply[2:3]) & 0xF0
status_text = (BATTERY_STATUS.discharging if status_byte == 0x30 else status_text = (BATTERY_STATUS.discharging if status_byte == 0x30 else BATTERY_STATUS.recharging
BATTERY_STATUS.recharging if status_byte == 0x50 else if status_byte == 0x50 else BATTERY_STATUS.full if status_byte == 0x90 else None)
BATTERY_STATUS.full if status_byte == 0x90 else None)
return charge, status_text, None return charge, status_text, None
if register == REGISTERS.battery_status: if register == REGISTERS.battery_status:
@ -202,8 +189,7 @@ def parse_battery_status(register, reply):
elif charging_byte & 0x22 == 0x22: elif charging_byte & 0x22 == 0x22:
status_text = BATTERY_STATUS.full status_text = BATTERY_STATUS.full
else: else:
_log.warn('could not parse 0x07 battery status: %02X (level %02X)', _log.warn('could not parse 0x07 battery status: %02X (level %02X)', charging_byte, status_byte)
charging_byte, status_byte)
status_text = None status_text = None
if charging_byte & 0x03 and status_byte == 0: if charging_byte & 0x03 and status_byte == 0:
@ -321,6 +307,5 @@ def set_notification_flags(device, *flag_bits):
flag_bits = sum(int(b) for b in flag_bits) flag_bits = sum(int(b) for b in flag_bits)
assert flag_bits & 0x00FFFFFF == flag_bits assert flag_bits & 0x00FFFFFF == flag_bits
result = write_register(device, REGISTERS.notifications, result = write_register(device, REGISTERS.notifications, _int2bytes(flag_bits, 3))
_int2bytes(flag_bits, 3))
return result is not None return result is not None

View File

@ -169,13 +169,9 @@ DEVICE_KIND = _NamedInts(keyboard=0x00,
presenter=0x06, presenter=0x06,
receiver=0x07) receiver=0x07)
FIRMWARE_KIND = _NamedInts(Firmware=0x00, FIRMWARE_KIND = _NamedInts(Firmware=0x00, Bootloader=0x01, Hardware=0x02, Other=0x03)
Bootloader=0x01,
Hardware=0x02,
Other=0x03)
BATTERY_OK = lambda status: status not in (BATTERY_STATUS.invalid_battery, BATTERY_OK = lambda status: status not in (BATTERY_STATUS.invalid_battery, BATTERY_STATUS.thermal_error)
BATTERY_STATUS.thermal_error)
BATTERY_STATUS = _NamedInts(discharging=0x00, BATTERY_STATUS = _NamedInts(discharging=0x00,
recharging=0x01, recharging=0x01,
@ -185,10 +181,7 @@ BATTERY_STATUS = _NamedInts(discharging=0x00,
invalid_battery=0x05, invalid_battery=0x05,
thermal_error=0x06) thermal_error=0x06)
CHARGE_STATUS = _NamedInts(charging=0x00, CHARGE_STATUS = _NamedInts(charging=0x00, full=0x01, not_charging=0x02, error=0x07)
full=0x01,
not_charging=0x02,
error=0x07)
CHARGE_LEVEL = _NamedInts(average=50, full=90, critical=5) CHARGE_LEVEL = _NamedInts(average=50, full=90, critical=5)
@ -259,8 +252,7 @@ class FeaturesArray(object):
self.device = None self.device = None
return False return False
reply = self.device.request(0x0000, _pack('!H', reply = self.device.request(0x0000, _pack('!H', FEATURE.FEATURE_SET))
FEATURE.FEATURE_SET))
if reply is None: if reply is None:
self.supported = False self.supported = False
else: else:
@ -268,9 +260,7 @@ class FeaturesArray(object):
if fs_index: if fs_index:
count = self.device.request(fs_index << 8) count = self.device.request(fs_index << 8)
if count is None: if count is None:
_log.warn( _log.warn('FEATURE_SET found, but failed to read features count')
'FEATURE_SET found, but failed to read features count'
)
# most likely the device is unavailable # most likely the device is unavailable
return False return False
else: else:
@ -294,8 +284,7 @@ class FeaturesArray(object):
raise IndexError(index) raise IndexError(index)
if self.features[index] is None: if self.features[index] is None:
feature = self.device.feature_request( feature = self.device.feature_request(FEATURE.FEATURE_SET, 0x10, index)
FEATURE.FEATURE_SET, 0x10, index)
if feature: if feature:
feature, = _unpack('!H', feature[:2]) feature, = _unpack('!H', feature[:2])
self.features[index] = FEATURE[feature] self.features[index] = FEATURE[feature]
@ -386,30 +375,23 @@ class KeysArray(object):
# TODO: add here additional variants for other REPROG_CONTROLS # TODO: add here additional variants for other REPROG_CONTROLS
if self.keys[index] is None: if self.keys[index] is None:
keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS, keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS, 0x10, index)
0x10, index)
self.keyversion = 1 self.keyversion = 1
if keydata is None: if keydata is None:
keydata = feature_request(self.device, keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x10, index)
FEATURE.REPROG_CONTROLS_V4, 0x10,
index)
self.keyversion = 4 self.keyversion = 4
if keydata: if keydata:
key, key_task, flags, pos, group, gmask = _unpack( key, key_task, flags, pos, group, gmask = _unpack('!HHBBBB', keydata[:8])
'!HHBBBB', keydata[:8])
ctrl_id_text = special_keys.CONTROL[key] ctrl_id_text = special_keys.CONTROL[key]
ctrl_task_text = special_keys.TASK[key_task] ctrl_task_text = special_keys.TASK[key_task]
if self.keyversion == 1: if self.keyversion == 1:
self.keys[index] = _ReprogrammableKeyInfo( self.keys[index] = _ReprogrammableKeyInfo(index, ctrl_id_text, ctrl_task_text, flags)
index, ctrl_id_text, ctrl_task_text, flags)
if self.keyversion == 4: if self.keyversion == 4:
try: try:
mapped_data = feature_request( mapped_data = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x20, key & 0xff00,
self.device, FEATURE.REPROG_CONTROLS_V4, 0x20, key & 0xff)
key & 0xff00, key & 0xff)
if mapped_data: if mapped_data:
remap_key, remap_flag, remapped = _unpack( remap_key, remap_flag, remapped = _unpack('!HBH', mapped_data[:5])
'!HBH', mapped_data[:5])
# if key not mapped map it to itself for display # if key not mapped map it to itself for display
if remapped == 0: if remapped == 0:
remapped = key remapped = key
@ -419,9 +401,8 @@ class KeysArray(object):
# remap_flag = 0 # remap_flag = 0
remapped_text = special_keys.CONTROL[remapped] remapped_text = special_keys.CONTROL[remapped]
self.keys[index] = _ReprogrammableKeyInfoV4( self.keys[index] = _ReprogrammableKeyInfoV4(index, ctrl_id_text, ctrl_task_text, flags, pos, group,
index, ctrl_id_text, ctrl_task_text, flags, pos, gmask, remapped_text)
group, gmask, remapped_text)
return self.keys[index] return self.keys[index]
@ -457,8 +438,7 @@ def feature_request(device, feature, function=0x00, *params):
if device.online and device.features: if device.online and device.features:
if feature in device.features: if feature in device.features:
feature_index = device.features.index(int(feature)) feature_index = device.features.index(int(feature))
return device.request((feature_index << 8) + (function & 0xFF), return device.request((feature_index << 8) + (function & 0xFF), *params)
*params)
def get_firmware(device): def get_firmware(device):
@ -472,23 +452,18 @@ def get_firmware(device):
fw = [] fw = []
for index in range(0, count): for index in range(0, count):
fw_info = feature_request(device, FEATURE.DEVICE_FW_VERSION, 0x10, fw_info = feature_request(device, FEATURE.DEVICE_FW_VERSION, 0x10, index)
index)
if fw_info: if fw_info:
level = ord(fw_info[:1]) & 0x0F level = ord(fw_info[:1]) & 0x0F
if level == 0 or level == 1: if level == 0 or level == 1:
name, version_major, version_minor, build = _unpack( name, version_major, version_minor, build = _unpack('!3sBBH', fw_info[1:8])
'!3sBBH', fw_info[1:8])
version = '%02X.%02X' % (version_major, version_minor) version = '%02X.%02X' % (version_major, version_minor)
if build: if build:
version += '.B%04X' % build version += '.B%04X' % build
extras = fw_info[9:].rstrip(b'\x00') or None extras = fw_info[9:].rstrip(b'\x00') or None
fw_info = _FirmwareInfo(FIRMWARE_KIND[level], fw_info = _FirmwareInfo(FIRMWARE_KIND[level], name.decode('ascii'), version, extras)
name.decode('ascii'), version,
extras)
elif level == FIRMWARE_KIND.Hardware: elif level == FIRMWARE_KIND.Hardware:
fw_info = _FirmwareInfo(FIRMWARE_KIND.Hardware, '', fw_info = _FirmwareInfo(FIRMWARE_KIND.Hardware, '', str(ord(fw_info[1:2])), None)
str(ord(fw_info[1:2])), None)
else: else:
fw_info = _FirmwareInfo(FIRMWARE_KIND.Other, '', '', None) fw_info = _FirmwareInfo(FIRMWARE_KIND.Other, '', '', None)
@ -525,14 +500,11 @@ def get_name(device):
name = b'' name = b''
while len(name) < name_length: while len(name) < name_length:
fragment = feature_request(device, FEATURE.DEVICE_NAME, 0x10, fragment = feature_request(device, FEATURE.DEVICE_NAME, 0x10, len(name))
len(name))
if fragment: if fragment:
name += fragment[:name_length - len(name)] name += fragment[:name_length - len(name)]
else: else:
_log.error( _log.error('failed to read whole name of %s (expected %d chars)', device, name_length)
'failed to read whole name of %s (expected %d chars)',
device, name_length)
return None return None
return name.decode('ascii') return name.decode('ascii')
@ -545,10 +517,8 @@ def get_battery(device):
discharge, dischargeNext, status = _unpack('!BBB', battery[:3]) discharge, dischargeNext, status = _unpack('!BBB', battery[:3])
discharge = None if discharge == 0 else discharge discharge = None if discharge == 0 else discharge
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug( _log.debug('device %d battery %d%% charged, next level %d%% charge, status %d = %s', device.number, discharge,
'device %d battery %d%% charged, next level %d%% charge, status %d = %s', dischargeNext, status, BATTERY_STATUS[status])
device.number, discharge, dischargeNext, status,
BATTERY_STATUS[status])
return discharge, BATTERY_STATUS[status], dischargeNext return discharge, BATTERY_STATUS[status], dischargeNext
@ -614,12 +584,10 @@ def get_mouse_pointer_info(device):
def get_vertical_scrolling_info(device): def get_vertical_scrolling_info(device):
vertical_scrolling_info = feature_request(device, vertical_scrolling_info = feature_request(device, FEATURE.VERTICAL_SCROLLING)
FEATURE.VERTICAL_SCROLLING)
if vertical_scrolling_info: if vertical_scrolling_info:
roller, ratchet, lines = _unpack('!BBB', vertical_scrolling_info[:3]) roller, ratchet, lines = _unpack('!BBB', vertical_scrolling_info[:3])
roller_type = ('reserved', 'standard', 'reserved', '3G', 'micro', roller_type = ('reserved', 'standard', 'reserved', '3G', 'micro', 'normal touch pad', 'inverted touch pad',
'normal touch pad', 'inverted touch pad',
'reserved')[roller] 'reserved')[roller]
return {'roller': roller_type, 'ratchet': ratchet, 'lines': lines} return {'roller': roller_type, 'ratchet': ratchet, 'lines': lines}

View File

@ -142,8 +142,7 @@ class EventsListener(_threading.Thread):
Incoming packets will be passed to the callback function in sequence. Incoming packets will be passed to the callback function in sequence.
""" """
def __init__(self, receiver, notifications_callback): def __init__(self, receiver, notifications_callback):
super(EventsListener, self).__init__(name=self.__class__.__name__ + super(EventsListener, self).__init__(name=self.__class__.__name__ + ':' + receiver.path.split('/')[2])
':' + receiver.path.split('/')[2])
self.daemon = True self.daemon = True
self._active = False self._active = False
@ -158,8 +157,7 @@ class EventsListener(_threading.Thread):
self._active = True self._active = True
# replace the handle with a threaded one # replace the handle with a threaded one
self.receiver.handle = _ThreadedHandle(self, self.receiver.path, self.receiver.handle = _ThreadedHandle(self, self.receiver.path, self.receiver.handle)
self.receiver.handle)
# get the right low-level handle for this thread # get the right low-level handle for this thread
ihandle = int(self.receiver.handle) ihandle = int(self.receiver.handle)
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):

View File

@ -72,8 +72,7 @@ def _process_receiver_notification(receiver, status, n):
# pairing lock notification # pairing lock notification
if n.sub_id == 0x4A: if n.sub_id == 0x4A:
status.lock_open = bool(n.address & 0x01) status.lock_open = bool(n.address & 0x01)
reason = (_('pairing lock is open') reason = (_('pairing lock is open') if status.lock_open else _('pairing lock is closed'))
if status.lock_open else _('pairing lock is closed'))
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
_log.info('%s: %s', receiver, reason) _log.info('%s: %s', receiver, reason)
@ -83,8 +82,7 @@ def _process_receiver_notification(receiver, status, n):
pair_error = ord(n.data[:1]) pair_error = ord(n.data[:1])
if pair_error: if pair_error:
status[ status[_K.ERROR] = error_string = _hidpp10.PAIRING_ERRORS[pair_error]
_K.ERROR] = error_string = _hidpp10.PAIRING_ERRORS[pair_error]
status.new_device = None status.new_device = None
_log.warn('pairing error %d: %s', pair_error, error_string) _log.warn('pairing error %d: %s', pair_error, error_string)
@ -124,8 +122,7 @@ def _process_device_notification(device, status, n):
try: try:
feature = device.features[n.sub_id] feature = device.features[n.sub_id]
except IndexError: except IndexError:
_log.warn('%s: notification from invalid feature index %02X: %s', _log.warn('%s: notification from invalid feature index %02X: %s', device, n.sub_id, n)
device, n.sub_id, n)
return False return False
return _process_feature_notification(device, status, n, feature) return _process_feature_notification(device, status, n, feature)
@ -157,8 +154,7 @@ def _process_dj_notification(device, status, n):
def _process_hidpp10_custom_notification(device, status, n): def _process_hidpp10_custom_notification(device, status, n):
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s (%s) custom notification %s', device, device.protocol, _log.debug('%s (%s) custom notification %s', device, device.protocol, n)
n)
if n.sub_id in (_R.battery_status, _R.battery_charge): if n.sub_id in (_R.battery_status, _R.battery_charge):
# message layout: 10 ix <register> <xx> <yy> <zz> <00> # message layout: 10 ix <register> <xx> <yy> <zz> <00>
@ -188,31 +184,22 @@ def _process_hidpp10_notification(device, status, n):
device.status = None device.status = None
if device.number in device.receiver: if device.number in device.receiver:
del device.receiver[device.number] del device.receiver[device.number]
status.changed(active=False, status.changed(active=False, alert=_ALERT.ALL, reason=_('unpaired'))
alert=_ALERT.ALL,
reason=_('unpaired'))
else: else:
_log.warn('%s: disconnection with unknown type %02X: %s', device, _log.warn('%s: disconnection with unknown type %02X: %s', device, n.address, n)
n.address, n)
return True return True
# wireless link notification # wireless link notification
if n.sub_id == 0x41: if n.sub_id == 0x41:
protocol_name = ( protocol_name = ('Bluetooth' if n.address == 0x01 else '27 MHz' if n.address == 0x02 else 'QUAD or eQUAD'
'Bluetooth' if n.address == 0x01 else '27 MHz' if n.address == 0x03 else 'eQUAD step 4 DJ' if n.address == 0x04 else 'DFU Lite' if n.address ==
if n.address == 0x02 else 'QUAD or eQUAD' if n.address == 0x03 else 0x05 else 'eQUAD step 4 Lite' if n.address == 0x06 else 'eQUAD step 4 Gaming' if n.address ==
'eQUAD step 4 DJ' if n.address == 0x04 else 'DFU Lite' if n. 0x07 else 'eQUAD step 4 for gamepads' if n.address == 0x08 else 'eQUAD nano Lite' if n.address ==
address == 0x05 else 'eQUAD step 4 Lite' if n.address == 0x0A else 'Lightspeed 1' if n.address == 0x0C else 'Lightspeed 1_1' if n.address == 0x0D else None)
0x06 else 'eQUAD step 4 Gaming' if n.address ==
0x07 else 'eQUAD step 4 for gamepads' if n.address ==
0x08 else 'eQUAD nano Lite' if n.address ==
0x0A else 'Lightspeed 1' if n.address ==
0x0C else 'Lightspeed 1_1' if n.address == 0x0D else None)
if protocol_name: if protocol_name:
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
wpid = _strhex(n.data[2:3] + n.data[1:2]) wpid = _strhex(n.data[2:3] + n.data[1:2])
assert wpid == device.wpid, '%s wpid mismatch, got %s' % ( assert wpid == device.wpid, '%s wpid mismatch, got %s' % (device, wpid)
device, wpid)
flags = ord(n.data[:1]) & 0xF0 flags = ord(n.data[:1]) & 0xF0
link_encrypted = bool(flags & 0x20) link_encrypted = bool(flags & 0x20)
@ -220,16 +207,12 @@ def _process_hidpp10_notification(device, status, n):
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
sw_present = bool(flags & 0x10) sw_present = bool(flags & 0x10)
has_payload = bool(flags & 0x80) has_payload = bool(flags & 0x80)
_log.debug( _log.debug('%s: %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s', device,
'%s: %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s', protocol_name, sw_present, link_encrypted, link_established, has_payload)
device, protocol_name, sw_present, link_encrypted,
link_established, has_payload)
status[_K.LINK_ENCRYPTED] = link_encrypted status[_K.LINK_ENCRYPTED] = link_encrypted
status.changed(active=link_established) status.changed(active=link_established)
else: else:
_log.warn( _log.warn('%s: connection notification with unknown protocol %02X: %s', device.number, n.address, n)
'%s: connection notification with unknown protocol %02X: %s',
device.number, n.address, n)
return True return True
@ -246,9 +229,7 @@ def _process_hidpp10_notification(device, status, n):
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: device powered on', device) _log.debug('%s: device powered on', device)
reason = status.to_string() or _('powered on') reason = status.to_string() or _('powered on')
status.changed(active=True, status.changed(active=True, alert=_ALERT.NOTIFICATION, reason=reason)
alert=_ALERT.NOTIFICATION,
reason=reason)
else: else:
_log.warn('%s: unknown %s', device, n) _log.warn('%s: unknown %s', device, n)
return True return True
@ -263,17 +244,14 @@ def _process_feature_notification(device, status, n, feature):
discharge_level = None if discharge_level == 0 else discharge_level discharge_level = None if discharge_level == 0 else discharge_level
discharge_next_level = ord(n.data[1:2]) discharge_next_level = ord(n.data[1:2])
battery_status = ord(n.data[2:3]) battery_status = ord(n.data[2:3])
status.set_battery_info(discharge_level, status.set_battery_info(discharge_level, _hidpp20.BATTERY_STATUS[battery_status], discharge_next_level)
_hidpp20.BATTERY_STATUS[battery_status],
discharge_next_level)
else: else:
_log.warn('%s: unknown BATTERY %s', device, n) _log.warn('%s: unknown BATTERY %s', device, n)
return True return True
if feature == _F.BATTERY_VOLTAGE: if feature == _F.BATTERY_VOLTAGE:
if n.address == 0x00: if n.address == 0x00:
level, status, voltage, _ignore, _ignore = _hidpp20.decipher_voltage( level, status, voltage, _ignore, _ignore = _hidpp20.decipher_voltage(n.data)
n.data)
status.set_battery_info(level, status, None, voltage) status.set_battery_info(level, status, None, voltage)
else: else:
_log.warn('%s: unknown VOLTAGE %s', device, n) _log.warn('%s: unknown VOLTAGE %s', device, n)
@ -293,9 +271,7 @@ def _process_feature_notification(device, status, n, feature):
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('wireless status: %s', n) _log.debug('wireless status: %s', n)
if n.data[0:3] == b'\x01\x01\x01': if n.data[0:3] == b'\x01\x01\x01':
status.changed(active=True, status.changed(active=True, alert=_ALERT.NOTIFICATION, reason='powered on')
alert=_ALERT.NOTIFICATION,
reason='powered on')
else: else:
_log.warn('%s: unknown WIRELESS %s', device, n) _log.warn('%s: unknown WIRELESS %s', device, n)
else: else:
@ -326,8 +302,7 @@ def _process_feature_notification(device, status, n, feature):
# trigger a new report chain # trigger a new report chain
reports_count = 15 reports_count = 15
reports_period = 2 # seconds reports_period = 2 # seconds
device.feature_request(_F.SOLAR_DASHBOARD, 0x00, reports_count, device.feature_request(_F.SOLAR_DASHBOARD, 0x00, reports_count, reports_period)
reports_period)
else: else:
_log.warn('%s: unknown SOLAR CHARGE %s', device, n) _log.warn('%s: unknown SOLAR CHARGE %s', device, n)
else: else:
@ -343,9 +318,7 @@ def _process_feature_notification(device, status, n, feature):
button_down = bool(touch & 0x02) button_down = bool(touch & 0x02)
mouse_lifted = bool(touch & 0x01) mouse_lifted = bool(touch & 0x01)
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
_log.info( _log.info('%s: TOUCH MOUSE status: button_down=%s mouse_lifted=%s', device, button_down, mouse_lifted)
'%s: TOUCH MOUSE status: button_down=%s mouse_lifted=%s',
device, button_down, mouse_lifted)
else: else:
_log.warn('%s: unknown TOUCH MOUSE %s', device, n) _log.warn('%s: unknown TOUCH MOUSE %s', device, n)
return True return True
@ -356,8 +329,7 @@ def _process_feature_notification(device, status, n, feature):
flags, delta_v = _unpack('>bh', n.data[:3]) flags, delta_v = _unpack('>bh', n.data[:3])
high_res = (flags & 0x10) != 0 high_res = (flags & 0x10) != 0
periods = flags & 0x0f periods = flags & 0x0f
_log.info('%s: WHEEL: res: %d periods: %d delta V:%-3d', _log.info('%s: WHEEL: res: %d periods: %d delta V:%-3d', device, high_res, periods, delta_v)
device, high_res, periods, delta_v)
return True return True
elif (n.address == 0x10): elif (n.address == 0x10):
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
@ -369,5 +341,4 @@ def _process_feature_notification(device, status, n, feature):
_log.warn('%s: unknown WHEEL %s', device, n) _log.warn('%s: unknown WHEEL %s', device, n)
return True return True
_log.warn('%s: unrecognized %s for feature %s (index %02X)', device, n, _log.warn('%s: unrecognized %s for feature %s (index %02X)', device, n, feature, n.sub_id)
feature, n.sub_id)

View File

@ -85,15 +85,13 @@ class PairedDevice(object):
if link_notification is not None: if link_notification is not None:
self.online = not bool(ord(link_notification.data[0:1]) & 0x40) self.online = not bool(ord(link_notification.data[0:1]) & 0x40)
self.wpid = _strhex(link_notification.data[2:3] + self.wpid = _strhex(link_notification.data[2:3] + link_notification.data[1:2])
link_notification.data[1:2])
# assert link_notification.address == (0x04 if unifying else 0x03) # assert link_notification.address == (0x04 if unifying else 0x03)
kind = ord(link_notification.data[0:1]) & 0x0F kind = ord(link_notification.data[0:1]) & 0x0F
self._kind = _hidpp10.DEVICE_KIND[kind] self._kind = _hidpp10.DEVICE_KIND[kind]
else: else:
# force a reading of the wpid # force a reading of the wpid
pair_info = receiver.read_register(_R.receiver_info, pair_info = receiver.read_register(_R.receiver_info, 0x20 + number - 1)
0x20 + number - 1)
if pair_info: if pair_info:
# may be either a Unifying receiver, or an Unifying-ready receiver # may be either a Unifying receiver, or an Unifying-ready receiver
self.wpid = _strhex(pair_info[3:5]) self.wpid = _strhex(pair_info[3:5])
@ -103,14 +101,10 @@ class PairedDevice(object):
else: else:
# unifying protocol not supported, must be a Nano receiver # unifying protocol not supported, must be a Nano receiver
device_info = self.receiver.read_register( device_info = self.receiver.read_register(_R.receiver_info, 0x04)
_R.receiver_info, 0x04)
if device_info is None: if device_info is None:
_log.error('failed to read Nano wpid for device %d of %s', _log.error('failed to read Nano wpid for device %d of %s', number, receiver)
number, receiver) raise _base.NoSuchDevice(number=number, receiver=receiver, error='read Nano wpid')
raise _base.NoSuchDevice(number=number,
receiver=receiver,
error='read Nano wpid')
self.wpid = _strhex(device_info[3:5]) self.wpid = _strhex(device_info[3:5])
self._polling_rate = 0 self._polling_rate = 0
@ -118,15 +112,13 @@ class PairedDevice(object):
# the wpid is necessary to properly identify wireless link on/off notifications # the wpid is necessary to properly identify wireless link on/off notifications
# also it gets set to None on this object when the device is unpaired # also it gets 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' % ( assert self.wpid is not None, 'failed to read wpid: device %d of %s' % (number, receiver)
number, receiver)
self.descriptor = _DESCRIPTORS.get(self.wpid) self.descriptor = _DESCRIPTORS.get(self.wpid)
if self.descriptor is None: if self.descriptor is None:
# Last chance to correctly identify the device; many Nano receivers # Last chance to correctly identify the device; many Nano receivers
# do not support this call. # do not support this call.
codename = self.receiver.read_register(_R.receiver_info, codename = self.receiver.read_register(_R.receiver_info, 0x40 + self.number - 1)
0x40 + self.number - 1)
if codename: if codename:
codename_length = ord(codename[1:2]) codename_length = ord(codename[1:2])
codename = codename[2:2 + codename_length] codename = codename[2:2 + codename_length]
@ -142,8 +134,7 @@ class PairedDevice(object):
self._kind = self.descriptor.kind self._kind = self.descriptor.kind
if self._protocol is not None: if self._protocol is not None:
self.features = None if self._protocol < 2.0 else _hidpp20.FeaturesArray( self.features = None if self._protocol < 2.0 else _hidpp20.FeaturesArray(self)
self)
else: else:
# may be a 2.0 device; if not, it will fix itself later # may be a 2.0 device; if not, it will fix itself later
self.features = _hidpp20.FeaturesArray(self) self.features = _hidpp20.FeaturesArray(self)
@ -162,8 +153,7 @@ class PairedDevice(object):
@property @property
def codename(self): def codename(self):
if self._codename is None: if self._codename is None:
codename = self.receiver.read_register(_R.receiver_info, codename = self.receiver.read_register(_R.receiver_info, 0x40 + self.number - 1)
0x40 + self.number - 1)
if codename: if codename:
codename_length = ord(codename[1:2]) codename_length = ord(codename[1:2])
codename = codename[2:2 + codename_length] codename = codename[2:2 + codename_length]
@ -184,8 +174,7 @@ class PairedDevice(object):
@property @property
def kind(self): def kind(self):
if self._kind is None: if self._kind is None:
pair_info = self.receiver.read_register(_R.receiver_info, pair_info = self.receiver.read_register(_R.receiver_info, 0x20 + self.number - 1)
0x20 + self.number - 1)
if pair_info: if pair_info:
kind = ord(pair_info[7:8]) & 0x0F kind = ord(pair_info[7:8]) & 0x0F
self._kind = _hidpp10.DEVICE_KIND[kind] self._kind = _hidpp10.DEVICE_KIND[kind]
@ -206,8 +195,7 @@ class PairedDevice(object):
@property @property
def serial(self): def serial(self):
if self._serial is None: if self._serial is None:
serial = self.receiver.read_register(_R.receiver_info, serial = self.receiver.read_register(_R.receiver_info, 0x30 + self.number - 1)
0x30 + self.number - 1)
if serial: if serial:
ps = ord(serial[9:10]) & 0x0F ps = ord(serial[9:10]) & 0x0F
self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps] self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps]
@ -225,8 +213,7 @@ class PairedDevice(object):
@property @property
def power_switch_location(self): def power_switch_location(self):
if self._power_switch is None: if self._power_switch is None:
ps = self.receiver.read_register(_R.receiver_info, ps = self.receiver.read_register(_R.receiver_info, 0x30 + self.number - 1)
0x30 + self.number - 1)
if ps is not None: if ps is not None:
ps = ord(ps[9:10]) & 0x0F ps = ord(ps[9:10]) & 0x0F
self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps] self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps]
@ -237,8 +224,7 @@ class PairedDevice(object):
@property @property
def polling_rate(self): def polling_rate(self):
if self._polling_rate is None: if self._polling_rate is None:
pair_info = self.receiver.read_register(_R.receiver_info, pair_info = self.receiver.read_register(_R.receiver_info, 0x20 + self.number - 1)
0x20 + self.number - 1)
if pair_info: if pair_info:
self._polling_rate = ord(pair_info[2:3]) self._polling_rate = ord(pair_info[2:3])
else: else:
@ -270,8 +256,7 @@ class PairedDevice(object):
else: else:
self._settings = [] self._settings = []
if not self._feature_settings_checked: if not self._feature_settings_checked:
self._feature_settings_checked = _check_feature_settings( self._feature_settings_checked = _check_feature_settings(self, self._settings)
self, self._settings)
return self._settings return self._settings
def enable_notifications(self, enable=True): def enable_notifications(self, enable=True):
@ -289,20 +274,16 @@ class PairedDevice(object):
set_flag_bits = 0 set_flag_bits = 0
ok = _hidpp10.set_notification_flags(self, set_flag_bits) ok = _hidpp10.set_notification_flags(self, set_flag_bits)
if ok is None: if ok is None:
_log.warn('%s: failed to %s device notifications', self, _log.warn('%s: failed to %s device notifications', self, 'enable' if enable else 'disable')
'enable' if enable else 'disable')
flag_bits = _hidpp10.get_notification_flags(self) flag_bits = _hidpp10.get_notification_flags(self)
flag_names = None if flag_bits is None else tuple( flag_names = None if flag_bits is None else tuple(_hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits))
_hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits))
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
_log.info('%s: device notifications %s %s', self, _log.info('%s: device notifications %s %s', self, 'enabled' if enable else 'disabled', flag_names)
'enabled' if enable else 'disabled', flag_names)
return flag_bits if ok else None return flag_bits if ok else None
def request(self, request_id, *params): def request(self, request_id, *params):
return _base.request(self.receiver.handle, self.number, request_id, return _base.request(self.receiver.handle, self.number, request_id, *params)
*params)
read_register = _hidpp10.read_register read_register = _hidpp10.read_register
write_register = _hidpp10.write_register write_register = _hidpp10.write_register
@ -336,8 +317,7 @@ class PairedDevice(object):
__bool__ = __nonzero__ = lambda self: self.wpid is not None and self.number in self.receiver __bool__ = __nonzero__ = lambda self: self.wpid is not None and self.number in self.receiver
def __str__(self): def __str__(self):
return '<PairedDevice(%d,%s,%s,%s)>' % ( return '<PairedDevice(%d,%s,%s,%s)>' % (self.number, self.wpid, self.codename or '?', self.serial)
self.number, self.wpid, self.codename or '?', self.serial)
__unicode__ = __repr__ = __str__ __unicode__ = __repr__ = __str__
@ -382,8 +362,7 @@ class Receiver(object):
self.name = product_info.get('name', '') self.name = product_info.get('name', '')
self.re_pairs = product_info.get('re_pairs', False) self.re_pairs = product_info.get('re_pairs', False)
self._str = '<%s(%s,%s%s)>' % (self.name.replace( self._str = '<%s(%s,%s%s)>' % (self.name.replace(
' ', ''), self.path, '' if isinstance(self.handle, int) else 'T', ' ', ''), self.path, '' if isinstance(self.handle, int) else 'T', self.handle)
self.handle)
self._firmware = None self._firmware = None
self._devices = {} self._devices = {}
@ -426,29 +405,24 @@ class Receiver(object):
set_flag_bits = 0 set_flag_bits = 0
ok = _hidpp10.set_notification_flags(self, set_flag_bits) ok = _hidpp10.set_notification_flags(self, set_flag_bits)
if ok is None: if ok is None:
_log.warn('%s: failed to %s receiver notifications', self, _log.warn('%s: failed to %s receiver notifications', self, 'enable' if enable else 'disable')
'enable' if enable else 'disable')
return None return None
flag_bits = _hidpp10.get_notification_flags(self) flag_bits = _hidpp10.get_notification_flags(self)
flag_names = None if flag_bits is None else tuple( flag_names = None if flag_bits is None else tuple(_hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits))
_hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits))
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
_log.info('%s: receiver notifications %s => %s', self, _log.info('%s: receiver notifications %s => %s', self, 'enabled' if enable else 'disabled', flag_names)
'enabled' if enable else 'disabled', flag_names)
return flag_bits return flag_bits
def notify_devices(self): def notify_devices(self):
"""Scan all devices.""" """Scan all devices."""
if self.handle: if self.handle:
if not self.write_register(_R.receiver_connection, 0x02): if not self.write_register(_R.receiver_connection, 0x02):
_log.warn('%s: failed to trigger device link notifications', _log.warn('%s: failed to trigger device link notifications', self)
self)
def register_new_device(self, number, notification=None): def register_new_device(self, number, notification=None):
if self._devices.get(number) is not None: if self._devices.get(number) is not None:
raise IndexError('%s: device number %d already registered' % raise IndexError('%s: device number %d already registered' % (self, number))
(self, number))
assert notification is None or notification.devnumber == number assert notification is None or notification.devnumber == number
assert notification is None or notification.sub_id == 0x41 assert notification is None or notification.sub_id == 0x41
@ -457,8 +431,7 @@ class Receiver(object):
dev = PairedDevice(self, number, notification) dev = PairedDevice(self, number, notification)
assert dev.wpid assert dev.wpid
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
_log.info('%s: found new device %d (%s)', self, number, _log.info('%s: found new device %d (%s)', self, number, dev.wpid)
dev.wpid)
self._devices[number] = dev self._devices[number] = dev
return dev return dev
except _base.NoSuchDevice: except _base.NoSuchDevice:
@ -470,12 +443,10 @@ class Receiver(object):
def set_lock(self, lock_closed=True, device=0, timeout=0): def set_lock(self, lock_closed=True, device=0, timeout=0):
if self.handle: if self.handle:
action = 0x02 if lock_closed else 0x01 action = 0x02 if lock_closed else 0x01
reply = self.write_register(_R.receiver_pairing, action, device, reply = self.write_register(_R.receiver_pairing, action, device, timeout)
timeout)
if reply: if reply:
return True return True
_log.warn('%s: failed to %s the receiver lock', self, _log.warn('%s: failed to %s the receiver lock', self, 'close' if lock_closed else 'open')
'close' if lock_closed else 'open')
def count(self): def count(self):
count = self.read_register(_R.receiver_connection) count = self.read_register(_R.receiver_connection)

View File

@ -37,28 +37,15 @@ del getLogger
# #
# #
KIND = _NamedInts(toggle=0x01, KIND = _NamedInts(toggle=0x01, choice=0x02, range=0x04, map_choice=0x0A, multiple_toggle=0x10)
choice=0x02,
range=0x04,
map_choice=0x0A,
multiple_toggle=0x10)
class Setting(object): class Setting(object):
"""A setting descriptor. """A setting descriptor.
Needs to be instantiated for each specific device.""" Needs to be instantiated for each specific device."""
__slots__ = ('name', 'label', 'description', 'kind', 'device_kind', __slots__ = ('name', 'label', 'description', 'kind', 'device_kind', 'feature', '_rw', '_validator', '_device', '_value')
'feature', '_rw', '_validator', '_device', '_value')
def __init__(self, def __init__(self, name, rw, validator, kind=None, label=None, description=None, device_kind=None, feature=None):
name,
rw,
validator,
kind=None,
label=None,
description=None,
device_kind=None,
feature=None):
assert name assert name
self.name = name self.name = name
self.label = label or name self.label = label or name
@ -109,8 +96,7 @@ class Setting(object):
assert hasattr(self, '_device') assert hasattr(self, '_device')
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings read %r from %s', self.name, self._value, _log.debug('%s: settings read %r from %s', self.name, self._value, self._device)
self._device)
if self._value is None and self._device.persister: if self._value is None and self._device.persister:
# We haven't read a value from the device yet, # We haven't read a value from the device yet,
@ -140,8 +126,7 @@ class Setting(object):
assert value is not None assert value is not None
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings write %r to %s', self.name, value, _log.debug('%s: settings write %r to %s', self.name, value, self._device)
self._device)
if self._device.online: if self._device.online:
# Remember the value we're trying to set, even if the write fails. # Remember the value we're trying to set, even if the write fails.
@ -159,8 +144,7 @@ class Setting(object):
data_bytes = self._validator.prepare_write(value, current_value) data_bytes = self._validator.prepare_write(value, current_value)
if data_bytes is not None: if data_bytes is not None:
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings prepare write(%s) => %r', _log.debug('%s: settings prepare write(%s) => %r', self.name, value, data_bytes)
self.name, value, data_bytes)
reply = self._rw.write(self._device, data_bytes) reply = self._rw.write(self._device, data_bytes)
if not reply: if not reply:
@ -174,8 +158,7 @@ class Setting(object):
assert hasattr(self, '_device') assert hasattr(self, '_device')
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: apply %s (%s)', self.name, self._value, _log.debug('%s: apply %s (%s)', self.name, self._value, self._device)
self._device)
value = self.read() value = self.read()
if value is not None: if value is not None:
@ -184,11 +167,9 @@ class Setting(object):
def __str__(self): def __str__(self):
if hasattr(self, '_value'): if hasattr(self, '_value'):
assert hasattr(self, '_device') assert hasattr(self, '_device')
return '<Setting([%s:%s] %s:%s=%s)>' % ( return '<Setting([%s:%s] %s:%s=%s)>' % (self._rw.kind, self._validator.kind, self._device.codename, self.name,
self._rw.kind, self._validator.kind, self._device.codename, self._value)
self.name, self._value) return '<Setting([%s:%s] %s)>' % (self._rw.kind, self._validator.kind, self.name)
return '<Setting([%s:%s] %s)>' % (self._rw.kind, self._validator.kind,
self.name)
__unicode__ = __repr__ = __str__ __unicode__ = __repr__ = __str__
@ -201,8 +182,7 @@ class Settings(Setting):
assert hasattr(self, '_device') assert hasattr(self, '_device')
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings read %r from %s', self.name, self._value, _log.debug('%s: settings read %r from %s', self.name, self._value, self._device)
self._device)
if self._value is None and getattr(self._device, 'persister', None): if self._value is None and getattr(self._device, 'persister', None):
# We haven't read a value from the device yet, # We haven't read a value from the device yet,
@ -210,8 +190,7 @@ class Settings(Setting):
self._value = self._device.persister.get(self.name) self._value = self._device.persister.get(self.name)
if cached and self._value is not None: if cached and self._value is not None:
if getattr(self._device, 'persister', if getattr(self._device, 'persister', None) and self.name not in self._device.persister:
None) and self.name not in self._device.persister:
# If this is a new device (or a new setting for an old device), # If this is a new device (or a new setting for an old device),
# make sure to save its current value for the next time. # make sure to save its current value for the next time.
self._device.persister[self.name] = self._value self._device.persister[self.name] = self._value
@ -224,11 +203,9 @@ class Settings(Setting):
if reply: if reply:
# keys are ints, because that is what the device uses, # keys are ints, because that is what the device uses,
# encoded into strings because JSON requires strings as keys # encoded into strings because JSON requires strings as keys
reply_map[str(int(key))] = self._validator.validate_read( reply_map[str(int(key))] = self._validator.validate_read(reply, key)
reply, key)
self._value = reply_map self._value = reply_map
if getattr(self._device, 'persister', if getattr(self._device, 'persister', None) and self.name not in self._device.persister:
None) and self.name not in self._device.persister:
# Don't update the persister if it already has a value, # Don't update the persister if it already has a value,
# otherwise the first read might overwrite the value we wanted. # otherwise the first read might overwrite the value we wanted.
self._device.persister[self.name] = self._value self._device.persister[self.name] = self._value
@ -240,25 +217,21 @@ class Settings(Setting):
assert key is not None assert key is not None
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings read %r key %r from %s', self.name, _log.debug('%s: settings read %r key %r from %s', self.name, self._value, key, self._device)
self._value, key, self._device)
if self._value is None and getattr(self._device, 'persister', None): if self._value is None and getattr(self._device, 'persister', None):
self._value = self._device.persister.get(self.name) self._value = self._device.persister.get(self.name)
if cached and self._value is not None: if cached and self._value is not None:
if getattr(self._device, 'persister', if getattr(self._device, 'persister', None) and self.name not in self._device.persister:
None) and self.name not in self._device.persister:
self._device.persister[self.name] = self._value self._device.persister[self.name] = self._value
return self._value[str(int(key))] return self._value[str(int(key))]
if self._device.online: if self._device.online:
reply = self._rw.read(self._device, key) reply = self._rw.read(self._device, key)
if reply: if reply:
self._value[str(int(key))] = self._validator.validate_read( self._value[str(int(key))] = self._validator.validate_read(reply, key)
reply, key) if getattr(self._device, 'persister', None) and self.name not in self._device.persister:
if getattr(self._device, 'persister',
None) and self.name not in self._device.persister:
self._device.persister[self.name] = self._value self._device.persister[self.name] = self._value
return self._value[str(int(key))] return self._value[str(int(key))]
@ -268,8 +241,7 @@ class Settings(Setting):
assert map is not None assert map is not None
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings write %r to %s', self.name, map, _log.debug('%s: settings write %r to %s', self.name, map, self._device)
self._device)
if self._device.online: if self._device.online:
# Remember the value we're trying to set, even if the write fails. # Remember the value we're trying to set, even if the write fails.
@ -283,9 +255,7 @@ class Settings(Setting):
data_bytes = self._validator.prepare_write(int(key), value) data_bytes = self._validator.prepare_write(int(key), value)
if data_bytes is not None: if data_bytes is not None:
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug( _log.debug('%s: settings prepare map write(%s,%s) => %r', self.name, key, value, data_bytes)
'%s: settings prepare map write(%s,%s) => %r',
self.name, key, value, data_bytes)
reply = self._rw.write(self._device, int(key), data_bytes) reply = self._rw.write(self._device, int(key), data_bytes)
if not reply: if not reply:
return None return None
@ -299,8 +269,7 @@ class Settings(Setting):
assert value is not None assert value is not None
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings write key %r value %r to %s', self.name, _log.debug('%s: settings write key %r value %r to %s', self.name, key, value, self._device)
key, value, self._device)
if self._device.online: if self._device.online:
# Remember the value we're trying to set, even if the write fails. # Remember the value we're trying to set, even if the write fails.
@ -313,9 +282,7 @@ class Settings(Setting):
data_bytes = self._validator.prepare_write(int(key), value) data_bytes = self._validator.prepare_write(int(key), value)
if data_bytes is not None: if data_bytes is not None:
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug( _log.debug('%s: settings prepare key value write(%s,%s) => %r', self.name, key, value, data_bytes)
'%s: settings prepare key value write(%s,%s) => %r',
self.name, key, value, data_bytes)
reply = self._rw.write(self._device, int(key), data_bytes) reply = self._rw.write(self._device, int(key), data_bytes)
if not reply: if not reply:
# tell whomever is calling that the write failed # tell whomever is calling that the write failed
@ -332,8 +299,7 @@ class BitFieldSetting(Setting):
assert hasattr(self, '_device') assert hasattr(self, '_device')
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings read %r from %s', self.name, self._value, _log.debug('%s: settings read %r from %s', self.name, self._value, self._device)
self._device)
if self._value is None and getattr(self._device, 'persister', None): if self._value is None and getattr(self._device, 'persister', None):
# We haven't read a value from the device yet, # We haven't read a value from the device yet,
@ -341,8 +307,7 @@ class BitFieldSetting(Setting):
self._value = self._device.persister.get(self.name) self._value = self._device.persister.get(self.name)
if cached and self._value is not None: if cached and self._value is not None:
if getattr(self._device, 'persister', if getattr(self._device, 'persister', None) and self.name not in self._device.persister:
None) and self.name not in self._device.persister:
# If this is a new device (or a new setting for an old device), # If this is a new device (or a new setting for an old device),
# make sure to save its current value for the next time. # make sure to save its current value for the next time.
self._device.persister[self.name] = self._value self._device.persister[self.name] = self._value
@ -356,8 +321,7 @@ class BitFieldSetting(Setting):
# encoded into strings because JSON requires strings as keys # encoded into strings because JSON requires strings as keys
reply_map = self._validator.validate_read(reply) reply_map = self._validator.validate_read(reply)
self._value = reply_map self._value = reply_map
if getattr(self._device, 'persister', if getattr(self._device, 'persister', None) and self.name not in self._device.persister:
None) and self.name not in self._device.persister:
# Don't update the persister if it already has a value, # Don't update the persister if it already has a value,
# otherwise the first read might overwrite the value we wanted. # otherwise the first read might overwrite the value we wanted.
self._device.persister[self.name] = self._value self._device.persister[self.name] = self._value
@ -369,15 +333,13 @@ class BitFieldSetting(Setting):
assert key is not None assert key is not None
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings read %r key %r from %s', self.name, _log.debug('%s: settings read %r key %r from %s', self.name, self._value, key, self._device)
self._value, key, self._device)
if self._value is None and getattr(self._device, 'persister', None): if self._value is None and getattr(self._device, 'persister', None):
self._value = self._device.persister.get(self.name) self._value = self._device.persister.get(self.name)
if cached and self._value is not None: if cached and self._value is not None:
if getattr(self._device, 'persister', if getattr(self._device, 'persister', None) and self.name not in self._device.persister:
None) and self.name not in self._device.persister:
self._device.persister[self.name] = self._value self._device.persister[self.name] = self._value
return self._value[str(int(key))] return self._value[str(int(key))]
@ -385,8 +347,7 @@ class BitFieldSetting(Setting):
reply = self._rw.read(self._device, key) reply = self._rw.read(self._device, key)
if reply: if reply:
self._value = self._validator.validate_read(reply) self._value = self._validator.validate_read(reply)
if getattr(self._device, 'persister', if getattr(self._device, 'persister', None) and self.name not in self._device.persister:
None) and self.name not in self._device.persister:
self._device.persister[self.name] = self._value self._device.persister[self.name] = self._value
return self._value[str(int(key))] return self._value[str(int(key))]
@ -396,8 +357,7 @@ class BitFieldSetting(Setting):
assert map is not None assert map is not None
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings write %r to %s', self.name, map, _log.debug('%s: settings write %r to %s', self.name, map, self._device)
self._device)
if self._device.online: if self._device.online:
# Remember the value we're trying to set, even if the write fails. # Remember the value we're trying to set, even if the write fails.
@ -409,8 +369,7 @@ class BitFieldSetting(Setting):
data_bytes = self._validator.prepare_write(self._value) data_bytes = self._validator.prepare_write(self._value)
if data_bytes is not None: if data_bytes is not None:
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings prepare map write(%s) => %r', _log.debug('%s: settings prepare map write(%s) => %r', self.name, self._value, data_bytes)
self.name, self._value, data_bytes)
reply = self._rw.write(self._device, data_bytes) reply = self._rw.write(self._device, data_bytes)
if not reply: if not reply:
return None return None
@ -423,8 +382,7 @@ class BitFieldSetting(Setting):
assert value is not None assert value is not None
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: settings write key %r value %r to %s', self.name, _log.debug('%s: settings write key %r value %r to %s', self.name, key, value, self._device)
key, value, self._device)
if self._device.online: if self._device.online:
# Remember the value we're trying to set, even if the write fails. # Remember the value we're trying to set, even if the write fails.
@ -438,9 +396,7 @@ class BitFieldSetting(Setting):
data_bytes = self._validator.prepare_write(self._value) data_bytes = self._validator.prepare_write(self._value)
if data_bytes is not None: if data_bytes is not None:
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug( _log.debug('%s: settings prepare key value write(%s,%s) => %r', self.name, key, str(value), data_bytes)
'%s: settings prepare key value write(%s,%s) => %r',
self.name, key, str(value), data_bytes)
reply = self._rw.write(self._device, data_bytes) reply = self._rw.write(self._device, data_bytes)
if not reply: if not reply:
# tell whomever is calling that the write failed # tell whomever is calling that the write failed
@ -477,10 +433,7 @@ class FeatureRW(object):
default_read_fnid = 0x00 default_read_fnid = 0x00
default_write_fnid = 0x10 default_write_fnid = 0x10
def __init__(self, def __init__(self, feature, read_fnid=default_read_fnid, write_fnid=default_write_fnid):
feature,
read_fnid=default_read_fnid,
write_fnid=default_write_fnid):
assert isinstance(feature, _NamedInt) assert isinstance(feature, _NamedInt)
self.feature = feature self.feature = feature
self.read_fnid = read_fnid self.read_fnid = read_fnid
@ -492,8 +445,7 @@ class FeatureRW(object):
def write(self, device, data_bytes): def write(self, device, data_bytes):
assert self.feature is not None assert self.feature is not None
return device.feature_request(self.feature, self.write_fnid, return device.feature_request(self.feature, self.write_fnid, data_bytes)
data_bytes)
class FeatureRWMap(FeatureRW): class FeatureRWMap(FeatureRW):
@ -502,11 +454,7 @@ class FeatureRWMap(FeatureRW):
default_write_fnid = 0x10 default_write_fnid = 0x10
default_key_bytes = 1 default_key_bytes = 1
def __init__(self, def __init__(self, feature, read_fnid=default_read_fnid, write_fnid=default_write_fnid, key_bytes=default_key_bytes):
feature,
read_fnid=default_read_fnid,
write_fnid=default_write_fnid,
key_bytes=default_key_bytes):
assert isinstance(feature, _NamedInt) assert isinstance(feature, _NamedInt)
self.feature = feature self.feature = feature
self.read_fnid = read_fnid self.read_fnid = read_fnid
@ -521,8 +469,7 @@ class FeatureRWMap(FeatureRW):
def write(self, device, key, data_bytes): def write(self, device, key, data_bytes):
assert self.feature is not None assert self.feature is not None
key_bytes = _int2bytes(key, self.key_bytes) key_bytes = _int2bytes(key, self.key_bytes)
return device.feature_request(self.feature, self.write_fnid, key_bytes, return device.feature_request(self.feature, self.write_fnid, key_bytes, data_bytes)
data_bytes)
# #
@ -540,10 +487,7 @@ class BooleanValidator(object):
# mask specifies all the affected bits in the value # mask specifies all the affected bits in the value
default_mask = 0xFF default_mask = 0xFF
def __init__(self, def __init__(self, true_value=default_true, false_value=default_false, mask=default_mask):
true_value=default_true,
false_value=default_false,
mask=default_mask):
if isinstance(true_value, int): if isinstance(true_value, int):
assert isinstance(false_value, int) assert isinstance(false_value, int)
if mask is None: if mask is None:
@ -582,14 +526,12 @@ class BooleanValidator(object):
if isinstance(self.mask, int): if isinstance(self.mask, int):
reply_value = ord(reply_bytes[:1]) & self.mask reply_value = ord(reply_bytes[:1]) & self.mask
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('BooleanValidator: validate read %r => %02X', _log.debug('BooleanValidator: validate read %r => %02X', reply_bytes, reply_value)
reply_bytes, reply_value)
if reply_value == self.true_value: if reply_value == self.true_value:
return True return True
if reply_value == self.false_value: if reply_value == self.false_value:
return False return False
_log.warn('BooleanValidator: reply %02X mismatched %02X/%02X/%02X', _log.warn('BooleanValidator: reply %02X mismatched %02X/%02X/%02X', reply_value, self.true_value, self.false_value,
reply_value, self.true_value, self.false_value,
self.mask) self.mask)
return False return False
@ -605,8 +547,7 @@ class BooleanValidator(object):
if reply_value == false_value: if reply_value == false_value:
return False return False
_log.warn('BooleanValidator: reply %r mismatched %r/%r/%r', _log.warn('BooleanValidator: reply %r mismatched %r/%r/%r', reply_bytes, self.true_value, self.false_value, self.mask)
reply_bytes, self.true_value, self.false_value, self.mask)
return False return False
def prepare_write(self, new_value, current_value=None): def prepare_write(self, new_value, current_value=None):
@ -620,8 +561,7 @@ class BooleanValidator(object):
if isinstance(self.mask, int): if isinstance(self.mask, int):
if current_value is not None and self.needs_current_value: if current_value is not None and self.needs_current_value:
to_write |= ord(current_value[:1]) & (0xFF ^ self.mask) to_write |= ord(current_value[:1]) & (0xFF ^ self.mask)
if current_value is not None and to_write == ord( if current_value is not None and to_write == ord(current_value[:1]):
current_value[:1]):
return None return None
else: else:
to_write = bytearray(to_write) to_write = bytearray(to_write)
@ -636,13 +576,11 @@ class BooleanValidator(object):
to_write[i] = b to_write[i] = b
to_write = bytes(to_write) to_write = bytes(to_write)
if current_value is not None and to_write == current_value[:len( if current_value is not None and to_write == current_value[:len(to_write)]:
to_write)]:
return None return None
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('BooleanValidator: prepare_write(%s, %s) => %r', _log.debug('BooleanValidator: prepare_write(%s, %s) => %r', new_value, current_value, to_write)
new_value, current_value, to_write)
return to_write return to_write
@ -657,8 +595,7 @@ class BitFieldValidator(object):
self.options = options self.options = options
self.byte_count = (max(x.bit_length() for x in options) + 7) // 8 self.byte_count = (max(x.bit_length() for x in options) + 7) // 8
if byte_count: if byte_count:
assert (isinstance(byte_count, int) assert (isinstance(byte_count, int) and byte_count >= self.byte_count)
and byte_count >= self.byte_count)
self.byte_count = byte_count self.byte_count = byte_count
def validate_read(self, reply_bytes): def validate_read(self, reply_bytes):
@ -705,8 +642,7 @@ class ChoicesValidator(object):
def validate_read(self, reply_bytes): def validate_read(self, reply_bytes):
reply_value = _bytes2int(reply_bytes[:self._bytes_count]) reply_value = _bytes2int(reply_bytes[:self._bytes_count])
valid_value = self.choices[reply_value] valid_value = self.choices[reply_value]
assert valid_value is not None, '%s: failed to validate read value %02X' % ( assert valid_value is not None, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
self.__class__.__name__, reply_value)
return valid_value return valid_value
def prepare_write(self, new_value, current_value=None): def prepare_write(self, new_value, current_value=None):
@ -731,12 +667,7 @@ class ChoicesValidator(object):
class ChoicesMapValidator(ChoicesValidator): class ChoicesMapValidator(ChoicesValidator):
kind = KIND.map_choice kind = KIND.map_choice
def __init__(self, def __init__(self, choices_map, key_bytes_count=None, skip_bytes_count=None, value_bytes_count=None, extra_default=None):
choices_map,
key_bytes_count=None,
skip_bytes_count=None,
value_bytes_count=None,
extra_default=None):
assert choices_map is not None assert choices_map is not None
assert isinstance(choices_map, dict) assert isinstance(choices_map, dict)
max_key_bits = 0 max_key_bits = 0
@ -770,22 +701,19 @@ class ChoicesMapValidator(ChoicesValidator):
# reprogrammable keys starts out as 0, which is not a choice, so don't use assert here # reprogrammable keys starts out as 0, which is not a choice, so don't use assert here
if self.extra_default is not None and self.extra_default == reply_value: if self.extra_default is not None and self.extra_default == reply_value:
return int(self.choices[key][0]) return int(self.choices[key][0])
assert reply_value in self.choices[ assert reply_value in self.choices[key], '%s: failed to validate read value %02X' % (self.__class__.__name__,
key], '%s: failed to validate read value %02X' % ( reply_value)
self.__class__.__name__, reply_value)
return reply_value return reply_value
def prepare_write(self, key, new_value): def prepare_write(self, key, new_value):
choices = self.choices[key] choices = self.choices[key]
if new_value not in choices and new_value != self.extra_default: if new_value not in choices and new_value != self.extra_default:
raise ValueError('invalid choice %r' % new_value) raise ValueError('invalid choice %r' % new_value)
return _int2bytes(new_value, return _int2bytes(new_value, self._skip_bytes_count + self._value_bytes_count)
self._skip_bytes_count + self._value_bytes_count)
class RangeValidator(object): class RangeValidator(object):
__slots__ = ('min_value', 'max_value', 'flag', '_bytes_count', __slots__ = ('min_value', 'max_value', 'flag', '_bytes_count', 'needs_current_value')
'needs_current_value')
kind = KIND.range kind = KIND.range
"""Translates between integers and a byte sequence. """Translates between integers and a byte sequence.
@ -807,10 +735,8 @@ class RangeValidator(object):
def validate_read(self, reply_bytes): def validate_read(self, reply_bytes):
reply_value = _bytes2int(reply_bytes[:self._bytes_count]) reply_value = _bytes2int(reply_bytes[:self._bytes_count])
assert reply_value >= self.min_value, '%s: failed to validate read value %02X' % ( assert reply_value >= self.min_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
self.__class__.__name__, reply_value) assert reply_value <= self.max_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value)
assert reply_value <= self.max_value, '%s: failed to validate read value %02X' % (
self.__class__.__name__, reply_value)
return reply_value return reply_value
def prepare_write(self, new_value, current_value=None): def prepare_write(self, new_value, current_value=None):

View File

@ -65,35 +65,16 @@ def register_toggle(name,
label=None, label=None,
description=None, description=None,
device_kind=None): device_kind=None):
validator = _BooleanV(true_value=true_value, validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask)
false_value=false_value,
mask=mask)
rw = _RegisterRW(register) rw = _RegisterRW(register)
return _Setting(name, return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind)
rw,
validator,
label=label,
description=description,
device_kind=device_kind)
def register_choices(name, def register_choices(name, register, choices, kind=_KIND.choice, label=None, description=None, device_kind=None):
register,
choices,
kind=_KIND.choice,
label=None,
description=None,
device_kind=None):
assert choices assert choices
validator = _ChoicesV(choices) validator = _ChoicesV(choices)
rw = _RegisterRW(register) rw = _RegisterRW(register)
return _Setting(name, return _Setting(name, rw, validator, kind=kind, label=label, description=description, device_kind=device_kind)
rw,
validator,
kind=kind,
label=label,
description=description,
device_kind=device_kind)
def feature_toggle(name, def feature_toggle(name,
@ -106,17 +87,9 @@ def feature_toggle(name,
label=None, label=None,
description=None, description=None,
device_kind=None): device_kind=None):
validator = _BooleanV(true_value=true_value, validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask)
false_value=false_value,
mask=mask)
rw = _FeatureRW(feature, read_function_id, write_function_id) rw = _FeatureRW(feature, read_function_id, write_function_id)
return _Setting(name, return _Setting(name, rw, validator, feature=feature, label=label, description=description, device_kind=device_kind)
rw,
validator,
feature=feature,
label=label,
description=description,
device_kind=device_kind)
def feature_bitfield_toggle(name, def feature_bitfield_toggle(name,
@ -139,15 +112,14 @@ def feature_bitfield_toggle(name,
device_kind=device_kind) device_kind=device_kind)
def feature_bitfield_toggle_dynamic( def feature_bitfield_toggle_dynamic(name,
name, feature,
feature, options_callback,
options_callback, read_function_id=_FeatureRW.default_read_fnid,
read_function_id=_FeatureRW.default_read_fnid, write_function_id=_FeatureRW.default_write_fnid,
write_function_id=_FeatureRW.default_write_fnid, label=None,
label=None, description=None,
description=None, device_kind=None):
device_kind=None):
def instantiate(device): def instantiate(device):
options = options_callback(device) options = options_callback(device)
setting = feature_bitfield_toggle(name, setting = feature_bitfield_toggle(name,
@ -235,10 +207,7 @@ def feature_map_choices(name,
skip_bytes_count=skip_bytes_count, skip_bytes_count=skip_bytes_count,
value_bytes_count=value_bytes_count, value_bytes_count=value_bytes_count,
extra_default=extra_default) extra_default=extra_default)
rw = _FeatureRWMap(feature, rw = _FeatureRWMap(feature, read_function_id, write_function_id, key_bytes=key_bytes_count)
read_function_id,
write_function_id,
key_bytes=key_bytes_count)
return _Settings(name, return _Settings(name,
rw, rw,
validator, validator,
@ -312,49 +281,33 @@ def feature_range(name,
# common strings for settings - name, string to display in main window, tool tip for main window # common strings for settings - name, string to display in main window, tool tip for main window
# #
_HAND_DETECTION = ( _HAND_DETECTION = ('hand-detection', _('Hand Detection'), _('Turn on illumination when the hands hover over the keyboard.'))
'hand-detection', _('Hand Detection'), _SMOOTH_SCROLL = ('smooth-scroll', _('Smooth Scrolling'), _('High-sensitivity mode for vertical scroll with the wheel.'))
_('Turn on illumination when the hands hover over the keyboard.')) _SIDE_SCROLL = ('side-scroll', _('Side Scrolling'),
_SMOOTH_SCROLL = ( _('When disabled, pushing the wheel sideways sends custom button events\n'
'smooth-scroll', _('Smooth Scrolling'), 'instead of the standard side-scrolling events.'))
_('High-sensitivity mode for vertical scroll with the wheel.')) _HI_RES_SCROLL = ('hi-res-scroll', _('High Resolution Scrolling'),
_SIDE_SCROLL = ( _('High-sensitivity mode for vertical scroll with the wheel.'))
'side-scroll', _('Side Scrolling'), _LOW_RES_SCROLL = ('lowres-smooth-scroll', _('HID++ Scrolling'), _('HID++ mode for vertical scroll with the wheel.') + '\n' +
_('When disabled, pushing the wheel sideways sends custom button events\n'
'instead of the standard side-scrolling events.'))
_HI_RES_SCROLL = (
'hi-res-scroll', _('High Resolution Scrolling'),
_('High-sensitivity mode for vertical scroll with the wheel.'))
_LOW_RES_SCROLL = ('lowres-smooth-scroll', _('HID++ Scrolling'),
_('HID++ mode for vertical scroll with the wheel.') + '\n' +
_('Effectively turns off wheel scrolling in Linux.')) _('Effectively turns off wheel scrolling in Linux.'))
_HIRES_INV = ('hires-smooth-invert', _('High Resolution Wheel Invert'), _HIRES_INV = ('hires-smooth-invert', _('High Resolution Wheel Invert'),
_('High-sensitivity wheel invert mode for vertical scroll.')) _('High-sensitivity wheel invert mode for vertical scroll.'))
_HIRES_RES = ('hires-smooth-resolution', _('Wheel Resolution'), _HIRES_RES = ('hires-smooth-resolution', _('Wheel Resolution'), _('High-sensitivity mode for vertical scroll with the wheel.'))
_('High-sensitivity mode for vertical scroll with the wheel.')) _FN_SWAP = ('fn-swap', _('Swap Fx function'),
_FN_SWAP = ( _('When set, the F1..F12 keys will activate their special function,\n'
'fn-swap', _('Swap Fx function'), 'and you must hold the FN key to activate their standard function.') + '\n\n' +
_('When set, the F1..F12 keys will activate their special function,\n' _('When unset, the F1..F12 keys will activate their standard function,\n'
'and you must hold the FN key to activate their standard function.') + 'and you must hold the FN key to activate their special function.'))
'\n\n' +
_('When unset, the F1..F12 keys will activate their standard function,\n'
'and you must hold the FN key to activate their special function.'))
_DPI = ('dpi', _('Sensitivity (DPI)'), None) _DPI = ('dpi', _('Sensitivity (DPI)'), None)
_POINTER_SPEED = ('pointer_speed', _('Sensitivity (Pointer Speed)'), _POINTER_SPEED = ('pointer_speed', _('Sensitivity (Pointer Speed)'),
_('Speed multiplier for mouse (256 is normal multiplier).')) _('Speed multiplier for mouse (256 is normal multiplier).'))
_SMART_SHIFT = ( _SMART_SHIFT = ('smart-shift', _('Smart Shift'),
'smart-shift', _('Smart Shift'), _('Automatically switch the mouse wheel between ratchet and freespin mode.\n'
_('Automatically switch the mouse wheel between ratchet and freespin mode.\n' 'The mouse wheel is always free at 0, and always locked at 50'))
'The mouse wheel is always free at 0, and always locked at 50')) _BACKLIGHT = ('backlight', _('Backlight'), _('Turn illumination on or off on keyboard.'))
_BACKLIGHT = ('backlight', _('Backlight'), _REPROGRAMMABLE_KEYS = ('reprogrammable-keys', _('Actions'), _('Change the action for the key or button.') + '\n' +
_('Turn illumination on or off on keyboard.')) _('Changing important actions (such as for the left mouse button) can result in an unusable system.'))
_REPROGRAMMABLE_KEYS = ('reprogrammable-keys', _( _DISABLE_KEYS = ('disable-keyboard-keys', _('Disable keys'), _('Disable specific keyboard keys.'))
'Actions'
), _('Change the action for the key or button.') + '\n' + _(
'Changing important actions (such as for the left mouse button) can result in an unusable system.'
))
_DISABLE_KEYS = ('disable-keyboard-keys', _('Disable keys'),
_('Disable specific keyboard keys.'))
# #
# #
@ -374,9 +327,7 @@ def _register_hand_detection(register=_R.keyboard_hand_detection,
device_kind=(_DK.keyboard, )) device_kind=(_DK.keyboard, ))
def _register_fn_swap(register=_R.keyboard_fn_swap, def _register_fn_swap(register=_R.keyboard_fn_swap, true_value=b'\x00\x01', mask=b'\x00\x01'):
true_value=b'\x00\x01',
mask=b'\x00\x01'):
return register_toggle(_FN_SWAP[0], return register_toggle(_FN_SWAP[0],
register, register,
true_value=true_value, true_value=true_value,
@ -386,9 +337,7 @@ def _register_fn_swap(register=_R.keyboard_fn_swap,
device_kind=(_DK.keyboard, )) device_kind=(_DK.keyboard, ))
def _register_smooth_scroll(register=_R.mouse_button_flags, def _register_smooth_scroll(register=_R.mouse_button_flags, true_value=0x40, mask=0x40):
true_value=0x40,
mask=0x40):
return register_toggle(_SMOOTH_SCROLL[0], return register_toggle(_SMOOTH_SCROLL[0],
register, register,
true_value=true_value, true_value=true_value,
@ -398,9 +347,7 @@ def _register_smooth_scroll(register=_R.mouse_button_flags,
device_kind=(_DK.mouse, _DK.trackball)) device_kind=(_DK.mouse, _DK.trackball))
def _register_side_scroll(register=_R.mouse_button_flags, def _register_side_scroll(register=_R.mouse_button_flags, true_value=0x02, mask=0x02):
true_value=0x02,
mask=0x02):
return register_toggle(_SIDE_SCROLL[0], return register_toggle(_SIDE_SCROLL[0],
register, register,
true_value=true_value, true_value=true_value,
@ -525,8 +472,7 @@ def _feature_smart_shift():
if threshold == _MAX_SMART_SHIFT_VALUE: if threshold == _MAX_SMART_SHIFT_VALUE:
threshold = 255 threshold = 255
data = _int2bytes(mode, data = _int2bytes(mode, count=1) + _int2bytes(threshold, count=1) * 2
count=1) + _int2bytes(threshold, count=1) * 2
return super(_SmartShiftRW, self).write(device, data) return super(_SmartShiftRW, self).write(device, data)
return feature_range(_SMART_SHIFT[0], return feature_range(_SMART_SHIFT[0],
@ -607,20 +553,15 @@ def _feature_reprogrammable_keys_choices(device):
choices = {} choices = {}
for i in range(0, count): # get the data for each key record on device for i in range(0, count): # get the data for each key record on device
keydata = device.feature_request(_F.REPROG_CONTROLS_V4, 0x10, i) keydata = device.feature_request(_F.REPROG_CONTROLS_V4, 0x10, i)
key, key_task, flags, pos, group, gmask = _unpack( key, key_task, flags, pos, group, gmask = _unpack('!HHBBBB', keydata[:8])
'!HHBBBB', keydata[:8])
action = _NamedInt(key, str(_special_keys.TASK[key_task])) action = _NamedInt(key, str(_special_keys.TASK[key_task]))
keys[i] = (_special_keys.CONTROL[key], action, flags, gmask) keys[i] = (_special_keys.CONTROL[key], action, flags, gmask)
groups[group].append(action) groups[group].append(action)
for k in keys: for k in keys:
# if k[2] & _special_keys.KEY_FLAG.reprogrammable: # this flag is only to show in UI, ignore in Solaar # if k[2] & _special_keys.KEY_FLAG.reprogrammable: # this flag is only to show in UI, ignore in Solaar
if k[3]: # only keys with a non-zero gmask are remappable if k[3]: # only keys with a non-zero gmask are remappable
key_choices = [ key_choices = [k[1]] # it should always be possible to map the key to itself
k[1] for g in range(1, 9): # group 0 and gmask 0 (k[3]) does not indicate remappability so don't consider group 0
] # it should always be possible to map the key to itself
for g in range(
1, 9
): # group 0 and gmask 0 (k[3]) does not indicate remappability so don't consider group 0
if (k[3] == 0 if g == 0 else k[3] & 2**(g - 1)): if (k[3] == 0 if g == 0 else k[3] & 2**(g - 1)):
for gm in groups[g]: for gm in groups[g]:
if int(gm) != int(k[0]): # don't put itself in twice if int(gm) != int(k[0]): # don't put itself in twice
@ -647,22 +588,19 @@ def _feature_reprogrammable_keys():
def _feature_disable_keyboard_keys_key_list(device): def _feature_disable_keyboard_keys_key_list(device):
mask = device.feature_request(_F.KEYBOARD_DISABLE_KEYS)[0] mask = device.feature_request(_F.KEYBOARD_DISABLE_KEYS)[0]
options = [ options = [_special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)]
_special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)
]
return options return options
def _feature_disable_keyboard_keys(): def _feature_disable_keyboard_keys():
return feature_bitfield_toggle_dynamic( return feature_bitfield_toggle_dynamic(_DISABLE_KEYS[0],
_DISABLE_KEYS[0], _F.KEYBOARD_DISABLE_KEYS,
_F.KEYBOARD_DISABLE_KEYS, _feature_disable_keyboard_keys_key_list,
_feature_disable_keyboard_keys_key_list, read_function_id=0x10,
read_function_id=0x10, write_function_id=0x20,
write_function_id=0x20, label=_DISABLE_KEYS[1],
label=_DISABLE_KEYS[1], description=_DISABLE_KEYS[2],
description=_DISABLE_KEYS[2], device_kind=(_DK.keyboard, ))
device_kind=(_DK.keyboard, ))
# #
@ -671,8 +609,7 @@ def _feature_disable_keyboard_keys():
def _S(name, featureID=None, featureFn=None, registerFn=None, identifier=None): def _S(name, featureID=None, featureFn=None, registerFn=None, identifier=None):
return (name, featureID, featureFn, registerFn, return (name, featureID, featureFn, registerFn, identifier if identifier else name.replace('-', '_'))
identifier if identifier else name.replace('-', '_'))
_SETTINGS_TABLE = [ _SETTINGS_TABLE = [
@ -683,29 +620,15 @@ _SETTINGS_TABLE = [
_S(_LOW_RES_SCROLL[0], _F.LOWRES_WHEEL, _feature_lowres_smooth_scroll), _S(_LOW_RES_SCROLL[0], _F.LOWRES_WHEEL, _feature_lowres_smooth_scroll),
_S(_HIRES_INV[0], _F.HIRES_WHEEL, _feature_hires_smooth_invert), _S(_HIRES_INV[0], _F.HIRES_WHEEL, _feature_hires_smooth_invert),
_S(_HIRES_RES[0], _F.HIRES_WHEEL, _feature_hires_smooth_resolution), _S(_HIRES_RES[0], _F.HIRES_WHEEL, _feature_hires_smooth_resolution),
_S(_FN_SWAP[0], _S(_FN_SWAP[0], _F.FN_INVERSION, _feature_fn_swap, registerFn=_register_fn_swap),
_F.FN_INVERSION, _S(_FN_SWAP[0], _F.NEW_FN_INVERSION, _feature_new_fn_swap, identifier='new_fn_swap'),
_feature_fn_swap, _S(_FN_SWAP[0], _F.K375S_FN_INVERSION, _feature_k375s_fn_swap, identifier='k375s_fn_swap'),
registerFn=_register_fn_swap), _S(_DPI[0], _F.ADJUSTABLE_DPI, _feature_adjustable_dpi, registerFn=_register_dpi),
_S(_FN_SWAP[0],
_F.NEW_FN_INVERSION,
_feature_new_fn_swap,
identifier='new_fn_swap'),
_S(_FN_SWAP[0],
_F.K375S_FN_INVERSION,
_feature_k375s_fn_swap,
identifier='k375s_fn_swap'),
_S(_DPI[0],
_F.ADJUSTABLE_DPI,
_feature_adjustable_dpi,
registerFn=_register_dpi),
_S(_POINTER_SPEED[0], _F.POINTER_SPEED, _feature_pointer_speed), _S(_POINTER_SPEED[0], _F.POINTER_SPEED, _feature_pointer_speed),
_S(_SMART_SHIFT[0], _F.SMART_SHIFT, _feature_smart_shift), _S(_SMART_SHIFT[0], _F.SMART_SHIFT, _feature_smart_shift),
_S(_BACKLIGHT[0], _F.BACKLIGHT2, _feature_backlight2), _S(_BACKLIGHT[0], _F.BACKLIGHT2, _feature_backlight2),
_S(_REPROGRAMMABLE_KEYS[0], _F.REPROG_CONTROLS_V4, _S(_REPROGRAMMABLE_KEYS[0], _F.REPROG_CONTROLS_V4, _feature_reprogrammable_keys),
_feature_reprogrammable_keys), _S(_DISABLE_KEYS[0], _F.KEYBOARD_DISABLE_KEYS, _feature_disable_keyboard_keys),
_S(_DISABLE_KEYS[0], _F.KEYBOARD_DISABLE_KEYS,
_feature_disable_keyboard_keys),
] ]
_SETTINGS_LIST = namedtuple('_SETTINGS_LIST', [s[4] for s in _SETTINGS_TABLE]) _SETTINGS_LIST = namedtuple('_SETTINGS_LIST', [s[4] for s in _SETTINGS_TABLE])
@ -741,13 +664,11 @@ def check_feature_settings(device, already_known):
try: try:
detected = featureFn()(device) detected = featureFn()(device)
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('check_feature[%s] detected %s', featureId, _log.debug('check_feature[%s] detected %s', featureId, detected)
detected)
if detected: if detected:
already_known.append(detected) already_known.append(detected)
except Exception as reason: except Exception as reason:
_log.error('check_feature[%s] inconsistent feature %s', featureId, _log.error('check_feature[%s] inconsistent feature %s', featureId, reason)
reason)
for name, featureId, featureFn, __, __ in _SETTINGS_TABLE: for name, featureId, featureFn, __, __ in _SETTINGS_TABLE:
if featureId and featureFn: if featureId and featureFn:

View File

@ -67,8 +67,7 @@ CONTROL = _NamedInts(
MINIMIZE_AS_WIN_M=0x0027, MINIMIZE_AS_WIN_M=0x0027,
MEDIA_PLAYER=0x0028, MEDIA_PLAYER=0x0028,
MEDIA_CENTER_LOGI=0x0029, MEDIA_CENTER_LOGI=0x0029,
MEDIA_CENTER_MSFT= MEDIA_CENTER_MSFT=0x002A, # Should not be used as it is not reprogrammable under Windows
0x002A, # Should not be used as it is not reprogrammable under Windows
CUSTOM_MENU=0x002B, CUSTOM_MENU=0x002B,
MESSENGER=0x002C, MESSENGER=0x002C,
MY_DOCUMENTS=0x002D, MY_DOCUMENTS=0x002D,
@ -237,8 +236,7 @@ CONTROL = _NamedInts(
F_Lock=0x00DE, F_Lock=0x00DE,
Switch_Highlight=0x00DF, Switch_Highlight=0x00DF,
Mission_Control__Task_View=0x00E0, # Switch_Workspaces on Craft Keyboard Mission_Control__Task_View=0x00E0, # Switch_Workspaces on Craft Keyboard
Dashboard_Launchpad__Action_Center= Dashboard_Launchpad__Action_Center=0x00E1, # Application_Launcher on Craft Keyboard
0x00E1, # Application_Launcher on Craft Keyboard
Backlight_Down=0x00E2, Backlight_Down=0x00E2,
Backlight_Up=0x00E3, Backlight_Up=0x00E3,
Previous_Fn=0x00E4, # Reprogrammable_Previous_Track / on Craft Keyboard Previous_Fn=0x00E4, # Reprogrammable_Previous_Track / on Craft Keyboard
@ -463,8 +461,7 @@ TASK = _NamedInts(
Fast_Backward=0x00BD, Fast_Backward=0x00BD,
Switch_Highlighting=0x00BE, Switch_Highlighting=0x00BE,
Mission_Control__Task_View=0x00BF, # Switch_Workspace on Craft Keyboard Mission_Control__Task_View=0x00BF, # Switch_Workspace on Craft Keyboard
Dashboard_Launchpad__Action_Center= Dashboard_Launchpad__Action_Center=0x00C0, # Application_Launcher on Craft Keyboard
0x00C0, # Application_Launcher on Craft Keyboard
Backlight_Down=0x00C1, # Backlight_Down_FW_internal_function Backlight_Down=0x00C1, # Backlight_Down_FW_internal_function
Backlight_Up=0x00C2, # Backlight_Up_FW_internal_function Backlight_Up=0x00C2, # Backlight_Up_FW_internal_function
Right_Click__App_Contextual_Menu=0x00C3, # Context_Menu on Craft Keyboard Right_Click__App_Contextual_Menu=0x00C3, # Context_Menu on Craft Keyboard

View File

@ -38,11 +38,7 @@ _R = _hidpp10.REGISTERS
# #
# #
ALERT = _NamedInts(NONE=0x00, ALERT = _NamedInts(NONE=0x00, NOTIFICATION=0x01, SHOW_WINDOW=0x02, ATTENTION=0x04, ALL=0xFF)
NOTIFICATION=0x01,
SHOW_WINDOW=0x02,
ATTENTION=0x04,
ALL=0xFF)
KEYS = _NamedInts( KEYS = _NamedInts(
BATTERY_LEVEL=1, BATTERY_LEVEL=1,
@ -106,10 +102,10 @@ class ReceiverStatus(dict):
def __str__(self): def __str__(self):
count = len(self._receiver) count = len(self._receiver)
return (_('No paired devices.') if count == 0 else ngettext( return (_('No paired devices.')
'%(count)s paired device.', '%(count)s paired devices.', count) % { if count == 0 else ngettext('%(count)s paired device.', '%(count)s paired devices.', count) % {
'count': count 'count': count
}) })
__unicode__ = __str__ __unicode__ = __str__
@ -161,13 +157,9 @@ class DeviceStatus(dict):
battery_level = self.get(KEYS.BATTERY_LEVEL) battery_level = self.get(KEYS.BATTERY_LEVEL)
if battery_level is not None: if battery_level is not None:
if isinstance(battery_level, _NamedInt): if isinstance(battery_level, _NamedInt):
yield _('Battery: %(level)s') % { yield _('Battery: %(level)s') % {'level': _(str(battery_level))}
'level': _(str(battery_level))
}
else: else:
yield _('Battery: %(percent)d%%') % { yield _('Battery: %(percent)d%%') % {'percent': battery_level}
'percent': battery_level
}
battery_status = self.get(KEYS.BATTERY_STATUS) battery_status = self.get(KEYS.BATTERY_STATUS)
if battery_status is not None: if battery_status is not None:
@ -184,20 +176,14 @@ class DeviceStatus(dict):
return ''.join(i for i in _items()) return ''.join(i for i in _items())
def __repr__(self): def __repr__(self):
return '{' + ', '.join('\'%s\': %r' % (k, v) return '{' + ', '.join('\'%s\': %r' % (k, v) for k, v in self.items()) + '}'
for k, v in self.items()) + '}'
def __bool__(self): def __bool__(self):
return bool(self._active) return bool(self._active)
__nonzero__ = __bool__ __nonzero__ = __bool__
def set_battery_info(self, def set_battery_info(self, level, status, nextLevel=None, voltage=None, timestamp=None):
level,
status,
nextLevel=None,
voltage=None,
timestamp=None):
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s: battery %s, %s', self._device, level, status) _log.debug('%s: battery %s, %s', self._device, level, status)
@ -207,8 +193,7 @@ class DeviceStatus(dict):
# It is not always possible to do this well # It is not always possible to do this well
if status == _hidpp20.BATTERY_STATUS.full: if status == _hidpp20.BATTERY_STATUS.full:
level = _hidpp10.BATTERY_APPOX.full level = _hidpp10.BATTERY_APPOX.full
elif status in (_hidpp20.BATTERY_STATUS.almost_full, elif status in (_hidpp20.BATTERY_STATUS.almost_full, _hidpp20.BATTERY_STATUS.recharging):
_hidpp20.BATTERY_STATUS.recharging):
level = _hidpp10.BATTERY_APPOX.good level = _hidpp10.BATTERY_APPOX.good
elif status == _hidpp20.BATTERY_STATUS.slow_recharge: elif status == _hidpp20.BATTERY_STATUS.slow_recharge:
level = _hidpp10.BATTERY_APPOX.low level = _hidpp10.BATTERY_APPOX.low
@ -218,55 +203,36 @@ class DeviceStatus(dict):
assert isinstance(level, int) assert isinstance(level, int)
# TODO: this is also executed when pressing Fn+F7 on K800. # TODO: this is also executed when pressing Fn+F7 on K800.
old_level, self[KEYS.BATTERY_LEVEL] = self.get( old_level, self[KEYS.BATTERY_LEVEL] = self.get(KEYS.BATTERY_LEVEL), level
KEYS.BATTERY_LEVEL), level old_status, self[KEYS.BATTERY_STATUS] = self.get(KEYS.BATTERY_STATUS), status
old_status, self[KEYS.BATTERY_STATUS] = self.get(
KEYS.BATTERY_STATUS), status
self[KEYS.BATTERY_NEXT_LEVEL] = nextLevel self[KEYS.BATTERY_NEXT_LEVEL] = nextLevel
if voltage is not None: if voltage is not None:
self[KEYS.BATTERY_VOLTAGE] = voltage self[KEYS.BATTERY_VOLTAGE] = voltage
charging = status in (_hidpp20.BATTERY_STATUS.recharging, charging = status in (_hidpp20.BATTERY_STATUS.recharging, _hidpp20.BATTERY_STATUS.almost_full,
_hidpp20.BATTERY_STATUS.almost_full, _hidpp20.BATTERY_STATUS.full, _hidpp20.BATTERY_STATUS.slow_recharge)
_hidpp20.BATTERY_STATUS.full, old_charging, self[KEYS.BATTERY_CHARGING] = self.get(KEYS.BATTERY_CHARGING), charging
_hidpp20.BATTERY_STATUS.slow_recharge)
old_charging, self[KEYS.BATTERY_CHARGING] = self.get(
KEYS.BATTERY_CHARGING), charging
changed = old_level != level or old_status != status or old_charging != charging changed = old_level != level or old_status != status or old_charging != charging
alert, reason = ALERT.NONE, None alert, reason = ALERT.NONE, None
if _hidpp20.BATTERY_OK(status) and (level is None or if _hidpp20.BATTERY_OK(status) and (level is None or level > _BATTERY_ATTENTION_LEVEL):
level > _BATTERY_ATTENTION_LEVEL):
self[KEYS.ERROR] = None self[KEYS.ERROR] = None
else: else:
_log.warn('%s: battery %d%%, ALERT %s', self._device, level, _log.warn('%s: battery %d%%, ALERT %s', self._device, level, status)
status)
if self.get(KEYS.ERROR) != status: if self.get(KEYS.ERROR) != status:
self[KEYS.ERROR] = status self[KEYS.ERROR] = status
# only show the notification once # only show the notification once
alert = ALERT.NOTIFICATION | ALERT.ATTENTION alert = ALERT.NOTIFICATION | ALERT.ATTENTION
if isinstance(level, _NamedInt): if isinstance(level, _NamedInt):
reason = _('Battery: %(level)s (%(status)s)') % { reason = _('Battery: %(level)s (%(status)s)') % {'level': _(level), 'status': _(status)}
'level': _(level),
'status': _(status)
}
else: else:
reason = _('Battery: %(percent)d%% (%(status)s)') % { reason = _('Battery: %(percent)d%% (%(status)s)') % {'percent': level, 'status': status.name}
'percent': level,
'status': status.name
}
if changed or reason: if changed or reason:
# update the leds on the device, if any # update the leds on the device, if any
_hidpp10.set_3leds(self._device, _hidpp10.set_3leds(self._device, level, charging=charging, warning=bool(alert))
level, self.changed(active=True, alert=alert, reason=reason, timestamp=timestamp)
charging=charging,
warning=bool(alert))
self.changed(active=True,
alert=alert,
reason=reason,
timestamp=timestamp)
# Retrieve and regularize battery status # Retrieve and regularize battery status
def read_battery(self, timestamp=None): def read_battery(self, timestamp=None):
@ -305,11 +271,7 @@ class DeviceStatus(dict):
self[KEYS.BATTERY_CHARGING] = None self[KEYS.BATTERY_CHARGING] = None
self.changed() self.changed()
def changed(self, def changed(self, active=None, alert=ALERT.NONE, reason=None, timestamp=None):
active=None,
alert=ALERT.NONE,
reason=None,
timestamp=None):
assert self._changed_callback assert self._changed_callback
d = self._device d = self._device
# assert d # may be invalid when processing the 'unpaired' notification # assert d # may be invalid when processing the 'unpaired' notification
@ -324,8 +286,7 @@ class DeviceStatus(dict):
# get cleared when the device is turned off (but not when the device # get cleared when the device is turned off (but not when the device
# goes idle, and we can't tell the difference right now). # goes idle, and we can't tell the difference right now).
if d.protocol < 2.0: if d.protocol < 2.0:
self[KEYS.NOTIFICATION_FLAGS] = d.enable_notifications( self[KEYS.NOTIFICATION_FLAGS] = d.enable_notifications()
)
# If we've been inactive for a long time, forget anything # If we've been inactive for a long time, forget anything
# about the battery. # about the battery.
@ -337,8 +298,7 @@ class DeviceStatus(dict):
# Devices lose configuration when they are turned off, # Devices lose configuration when they are turned off,
# make sure they're up-to-date. # make sure they're up-to-date.
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('%s pushing device settings %s', d, _log.debug('%s pushing device settings %s', d, d.settings)
d.settings)
for s in d.settings: for s in d.settings:
s.apply() s.apply()

View File

@ -36,65 +36,43 @@ del getLogger
def _create_parser(): def _create_parser():
parser = _argparse.ArgumentParser( parser = _argparse.ArgumentParser(prog=NAME.lower(),
prog=NAME.lower(), add_help=False,
add_help=False, epilog='For details on individual actions, run `%s <action> --help`.' % NAME.lower())
epilog='For details on individual actions, run `%s <action> --help`.' % subparsers = parser.add_subparsers(title='actions', help='optional action to perform')
NAME.lower())
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( sp.add_argument('device',
'device', nargs='?',
nargs='?', default='all',
default='all', help='device to show information about; may be a device number (1..6), a serial, '
help= 'a substring of a device\'s name, or "all" (the default)')
'device to show information about; may be a device number (1..6), a serial, '
'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', sp = subparsers.add_parser('probe', help='probe a receiver (debugging use only)')
help='probe a receiver (debugging use only)') sp.add_argument('receiver', nargs='?', help='select a certain receiver when more than one is present')
sp.add_argument(
'receiver',
nargs='?',
help='select a certain receiver when more than one is present')
sp.set_defaults(action='probe') sp.set_defaults(action='probe')
sp = subparsers.add_parser( sp = subparsers.add_parser('config',
'config', help='read/write device-specific settings',
help='read/write device-specific settings', epilog='Please note that configuration only works on active devices.')
epilog='Please note that configuration only works on active devices.') sp.add_argument('device',
sp.add_argument( help='device to configure; may be a device number (1..6), a device serial, '
'device', 'or at least 3 characters of a device\'s name')
help= sp.add_argument('setting', nargs='?', help='device-specific setting; leave empty to list available settings')
'device to configure; may be a device number (1..6), a device serial, '
'or at least 3 characters of a device\'s name')
sp.add_argument(
'setting',
nargs='?',
help='device-specific setting; leave empty to list available settings')
sp.add_argument('value', nargs='?', help='new value for the setting') sp.add_argument('value', nargs='?', help='new value for the setting')
sp.set_defaults(action='config') sp.set_defaults(action='config')
sp = subparsers.add_parser( sp = subparsers.add_parser('pair',
'pair', help='pair a new device',
help='pair a new device', epilog='The Logitech Unifying Receiver supports up to 6 paired devices at the same time.')
epilog= sp.add_argument('receiver', nargs='?', help='select a certain receiver when more than one is present')
'The Logitech Unifying Receiver supports up to 6 paired devices at the same time.'
)
sp.add_argument(
'receiver',
nargs='?',
help='select a certain receiver 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( sp.add_argument('device',
'device', help='device to unpair; may be a device number (1..6), a serial, '
help='device to unpair; may be a device number (1..6), a serial, ' 'or a substring of a device\'s name.')
'or a substring of a device\'s name.')
sp.set_defaults(action='unpair') sp.set_defaults(action='unpair')
return parser, subparsers.choices return parser, subparsers.choices
@ -126,8 +104,7 @@ def _find_receiver(receivers, name):
assert name assert name
for r in receivers: for r in receivers:
if name in r.name.lower() or (r.serial is not None if name in r.name.lower() or (r.serial is not None and name == r.serial.lower()):
and name == r.serial.lower()):
return r return r
@ -153,8 +130,7 @@ def _find_device(receivers, name):
return dev return dev
for dev in r: for dev in r:
if (name == dev.serial.lower() or name == dev.codename.lower() if (name == dev.serial.lower() or name == dev.codename.lower() or name == str(dev.kind).lower()
or name == str(dev.kind).lower()
or name in dev.name.lower()): or name in dev.name.lower()):
return dev return dev

View File

@ -29,11 +29,9 @@ def _print_setting(s, verbose=True):
if s.description: if s.description:
print('#', s.description.replace('\n', ' ')) print('#', s.description.replace('\n', ' '))
if s.kind == _settings.KIND.toggle: if s.kind == _settings.KIND.toggle:
print( print('# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0')
'# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0')
elif s.choices: elif s.choices:
print('# possible values: one of [', print('# possible values: one of [', ', '.join(str(v) for v in s.choices),
', '.join(str(v) for v in s.choices),
'], or higher/lower/highest/max/lowest/min') '], or higher/lower/highest/max/lowest/min')
else: else:
# wtf? # wtf?
@ -90,8 +88,7 @@ def run(receivers, args, find_receiver, find_device):
elif value.lower() in ('false', 'no', 'off', 'f', 'n'): elif value.lower() in ('false', 'no', 'off', 'f', 'n'):
value = False value = False
else: else:
raise Exception("don't know how to interpret '%s' as boolean" % raise Exception("don't know how to interpret '%s' as boolean" % value)
value)
elif setting.choices: elif setting.choices:
value = args.value.lower() value = args.value.lower()
@ -99,25 +96,20 @@ def run(receivers, args, find_receiver, find_device):
if value in ('higher', 'lower'): if value in ('higher', 'lower'):
old_value = setting.read() old_value = setting.read()
if old_value is None: if old_value is None:
raise Exception("could not read current value of '%s'" % raise Exception("could not read current value of '%s'" % setting.name)
setting.name)
if value == 'lower': if value == 'lower':
lower_values = setting.choices[:old_value] lower_values = setting.choices[:old_value]
value = lower_values[ value = lower_values[-1] if lower_values else setting.choices[:][0]
-1] if lower_values else setting.choices[:][0]
elif value == 'higher': elif value == 'higher':
higher_values = setting.choices[old_value + 1:] higher_values = setting.choices[old_value + 1:]
value = higher_values[ value = higher_values[0] if higher_values else setting.choices[:][-1]
0] if higher_values else setting.choices[:][-1]
elif value in ('highest', 'max'): elif value in ('highest', 'max'):
value = setting.choices[:][-1] value = setting.choices[:][-1]
elif value in ('lowest', 'min'): elif value in ('lowest', 'min'):
value = setting.choices[:][0] value = setting.choices[:][0]
elif value not in setting.choices: elif value not in setting.choices:
raise Exception( raise Exception("possible values for '%s' are: [%s]" % (setting.name, ', '.join(str(v) for v in setting.choices)))
"possible values for '%s' are: [%s]" %
(setting.name, ', '.join(str(v) for v in setting.choices)))
value = setting.choices[value] value = setting.choices[value]
elif setting.kind == _settings.KIND.range: elif setting.kind == _settings.KIND.range:
@ -131,6 +123,5 @@ def run(receivers, args, find_receiver, find_device):
result = setting.write(value) result = setting.write(value)
if result is None: if result is None:
raise Exception("failed to set '%s' = '%s' [%r]" % raise Exception("failed to set '%s' = '%s' [%r]" % (setting.name, str(value), value))
(setting.name, str(value), value))
_print_setting(setting, False) _print_setting(setting, False)

View File

@ -39,15 +39,12 @@ def run(receivers, args, find_receiver, _ignore):
receiver = receivers[0] receiver = receivers[0]
assert receiver assert receiver
receiver.status = _status.ReceiverStatus(receiver, receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None)
lambda *args, **kwargs: None)
# check if it's necessary to set the notification flags # check if it's necessary to set the notification flags
old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0 old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0
if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless):
_hidpp10.set_notification_flags( _hidpp10.set_notification_flags(receiver, old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless)
receiver,
old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless)
# get all current devices # get all current devices
known_devices = [dev.number for dev in receiver] known_devices = [dev.number for dev in receiver]
@ -61,17 +58,14 @@ def run(receivers, args, find_receiver, _ignore):
if n.devnumber not in known_devices: if n.devnumber not in known_devices:
receiver.status.new_device = receiver[n.devnumber] receiver.status.new_device = receiver[n.devnumber]
elif receiver.re_pairs: elif receiver.re_pairs:
del receiver[ del receiver[n.devnumber] # get rid of information on device re-paired away
n.
devnumber] # get rid of information on device re-paired away
receiver.status.new_device = receiver[n.devnumber] receiver.status.new_device = receiver[n.devnumber]
timeout = 20 # seconds timeout = 20 # seconds
receiver.handle = _HandleWithNotificationHook(receiver.handle) receiver.handle = _HandleWithNotificationHook(receiver.handle)
receiver.set_lock(False, timeout=timeout) receiver.set_lock(False, timeout=timeout)
print('Pairing: turn your new device on (timing out in', timeout, print('Pairing: turn your new device on (timing out in', timeout, 'seconds).')
'seconds).')
# the lock-open notification may come slightly later, wait for it a bit # the lock-open notification may come slightly later, wait for it a bit
pairing_start = _timestamp() pairing_start = _timestamp()
@ -91,8 +85,7 @@ def run(receivers, args, find_receiver, _ignore):
if receiver.status.new_device: if receiver.status.new_device:
dev = receiver.status.new_device dev = receiver.status.new_device
print('Paired device %d: %s (%s) [%s:%s]' % print('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial))
(dev.number, dev.name, dev.codename, dev.wpid, dev.serial))
else: else:
error = receiver.status.get(_status.KEYS.ERROR) error = receiver.status.get(_status.KEYS.ERROR)
if error: if error:

View File

@ -43,32 +43,24 @@ def run(receivers, args, find_receiver, _ignore):
print(' Register Dump') print(' Register Dump')
register = receiver.read_register(_R.notifications) register = receiver.read_register(_R.notifications)
print(' Notification Register %#04x: %s' % print(' Notification Register %#04x: %s' % (_R.notifications % 0x100, '0x' + _strhex(register) if register else 'None'))
(_R.notifications % 0x100,
'0x' + _strhex(register) if register else 'None'))
register = receiver.read_register(_R.receiver_connection) register = receiver.read_register(_R.receiver_connection)
print(' Connection State %#04x: %s' % print(' Connection State %#04x: %s' %
(_R.receiver_connection % 0x100, (_R.receiver_connection % 0x100, '0x' + _strhex(register) if register else 'None'))
'0x' + _strhex(register) if register else 'None'))
register = receiver.read_register(_R.devices_activity) register = receiver.read_register(_R.devices_activity)
print(' Device Activity %#04x: %s' % print(' Device Activity %#04x: %s' %
(_R.devices_activity % 0x100, (_R.devices_activity % 0x100, '0x' + _strhex(register) if register else 'None'))
'0x' + _strhex(register) if register else 'None'))
for device in range(0, 6): for device in range(0, 6):
for sub_reg in [0x0, 0x10, 0x20, 0x30]: for sub_reg in [0x0, 0x10, 0x20, 0x30]:
register = receiver.read_register(_R.receiver_info, register = receiver.read_register(_R.receiver_info, sub_reg + device)
sub_reg + device)
print(' Pairing Register %#04x %#04x: %s' % print(' Pairing Register %#04x %#04x: %s' %
(_R.receiver_info % 0x100, sub_reg + device, (_R.receiver_info % 0x100, sub_reg + device, '0x' + _strhex(register) if register else 'None'))
'0x' + _strhex(register) if register else 'None'))
register = receiver.read_register(_R.receiver_info, 0x40 + device) register = receiver.read_register(_R.receiver_info, 0x40 + device)
print(' Pairing Name %#04x %#02x: %s' % print(' Pairing Name %#04x %#02x: %s' %
(_R.receiver_info % 0x100, 0x40 + device, (_R.receiver_info % 0x100, 0x40 + device, register[2:2 + ord(register[1:2])] if register else 'None'))
register[2:2 + ord(register[1:2])] if register else 'None'))
for sub_reg in range(0, 5): for sub_reg in range(0, 5):
register = receiver.read_register(_R.firmware, sub_reg) register = receiver.read_register(_R.firmware, sub_reg)
print(' Firmware %#04x %#04x: %s' % print(' Firmware %#04x %#04x: %s' %
(_R.firmware % 0x100, sub_reg, (_R.firmware % 0x100, sub_reg, '0x' + _strhex(register) if register else 'None'))
'0x' + _strhex(register) if register else 'None'))

View File

@ -37,28 +37,22 @@ def _print_receiver(receiver):
for f in 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, print(' Has', paired_count, 'paired device(s) out of a maximum of %d.' % receiver.max_devices)
'paired device(s) out of a maximum of %d.' % receiver.max_devices)
if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0: if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0:
print(' Has %d successful pairing(s) remaining.' % print(' Has %d successful pairing(s) remaining.' % receiver.remaining_pairings())
receiver.remaining_pairings())
notification_flags = _hidpp10.get_notification_flags(receiver) notification_flags = _hidpp10.get_notification_flags(receiver)
if notification_flags is not None: if notification_flags is not None:
if notification_flags: if notification_flags:
notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names( notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags)
notification_flags) print(' Notifications: %s (0x%06X)' % (', '.join(notification_names), notification_flags))
print(' Notifications: %s (0x%06X)' %
(', '.join(notification_names), notification_flags))
else: else:
print(' Notifications: (none)') print(' Notifications: (none)')
activity = receiver.read_register(_hidpp10.REGISTERS.devices_activity) activity = receiver.read_register(_hidpp10.REGISTERS.devices_activity)
if activity: if activity:
activity = [(d, ord(activity[d - 1:d])) activity = [(d, ord(activity[d - 1:d])) for d in range(1, receiver.max_devices)]
for d in range(1, receiver.max_devices)] activity_text = ', '.join(('%d=%d' % (d, a)) for d, a in activity if a > 0)
activity_text = ', '.join(
('%d=%d' % (d, a)) for d, a in activity if a > 0)
print(' Device activity counters:', activity_text or '(empty)') print(' Device activity counters:', activity_text or '(empty)')
@ -85,26 +79,21 @@ def _print_device(dev):
else: else:
print(' Protocol : unknown (device is offline)') print(' Protocol : unknown (device is offline)')
if dev.polling_rate: if dev.polling_rate:
print(' Polling rate :', dev.polling_rate, print(' Polling rate :', dev.polling_rate, 'ms (%dHz)' % (1000 // dev.polling_rate))
'ms (%dHz)' % (1000 // dev.polling_rate))
print(' Serial number:', dev.serial) print(' Serial number:', dev.serial)
if dev.firmware: if dev.firmware:
for fw in dev.firmware: for fw in dev.firmware:
print(' %11s:' % fw.kind, print(' %11s:' % fw.kind, (fw.name + ' ' + fw.version).strip())
(fw.name + ' ' + fw.version).strip())
if dev.power_switch_location: if dev.power_switch_location:
print(' The power switch is located on the %s.' % print(' The power switch is located on the %s.' % dev.power_switch_location)
dev.power_switch_location)
if dev.online: if dev.online:
notification_flags = _hidpp10.get_notification_flags(dev) notification_flags = _hidpp10.get_notification_flags(dev)
if notification_flags is not None: if notification_flags is not None:
if notification_flags: if notification_flags:
notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names( notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags)
notification_flags) print(' Notifications: %s (0x%06X).' % (', '.join(notification_names), notification_flags))
print(' Notifications: %s (0x%06X).' %
(', '.join(notification_names), notification_flags))
else: else:
print(' Notifications: (none).') print(' Notifications: (none).')
@ -118,8 +107,7 @@ def _print_device(dev):
flags = dev.request(0x0000, feature.bytes(2)) flags = dev.request(0x0000, feature.bytes(2))
flags = 0 if flags is None else ord(flags[1:2]) flags = 0 if flags is None else ord(flags[1:2])
flags = _hidpp20.FEATURE_FLAG.flag_names(flags) flags = _hidpp20.FEATURE_FLAG.flag_names(flags)
print(' %2d: %-22s {%04X} %s' % print(' %2d: %-22s {%04X} %s' % (index, feature, feature, ', '.join(flags)))
(index, feature, feature, ', '.join(flags)))
if feature == _hidpp20.FEATURE.HIRES_WHEEL: if feature == _hidpp20.FEATURE.HIRES_WHEEL:
wheel = _hidpp20.get_hires_wheel(dev) wheel = _hidpp20.get_hires_wheel(dev)
if wheel: if wheel:
@ -149,8 +137,7 @@ def _print_device(dev):
mouse_pointer = _hidpp20.get_mouse_pointer_info(dev) mouse_pointer = _hidpp20.get_mouse_pointer_info(dev)
if mouse_pointer: if mouse_pointer:
print(' DPI: %s' % mouse_pointer['dpi']) print(' DPI: %s' % mouse_pointer['dpi'])
print(' Acceleration: %s' % print(' Acceleration: %s' % mouse_pointer['acceleration'])
mouse_pointer['acceleration'])
if mouse_pointer['suggest_os_ballistics']: if mouse_pointer['suggest_os_ballistics']:
print(' Use OS ballistics') print(' Use OS ballistics')
else: else:
@ -160,25 +147,19 @@ def _print_device(dev):
else: else:
print(' No vertical tuning, standard mice') print(' No vertical tuning, standard mice')
if feature == _hidpp20.FEATURE.VERTICAL_SCROLLING: if feature == _hidpp20.FEATURE.VERTICAL_SCROLLING:
vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info( vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev)
dev)
if vertical_scrolling_info: if vertical_scrolling_info:
print(' Roller type: %s' % print(' Roller type: %s' % vertical_scrolling_info['roller'])
vertical_scrolling_info['roller']) print(' Ratchet per turn: %s' % vertical_scrolling_info['ratchet'])
print(' Ratchet per turn: %s' % print(' Scroll lines: %s' % vertical_scrolling_info['lines'])
vertical_scrolling_info['ratchet'])
print(' Scroll lines: %s' %
vertical_scrolling_info['lines'])
elif feature == _hidpp20.FEATURE.HI_RES_SCROLLING: elif feature == _hidpp20.FEATURE.HI_RES_SCROLLING:
scrolling_mode, scrolling_resolution = _hidpp20.get_hi_res_scrolling_info( scrolling_mode, scrolling_resolution = _hidpp20.get_hi_res_scrolling_info(dev)
dev)
if scrolling_mode: if scrolling_mode:
print(' Hi-res scrolling enabled') print(' Hi-res scrolling enabled')
else: else:
print(' Hi-res scrolling disabled') print(' Hi-res scrolling disabled')
if scrolling_resolution: if scrolling_resolution:
print(' Hi-res scrolling multiplier: %s' % print(' Hi-res scrolling multiplier: %s' % scrolling_resolution)
scrolling_resolution)
elif feature == _hidpp20.FEATURE.POINTER_SPEED: elif feature == _hidpp20.FEATURE.POINTER_SPEED:
pointer_speed = _hidpp20.get_pointer_speed_info(dev) pointer_speed = _hidpp20.get_pointer_speed_info(dev)
if pointer_speed: if pointer_speed:
@ -189,10 +170,8 @@ def _print_device(dev):
print(' Wheel Reports: %s' % wheel_status) print(' Wheel Reports: %s' % wheel_status)
elif feature == _hidpp20.FEATURE.NEW_FN_INVERSION: elif feature == _hidpp20.FEATURE.NEW_FN_INVERSION:
inverted, default_inverted = _hidpp20.get_new_fn_inversion(dev) inverted, default_inverted = _hidpp20.get_new_fn_inversion(dev)
print(' Fn-swap:', print(' Fn-swap:', 'enabled' if inverted else 'disabled')
'enabled' if inverted else 'disabled') print(' Fn-swap default:', 'enabled' if default_inverted else 'disabled')
print(' Fn-swap default:',
'enabled' if default_inverted else 'disabled')
for setting in dev_settings: for setting in dev_settings:
if setting.feature == feature: if setting.feature == feature:
v = setting.read(False) v = setting.read(False)
@ -204,13 +183,10 @@ def _print_device(dev):
flags = _special_keys.KEY_FLAG.flag_names(k.flags) flags = _special_keys.KEY_FLAG.flag_names(k.flags)
# TODO: add here additional variants for other REPROG_CONTROLS # TODO: add here additional variants for other REPROG_CONTROLS
if dev.keys.keyversion == 1: if dev.keys.keyversion == 1:
print(' %2d: %-26s => %-27s %s' % print(' %2d: %-26s => %-27s %s' % (k.index, k.key, k.task, ', '.join(flags)))
(k.index, k.key, k.task, ', '.join(flags)))
if dev.keys.keyversion == 4: if dev.keys.keyversion == 4:
print(' %2d: %-26s, default: %-27s => %-26s' % print(' %2d: %-26s, default: %-27s => %-26s' % (k.index, k.key, k.task, k.remapped))
(k.index, k.key, k.task, k.remapped)) print(' %s, pos:%d, group:%1d, gmask:%d' % (', '.join(flags), k.pos, k.group, k.group_mask))
print(' %s, pos:%d, group:%1d, gmask:%d' %
(', '.join(flags), k.pos, k.group, k.group_mask))
if dev.online: if dev.online:
battery = _hidpp20.get_battery(dev) battery = _hidpp20.get_battery(dev)
if battery is None: if battery is None:
@ -218,14 +194,12 @@ def _print_device(dev):
if battery is not None: if battery is not None:
level, status, nextLevel = battery level, status, nextLevel = battery
text = _battery_text(level) text = _battery_text(level)
nextText = '' if nextLevel is None else ', next level ' + _battery_text( nextText = '' if nextLevel is None else ', next level ' + _battery_text(nextLevel)
nextLevel)
print(' Battery: %s, %s%s.' % (text, status, nextText)) print(' Battery: %s, %s%s.' % (text, status, nextText))
else: else:
battery_voltage = _hidpp20.get_voltage(dev) battery_voltage = _hidpp20.get_voltage(dev)
if battery_voltage: if battery_voltage:
(level, status, voltage, charge_sts, (level, status, voltage, charge_sts, charge_type) = battery_voltage
charge_type) = battery_voltage
print(' Battery: %smV, %s, %s.' % (voltage, status, level)) print(' Battery: %smV, %s, %s.' % (voltage, status, level))
else: else:
print(' Battery status unavailable.') print(' Battery status unavailable.')

View File

@ -28,15 +28,12 @@ def run(receivers, args, find_receiver, find_device):
dev = find_device(receivers, device_name) dev = find_device(receivers, device_name)
if not dev.receiver.may_unpair: if not dev.receiver.may_unpair:
print( print('Receiver for %s [%s:%s] does not unpair, but attempting anyway' % (dev.name, dev.wpid, dev.serial))
'Receiver for %s [%s:%s] does not unpair, but attempting anyway' %
(dev.name, dev.wpid, dev.serial))
try: try:
# query these now, it's last chance to get them # query these now, it's last chance to get them
number, codename, wpid, serial = dev.number, dev.codename, dev.wpid, dev.serial number, codename, wpid, serial = dev.number, dev.codename, dev.wpid, dev.serial
dev.receiver._unpair_device(number, True) # force an unpair dev.receiver._unpair_device(number, True) # force an unpair
print('Unpaired %d: %s (%s) [%s:%s]' % print('Unpaired %d: %s (%s) [%s:%s]' % (number, dev.name, codename, wpid, serial))
(number, dev.name, codename, wpid, serial))
except Exception as e: except Exception as e:
raise Exception('failed to unpair device %s: %s' % (dev.name, e)) raise Exception('failed to unpair device %s: %s' % (dev.name, e))

View File

@ -31,8 +31,7 @@ from solaar import __version__
_log = getLogger(__name__) _log = getLogger(__name__)
del getLogger del getLogger
_XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser( _XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser(_path.join('~', '.config'))
_path.join('~', '.config'))
_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json') _file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json')
_KEY_VERSION = '_version' _KEY_VERSION = '_version'
@ -78,11 +77,7 @@ def save():
try: try:
with open(_file_path, 'w') as config_file: with open(_file_path, 'w') as config_file:
_json_save(_configuration, _json_save(_configuration, config_file, skipkeys=True, indent=2, sort_keys=True)
config_file,
skipkeys=True,
indent=2,
sort_keys=True)
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
_log.info('saved %s to %s', _configuration, _file_path) _log.info('saved %s to %s', _configuration, _file_path)

View File

@ -48,47 +48,26 @@ prefer_symbolic_battery_icons = False
def _parse_arguments(): def _parse_arguments():
import argparse import argparse
arg_parser = argparse.ArgumentParser(prog=NAME.lower()) arg_parser = argparse.ArgumentParser(prog=NAME.lower())
arg_parser.add_argument( arg_parser.add_argument('-d',
'-d', '--debug',
'--debug', action='count',
action='count', default=0,
default=0, help='print logging messages, for debugging purposes (may be repeated for extra verbosity)')
help= arg_parser.add_argument('-D',
'print logging messages, for debugging purposes (may be repeated for extra verbosity)' '--hidraw',
) action='store',
arg_parser.add_argument( dest='hidraw_path',
'-D', metavar='PATH',
'--hidraw', help='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2')
action='store', arg_parser.add_argument('--restart-on-wake-up', action='store_true', help='restart Solaar on sleep wake-up (experimental)')
dest='hidraw_path', arg_parser.add_argument('-w',
metavar='PATH', '--window',
help= choices=('show', 'hide', 'only'),
'unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2' help='start with window showing / hidden / only (no tray icon)')
) arg_parser.add_argument('-b', '--battery-icons', choices=('regular', 'symbolic'), help='prefer regular / symbolic icons')
arg_parser.add_argument( arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__)
'--restart-on-wake-up', arg_parser.add_argument('--help-actions', action='store_true', help='print help for the optional actions')
action='store_true', arg_parser.add_argument('action', nargs=argparse.REMAINDER, choices=_cli.actions, help='optional actions to perform')
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)')
arg_parser.add_argument('-b',
'--battery-icons',
choices=('regular', 'symbolic'),
help='prefer regular / symbolic 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() args = arg_parser.parse_args()
@ -106,17 +85,14 @@ def _parse_arguments():
if args.debug > 0: if args.debug > 0:
log_level = logging.WARNING - 10 * args.debug log_level = logging.WARNING - 10 * args.debug
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'
logging.basicConfig(level=max(log_level, logging.DEBUG), logging.basicConfig(level=max(log_level, logging.DEBUG), format=log_format, datefmt='%H:%M:%S')
format=log_format,
datefmt='%H:%M:%S')
else: else:
logging.root.addHandler(logging.NullHandler()) logging.root.addHandler(logging.NullHandler())
logging.root.setLevel(logging.ERROR) logging.root.setLevel(logging.ERROR)
if not args.action: if not args.action:
if logging.root.isEnabledFor(logging.INFO): if logging.root.isEnabledFor(logging.INFO):
logging.info('language %s (%s), translations path %s', logging.info('language %s (%s), translations path %s', _i18n.language, _i18n.encoding, _i18n.path)
_i18n.language, _i18n.encoding, _i18n.path)
return args return args

View File

@ -33,18 +33,14 @@ def _find_locale_path(lc_domain):
import os.path as _path import os.path as _path
import sys as _sys import sys as _sys
prefix_share = _path.normpath( prefix_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..'))
_path.join(_path.realpath(_sys.path[0]), '..')) src_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..', 'share'))
src_share = _path.normpath(
_path.join(_path.realpath(_sys.path[0]), '..', 'share'))
del _sys del _sys
from glob import glob as _glob from glob import glob as _glob
for location in prefix_share, src_share: for location in prefix_share, src_share:
mo_files = _glob( mo_files = _glob(_path.join(location, 'locale', '*', 'LC_MESSAGES', lc_domain + '.mo'))
_path.join(location, 'locale', '*', 'LC_MESSAGES',
lc_domain + '.mo'))
if mo_files: if mo_files:
return _path.join(location, 'locale') return _path.join(location, 'locale')

View File

@ -42,9 +42,7 @@ del getLogger
# #
# #
_GHOST_DEVICE = namedtuple( _GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ('receiver', 'number', 'name', 'kind', 'status', 'online'))
'_GHOST_DEVICE',
('receiver', 'number', 'name', 'kind', 'status', 'online'))
_GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__bool__ = lambda self: False
_GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__
del namedtuple del namedtuple
@ -72,8 +70,7 @@ class ReceiverListener(_listener.EventsListener):
"""Keeps the status of a Receiver. """Keeps the status of a Receiver.
""" """
def __init__(self, receiver, status_changed_callback): def __init__(self, receiver, status_changed_callback):
super(ReceiverListener, self).__init__(receiver, super(ReceiverListener, self).__init__(receiver, self._notifications_handler)
self._notifications_handler)
# no reason to enable polling yet # no reason to enable polling yet
# self.tick_period = _POLL_TICK # self.tick_period = _POLL_TICK
# self._last_tick = 0 # self._last_tick = 0
@ -84,11 +81,9 @@ class ReceiverListener(_listener.EventsListener):
def has_started(self): def has_started(self):
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
_log.info('%s: notifications listener has started (%s)', _log.info('%s: notifications listener has started (%s)', self.receiver, self.receiver.handle)
self.receiver, self.receiver.handle)
notification_flags = self.receiver.enable_notifications() notification_flags = self.receiver.enable_notifications()
self.receiver.status[ self.receiver.status[_status.KEYS.NOTIFICATION_FLAGS] = notification_flags
_status.KEYS.NOTIFICATION_FLAGS] = notification_flags
self.receiver.notify_devices() self.receiver.notify_devices()
self._status_changed(self.receiver) # , _status.ALERT.NOTIFICATION) self._status_changed(self.receiver) # , _status.ALERT.NOTIFICATION)
@ -151,14 +146,11 @@ class ReceiverListener(_listener.EventsListener):
assert device is not None assert device is not None
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
if device.kind is None: if device.kind is None:
_log.info('status_changed %s: %s, %s (%X) %s', device, _log.info('status_changed %s: %s, %s (%X) %s', device, 'present' if bool(device) else 'removed', device.status,
'present' if bool(device) else 'removed', alert, reason or '')
device.status, alert, reason or '')
else: else:
_log.info('status_changed %s: %s %s, %s (%X) %s', device, _log.info('status_changed %s: %s %s, %s (%X) %s', device, 'paired' if bool(device) else 'unpaired',
'paired' if bool(device) else 'unpaired', 'online' if device.online else 'offline', device.status, alert, reason or '')
'online' if device.online else 'offline',
device.status, alert, reason or '')
if device.kind is None: if device.kind is None:
assert device == self.receiver assert device == self.receiver
@ -193,9 +185,7 @@ class ReceiverListener(_listener.EventsListener):
# a device notification # a device notification
if not (0 < n.devnumber <= self.receiver.max_devices): if not (0 < n.devnumber <= self.receiver.max_devices):
if _log.isEnabledFor(_WARNING): if _log.isEnabledFor(_WARNING):
_log.warning( _log.warning(_('Unexpected device number (%s) in notification %s.' % (n.devnumber, n)))
_('Unexpected device number (%s) in notification %s.' %
(n.devnumber, n)))
return return
already_known = n.devnumber in self.receiver already_known = n.devnumber in self.receiver
@ -215,14 +205,10 @@ class ReceiverListener(_listener.EventsListener):
if n.sub_id == 0x41: if n.sub_id == 0x41:
if not already_known: if not already_known:
dev = self.receiver.register_new_device(n.devnumber, n) dev = self.receiver.register_new_device(n.devnumber, n)
elif self.receiver.status.lock_open and self.receiver.re_pairs and not ord( elif self.receiver.status.lock_open and self.receiver.re_pairs and not ord(n.data[0:1]) & 0x40:
n.data[0:1]) & 0x40:
dev = self.receiver[n.devnumber] dev = self.receiver[n.devnumber]
del self.receiver[ del self.receiver[n.devnumber] # get rid of information on device re-paired away
n. self._status_changed(dev) # signal that this device has changed
devnumber] # get rid of information on device re-paired away
self._status_changed(
dev) # signal that this device has changed
dev = self.receiver.register_new_device(n.devnumber, n) dev = self.receiver.register_new_device(n.devnumber, n)
self.receiver.status.new_device = self.receiver[n.devnumber] self.receiver.status.new_device = self.receiver[n.devnumber]
else: else:
@ -231,8 +217,7 @@ class ReceiverListener(_listener.EventsListener):
dev = self.receiver[n.devnumber] dev = self.receiver[n.devnumber]
if not dev: if not dev:
_log.warn('%s: received %s for invalid device %d: %r', _log.warn('%s: received %s for invalid device %d: %r', self.receiver, n, n.devnumber, dev)
self.receiver, n, n.devnumber, dev)
return return
# Apply settings every time the device connects # Apply settings every time the device connects
@ -259,8 +244,7 @@ class ReceiverListener(_listener.EventsListener):
dev.ping() dev.ping()
def __str__(self): def __str__(self):
return '<ReceiverListener(%s,%s)>' % (self.receiver.path, return '<ReceiverListener(%s,%s)>' % (self.receiver.path, self.receiver.handle)
self.receiver.handle)
__unicode__ = __str__ __unicode__ = __str__
@ -372,8 +356,7 @@ def _process_receiver_event(action, device_info):
try: try:
import subprocess import subprocess
import re import re
output = subprocess.check_output( output = subprocess.check_output(['/usr/bin/getfacl', '-p', device_info.path])
['/usr/bin/getfacl', '-p', device_info.path])
if not re.search(b'user:.+:', output): if not re.search(b'user:.+:', output):
_error_callback('permissions', device_info.path) _error_callback('permissions', device_info.path)
except Exception: except Exception:

View File

@ -54,14 +54,12 @@ def _error_dialog(reason, object):
text = (_('Failed to unpair %{device} from %{receiver}.').format(device=object.name, receiver=object.receiver.name) + 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.')) '\n\n' + _('The receiver returned an error, with no further details.'))
else: else:
raise Exception("ui.error_dialog: don't know how to handle (%s, %s)", raise Exception("ui.error_dialog: don't know how to handle (%s, %s)", reason, object)
reason, object)
assert title assert title
assert text assert text
m = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, m = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, text)
Gtk.ButtonsType.CLOSE, text)
m.set_title(title) m.set_title(title)
m.run() m.run()
m.destroy() m.destroy()
@ -93,8 +91,7 @@ from . import notify, tray, window # isort:skip # noqa: E402
def _startup(app, startup_hook, use_tray, show_window): def _startup(app, startup_hook, use_tray, show_window):
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('startup registered=%s, remote=%s', app.get_is_registered(), _log.debug('startup registered=%s, remote=%s', app.get_is_registered(), app.get_is_remote())
app.get_is_remote())
from solaar.tasks import TaskRunner as _TaskRunner from solaar.tasks import TaskRunner as _TaskRunner
global _task_runner global _task_runner
@ -144,12 +141,9 @@ def run_loop(startup_hook, shutdown_hook, use_tray, show_window, args=None):
assert use_tray or show_window, 'need either tray or visible window' assert use_tray or show_window, 'need either tray or visible window'
# from gi.repository.Gio import ApplicationFlags as _ApplicationFlags # from gi.repository.Gio import ApplicationFlags as _ApplicationFlags
APP_ID = 'io.github.pwr.solaar' APP_ID = 'io.github.pwr.solaar'
application = Gtk.Application.new( application = Gtk.Application.new(APP_ID, 0) # _ApplicationFlags.HANDLES_COMMAND_LINE)
APP_ID, 0) # _ApplicationFlags.HANDLES_COMMAND_LINE)
application.connect( application.connect('startup', lambda app, startup_hook: _startup(app, startup_hook, use_tray, show_window), startup_hook)
'startup', lambda app, startup_hook: _startup(
app, startup_hook, use_tray, show_window), startup_hook)
application.connect('command-line', _command_line) application.connect('command-line', _command_line)
application.connect('activate', _activate) application.connect('activate', _activate)
application.connect('shutdown', _shutdown, shutdown_hook) application.connect('shutdown', _shutdown, shutdown_hook)

View File

@ -35,9 +35,7 @@ def _create():
about.set_program_name(NAME) about.set_program_name(NAME)
about.set_version(__version__) about.set_version(__version__)
about.set_comments( about.set_comments(_('Shows status of devices connected\nthrough wireless Logitech receivers.'))
_('Shows status of devices connected\nthrough wireless Logitech receivers.'
))
about.set_logo_icon_name(NAME.lower()) about.set_logo_icon_name(NAME.lower())
@ -46,8 +44,7 @@ def _create():
about.set_authors(('Daniel Pavel http://github.com/pwr', )) about.set_authors(('Daniel Pavel http://github.com/pwr', ))
try: try:
about.add_credit_section(_('GUI design'), about.add_credit_section(_('GUI design'), ('Julien Gascard', 'Daniel Pavel'))
('Julien Gascard', 'Daniel Pavel'))
about.add_credit_section(_('Testing'), ( about.add_credit_section(_('Testing'), (
'Douglas Wagner', 'Douglas Wagner',
'Julien Gascard', 'Julien Gascard',

View File

@ -67,10 +67,7 @@ def make_toggle(name, label, function, stock_id=None, *args):
# action.set_sensitive(notify.available) # action.set_sensitive(notify.available)
# toggle_notifications = make_toggle('notifications', 'Notifications', _toggle_notifications) # toggle_notifications = make_toggle('notifications', 'Notifications', _toggle_notifications)
about = make('help-about', about = make('help-about', _('About') + ' ' + NAME, _show_about_window, stock_id=Gtk.STOCK_ABOUT)
_('About') + ' ' + NAME,
_show_about_window,
stock_id=Gtk.STOCK_ABOUT)
# #
# #
@ -94,8 +91,7 @@ def unpair(window, device):
assert device assert device
assert device.kind is not None assert device.kind is not None
qdialog = Gtk.MessageDialog(window, 0, Gtk.MessageType.QUESTION, qdialog = Gtk.MessageDialog(window, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE,
Gtk.ButtonsType.NONE,
_('Unpair') + ' ' + device.name + ' ?') _('Unpair') + ' ' + device.name + ' ?')
qdialog.set_icon_name('remove') qdialog.set_icon_name('remove')
qdialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) qdialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)

View File

@ -106,20 +106,15 @@ def _create_map_choice_control(setting):
def _map_value_notify_value(cbbox, s): def _map_value_notify_value(cbbox, s):
setting, keyBox = s setting, keyBox = s
key_choice = keyBox.get_active_id() key_choice = keyBox.get_active_id()
if key_choice is not None and cbbox.get_sensitive( if key_choice is not None and cbbox.get_sensitive() and cbbox.get_active_id():
) and cbbox.get_active_id():
if setting._value.get(key_choice) != int(cbbox.get_active_id()): if setting._value.get(key_choice) != int(cbbox.get_active_id()):
setting._value[key_choice] = int(cbbox.get_active_id()) setting._value[key_choice] = int(cbbox.get_active_id())
_write_async_key_value(setting, key_choice, _write_async_key_value(setting, key_choice, setting._value[key_choice], cbbox.get_parent().get_parent())
setting._value[key_choice],
cbbox.get_parent().get_parent())
def _map_populate_value_box(valueBox, setting, key_choice): def _map_populate_value_box(valueBox, setting, key_choice):
choices = None choices = None
choices = setting.choices[key_choice] choices = setting.choices[key_choice]
current = setting._value.get( current = setting._value.get(str(key_choice)) # just in case the persisted value is missing some keys
str(key_choice
)) # just in case the persisted value is missing some keys
if choices: if choices:
# TODO i18n text entries # TODO i18n text entries
for choice in choices: for choice in choices:
@ -155,12 +150,10 @@ def _create_slider_control(setting):
self.gtk_range.set_round_digits(0) self.gtk_range.set_round_digits(0)
self.gtk_range.set_digits(0) self.gtk_range.set_digits(0)
self.gtk_range.set_increments(1, 5) self.gtk_range.set_increments(1, 5)
self.gtk_range.connect('value-changed', lambda _, c: c._changed(), self.gtk_range.connect('value-changed', lambda _, c: c._changed(), self)
self)
def _write(self): def _write(self):
_write_async(self.setting, int(self.gtk_range.get_value()), _write_async(self.setting, int(self.gtk_range.get_value()), self.gtk_range.get_parent())
self.gtk_range.get_parent())
self.timer.cancel() self.timer.cancel()
def _changed(self): def _changed(self):
@ -186,8 +179,7 @@ def _create_sbox(s):
spinner = Gtk.Spinner() spinner = Gtk.Spinner()
spinner.set_tooltip_text(_('Working') + '...') spinner.set_tooltip_text(_('Working') + '...')
failed = Gtk.Image.new_from_icon_name('dialog-warning', failed = Gtk.Image.new_from_icon_name('dialog-warning', Gtk.IconSize.SMALL_TOOLBAR)
Gtk.IconSize.SMALL_TOOLBAR)
failed.set_tooltip_text(_('Read/write operation failed.')) failed.set_tooltip_text(_('Read/write operation failed.'))
if s.kind == _SETTING_KIND.toggle: if s.kind == _SETTING_KIND.toggle:
@ -237,8 +229,7 @@ def _create_sbox(s):
def _update_setting_item(sbox, value, is_online=True): def _update_setting_item(sbox, value, is_online=True):
_ignore, failed, spinner, control = sbox.get_children( _ignore, failed, spinner, control = sbox.get_children() # depends on box layout
) # depends on box layout
spinner.set_visible(False) spinner.set_visible(False)
spinner.stop() spinner.stop()

View File

@ -55,23 +55,16 @@ def _look_for_application_icons():
import sys as _sys import sys as _sys
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('sys.path[0] = %s', _sys.path[0]) _log.debug('sys.path[0] = %s', _sys.path[0])
prefix_share = _path.normpath( prefix_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..'))
_path.join(_path.realpath(_sys.path[0]), '..')) src_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..', 'share'))
src_share = _path.normpath( local_share = _environ.get('XDG_DATA_HOME', _path.expanduser(_path.join('~', '.local', 'share')))
_path.join(_path.realpath(_sys.path[0]), '..', 'share'))
local_share = _environ.get(
'XDG_DATA_HOME', _path.expanduser(_path.join('~', '.local', 'share')))
data_dirs = _environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share') data_dirs = _environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share')
repo_share = _path.normpath( repo_share = _path.normpath(_path.join(_path.dirname(__file__), '..', '..', '..', 'share'))
_path.join(_path.dirname(__file__), '..', '..', '..', 'share')) setuptools_share = _path.normpath(_path.join(_path.dirname(__file__), '..', '..', 'share'))
setuptools_share = _path.normpath(
_path.join(_path.dirname(__file__), '..', '..', 'share'))
del _sys del _sys
share_solaar = [prefix_share] + list( share_solaar = [prefix_share] + list(
_path.join(x, 'solaar') _path.join(x, 'solaar') for x in [src_share, local_share, setuptools_share, repo_share] + data_dirs.split(':'))
for x in [src_share, local_share, setuptools_share, repo_share] +
data_dirs.split(':'))
for location in share_solaar: for location in share_solaar:
location = _path.join(location, 'icons') location = _path.join(location, 'icons')
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
@ -137,10 +130,8 @@ def _battery_icon_name(level, charging):
if level is None or level < 0: if level is None or level < 0:
return 'battery-missing' + ('-symbolic' if _use_symbolic_icons else '') return 'battery-missing' + ('-symbolic' if _use_symbolic_icons else '')
level_name = _first_res(level, ((90, 'full'), (50, 'good'), (20, 'low'), level_name = _first_res(level, ((90, 'full'), (50, 'good'), (20, 'low'), (5, 'caution'), (0, 'empty')))
(5, 'caution'), (0, 'empty'))) return 'battery-%s%s%s' % (level_name, '-charging' if charging else '', '-symbolic' if _use_symbolic_icons else '')
return 'battery-%s%s%s' % (level_name, '-charging' if charging else '',
'-symbolic' if _use_symbolic_icons else '')
# #

View File

@ -87,8 +87,7 @@ def _check_lock_state(assistant, receiver, count=2):
if count > 0: if count > 0:
# the actual device notification may arrive after the lock was paired, # the actual device notification may arrive after the lock was paired,
# so have a little patience # so have a little patience
GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, receiver, count - 1)
receiver, count - 1)
else: else:
_pairing_failed(assistant, receiver, 'failed to open pairing lock') _pairing_failed(assistant, receiver, 'failed to open pairing lock')
return False return False
@ -107,12 +106,10 @@ def _prepare(assistant, page, receiver):
assert receiver.status.get(_K.ERROR) is None assert receiver.status.get(_K.ERROR) is None
spinner = page.get_children()[-1] spinner = page.get_children()[-1]
spinner.start() spinner.start()
GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, receiver)
receiver)
assistant.set_page_complete(page, True) assistant.set_page_complete(page, True)
else: else:
GLib.idle_add(_pairing_failed, assistant, receiver, GLib.idle_add(_pairing_failed, assistant, receiver, 'the pairing lock did not open')
'the pairing lock did not open')
else: else:
assistant.remove_page(0) assistant.remove_page(0)
@ -136,19 +133,14 @@ def _pairing_failed(assistant, receiver, error):
header = _('Pairing failed') + ': ' + _(str(error)) + '.' header = _('Pairing failed') + ': ' + _(str(error)) + '.'
if 'timeout' in str(error): if 'timeout' in str(error):
text = _( text = _('Make sure your device is within range, and has a decent battery charge.')
'Make sure your device is within range, and has a decent battery charge.'
)
elif str(error) == 'device not supported': elif str(error) == 'device not supported':
text = _( text = _('A new device was detected, but it is not compatible with this receiver.')
'A new device was detected, but it is not compatible with this receiver.'
)
elif 'many' in str(error): elif 'many' in str(error):
text = _('The receiver only supports %d paired device(s).') text = _('The receiver only supports %d paired device(s).')
else: else:
text = _('No further details are available about the error.') text = _('No further details are available about the error.')
_create_page(assistant, Gtk.AssistantPageType.SUMMARY, header, _create_page(assistant, Gtk.AssistantPageType.SUMMARY, header, 'dialog-error', text)
'dialog-error', text)
assistant.next_page() assistant.next_page()
assistant.commit() assistant.commit()
@ -204,24 +196,19 @@ def create(receiver):
assert receiver.kind is None assert receiver.kind is None
assistant = Gtk.Assistant() assistant = Gtk.Assistant()
assistant.set_title( assistant.set_title(_('%(receiver_name)s: pair new device') % {'receiver_name': receiver.name})
_('%(receiver_name)s: pair new device') %
{'receiver_name': receiver.name})
assistant.set_icon_name('list-add') assistant.set_icon_name('list-add')
assistant.set_size_request(400, 240) assistant.set_size_request(400, 240)
assistant.set_resizable(False) assistant.set_resizable(False)
assistant.set_role('pair-device') assistant.set_role('pair-device')
page_text = _( page_text = _('If the device is already turned on, turn if off and on again.')
'If the device is already turned on, turn if off and on again.')
if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0: if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0:
page_text += _('\n\nThis receiver has %d pairing(s) remaining.' page_text += _('\n\nThis receiver has %d pairing(s) remaining.') % receiver.remaining_pairings()
) % receiver.remaining_pairings()
page_text += _('\nCancelling at this point will not use up a pairing.') page_text += _('\nCancelling at this point will not use up a pairing.')
page_intro = _create_page(assistant, Gtk.AssistantPageType.PROGRESS, page_intro = _create_page(assistant, Gtk.AssistantPageType.PROGRESS, _('Turn on the device you want to pair.'),
_('Turn on the device you want to pair.'),
'preferences-desktop-peripherals', page_text) 'preferences-desktop-peripherals', page_text)
spinner = Gtk.Spinner() spinner = Gtk.Spinner()
spinner.set_visible(True) spinner.set_visible(True)

View File

@ -63,11 +63,7 @@ def _create_menu(quit_handler):
from .action import about, make from .action import about, make
menu.append(about.create_menu_item()) menu.append(about.create_menu_item())
menu.append( menu.append(make('application-exit', _('Quit'), quit_handler, stock_id=Gtk.STOCK_QUIT).create_menu_item())
make('application-exit',
_('Quit'),
quit_handler,
stock_id=Gtk.STOCK_QUIT).create_menu_item())
del about, make del about, make
menu.show_all() menu.show_all()
@ -172,24 +168,21 @@ try:
from gi.repository import AppIndicator3 from gi.repository import AppIndicator3
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('using %sAppIndicator3' % _log.debug('using %sAppIndicator3' % ('Ayatana ' if ayatana_appindicator_found else ''))
('Ayatana ' if ayatana_appindicator_found else ''))
# Defense against AppIndicator3 bug that treats files in current directory as icon files # Defense against AppIndicator3 bug that treats files in current directory as icon files
# https://bugs.launchpad.net/ubuntu/+source/libappindicator/+bug/1363277 # https://bugs.launchpad.net/ubuntu/+source/libappindicator/+bug/1363277
def _icon_file(icon_name): def _icon_file(icon_name):
if not os.path.isfile(icon_name): if not os.path.isfile(icon_name):
return icon_name return icon_name
icon_info = Gtk.IconTheme.get_default().lookup_icon( icon_info = Gtk.IconTheme.get_default().lookup_icon(icon_name, _TRAY_ICON_SIZE, 0)
icon_name, _TRAY_ICON_SIZE, 0)
return icon_info.get_filename() if icon_info else icon_name return icon_info.get_filename() if icon_info else icon_name
def _create(menu): def _create(menu):
theme_paths = Gtk.IconTheme.get_default().get_search_path() theme_paths = Gtk.IconTheme.get_default().get_search_path()
ind = AppIndicator3.Indicator.new_with_path( ind = AppIndicator3.Indicator.new_with_path('indicator-solaar', _icon_file(_icons.TRAY_INIT),
'indicator-solaar', _icon_file(_icons.TRAY_INIT), AppIndicator3.IndicatorCategory.HARDWARE, ':'.join(theme_paths))
AppIndicator3.IndicatorCategory.HARDWARE, ':'.join(theme_paths))
ind.set_title(NAME) ind.set_title(NAME)
ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE) ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
ind.set_attention_icon_full(_icon_file(_icons.TRAY_ATTENTION), '') ind.set_attention_icon_full(_icon_file(_icons.TRAY_ATTENTION), '')
@ -229,11 +222,9 @@ try:
def attention(reason=None): def attention(reason=None):
if _icon.get_status() != AppIndicator3.IndicatorStatus.ATTENTION: if _icon.get_status() != AppIndicator3.IndicatorStatus.ATTENTION:
_icon.set_attention_icon_full(_icon_file(_icons.TRAY_ATTENTION), _icon.set_attention_icon_full(_icon_file(_icons.TRAY_ATTENTION), reason or '')
reason or '')
_icon.set_status(AppIndicator3.IndicatorStatus.ATTENTION) _icon.set_status(AppIndicator3.IndicatorStatus.ATTENTION)
GLib.timeout_add(10 * 1000, _icon.set_status, GLib.timeout_add(10 * 1000, _icon.set_status, AppIndicator3.IndicatorStatus.ACTIVE)
AppIndicator3.IndicatorStatus.ACTIVE)
except ImportError: except ImportError:
@ -247,9 +238,7 @@ except ImportError:
icon.set_tooltip_text(NAME) icon.set_tooltip_text(NAME)
icon.connect('activate', _window_toggle) icon.connect('activate', _window_toggle)
icon.connect('scroll-event', _scroll) icon.connect('scroll-event', _scroll)
icon.connect( icon.connect('popup-menu', lambda icon, button, time: menu.popup(None, None, icon.position_menu, icon, button, time))
'popup-menu', lambda icon, button, time: menu.popup(
None, None, icon.position_menu, icon, button, time))
return icon return icon
@ -326,8 +315,7 @@ def _generate_description_lines():
yield '\t%s <small>(' % p + _('offline') + ')</small>' yield '\t%s <small>(' % p + _('offline') + ')</small>'
else: else:
if status: if status:
yield '<b>%s</b> <small>(' % name + _( yield '<b>%s</b> <small>(' % name + _('no status') + ')</small>'
'no status') + ')</small>'
else: else:
yield '<b>%s</b> <small>(' % name + _('offline') + ')</small>' yield '<b>%s</b> <small>(' % name + _('offline') + ')</small>'
yield '' yield ''
@ -385,20 +373,17 @@ def _add_device(device):
break break
index = index + 1 index = index + 1
new_device_info = (receiver_path, device.number, device.name, new_device_info = (receiver_path, device.number, device.name, device.status)
device.status)
assert len(new_device_info) == len(_RECEIVER_SEPARATOR) assert len(new_device_info) == len(_RECEIVER_SEPARATOR)
_devices_info.insert(index, new_device_info) _devices_info.insert(index, new_device_info)
# label_prefix = b'\xE2\x94\x84 '.decode('utf-8') # label_prefix = b'\xE2\x94\x84 '.decode('utf-8')
label_prefix = ' ' label_prefix = ' '
new_menu_item = Gtk.ImageMenuItem.new_with_label(label_prefix + new_menu_item = Gtk.ImageMenuItem.new_with_label(label_prefix + device.name)
device.name)
new_menu_item.set_image(Gtk.Image()) new_menu_item.set_image(Gtk.Image())
new_menu_item.show_all() new_menu_item.show_all()
new_menu_item.connect('activate', _window_popup, receiver_path, new_menu_item.connect('activate', _window_popup, receiver_path, device.number)
device.number)
_menu.insert(new_menu_item, index) _menu.insert(new_menu_item, index)
return index return index
@ -427,8 +412,7 @@ def _add_receiver(receiver):
new_menu_item = Gtk.ImageMenuItem.new_with_label(receiver.name) new_menu_item = Gtk.ImageMenuItem.new_with_label(receiver.name)
_menu.insert(new_menu_item, index) _menu.insert(new_menu_item, index)
icon_set = _icons.device_icon_set(receiver.name) icon_set = _icons.device_icon_set(receiver.name)
new_menu_item.set_image(Gtk.Image().new_from_icon_set( new_menu_item.set_image(Gtk.Image().new_from_icon_set(icon_set, _MENU_ICON_SIZE))
icon_set, _MENU_ICON_SIZE))
new_menu_item.show_all() new_menu_item.show_all()
new_menu_item.connect('activate', _window_popup, receiver.path) new_menu_item.connect('activate', _window_popup, receiver.path)
@ -521,8 +505,7 @@ def update(device=None):
receiver_path = device.path receiver_path = device.path
if is_alive: if is_alive:
index = None index = None
for idx, (path, _ignore, _ignore, for idx, (path, _ignore, _ignore, _ignore) in enumerate(_devices_info):
_ignore) in enumerate(_devices_info):
if path == receiver_path: if path == receiver_path:
index = idx index = idx
break break
@ -537,8 +520,7 @@ def update(device=None):
is_paired = bool(device) is_paired = bool(device)
receiver_path = device.receiver.path receiver_path = device.receiver.path
index = None index = None
for idx, (path, number, _ignore, for idx, (path, number, _ignore, _ignore) in enumerate(_devices_info):
_ignore) in enumerate(_devices_info):
if path == receiver_path and number == device.number: if path == receiver_path and number == device.number:
index = idx index = idx
@ -557,8 +539,7 @@ def update(device=None):
menu_items[no_receivers_index + 1].set_visible(not _devices_info) menu_items[no_receivers_index + 1].set_visible(not _devices_info)
global _picked_device global _picked_device
if (not _picked_device or _last_scroll if (not _picked_device or _last_scroll == 0) and device is not None and device.kind is not None:
== 0) and device is not None and device.kind is not None:
# if it's just a receiver update, it's unlikely the picked device would change # if it's just a receiver update, it's unlikely the picked device would change
_picked_device = _pick_device_with_lowest_battery() _picked_device = _pick_device_with_lowest_battery()

View File

@ -58,14 +58,7 @@ except (ValueError, AttributeError):
_CAN_SET_ROW_NONE = '' _CAN_SET_ROW_NONE = ''
# tree model columns # tree model columns
_COLUMN = _NamedInts(PATH=0, _COLUMN = _NamedInts(PATH=0, NUMBER=1, ACTIVE=2, NAME=3, ICON=4, STATUS_TEXT=5, STATUS_ICON=6, DEVICE=7)
NUMBER=1,
ACTIVE=2,
NAME=3,
ICON=4,
STATUS_TEXT=5,
STATUS_ICON=6,
DEVICE=7)
_COLUMN_TYPES = (str, int, bool, str, str, str, str, TYPE_PYOBJECT) _COLUMN_TYPES = (str, int, bool, str, str, str, str, TYPE_PYOBJECT)
_TREE_SEPATATOR = (None, 0, False, None, None, None, None, None) _TREE_SEPATATOR = (None, 0, False, None, None, None, None, None)
assert len(_TREE_SEPATATOR) == len(_COLUMN_TYPES) assert len(_TREE_SEPATATOR) == len(_COLUMN_TYPES)
@ -76,12 +69,7 @@ assert len(_COLUMN_TYPES) == len(_COLUMN)
# #
def _new_button(label, def _new_button(label, icon_name=None, icon_size=_NORMAL_BUTTON_ICON_SIZE, tooltip=None, toggle=False, clicked=None):
icon_name=None,
icon_size=_NORMAL_BUTTON_ICON_SIZE,
tooltip=None,
toggle=False,
clicked=None):
if toggle: if toggle:
b = Gtk.ToggleButton() b = Gtk.ToggleButton()
else: else:
@ -198,9 +186,7 @@ def _create_buttons_box():
assert receiver.kind is None assert receiver.kind is None
_action.pair(_window, receiver) _action.pair(_window, receiver)
bb._pair = _new_button(_('Pair new device'), bb._pair = _new_button(_('Pair new device'), 'list-add', clicked=_pair_new_device)
'list-add',
clicked=_pair_new_device)
bb.add(bb._pair) bb.add(bb._pair)
def _unpair_current_device(trigger): def _unpair_current_device(trigger):
@ -211,9 +197,7 @@ def _create_buttons_box():
assert device.kind is not None assert device.kind is not None
_action.unpair(_window, device) _action.unpair(_window, device)
bb._unpair = _new_button(_('Unpair'), bb._unpair = _new_button(_('Unpair'), 'edit-delete', clicked=_unpair_current_device)
'edit-delete',
clicked=_unpair_current_device)
bb.add(bb._unpair) bb.add(bb._unpair)
return bb return bb
@ -239,8 +223,7 @@ def _create_info_panel():
b1.pack_start(p._icon, False, False, 0) b1.pack_start(p._icon, False, False, 0)
p.pack_start(b1, False, False, 0) p.pack_start(b1, False, False, 0)
p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, 0) # spacer
0) # spacer
p._receiver = _create_receiver_panel() p._receiver = _create_receiver_panel()
p.pack_start(p._receiver, True, True, 0) p.pack_start(p._receiver, True, True, 0)
@ -248,8 +231,7 @@ def _create_info_panel():
p._device = _create_device_panel() p._device = _create_device_panel()
p.pack_start(p._device, True, True, 0) p.pack_start(p._device, True, True, 0)
p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, 0) # spacer
0) # spacer
p._buttons = _create_buttons_box() p._buttons = _create_buttons_box()
p.pack_end(p._buttons, False, False, 0) p.pack_end(p._buttons, False, False, 0)
@ -293,20 +275,16 @@ def _create_tree(model):
status_cell_renderer.set_property('scale', 0.85) status_cell_renderer.set_property('scale', 0.85)
status_cell_renderer.set_property('xalign', 1) status_cell_renderer.set_property('xalign', 1)
status_column = Gtk.TreeViewColumn('status text', status_cell_renderer) status_column = Gtk.TreeViewColumn('status text', status_cell_renderer)
status_column.add_attribute(status_cell_renderer, 'sensitive', status_column.add_attribute(status_cell_renderer, 'sensitive', _COLUMN.ACTIVE)
_COLUMN.ACTIVE) status_column.add_attribute(status_cell_renderer, 'text', _COLUMN.STATUS_TEXT)
status_column.add_attribute(status_cell_renderer, 'text',
_COLUMN.STATUS_TEXT)
status_column.set_expand(True) status_column.set_expand(True)
tree.append_column(status_column) tree.append_column(status_column)
battery_cell_renderer = Gtk.CellRendererPixbuf() battery_cell_renderer = Gtk.CellRendererPixbuf()
battery_cell_renderer.set_property('stock-size', _TREE_ICON_SIZE) battery_cell_renderer.set_property('stock-size', _TREE_ICON_SIZE)
battery_column = Gtk.TreeViewColumn('status icon', battery_cell_renderer) battery_column = Gtk.TreeViewColumn('status icon', battery_cell_renderer)
battery_column.add_attribute(battery_cell_renderer, 'sensitive', battery_column.add_attribute(battery_cell_renderer, 'sensitive', _COLUMN.ACTIVE)
_COLUMN.ACTIVE) battery_column.add_attribute(battery_cell_renderer, 'icon-name', _COLUMN.STATUS_ICON)
battery_column.add_attribute(battery_cell_renderer, 'icon-name',
_COLUMN.STATUS_ICON)
tree.append_column(battery_column) tree.append_column(battery_column)
return tree return tree
@ -339,10 +317,7 @@ def _create_window_layout():
bottom_buttons_box = Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL) bottom_buttons_box = Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL)
bottom_buttons_box.set_layout(Gtk.ButtonBoxStyle.START) bottom_buttons_box.set_layout(Gtk.ButtonBoxStyle.START)
bottom_buttons_box.set_spacing(20) bottom_buttons_box.set_spacing(20)
quit_button = _new_button(_('Quit') + ' ' + NAME, quit_button = _new_button(_('Quit') + ' ' + NAME, 'application-exit', icon_size=_SMALL_BUTTON_ICON_SIZE, clicked=destroy)
'application-exit',
icon_size=_SMALL_BUTTON_ICON_SIZE,
clicked=destroy)
bottom_buttons_box.add(quit_button) bottom_buttons_box.add(quit_button)
about_button = _new_button(_('About') + ' ' + NAME, about_button = _new_button(_('About') + ' ' + NAME,
'help-about', 'help-about',
@ -406,8 +381,7 @@ def _find_selected_device_id():
selection = _tree.get_selection() selection = _tree.get_selection()
model, item = selection.get_selected() model, item = selection.get_selected()
if item: if item:
return _model.get_value(item, _COLUMN.PATH), _model.get_value( return _model.get_value(item, _COLUMN.PATH), _model.get_value(item, _COLUMN.NUMBER)
item, _COLUMN.NUMBER)
# triggered by changing selection in the tree # triggered by changing selection in the tree
@ -440,8 +414,7 @@ def _receiver_row(receiver_path, receiver=None):
icon_name = _icons.device_icon_name(receiver.name) icon_name = _icons.device_icon_name(receiver.name)
status_text = None status_text = None
status_icon = None status_icon = None
row_data = (receiver_path, 0, True, receiver.name, icon_name, row_data = (receiver_path, 0, True, receiver.name, icon_name, status_text, status_icon, receiver)
status_text, status_icon, receiver)
assert len(row_data) == len(_TREE_SEPATATOR) assert len(row_data) == len(_TREE_SEPATATOR)
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('new receiver row %s', row_data) _log.debug('new receiver row %s', row_data)
@ -456,8 +429,7 @@ def _device_row(receiver_path, device_number, device=None):
assert receiver_path assert receiver_path
assert device_number is not None assert device_number is not None
receiver_row = _receiver_row(receiver_path, receiver_row = _receiver_row(receiver_path, None if device is None else device.receiver)
None if device is None else device.receiver)
item = _model.iter_children(receiver_row) item = _model.iter_children(receiver_row)
new_child_index = 0 new_child_index = 0
while item: while item:
@ -475,13 +447,11 @@ def _device_row(receiver_path, device_number, device=None):
icon_name = _icons.device_icon_name(device.name, device.kind) icon_name = _icons.device_icon_name(device.name, device.kind)
status_text = None status_text = None
status_icon = None status_icon = None
row_data = (receiver_path, device_number, bool(device.online), row_data = (receiver_path, device_number, bool(device.online), device.codename, icon_name, status_text, status_icon,
device.codename, icon_name, status_text, status_icon,
device) device)
assert len(row_data) == len(_TREE_SEPATATOR) assert len(row_data) == len(_TREE_SEPATATOR)
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):
_log.debug('new device row %s at index %d', row_data, _log.debug('new device row %s at index %d', row_data, new_child_index)
new_child_index)
item = _model.insert(receiver_row, new_child_index, row_data) item = _model.insert(receiver_row, new_child_index, row_data)
return item or None return item or None
@ -503,8 +473,7 @@ def select(receiver_path, device_number=None):
selection = _tree.get_selection() selection = _tree.get_selection()
selection.select_iter(item) selection.select_iter(item)
else: else:
_log.warn('select(%s, %s) failed to find an item', receiver_path, _log.warn('select(%s, %s) failed to find an item', receiver_path, device_number)
device_number)
def _hide(w, _ignore=None): def _hide(w, _ignore=None):
@ -562,14 +531,12 @@ def _update_details(button):
yield (_('Index'), device.number) yield (_('Index'), device.number)
yield (_('Wireless PID'), device.wpid) yield (_('Wireless PID'), device.wpid)
hid_version = device.protocol hid_version = device.protocol
yield (_('Protocol'), 'HID++ %1.1f' % yield (_('Protocol'), 'HID++ %1.1f' % hid_version if hid_version else _('Unknown'))
hid_version if hid_version else _('Unknown'))
if read_all and device.polling_rate: if read_all and device.polling_rate:
yield (_('Polling rate'), yield (_('Polling rate'), _('%(rate)d ms (%(rate_hz)dHz)') % {
_('%(rate)d ms (%(rate_hz)dHz)') % { 'rate': device.polling_rate,
'rate': device.polling_rate, 'rate_hz': 1000 // device.polling_rate
'rate_hz': 1000 // device.polling_rate })
})
if read_all or not device.online: if read_all or not device.online:
yield (_('Serial'), device.serial) yield (_('Serial'), device.serial)
@ -579,17 +546,13 @@ def _update_details(button):
if read_all: if read_all:
if device.firmware: if device.firmware:
for fw in list(device.firmware): for fw in list(device.firmware):
yield (' ' + _(str(fw.kind)), yield (' ' + _(str(fw.kind)), (fw.name + ' ' + fw.version).strip())
(fw.name + ' ' + fw.version).strip())
elif device.kind is None or device.online: elif device.kind is None or device.online:
yield (' %s' % _('Firmware'), '...') yield (' %s' % _('Firmware'), '...')
flag_bits = device.status.get(_K.NOTIFICATION_FLAGS) flag_bits = device.status.get(_K.NOTIFICATION_FLAGS)
if flag_bits is not None: if flag_bits is not None:
flag_names = ( flag_names = ('(%s)' % _('none'), ) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits)
'(%s)' % _('none'),
) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(
flag_bits)
yield (_('Notifications'), ('\n%15s' % ' ').join(flag_names)) yield (_('Notifications'), ('\n%15s' % ' ').join(flag_names))
def _set_details(text): def _set_details(text):
@ -626,26 +589,22 @@ def _update_receiver_panel(receiver, panel, buttons, full=False):
devices_count = len(receiver) devices_count = len(receiver)
paired_text = _('No device paired.') if devices_count == 0 else ngettext( paired_text = _('No device paired.') if devices_count == 0 else ngettext('%(count)s paired device.',
'%(count)s paired device.', '%(count)s paired devices.', '%(count)s paired devices.', devices_count) % {
devices_count) % { 'count': devices_count
'count': devices_count }
}
if (receiver.max_devices > 0): if (receiver.max_devices > 0):
paired_text += '\n\n<small>%s</small>' % ngettext( 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 device can be paired to this receiver.', 'Up to %(max_count)s devices can be paired to this receiver.',
'Up to %(max_count)s devices can be paired to this receiver.', receiver.max_devices) % {
receiver.max_devices) % { 'max_count': receiver.max_devices
'max_count': receiver.max_devices }
}
elif devices_count > 0: elif devices_count > 0:
paired_text += '\n\n<small>%s</small>' % _( paired_text += '\n\n<small>%s</small>' % _('Only one device can be paired to this receiver.')
'Only one device can be paired to this receiver.')
pairings = receiver.remaining_pairings(False) pairings = receiver.remaining_pairings(False)
if (pairings is not None and pairings >= 0): if (pairings is not None and pairings >= 0):
paired_text += '\n<small>%s</small>' % _( paired_text += '\n<small>%s</small>' % _('This receiver has %d pairing(s) remaining.') % pairings
'This receiver has %d pairing(s) remaining.') % pairings
panel._count.set_markup(paired_text) panel._count.set_markup(paired_text)
@ -669,11 +628,8 @@ def _update_receiver_panel(receiver, panel, buttons, full=False):
if (receiver.may_unpair or receiver.re_pairs) and not is_pairing and \ if (receiver.may_unpair or receiver.re_pairs) and not is_pairing and \
(receiver.remaining_pairings() is None or receiver.remaining_pairings() != 0): (receiver.remaining_pairings() is None or receiver.remaining_pairings() != 0):
if not receiver.re_pairs and devices_count >= receiver.max_devices: if not receiver.re_pairs and devices_count >= receiver.max_devices:
paired_devices = tuple(n paired_devices = tuple(n for n in range(1, receiver.max_devices + 1) if n in receiver)
for n in range(1, receiver.max_devices + 1) buttons._pair.set_sensitive(len(paired_devices) < receiver.max_devices)
if n in receiver)
buttons._pair.set_sensitive(
len(paired_devices) < receiver.max_devices)
else: else:
buttons._pair.set_sensitive(True) buttons._pair.set_sensitive(True)
else: else:
@ -704,20 +660,16 @@ def _update_device_panel(device, panel, buttons, full=False):
panel._battery._icon.set_sensitive(True) panel._battery._icon.set_sensitive(True)
if battery_voltage is not None: if battery_voltage is not None:
text = '%(battery_voltage)dmV' % { text = '%(battery_voltage)dmV' % {'battery_voltage': battery_voltage}
'battery_voltage': battery_voltage
}
elif isinstance(battery_level, _NamedInt): elif isinstance(battery_level, _NamedInt):
text = _(str(battery_level)) text = _(str(battery_level))
else: else:
text = '%(battery_percent)d%%' % {'battery_percent': battery_level} text = '%(battery_percent)d%%' % {'battery_percent': battery_level}
if battery_next_level is not None: if battery_next_level is not None:
if isinstance(battery_next_level, _NamedInt): if isinstance(battery_next_level, _NamedInt):
text += '<small> (' + _('next ') + _( text += '<small> (' + _('next ') + _(str(battery_next_level)) + ')</small>'
str(battery_next_level)) + ')</small>'
else: else:
text += '<small> (' + _('next ') + ( text += '<small> (' + _('next ') + ('%d%%' % battery_next_level) + ')</small>'
'%d%%' % battery_next_level) + ')</small>'
if is_online: if is_online:
if charging: if charging:
text += ' <small>(%s)</small>' % _('charging') text += ' <small>(%s)</small>' % _('charging')
@ -730,23 +682,18 @@ def _update_device_panel(device, panel, buttons, full=False):
not_secure = device.status.get(_K.LINK_ENCRYPTED) is False not_secure = device.status.get(_K.LINK_ENCRYPTED) is False
if not_secure: if not_secure:
panel._secure._text.set_text(_('not encrypted')) panel._secure._text.set_text(_('not encrypted'))
panel._secure._icon.set_from_icon_name('security-low', panel._secure._icon.set_from_icon_name('security-low', _INFO_ICON_SIZE)
_INFO_ICON_SIZE)
panel._secure.set_tooltip_text( panel._secure.set_tooltip_text(
_('The wireless link between this device and its receiver is not encrypted.\n' _('The wireless link between this device and its receiver is not encrypted.\n'
'\n' '\n'
'For pointing devices (mice, trackballs, trackpads), this is a minor security issue.\n' 'For pointing devices (mice, trackballs, trackpads), this is a minor security issue.\n'
'\n' '\n'
'It is, however, a major security issue for text-input devices (keyboards, numpads),\n' 'It is, however, a major security issue for text-input devices (keyboards, numpads),\n'
'because typed text can be sniffed inconspicuously by 3rd parties within range.' 'because typed text can be sniffed inconspicuously by 3rd parties within range.'))
))
else: else:
panel._secure._text.set_text(_('encrypted')) panel._secure._text.set_text(_('encrypted'))
panel._secure._icon.set_from_icon_name('security-high', panel._secure._icon.set_from_icon_name('security-high', _INFO_ICON_SIZE)
_INFO_ICON_SIZE) panel._secure.set_tooltip_text(_('The wireless link between this device and its receiver is encrypted.'))
panel._secure.set_tooltip_text(
_('The wireless link between this device and its receiver is encrypted.'
))
panel._secure._icon.set_visible(True) panel._secure._icon.set_visible(True)
else: else:
panel._secure._text.set_markup('<small>%s</small>' % _('offline')) panel._secure._text.set_markup('<small>%s</small>' % _('offline'))
@ -758,10 +705,8 @@ def _update_device_panel(device, panel, buttons, full=False):
if light_level is None: if light_level is None:
panel._lux.set_visible(False) panel._lux.set_visible(False)
else: else:
panel._lux._icon.set_from_icon_name(_icons.lux(light_level), panel._lux._icon.set_from_icon_name(_icons.lux(light_level), _INFO_ICON_SIZE)
_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) panel._lux.set_visible(True)
else: else:
panel._lux.set_visible(False) panel._lux.set_visible(False)
@ -880,9 +825,7 @@ def update(device, need_popup=False):
if is_alive and item: if is_alive and item:
was_pairing = bool(_model.get_value(item, _COLUMN.STATUS_ICON)) was_pairing = bool(_model.get_value(item, _COLUMN.STATUS_ICON))
is_pairing = bool(device.status.lock_open) is_pairing = bool(device.status.lock_open)
_model.set_value( _model.set_value(item, _COLUMN.STATUS_ICON, 'network-wireless' if is_pairing else _CAN_SET_ROW_NONE)
item, _COLUMN.STATUS_ICON,
'network-wireless' if is_pairing else _CAN_SET_ROW_NONE)
if selected_device_id == (device.path, 0): if selected_device_id == (device.path, 0):
full_update = need_popup or was_pairing != is_pairing full_update = need_popup or was_pairing != is_pairing
@ -898,10 +841,8 @@ def update(device, need_popup=False):
# peripheral # peripheral
is_paired = bool(device) is_paired = bool(device)
assert device.receiver assert device.receiver
assert device.number is not None and device.number > 0, 'invalid device number' + str( assert device.number is not None and device.number > 0, 'invalid device number' + str(device.number)
device.number) item = _device_row(device.receiver.path, device.number, device if is_paired else None)
item = _device_row(device.receiver.path, device.number,
device if is_paired else None)
if is_paired and item: if is_paired and item:
was_online = _model.get_value(item, _COLUMN.ACTIVE) was_online = _model.get_value(item, _COLUMN.ACTIVE)
@ -915,15 +856,11 @@ def update(device, need_popup=False):
_model.set_value(item, _COLUMN.STATUS_ICON, _CAN_SET_ROW_NONE) _model.set_value(item, _COLUMN.STATUS_ICON, _CAN_SET_ROW_NONE)
else: else:
if battery_voltage is not None: if battery_voltage is not None:
status_text = '%(battery_voltage)dmV' % { status_text = '%(battery_voltage)dmV' % {'battery_voltage': battery_voltage}
'battery_voltage': battery_voltage
}
elif isinstance(battery_level, _NamedInt): elif isinstance(battery_level, _NamedInt):
status_text = _(str(battery_level)) status_text = _(str(battery_level))
else: else:
status_text = '%(battery_percent)d%%' % { status_text = '%(battery_percent)d%%' % {'battery_percent': battery_level}
'battery_percent': battery_level
}
_model.set_value(item, _COLUMN.STATUS_TEXT, status_text) _model.set_value(item, _COLUMN.STATUS_TEXT, status_text)
charging = device.status.get(_K.BATTERY_CHARGING) charging = device.status.get(_K.BATTERY_CHARGING)

View File

@ -76,24 +76,14 @@ try:
bus = dbus.SystemBus() bus = dbus.SystemBus()
assert bus assert bus
bus.add_signal_receiver(_suspend, bus.add_signal_receiver(_suspend, signal_name='Sleeping', dbus_interface=_UPOWER_INTERFACE, bus_name=_UPOWER_BUS)
signal_name='Sleeping',
dbus_interface=_UPOWER_INTERFACE,
bus_name=_UPOWER_BUS)
bus.add_signal_receiver(_resume, bus.add_signal_receiver(_resume, signal_name='Resuming', dbus_interface=_UPOWER_INTERFACE, bus_name=_UPOWER_BUS)
signal_name='Resuming',
dbus_interface=_UPOWER_INTERFACE,
bus_name=_UPOWER_BUS)
bus.add_signal_receiver(_suspend_or_resume, bus.add_signal_receiver(_suspend_or_resume, 'PrepareForSleep', dbus_interface=_LOGIND_INTERFACE, bus_name=_LOGIND_BUS)
'PrepareForSleep',
dbus_interface=_LOGIND_INTERFACE,
bus_name=_LOGIND_BUS)
if _log.isEnabledFor(_INFO): if _log.isEnabledFor(_INFO):
_log.info( _log.info('connected to system dbus, watching for suspend/resume events')
'connected to system dbus, watching for suspend/resume events')
except Exception: except Exception:
# Either: # Either:

View File

@ -3,6 +3,9 @@ max-line-length = 127
extend-ignore = E266,E731,E741 extend-ignore = E266,E731,E741
min-python-version = 3.5 min-python-version = 3.5
[yapf]
column_limit = 127
[isort] [isort]
line_length = 127 line_length = 127
lines_between_types = 1 lines_between_types = 1

View File

@ -19,9 +19,7 @@ def _data_files():
yield 'share/solaar/icons', _glob('share/solaar/icons/solaar*.svg') yield 'share/solaar/icons', _glob('share/solaar/icons/solaar*.svg')
yield 'share/solaar/icons', _glob('share/solaar/icons/light_*.png') yield 'share/solaar/icons', _glob('share/solaar/icons/light_*.png')
yield 'share/icons/hicolor/scalable/apps', [ yield 'share/icons/hicolor/scalable/apps', ['share/solaar/icons/solaar.svg']
'share/solaar/icons/solaar.svg'
]
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 _dirname(mo), [mo]
@ -67,9 +65,7 @@ battery status, and show and modify some of the modifiable features of devices.
'pyudev (>= 0.13)', 'pyudev (>= 0.13)',
], ],
package_dir={'': 'lib'}, package_dir={'': 'lib'},
packages=[ packages=['hidapi', 'logitech_receiver', 'solaar', 'solaar.ui', 'solaar.cli'],
'hidapi', 'logitech_receiver', 'solaar', 'solaar.ui', 'solaar.cli'
],
data_files=list(_data_files()), data_files=list(_data_files()),
scripts=_glob('bin/*'), scripts=_glob('bin/*'),
) )

View File

@ -10,8 +10,7 @@ def init_paths():
import sys import sys
import os.path as _path import os.path as _path
src_lib = _path.normpath( src_lib = _path.normpath(_path.join(_path.realpath(sys.path[0]), '..', 'lib'))
_path.join(_path.realpath(sys.path[0]), '..', 'lib'))
init_py = _path.join(src_lib, 'hidapi', '__init__.py') init_py = _path.join(src_lib, 'hidapi', '__init__.py')
if _path.exists(init_py): if _path.exists(init_py):
sys.path[0] = src_lib sys.path[0] = src_lib

View File

@ -16,5 +16,4 @@ def print_event(action, device):
print('~~~~ device [%s] %s' % (action, device)) print('~~~~ device [%s] %s' % (action, device))
hidapi.monitor(print_event, DEVICE_UNIFYING_RECEIVER, hidapi.monitor(print_event, DEVICE_UNIFYING_RECEIVER, DEVICE_UNIFYING_RECEIVER_2, DEVICE_NANO_RECEIVER)
DEVICE_UNIFYING_RECEIVER_2, DEVICE_NANO_RECEIVER)