yapf: set max line lenght to 127
Signed-off-by: Filipe Laíns <lains@archlinux.org>
This commit is contained in:
parent
627185079f
commit
8e89aa0038
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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'))
|
|
||||||
|
|
|
@ -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.')
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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 '')
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -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/*'),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
Loading…
Reference in New Issue