yapf: set max line lenght to 127

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -65,35 +65,16 @@ def register_toggle(name,
label=None,
description=None,
device_kind=None):
validator = _BooleanV(true_value=true_value,
false_value=false_value,
mask=mask)
validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask)
rw = _RegisterRW(register)
return _Setting(name,
rw,
validator,
label=label,
description=description,
device_kind=device_kind)
return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind)
def register_choices(name,
register,
choices,
kind=_KIND.choice,
label=None,
description=None,
device_kind=None):
def register_choices(name, register, choices, kind=_KIND.choice, label=None, description=None, device_kind=None):
assert choices
validator = _ChoicesV(choices)
rw = _RegisterRW(register)
return _Setting(name,
rw,
validator,
kind=kind,
label=label,
description=description,
device_kind=device_kind)
return _Setting(name, rw, validator, kind=kind, label=label, description=description, device_kind=device_kind)
def feature_toggle(name,
@ -106,17 +87,9 @@ def feature_toggle(name,
label=None,
description=None,
device_kind=None):
validator = _BooleanV(true_value=true_value,
false_value=false_value,
mask=mask)
validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask)
rw = _FeatureRW(feature, read_function_id, write_function_id)
return _Setting(name,
rw,
validator,
feature=feature,
label=label,
description=description,
device_kind=device_kind)
return _Setting(name, rw, validator, feature=feature, label=label, description=description, device_kind=device_kind)
def feature_bitfield_toggle(name,
@ -139,15 +112,14 @@ def feature_bitfield_toggle(name,
device_kind=device_kind)
def feature_bitfield_toggle_dynamic(
name,
feature,
options_callback,
read_function_id=_FeatureRW.default_read_fnid,
write_function_id=_FeatureRW.default_write_fnid,
label=None,
description=None,
device_kind=None):
def feature_bitfield_toggle_dynamic(name,
feature,
options_callback,
read_function_id=_FeatureRW.default_read_fnid,
write_function_id=_FeatureRW.default_write_fnid,
label=None,
description=None,
device_kind=None):
def instantiate(device):
options = options_callback(device)
setting = feature_bitfield_toggle(name,
@ -235,10 +207,7 @@ def feature_map_choices(name,
skip_bytes_count=skip_bytes_count,
value_bytes_count=value_bytes_count,
extra_default=extra_default)
rw = _FeatureRWMap(feature,
read_function_id,
write_function_id,
key_bytes=key_bytes_count)
rw = _FeatureRWMap(feature, read_function_id, write_function_id, key_bytes=key_bytes_count)
return _Settings(name,
rw,
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
#
_HAND_DETECTION = (
'hand-detection', _('Hand Detection'),
_('Turn on illumination when the hands hover over the keyboard.'))
_SMOOTH_SCROLL = (
'smooth-scroll', _('Smooth Scrolling'),
_('High-sensitivity mode for vertical scroll with the wheel.'))
_SIDE_SCROLL = (
'side-scroll', _('Side Scrolling'),
_('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' +
_HAND_DETECTION = ('hand-detection', _('Hand Detection'), _('Turn on illumination when the hands hover over the keyboard.'))
_SMOOTH_SCROLL = ('smooth-scroll', _('Smooth Scrolling'), _('High-sensitivity mode for vertical scroll with the wheel.'))
_SIDE_SCROLL = ('side-scroll', _('Side Scrolling'),
_('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.'))
_HIRES_INV = ('hires-smooth-invert', _('High Resolution Wheel Invert'),
_('High-sensitivity wheel invert mode for vertical scroll.'))
_HIRES_RES = ('hires-smooth-resolution', _('Wheel Resolution'),
_('High-sensitivity mode for vertical scroll with the wheel.'))
_FN_SWAP = (
'fn-swap', _('Swap Fx function'),
_('When set, the F1..F12 keys will activate their special function,\n'
'and you must hold the FN key to activate their standard 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.'))
_HIRES_RES = ('hires-smooth-resolution', _('Wheel Resolution'), _('High-sensitivity mode for vertical scroll with the wheel.'))
_FN_SWAP = ('fn-swap', _('Swap Fx function'),
_('When set, the F1..F12 keys will activate their special function,\n'
'and you must hold the FN key to activate their standard 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)
_POINTER_SPEED = ('pointer_speed', _('Sensitivity (Pointer Speed)'),
_('Speed multiplier for mouse (256 is normal multiplier).'))
_SMART_SHIFT = (
'smart-shift', _('Smart Shift'),
_('Automatically switch the mouse wheel between ratchet and freespin mode.\n'
'The mouse wheel is always free at 0, and always locked at 50'))
_BACKLIGHT = ('backlight', _('Backlight'),
_('Turn illumination on or off on keyboard.'))
_REPROGRAMMABLE_KEYS = ('reprogrammable-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.'))
_SMART_SHIFT = ('smart-shift', _('Smart Shift'),
_('Automatically switch the mouse wheel between ratchet and freespin mode.\n'
'The mouse wheel is always free at 0, and always locked at 50'))
_BACKLIGHT = ('backlight', _('Backlight'), _('Turn illumination on or off on keyboard.'))
_REPROGRAMMABLE_KEYS = ('reprogrammable-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, ))
def _register_fn_swap(register=_R.keyboard_fn_swap,
true_value=b'\x00\x01',
mask=b'\x00\x01'):
def _register_fn_swap(register=_R.keyboard_fn_swap, true_value=b'\x00\x01', mask=b'\x00\x01'):
return register_toggle(_FN_SWAP[0],
register,
true_value=true_value,
@ -386,9 +337,7 @@ def _register_fn_swap(register=_R.keyboard_fn_swap,
device_kind=(_DK.keyboard, ))
def _register_smooth_scroll(register=_R.mouse_button_flags,
true_value=0x40,
mask=0x40):
def _register_smooth_scroll(register=_R.mouse_button_flags, true_value=0x40, mask=0x40):
return register_toggle(_SMOOTH_SCROLL[0],
register,
true_value=true_value,
@ -398,9 +347,7 @@ def _register_smooth_scroll(register=_R.mouse_button_flags,
device_kind=(_DK.mouse, _DK.trackball))
def _register_side_scroll(register=_R.mouse_button_flags,
true_value=0x02,
mask=0x02):
def _register_side_scroll(register=_R.mouse_button_flags, true_value=0x02, mask=0x02):
return register_toggle(_SIDE_SCROLL[0],
register,
true_value=true_value,
@ -525,8 +472,7 @@ def _feature_smart_shift():
if threshold == _MAX_SMART_SHIFT_VALUE:
threshold = 255
data = _int2bytes(mode,
count=1) + _int2bytes(threshold, count=1) * 2
data = _int2bytes(mode, count=1) + _int2bytes(threshold, count=1) * 2
return super(_SmartShiftRW, self).write(device, data)
return feature_range(_SMART_SHIFT[0],
@ -607,20 +553,15 @@ def _feature_reprogrammable_keys_choices(device):
choices = {}
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)
key, key_task, flags, pos, group, gmask = _unpack(
'!HHBBBB', keydata[:8])
key, key_task, flags, pos, group, gmask = _unpack('!HHBBBB', keydata[:8])
action = _NamedInt(key, str(_special_keys.TASK[key_task]))
keys[i] = (_special_keys.CONTROL[key], action, flags, gmask)
groups[group].append(action)
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[3]: # only keys with a non-zero gmask are remappable
key_choices = [
k[1]
] # 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
key_choices = [k[1]] # 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)):
for gm in groups[g]:
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):
mask = device.feature_request(_F.KEYBOARD_DISABLE_KEYS)[0]
options = [
_special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)
]
options = [_special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)]
return options
def _feature_disable_keyboard_keys():
return feature_bitfield_toggle_dynamic(
_DISABLE_KEYS[0],
_F.KEYBOARD_DISABLE_KEYS,
_feature_disable_keyboard_keys_key_list,
read_function_id=0x10,
write_function_id=0x20,
label=_DISABLE_KEYS[1],
description=_DISABLE_KEYS[2],
device_kind=(_DK.keyboard, ))
return feature_bitfield_toggle_dynamic(_DISABLE_KEYS[0],
_F.KEYBOARD_DISABLE_KEYS,
_feature_disable_keyboard_keys_key_list,
read_function_id=0x10,
write_function_id=0x20,
label=_DISABLE_KEYS[1],
description=_DISABLE_KEYS[2],
device_kind=(_DK.keyboard, ))
#
@ -671,8 +609,7 @@ def _feature_disable_keyboard_keys():
def _S(name, featureID=None, featureFn=None, registerFn=None, identifier=None):
return (name, featureID, featureFn, registerFn,
identifier if identifier else name.replace('-', '_'))
return (name, featureID, featureFn, registerFn, identifier if identifier else name.replace('-', '_'))
_SETTINGS_TABLE = [
@ -683,29 +620,15 @@ _SETTINGS_TABLE = [
_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_RES[0], _F.HIRES_WHEEL, _feature_hires_smooth_resolution),
_S(_FN_SWAP[0],
_F.FN_INVERSION,
_feature_fn_swap,
registerFn=_register_fn_swap),
_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(_FN_SWAP[0], _F.FN_INVERSION, _feature_fn_swap, registerFn=_register_fn_swap),
_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(_SMART_SHIFT[0], _F.SMART_SHIFT, _feature_smart_shift),
_S(_BACKLIGHT[0], _F.BACKLIGHT2, _feature_backlight2),
_S(_REPROGRAMMABLE_KEYS[0], _F.REPROG_CONTROLS_V4,
_feature_reprogrammable_keys),
_S(_DISABLE_KEYS[0], _F.KEYBOARD_DISABLE_KEYS,
_feature_disable_keyboard_keys),
_S(_REPROGRAMMABLE_KEYS[0], _F.REPROG_CONTROLS_V4, _feature_reprogrammable_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])
@ -741,13 +664,11 @@ def check_feature_settings(device, already_known):
try:
detected = featureFn()(device)
if _log.isEnabledFor(_DEBUG):
_log.debug('check_feature[%s] detected %s', featureId,
detected)
_log.debug('check_feature[%s] detected %s', featureId, detected)
if detected:
already_known.append(detected)
except Exception as reason:
_log.error('check_feature[%s] inconsistent feature %s', featureId,
reason)
_log.error('check_feature[%s] inconsistent feature %s', featureId, reason)
for name, featureId, featureFn, __, __ in _SETTINGS_TABLE:
if featureId and featureFn:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,9 +19,7 @@ def _data_files():
yield 'share/solaar/icons', _glob('share/solaar/icons/solaar*.svg')
yield 'share/solaar/icons', _glob('share/solaar/icons/light_*.png')
yield 'share/icons/hicolor/scalable/apps', [
'share/solaar/icons/solaar.svg'
]
yield 'share/icons/hicolor/scalable/apps', ['share/solaar/icons/solaar.svg']
for mo in _glob('share/locale/*/LC_MESSAGES/solaar.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)',
],
package_dir={'': 'lib'},
packages=[
'hidapi', 'logitech_receiver', 'solaar', 'solaar.ui', 'solaar.cli'
],
packages=['hidapi', 'logitech_receiver', 'solaar', 'solaar.ui', 'solaar.cli'],
data_files=list(_data_files()),
scripts=_glob('bin/*'),
)

View File

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

View File

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