yapf: change code style to yapf
Signed-off-by: Filipe Laíns <lains@archlinux.org>
This commit is contained in:
parent
cab523e122
commit
72a8d311bc
|
@ -36,7 +36,9 @@ def init_paths():
|
|||
sys.path[0].encode(sys.getfilesystemencoding())
|
||||
|
||||
except UnicodeError:
|
||||
sys.stderr.write('ERROR: Solaar cannot recognize encoding of filesystem path, this may happen because non UTF-8 characters in the pathname.\n')
|
||||
sys.stderr.write(
|
||||
'ERROR: Solaar cannot recognize encoding of filesystem path, this may happen because non UTF-8 characters in the pathname.\n'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
prefix = _path.normpath(_path.join(_path.realpath(decoded_path), '..'))
|
||||
|
|
|
@ -37,7 +37,9 @@ 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()
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""Generic Human Interface Device API."""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
|
|
@ -63,13 +63,15 @@ from threading import Lock
|
|||
print_lock = Lock()
|
||||
del Lock
|
||||
|
||||
|
||||
def _print(marker, data, scroll=False):
|
||||
t = time.time() - start_time
|
||||
if is_string(data):
|
||||
s = marker + ' ' + data
|
||||
else:
|
||||
hexs = strhex(data)
|
||||
s = '%s (% 8.3f) [%s %s %s %s] %s' % (marker, t, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:], repr(data))
|
||||
s = '%s (% 8.3f) [%s %s %s %s] %s' % (marker, t, hexs[0:2], hexs[2:4],
|
||||
hexs[4:8], hexs[8:], repr(data))
|
||||
|
||||
with print_lock:
|
||||
# allow only one thread at a time to write to the console, otherwise
|
||||
|
@ -126,17 +128,23 @@ 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]
|
||||
|
@ -161,34 +169,41 @@ def _open(args):
|
|||
if not handle:
|
||||
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),
|
||||
print(".. Opened handle %r, vendor %r product %r serial %r." %
|
||||
(handle, _hid.get_manufacturer(handle), _hid.get_product(handle),
|
||||
_hid.get_serial(handle)))
|
||||
if args.hidpp:
|
||||
if _hid.get_manufacturer(handle) != 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.")
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
@ -197,12 +212,15 @@ 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:
|
||||
|
@ -217,7 +235,8 @@ 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)
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""Generic Human Interface Device API.
|
||||
|
||||
It is currently a partial pure-Python implementation of the native HID API
|
||||
|
@ -35,10 +34,8 @@ from select import select as _select
|
|||
from pyudev import Context as _Context, Monitor as _Monitor, Device as _Device
|
||||
from pyudev import DeviceNotFoundError
|
||||
|
||||
|
||||
native_implementation = 'udev'
|
||||
|
||||
|
||||
# the tuple object we'll expose when enumerating devices
|
||||
from collections import namedtuple
|
||||
DeviceInfo = namedtuple('DeviceInfo', [
|
||||
|
@ -54,12 +51,12 @@ DeviceInfo = namedtuple('DeviceInfo', [
|
|||
])
|
||||
del namedtuple
|
||||
|
||||
|
||||
#
|
||||
# exposed API
|
||||
# docstrings mostly copied from hidapi.h
|
||||
#
|
||||
|
||||
|
||||
def init():
|
||||
"""This function is a no-op, and exists only to match the native hidapi
|
||||
implementation.
|
||||
|
@ -114,9 +111,11 @@ 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
|
||||
|
||||
|
@ -183,15 +182,18 @@ 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:
|
||||
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()
|
||||
|
@ -282,7 +284,9 @@ 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):
|
||||
|
@ -303,11 +307,13 @@ 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]
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""Low-level interface for devices connected through a Logitech Universal
|
||||
Receiver (UR).
|
||||
|
||||
|
@ -43,10 +42,8 @@ _log.setLevel(logging.root.level)
|
|||
|
||||
del logging
|
||||
|
||||
|
||||
__version__ = '0.9'
|
||||
|
||||
|
||||
from .common import strhex
|
||||
from .base import NoReceiver, NoSuchDevice, DeviceUnreachable
|
||||
from .receiver import Receiver, PairedDevice
|
||||
|
|
|
@ -29,7 +29,6 @@ from logging import getLogger, DEBUG as _DEBUG
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from .common import strhex as _strhex, KwException as _KwException, pack as _pack
|
||||
from . import hidpp10 as _hidpp10
|
||||
from . import hidpp20 as _hidpp20
|
||||
|
@ -51,8 +50,6 @@ report_lengths = {
|
|||
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
|
||||
|
@ -66,6 +63,7 @@ _PING_TIMEOUT = DEFAULT_TIMEOUT * 2
|
|||
# Exceptions that may be raised by this API.
|
||||
#
|
||||
|
||||
|
||||
class NoReceiver(_KwException):
|
||||
"""Raised when trying to talk through a previously open handle, when the
|
||||
receiver is no longer available. Should only happen if the receiver is
|
||||
|
@ -83,12 +81,14 @@ class DeviceUnreachable(_KwException):
|
|||
"""Raised when a request is made to an unreachable (turned off) device."""
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from .base_usb import ALL as _RECEIVER_USB_IDS
|
||||
|
||||
|
||||
def receivers():
|
||||
"""List all the Linux devices exposed by the UR attached to the machine."""
|
||||
for receiver_usb_id in _RECEIVER_USB_IDS:
|
||||
|
@ -100,10 +100,12 @@ def notify_on_receivers_glib(callback):
|
|||
"""Watch for matching devices and notifies the callback on the GLib thread."""
|
||||
_hid.monitor_glib(callback, *_RECEIVER_USB_IDS)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def open_path(path):
|
||||
"""Checks if the given Linux device path points to the right UR device.
|
||||
|
||||
|
@ -170,12 +172,14 @@ 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)
|
||||
|
||||
|
@ -206,7 +210,8 @@ 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
|
||||
|
||||
|
||||
|
@ -224,7 +229,8 @@ 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)
|
||||
|
||||
|
@ -233,14 +239,17 @@ 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:]
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def _skip_incoming(handle, ihandle, notifications_hook):
|
||||
"""Read anything already in the input buffer.
|
||||
|
||||
|
@ -252,7 +261,8 @@ 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)
|
||||
|
||||
|
@ -285,22 +295,21 @@ def make_notification(devnumber, data):
|
|||
address = ord(data[1:2])
|
||||
if (
|
||||
# standard HID++ 1.0 notification, SubId may be 0x40 - 0x7F
|
||||
(sub_id >= 0x40)
|
||||
or
|
||||
(sub_id >= 0x40) or
|
||||
# custom HID++1.0 battery events, where SubId is 0x07/0x0D
|
||||
(sub_id in (0x07, 0x0D) and len(data) == 5 and data[4:5] == b'\x00')
|
||||
or
|
||||
(sub_id in (0x07, 0x0D) and len(data) == 5 and data[4:5] == b'\x00') or
|
||||
# custom HID++1.0 illumination event, where SubId is 0x17
|
||||
(sub_id == 0x17 and len(data) == 5)
|
||||
or
|
||||
(sub_id == 0x17 and len(data) == 5) or
|
||||
# HID++ 2.0 feature notifications have the SoftwareID 0
|
||||
(address & 0x0F == 0x00)
|
||||
):
|
||||
(address & 0x0F == 0x00)):
|
||||
return _HIDPP_Notification(devnumber, sub_id, address, data[2:])
|
||||
|
||||
|
||||
from collections import namedtuple
|
||||
_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
|
||||
|
@ -309,6 +318,7 @@ del namedtuple
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def request(handle, devnumber, request_id, *params):
|
||||
"""Makes a feature call to a device and waits for a matching reply.
|
||||
|
||||
|
@ -339,7 +349,8 @@ 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):
|
||||
|
@ -361,7 +372,8 @@ 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
|
||||
|
@ -373,16 +385,24 @@ 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:
|
||||
|
@ -463,11 +483,13 @@ 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])
|
||||
|
||||
|
@ -478,8 +500,11 @@ 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)
|
||||
|
@ -490,5 +515,6 @@ 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)
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
_DRIVER = ('hid-generic', 'generic-usb', 'logitech-djreceiver')
|
||||
|
||||
# max_devices is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to 1
|
||||
|
@ -116,7 +115,6 @@ LIGHTSPEED_RECEIVER_C53d = _lightspeed_receiver(0xc53d)
|
|||
|
||||
del _DRIVER, _unifying_receiver, _nano_receiver, _lenovo_receiver, _lightspeed_receiver
|
||||
|
||||
|
||||
ALL = (
|
||||
UNIFYING_RECEIVER_C52B,
|
||||
UNIFYING_RECEIVER_C532,
|
||||
|
@ -139,6 +137,7 @@ ALL = (
|
|||
LIGHTSPEED_RECEIVER_C53d,
|
||||
)
|
||||
|
||||
|
||||
def product_information(usb_id):
|
||||
if isinstance(usb_id, str):
|
||||
usb_id = int(usb_id, 16)
|
||||
|
|
|
@ -46,12 +46,12 @@ except:
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
class NamedInt(int):
|
||||
"""An reqular Python integer with an attached name.
|
||||
|
||||
Caution: comparison with strings will also match this NamedInt's name
|
||||
(case-insensitive)."""
|
||||
|
||||
def __new__(cls, value, name):
|
||||
assert is_string(name)
|
||||
obj = int.__new__(cls, value)
|
||||
|
@ -80,6 +80,7 @@ class NamedInt(int):
|
|||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
__unicode__ = __str__
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -104,11 +105,15 @@ 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}
|
||||
|
@ -121,8 +126,15 @@ 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):
|
||||
|
@ -154,10 +166,13 @@ 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]:
|
||||
|
@ -264,29 +279,15 @@ class KwException(Exception):
|
|||
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
"""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'])
|
||||
'index', 'key', 'task', 'flags', 'pos', 'group', 'group_mask', 'remapped'
|
||||
])
|
||||
|
||||
del namedtuple
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
from .common import NamedInts as _NamedInts
|
||||
from .hidpp10 import REGISTERS as _R, DEVICE_KIND as _DK
|
||||
from .settings_templates import RegisterSettings as _RS, FeatureSettings as _FS
|
||||
|
@ -30,21 +29,28 @@ from .settings_templates import RegisterSettings as _RS, FeatureSettings as _FS
|
|||
|
||||
from collections import namedtuple
|
||||
_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
|
||||
|
@ -64,18 +70,29 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None,
|
|||
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,
|
||||
wpid=wpid, codename=codename, protocol=protocol,
|
||||
registers=registers, settings=settings, persister=persister)
|
||||
device_descriptor = _DeviceDescriptor(name=name,
|
||||
kind=kind,
|
||||
wpid=wpid,
|
||||
codename=codename,
|
||||
protocol=protocol,
|
||||
registers=registers,
|
||||
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:
|
||||
|
@ -83,14 +100,17 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None,
|
|||
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
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
_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))
|
||||
|
||||
#
|
||||
#
|
||||
|
@ -145,96 +165,142 @@ _PERFORMANCE_MX_DPIS = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 1
|
|||
|
||||
_D('Wireless Keyboard K230', protocol=2.0, wpid='400D')
|
||||
_D('Wireless Keyboard K270(unifying)', protocol=2.0, wpid='4003')
|
||||
_D('Wireless Keyboard MK270', protocol=2.0, wpid='4023',
|
||||
settings=[
|
||||
_FS.fn_swap()
|
||||
],
|
||||
_D(
|
||||
'Wireless Keyboard MK270',
|
||||
protocol=2.0,
|
||||
wpid='4023',
|
||||
settings=[_FS.fn_swap()],
|
||||
)
|
||||
_D('Wireless Keyboard K270', protocol=1.0,
|
||||
_D(
|
||||
'Wireless Keyboard K270',
|
||||
protocol=1.0,
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('Wireless Keyboard MK300', protocol=1.0, wpid='8521',
|
||||
_D(
|
||||
'Wireless Keyboard MK300',
|
||||
protocol=1.0,
|
||||
wpid='8521',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
|
||||
_D('Wireless Keyboard MK320', protocol=1.0, wpid='200F',
|
||||
_D(
|
||||
'Wireless Keyboard MK320',
|
||||
protocol=1.0,
|
||||
wpid='200F',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('Wireless Keyboard MK330')
|
||||
_D('Wireless Compact Keyboard K340', protocol=1.0, wpid='2007',
|
||||
_D(
|
||||
'Wireless Compact Keyboard K340',
|
||||
protocol=1.0,
|
||||
wpid='2007',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('Wireless Wave Keyboard K350', protocol=1.0, wpid='200A',
|
||||
_D(
|
||||
'Wireless Wave Keyboard K350',
|
||||
protocol=1.0,
|
||||
wpid='200A',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('Wireless Keyboard K360', protocol=2.0, wpid='4004',
|
||||
settings=[
|
||||
_FS.fn_swap()
|
||||
],
|
||||
_D(
|
||||
'Wireless Keyboard K360',
|
||||
protocol=2.0,
|
||||
wpid='4004',
|
||||
settings=[_FS.fn_swap()],
|
||||
)
|
||||
_D('Wireless Keyboard K375s', protocol=2.0, wpid='4061',
|
||||
settings=[
|
||||
_FS.k375s_fn_swap()
|
||||
],
|
||||
_D(
|
||||
'Wireless Keyboard K375s',
|
||||
protocol=2.0,
|
||||
wpid='4061',
|
||||
settings=[_FS.k375s_fn_swap()],
|
||||
)
|
||||
_D('Wireless Touch Keyboard K400', protocol=2.0, wpid=('400E', '4024'),
|
||||
settings=[
|
||||
_FS.fn_swap()
|
||||
],
|
||||
_D(
|
||||
'Wireless Touch Keyboard K400',
|
||||
protocol=2.0,
|
||||
wpid=('400E', '4024'),
|
||||
settings=[_FS.fn_swap()],
|
||||
)
|
||||
_D('Wireless Touch Keyboard K400 Plus', codename='K400 Plus', protocol=2.0, wpid='404D',
|
||||
_D(
|
||||
'Wireless Touch Keyboard K400 Plus',
|
||||
codename='K400 Plus',
|
||||
protocol=2.0,
|
||||
wpid='404D',
|
||||
settings=[
|
||||
_FS.new_fn_swap(),
|
||||
_FS.reprogrammable_keys(),
|
||||
_FS.disable_keyboard_keys(),
|
||||
],
|
||||
)
|
||||
_D('Wireless Keyboard K520', protocol=1.0, wpid='2011',
|
||||
_D(
|
||||
'Wireless Keyboard K520',
|
||||
protocol=1.0,
|
||||
wpid='2011',
|
||||
registers=(_R.battery_status, ),
|
||||
settings=[
|
||||
_RS.fn_swap(),
|
||||
],
|
||||
)
|
||||
_D('Number Pad N545', protocol=1.0, wpid='2006',
|
||||
_D(
|
||||
'Number Pad N545',
|
||||
protocol=1.0,
|
||||
wpid='2006',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('Wireless Keyboard MK550')
|
||||
_D('Wireless Keyboard MK700', protocol=1.0, wpid='2008',
|
||||
_D(
|
||||
'Wireless Keyboard MK700',
|
||||
protocol=1.0,
|
||||
wpid='2008',
|
||||
registers=(_R.battery_status, ),
|
||||
settings=[
|
||||
_RS.fn_swap(),
|
||||
],
|
||||
)
|
||||
_D('Wireless Solar Keyboard K750', protocol=2.0, wpid='4002',
|
||||
settings=[
|
||||
_FS.fn_swap()
|
||||
],
|
||||
_D(
|
||||
'Wireless Solar Keyboard K750',
|
||||
protocol=2.0,
|
||||
wpid='4002',
|
||||
settings=[_FS.fn_swap()],
|
||||
)
|
||||
_D('Wireless Multi-Device Keyboard K780', protocol=4.5, wpid='405B',
|
||||
settings=[
|
||||
_FS.new_fn_swap()
|
||||
],
|
||||
_D(
|
||||
'Wireless Multi-Device Keyboard K780',
|
||||
protocol=4.5,
|
||||
wpid='405B',
|
||||
settings=[_FS.new_fn_swap()],
|
||||
)
|
||||
_D('Wireless Illuminated Keyboard K800', protocol=1.0, wpid='2010',
|
||||
registers=(_R.battery_status, _R.three_leds, ),
|
||||
_D(
|
||||
'Wireless Illuminated Keyboard K800',
|
||||
protocol=1.0,
|
||||
wpid='2010',
|
||||
registers=(
|
||||
_R.battery_status,
|
||||
_R.three_leds,
|
||||
),
|
||||
settings=[
|
||||
_RS.fn_swap(),
|
||||
_RS.hand_detection(),
|
||||
],
|
||||
)
|
||||
_D('Wireless Illuminated Keyboard K800 new', codename='K800 new', protocol=4.5, wpid='406E',
|
||||
settings=[
|
||||
_FS.fn_swap()
|
||||
],
|
||||
_D(
|
||||
'Wireless Illuminated Keyboard K800 new',
|
||||
codename='K800 new',
|
||||
protocol=4.5,
|
||||
wpid='406E',
|
||||
settings=[_FS.fn_swap()],
|
||||
)
|
||||
_D('Illuminated Living-Room Keyboard K830', protocol=2.0, wpid='4032',
|
||||
settings=[
|
||||
_FS.new_fn_swap()
|
||||
],
|
||||
_D(
|
||||
'Illuminated Living-Room Keyboard K830',
|
||||
protocol=2.0,
|
||||
wpid='4032',
|
||||
settings=[_FS.new_fn_swap()],
|
||||
)
|
||||
_D('Craft Advanced Keyboard', codename='Craft', protocol=4.5, wpid='4066')
|
||||
|
||||
_D('Wireless Keyboard S510', codename='S510', protocol=1.0, wpid='3622',
|
||||
_D(
|
||||
'Wireless Keyboard S510',
|
||||
codename='S510',
|
||||
protocol=1.0,
|
||||
wpid='3622',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
|
||||
|
@ -242,14 +308,20 @@ _D('Wireless Keyboard S510', codename='S510', protocol=1.0, wpid='3622',
|
|||
|
||||
_D('Wireless Mouse M150', protocol=2.0, wpid='4022')
|
||||
_D('Wireless Mouse M175', protocol=2.0, wpid='4008')
|
||||
_D('Wireless Mouse M185 new', codename='M185n', protocol=4.5, wpid='4054',
|
||||
_D('Wireless Mouse M185 new',
|
||||
codename='M185n',
|
||||
protocol=4.5,
|
||||
wpid='4054',
|
||||
settings=[
|
||||
_FS.lowres_smooth_scroll(),
|
||||
_FS.pointer_speed(),
|
||||
])
|
||||
# Apparently Logitech uses wpid 4055 for three different mice
|
||||
# That's not so strange, as M185 is used on both Unifying-ready and non-Unifying-ready mice
|
||||
_D('Wireless Mouse M185/M235/M310', codename='M185/M235/M310', protocol=4.5, wpid='4055',
|
||||
_D('Wireless Mouse M185/M235/M310',
|
||||
codename='M185/M235/M310',
|
||||
protocol=4.5,
|
||||
wpid='4055',
|
||||
settings=[
|
||||
_FS.lowres_smooth_scroll(),
|
||||
_FS.pointer_speed(),
|
||||
|
@ -257,61 +329,93 @@ _D('Wireless Mouse M185/M235/M310', codename='M185/M235/M310', protocol=4.5, wpi
|
|||
_D('Wireless Mouse M185', protocol=2.0, wpid='4038')
|
||||
_D('Wireless Mouse M187', protocol=2.0, wpid='4019')
|
||||
_D('Wireless Mouse M215', protocol=1.0, wpid='1020')
|
||||
_D('Wireless Mouse M305', protocol=1.0, wpid='101F',
|
||||
_D(
|
||||
'Wireless Mouse M305',
|
||||
protocol=1.0,
|
||||
wpid='101F',
|
||||
registers=(_R.battery_status, ),
|
||||
settings=[
|
||||
_RS.side_scroll(),
|
||||
],
|
||||
)
|
||||
_D('Wireless Mouse M310', protocol=1.0, wpid='1024',
|
||||
_D(
|
||||
'Wireless Mouse M310',
|
||||
protocol=1.0,
|
||||
wpid='1024',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('Wireless Mouse M315')
|
||||
_D('Wireless Mouse M317')
|
||||
_D('Wireless Mouse M325', protocol=2.0, wpid='400A',
|
||||
_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', protocol=1.0, wpid='101C',
|
||||
_D(
|
||||
'Wireless Mouse M350',
|
||||
protocol=1.0,
|
||||
wpid='101C',
|
||||
registers=(_R.battery_charge, ),
|
||||
)
|
||||
_D('Wireless Mouse Pebble M350', codename='Pebble', protocol=2.0, wpid='4080')
|
||||
_D('Wireless Mouse M505', codename='M505/B605', protocol=1.0, wpid='101D',
|
||||
_D(
|
||||
'Wireless Mouse M505',
|
||||
codename='M505/B605',
|
||||
protocol=1.0,
|
||||
wpid='101D',
|
||||
registers=(_R.battery_charge, ),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
_RS.side_scroll(),
|
||||
],
|
||||
)
|
||||
_D('Wireless Mouse M510', protocol=1.0, wpid='1025',
|
||||
_D(
|
||||
'Wireless Mouse M510',
|
||||
protocol=1.0,
|
||||
wpid='1025',
|
||||
registers=(_R.battery_status, ),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
_RS.side_scroll(),
|
||||
],
|
||||
)
|
||||
_D('Wireless Mouse M510', codename='M510v2', protocol=2.0, wpid='4051',
|
||||
_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('Multi Device Silent Mouse M585/M590', codename='M585/M590', protocol=4.5, wpid='406B',
|
||||
_D(
|
||||
'Multi Device Silent Mouse M585/M590',
|
||||
codename='M585/M590',
|
||||
protocol=4.5,
|
||||
wpid='406B',
|
||||
settings=[
|
||||
_FS.lowres_smooth_scroll(),
|
||||
_FS.pointer_speed(),
|
||||
],
|
||||
)
|
||||
_D('Touch Mouse M600', protocol=2.0, wpid='401A')
|
||||
_D('Marathon Mouse M705 (M-R0009)', codename='M705 (M-R0009)', protocol=1.0, wpid='101B',
|
||||
_D(
|
||||
'Marathon Mouse M705 (M-R0009)',
|
||||
codename='M705 (M-R0009)',
|
||||
protocol=1.0,
|
||||
wpid='101B',
|
||||
registers=(_R.battery_charge, ),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
_RS.side_scroll(),
|
||||
],
|
||||
)
|
||||
_D('Marathon Mouse M705 (M-R0073)', codename='M705 (M-R0073)', protocol=4.5, wpid='406D',
|
||||
_D('Marathon Mouse M705 (M-R0073)',
|
||||
codename='M705 (M-R0073)',
|
||||
protocol=4.5,
|
||||
wpid='406D',
|
||||
settings=[
|
||||
_FS.hires_smooth_invert(),
|
||||
_FS.hires_smooth_resolution(),
|
||||
|
@ -320,21 +424,36 @@ _D('Marathon Mouse M705 (M-R0073)', codename='M705 (M-R0073)', protocol=4.5, wpi
|
|||
_D('Zone Touch Mouse T400')
|
||||
_D('Touch Mouse T620', protocol=2.0)
|
||||
_D('Logitech Cube', kind=_DK.mouse, protocol=2.0)
|
||||
_D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017',
|
||||
_D(
|
||||
'Anywhere Mouse MX',
|
||||
codename='Anywhere MX',
|
||||
protocol=1.0,
|
||||
wpid='1017',
|
||||
registers=(_R.battery_charge, ),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
_RS.side_scroll(),
|
||||
],
|
||||
)
|
||||
_D('Anywhere Mouse MX 2', codename='Anywhere MX 2', protocol=4.5, wpid='404A',
|
||||
_D(
|
||||
'Anywhere Mouse MX 2',
|
||||
codename='Anywhere MX 2',
|
||||
protocol=4.5,
|
||||
wpid='404A',
|
||||
settings=[
|
||||
_FS.hires_smooth_invert(),
|
||||
_FS.hires_smooth_resolution(),
|
||||
],
|
||||
)
|
||||
_D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A',
|
||||
registers=(_R.battery_status, _R.three_leds, ),
|
||||
_D(
|
||||
'Performance Mouse MX',
|
||||
codename='Performance MX',
|
||||
protocol=1.0,
|
||||
wpid='101A',
|
||||
registers=(
|
||||
_R.battery_status,
|
||||
_R.three_leds,
|
||||
),
|
||||
settings=[
|
||||
_RS.dpi(choices=_PERFORMANCE_MX_DPIS),
|
||||
_RS.smooth_scroll(),
|
||||
|
@ -342,43 +461,80 @@ _D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A',
|
|||
],
|
||||
)
|
||||
|
||||
_D('Wireless Mouse MX Master', codename='MX Master', protocol=4.5, wpid='4041',
|
||||
_D(
|
||||
'Wireless Mouse MX Master',
|
||||
codename='MX Master',
|
||||
protocol=4.5,
|
||||
wpid='4041',
|
||||
settings=[
|
||||
_FS.hires_smooth_invert(),
|
||||
_FS.hires_smooth_resolution(),
|
||||
],
|
||||
)
|
||||
|
||||
_D('Wireless Mouse MX Master 2S', codename='MX Master 2S', protocol=4.5,wpid='4069',
|
||||
_D(
|
||||
'Wireless Mouse MX Master 2S',
|
||||
codename='MX Master 2S',
|
||||
protocol=4.5,
|
||||
wpid='4069',
|
||||
settings=[
|
||||
_FS.hires_smooth_invert(),
|
||||
_FS.hires_smooth_resolution(),
|
||||
],
|
||||
)
|
||||
|
||||
_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', codename='G7', protocol=1.0, wpid='1002',
|
||||
_D(
|
||||
'G7 Cordless Laser Mouse',
|
||||
codename='G7',
|
||||
protocol=1.0,
|
||||
wpid='1002',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('G700 Gaming Mouse', codename='G700', protocol=1.0, wpid='1023',
|
||||
registers=(_R.battery_status, _R.three_leds, ),
|
||||
_D(
|
||||
'G700 Gaming Mouse',
|
||||
codename='G700',
|
||||
protocol=1.0,
|
||||
wpid='1023',
|
||||
registers=(
|
||||
_R.battery_status,
|
||||
_R.three_leds,
|
||||
),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
_RS.side_scroll(),
|
||||
],
|
||||
)
|
||||
_D('G700s Gaming Mouse', codename='G700s', protocol=1.0, wpid='102A',
|
||||
registers=(_R.battery_status, _R.three_leds, ),
|
||||
_D(
|
||||
'G700s Gaming Mouse',
|
||||
codename='G700s',
|
||||
protocol=1.0,
|
||||
wpid='102A',
|
||||
registers=(
|
||||
_R.battery_status,
|
||||
_R.three_leds,
|
||||
),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
_RS.side_scroll(),
|
||||
],
|
||||
)
|
||||
_D('LX5 Cordless Mouse', codename='LX5', protocol=1.0, wpid='5612',
|
||||
_D(
|
||||
'LX5 Cordless Mouse',
|
||||
codename='LX5',
|
||||
protocol=1.0,
|
||||
wpid='5612',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('Wireless Mouse M30', codename='M30', protocol=1.0, wpid='6822',
|
||||
_D(
|
||||
'Wireless Mouse M30',
|
||||
codename='M30',
|
||||
protocol=1.0,
|
||||
wpid='6822',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
|
||||
|
@ -396,17 +552,29 @@ _D('Wireless Touchpad', codename='Wireless Touch', protocol=2.0, wpid='4011')
|
|||
# A wpid is necessary to properly identify them.
|
||||
#
|
||||
|
||||
_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid=('100B', '100F'),
|
||||
_D(
|
||||
'VX Nano Cordless Laser Mouse',
|
||||
codename='VX Nano',
|
||||
protocol=1.0,
|
||||
wpid=('100B', '100F'),
|
||||
registers=(_R.battery_charge, ),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
_RS.side_scroll(),
|
||||
],
|
||||
)
|
||||
_D('V450 Nano Cordless Laser Mouse', codename='V450 Nano', protocol=1.0, wpid='1011',
|
||||
_D(
|
||||
'V450 Nano Cordless Laser Mouse',
|
||||
codename='V450 Nano',
|
||||
protocol=1.0,
|
||||
wpid='1011',
|
||||
registers=(_R.battery_charge, ),
|
||||
)
|
||||
_D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1013',
|
||||
_D(
|
||||
'V550 Nano Cordless Laser Mouse',
|
||||
codename='V550 Nano',
|
||||
protocol=1.0,
|
||||
wpid='1013',
|
||||
registers=(_R.battery_charge, ),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
|
@ -416,31 +584,71 @@ _D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1
|
|||
|
||||
# Mini receiver mice
|
||||
|
||||
_D('MX610 Laser Cordless Mouse', codename='MX610', protocol=1.0, wpid='1001',
|
||||
_D(
|
||||
'MX610 Laser Cordless Mouse',
|
||||
codename='MX610',
|
||||
protocol=1.0,
|
||||
wpid='1001',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('MX620 Laser Cordless Mouse', codename='MX620', protocol=1.0, wpid=('100A', '1016'),
|
||||
_D(
|
||||
'MX620 Laser Cordless Mouse',
|
||||
codename='MX620',
|
||||
protocol=1.0,
|
||||
wpid=('100A', '1016'),
|
||||
registers=(_R.battery_charge, ),
|
||||
)
|
||||
_D('MX610 Left-Handled Mouse', codename='MX610L', protocol=1.0, wpid='1004',
|
||||
_D(
|
||||
'MX610 Left-Handled Mouse',
|
||||
codename='MX610L',
|
||||
protocol=1.0,
|
||||
wpid='1004',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('V400 Laser Cordless Mouse', codename='V400', protocol=1.0, wpid='1003',
|
||||
_D(
|
||||
'V400 Laser Cordless Mouse',
|
||||
codename='V400',
|
||||
protocol=1.0,
|
||||
wpid='1003',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('V450 Laser Cordless Mouse', codename='V450', protocol=1.0, wpid='1005',
|
||||
_D(
|
||||
'V450 Laser Cordless Mouse',
|
||||
codename='V450',
|
||||
protocol=1.0,
|
||||
wpid='1005',
|
||||
registers=(_R.battery_status, ),
|
||||
)
|
||||
_D('VX Revolution', codename='VX Revolution', kind=_DK.mouse, protocol=1.0, wpid=('1006', '100D'),
|
||||
_D(
|
||||
'VX Revolution',
|
||||
codename='VX Revolution',
|
||||
kind=_DK.mouse,
|
||||
protocol=1.0,
|
||||
wpid=('1006', '100D'),
|
||||
registers=(_R.battery_charge, ),
|
||||
)
|
||||
_D('MX Air', codename='MX Air', protocol=1.0, kind=_DK.mouse, wpid=('1007', '100E'),
|
||||
_D(
|
||||
'MX Air',
|
||||
codename='MX Air',
|
||||
protocol=1.0,
|
||||
kind=_DK.mouse,
|
||||
wpid=('1007', '100E'),
|
||||
registers=(_R.battery_charge, ),
|
||||
)
|
||||
_D('MX Revolution', codename='MX Revolution', protocol=1.0, kind=_DK.mouse, wpid=('1008', '100C'),
|
||||
_D(
|
||||
'MX Revolution',
|
||||
codename='MX Revolution',
|
||||
protocol=1.0,
|
||||
kind=_DK.mouse,
|
||||
wpid=('1008', '100C'),
|
||||
registers=(_R.battery_charge, ),
|
||||
)
|
||||
_D('MX 1100 Cordless Laser Mouse', codename='MX 1100', protocol=1.0, kind=_DK.mouse, wpid='1014',
|
||||
_D(
|
||||
'MX 1100 Cordless Laser Mouse',
|
||||
codename='MX 1100',
|
||||
protocol=1.0,
|
||||
kind=_DK.mouse,
|
||||
wpid='1014',
|
||||
registers=(_R.battery_charge, ),
|
||||
settings=[
|
||||
_RS.smooth_scroll(),
|
||||
|
|
|
@ -23,12 +23,9 @@ from logging import getLogger # , DEBUG as _DEBUG
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from .common import (strhex as _strhex,
|
||||
bytes2int as _bytes2int,
|
||||
int2bytes as _int2bytes,
|
||||
NamedInts as _NamedInts,
|
||||
FirmwareInfo as _FirmwareInfo)
|
||||
from .common import (strhex as _strhex, bytes2int as _bytes2int, int2bytes as
|
||||
_int2bytes, NamedInts as _NamedInts, FirmwareInfo as
|
||||
_FirmwareInfo)
|
||||
from .hidpp20 import FIRMWARE_KIND, BATTERY_STATUS
|
||||
|
||||
#
|
||||
|
@ -36,16 +33,14 @@ from .hidpp20 import FIRMWARE_KIND, BATTERY_STATUS
|
|||
# documentation, some of them guessed.
|
||||
#
|
||||
|
||||
DEVICE_KIND = _NamedInts(
|
||||
keyboard=0x01,
|
||||
DEVICE_KIND = _NamedInts(keyboard=0x01,
|
||||
mouse=0x02,
|
||||
numpad=0x03,
|
||||
presenter=0x04,
|
||||
trackball=0x08,
|
||||
touchpad=0x09)
|
||||
|
||||
POWER_SWITCH_LOCATION = _NamedInts(
|
||||
base=0x01,
|
||||
POWER_SWITCH_LOCATION = _NamedInts(base=0x01,
|
||||
top_case=0x02,
|
||||
edge_of_top_right_corner=0x03,
|
||||
top_left_corner=0x05,
|
||||
|
@ -72,15 +67,16 @@ POWER_SWITCH_LOCATION = _NamedInts(
|
|||
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
|
||||
)
|
||||
|
||||
ERROR = _NamedInts(
|
||||
invalid_SubID__command=0x01,
|
||||
ERROR = _NamedInts(invalid_SubID__command=0x01,
|
||||
invalid_address=0x02,
|
||||
invalid_value=0x03,
|
||||
connection_request_failed=0x04,
|
||||
|
@ -93,19 +89,12 @@ ERROR = _NamedInts(
|
|||
unsupported_parameter_value=0x0B,
|
||||
wrong_pin_code=0x0C)
|
||||
|
||||
PAIRING_ERRORS = _NamedInts(
|
||||
device_timeout=0x01,
|
||||
PAIRING_ERRORS = _NamedInts(device_timeout=0x01,
|
||||
device_not_supported=0x02,
|
||||
too_many_devices=0x03,
|
||||
sequence_timeout=0x06)
|
||||
|
||||
BATTERY_APPOX = _NamedInts(
|
||||
empty = 0,
|
||||
critical = 5,
|
||||
low = 20,
|
||||
good = 50,
|
||||
full = 90)
|
||||
|
||||
BATTERY_APPOX = _NamedInts(empty=0, critical=5, low=20, good=50, full=90)
|
||||
"""Known registers.
|
||||
Devices usually have a (small) sub-set of these. Some registers are only
|
||||
applicable to certain device kinds (e.g. smooth_scroll only applies to mice."""
|
||||
|
@ -135,15 +124,18 @@ REGISTERS = _NamedInts(
|
|||
# functions
|
||||
#
|
||||
|
||||
|
||||
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)
|
||||
|
@ -154,7 +146,6 @@ def get_battery(device):
|
|||
assert device.kind is not None
|
||||
if not device.online:
|
||||
return
|
||||
|
||||
"""Reads a device's battery level, if provided by the HID++ 1.0 protocol."""
|
||||
if device.protocol and device.protocol >= 2.0:
|
||||
# let's just assume HID++ 2.0 devices do not provide the battery info in a register
|
||||
|
@ -185,15 +176,15 @@ 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:
|
||||
status_byte = ord(reply[:1])
|
||||
charge = (BATTERY_APPOX.full if status_byte == 7 # full
|
||||
charge = (
|
||||
BATTERY_APPOX.full if status_byte == 7 # full
|
||||
else BATTERY_APPOX.good if status_byte == 5 # good
|
||||
else BATTERY_APPOX.low if status_byte == 3 # low
|
||||
else BATTERY_APPOX.critical if status_byte == 1 # critical
|
||||
|
@ -208,7 +199,8 @@ 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:
|
||||
|
@ -326,5 +318,6 @@ 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
|
||||
|
|
|
@ -25,14 +25,10 @@ from logging import getLogger, DEBUG as _DEBUG
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from .common import (FirmwareInfo as _FirmwareInfo,
|
||||
ReprogrammableKeyInfo as _ReprogrammableKeyInfo,
|
||||
ReprogrammableKeyInfoV4 as _ReprogrammableKeyInfoV4,
|
||||
KwException as _KwException,
|
||||
NamedInts as _NamedInts,
|
||||
pack as _pack,
|
||||
unpack as _unpack)
|
||||
from .common import (FirmwareInfo as _FirmwareInfo, ReprogrammableKeyInfo as
|
||||
_ReprogrammableKeyInfo, ReprogrammableKeyInfoV4 as
|
||||
_ReprogrammableKeyInfoV4, KwException as _KwException,
|
||||
NamedInts as _NamedInts, pack as _pack, unpack as _unpack)
|
||||
from . import special_keys
|
||||
|
||||
#
|
||||
|
@ -156,13 +152,9 @@ FEATURE = _NamedInts(
|
|||
)
|
||||
FEATURE._fallback = lambda x: 'unknown:%04X' % x
|
||||
|
||||
FEATURE_FLAG = _NamedInts(
|
||||
internal=0x20,
|
||||
hidden=0x40,
|
||||
obsolete=0x80)
|
||||
FEATURE_FLAG = _NamedInts(internal=0x20, hidden=0x40, obsolete=0x80)
|
||||
|
||||
DEVICE_KIND = _NamedInts(
|
||||
keyboard=0x00,
|
||||
DEVICE_KIND = _NamedInts(keyboard=0x00,
|
||||
remote_control=0x01,
|
||||
numpad=0x02,
|
||||
mouse=0x03,
|
||||
|
@ -171,16 +163,15 @@ DEVICE_KIND = _NamedInts(
|
|||
presenter=0x06,
|
||||
receiver=0x07)
|
||||
|
||||
FIRMWARE_KIND = _NamedInts(
|
||||
Firmware=0x00,
|
||||
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,
|
||||
BATTERY_STATUS = _NamedInts(discharging=0x00,
|
||||
recharging=0x01,
|
||||
almost_full=0x02,
|
||||
full=0x03,
|
||||
|
@ -188,24 +179,16 @@ BATTERY_STATUS = _NamedInts(
|
|||
invalid_battery=0x05,
|
||||
thermal_error=0x06)
|
||||
|
||||
CHARGE_STATUS = _NamedInts(
|
||||
charging=0x00,
|
||||
CHARGE_STATUS = _NamedInts(charging=0x00,
|
||||
full=0x01,
|
||||
not_charging=0x02,
|
||||
error=0x07)
|
||||
|
||||
CHARGE_LEVEL = _NamedInts(
|
||||
average=50,
|
||||
full=90,
|
||||
critical=5)
|
||||
CHARGE_LEVEL = _NamedInts(average=50, full=90, critical=5)
|
||||
|
||||
CHARGE_TYPE = _NamedInts(
|
||||
standard=0x00,
|
||||
fast=0x01,
|
||||
slow=0x02)
|
||||
CHARGE_TYPE = _NamedInts(standard=0x00, fast=0x01, slow=0x02)
|
||||
|
||||
ERROR = _NamedInts(
|
||||
unknown=0x01,
|
||||
ERROR = _NamedInts(unknown=0x01,
|
||||
invalid_argument=0x02,
|
||||
out_of_range=0x03,
|
||||
hardware_error=0x04,
|
||||
|
@ -219,18 +202,22 @@ ERROR = _NamedInts(
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
class FeatureNotSupported(_KwException):
|
||||
"""Raised when trying to request a feature not supported by the device."""
|
||||
pass
|
||||
|
||||
|
||||
class FeatureCallError(_KwException):
|
||||
"""Raised if the device replied to a feature call with an error."""
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
class FeaturesArray(object):
|
||||
"""A sequence of features supported by a HID++ 2.0 device."""
|
||||
__slots__ = ('supported', 'device', 'features', 'non_features')
|
||||
|
@ -266,7 +253,8 @@ 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:
|
||||
|
@ -274,7 +262,9 @@ 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:
|
||||
|
@ -298,7 +288,8 @@ 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]
|
||||
|
@ -314,7 +305,7 @@ class FeaturesArray(object):
|
|||
if self._check():
|
||||
ivalue = int(featureId)
|
||||
if ivalue in self.non_features:
|
||||
return False;
|
||||
return False
|
||||
|
||||
may_have = False
|
||||
for f in self.features:
|
||||
|
@ -366,10 +357,12 @@ class FeaturesArray(object):
|
|||
def __len__(self):
|
||||
return len(self.features) if self._check() else 0
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
class KeysArray(object):
|
||||
"""A sequence of key mappings supported by a HID++ 2.0 device."""
|
||||
__slots__ = ('device', 'keys', 'keyversion')
|
||||
|
@ -387,22 +380,30 @@ 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
|
||||
|
@ -412,7 +413,9 @@ 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]
|
||||
|
||||
|
@ -438,15 +441,18 @@ class KeysArray(object):
|
|||
def __len__(self):
|
||||
return len(self.keys)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
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):
|
||||
|
@ -460,18 +466,23 @@ 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)
|
||||
|
||||
|
@ -508,11 +519,14 @@ 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')
|
||||
|
@ -525,8 +539,10 @@ 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
|
||||
|
||||
|
||||
|
@ -561,8 +577,10 @@ def decipher_voltage(voltage_report):
|
|||
charge_lvl = CHARGE_LEVEL.critical
|
||||
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug("device %d, battery voltage %d mV, charging = %s, charge status %d = %s, charge level %s, charge type %s",
|
||||
device.number, voltage, status, (flags & 0x03), charge_sts, charge_lvl, charge_type)
|
||||
_log.debug(
|
||||
"device %d, battery voltage %d mV, charging = %s, charge status %d = %s, charge level %s, charge type %s",
|
||||
device.number, voltage, status, (flags & 0x03), charge_sts,
|
||||
charge_lvl, charge_type)
|
||||
|
||||
return charge_lvl, status, voltage, charge_sts, charge_type
|
||||
|
||||
|
@ -592,15 +610,14 @@ 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', 'reserved')[roller]
|
||||
return {
|
||||
'roller': roller_type,
|
||||
'ratchet': ratchet,
|
||||
'lines': lines
|
||||
}
|
||||
roller_type = ('reserved', 'standard', 'reserved', '3G', 'micro',
|
||||
'normal touch pad', 'inverted touch pad',
|
||||
'reserved')[roller]
|
||||
return {'roller': roller_type, 'ratchet': ratchet, 'lines': lines}
|
||||
|
||||
|
||||
def get_hi_res_scrolling_info(device):
|
||||
|
@ -613,7 +630,8 @@ def get_hi_res_scrolling_info(device):
|
|||
def get_pointer_speed_info(device):
|
||||
pointer_speed_info = feature_request(device, FEATURE.POINTER_SPEED)
|
||||
if pointer_speed_info:
|
||||
pointer_speed_hi, pointer_speed_lo = _unpack('!BB', pointer_speed_info[:2])
|
||||
pointer_speed_hi, pointer_speed_lo = _unpack('!BB',
|
||||
pointer_speed_info[:2])
|
||||
#if pointer_speed_lo > 0:
|
||||
# pointer_speed_lo = pointer_speed_lo
|
||||
return pointer_speed_hi + pointer_speed_lo / 256
|
||||
|
@ -632,7 +650,6 @@ def get_hires_wheel(device):
|
|||
mode = feature_request(device, FEATURE.HIRES_WHEEL, 0x10)
|
||||
ratchet = feature_request(device, FEATURE.HIRES_WHEEL, 0x030)
|
||||
|
||||
|
||||
if caps and mode and ratchet:
|
||||
# Parse caps
|
||||
multi, flags = _unpack('!BB', caps[:2])
|
||||
|
@ -654,6 +671,7 @@ def get_hires_wheel(device):
|
|||
|
||||
return multi, has_invert, has_ratchet, inv, res, target, ratchet
|
||||
|
||||
|
||||
def get_new_fn_inversion(device):
|
||||
state = feature_request(device, FEATURE.NEW_FN_INVERSION, 0x00)
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
|||
|
||||
import gettext as _gettext
|
||||
|
||||
|
||||
try:
|
||||
unicode
|
||||
_ = lambda x: _gettext.gettext(x).decode('UTF-8')
|
||||
|
@ -32,21 +31,34 @@ except:
|
|||
_ = _gettext.gettext
|
||||
ngettext = _gettext.ngettext
|
||||
|
||||
|
||||
# A few common strings, not always accessible as such in the code.
|
||||
|
||||
_DUMMY = (
|
||||
# approximative battery levels
|
||||
_("empty"), _("critical"), _("low"), _("good"), _("full"),
|
||||
_("empty"),
|
||||
_("critical"),
|
||||
_("low"),
|
||||
_("good"),
|
||||
_("full"),
|
||||
|
||||
# battery charging statuses
|
||||
_("discharging"), _("recharging"), _("almost full"), _("charged"),
|
||||
_("slow recharge"), _("invalid battery"), _("thermal error"),
|
||||
_("discharging"),
|
||||
_("recharging"),
|
||||
_("almost full"),
|
||||
_("charged"),
|
||||
_("slow recharge"),
|
||||
_("invalid battery"),
|
||||
_("thermal error"),
|
||||
|
||||
# pairing errors
|
||||
_("device timeout"), _("device not supported"), _("too many devices"), _("sequence timeout"),
|
||||
_("device timeout"),
|
||||
_("device not supported"),
|
||||
_("too many devices"),
|
||||
_("sequence timeout"),
|
||||
|
||||
# firmware kinds
|
||||
_("Firmware"), _("Bootloader"), _("Hardware"), _("Other"),
|
||||
|
||||
_("Firmware"),
|
||||
_("Bootloader"),
|
||||
_("Hardware"),
|
||||
_("Other"),
|
||||
)
|
||||
|
|
|
@ -32,13 +32,13 @@ from logging import getLogger, DEBUG as _DEBUG, INFO as _INFO
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from . import base as _base
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
class _ThreadedHandle(object):
|
||||
"""A thread-local wrapper with different open handles for each thread.
|
||||
|
||||
|
@ -97,11 +97,13 @@ class _ThreadedHandle(object):
|
|||
return self._local.handle
|
||||
except:
|
||||
return self._open()
|
||||
|
||||
__int__ = __index__
|
||||
|
||||
def __str__(self):
|
||||
if self._local:
|
||||
return str(int(self))
|
||||
|
||||
__unicode__ = __str__
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -109,8 +111,10 @@ class _ThreadedHandle(object):
|
|||
|
||||
def __bool__(self):
|
||||
return bool(self._local)
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -134,7 +138,8 @@ 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
|
||||
|
@ -149,7 +154,8 @@ 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):
|
||||
|
@ -227,4 +233,5 @@ class EventsListener(_threading.Thread):
|
|||
|
||||
def __bool__(self):
|
||||
return bool(self._active and self.receiver)
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
|
|
@ -26,7 +26,6 @@ from logging import getLogger, DEBUG as _DEBUG, INFO as _INFO
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from .i18n import _
|
||||
from .common import strhex as _strhex, unpack as _unpack
|
||||
from . import hidpp10 as _hidpp10
|
||||
|
@ -41,6 +40,7 @@ _F = _hidpp20.FEATURE
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def process(device, notification):
|
||||
assert device
|
||||
assert notification
|
||||
|
@ -54,10 +54,12 @@ def process(device, notification):
|
|||
|
||||
return _process_device_notification(device, status, notification)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def _process_receiver_notification(receiver, status, n):
|
||||
# supposedly only 0x4x notifications arrive for the receiver
|
||||
assert n.sub_id & 0x40 == 0x40
|
||||
|
@ -65,7 +67,8 @@ 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)
|
||||
|
||||
|
@ -75,7 +78,8 @@ 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)
|
||||
|
||||
|
@ -84,10 +88,12 @@ def _process_receiver_notification(receiver, status, n):
|
|||
|
||||
_log.warn("%s: unhandled notification %s", receiver, n)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def _process_device_notification(device, status, n):
|
||||
# incoming packets with SubId >= 0x80 are supposedly replies from
|
||||
# HID++ 1.0 requests, should never get here
|
||||
|
@ -113,7 +119,8 @@ 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)
|
||||
|
@ -145,7 +152,8 @@ 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>
|
||||
|
@ -175,29 +183,31 @@ 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)
|
||||
|
@ -205,12 +215,16 @@ 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
|
||||
|
||||
|
@ -227,7 +241,9 @@ 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
|
||||
|
@ -242,14 +258,17 @@ 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)
|
||||
|
@ -269,7 +288,9 @@ 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:
|
||||
|
@ -300,7 +321,8 @@ 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:
|
||||
|
@ -316,7 +338,9 @@ 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
|
||||
|
@ -327,7 +351,8 @@ 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):
|
||||
|
@ -339,4 +364,5 @@ 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)
|
||||
|
|
|
@ -25,7 +25,6 @@ from logging import getLogger, INFO as _INFO
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from .i18n import _
|
||||
from . import base as _base
|
||||
from . import hidpp10 as _hidpp10
|
||||
|
@ -41,6 +40,7 @@ _R = _hidpp10.REGISTERS
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
class PairedDevice(object):
|
||||
def __init__(self, receiver, number, link_notification=None):
|
||||
assert receiver
|
||||
|
@ -83,13 +83,15 @@ 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])
|
||||
|
@ -99,10 +101,14 @@ 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
|
||||
|
@ -110,13 +116,15 @@ 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]
|
||||
|
@ -132,7 +140,8 @@ 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)
|
||||
|
@ -151,7 +160,8 @@ 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]
|
||||
|
@ -172,7 +182,8 @@ 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]
|
||||
|
@ -193,7 +204,8 @@ 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]
|
||||
|
@ -211,7 +223,8 @@ 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]
|
||||
|
@ -222,7 +235,8 @@ 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:
|
||||
|
@ -254,7 +268,8 @@ 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):
|
||||
|
@ -272,16 +287,20 @@ 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
|
||||
|
@ -300,6 +319,7 @@ class PairedDevice(object):
|
|||
|
||||
def __index__(self):
|
||||
return self.number
|
||||
|
||||
__int__ = __index__
|
||||
|
||||
def __eq__(self, other):
|
||||
|
@ -314,13 +334,17 @@ 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__
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
class Receiver(object):
|
||||
"""A Unifying Receiver instance.
|
||||
|
||||
|
@ -355,7 +379,9 @@ 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._str = '<%s(%s,%s%s)>' % (self.name.replace(
|
||||
' ', ''), self.path, '' if isinstance(self.handle, int) else 'T',
|
||||
self.handle)
|
||||
|
||||
self._firmware = None
|
||||
self._devices = {}
|
||||
|
@ -398,24 +424,29 @@ 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
|
||||
|
@ -424,7 +455,8 @@ 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:
|
||||
|
@ -436,10 +468,12 @@ 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)
|
||||
|
@ -534,6 +568,7 @@ class Receiver(object):
|
|||
|
||||
def __str__(self):
|
||||
return self._str
|
||||
|
||||
__unicode__ = __repr__ = __str__
|
||||
|
||||
__bool__ = __nonzero__ = lambda self: self.handle is not None
|
||||
|
|
|
@ -37,15 +37,28 @@ from .common import (
|
|||
#
|
||||
#
|
||||
|
||||
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
|
||||
|
@ -96,7 +109,8 @@ 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,
|
||||
|
@ -126,7 +140,8 @@ 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.
|
||||
|
@ -144,7 +159,8 @@ 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:
|
||||
|
@ -158,7 +174,8 @@ 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:
|
||||
|
@ -167,21 +184,25 @@ 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__
|
||||
|
||||
|
||||
class Settings(Setting):
|
||||
"""A setting descriptor for multiple choices, being a map from keys to values.
|
||||
Needs to be instantiated for each specific device."""
|
||||
|
||||
def read(self, cached=True):
|
||||
assert hasattr(self, '_value')
|
||||
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,
|
||||
|
@ -189,7 +210,8 @@ 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
|
||||
|
@ -202,9 +224,11 @@ 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
|
||||
|
@ -216,21 +240,25 @@ 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))]
|
||||
|
||||
|
@ -240,7 +268,8 @@ 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.
|
||||
|
@ -254,7 +283,9 @@ 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
|
||||
|
@ -268,7 +299,8 @@ 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.
|
||||
|
@ -281,7 +313,9 @@ 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
|
||||
|
@ -293,13 +327,13 @@ class Settings(Setting):
|
|||
class BitFieldSetting(Setting):
|
||||
"""A setting descriptor for a set of choices represented by one bit each, being a map from options to booleans.
|
||||
Needs to be instantiated for each specific device."""
|
||||
|
||||
def read(self, cached=True):
|
||||
assert hasattr(self, '_value')
|
||||
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,
|
||||
|
@ -307,7 +341,8 @@ 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
|
||||
|
@ -321,7 +356,8 @@ 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
|
||||
|
@ -333,13 +369,15 @@ 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))]
|
||||
|
||||
|
@ -347,7 +385,8 @@ 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))]
|
||||
|
||||
|
@ -357,7 +396,8 @@ 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.
|
||||
|
@ -369,7 +409,8 @@ 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
|
||||
|
@ -382,7 +423,8 @@ 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.
|
||||
|
@ -396,7 +438,9 @@ 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
|
||||
|
@ -404,10 +448,12 @@ class BitFieldSetting(Setting):
|
|||
|
||||
return value
|
||||
|
||||
|
||||
#
|
||||
# read/write low-level operators
|
||||
#
|
||||
|
||||
|
||||
class RegisterRW(object):
|
||||
__slots__ = ('register', )
|
||||
|
||||
|
@ -431,7 +477,10 @@ 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
|
||||
|
@ -443,7 +492,8 @@ 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):
|
||||
|
@ -452,7 +502,11 @@ 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
|
||||
|
@ -467,7 +521,8 @@ 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)
|
||||
|
||||
|
||||
#
|
||||
|
@ -475,6 +530,7 @@ class FeatureRWMap(FeatureRW):
|
|||
# handle the conversion from read bytes, to setting value, and back
|
||||
#
|
||||
|
||||
|
||||
class BooleanValidator(object):
|
||||
__slots__ = ('true_value', 'false_value', 'mask', 'needs_current_value')
|
||||
|
||||
|
@ -484,7 +540,10 @@ 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:
|
||||
|
@ -523,13 +582,15 @@ 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, self.mask)
|
||||
reply_value, self.true_value, self.false_value,
|
||||
self.mask)
|
||||
return False
|
||||
|
||||
count = len(self.mask)
|
||||
|
@ -559,7 +620,8 @@ 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)
|
||||
|
@ -574,11 +636,13 @@ 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
|
||||
|
||||
|
@ -593,7 +657,8 @@ 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):
|
||||
|
@ -619,7 +684,6 @@ class ChoicesValidator(object):
|
|||
__slots__ = ('choices', 'flag', '_bytes_count', 'needs_current_value')
|
||||
|
||||
kind = KIND.choice
|
||||
|
||||
"""Translates between NamedInts and a byte sequence.
|
||||
:param choices: a list of NamedInts
|
||||
:param bytes_count: the size of the derived byte sequence. If None, it
|
||||
|
@ -641,7 +705,8 @@ 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):
|
||||
|
@ -662,10 +727,16 @@ class ChoicesValidator(object):
|
|||
assert isinstance(choice, _NamedInt)
|
||||
return choice.bytes(self._bytes_count)
|
||||
|
||||
|
||||
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
|
||||
|
@ -699,21 +770,24 @@ 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.
|
||||
:param min_value: minimum accepted value (inclusive)
|
||||
:param max_value: maximum accepted value (inclusive)
|
||||
|
@ -733,8 +807,10 @@ 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):
|
||||
|
|
|
@ -58,241 +58,451 @@ _F = _hidpp20.FEATURE
|
|||
# pre-defined basic setting descriptors
|
||||
#
|
||||
|
||||
def register_toggle(name, register,
|
||||
|
||||
def register_toggle(name,
|
||||
register,
|
||||
true_value=_BooleanV.default_true,
|
||||
false_value=_BooleanV.default_false,
|
||||
mask=_BooleanV.default_mask,
|
||||
label=None, description=None, device_kind=None):
|
||||
validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask)
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None):
|
||||
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,
|
||||
def register_choices(name,
|
||||
register,
|
||||
choices,
|
||||
kind=_KIND.choice,
|
||||
label=None, description=None, device_kind=None):
|
||||
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, feature,
|
||||
def feature_toggle(name,
|
||||
feature,
|
||||
read_function_id=_FeatureRW.default_read_fnid,
|
||||
write_function_id=_FeatureRW.default_write_fnid,
|
||||
true_value=_BooleanV.default_true,
|
||||
false_value=_BooleanV.default_false,
|
||||
mask=_BooleanV.default_mask,
|
||||
label=None, description=None, device_kind=None):
|
||||
validator = _BooleanV(true_value=true_value, false_value=false_value, mask=mask)
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None):
|
||||
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, feature, options,
|
||||
|
||||
def feature_bitfield_toggle(name,
|
||||
feature,
|
||||
options,
|
||||
read_function_id=_FeatureRW.default_read_fnid,
|
||||
write_function_id=_FeatureRW.default_write_fnid,
|
||||
label=None, description=None, device_kind=None):
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None):
|
||||
assert options
|
||||
validator = _BitFieldV(options)
|
||||
rw = _FeatureRW(feature, read_function_id, write_function_id)
|
||||
return _BitFieldSetting(name, rw, validator, feature=feature, label=label, description=description, device_kind=device_kind)
|
||||
return _BitFieldSetting(name,
|
||||
rw,
|
||||
validator,
|
||||
feature=feature,
|
||||
label=label,
|
||||
description=description,
|
||||
device_kind=device_kind)
|
||||
|
||||
def feature_bitfield_toggle_dynamic(name, feature, options_callback,
|
||||
|
||||
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):
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None):
|
||||
def instantiate(device):
|
||||
options = options_callback(device)
|
||||
setting = feature_bitfield_toggle(name, feature, options,
|
||||
setting = feature_bitfield_toggle(name,
|
||||
feature,
|
||||
options,
|
||||
read_function_id=read_function_id,
|
||||
write_function_id=write_function_id,
|
||||
label=label,
|
||||
description=description, device_kind=device_kind)
|
||||
description=description,
|
||||
device_kind=device_kind)
|
||||
return setting(device)
|
||||
|
||||
instantiate._rw_kind = _FeatureRW.kind
|
||||
return instantiate
|
||||
|
||||
def feature_choices(name, feature, choices,
|
||||
read_function_id, write_function_id,
|
||||
|
||||
def feature_choices(name,
|
||||
feature,
|
||||
choices,
|
||||
read_function_id,
|
||||
write_function_id,
|
||||
bytes_count=None,
|
||||
label=None, description=None, device_kind=None):
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None):
|
||||
assert choices
|
||||
validator = _ChoicesV(choices, bytes_count=bytes_count)
|
||||
rw = _FeatureRW(feature, read_function_id, write_function_id)
|
||||
return _Setting(name, rw, validator, feature=feature, kind=_KIND.choice, label=label, description=description, device_kind=device_kind)
|
||||
return _Setting(name,
|
||||
rw,
|
||||
validator,
|
||||
feature=feature,
|
||||
kind=_KIND.choice,
|
||||
label=label,
|
||||
description=description,
|
||||
device_kind=device_kind)
|
||||
|
||||
def feature_choices_dynamic(name, feature, choices_callback,
|
||||
read_function_id, write_function_id,
|
||||
|
||||
def feature_choices_dynamic(name,
|
||||
feature,
|
||||
choices_callback,
|
||||
read_function_id,
|
||||
write_function_id,
|
||||
bytes_count=None,
|
||||
label=None, description=None, device_kind=None):
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None):
|
||||
# Proxy that obtains choices dynamically from a device
|
||||
def instantiate(device):
|
||||
# Obtain choices for this feature
|
||||
choices = choices_callback(device)
|
||||
setting = feature_choices(name, feature, choices,
|
||||
read_function_id, write_function_id,
|
||||
setting = feature_choices(name,
|
||||
feature,
|
||||
choices,
|
||||
read_function_id,
|
||||
write_function_id,
|
||||
bytes_count=bytes_count,
|
||||
label=label, description=description, device_kind=device_kind)
|
||||
label=label,
|
||||
description=description,
|
||||
device_kind=device_kind)
|
||||
return setting(device)
|
||||
|
||||
instantiate._rw_kind = _FeatureRW.kind
|
||||
return instantiate
|
||||
|
||||
|
||||
# maintain a mapping from keys (NamedInts) to one of a list of choices (NamedInts), default is first one
|
||||
# the setting is stored as a JSON-compatible object mapping the key int (as a string) to the choice int
|
||||
# extra_default is an extra value that comes from the device that also means the default
|
||||
def feature_map_choices(name, feature, choicesmap,
|
||||
read_function_id, write_function_id,
|
||||
key_bytes_count=None, skip_bytes_count=None, value_bytes_count=None,
|
||||
label=None, description=None, device_kind=None, extra_default=None):
|
||||
def feature_map_choices(name,
|
||||
feature,
|
||||
choicesmap,
|
||||
read_function_id,
|
||||
write_function_id,
|
||||
key_bytes_count=None,
|
||||
skip_bytes_count=None,
|
||||
value_bytes_count=None,
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None,
|
||||
extra_default=None):
|
||||
assert choicesmap
|
||||
validator = _ChoicesMapV(choicesmap, key_bytes_count=key_bytes_count, 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)
|
||||
return _Settings(name, rw, validator, feature=feature, kind=_KIND.map_choice, label=label, description=description, device_kind=device_kind)
|
||||
validator = _ChoicesMapV(choicesmap,
|
||||
key_bytes_count=key_bytes_count,
|
||||
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)
|
||||
return _Settings(name,
|
||||
rw,
|
||||
validator,
|
||||
feature=feature,
|
||||
kind=_KIND.map_choice,
|
||||
label=label,
|
||||
description=description,
|
||||
device_kind=device_kind)
|
||||
|
||||
def feature_map_choices_dynamic(name, feature, choices_callback,
|
||||
read_function_id, write_function_id,
|
||||
key_bytes_count=None, skip_bytes_count=None, value_bytes_count=None,
|
||||
label=None, description=None, device_kind=None, extra_default=None):
|
||||
|
||||
def feature_map_choices_dynamic(name,
|
||||
feature,
|
||||
choices_callback,
|
||||
read_function_id,
|
||||
write_function_id,
|
||||
key_bytes_count=None,
|
||||
skip_bytes_count=None,
|
||||
value_bytes_count=None,
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None,
|
||||
extra_default=None):
|
||||
# Proxy that obtains choices dynamically from a device
|
||||
def instantiate(device):
|
||||
choices = choices_callback(device)
|
||||
if not choices: # no choices, so don't create a Setting
|
||||
return None
|
||||
setting = feature_map_choices(name, feature, choices,
|
||||
read_function_id, write_function_id,
|
||||
key_bytes_count=key_bytes_count, skip_bytes_count=skip_bytes_count, value_bytes_count=value_bytes_count,
|
||||
label=label, description=description, device_kind=device_kind, extra_default=extra_default)
|
||||
setting = feature_map_choices(name,
|
||||
feature,
|
||||
choices,
|
||||
read_function_id,
|
||||
write_function_id,
|
||||
key_bytes_count=key_bytes_count,
|
||||
skip_bytes_count=skip_bytes_count,
|
||||
value_bytes_count=value_bytes_count,
|
||||
label=label,
|
||||
description=description,
|
||||
device_kind=device_kind,
|
||||
extra_default=extra_default)
|
||||
return setting(device)
|
||||
|
||||
instantiate._rw_kind = _FeatureRWMap.kind
|
||||
return instantiate
|
||||
|
||||
def feature_range(name, feature, min_value, max_value,
|
||||
|
||||
def feature_range(name,
|
||||
feature,
|
||||
min_value,
|
||||
max_value,
|
||||
read_function_id=_FeatureRW.default_read_fnid,
|
||||
write_function_id=_FeatureRW.default_write_fnid,
|
||||
rw=None,
|
||||
bytes_count=None,
|
||||
label=None, description=None, device_kind=None):
|
||||
label=None,
|
||||
description=None,
|
||||
device_kind=None):
|
||||
validator = _RangeV(min_value, max_value, bytes_count=bytes_count)
|
||||
if rw is None:
|
||||
rw = _FeatureRW(feature, read_function_id, write_function_id)
|
||||
return _Setting(name, rw, validator, feature=feature, kind=_KIND.range, label=label, description=description, device_kind=device_kind)
|
||||
return _Setting(name,
|
||||
rw,
|
||||
validator,
|
||||
feature=feature,
|
||||
kind=_KIND.range,
|
||||
label=label,
|
||||
description=description,
|
||||
device_kind=device_kind)
|
||||
|
||||
|
||||
#
|
||||
# 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"),
|
||||
_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."))
|
||||
_LOW_RES_SCROLL = ('lowres-smooth-scroll', _("HID++ Scrolling"), _("HID++ mode for vertical scroll with the wheel.") + '\n' +
|
||||
_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' +
|
||||
_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"
|
||||
_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."))
|
||||
_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."))
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def _register_hand_detection(register=_R.keyboard_hand_detection,
|
||||
true_value=b'\x00\x00\x00', false_value=b'\x00\x00\x30', mask=b'\x00\x00\xFF'):
|
||||
return register_toggle(_HAND_DETECTION[0], register, true_value=true_value, false_value=false_value,
|
||||
label=_HAND_DETECTION[1], description=_HAND_DETECTION[2],
|
||||
true_value=b'\x00\x00\x00',
|
||||
false_value=b'\x00\x00\x30',
|
||||
mask=b'\x00\x00\xFF'):
|
||||
return register_toggle(_HAND_DETECTION[0],
|
||||
register,
|
||||
true_value=true_value,
|
||||
false_value=false_value,
|
||||
label=_HAND_DETECTION[1],
|
||||
description=_HAND_DETECTION[2],
|
||||
device_kind=(_DK.keyboard, ))
|
||||
|
||||
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, mask=mask,
|
||||
label=_FN_SWAP[1], description=_FN_SWAP[2],
|
||||
|
||||
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,
|
||||
mask=mask,
|
||||
label=_FN_SWAP[1],
|
||||
description=_FN_SWAP[2],
|
||||
device_kind=(_DK.keyboard, ))
|
||||
|
||||
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, mask=mask,
|
||||
label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2],
|
||||
|
||||
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,
|
||||
mask=mask,
|
||||
label=_SMOOTH_SCROLL[1],
|
||||
description=_SMOOTH_SCROLL[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
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, mask=mask,
|
||||
label=_SIDE_SCROLL[1], description=_SIDE_SCROLL[2],
|
||||
|
||||
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,
|
||||
mask=mask,
|
||||
label=_SIDE_SCROLL[1],
|
||||
description=_SIDE_SCROLL[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _register_dpi(register=_R.mouse_dpi, choices=None):
|
||||
return register_choices(_DPI[0], register, choices,
|
||||
label=_DPI[1], description=_DPI[2],
|
||||
return register_choices(_DPI[0],
|
||||
register,
|
||||
choices,
|
||||
label=_DPI[1],
|
||||
description=_DPI[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_fn_swap():
|
||||
return feature_toggle(_FN_SWAP[0], _F.FN_INVERSION,
|
||||
label=_FN_SWAP[1], description=_FN_SWAP[2],
|
||||
return feature_toggle(_FN_SWAP[0],
|
||||
_F.FN_INVERSION,
|
||||
label=_FN_SWAP[1],
|
||||
description=_FN_SWAP[2],
|
||||
device_kind=(_DK.keyboard, ))
|
||||
|
||||
|
||||
# this might not be correct for this feature
|
||||
def _feature_new_fn_swap():
|
||||
return feature_toggle(_FN_SWAP[0], _F.NEW_FN_INVERSION,
|
||||
label=_FN_SWAP[1], description=_FN_SWAP[2],
|
||||
return feature_toggle(_FN_SWAP[0],
|
||||
_F.NEW_FN_INVERSION,
|
||||
label=_FN_SWAP[1],
|
||||
description=_FN_SWAP[2],
|
||||
device_kind=(_DK.keyboard, ))
|
||||
|
||||
|
||||
# ignore the capabilities part of the feature - all devices should be able to swap Fn state
|
||||
# just use the current host (first byte = 0xFF) part of the feature to read and set the Fn state
|
||||
def _feature_k375s_fn_swap():
|
||||
return feature_toggle(_FN_SWAP[0], _F.K375S_FN_INVERSION,
|
||||
label=_FN_SWAP[1], description=_FN_SWAP[2],
|
||||
true_value=b'\xFF\x01', false_value=b'\xFF\x00',
|
||||
return feature_toggle(_FN_SWAP[0],
|
||||
_F.K375S_FN_INVERSION,
|
||||
label=_FN_SWAP[1],
|
||||
description=_FN_SWAP[2],
|
||||
true_value=b'\xFF\x01',
|
||||
false_value=b'\xFF\x00',
|
||||
device_kind=(_DK.keyboard, ))
|
||||
|
||||
|
||||
# FIXME: This will enable all supported backlight settings, we should allow the users to select which settings they want to enable.
|
||||
def _feature_backlight2():
|
||||
return feature_toggle(_BACKLIGHT[0], _F.BACKLIGHT2,
|
||||
label=_BACKLIGHT[1], description=_BACKLIGHT[2],
|
||||
return feature_toggle(_BACKLIGHT[0],
|
||||
_F.BACKLIGHT2,
|
||||
label=_BACKLIGHT[1],
|
||||
description=_BACKLIGHT[2],
|
||||
device_kind=(_DK.keyboard, ))
|
||||
|
||||
|
||||
def _feature_hi_res_scroll():
|
||||
return feature_toggle(_HI_RES_SCROLL[0], _F.HI_RES_SCROLLING,
|
||||
label=_HI_RES_SCROLL[1], description=_HI_RES_SCROLL[2],
|
||||
return feature_toggle(_HI_RES_SCROLL[0],
|
||||
_F.HI_RES_SCROLLING,
|
||||
label=_HI_RES_SCROLL[1],
|
||||
description=_HI_RES_SCROLL[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_lowres_smooth_scroll():
|
||||
return feature_toggle(_LOW_RES_SCROLL[0], _F.LOWRES_WHEEL,
|
||||
label=_LOW_RES_SCROLL[1], description=_LOW_RES_SCROLL[2],
|
||||
return feature_toggle(_LOW_RES_SCROLL[0],
|
||||
_F.LOWRES_WHEEL,
|
||||
label=_LOW_RES_SCROLL[1],
|
||||
description=_LOW_RES_SCROLL[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_hires_smooth_invert():
|
||||
return feature_toggle(_HIRES_INV[0], _F.HIRES_WHEEL,
|
||||
return feature_toggle(_HIRES_INV[0],
|
||||
_F.HIRES_WHEEL,
|
||||
read_function_id=0x10,
|
||||
write_function_id=0x20,
|
||||
true_value=0x04, mask=0x04,
|
||||
label=_HIRES_INV[1], description=_HIRES_INV[2],
|
||||
true_value=0x04,
|
||||
mask=0x04,
|
||||
label=_HIRES_INV[1],
|
||||
description=_HIRES_INV[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_hires_smooth_resolution():
|
||||
return feature_toggle(_HIRES_RES[0], _F.HIRES_WHEEL,
|
||||
return feature_toggle(_HIRES_RES[0],
|
||||
_F.HIRES_WHEEL,
|
||||
read_function_id=0x10,
|
||||
write_function_id=0x20,
|
||||
true_value=0x02, mask=0x02,
|
||||
label=_HIRES_RES[1], description=_HIRES_RES[2],
|
||||
true_value=0x02,
|
||||
mask=0x02,
|
||||
label=_HIRES_RES[1],
|
||||
description=_HIRES_RES[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_smart_shift():
|
||||
_MIN_SMART_SHIFT_VALUE = 0
|
||||
_MAX_SMART_SHIFT_VALUE = 50
|
||||
|
||||
class _SmartShiftRW(_FeatureRW):
|
||||
def __init__(self, feature):
|
||||
super(_SmartShiftRW, self).__init__(feature)
|
||||
|
@ -316,16 +526,21 @@ 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], _F.SMART_SHIFT,
|
||||
_MIN_SMART_SHIFT_VALUE, _MAX_SMART_SHIFT_VALUE,
|
||||
return feature_range(_SMART_SHIFT[0],
|
||||
_F.SMART_SHIFT,
|
||||
_MIN_SMART_SHIFT_VALUE,
|
||||
_MAX_SMART_SHIFT_VALUE,
|
||||
bytes_count=1,
|
||||
rw=_SmartShiftRW(_F.SMART_SHIFT),
|
||||
label=_SMART_SHIFT[1], description=_SMART_SHIFT[2],
|
||||
label=_SMART_SHIFT[1],
|
||||
description=_SMART_SHIFT[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_adjustable_dpi_choices(device):
|
||||
# [1] getSensorDpiList(sensorIdx)
|
||||
reply = device.feature_request(_F.ADJUSTABLE_DPI, 0x10)
|
||||
|
@ -348,29 +563,38 @@ def _feature_adjustable_dpi_choices(device):
|
|||
dpi_list = range(dpi_list[0], dpi_list[1] + 1, step)
|
||||
return _NamedInts.list(dpi_list)
|
||||
|
||||
|
||||
def _feature_adjustable_dpi():
|
||||
"""Pointer Speed feature"""
|
||||
# Assume sensorIdx 0 (there is only one sensor)
|
||||
# [2] getSensorDpi(sensorIdx) -> sensorIdx, dpiMSB, dpiLSB
|
||||
# [3] setSensorDpi(sensorIdx, dpi)
|
||||
return feature_choices_dynamic(_DPI[0], _F.ADJUSTABLE_DPI,
|
||||
return feature_choices_dynamic(_DPI[0],
|
||||
_F.ADJUSTABLE_DPI,
|
||||
_feature_adjustable_dpi_choices,
|
||||
read_function_id=0x20,
|
||||
write_function_id=0x30,
|
||||
bytes_count=3,
|
||||
label=_DPI[1], description=_DPI[2],
|
||||
label=_DPI[1],
|
||||
description=_DPI[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
def _feature_pointer_speed():
|
||||
"""Pointer Speed feature"""
|
||||
# min and max values taken from usb traces of Win software
|
||||
return feature_range(_POINTER_SPEED[0], _F.POINTER_SPEED, 0x002e, 0x01ff,
|
||||
return feature_range(_POINTER_SPEED[0],
|
||||
_F.POINTER_SPEED,
|
||||
0x002e,
|
||||
0x01ff,
|
||||
read_function_id=0x0,
|
||||
write_function_id=0x10,
|
||||
bytes_count=2,
|
||||
label=_POINTER_SPEED[1], description=_POINTER_SPEED[2],
|
||||
label=_POINTER_SPEED[1],
|
||||
description=_POINTER_SPEED[2],
|
||||
device_kind=(_DK.mouse, _DK.trackball))
|
||||
|
||||
|
||||
# the keys for the choice map are Logitech controls (from special_keys)
|
||||
# each choice value is a NamedInt with the string from a task (to be shown to the user)
|
||||
# and the integer being the control number for that task (to be written to the device)
|
||||
|
@ -384,15 +608,20 @@ 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
|
||||
|
@ -401,25 +630,41 @@ def _feature_reprogrammable_keys_choices(device):
|
|||
choices[k[0]] = key_choices
|
||||
return choices
|
||||
|
||||
|
||||
def _feature_reprogrammable_keys():
|
||||
return feature_map_choices_dynamic(_REPROGRAMMABLE_KEYS[0], _F.REPROG_CONTROLS_V4,
|
||||
return feature_map_choices_dynamic(_REPROGRAMMABLE_KEYS[0],
|
||||
_F.REPROG_CONTROLS_V4,
|
||||
_feature_reprogrammable_keys_choices,
|
||||
read_function_id=0x20, write_function_id=0x30,
|
||||
key_bytes_count=2, skip_bytes_count=1, value_bytes_count=2,
|
||||
label=_REPROGRAMMABLE_KEYS[1], description=_REPROGRAMMABLE_KEYS[2],
|
||||
device_kind=(_DK.keyboard,), extra_default=0)
|
||||
read_function_id=0x20,
|
||||
write_function_id=0x30,
|
||||
key_bytes_count=2,
|
||||
skip_bytes_count=1,
|
||||
value_bytes_count=2,
|
||||
label=_REPROGRAMMABLE_KEYS[1],
|
||||
description=_REPROGRAMMABLE_KEYS[2],
|
||||
device_kind=(_DK.keyboard, ),
|
||||
extra_default=0)
|
||||
|
||||
|
||||
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,
|
||||
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,))
|
||||
read_function_id=0x10,
|
||||
write_function_id=0x20,
|
||||
label=_DISABLE_KEYS[1],
|
||||
description=_DISABLE_KEYS[2],
|
||||
device_kind=(_DK.keyboard, ))
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
|
@ -427,8 +672,11 @@ def _feature_disable_keyboard_keys():
|
|||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
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 = [
|
||||
_S(_HAND_DETECTION[0], registerFn=_register_hand_detection),
|
||||
|
@ -438,15 +686,29 @@ _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])
|
||||
|
@ -459,6 +721,7 @@ del _SETTINGS_LIST
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
# Returns True if device was queried to find features, False otherwise
|
||||
def check_feature_settings(device, already_known):
|
||||
"""Try to auto-detect device settings by the HID++ 2.0 features they have."""
|
||||
|
@ -481,11 +744,13 @@ 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:
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
from .common import NamedInts as _NamedInts
|
||||
|
||||
# <controls.xml awk -F\" '/<Control /{sub(/^LD_FINFO_(CTRLID_)?/, "", $2);printf("\t%s=0x%04X,\n", $2, $4)}' | sort -t= -k2
|
||||
|
@ -68,7 +67,8 @@ 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,
|
||||
|
@ -236,7 +236,8 @@ 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
|
||||
|
@ -284,7 +285,6 @@ TASK = _NamedInts(
|
|||
Next=0x0005,
|
||||
Previous=0x0006,
|
||||
Stop=0x0007,
|
||||
|
||||
Application_Switcher=0x0008,
|
||||
BurnMediaPlayer=0x0009,
|
||||
Calculator=0x000A,
|
||||
|
@ -311,7 +311,6 @@ TASK = _NamedInts(
|
|||
# Both 0x001E and 0x001F are known as MediaCenterSet
|
||||
Media_Center_Logitech=0x001E,
|
||||
Media_Center_Microsoft=0x001F,
|
||||
|
||||
UserMenu=0x0020,
|
||||
Messenger=0x0021,
|
||||
PersonalFolders=0x0022,
|
||||
|
@ -462,7 +461,8 @@ 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
|
||||
|
@ -490,16 +490,14 @@ TASK = _NamedInts(
|
|||
)
|
||||
TASK._fallback = lambda x: 'unknown:%04X' % x
|
||||
# hidpp 4.5 info from https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html
|
||||
KEY_FLAG = _NamedInts(
|
||||
virtual=0x80,
|
||||
KEY_FLAG = _NamedInts(virtual=0x80,
|
||||
persistently_divertable=0x40,
|
||||
divertable=0x20,
|
||||
reprogrammable=0x10,
|
||||
FN_sensitive=0x08,
|
||||
nonstandard=0x04,
|
||||
is_FN=0x02,
|
||||
mse=0x01
|
||||
)
|
||||
mse=0x01)
|
||||
|
||||
DISABLE = _NamedInts(
|
||||
Caps_Lock=0x01,
|
||||
|
|
|
@ -25,7 +25,6 @@ from logging import getLogger, DEBUG as _DEBUG
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from .i18n import _, ngettext
|
||||
from .common import NamedInts as _NamedInts, NamedInt as _NamedInt
|
||||
from . import hidpp10 as _hidpp10
|
||||
|
@ -37,7 +36,11 @@ _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,
|
||||
|
@ -64,6 +67,7 @@ _LONG_SLEEP = 15 * 60 # seconds
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def attach_to(device, changed_callback):
|
||||
assert device
|
||||
assert changed_callback
|
||||
|
@ -74,10 +78,12 @@ def attach_to(device, changed_callback):
|
|||
else:
|
||||
device.status = DeviceStatus(device, changed_callback)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
class ReceiverStatus(dict):
|
||||
"""The 'runtime' status of a receiver, mostly about the pairing process --
|
||||
is the pairing lock open or closed, any pairing errors, etc.
|
||||
|
@ -98,8 +104,11 @@ 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__
|
||||
|
||||
def changed(self, alert=ALERT.NOTIFICATION, reason=None):
|
||||
|
@ -119,10 +128,12 @@ class ReceiverStatus(dict):
|
|||
# # get an update of the notification flags
|
||||
# # self[KEYS.NOTIFICATION_FLAGS] = _hidpp10.get_notification_flags(r)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
class DeviceStatus(dict):
|
||||
"""Holds the 'runtime' status of a peripheral -- things like
|
||||
active/inactive, battery charge, lux, etc. It updates them mostly by
|
||||
|
@ -148,9 +159,13 @@ 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:
|
||||
|
@ -166,13 +181,20 @@ 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)
|
||||
|
||||
|
@ -182,7 +204,8 @@ 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
|
||||
|
@ -192,36 +215,55 @@ 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):
|
||||
|
@ -260,7 +302,11 @@ 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
|
||||
|
@ -275,7 +321,8 @@ 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.
|
||||
|
@ -287,7 +334,8 @@ 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()
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
import argparse as _argparse
|
||||
import sys as _sys
|
||||
|
||||
|
@ -27,49 +26,71 @@ from logging import getLogger, DEBUG as _DEBUG
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from solaar import NAME
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def _create_parser():
|
||||
parser = _argparse.ArgumentParser(prog=NAME.lower(), add_help=False,
|
||||
epilog='For details on individual actions, run `%s <action> --help`.' % NAME.lower())
|
||||
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, '
|
||||
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='?',
|
||||
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',
|
||||
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, '
|
||||
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='?',
|
||||
sp.add_argument(
|
||||
'setting',
|
||||
nargs='?',
|
||||
help='device-specific setting; leave empty to list available settings')
|
||||
sp.add_argument('value', nargs='?',
|
||||
help='new value for the setting')
|
||||
sp.add_argument('value', nargs='?', help='new value for the setting')
|
||||
sp.set_defaults(action='config')
|
||||
|
||||
sp = 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='?',
|
||||
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',
|
||||
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')
|
||||
|
@ -103,7 +124,8 @@ 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
|
||||
|
||||
|
||||
|
@ -128,10 +150,9 @@ 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() or
|
||||
name in dev.name.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
|
||||
|
||||
raise Exception("no device found matching '%s'" % name)
|
||||
|
@ -163,7 +184,8 @@ def run(cli_args=None, hidraw_path=None):
|
|||
except AssertionError as e:
|
||||
from traceback import extract_tb
|
||||
tb_last = extract_tb(_sys.exc_info()[2])[-1]
|
||||
_sys.exit('%s: assertion failed: %s line %d' % (NAME.lower(), tb_last[0], tb_last[1]))
|
||||
_sys.exit('%s: assertion failed: %s line %d' %
|
||||
(NAME.lower(), tb_last[0], tb_last[1]))
|
||||
except Exception as e:
|
||||
from traceback import format_exc
|
||||
_sys.exit('%s: error: %s' % (NAME.lower(), format_exc()))
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
from solaar import configuration as _configuration
|
||||
from logitech_receiver import settings as _settings
|
||||
|
||||
|
@ -30,9 +29,12 @@ 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), '], or higher/lower/highest/max/lowest/min')
|
||||
print('# possible values: one of [',
|
||||
', '.join(str(v) for v in s.choices),
|
||||
'], or higher/lower/highest/max/lowest/min')
|
||||
else:
|
||||
# wtf?
|
||||
pass
|
||||
|
@ -88,7 +90,8 @@ 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()
|
||||
|
@ -96,20 +99,25 @@ 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:
|
||||
|
@ -123,5 +131,6 @@ 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)
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
from time import time as _timestamp
|
||||
|
||||
from logitech_receiver import (
|
||||
|
@ -42,12 +41,15 @@ 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,14 +63,17 @@ 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()
|
||||
|
@ -88,11 +93,11 @@ def run(receivers, args, find_receiver, _ignore):
|
|||
|
||||
if receiver.status.new_device:
|
||||
dev = receiver.status.new_device
|
||||
print ('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial))
|
||||
print('Paired device %d: %s (%s) [%s:%s]' %
|
||||
(dev.number, dev.name, dev.codename, dev.wpid, dev.serial))
|
||||
else:
|
||||
error = receiver.status.get(_status.KEYS.ERROR)
|
||||
if error:
|
||||
raise Exception("pairing failed: %s" % error)
|
||||
else:
|
||||
print('Paired a device') # this is better than an error
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
from time import time as _timestamp
|
||||
|
||||
from logitech_receiver.common import strhex as _strhex
|
||||
|
@ -35,6 +34,7 @@ _R = _hidpp10.REGISTERS
|
|||
|
||||
from solaar.cli.show import _print_receiver
|
||||
|
||||
|
||||
def run(receivers, args, find_receiver, _ignore):
|
||||
assert receivers
|
||||
|
||||
|
@ -52,19 +52,32 @@ 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"))
|
||||
print(" Connection State %#04x: %s" %
|
||||
(_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"))
|
||||
print(" Device Activity %#04x: %s" %
|
||||
(_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)
|
||||
print(" Pairing Register %#04x %#04x: %s" % (_R.receiver_info%0x100,sub_reg + device,'0x'+_strhex(register) if register else "None"))
|
||||
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"))
|
||||
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"))
|
||||
print(" Pairing Name %#04x %#02x: %s" %
|
||||
(_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"))
|
||||
print(" Firmware %#04x %#04x: %s" %
|
||||
(_R.firmware % 0x100, sub_reg,
|
||||
'0x' + _strhex(register) if register else "None"))
|
||||
|
|
|
@ -19,13 +19,9 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
from logitech_receiver import (
|
||||
hidpp10 as _hidpp10,
|
||||
hidpp20 as _hidpp20,
|
||||
from logitech_receiver import (hidpp10 as _hidpp10, hidpp20 as _hidpp20,
|
||||
special_keys as _special_keys,
|
||||
settings_templates as _settings_templates
|
||||
)
|
||||
settings_templates as _settings_templates)
|
||||
from logitech_receiver.common import NamedInt as _NamedInt
|
||||
|
||||
|
||||
|
@ -40,24 +36,31 @@ 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)')
|
||||
|
||||
|
||||
def _battery_text(level):
|
||||
if level is None:
|
||||
return 'N/A'
|
||||
|
@ -66,6 +69,7 @@ def _battery_text(level) :
|
|||
else:
|
||||
return '%d%%' % level
|
||||
|
||||
|
||||
def _print_device(dev):
|
||||
assert dev
|
||||
# check if the device is online
|
||||
|
@ -80,21 +84,26 @@ 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).')
|
||||
|
||||
|
@ -108,7 +117,8 @@ 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:
|
||||
|
@ -138,7 +148,8 @@ 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:
|
||||
|
@ -148,19 +159,25 @@ 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:
|
||||
|
@ -171,8 +188,10 @@ 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)
|
||||
|
@ -184,10 +203,13 @@ 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:
|
||||
|
@ -195,12 +217,14 @@ 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.')
|
||||
|
|
|
@ -28,12 +28,15 @@ 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))
|
||||
|
|
|
@ -25,17 +25,16 @@ from logging import getLogger, DEBUG as _DEBUG, INFO as _INFO
|
|||
_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')
|
||||
|
||||
|
||||
from solaar import __version__
|
||||
_KEY_VERSION = '_version'
|
||||
_KEY_NAME = '_name'
|
||||
_configuration = {}
|
||||
|
||||
|
||||
|
||||
def _load():
|
||||
if _path.isfile(_file_path):
|
||||
loaded_configuration = {}
|
||||
|
@ -74,7 +73,11 @@ 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)
|
||||
|
|
|
@ -22,7 +22,6 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
|||
|
||||
import importlib
|
||||
|
||||
|
||||
from solaar import __version__, NAME
|
||||
import solaar.i18n as _i18n
|
||||
import solaar.cli as _cli
|
||||
|
@ -31,6 +30,7 @@ import solaar.cli as _cli
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def _require(module, os_package, gi=None, gi_package=None, gi_version=None):
|
||||
try:
|
||||
if gi is not None:
|
||||
|
@ -40,23 +40,53 @@ def _require(module, os_package, gi=None, gi_package=None, gi_version=None):
|
|||
import sys
|
||||
sys.exit("%s: missing required system package %s" % (NAME, os_package))
|
||||
|
||||
|
||||
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',
|
||||
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',
|
||||
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,
|
||||
arg_parser.add_argument('action',
|
||||
nargs=argparse.REMAINDER,
|
||||
choices=_cli.actions,
|
||||
help='optional actions to perform')
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
|
@ -75,14 +105,17 @@ 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
|
||||
|
||||
|
@ -115,7 +148,8 @@ def main():
|
|||
_upower.watch(lambda: listener.ping_all(True))
|
||||
|
||||
# main UI event loop
|
||||
ui.run_loop(listener.start_all, listener.stop_all, args.window!='only', args.window!='hide')
|
||||
ui.run_loop(listener.start_all, listener.stop_all,
|
||||
args.window != 'only', args.window != 'hide')
|
||||
except Exception as e:
|
||||
import sys
|
||||
from traceback import format_exc
|
||||
|
|
|
@ -25,18 +25,23 @@ from solaar import NAME as _NAME
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
|
|
@ -24,35 +24,33 @@ from logging import getLogger, INFO as _INFO, WARNING as _WARNING
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from solaar.i18n import _
|
||||
from . import configuration
|
||||
from logitech_receiver import (
|
||||
Receiver,
|
||||
listener as _listener,
|
||||
status as _status,
|
||||
notifications as _notifications
|
||||
)
|
||||
from logitech_receiver import (Receiver, listener as _listener, status as
|
||||
_status, notifications as _notifications)
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from collections import namedtuple
|
||||
_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
|
||||
|
||||
|
||||
def _ghost(device):
|
||||
return _GHOST_DEVICE(
|
||||
receiver=device.receiver,
|
||||
return _GHOST_DEVICE(receiver=device.receiver,
|
||||
number=device.number,
|
||||
name=device.name,
|
||||
kind=device.kind,
|
||||
status=None,
|
||||
online=False)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -66,7 +64,8 @@ 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
|
||||
|
@ -77,9 +76,11 @@ 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)
|
||||
|
||||
|
@ -184,7 +185,9 @@ 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
|
||||
|
||||
|
@ -204,10 +207,14 @@ 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:
|
||||
|
@ -216,7 +223,8 @@ 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
|
||||
|
@ -243,9 +251,12 @@ 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__
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -294,6 +305,7 @@ def stop_all():
|
|||
for l in listeners:
|
||||
l.join()
|
||||
|
||||
|
||||
# ping all devices to find out whether they are connected
|
||||
# after a resume, the device may have been off
|
||||
# so mark its saved status to ensure that the status is pushed to the device when it comes back
|
||||
|
@ -315,6 +327,7 @@ from logitech_receiver import base as _base
|
|||
_status_callback = None
|
||||
_error_callback = None
|
||||
|
||||
|
||||
def setup_scanner(status_changed_callback, error_callback):
|
||||
global _status_callback, _error_callback
|
||||
assert _status_callback is None, 'scanner was already set-up'
|
||||
|
@ -351,7 +364,8 @@ def _process_receiver_event(action, device_info):
|
|||
# for this special case is not good.)
|
||||
try:
|
||||
import subprocess, 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:
|
||||
|
|
|
@ -35,6 +35,7 @@ except ImportError:
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
class TaskRunner(_Thread):
|
||||
def __init__(self, name):
|
||||
super(TaskRunner, self).__init__(name=name)
|
||||
|
|
|
@ -19,14 +19,12 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
from logging import getLogger, DEBUG as _DEBUG
|
||||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
from gi.repository import GLib, Gtk
|
||||
|
||||
|
||||
from solaar.i18n import _
|
||||
|
||||
#
|
||||
|
@ -41,6 +39,7 @@ GLib.threads_init()
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def _error_dialog(reason, object):
|
||||
_log.error("error: %s %s", reason, object)
|
||||
|
||||
|
@ -55,12 +54,14 @@ def _error_dialog(reason, object):
|
|||
'\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()
|
||||
|
@ -70,15 +71,19 @@ def error_dialog(reason, object):
|
|||
assert reason is not None
|
||||
GLib.idle_add(_error_dialog, reason, object)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
_task_runner = None
|
||||
|
||||
|
||||
def ui_async(function, *args, **kwargs):
|
||||
if _task_runner:
|
||||
_task_runner(function, *args, **kwargs)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -88,7 +93,8 @@ from . import notify, tray, window
|
|||
|
||||
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
|
||||
|
@ -138,15 +144,19 @@ 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)
|
||||
|
||||
application.run(args)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
|
|
@ -36,7 +36,9 @@ 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())
|
||||
|
||||
|
@ -45,7 +47,8 @@ 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',
|
||||
|
@ -83,6 +86,7 @@ def _create():
|
|||
def _hide(dialog, event):
|
||||
dialog.hide()
|
||||
return True
|
||||
|
||||
about.connect('delete-event', _hide)
|
||||
|
||||
return about
|
||||
|
|
|
@ -25,13 +25,13 @@ from gi.repository import Gtk, Gdk
|
|||
# _log = getLogger(__name__)
|
||||
# del getLogger
|
||||
|
||||
|
||||
from solaar.i18n import _
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def make(name, label, function, stock_id=None, *args):
|
||||
action = Gtk.Action(name, label, label, None)
|
||||
action.set_icon_name(name)
|
||||
|
@ -50,6 +50,7 @@ def make_toggle(name, label, function, stock_id=None, *args):
|
|||
action.connect('activate', function, *args)
|
||||
return action
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -62,16 +63,20 @@ def make_toggle(name, label, function, stock_id=None, *args):
|
|||
# action.set_sensitive(notify.available)
|
||||
# toggle_notifications = make_toggle('notifications', 'Notifications', _toggle_notifications)
|
||||
|
||||
|
||||
from .about import show_window as _show_about_window
|
||||
from solaar import NAME
|
||||
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)
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from . import pair_window
|
||||
|
||||
|
||||
def pair(window, receiver):
|
||||
assert receiver
|
||||
assert receiver.kind is None
|
||||
|
@ -86,12 +91,14 @@ def pair(window, receiver):
|
|||
|
||||
|
||||
from ..ui import error_dialog
|
||||
|
||||
|
||||
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)
|
||||
|
|
|
@ -30,6 +30,7 @@ from logitech_receiver.settings import KIND as _SETTING_KIND
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def _read_async(setting, force_read, sbox, device_is_online):
|
||||
def _do_read(s, force, sb, online):
|
||||
v = s.read(not force)
|
||||
|
@ -70,6 +71,7 @@ def _write_async_key_value(setting, key, value, sbox):
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def _create_toggle_control(setting):
|
||||
def _switch_notify(switch, _ignore, s):
|
||||
if switch.get_sensitive():
|
||||
|
@ -79,6 +81,7 @@ def _create_toggle_control(setting):
|
|||
c.connect('notify::active', _switch_notify, setting)
|
||||
return c
|
||||
|
||||
|
||||
def _create_choice_control(setting):
|
||||
def _combo_notify(cbbox, s):
|
||||
if cbbox.get_sensitive():
|
||||
|
@ -91,6 +94,7 @@ def _create_choice_control(setting):
|
|||
c.connect('changed', _combo_notify, setting)
|
||||
return c
|
||||
|
||||
|
||||
def _create_map_choice_control(setting):
|
||||
def _map_value_notify_key(cbbox, s):
|
||||
setting, valueBox = s
|
||||
|
@ -102,15 +106,20 @@ 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:
|
||||
|
@ -132,9 +141,11 @@ def _create_map_choice_control(setting):
|
|||
valueBox.connect('changed', _map_value_notify_value, (setting, keyBox))
|
||||
return c
|
||||
|
||||
|
||||
def _create_slider_control(setting):
|
||||
class SliderControl:
|
||||
__slots__ = ('gtk_range', 'timer', 'setting')
|
||||
|
||||
def __init__(self, setting):
|
||||
self.setting = setting
|
||||
self.timer = None
|
||||
|
@ -144,13 +155,11 @@ 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.gtk_range.connect('value-changed', lambda _, c: c._changed(),
|
||||
self)
|
||||
|
||||
def _write(self):
|
||||
_write_async(self.setting,
|
||||
int(self.gtk_range.get_value()),
|
||||
_write_async(self.setting, int(self.gtk_range.get_value()),
|
||||
self.gtk_range.get_parent())
|
||||
self.timer.cancel()
|
||||
|
||||
|
@ -164,10 +173,12 @@ def _create_slider_control(setting):
|
|||
control = SliderControl(setting)
|
||||
return control.gtk_range
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def _create_sbox(s):
|
||||
sbox = Gtk.HBox(homogeneous=False, spacing=6)
|
||||
sbox.pack_start(Gtk.Label(s.label), False, False, 0)
|
||||
|
@ -175,7 +186,8 @@ 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:
|
||||
|
@ -193,15 +205,18 @@ def _create_sbox(s):
|
|||
elif s.kind == _SETTING_KIND.multiple_toggle:
|
||||
# ugly temporary hack!
|
||||
choices = {k: [False, True] for k in s._validator.options}
|
||||
|
||||
class X:
|
||||
def __init__(self, obj, ext):
|
||||
self.obj = obj
|
||||
self.ext = ext
|
||||
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
return self.ext[attr]
|
||||
except KeyError:
|
||||
return getattr(self.obj, attr)
|
||||
|
||||
control = _create_map_choice_control(X(s, {'choices': choices}))
|
||||
sbox.pack_end(control, True, True, 0)
|
||||
else:
|
||||
|
@ -222,7 +237,8 @@ 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()
|
||||
|
||||
|
@ -246,6 +262,7 @@ def _update_setting_item(sbox, value, is_online=True):
|
|||
raise Exception("NotImplemented")
|
||||
control.set_sensitive(True)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -254,6 +271,7 @@ def _update_setting_item(sbox, value, is_online=True):
|
|||
_box = None
|
||||
_items = {}
|
||||
|
||||
|
||||
def create():
|
||||
global _box
|
||||
assert _box is None
|
||||
|
|
|
@ -52,15 +52,23 @@ 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(':'))
|
||||
share_solaar = [prefix_share] + list(
|
||||
_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):
|
||||
|
@ -76,6 +84,7 @@ def _look_for_application_icons():
|
|||
_default_theme = None
|
||||
_use_symbolic_icons = False
|
||||
|
||||
|
||||
def _init_icon_paths():
|
||||
global _default_theme
|
||||
if _default_theme:
|
||||
|
@ -97,10 +106,12 @@ def _init_icon_paths():
|
|||
if not _default_theme.has_icon('battery-good'):
|
||||
_log.warning("failed to detect icons")
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def battery(level=None, charging=False):
|
||||
icon_name = _battery_icon_name(level, charging)
|
||||
if not _default_theme.has_icon(icon_name):
|
||||
|
@ -110,35 +121,43 @@ def battery(level=None, charging=False):
|
|||
_log.debug("battery icon for %s:%s = %s", level, charging, icon_name)
|
||||
return icon_name
|
||||
|
||||
|
||||
# return first res where val >= guard
|
||||
# _first_res(val,((guard,res),...))
|
||||
def _first_res(val, pairs):
|
||||
return next((res for guard, res in pairs if val >= guard), None)
|
||||
|
||||
|
||||
def _battery_icon_name(level, charging):
|
||||
_init_icon_paths()
|
||||
|
||||
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 '')
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def lux(level=None):
|
||||
if level is None or level < 0:
|
||||
return 'light_unknown'
|
||||
return 'light_%03d' % (20 * ((level + 50) // 100))
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
_ICON_SETS = {}
|
||||
|
||||
|
||||
def device_icon_set(name='_', kind=None):
|
||||
icon_set = _ICON_SETS.get(name)
|
||||
if icon_set is None:
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
|
||||
from solaar.i18n import _
|
||||
|
||||
#
|
||||
|
@ -66,7 +65,6 @@ if available:
|
|||
available = False
|
||||
return available and Notify.is_initted()
|
||||
|
||||
|
||||
def uninit():
|
||||
if available and Notify.is_initted():
|
||||
if _log.isEnabledFor(_INFO):
|
||||
|
@ -74,7 +72,6 @@ if available:
|
|||
_notifications.clear()
|
||||
Notify.uninit()
|
||||
|
||||
|
||||
# def toggle(action):
|
||||
# if action.get_active():
|
||||
# init()
|
||||
|
@ -83,7 +80,6 @@ if available:
|
|||
# action.set_sensitive(available)
|
||||
# return action.get_active()
|
||||
|
||||
|
||||
def alert(reason, icon=None):
|
||||
assert reason
|
||||
|
||||
|
@ -108,7 +104,6 @@ if available:
|
|||
except Exception:
|
||||
_log.exception("showing %s", n)
|
||||
|
||||
|
||||
def show(dev, reason=None, icon=None):
|
||||
"""Show a notification with title and text."""
|
||||
if available and Notify.is_initted():
|
||||
|
|
|
@ -25,7 +25,6 @@ from logging import getLogger, DEBUG as _DEBUG
|
|||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
||||
from solaar.i18n import _
|
||||
from . import icons as _icons
|
||||
from logitech_receiver.status import KEYS as _K
|
||||
|
@ -86,7 +85,8 @@ 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
|
||||
|
@ -105,10 +105,12 @@ 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)
|
||||
|
||||
|
@ -132,14 +134,19 @@ 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()
|
||||
|
@ -176,11 +183,17 @@ def _pairing_succeeded(assistant, receiver, device):
|
|||
def _check_encrypted(dev):
|
||||
if assistant.is_drawable():
|
||||
if device.status.get(_K.LINK_ENCRYPTED) == False:
|
||||
hbox.pack_start(Gtk.Image.new_from_icon_name('security-low', Gtk.IconSize.MENU), False, False, 0)
|
||||
hbox.pack_start(Gtk.Label(_("The wireless link is not encrypted") + '!'), False, False, 0)
|
||||
hbox.pack_start(
|
||||
Gtk.Image.new_from_icon_name('security-low',
|
||||
Gtk.IconSize.MENU), False,
|
||||
False, 0)
|
||||
hbox.pack_start(
|
||||
Gtk.Label(_("The wireless link is not encrypted") + '!'),
|
||||
False, False, 0)
|
||||
hbox.show_all()
|
||||
else:
|
||||
return True
|
||||
|
||||
GLib.timeout_add(_STATUS_CHECK, _check_encrypted, device)
|
||||
|
||||
page.show_all()
|
||||
|
@ -194,21 +207,25 @@ 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."), 'preferences-desktop-peripherals',
|
||||
page_text)
|
||||
_("Turn on the device you want to pair."),
|
||||
'preferences-desktop-peripherals', page_text)
|
||||
spinner = Gtk.Spinner()
|
||||
spinner.set_visible(True)
|
||||
page_intro.pack_end(spinner, True, True, 24)
|
||||
|
|
|
@ -29,7 +29,6 @@ from time import time as _timestamp
|
|||
from gi.repository import Gtk, GLib
|
||||
from gi.repository.Gdk import ScrollDirection
|
||||
|
||||
|
||||
from solaar import NAME
|
||||
from solaar.i18n import _
|
||||
from logitech_receiver.status import KEYS as _K
|
||||
|
@ -48,6 +47,7 @@ _RECEIVER_SEPARATOR = ('~', None, None, None)
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def _create_menu(quit_handler):
|
||||
menu = Gtk.Menu()
|
||||
|
||||
|
@ -60,7 +60,11 @@ 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()
|
||||
|
@ -69,6 +73,8 @@ def _create_menu(quit_handler):
|
|||
|
||||
|
||||
_last_scroll = 0
|
||||
|
||||
|
||||
def _scroll(tray_icon, event, direction=None):
|
||||
if direction is None:
|
||||
direction = event.direction
|
||||
|
@ -163,24 +169,24 @@ 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))
|
||||
'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), '')
|
||||
|
@ -191,11 +197,9 @@ try:
|
|||
|
||||
return ind
|
||||
|
||||
|
||||
def _destroy(indicator):
|
||||
indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
|
||||
|
||||
|
||||
def _update_tray_icon():
|
||||
if _picked_device:
|
||||
_ignore, _ignore, name, device_status = _picked_device
|
||||
|
@ -214,19 +218,19 @@ try:
|
|||
# icon_file = _icons.icon_file(icon_name, _TRAY_ICON_SIZE)
|
||||
_icon.set_icon_full(_icon_file(tray_icon_name), description)
|
||||
|
||||
|
||||
def _update_menu_icon(image_widget, icon_name):
|
||||
image_widget.set_from_icon_name(icon_name, _MENU_ICON_SIZE)
|
||||
# icon_file = _icons.icon_file(icon_name, _MENU_ICON_SIZE)
|
||||
# image_widget.set_from_file(icon_file)
|
||||
# image_widget.set_pixel_size(_TRAY_ICON_SIZE)
|
||||
|
||||
|
||||
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:
|
||||
|
||||
|
@ -240,16 +244,15 @@ 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
|
||||
|
||||
|
||||
def _destroy(icon):
|
||||
icon.set_visible(False)
|
||||
|
||||
|
||||
def _update_tray_icon():
|
||||
tooltip_lines = _generate_tooltip_lines()
|
||||
tooltip = '\n'.join(tooltip_lines).rstrip('\n')
|
||||
|
@ -265,11 +268,9 @@ except ImportError:
|
|||
tray_icon_name = _icons.TRAY_OKAY if _devices_info else _icons.TRAY_ATTENTION
|
||||
_icon.set_from_icon_name(tray_icon_name)
|
||||
|
||||
|
||||
def _update_menu_icon(image_widget, icon_name):
|
||||
image_widget.set_from_icon_name(icon_name, _MENU_ICON_SIZE)
|
||||
|
||||
|
||||
_icon_before_attention = None
|
||||
|
||||
def _blink(count):
|
||||
|
@ -290,10 +291,12 @@ except ImportError:
|
|||
_icon_before_attention = _icon.get_icon_name()
|
||||
GLib.idle_add(_blink, 9)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def _generate_tooltip_lines():
|
||||
if not _devices_info:
|
||||
yield '<b>%s</b>: ' % NAME + _("no receiver")
|
||||
|
@ -320,7 +323,8 @@ 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 ''
|
||||
|
@ -352,6 +356,7 @@ def _pick_device_with_lowest_battery():
|
|||
#
|
||||
#
|
||||
|
||||
|
||||
def _add_device(device):
|
||||
assert device
|
||||
assert device.receiver
|
||||
|
@ -377,17 +382,20 @@ 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
|
||||
|
@ -416,7 +424,8 @@ 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)
|
||||
|
||||
|
@ -461,6 +470,7 @@ def _update_menu_item(index, device):
|
|||
image_widget.set_sensitive(bool(device.online))
|
||||
_update_menu_icon(image_widget, icon_name)
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -476,6 +486,7 @@ _devices_info = []
|
|||
_menu = None
|
||||
_icon = None
|
||||
|
||||
|
||||
def init(_quit_handler):
|
||||
global _menu, _icon
|
||||
assert _menu is None
|
||||
|
@ -507,7 +518,8 @@ 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
|
||||
|
@ -522,7 +534,8 @@ 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
|
||||
|
||||
|
@ -541,7 +554,8 @@ 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()
|
||||
|
||||
|
|
|
@ -54,7 +54,14 @@ 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)
|
||||
|
@ -64,7 +71,13 @@ assert len(_COLUMN_TYPES) == len(_COLUMN)
|
|||
# create UI layout
|
||||
#
|
||||
|
||||
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:
|
||||
|
@ -163,8 +176,12 @@ def _create_buttons_box():
|
|||
bb = Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL)
|
||||
bb.set_layout(Gtk.ButtonBoxStyle.END)
|
||||
|
||||
bb._details = _new_button(None, 'dialog-information', _SMALL_BUTTON_ICON_SIZE,
|
||||
tooltip=_("Show Technical Details"), toggle=True, clicked=_update_details)
|
||||
bb._details = _new_button(None,
|
||||
'dialog-information',
|
||||
_SMALL_BUTTON_ICON_SIZE,
|
||||
tooltip=_("Show Technical Details"),
|
||||
toggle=True,
|
||||
clicked=_update_details)
|
||||
bb.add(bb._details)
|
||||
bb.set_child_secondary(bb._details, True)
|
||||
bb.set_child_non_homogeneous(bb._details, True)
|
||||
|
@ -177,7 +194,9 @@ 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):
|
||||
|
@ -188,7 +207,9 @@ 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
|
||||
|
@ -214,7 +235,8 @@ 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)
|
||||
|
@ -222,7 +244,8 @@ 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)
|
||||
|
@ -244,6 +267,7 @@ def _create_tree(model):
|
|||
|
||||
def _is_separator(model, item, _ignore=None):
|
||||
return model.get_value(item, _COLUMN.PATH) is None
|
||||
|
||||
tree.set_row_separator_func(_is_separator, None)
|
||||
|
||||
icon_cell_renderer = Gtk.CellRendererPixbuf()
|
||||
|
@ -265,16 +289,20 @@ 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
|
||||
|
@ -307,11 +335,15 @@ 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',
|
||||
icon_size=_SMALL_BUTTON_ICON_SIZE, clicked=_show_about_window)
|
||||
about_button = _new_button(_("About") + ' ' + NAME,
|
||||
'help-about',
|
||||
icon_size=_SMALL_BUTTON_ICON_SIZE,
|
||||
clicked=_show_about_window)
|
||||
bottom_buttons_box.add(about_button)
|
||||
|
||||
# solaar_version = Gtk.Label()
|
||||
|
@ -354,10 +386,12 @@ def _create(delete_action):
|
|||
|
||||
return window
|
||||
|
||||
|
||||
#
|
||||
# window updates
|
||||
#
|
||||
|
||||
|
||||
def _find_selected_device():
|
||||
selection = _tree.get_selection()
|
||||
model, item = selection.get_selected()
|
||||
|
@ -368,7 +402,8 @@ 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
|
||||
|
@ -401,7 +436,8 @@ 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)
|
||||
|
@ -416,7 +452,8 @@ 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:
|
||||
|
@ -434,18 +471,23 @@ 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, device)
|
||||
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
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def select(receiver_path, device_number=None):
|
||||
assert _window
|
||||
assert receiver_path is not None
|
||||
|
@ -457,7 +499,8 @@ 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):
|
||||
|
@ -483,10 +526,12 @@ def toggle(trigger=None):
|
|||
else:
|
||||
_window.present()
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def _update_details(button):
|
||||
assert button
|
||||
visible = button.get_active()
|
||||
|
@ -513,9 +558,14 @@ 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)
|
||||
|
@ -525,13 +575,17 @@ 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):
|
||||
|
@ -568,15 +622,26 @@ 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)
|
||||
|
||||
|
@ -600,8 +665,11 @@ 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:
|
||||
|
@ -632,16 +700,20 @@ 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")
|
||||
|
@ -654,17 +726,23 @@ def _update_device_panel(device, panel, buttons, full=False):
|
|||
not_secure = device.status.get(_K.LINK_ENCRYPTED) == 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.set_tooltip_text(_("The wireless link between this device and its receiver is not encrypted.\n"
|
||||
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"))
|
||||
|
@ -676,8 +754,10 @@ 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)
|
||||
|
@ -726,6 +806,7 @@ def _update_info_panel(device, full=False):
|
|||
if full:
|
||||
_update_details(_info._buttons._details)
|
||||
|
||||
|
||||
#
|
||||
# window layout:
|
||||
# +--------------------------------+
|
||||
|
@ -795,7 +876,9 @@ 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
|
||||
|
@ -811,8 +894,10 @@ 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)
|
||||
|
@ -826,11 +911,15 @@ 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)
|
||||
|
|
|
@ -28,6 +28,8 @@ del getLogger
|
|||
#
|
||||
|
||||
_suspend_callback = None
|
||||
|
||||
|
||||
def _suspend():
|
||||
if _suspend_callback:
|
||||
if _log.isEnabledFor(_INFO):
|
||||
|
@ -36,15 +38,19 @@ def _suspend():
|
|||
|
||||
|
||||
_resume_callback = None
|
||||
|
||||
|
||||
def _resume():
|
||||
if _resume_callback:
|
||||
if _log.isEnabledFor(_INFO):
|
||||
_log.info("received resume event")
|
||||
_resume_callback()
|
||||
|
||||
|
||||
def _suspend_or_resume(suspend):
|
||||
_suspend() if suspend else _resume()
|
||||
|
||||
|
||||
def watch(on_resume_callback=None, on_suspend_callback=None):
|
||||
"""Register callback for suspend/resume events.
|
||||
They are called only if the system DBus is running, and the UPower daemon is available."""
|
||||
|
@ -68,17 +74,24 @@ 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:
|
||||
# Either:
|
||||
|
|
17
setup.py
17
setup.py
|
@ -18,7 +18,9 @@ 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]
|
||||
|
@ -30,7 +32,8 @@ def _data_files():
|
|||
del _dirname
|
||||
|
||||
|
||||
setup(name=NAME.lower(),
|
||||
setup(
|
||||
name=NAME.lower(),
|
||||
version=__version__,
|
||||
description='Linux devices manager for the Logitech Unifying Receiver.',
|
||||
long_description='''
|
||||
|
@ -53,17 +56,19 @@ battery status, and show and modify some of the modifiable features of devices.
|
|||
'Operating System :: POSIX :: Linux',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
|
||||
platforms=['linux'],
|
||||
|
||||
# sudo apt install python-gi python3-gi \
|
||||
# gir1.2-gtk-3.0 gir1.2-notify-0.7 gir1.2-ayatanaappindicator3-0.1
|
||||
# os_requires=['gi.repository.GObject (>= 2.0)', 'gi.repository.Gtk (>= 3.0)'],
|
||||
|
||||
python_requires='>=3.2',
|
||||
install_requires=['pyudev (>= 0.13)', ],
|
||||
install_requires=[
|
||||
'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/*'),
|
||||
)
|
||||
|
|
|
@ -10,7 +10,8 @@ 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
|
||||
|
|
|
@ -16,8 +16,5 @@ 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)
|
||||
|
|
Loading…
Reference in New Issue