Clean up imports (#2537)

* Remove import as _ in solaar startup

Related #2273

* Remove import as _ in listener

Related #2273

* Remove import as _ in cli init

Related #2273

* Remove import as _ in gtk

Related #2273

* Remove import as _ in show

Related #2273

* Remove import as _ in tray

Related #2273

* Remove import as _ in profiles

Related #2273

* Remove import as _ in config

Related #2273

* Remove import as _ in config panel

Related #2273

* Remove import as _ in window

Related #2273

* Remove import as _ in pair

Related #2273

* Remove import as _ in pair window

Related #2273

* Remove import as _ in cli package

Related #2273

* Remove import as _ in ui package

Related #2273

* Remove commented out code

Related #2273

* Use constant for Logitech ID
This commit is contained in:
MattHag 2024-07-15 14:37:18 +02:00 committed by GitHub
parent d9d67ed738
commit 67829c5807
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 368 additions and 427 deletions

View File

@ -21,10 +21,9 @@
def init_paths(): def init_paths():
"""Make the app work in the source tree.""" """Make the app work in the source tree."""
import os.path as _path import os.path
import sys import sys
# Python 2 need conversion from utf-8 filenames
# Python 3 might have problems converting back to UTF-8 in case of Unicode surrogates # Python 3 might have problems converting back to UTF-8 in case of Unicode surrogates
try: try:
decoded_path = sys.path[0] decoded_path = sys.path[0]
@ -33,18 +32,17 @@ def init_paths():
except UnicodeError: except UnicodeError:
sys.stderr.write( sys.stderr.write(
"ERROR: Solaar cannot recognize encoding of filesystem path, " "ERROR: Solaar cannot recognize encoding of filesystem path, "
"this may happen because non UTF-8 characters in the pathname.\n" "this may happen due to non UTF-8 characters in the pathname.\n"
) )
sys.exit(1) sys.exit(1)
prefix = _path.normpath(_path.join(_path.realpath(decoded_path), "..")) root = os.path.join(os.path.realpath(decoded_path), "..")
src_lib = _path.join(prefix, "lib") prefix = os.path.normpath(root)
share_lib = _path.join(prefix, "share", "solaar", "lib") src_lib = os.path.join(prefix, "lib")
share_lib = os.path.join(prefix, "share", "solaar", "lib")
for location in src_lib, share_lib: for location in src_lib, share_lib:
init_py = _path.join(location, "solaar", "__init__.py") init_py = os.path.join(location, "solaar", "__init__.py")
# print ("sys.path[0]: checking", init_py) if os.path.exists(init_py):
if _path.exists(init_py):
# print ("sys.path[0]: found", location, "replacing", sys.path[0])
sys.path[0] = location sys.path[0] = location
break break

View File

@ -29,6 +29,8 @@ from threading import Thread
import hidapi import hidapi
LOGITECH_VENDOR_ID = 0x046D
interactive = os.isatty(0) interactive = os.isatty(0)
prompt = "?? Input: " if interactive else "" prompt = "?? Input: " if interactive else ""
start_time = time.time() start_time = time.time()
@ -126,8 +128,8 @@ def _validate_input(line, hidpp=False):
def _open(args): def _open(args):
def matchfn(bid, vid, pid, _a, _b): def matchfn(bid, vid, pid, _a, _b):
if vid == 0x046D: if vid == LOGITECH_VENDOR_ID:
return {"vid": 0x046D} return {"vid": vid}
device = args.device device = args.device
if args.hidpp and not device: if args.hidpp and not device:

View File

@ -153,8 +153,6 @@ def _match(action, device, filterfn):
return d_info return d_info
elif action == "remove": elif action == "remove":
# print (dict(device), dict(usb_device))
d_info = DeviceInfo( d_info = DeviceInfo(
path=device.device_node, path=device.device_node,
bus_id=None, bus_id=None,
@ -217,16 +215,6 @@ def find_paired_node_wpid(receiver_path, index):
def monitor_glib(callback, filterfn): def monitor_glib(callback, filterfn):
c = pyudev.Context() c = pyudev.Context()
# already existing devices
# for device in c.list_devices(subsystem='hidraw'):
# # print (device, dict(device), dict(device.attributes))
# for filter in device_filters:
# d_info = _match('add', device, *filter)
# if d_info:
# GLib.idle_add(callback, 'add', d_info)
# break
m = pyudev.Monitor.from_netlink(c) m = pyudev.Monitor.from_netlink(c)
m.filter_by(subsystem="hidraw") m.filter_by(subsystem="hidraw")
@ -248,15 +236,12 @@ def monitor_glib(callback, filterfn):
try: try:
# io_add_watch_full may not be available... # io_add_watch_full may not be available...
GLib.io_add_watch_full(m, GLib.PRIORITY_LOW, GLib.IO_IN, _process_udev_event, callback, filterfn) GLib.io_add_watch_full(m, GLib.PRIORITY_LOW, GLib.IO_IN, _process_udev_event, callback, filterfn)
# print ("did io_add_watch_full")
except AttributeError: except AttributeError:
try: try:
# and the priority parameter appeared later in the API # and the priority parameter appeared later in the API
GLib.io_add_watch(m, GLib.PRIORITY_LOW, GLib.IO_IN, _process_udev_event, callback, filterfn) GLib.io_add_watch(m, GLib.PRIORITY_LOW, GLib.IO_IN, _process_udev_event, callback, filterfn)
# print ("did io_add_watch with priority")
except Exception: except Exception:
GLib.io_add_watch(m, GLib.IO_IN, _process_udev_event, callback, filterfn) GLib.io_add_watch(m, GLib.IO_IN, _process_udev_event, callback, filterfn)
# print ("did io_add_watch")
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("Starting dbus monitoring") logger.debug("Starting dbus monitoring")

View File

@ -14,20 +14,18 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import pkgutil as _pkgutil import pkgutil
import subprocess as _subprocess import subprocess
import sys as _sys import sys
NAME = "Solaar" NAME = "Solaar"
try: try:
__version__ = ( __version__ = (
_subprocess.check_output(["git", "describe", "--always"], cwd=_sys.path[0], stderr=_subprocess.DEVNULL) subprocess.check_output(["git", "describe", "--always"], cwd=sys.path[0], stderr=subprocess.DEVNULL).strip().decode()
.strip()
.decode()
) )
except Exception: except Exception:
try: try:
__version__ = _pkgutil.get_data("solaar", "commit").strip().decode() __version__ = pkgutil.get_data("solaar", "commit").strip().decode()
except Exception: except Exception:
__version__ = _pkgutil.get_data("solaar", "version").strip().decode() __version__ = pkgutil.get_data("solaar", "version").strip().decode()

View File

@ -14,18 +14,17 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import argparse as _argparse import argparse
import logging import logging
import sys as _sys import sys
from importlib import import_module from importlib import import_module
from traceback import extract_tb from traceback import extract_tb
from traceback import format_exc from traceback import format_exc
import logitech_receiver.device as _device
import logitech_receiver.receiver as _receiver
from logitech_receiver import base from logitech_receiver import base
from logitech_receiver import device
from logitech_receiver import receiver
from solaar import NAME from solaar import NAME
@ -33,7 +32,7 @@ logger = logging.getLogger(__name__)
def _create_parser(): def _create_parser():
parser = _argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog=NAME.lower(), add_help=False, epilog=f"For details on individual actions, run `{NAME.lower()} <action> --help`." prog=NAME.lower(), add_help=False, epilog=f"For details on individual actions, run `{NAME.lower()} <action> --help`."
) )
subparsers = parser.add_subparsers(title="actions", help="optional action to perform") subparsers = parser.add_subparsers(title="actions", help="optional action to perform")
@ -107,14 +106,14 @@ def _receivers(dev_path=None):
if dev_path is not None and dev_path != dev_info.path: if dev_path is not None and dev_path != dev_info.path:
continue continue
try: try:
r = _receiver.ReceiverFactory.create_receiver(dev_info) r = receiver.ReceiverFactory.create_receiver(dev_info)
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("[%s] => %s", dev_info.path, r) logger.debug("[%s] => %s", dev_info.path, r)
if r: if r:
yield r yield r
except Exception as e: except Exception as e:
logger.exception("opening " + str(dev_info)) logger.exception("opening " + str(dev_info))
_sys.exit(f"{NAME.lower()}: error: {str(e)}") sys.exit(f"{NAME.lower()}: error: {str(e)}")
def _receivers_and_devices(dev_path=None): def _receivers_and_devices(dev_path=None):
@ -123,9 +122,9 @@ def _receivers_and_devices(dev_path=None):
continue continue
try: try:
if dev_info.isDevice: if dev_info.isDevice:
d = _device.DeviceFactory.create_device(base, dev_info) d = device.DeviceFactory.create_device(base, dev_info)
else: else:
d = _receiver.ReceiverFactory.create_receiver(dev_info) d = receiver.ReceiverFactory.create_receiver(dev_info)
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("[%s] => %s", dev_info.path, d) logger.debug("[%s] => %s", dev_info.path, d)
@ -133,7 +132,7 @@ def _receivers_and_devices(dev_path=None):
yield d yield d
except Exception as e: except Exception as e:
logger.exception("opening " + str(dev_info)) logger.exception("opening " + str(dev_info))
_sys.exit(f"{NAME.lower()}: error: {str(e)}") sys.exit(f"{NAME.lower()}: error: {str(e)}")
def _find_receiver(receivers, name): def _find_receiver(receivers, name):
@ -185,9 +184,6 @@ def _find_device(receivers, name):
break break
# raise Exception("no device found matching '%s'" % name)
def run(cli_args=None, hidraw_path=None): def run(cli_args=None, hidraw_path=None):
if cli_args: if cli_args:
action = cli_args[0] action = cli_args[0]
@ -197,9 +193,9 @@ def run(cli_args=None, hidraw_path=None):
# Python 3 has an undocumented 'feature' that breaks parsing empty args # Python 3 has an undocumented 'feature' that breaks parsing empty args
# http://bugs.python.org/issue16308 # http://bugs.python.org/issue16308
if "cmd" not in args: if "cmd" not in args:
_cli_parser.print_usage(_sys.stderr) _cli_parser.print_usage(sys.stderr)
_sys.stderr.write(f"{NAME.lower()}: error: too few arguments\n") sys.stderr.write(f"{NAME.lower()}: error: too few arguments\n")
_sys.exit(2) sys.exit(2)
action = args.action action = args.action
assert action in actions assert action in actions
@ -215,7 +211,7 @@ def run(cli_args=None, hidraw_path=None):
m = import_module("." + action, package=__name__) m = import_module("." + action, package=__name__)
m.run(c, args, _find_receiver, _find_device) m.run(c, args, _find_receiver, _find_device)
except AssertionError: except AssertionError:
tb_last = extract_tb(_sys.exc_info()[2])[-1] tb_last = extract_tb(sys.exc_info()[2])[-1]
_sys.exit(f"{NAME.lower()}: assertion failed: {tb_last[0]} line {int(tb_last[1])}") sys.exit(f"{NAME.lower()}: assertion failed: {tb_last[0]} line {int(tb_last[1])}")
except Exception: except Exception:
_sys.exit(f"{NAME.lower()}: error: {format_exc()}") sys.exit(f"{NAME.lower()}: error: {format_exc()}")

View File

@ -14,13 +14,13 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import yaml as _yaml import yaml
from logitech_receiver import settings as _settings from logitech_receiver import settings
from logitech_receiver import settings_templates as _settings_templates from logitech_receiver import settings_templates
from logitech_receiver.common import NamedInts as _NamedInts from logitech_receiver.common import NamedInts
from solaar import configuration as _configuration from solaar import configuration
def _print_setting(s, verbose=True): def _print_setting(s, verbose=True):
@ -28,9 +28,9 @@ def _print_setting(s, verbose=True):
if verbose: if verbose:
if s.description: if s.description:
print("#", s.description.replace("\n", " ")) print("#", s.description.replace("\n", " "))
if s.kind == _settings.KIND.toggle: if s.kind == settings.KIND.toggle:
print("# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~") print("# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~")
elif s.kind == _settings.KIND.choice: elif s.kind == settings.KIND.choice:
print( print(
"# possible values: one of [", "# possible values: one of [",
", ".join(str(v) for v in s.choices), ", ".join(str(v) for v in s.choices),
@ -51,7 +51,7 @@ def _print_setting_keyed(s, key, verbose=True):
if verbose: if verbose:
if s.description: if s.description:
print("#", s.description.replace("\n", " ")) print("#", s.description.replace("\n", " "))
if s.kind == _settings.KIND.multiple_toggle: if s.kind == settings.KIND.multiple_toggle:
k = next((k for k in s._labels if key == k), None) k = next((k for k in s._labels if key == k), None)
if k is None: if k is None:
print(s.name, "=? (key not found)") print(s.name, "=? (key not found)")
@ -62,7 +62,7 @@ def _print_setting_keyed(s, key, verbose=True):
print(s.name, "= ? (failed to read from device)") print(s.name, "= ? (failed to read from device)")
else: else:
print(s.name, s.val_to_string({k: value[str(int(k))]})) print(s.name, s.val_to_string({k: value[str(int(k))]}))
elif s.kind == _settings.KIND.map_choice: elif s.kind == settings.KIND.map_choice:
k = next((k for k in s.choices.keys() if key == k), None) k = next((k for k in s.choices.keys() if key == k), None)
if k is None: if k is None:
print(s.name, "=? (key not found)") print(s.name, "=? (key not found)")
@ -158,8 +158,7 @@ def run(receivers, args, find_receiver, find_device):
if not args.setting: # print all settings, so first set them all up if not args.setting: # print all settings, so first set them all up
if not dev.settings: if not dev.settings:
raise Exception(f"no settings for {dev.name}") raise Exception(f"no settings for {dev.name}")
_configuration.attach_to(dev) configuration.attach_to(dev)
# _settings.apply_all_settings(dev)
print(dev.name, f"({dev.codename}) [{dev.wpid}:{dev.serial}]") print(dev.name, f"({dev.codename}) [{dev.wpid}:{dev.serial}]")
for s in dev.settings: for s in dev.settings:
print("") print("")
@ -167,7 +166,7 @@ def run(receivers, args, find_receiver, find_device):
return return
setting_name = args.setting.lower() setting_name = args.setting.lower()
setting = _settings_templates.check_feature_setting(dev, setting_name) setting = settings_templates.check_feature_setting(dev, setting_name)
if not setting and dev.descriptor and dev.descriptor.settings: if not setting and dev.descriptor and dev.descriptor.settings:
for sclass in dev.descriptor.settings: for sclass in dev.descriptor.settings:
if sclass.register and sclass.name == setting_name: if sclass.register and sclass.name == setting_name:
@ -179,7 +178,6 @@ def run(receivers, args, find_receiver, find_device):
raise Exception(f"no setting '{args.setting}' for {dev.name}") raise Exception(f"no setting '{args.setting}' for {dev.name}")
if args.value_key is None: if args.value_key is None:
# setting.apply()
_print_setting(setting) _print_setting(setting)
return return
@ -210,32 +208,32 @@ def run(receivers, args, find_receiver, find_device):
if remote: if remote:
argl = ["config", dev.serial or dev.unitId, setting.name] argl = ["config", dev.serial or dev.unitId, setting.name]
argl.extend([a for a in [args.value_key, args.extra_subkey, args.extra2] if a is not None]) argl.extend([a for a in [args.value_key, args.extra_subkey, args.extra2] if a is not None])
application.run(_yaml.safe_dump(argl)) application.run(yaml.safe_dump(argl))
else: else:
if dev.persister and setting.persist: if dev.persister and setting.persist:
dev.persister[setting.name] = setting._value dev.persister[setting.name] = setting._value
def set(dev, setting, args, save): def set(dev, setting, args, save):
if setting.kind == _settings.KIND.toggle: if setting.kind == settings.KIND.toggle:
value = select_toggle(args.value_key, setting) value = select_toggle(args.value_key, setting)
args.value_key = value args.value_key = value
message = f"Setting {setting.name} of {dev.name} to {value}" message = f"Setting {setting.name} of {dev.name} to {value}"
result = setting.write(value, save=save) result = setting.write(value, save=save)
elif setting.kind == _settings.KIND.range: elif setting.kind == settings.KIND.range:
value = select_range(args.value_key, setting) value = select_range(args.value_key, setting)
args.value_key = value args.value_key = value
message = f"Setting {setting.name} of {dev.name} to {value}" message = f"Setting {setting.name} of {dev.name} to {value}"
result = setting.write(value, save=save) result = setting.write(value, save=save)
elif setting.kind == _settings.KIND.choice: elif setting.kind == settings.KIND.choice:
value = select_choice(args.value_key, setting.choices, setting, None) value = select_choice(args.value_key, setting.choices, setting, None)
args.value_key = int(value) args.value_key = int(value)
message = f"Setting {setting.name} of {dev.name} to {value}" message = f"Setting {setting.name} of {dev.name} to {value}"
result = setting.write(value, save=save) result = setting.write(value, save=save)
elif setting.kind == _settings.KIND.map_choice: elif setting.kind == settings.KIND.map_choice:
if args.extra_subkey is None: if args.extra_subkey is None:
_print_setting_keyed(setting, args.value_key) _print_setting_keyed(setting, args.value_key)
return (None, None, None) return (None, None, None)
@ -253,13 +251,13 @@ def set(dev, setting, args, save):
message = f"Setting {setting.name} of {dev.name} key {k!r} to {value!r}" message = f"Setting {setting.name} of {dev.name} key {k!r} to {value!r}"
result = setting.write_key_value(int(k), value, save=save) result = setting.write_key_value(int(k), value, save=save)
elif setting.kind == _settings.KIND.multiple_toggle: elif setting.kind == settings.KIND.multiple_toggle:
if args.extra_subkey is None: if args.extra_subkey is None:
_print_setting_keyed(setting, args.value_key) _print_setting_keyed(setting, args.value_key)
return (None, None, None) return (None, None, None)
key = args.value_key key = args.value_key
all_keys = getattr(setting, "choices_universe", None) all_keys = getattr(setting, "choices_universe", None)
ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, _NamedInts) else to_int(key) ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, NamedInts) else to_int(key)
k = next((k for k in setting._labels if key == k), None) k = next((k for k in setting._labels if key == k), None)
if k is None and ikey is not None: if k is None and ikey is not None:
k = next((k for k in setting._labels if ikey == k), None) k = next((k for k in setting._labels if ikey == k), None)
@ -272,12 +270,12 @@ def set(dev, setting, args, save):
message = f"Setting {setting.name} key {k!r} to {value!r}" message = f"Setting {setting.name} key {k!r} to {value!r}"
result = setting.write_key_value(str(int(k)), value, save=save) result = setting.write_key_value(str(int(k)), value, save=save)
elif setting.kind == _settings.KIND.multiple_range: elif setting.kind == settings.KIND.multiple_range:
if args.extra_subkey is None: if args.extra_subkey is None:
raise Exception(f"{setting.name}: setting needs both key and value to set") raise Exception(f"{setting.name}: setting needs both key and value to set")
key = args.value_key key = args.value_key
all_keys = getattr(setting, "choices_universe", None) all_keys = getattr(setting, "choices_universe", None)
ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, _NamedInts) else to_int(key) ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, NamedInts) else to_int(key)
if args.extra2 is None or to_int(args.extra2) is None: if args.extra2 is None or to_int(args.extra2) is None:
raise Exception(f"{setting.name}: setting needs an integer value, not {args.extra2}") raise Exception(f"{setting.name}: setting needs an integer value, not {args.extra2}")
if not setting._value: # ensure that there are values to look through if not setting._value: # ensure that there are values to look through

View File

@ -14,12 +14,12 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from time import time as _timestamp from time import time
from logitech_receiver import base as _base from logitech_receiver import base
from logitech_receiver import hidpp10 from logitech_receiver import hidpp10
from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp10_constants
from logitech_receiver import notifications as _notifications from logitech_receiver import notifications
_hidpp10 = hidpp10.Hidpp10() _hidpp10 = hidpp10.Hidpp10()
@ -39,8 +39,8 @@ def run(receivers, args, find_receiver, _ignore):
# check if it's necessary to set the notification flags # check if it's necessary to set the notification flags
old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0 old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0
if not (old_notification_flags & _hidpp10_constants.NOTIFICATION_FLAG.wireless): if not (old_notification_flags & hidpp10_constants.NOTIFICATION_FLAG.wireless):
_hidpp10.set_notification_flags(receiver, old_notification_flags | _hidpp10_constants.NOTIFICATION_FLAG.wireless) _hidpp10.set_notification_flags(receiver, old_notification_flags | hidpp10_constants.NOTIFICATION_FLAG.wireless)
# get all current devices # get all current devices
known_devices = [dev.number for dev in receiver] known_devices = [dev.number for dev in receiver]
@ -50,8 +50,8 @@ def run(receivers, args, find_receiver, _ignore):
nonlocal known_devices nonlocal known_devices
assert n assert n
if n.devnumber == 0xFF: if n.devnumber == 0xFF:
_notifications.process(receiver, n) notifications.process(receiver, n)
elif n.sub_id == 0x41 and len(n.data) == _base._SHORT_MESSAGE_SIZE - 4: elif n.sub_id == 0x41 and len(n.data) == base._SHORT_MESSAGE_SIZE - 4:
kd, known_devices = known_devices, None # only process one connection notification kd, known_devices = known_devices, None # only process one connection notification
if kd is not None: if kd is not None:
if n.devnumber not in kd: if n.devnumber not in kd:
@ -66,13 +66,13 @@ def run(receivers, args, find_receiver, _ignore):
if receiver.receiver_kind == "bolt": # Bolt receivers require authentication to pair a device if receiver.receiver_kind == "bolt": # Bolt receivers require authentication to pair a device
receiver.discover(timeout=timeout) receiver.discover(timeout=timeout)
print("Bolt Pairing: long-press the pairing key or button on your device (timing out in", timeout, "seconds).") print("Bolt Pairing: long-press the pairing key or button on your device (timing out in", timeout, "seconds).")
pairing_start = _timestamp() pairing_start = time()
patience = 5 # the discovering notification may come slightly later, so be patient patience = 5 # the discovering notification may come slightly later, so be patient
while receiver.pairing.discovering or _timestamp() - pairing_start < patience: while receiver.pairing.discovering or time() - pairing_start < patience:
if receiver.pairing.device_address and receiver.pairing.device_authentication and receiver.pairing.device_name: if receiver.pairing.device_address and receiver.pairing.device_authentication and receiver.pairing.device_name:
break break
n = _base.read(receiver.handle) n = base.read(receiver.handle)
n = _base.make_notification(*n) if n else None n = base.make_notification(*n) if n else None
if n: if n:
receiver.handle.notifications_hook(n) receiver.handle.notifications_hook(n)
address = receiver.pairing.device_address address = receiver.pairing.device_address
@ -83,15 +83,15 @@ def run(receivers, args, find_receiver, _ignore):
receiver.pair_device( receiver.pair_device(
address=address, address=address,
authentication=authentication, authentication=authentication,
entropy=20 if kind == _hidpp10_constants.DEVICE_KIND.keyboard else 10, entropy=20 if kind == hidpp10_constants.DEVICE_KIND.keyboard else 10,
) )
pairing_start = _timestamp() pairing_start = time()
patience = 5 # the discovering notification may come slightly later, so be patient patience = 5 # the discovering notification may come slightly later, so be patient
while receiver.pairing.lock_open or _timestamp() - pairing_start < patience: while receiver.pairing.lock_open or time() - pairing_start < patience:
if receiver.pairing.device_passkey: if receiver.pairing.device_passkey:
break break
n = _base.read(receiver.handle) n = base.read(receiver.handle)
n = _base.make_notification(*n) if n else None n = base.make_notification(*n) if n else None
if n: if n:
receiver.handle.notifications_hook(n) receiver.handle.notifications_hook(n)
if authentication & 0x01: if authentication & 0x01:
@ -102,24 +102,24 @@ def run(receivers, args, find_receiver, _ignore):
print(f"Bolt Pairing: press {passkey}") print(f"Bolt Pairing: press {passkey}")
print("and then press left and right buttons simultaneously") print("and then press left and right buttons simultaneously")
while receiver.pairing.lock_open: while receiver.pairing.lock_open:
n = _base.read(receiver.handle) n = base.read(receiver.handle)
n = _base.make_notification(*n) if n else None n = base.make_notification(*n) if n else None
if n: if n:
receiver.handle.notifications_hook(n) receiver.handle.notifications_hook(n)
else: else:
receiver.set_lock(False, timeout=timeout) receiver.set_lock(False, timeout=timeout)
print("Pairing: turn your new device on (timing out in", timeout, "seconds).") print("Pairing: turn your new device on (timing out in", timeout, "seconds).")
pairing_start = _timestamp() pairing_start = time()
patience = 5 # the lock-open notification may come slightly later, wait for it a bit patience = 5 # the lock-open notification may come slightly later, wait for it a bit
while receiver.pairing.lock_open or _timestamp() - pairing_start < patience: while receiver.pairing.lock_open or time() - pairing_start < patience:
n = _base.read(receiver.handle) n = base.read(receiver.handle)
if n: if n:
n = _base.make_notification(*n) n = base.make_notification(*n)
if n: if n:
receiver.handle.notifications_hook(n) receiver.handle.notifications_hook(n)
if not (old_notification_flags & _hidpp10_constants.NOTIFICATION_FLAG.wireless): if not (old_notification_flags & hidpp10_constants.NOTIFICATION_FLAG.wireless):
# only clear the flags if they weren't set before, otherwise a # only clear the flags if they weren't set before, otherwise a
# concurrently running Solaar app might stop working properly # concurrently running Solaar app might stop working properly
_hidpp10.set_notification_flags(receiver, old_notification_flags) _hidpp10.set_notification_flags(receiver, old_notification_flags)

View File

@ -14,10 +14,10 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from logitech_receiver import base as _base from logitech_receiver import base
from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp10_constants
from logitech_receiver.common import strhex as _strhex from logitech_receiver.common import strhex
from logitech_receiver.hidpp10_constants import Registers as Reg from logitech_receiver.hidpp10_constants import Registers
from solaar.cli.show import _print_device from solaar.cli.show import _print_device
from solaar.cli.show import _print_receiver from solaar.cli.show import _print_receiver
@ -44,37 +44,42 @@ def run(receivers, args, find_receiver, _ignore):
print("") print("")
print(" Register Dump") print(" Register Dump")
rgst = receiver.read_register(Reg.NOTIFICATIONS) rgst = receiver.read_register(Registers.NOTIFICATIONS)
print(" Notifications %#04x: %s" % (Reg.NOTIFICATIONS % 0x100, "0x" + _strhex(rgst) if rgst else "None")) print(" Notifications %#04x: %s" % (Registers.NOTIFICATIONS % 0x100, "0x" + strhex(rgst) if rgst else "None"))
rgst = receiver.read_register(Reg.RECEIVER_CONNECTION) rgst = receiver.read_register(Registers.RECEIVER_CONNECTION)
print(" Connection State %#04x: %s" % (Reg.RECEIVER_CONNECTION % 0x100, "0x" + _strhex(rgst) if rgst else "None")) print(
rgst = receiver.read_register(Reg.DEVICES_ACTIVITY) " Connection State %#04x: %s"
print(" Device Activity %#04x: %s" % (Reg.DEVICES_ACTIVITY % 0x100, "0x" + _strhex(rgst) if rgst else "None")) % (Registers.RECEIVER_CONNECTION % 0x100, "0x" + strhex(rgst) if rgst else "None")
)
rgst = receiver.read_register(Registers.DEVICES_ACTIVITY)
print(
" Device Activity %#04x: %s" % (Registers.DEVICES_ACTIVITY % 0x100, "0x" + strhex(rgst) if rgst else "None")
)
for sub_reg in range(0, 16): for sub_reg in range(0, 16):
rgst = receiver.read_register(Reg.RECEIVER_INFO, sub_reg) rgst = receiver.read_register(Registers.RECEIVER_INFO, sub_reg)
print( print(
" Pairing Register %#04x %#04x: %s" " Pairing Register %#04x %#04x: %s"
% (Reg.RECEIVER_INFO % 0x100, sub_reg, "0x" + _strhex(rgst) if rgst else "None") % (Registers.RECEIVER_INFO % 0x100, sub_reg, "0x" + strhex(rgst) if rgst else "None")
) )
for device in range(0, 7): for device in range(0, 7):
for sub_reg in [0x10, 0x20, 0x30, 0x50]: for sub_reg in [0x10, 0x20, 0x30, 0x50]:
rgst = receiver.read_register(Reg.RECEIVER_INFO, sub_reg + device) rgst = receiver.read_register(Registers.RECEIVER_INFO, sub_reg + device)
print( print(
" Pairing Register %#04x %#04x: %s" " Pairing Register %#04x %#04x: %s"
% (Reg.RECEIVER_INFO % 0x100, sub_reg + device, "0x" + _strhex(rgst) if rgst else "None") % (Registers.RECEIVER_INFO % 0x100, sub_reg + device, "0x" + strhex(rgst) if rgst else "None")
) )
rgst = receiver.read_register(Reg.RECEIVER_INFO, 0x40 + device) rgst = receiver.read_register(Registers.RECEIVER_INFO, 0x40 + device)
print( print(
" Pairing Name %#04x %#02x: %s" " Pairing Name %#04x %#02x: %s"
% (Reg.RECEIVER_INFO % 0x100, 0x40 + device, rgst[2 : 2 + ord(rgst[1:2])] if rgst else "None") % (Registers.RECEIVER_INFO % 0x100, 0x40 + device, rgst[2 : 2 + ord(rgst[1:2])] if rgst else "None")
) )
for part in range(1, 4): for part in range(1, 4):
rgst = receiver.read_register(Reg.RECEIVER_INFO, 0x60 + device, part) rgst = receiver.read_register(Registers.RECEIVER_INFO, 0x60 + device, part)
print( print(
" Pairing Name %#04x %#02x %#02x: %2d %s" " Pairing Name %#04x %#02x %#02x: %2d %s"
% ( % (
Reg.RECEIVER_INFO % 0x100, Registers.RECEIVER_INFO % 0x100,
0x60 + device, 0x60 + device,
part, part,
ord(rgst[2:3]) if rgst else 0, ord(rgst[2:3]) if rgst else 0,
@ -82,39 +87,39 @@ def run(receivers, args, find_receiver, _ignore):
) )
) )
for sub_reg in range(0, 5): for sub_reg in range(0, 5):
rgst = receiver.read_register(Reg.FIRMWARE, sub_reg) rgst = receiver.read_register(Registers.FIRMWARE, sub_reg)
print( print(
" Firmware %#04x %#04x: %s" " Firmware %#04x %#04x: %s"
% (Reg.FIRMWARE % 0x100, sub_reg, "0x" + _strhex(rgst) if rgst is not None else "None") % (Registers.FIRMWARE % 0x100, sub_reg, "0x" + strhex(rgst) if rgst is not None else "None")
) )
print("") print("")
for reg in range(0, 0xFF): for reg in range(0, 0xFF):
last = None last = None
for sub in range(0, 0xFF): for sub in range(0, 0xFF):
rgst = _base.request(receiver.handle, 0xFF, 0x8100 | reg, sub, return_error=True) rgst = base.request(receiver.handle, 0xFF, 0x8100 | reg, sub, return_error=True)
if isinstance(rgst, int) and rgst == _hidpp10_constants.ERROR.invalid_address: if isinstance(rgst, int) and rgst == hidpp10_constants.ERROR.invalid_address:
break break
elif isinstance(rgst, int) and rgst == _hidpp10_constants.ERROR.invalid_value: elif isinstance(rgst, int) and rgst == hidpp10_constants.ERROR.invalid_value:
continue continue
else: else:
if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst: if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst:
print( print(
" Register Short %#04x %#04x: %s" " Register Short %#04x %#04x: %s"
% (reg, sub, "0x" + _strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) % (reg, sub, "0x" + strhex(rgst) if isinstance(rgst, bytes) else str(rgst))
) )
last = rgst last = rgst
last = None last = None
for sub in range(0, 0xFF): for sub in range(0, 0xFF):
rgst = _base.request(receiver.handle, 0xFF, 0x8100 | (0x200 + reg), sub, return_error=True) rgst = base.request(receiver.handle, 0xFF, 0x8100 | (0x200 + reg), sub, return_error=True)
if isinstance(rgst, int) and rgst == _hidpp10_constants.ERROR.invalid_address: if isinstance(rgst, int) and rgst == hidpp10_constants.ERROR.invalid_address:
break break
elif isinstance(rgst, int) and rgst == _hidpp10_constants.ERROR.invalid_value: elif isinstance(rgst, int) and rgst == hidpp10_constants.ERROR.invalid_value:
continue continue
else: else:
if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst: if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst:
print( print(
" Register Long %#04x %#04x: %s" " Register Long %#04x %#04x: %s"
% (reg, sub, "0x" + _strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) % (reg, sub, "0x" + strhex(rgst) if isinstance(rgst, bytes) else str(rgst))
) )
last = rgst last = rgst

View File

@ -14,12 +14,12 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import traceback as _traceback import traceback
import yaml as _yaml import yaml
from logitech_receiver.hidpp20 import OnboardProfiles as _OnboardProfiles from logitech_receiver.hidpp20 import OnboardProfiles
from logitech_receiver.hidpp20 import OnboardProfilesVersion as _OnboardProfilesVersion from logitech_receiver.hidpp20 import OnboardProfilesVersion
def run(receivers, args, find_receiver, find_device): def run(receivers, args, find_receiver, find_device):
@ -42,15 +42,15 @@ def run(receivers, args, find_receiver, find_device):
print(f"Device {dev.name} is either offline or has no onboard profiles") print(f"Device {dev.name} is either offline or has no onboard profiles")
elif not profiles_file: elif not profiles_file:
print(f"#Dumping profiles from {dev.name}") print(f"#Dumping profiles from {dev.name}")
print(_yaml.dump(dev.profiles)) print(yaml.dump(dev.profiles))
else: else:
try: try:
with open(profiles_file, "r") as f: with open(profiles_file, "r") as f:
print(f"Reading profiles from {profiles_file}") print(f"Reading profiles from {profiles_file}")
profiles = _yaml.safe_load(f) profiles = yaml.safe_load(f)
if not isinstance(profiles, _OnboardProfiles): if not isinstance(profiles, OnboardProfiles):
print("Profiles file does not contain current onboard profiles") print("Profiles file does not contain current onboard profiles")
elif getattr(profiles, "version", None) != _OnboardProfilesVersion: elif getattr(profiles, "version", None) != OnboardProfilesVersion:
version = getattr(profiles, "version", None) version = getattr(profiles, "version", None)
print(f"Missing or incorrect profile version {version} in loaded profile") print(f"Missing or incorrect profile version {version} in loaded profile")
elif getattr(profiles, "name", None) != dev.name: elif getattr(profiles, "name", None) != dev.name:
@ -62,4 +62,4 @@ def run(receivers, args, find_receiver, find_device):
print(f"Wrote {written} sectors to {dev.name}") print(f"Wrote {written} sectors to {dev.name}")
except Exception as exc: except Exception as exc:
print("Profiles not written:", exc) print("Profiles not written:", exc)
print(_traceback.format_exc()) print(traceback.format_exc())

View File

@ -16,13 +16,14 @@
from logitech_receiver import exceptions from logitech_receiver import exceptions
from logitech_receiver import hidpp10 from logitech_receiver import hidpp10
from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp10_constants
from logitech_receiver import hidpp20 from logitech_receiver import hidpp20
from logitech_receiver import hidpp20_constants as _hidpp20_constants from logitech_receiver import hidpp20_constants
from logitech_receiver import receiver as _receiver from logitech_receiver import receiver
from logitech_receiver import settings_templates as _settings_templates from logitech_receiver import settings_templates
from logitech_receiver.common import NamedInt as _NamedInt from logitech_receiver.common import LOGITECH_VENDOR_ID
from logitech_receiver.common import strhex as _strhex from logitech_receiver.common import NamedInt
from logitech_receiver.common import strhex
from solaar import NAME from solaar import NAME
from solaar import __version__ from solaar import __version__
@ -36,7 +37,7 @@ def _print_receiver(receiver):
print(receiver.name) print(receiver.name)
print(" Device path :", receiver.path) print(" Device path :", receiver.path)
print(f" USB id : 046d:{receiver.product_id}") print(f" USB id : {LOGITECH_VENDOR_ID:04x}:{receiver.product_id}")
print(" Serial :", receiver.serial) print(" Serial :", receiver.serial)
pending = hidpp10.get_configuration_pending_flags(receiver) pending = hidpp10.get_configuration_pending_flags(receiver)
if pending: if pending:
@ -52,12 +53,12 @@ def _print_receiver(receiver):
notification_flags = _hidpp10.get_notification_flags(receiver) notification_flags = _hidpp10.get_notification_flags(receiver)
if notification_flags is not None: if notification_flags is not None:
if notification_flags: if notification_flags:
notification_names = _hidpp10_constants.NOTIFICATION_FLAG.flag_names(notification_flags) notification_names = hidpp10_constants.NOTIFICATION_FLAG.flag_names(notification_flags)
print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags:06X})") print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags:06X})")
else: else:
print(" Notifications: (none)") print(" Notifications: (none)")
activity = receiver.read_register(_hidpp10_constants.Registers.DEVICES_ACTIVITY) activity = receiver.read_register(hidpp10_constants.Registers.DEVICES_ACTIVITY)
if activity: if activity:
activity = [(d, ord(activity[d - 1 : d])) for d in range(1, receiver.max_devices)] activity = [(d, ord(activity[d - 1 : d])) for d in range(1, receiver.max_devices)]
activity_text = ", ".join(f"{int(d)}={int(a)}" for d, a in activity if a > 0) activity_text = ", ".join(f"{int(d)}={int(a)}" for d, a in activity if a > 0)
@ -67,7 +68,7 @@ def _print_receiver(receiver):
def _battery_text(level) -> str: def _battery_text(level) -> str:
if level is None: if level is None:
return "N/A" return "N/A"
elif isinstance(level, _NamedInt): elif isinstance(level, NamedInt):
return str(level) return str(level)
else: else:
return f"{int(level)}%" return f"{int(level)}%"
@ -103,7 +104,7 @@ def _print_device(dev, num=None):
if dev.wpid: if dev.wpid:
print(f" WPID : {dev.wpid}") print(f" WPID : {dev.wpid}")
if dev.product_id: if dev.product_id:
print(f" USB id : 046d:{dev.product_id}") print(f" USB id : {LOGITECH_VENDOR_ID:04x}:{dev.product_id}")
print(" Codename :", dev.codename) print(" Codename :", dev.codename)
print(" Kind :", dev.kind) print(" Kind :", dev.kind)
if dev.protocol: if dev.protocol:
@ -128,14 +129,14 @@ def _print_device(dev, num=None):
notification_flags = _hidpp10.get_notification_flags(dev) notification_flags = _hidpp10.get_notification_flags(dev)
if notification_flags is not None: if notification_flags is not None:
if notification_flags: if notification_flags:
notification_names = _hidpp10_constants.NOTIFICATION_FLAG.flag_names(notification_flags) notification_names = hidpp10_constants.NOTIFICATION_FLAG.flag_names(notification_flags)
print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags:06X}).") print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags:06X}).")
else: else:
print(" Notifications: (none).") print(" Notifications: (none).")
device_features = _hidpp10.get_device_features(dev) device_features = _hidpp10.get_device_features(dev)
if device_features is not None: if device_features is not None:
if device_features: if device_features:
device_features_names = _hidpp10_constants.DEVICE_FEATURES.flag_names(device_features) device_features_names = hidpp10_constants.DEVICE_FEATURES.flag_names(device_features)
print(f" Features: {', '.join(device_features_names)} (0x{device_features:06X})") print(f" Features: {', '.join(device_features_names)} (0x{device_features:06X})")
else: else:
print(" Features: (none)") print(" Features: (none)")
@ -143,15 +144,15 @@ def _print_device(dev, num=None):
if dev.online and dev.features: if dev.online and dev.features:
print(f" Supports {len(dev.features)} HID++ 2.0 features:") print(f" Supports {len(dev.features)} HID++ 2.0 features:")
dev_settings = [] dev_settings = []
_settings_templates.check_feature_settings(dev, dev_settings) settings_templates.check_feature_settings(dev, dev_settings)
for feature, index in dev.features.enumerate(): for feature, index in dev.features.enumerate():
flags = dev.request(0x0000, feature.bytes(2)) flags = dev.request(0x0000, feature.bytes(2))
flags = 0 if flags is None else ord(flags[1:2]) flags = 0 if flags is None else ord(flags[1:2])
flags = _hidpp20_constants.FEATURE_FLAG.flag_names(flags) flags = hidpp20_constants.FEATURE_FLAG.flag_names(flags)
version = dev.features.get_feature_version(int(feature)) version = dev.features.get_feature_version(int(feature))
version = version if version else 0 version = version if version else 0
print(" %2d: %-22s {%04X} V%s %s " % (index, feature, feature, version, ", ".join(flags))) print(" %2d: %-22s {%04X} V%s %s " % (index, feature, feature, version, ", ".join(flags)))
if feature == _hidpp20_constants.FEATURE.HIRES_WHEEL: if feature == hidpp20_constants.FEATURE.HIRES_WHEEL:
wheel = _hidpp20.get_hires_wheel(dev) wheel = _hidpp20.get_hires_wheel(dev)
if wheel: if wheel:
multi, has_invert, has_switch, inv, res, target, ratchet = wheel multi, has_invert, has_switch, inv, res, target, ratchet = wheel
@ -168,7 +169,7 @@ def _print_device(dev, num=None):
print(" HID++ notification") print(" HID++ notification")
else: else:
print(" HID notification") print(" HID notification")
elif feature == _hidpp20_constants.FEATURE.MOUSE_POINTER: elif feature == hidpp20_constants.FEATURE.MOUSE_POINTER:
mouse_pointer = _hidpp20.get_mouse_pointer_info(dev) mouse_pointer = _hidpp20.get_mouse_pointer_info(dev)
if mouse_pointer: if mouse_pointer:
print(f" DPI: {mouse_pointer['dpi']}") print(f" DPI: {mouse_pointer['dpi']}")
@ -181,13 +182,13 @@ def _print_device(dev, num=None):
print(" Provide vertical tuning, trackball") print(" Provide vertical tuning, trackball")
else: else:
print(" No vertical tuning, standard mice") print(" No vertical tuning, standard mice")
elif feature == _hidpp20_constants.FEATURE.VERTICAL_SCROLLING: elif feature == hidpp20_constants.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: if vertical_scrolling_info:
print(f" Roller type: {vertical_scrolling_info['roller']}") print(f" Roller type: {vertical_scrolling_info['roller']}")
print(f" Ratchet per turn: {vertical_scrolling_info['ratchet']}") print(f" Ratchet per turn: {vertical_scrolling_info['ratchet']}")
print(f" Scroll lines: {vertical_scrolling_info['lines']}") print(f" Scroll lines: {vertical_scrolling_info['lines']}")
elif feature == _hidpp20_constants.FEATURE.HI_RES_SCROLLING: elif feature == hidpp20_constants.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: if scrolling_mode:
print(" Hi-res scrolling enabled") print(" Hi-res scrolling enabled")
@ -195,49 +196,49 @@ def _print_device(dev, num=None):
print(" Hi-res scrolling disabled") print(" Hi-res scrolling disabled")
if scrolling_resolution: if scrolling_resolution:
print(f" Hi-res scrolling multiplier: {scrolling_resolution}") print(f" Hi-res scrolling multiplier: {scrolling_resolution}")
elif feature == _hidpp20_constants.FEATURE.POINTER_SPEED: elif feature == hidpp20_constants.FEATURE.POINTER_SPEED:
pointer_speed = _hidpp20.get_pointer_speed_info(dev) pointer_speed = _hidpp20.get_pointer_speed_info(dev)
if pointer_speed: if pointer_speed:
print(f" Pointer Speed: {pointer_speed}") print(f" Pointer Speed: {pointer_speed}")
elif feature == _hidpp20_constants.FEATURE.LOWRES_WHEEL: elif feature == hidpp20_constants.FEATURE.LOWRES_WHEEL:
wheel_status = _hidpp20.get_lowres_wheel_status(dev) wheel_status = _hidpp20.get_lowres_wheel_status(dev)
if wheel_status: if wheel_status:
print(f" Wheel Reports: {wheel_status}") print(f" Wheel Reports: {wheel_status}")
elif feature == _hidpp20_constants.FEATURE.NEW_FN_INVERSION: elif feature == hidpp20_constants.FEATURE.NEW_FN_INVERSION:
inversion = _hidpp20.get_new_fn_inversion(dev) inversion = _hidpp20.get_new_fn_inversion(dev)
if inversion: if inversion:
inverted, default_inverted = inversion inverted, default_inverted = inversion
print(" Fn-swap:", "enabled" if inverted else "disabled") print(" Fn-swap:", "enabled" if inverted else "disabled")
print(" Fn-swap default:", "enabled" if default_inverted else "disabled") print(" Fn-swap default:", "enabled" if default_inverted else "disabled")
elif feature == _hidpp20_constants.FEATURE.HOSTS_INFO: elif feature == hidpp20_constants.FEATURE.HOSTS_INFO:
host_names = _hidpp20.get_host_names(dev) host_names = _hidpp20.get_host_names(dev)
for host, (paired, name) in host_names.items(): for host, (paired, name) in host_names.items():
print(f" Host {host} ({'paired' if paired else 'unpaired'}): {name}") print(f" Host {host} ({'paired' if paired else 'unpaired'}): {name}")
elif feature == _hidpp20_constants.FEATURE.DEVICE_NAME: elif feature == hidpp20_constants.FEATURE.DEVICE_NAME:
print(f" Name: {_hidpp20.get_name(dev)}") print(f" Name: {_hidpp20.get_name(dev)}")
print(f" Kind: {_hidpp20.get_kind(dev)}") print(f" Kind: {_hidpp20.get_kind(dev)}")
elif feature == _hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME: elif feature == hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME:
print(f" Friendly Name: {_hidpp20.get_friendly_name(dev)}") print(f" Friendly Name: {_hidpp20.get_friendly_name(dev)}")
elif feature == _hidpp20_constants.FEATURE.DEVICE_FW_VERSION: elif feature == hidpp20_constants.FEATURE.DEVICE_FW_VERSION:
for fw in _hidpp20.get_firmware(dev): for fw in _hidpp20.get_firmware(dev):
extras = _strhex(fw.extras) if fw.extras else "" extras = strhex(fw.extras) if fw.extras else ""
print(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}") print(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}")
ids = _hidpp20.get_ids(dev) ids = _hidpp20.get_ids(dev)
if ids: if ids:
unitId, modelId, tid_map = ids unitId, modelId, tid_map = ids
print(f" Unit ID: {unitId} Model ID: {modelId} Transport IDs: {tid_map}") print(f" Unit ID: {unitId} Model ID: {modelId} Transport IDs: {tid_map}")
elif ( elif (
feature == _hidpp20_constants.FEATURE.REPORT_RATE feature == hidpp20_constants.FEATURE.REPORT_RATE
or feature == _hidpp20_constants.FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE or feature == hidpp20_constants.FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE
): ):
print(f" Report Rate: {_hidpp20.get_polling_rate(dev)}") print(f" Report Rate: {_hidpp20.get_polling_rate(dev)}")
elif feature == _hidpp20_constants.FEATURE.CONFIG_CHANGE: elif feature == hidpp20_constants.FEATURE.CONFIG_CHANGE:
response = dev.feature_request(_hidpp20_constants.FEATURE.CONFIG_CHANGE, 0x00) response = dev.feature_request(hidpp20_constants.FEATURE.CONFIG_CHANGE, 0x00)
print(f" Configuration: {response.hex()}") print(f" Configuration: {response.hex()}")
elif feature == _hidpp20_constants.FEATURE.REMAINING_PAIRING: elif feature == hidpp20_constants.FEATURE.REMAINING_PAIRING:
print(f" Remaining Pairings: {int(_hidpp20.get_remaining_pairing(dev))}") print(f" Remaining Pairings: {int(_hidpp20.get_remaining_pairing(dev))}")
elif feature == _hidpp20_constants.FEATURE.ONBOARD_PROFILES: elif feature == hidpp20_constants.FEATURE.ONBOARD_PROFILES:
if _hidpp20.get_onboard_mode(dev) == _hidpp20_constants.ONBOARD_MODES.MODE_HOST: if _hidpp20.get_onboard_mode(dev) == hidpp20_constants.ONBOARD_MODES.MODE_HOST:
mode = "Host" mode = "Host"
else: else:
mode = "On-Board" mode = "On-Board"
@ -266,9 +267,9 @@ def _print_device(dev, num=None):
print(f" Has {len(dev.keys)} reprogrammable keys:") print(f" Has {len(dev.keys)} reprogrammable keys:")
for k in dev.keys: for k in dev.keys:
# TODO: add here additional variants for other REPROG_CONTROLS # TODO: add here additional variants for other REPROG_CONTROLS
if dev.keys.keyversion == _hidpp20_constants.FEATURE.REPROG_CONTROLS_V2: if dev.keys.keyversion == hidpp20_constants.FEATURE.REPROG_CONTROLS_V2:
print(" %2d: %-26s => %-27s %s" % (k.index, k.key, k.default_task, ", ".join(k.flags))) print(" %2d: %-26s => %-27s %s" % (k.index, k.key, k.default_task, ", ".join(k.flags)))
if dev.keys.keyversion == _hidpp20_constants.FEATURE.REPROG_CONTROLS_V4: if dev.keys.keyversion == hidpp20_constants.FEATURE.REPROG_CONTROLS_V4:
print(" %2d: %-26s, default: %-27s => %-26s" % (k.index, k.key, k.default_task, k.mapped_to)) print(" %2d: %-26s, default: %-27s => %-26s" % (k.index, k.key, k.default_task, k.mapped_to))
gmask_fmt = ",".join(k.group_mask) gmask_fmt = ",".join(k.group_mask)
gmask_fmt = gmask_fmt if gmask_fmt else "empty" gmask_fmt = gmask_fmt if gmask_fmt else "empty"
@ -311,7 +312,7 @@ def run(devices, args, find_receiver, find_device):
if device_name == "all": if device_name == "all":
for d in devices: for d in devices:
if isinstance(d, _receiver.Receiver): if isinstance(d, receiver.Receiver):
_print_receiver(d) _print_receiver(d)
count = d.count() count = d.count()
if count: if count:

View File

@ -15,9 +15,9 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import json as _json import json
import logging import logging
import os as _os import os
import threading import threading
import yaml import yaml
@ -28,9 +28,9 @@ from solaar import __version__
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_XDG_CONFIG_HOME = _os.environ.get("XDG_CONFIG_HOME") or _os.path.expanduser(_os.path.join("~", ".config")) _XDG_CONFIG_HOME = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser(os.path.join("~", ".config"))
_yaml_file_path = _os.path.join(_XDG_CONFIG_HOME, "solaar", "config.yaml") _yaml_file_path = os.path.join(_XDG_CONFIG_HOME, "solaar", "config.yaml")
_json_file_path = _os.path.join(_XDG_CONFIG_HOME, "solaar", "config.json") _json_file_path = os.path.join(_XDG_CONFIG_HOME, "solaar", "config.json")
_KEY_VERSION = "_version" _KEY_VERSION = "_version"
_KEY_NAME = "_NAME" _KEY_NAME = "_NAME"
@ -45,18 +45,18 @@ _config = []
def _load(): def _load():
loaded_config = [] loaded_config = []
if _os.path.isfile(_yaml_file_path): if os.path.isfile(_yaml_file_path):
path = _yaml_file_path path = _yaml_file_path
try: try:
with open(_yaml_file_path) as config_file: with open(_yaml_file_path) as config_file:
loaded_config = yaml.safe_load(config_file) loaded_config = yaml.safe_load(config_file)
except Exception as e: except Exception as e:
logger.error("failed to load from %s: %s", _yaml_file_path, e) logger.error("failed to load from %s: %s", _yaml_file_path, e)
elif _os.path.isfile(_json_file_path): elif os.path.isfile(_json_file_path):
path = _json_file_path path = _json_file_path
try: try:
with open(_json_file_path) as config_file: with open(_json_file_path) as config_file:
loaded_config = _json.load(config_file) loaded_config = json.load(config_file)
except Exception as e: except Exception as e:
logger.error("failed to load from %s: %s", _json_file_path, e) logger.error("failed to load from %s: %s", _json_file_path, e)
loaded_config = _convert_json(loaded_config) loaded_config = _convert_json(loaded_config)
@ -129,10 +129,10 @@ def save(defer=False):
global save_timer global save_timer
if not _config: if not _config:
return return
dirname = _os.path.dirname(_yaml_file_path) dirname = os.path.dirname(_yaml_file_path)
if not _os.path.isdir(dirname): if not os.path.isdir(dirname):
try: try:
_os.makedirs(dirname) os.makedirs(dirname)
except Exception: except Exception:
logger.error("failed to create %s", dirname) logger.error("failed to create %s", dirname)
return return

View File

@ -29,15 +29,13 @@ import tempfile
from traceback import format_exc from traceback import format_exc
import solaar.cli as _cli
import solaar.configuration as _configuration
import solaar.dbus as _dbus
import solaar.listener as _listener
import solaar.ui as _ui
import solaar.ui.common as _common
from solaar import NAME from solaar import NAME
from solaar import __version__ from solaar import __version__
from solaar import cli
from solaar import configuration
from solaar import dbus
from solaar import listener
from solaar import ui
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -87,12 +85,12 @@ def _parse_arguments():
arg_parser.add_argument("--tray-icon-size", type=int, help="explicit size for tray icons") arg_parser.add_argument("--tray-icon-size", type=int, help="explicit size for tray icons")
arg_parser.add_argument("-V", "--version", action="version", version="%(prog)s " + __version__) arg_parser.add_argument("-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("--help-actions", action="store_true", help="print help for the optional actions")
arg_parser.add_argument("action", nargs=argparse.REMAINDER, choices=_cli.actions, help="optional actions to perform") arg_parser.add_argument("action", nargs=argparse.REMAINDER, choices=cli.actions, help="optional actions to perform")
args = arg_parser.parse_args() args = arg_parser.parse_args()
if args.help_actions: if args.help_actions:
_cli.print_help() cli.print_help()
return return
if args.window is None: if args.window is None:
@ -146,7 +144,7 @@ def main():
return return
if args.action: if args.action:
# if any argument, run comandline and exit # if any argument, run comandline and exit
return _cli.run(args.action, args.hidraw_path) return cli.run(args.action, args.hidraw_path)
gi = _require("gi", "python3-gi (in Ubuntu) or python3-gobject (in Fedora)") gi = _require("gi", "python3-gi (in Ubuntu) or python3-gobject (in Fedora)")
_require("gi.repository.Gtk", "gir1.2-gtk-3.0", gi, "Gtk", "3.0") _require("gi.repository.Gtk", "gir1.2-gtk-3.0", gi, "Gtk", "3.0")
@ -166,17 +164,17 @@ def main():
logger.warning("Solaar udev file not found in expected location") logger.warning("Solaar udev file not found in expected location")
logger.warning("See https://pwr-solaar.github.io/Solaar/installation for more information") logger.warning("See https://pwr-solaar.github.io/Solaar/installation for more information")
try: try:
_listener.setup_scanner(_ui.status_changed, _ui.setting_changed, _common.error_dialog) listener.setup_scanner(ui.status_changed, ui.setting_changed, ui.common.error_dialog)
if args.restart_on_wake_up: if args.restart_on_wake_up:
_dbus.watch_suspend_resume(_listener.start_all, _listener.stop_all) dbus.watch_suspend_resume(listener.start_all, listener.stop_all)
else: else:
_dbus.watch_suspend_resume(lambda: _listener.ping_all(True)) dbus.watch_suspend_resume(lambda: listener.ping_all(True))
_configuration.defer_saves = True # allow configuration saves to be deferred configuration.defer_saves = True # allow configuration saves to be deferred
# main UI event loop # 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: except Exception:
sys.exit(f"{NAME.lower()}: error: {format_exc()}") sys.exit(f"{NAME.lower()}: error: {format_exc()}")

View File

@ -15,7 +15,7 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import errno as _errno import errno
import logging import logging
import subprocess import subprocess
import time import time
@ -24,15 +24,13 @@ from collections import namedtuple
from functools import partial from functools import partial
import gi import gi
import logitech_receiver.device as _device import logitech_receiver
import logitech_receiver.receiver as _receiver
from logitech_receiver import base as _base from logitech_receiver import base
from logitech_receiver import exceptions from logitech_receiver import exceptions
from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp10_constants
from logitech_receiver import listener as _listener from logitech_receiver import listener
from logitech_receiver import notifications as _notifications from logitech_receiver import notifications
from logitech_receiver.hidpp10_constants import Registers
from . import configuration from . import configuration
from . import dbus from . import dbus
@ -43,9 +41,6 @@ from gi.repository import GLib # NOQA: E402 # isort:skip
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_IR = _hidpp10_constants.INFO_SUBREGISTERS
_GHOST_DEVICE = namedtuple("_GHOST_DEVICE", ("receiver", "number", "name", "kind", "online")) _GHOST_DEVICE = namedtuple("_GHOST_DEVICE", ("receiver", "number", "name", "kind", "online"))
_GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__bool__ = lambda self: False
_GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__
@ -55,7 +50,7 @@ def _ghost(device):
return _GHOST_DEVICE(receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, online=False) return _GHOST_DEVICE(receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, online=False)
class SolaarListener(_listener.EventsListener): class SolaarListener(listener.EventsListener):
"""Keeps the status of a Receiver or Device (member name is receiver but it can also be a device).""" """Keeps the status of a Receiver or Device (member name is receiver but it can also be a device)."""
def __init__(self, receiver, status_changed_callback): def __init__(self, receiver, status_changed_callback):
@ -69,7 +64,7 @@ class SolaarListener(_listener.EventsListener):
logger.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle) logger.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle)
nfs = self.receiver.enable_connection_notifications() nfs = self.receiver.enable_connection_notifications()
if logger.isEnabledFor(logging.WARNING): if logger.isEnabledFor(logging.WARNING):
if not self.receiver.isDevice and not ((nfs if nfs else 0) & _hidpp10_constants.NOTIFICATION_FLAG.wireless): if not self.receiver.isDevice and not ((nfs if nfs else 0) & hidpp10_constants.NOTIFICATION_FLAG.wireless):
logger.warning( logger.warning(
"Receiver on %s might not support connection notifications, GUI might not show its devices", "Receiver on %s might not support connection notifications, GUI might not show its devices",
self.receiver.path, self.receiver.path,
@ -142,11 +137,9 @@ class SolaarListener(_listener.EventsListener):
def _notifications_handler(self, n): def _notifications_handler(self, n):
assert self.receiver assert self.receiver
# if logger.isEnabledFor(logging.DEBUG):
# logger.debug("%s: handling %s", self.receiver, n)
if n.devnumber == 0xFF: if n.devnumber == 0xFF:
# a receiver notification # a receiver notification
_notifications.process(self.receiver, n) notifications.process(self.receiver, n)
return return
# a notification that came in to the device listener - strange, but nothing needs to be done here # a notification that came in to the device listener - strange, but nothing needs to be done here
@ -156,7 +149,7 @@ class SolaarListener(_listener.EventsListener):
return return
# DJ pairing notification - ignore - hid++ 1.0 pairing notification is all that is needed # DJ pairing notification - ignore - hid++ 1.0 pairing notification is all that is needed
if n.sub_id == 0x41 and n.report_id == _base.DJ_MESSAGE_ID: if n.sub_id == 0x41 and n.report_id == base.DJ_MESSAGE_ID:
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
logger.info("ignoring DJ pairing notification %s", n) logger.info("ignoring DJ pairing notification %s", n)
return return
@ -170,7 +163,7 @@ class SolaarListener(_listener.EventsListener):
# FIXME: hacky fix for kernel/hardware race condition # FIXME: hacky fix for kernel/hardware race condition
# If the device was just turned on or woken up from sleep, it may not be ready to receive commands. # If the device was just turned on or woken up from sleep, it may not be ready to receive commands.
# The "payload" bit of the wireless tatus notification seems to tell us this. If this is the case, we # The "payload" bit of the wireless status notification seems to tell us this. If this is the case, we
# must wait a short amount of time to avoid causing a broken pipe error. # must wait a short amount of time to avoid causing a broken pipe error.
device_ready = not bool(ord(n.data[0:1]) & 0x80) or n.sub_id != 0x41 device_ready = not bool(ord(n.data[0:1]) & 0x80) or n.sub_id != 0x41
if not device_ready: if not device_ready:
@ -183,7 +176,13 @@ class SolaarListener(_listener.EventsListener):
if not already_known: if not already_known:
if n.address == 0x0A and not self.receiver.receiver_kind == "bolt": if n.address == 0x0A and not self.receiver.receiver_kind == "bolt":
# some Nanos send a notification even if no new pairing - check that there really is a device there # some Nanos send a notification even if no new pairing - check that there really is a device there
if self.receiver.read_register(Registers.RECEIVER_INFO, _IR.pairing_information + n.devnumber - 1) is None: if (
self.receiver.read_register(
hidpp10_constants.Registers.RECEIVER_INFO,
hidpp10_constants.INFO_SUBREGISTERS.pairing_information + n.devnumber - 1,
)
is None
):
return return
dev = self.receiver.register_new_device(n.devnumber, n) dev = self.receiver.register_new_device(n.devnumber, n)
elif self.receiver.pairing.lock_open and self.receiver.re_pairs and not ord(n.data[0:1]) & 0x40: elif self.receiver.pairing.lock_open and self.receiver.re_pairs and not ord(n.data[0:1]) & 0x40:
@ -212,7 +211,7 @@ class SolaarListener(_listener.EventsListener):
# the receiver changed status as well # the receiver changed status as well
self._status_changed(self.receiver) self._status_changed(self.receiver)
_notifications.process(dev, n) notifications.process(dev, n)
if self.receiver.pairing.lock_open and not already_known: if self.receiver.pairing.lock_open and not already_known:
# this should be the first notification after a device was paired # this should be the first notification after a device was paired
@ -256,17 +255,17 @@ def _start(device_info):
assert _status_callback and _setting_callback assert _status_callback and _setting_callback
isDevice = device_info.isDevice isDevice = device_info.isDevice
if not isDevice: if not isDevice:
receiver = _receiver.ReceiverFactory.create_receiver(device_info, _setting_callback) receiver_ = logitech_receiver.receiver.ReceiverFactory.create_receiver(device_info, _setting_callback)
else: else:
receiver = _device.DeviceFactory.create_device(_base, device_info, _setting_callback) receiver_ = logitech_receiver.device.DeviceFactory.create_device(base, device_info, _setting_callback)
if receiver: if receiver_:
configuration.attach_to(receiver) configuration.attach_to(receiver_)
if receiver.bluetooth and receiver.hid_serial: if receiver_.bluetooth and receiver_.hid_serial:
dbus.watch_bluez_connect(receiver.hid_serial, partial(_process_bluez_dbus, receiver)) dbus.watch_bluez_connect(receiver_.hid_serial, partial(_process_bluez_dbus, receiver_))
receiver.cleanups.append(_cleanup_bluez_dbus) receiver_.cleanups.append(_cleanup_bluez_dbus)
if receiver: if receiver_:
rl = SolaarListener(receiver, _status_callback) rl = SolaarListener(receiver_, _status_callback)
rl.start() rl.start()
_all_listeners[device_info.path] = rl _all_listeners[device_info.path] = rl
return rl return rl
@ -278,7 +277,7 @@ def start_all():
stop_all() # just in case this it called twice in a row... stop_all() # just in case this it called twice in a row...
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
logger.info("starting receiver listening threads") logger.info("starting receiver listening threads")
for device_info in _base.receivers_and_devices(): for device_info in base.receivers_and_devices():
_process_receiver_event("add", device_info) _process_receiver_event("add", device_info)
@ -333,14 +332,14 @@ def setup_scanner(status_changed_callback, setting_changed_callback, error_callb
_status_callback = status_changed_callback _status_callback = status_changed_callback
_setting_callback = setting_changed_callback _setting_callback = setting_changed_callback
_error_callback = error_callback _error_callback = error_callback
_base.notify_on_receivers_glib(_process_receiver_event) base.notify_on_receivers_glib(_process_receiver_event)
def _process_add(device_info, retry): def _process_add(device_info, retry):
try: try:
_start(device_info) _start(device_info)
except OSError as e: except OSError as e:
if e.errno == _errno.EACCES: if e.errno == errno.EACCES:
try: try:
output = subprocess.check_output(["/usr/bin/getfacl", "-p", device_info.path], text=True) output = subprocess.check_output(["/usr/bin/getfacl", "-p", device_info.path], text=True)
if logger.isEnabledFor(logging.WARNING): if logger.isEnabledFor(logging.WARNING):

View File

@ -18,21 +18,21 @@
import logging import logging
from threading import Thread as _Thread from threading import Thread
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
try: try:
from Queue import Queue as _Queue from Queue import Queue
except ImportError: except ImportError:
from queue import Queue as _Queue from queue import Queue
class TaskRunner(_Thread): class TaskRunner(Thread):
def __init__(self, name): def __init__(self, name):
super().__init__(name=name) super().__init__(name=name)
self.daemon = True self.daemon = True
self.queue = _Queue(16) self.queue = Queue(16)
self.alive = False self.alive = False
def __call__(self, function, *args, **kwargs): def __call__(self, function, *args, **kwargs):

View File

@ -18,7 +18,7 @@
import logging import logging
import gi import gi
import yaml as _yaml import yaml
from logitech_receiver.common import Alert from logitech_receiver.common import Alert
@ -65,7 +65,7 @@ def _activate(app):
def _command_line(app, command_line): def _command_line(app, command_line):
args = command_line.get_arguments() args = command_line.get_arguments()
args = _yaml.safe_load("".join(args)) if args else args args = yaml.safe_load("".join(args)) if args else args
if not args: if not args:
_activate(app) _activate(app)
elif args[0] == "config": # config call from remote instance elif args[0] == "config": # config call from remote instance

View File

@ -19,7 +19,7 @@ import logging
import gi import gi
from solaar.i18n import _ from solaar.i18n import _
from solaar.tasks import TaskRunner as _TaskRunner from solaar.tasks import TaskRunner
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import GLib # NOQA: E402 from gi.repository import GLib # NOQA: E402
@ -74,7 +74,7 @@ _task_runner = None
def start_async(): def start_async():
global _task_runner global _task_runner
_task_runner = _TaskRunner("AsyncUI") _task_runner = TaskRunner("AsyncUI")
_task_runner.start() _task_runner.start()

View File

@ -18,18 +18,17 @@
import logging import logging
import traceback import traceback
from threading import Timer as _Timer from threading import Timer
import gi import gi
from logitech_receiver.hidpp20 import LEDEffectSetting as _LEDEffectSetting from logitech_receiver import hidpp20
from logitech_receiver.settings import KIND as _SETTING_KIND from logitech_receiver import settings
from logitech_receiver.settings import SENSITIVITY_IGNORE as _SENSITIVITY_IGNORE
from solaar.i18n import _ from solaar.i18n import _
from solaar.i18n import ngettext from solaar.i18n import ngettext
from .common import ui_async as _ui_async from .common import ui_async
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gdk # NOQA: E402 from gi.repository import Gdk # NOQA: E402
@ -49,7 +48,7 @@ def _read_async(setting, force_read, sbox, device_is_online, sensitive):
logger.warning("%s: error reading so use None (%s): %s", s.name, s._device, repr(e)) logger.warning("%s: error reading so use None (%s): %s", s.name, s._device, repr(e))
GLib.idle_add(_update_setting_item, sb, v, online, sensitive, True, priority=99) GLib.idle_add(_update_setting_item, sb, v, online, sensitive, True, priority=99)
_ui_async(_do_read, setting, force_read, sbox, device_is_online, sensitive) ui_async(_do_read, setting, force_read, sbox, device_is_online, sensitive)
def _write_async(setting, value, sbox, sensitive=True, key=None): def _write_async(setting, value, sbox, sensitive=True, key=None):
@ -71,7 +70,7 @@ def _write_async(setting, value, sbox, sensitive=True, key=None):
sbox._failed.set_visible(False) sbox._failed.set_visible(False)
sbox._spinner.set_visible(True) sbox._spinner.set_visible(True)
sbox._spinner.start() sbox._spinner.start()
_ui_async(_do_write, setting, value, sbox, key) ui_async(_do_write, setting, value, sbox, key)
class ComboBoxText(Gtk.ComboBoxText): class ComboBoxText(Gtk.ComboBoxText):
@ -105,7 +104,7 @@ class Control:
def layout(self, sbox, label, change, spinner, failed): def layout(self, sbox, label, change, spinner, failed):
sbox.pack_start(label, False, False, 0) sbox.pack_start(label, False, False, 0)
sbox.pack_end(change, False, False, 0) sbox.pack_end(change, False, False, 0)
fill = sbox.setting.kind == _SETTING_KIND.range or sbox.setting.kind == _SETTING_KIND.hetero fill = sbox.setting.kind == settings.KIND.range or sbox.setting.kind == settings.KIND.hetero
sbox.pack_end(self, fill, fill, 0) sbox.pack_end(self, fill, fill, 0)
sbox.pack_end(spinner, False, False, 0) sbox.pack_end(spinner, False, False, 0)
sbox.pack_end(failed, False, False, 0) sbox.pack_end(failed, False, False, 0)
@ -144,7 +143,7 @@ class SliderControl(Gtk.Scale, Control):
if self.get_sensitive(): if self.get_sensitive():
if self.timer: if self.timer:
self.timer.cancel() self.timer.cancel()
self.timer = _Timer(0.5, lambda: GLib.idle_add(self.do_change)) self.timer = Timer(0.5, lambda: GLib.idle_add(self.do_change))
self.timer.start() self.timer.start()
def do_change(self): def do_change(self):
@ -435,7 +434,7 @@ class MultipleRangeControl(MultipleControl):
if control.get_sensitive(): if control.get_sensitive():
if hasattr(control, "_timer"): if hasattr(control, "_timer"):
control._timer.cancel() control._timer.cancel()
control._timer = _Timer(0.5, lambda: GLib.idle_add(self._write, control, item, sub_item)) control._timer = Timer(0.5, lambda: GLib.idle_add(self._write, control, item, sub_item))
control._timer.start() control._timer.start()
def _write(self, control, item, sub_item): def _write(self, control, item, sub_item):
@ -495,7 +494,7 @@ class PackedRangeControl(MultipleRangeControl):
if control.get_sensitive(): if control.get_sensitive():
if hasattr(control, "_timer"): if hasattr(control, "_timer"):
control._timer.cancel() control._timer.cancel()
control._timer = _Timer(0.5, lambda: GLib.idle_add(self._write, control, item)) control._timer = Timer(0.5, lambda: GLib.idle_add(self._write, control, item))
control._timer.start() control._timer.start()
def _write(self, control, item): def _write(self, control, item):
@ -537,14 +536,14 @@ class HeteroKeyControl(Gtk.HBox, Control):
item_lblbox.set_visible(False) item_lblbox.set_visible(False)
else: else:
item_lblbox = None item_lblbox = None
if item["kind"] == _SETTING_KIND.choice: if item["kind"] == settings.KIND.choice:
item_box = ComboBoxText() item_box = ComboBoxText()
for entry in item["choices"]: for entry in item["choices"]:
item_box.append(str(int(entry)), str(entry)) item_box.append(str(int(entry)), str(entry))
item_box.set_active(0) item_box.set_active(0)
item_box.connect("changed", self.changed) item_box.connect("changed", self.changed)
self.pack_start(item_box, False, False, 0) self.pack_start(item_box, False, False, 0)
elif item["kind"] == _SETTING_KIND.range: elif item["kind"] == settings.KIND.range:
item_box = Scale() item_box = Scale()
item_box.set_range(item["min"], item["max"]) item_box.set_range(item["min"], item["max"])
item_box.set_round_digits(0) item_box.set_round_digits(0)
@ -559,7 +558,7 @@ class HeteroKeyControl(Gtk.HBox, Control):
result = {} result = {}
for k, (_lblbox, box) in self._items.items(): for k, (_lblbox, box) in self._items.items():
result[str(k)] = box.get_value() result[str(k)] = box.get_value()
result = _LEDEffectSetting(**result) result = hidpp20.LEDEffectSetting(**result)
return result return result
def set_value(self, value): def set_value(self, value):
@ -587,7 +586,7 @@ class HeteroKeyControl(Gtk.HBox, Control):
self.setup_visibles(int(self._items["ID"][1].get_value())) self.setup_visibles(int(self._items["ID"][1].get_value()))
if hasattr(control, "_timer"): if hasattr(control, "_timer"):
control._timer.cancel() control._timer.cancel()
control._timer = _Timer(0.3, lambda: GLib.idle_add(self._write, control)) control._timer = Timer(0.3, lambda: GLib.idle_add(self._write, control))
control._timer.start() control._timer.start()
def _write(self, control): def _write(self, control):
@ -598,13 +597,13 @@ class HeteroKeyControl(Gtk.HBox, Control):
_write_async(self.sbox.setting, new_state, self.sbox) _write_async(self.sbox.setting, new_state, self.sbox)
_allowables_icons = {True: "changes-allow", False: "changes-prevent", _SENSITIVITY_IGNORE: "dialog-error"} _allowables_icons = {True: "changes-allow", False: "changes-prevent", settings.SENSITIVITY_IGNORE: "dialog-error"}
_allowables_tooltips = { _allowables_tooltips = {
True: _("Changes allowed"), True: _("Changes allowed"),
False: _("No changes allowed"), False: _("No changes allowed"),
_SENSITIVITY_IGNORE: _("Ignore this setting"), settings.SENSITIVITY_IGNORE: _("Ignore this setting"),
} }
_next_allowable = {True: False, False: _SENSITIVITY_IGNORE, _SENSITIVITY_IGNORE: True} _next_allowable = {True: False, False: settings.SENSITIVITY_IGNORE, settings.SENSITIVITY_IGNORE: True}
_icons_allowables = {v: k for k, v in _allowables_icons.items()} _icons_allowables = {v: k for k, v in _allowables_icons.items()}
@ -618,7 +617,7 @@ def _change_click(button, sbox):
_change_icon(new_allowed, icon) _change_icon(new_allowed, icon)
if sbox.setting._device.persister: # remember the new setting sensitivity if sbox.setting._device.persister: # remember the new setting sensitivity
sbox.setting._device.persister.set_sensitivity(sbox.setting.name, new_allowed) sbox.setting._device.persister.set_sensitivity(sbox.setting.name, new_allowed)
if allowed == _SENSITIVITY_IGNORE: # update setting if it was being ignored if allowed == settings.SENSITIVITY_IGNORE: # update setting if it was being ignored
setting = next((s for s in sbox.setting._device.settings if s.name == sbox.setting.name), None) setting = next((s for s in sbox.setting._device.settings if s.name == sbox.setting.name), None)
if setting: if setting:
persisted = sbox.setting._device.persister.get(setting.name) if sbox.setting._device.persister else None persisted = sbox.setting._device.persister.get(setting.name) if sbox.setting._device.persister else None
@ -660,21 +659,21 @@ def _create_sbox(s, device):
change.set_sensitive(True) change.set_sensitive(True)
change.connect("clicked", _change_click, sbox) change.connect("clicked", _change_click, sbox)
if s.kind == _SETTING_KIND.toggle: if s.kind == settings.KIND.toggle:
control = ToggleControl(sbox) control = ToggleControl(sbox)
elif s.kind == _SETTING_KIND.range: elif s.kind == settings.KIND.range:
control = SliderControl(sbox) control = SliderControl(sbox)
elif s.kind == _SETTING_KIND.choice: elif s.kind == settings.KIND.choice:
control = _create_choice_control(sbox) control = _create_choice_control(sbox)
elif s.kind == _SETTING_KIND.map_choice: elif s.kind == settings.KIND.map_choice:
control = MapChoiceControl(sbox) control = MapChoiceControl(sbox)
elif s.kind == _SETTING_KIND.multiple_toggle: elif s.kind == settings.KIND.multiple_toggle:
control = MultipleToggleControl(sbox, change) control = MultipleToggleControl(sbox, change)
elif s.kind == _SETTING_KIND.multiple_range: elif s.kind == settings.KIND.multiple_range:
control = MultipleRangeControl(sbox, change) control = MultipleRangeControl(sbox, change)
elif s.kind == _SETTING_KIND.packed_range: elif s.kind == settings.KIND.packed_range:
control = PackedRangeControl(sbox, change) control = PackedRangeControl(sbox, change)
elif s.kind == _SETTING_KIND.hetero: elif s.kind == settings.KIND.hetero:
control = HeteroKeyControl(sbox, change) control = HeteroKeyControl(sbox, change)
else: else:
if logger.isEnabledFor(logging.WARNING): if logger.isEnabledFor(logging.WARNING):
@ -691,7 +690,6 @@ def _create_sbox(s, device):
def _update_setting_item(sbox, value, is_online=True, sensitive=True, nullOK=False): def _update_setting_item(sbox, value, is_online=True, sensitive=True, nullOK=False):
# sbox._spinner.set_visible(False) # don't repack item box
sbox._spinner.stop() sbox._spinner.stop()
sensitive = sbox._change_icon._allowed if sensitive is None else sensitive sensitive = sbox._change_icon._allowed if sensitive is None else sensitive
if value is None and not nullOK: if value is None and not nullOK:

View File

@ -35,8 +35,8 @@ from logitech_receiver.common import NamedInt
from logitech_receiver.common import NamedInts from logitech_receiver.common import NamedInts
from logitech_receiver.common import UnsortedNamedInts from logitech_receiver.common import UnsortedNamedInts
from logitech_receiver.settings import KIND as _SKIND from logitech_receiver.settings import KIND as _SKIND
from logitech_receiver.settings import Setting as _Setting from logitech_receiver.settings import Setting
from logitech_receiver.settings_templates import SETTINGS as _SETTINGS from logitech_receiver.settings_templates import SETTINGS
from solaar.i18n import _ from solaar.i18n import _
from solaar.ui import rule_actions from solaar.ui import rule_actions
@ -931,7 +931,7 @@ class DeviceInfo:
serial: str = "" serial: str = ""
unitId: str = "" unitId: str = ""
codename: str = "" codename: str = ""
settings: Dict[str, _Setting] = field(default_factory=dict) settings: Dict[str, Setting] = field(default_factory=dict)
def __post_init__(self): def __post_init__(self):
if self.serial is None or self.serial == "?": if self.serial is None or self.serial == "?":
@ -1281,7 +1281,7 @@ class SetValueControl(Gtk.HBox):
def _all_settings(): def _all_settings():
settings = {} settings = {}
for s in sorted(_SETTINGS, key=lambda setting: setting.label): for s in sorted(SETTINGS, key=lambda setting: setting.label):
if s.name not in settings: if s.name not in settings:
settings[s.name] = [s] settings[s.name] = [s]
else: else:
@ -1326,7 +1326,6 @@ class _DeviceUI:
self.device_field.set_value("") self.device_field.set_value("")
self.device_field.set_valign(Gtk.Align.CENTER) self.device_field.set_valign(Gtk.Align.CENTER)
self.device_field.set_size_request(400, 0) self.device_field.set_size_request(400, 0)
# self.device_field.connect('changed', self._changed_device)
self.device_field.connect("changed", self._on_update) self.device_field.connect("changed", self._on_update)
self.widgets[self.device_field] = (1, 1, 1, 1) self.widgets[self.device_field] = (1, 1, 1, 1)
@ -1477,9 +1476,9 @@ class _SettingWithValueUI:
(including the extra value if it exists) and the second element is the extra value to be pinned to (including the extra value if it exists) and the second element is the extra value to be pinned to
the start of the list (or `None` if there is no extra value). the start of the list (or `None` if there is no extra value).
""" """
if isinstance(setting, _Setting): if isinstance(setting, Setting):
setting = type(setting) setting = type(setting)
if isinstance(setting, type) and issubclass(setting, _Setting): if isinstance(setting, type) and issubclass(setting, Setting):
choices = UnsortedNamedInts() choices = UnsortedNamedInts()
universe = getattr(setting, "choices_universe", None) universe = getattr(setting, "choices_universe", None)
if universe: if universe:

View File

@ -21,7 +21,7 @@ import logging
from solaar import NAME from solaar import NAME
from solaar.i18n import _ from solaar.i18n import _
from . import icons as _icons from . import icons
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -66,14 +66,6 @@ if available:
_notifications.clear() _notifications.clear()
Notify.uninit() Notify.uninit()
# def toggle(action):
# if action.get_active():
# init()
# else:
# uninit()
# action.set_sensitive(available)
# return action.get_active()
def alert(reason, icon=None): def alert(reason, icon=None):
assert reason assert reason
@ -84,15 +76,13 @@ if available:
# we need to use the filename here because the notifications daemon # we need to use the filename here because the notifications daemon
# is an external application that does not know about our icon sets # is an external application that does not know about our icon sets
icon_file = _icons.icon_file(NAME.lower()) if icon is None else _icons.icon_file(icon) icon_file = icons.icon_file(NAME.lower()) if icon is None else icons.icon_file(icon)
n.update(NAME.lower(), reason, icon_file) n.update(NAME.lower(), reason, icon_file)
n.set_urgency(Notify.Urgency.NORMAL) n.set_urgency(Notify.Urgency.NORMAL)
n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower())) n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower()))
try: try:
# if logger.isEnabledFor(logging.DEBUG):
# logger.debug("showing %s", n)
n.show() n.show()
except Exception: except Exception:
logger.exception("showing %s", n) logger.exception("showing %s", n)
@ -113,16 +103,10 @@ if available:
message = reason message = reason
else: else:
message = _("unspecified reason") message = _("unspecified reason")
# elif dev.status is None:
# message = _("unpaired")
# elif bool(dev.status):
# message = dev.status_string() or _("connected")
# else:
# message = _("offline")
# we need to use the filename here because the notifications daemon # we need to use the filename here because the notifications daemon
# is an external application that does not know about our icon sets # is an external application that does not know about our icon sets
icon_file = _icons.device_icon_file(dev.name, dev.kind) if icon is None else _icons.icon_file(icon) icon_file = icons.device_icon_file(dev.name, dev.kind) if icon is None else icons.icon_file(icon)
n.update(summary, message, icon_file) n.update(summary, message, icon_file)
n.set_urgency(Notify.Urgency.NORMAL) n.set_urgency(Notify.Urgency.NORMAL)
@ -131,8 +115,6 @@ if available:
n.set_hint("value", GLib.Variant("i", progress)) n.set_hint("value", GLib.Variant("i", progress))
try: try:
# if logger.isEnabledFor(logging.DEBUG):
# logger.debug("showing %s", n)
n.show() n.show()
except Exception: except Exception:
logger.exception("showing %s", n) logger.exception("showing %s", n)

View File

@ -19,12 +19,12 @@ import logging
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Gtk from gi.repository import Gtk
from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp10_constants
from solaar.i18n import _ from solaar.i18n import _
from solaar.i18n import ngettext from solaar.i18n import ngettext
from . import icons as _icons from . import icons
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -108,7 +108,7 @@ def _check_lock_state(assistant, receiver, count):
return True return True
elif receiver.pairing.discovering and receiver.pairing.device_address and receiver.pairing.device_name: elif receiver.pairing.discovering and receiver.pairing.device_address and receiver.pairing.device_name:
add = receiver.pairing.device_address add = receiver.pairing.device_address
ent = 20 if receiver.pairing.device_kind == _hidpp10_constants.DEVICE_KIND.keyboard else 10 ent = 20 if receiver.pairing.device_kind == hidpp10_constants.DEVICE_KIND.keyboard else 10
if receiver.pair_device(address=add, authentication=receiver.pairing.device_authentication, entropy=ent): if receiver.pair_device(address=add, authentication=receiver.pairing.device_authentication, entropy=ent):
return True return True
else: else:
@ -200,8 +200,8 @@ def _create_success_page(assistant, device):
header = Gtk.Label(label=_("Found a new device:")) header = Gtk.Label(label=_("Found a new device:"))
page.pack_start(header, False, False, 0) page.pack_start(header, False, False, 0)
device_icon = Gtk.Image() device_icon = Gtk.Image()
icon_name = _icons.device_icon_name(device.name, device.kind) icon_name = icons.device_icon_name(device.name, device.kind)
device_icon.set_from_icon_name(icon_name, _icons.LARGE_SIZE) device_icon.set_from_icon_name(icon_name, icons.LARGE_SIZE)
page.pack_start(device_icon, True, True, 0) page.pack_start(device_icon, True, True, 0)
device_label = Gtk.Label() device_label = Gtk.Label()
device_label.set_markup(f"<b>{device.name}</b>") device_label.set_markup(f"<b>{device.name}</b>")

View File

@ -17,12 +17,12 @@
from shlex import quote as shlex_quote from shlex import quote as shlex_quote
from gi.repository import Gtk from gi.repository import Gtk
from logitech_receiver import diversion as _DIV from logitech_receiver import diversion
from logitech_receiver.diversion import CLICK from logitech_receiver.diversion import CLICK
from logitech_receiver.diversion import DEPRESS from logitech_receiver.diversion import DEPRESS
from logitech_receiver.diversion import RELEASE from logitech_receiver.diversion import RELEASE
from logitech_receiver.diversion import XK_KEYS as _XK_KEYS from logitech_receiver.diversion import XK_KEYS
from logitech_receiver.diversion import buttons as _buttons from logitech_receiver.diversion import buttons
from solaar.i18n import _ from solaar.i18n import _
from solaar.ui.rule_base import CompletionEntry from solaar.ui.rule_base import CompletionEntry
@ -30,7 +30,7 @@ from solaar.ui.rule_base import RuleComponentUI
class ActionUI(RuleComponentUI): class ActionUI(RuleComponentUI):
CLASS = _DIV.Action CLASS = diversion.Action
@classmethod @classmethod
def icon_name(cls): def icon_name(cls):
@ -38,8 +38,8 @@ class ActionUI(RuleComponentUI):
class KeyPressUI(ActionUI): class KeyPressUI(ActionUI):
CLASS = _DIV.KeyPress CLASS = diversion.KeyPress
KEY_NAMES = [k[3:] if k.startswith("XK_") else k for k, v in _XK_KEYS.items() if isinstance(v, int)] KEY_NAMES = [k[3:] if k.startswith("XK_") else k for k, v in XK_KEYS.items() if isinstance(v, int)]
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}
@ -107,7 +107,6 @@ class KeyPressUI(ActionUI):
self._create_field() self._create_field()
self._create_del_btn() self._create_del_btn()
# self.widgets[self.add_btn] = (n + 1, 0, 1, 1)
self.widgets[self.add_btn] = (n, 1, 1, 1) self.widgets[self.add_btn] = (n, 1, 1, 1)
super().show(component, editable) super().show(component, editable)
for i in range(n): for i in range(n):
@ -137,7 +136,7 @@ class KeyPressUI(ActionUI):
class MouseScrollUI(ActionUI): class MouseScrollUI(ActionUI):
CLASS = _DIV.MouseScroll CLASS = diversion.MouseScroll
MIN_VALUE = -2000 MIN_VALUE = -2000
MAX_VALUE = 2000 MAX_VALUE = 2000
@ -191,10 +190,10 @@ class MouseScrollUI(ActionUI):
class MouseClickUI(ActionUI): class MouseClickUI(ActionUI):
CLASS = _DIV.MouseClick CLASS = diversion.MouseClick
MIN_VALUE = 1 MIN_VALUE = 1
MAX_VALUE = 9 MAX_VALUE = 9
BUTTONS = list(_buttons.keys()) BUTTONS = list(buttons.keys())
ACTIONS = [CLICK, DEPRESS, RELEASE] ACTIONS = [CLICK, DEPRESS, RELEASE]
def create_widgets(self): def create_widgets(self):
@ -249,7 +248,7 @@ class MouseClickUI(ActionUI):
class ExecuteUI(ActionUI): class ExecuteUI(ActionUI):
CLASS = _DIV.Execute CLASS = diversion.Execute
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}

View File

@ -17,7 +17,7 @@
from contextlib import contextmanager as contextlib_contextmanager from contextlib import contextmanager as contextlib_contextmanager
from gi.repository import Gtk from gi.repository import Gtk
from logitech_receiver import diversion as _DIV from logitech_receiver import diversion
def norm(s): def norm(s):
@ -47,7 +47,7 @@ class CompletionEntry(Gtk.Entry):
class RuleComponentUI: class RuleComponentUI:
CLASS = _DIV.RuleComponent CLASS = diversion.RuleComponent
def __init__(self, panel, on_update=None): def __init__(self, panel, on_update=None):
self.panel = panel self.panel = panel

View File

@ -16,10 +16,10 @@
from dataclasses import dataclass from dataclasses import dataclass
from gi.repository import Gtk from gi.repository import Gtk
from logitech_receiver import diversion as _DIV from logitech_receiver import diversion
from logitech_receiver.diversion import Key as _Key from logitech_receiver.diversion import Key
from logitech_receiver.hidpp20 import FEATURE as _ALL_FEATURES from logitech_receiver.hidpp20 import FEATURE
from logitech_receiver.special_keys import CONTROL as _CONTROL from logitech_receiver.special_keys import CONTROL
from solaar.i18n import _ from solaar.i18n import _
from solaar.ui.rule_base import CompletionEntry from solaar.ui.rule_base import CompletionEntry
@ -27,7 +27,7 @@ from solaar.ui.rule_base import RuleComponentUI
class ConditionUI(RuleComponentUI): class ConditionUI(RuleComponentUI):
CLASS = _DIV.Condition CLASS = diversion.Condition
@classmethod @classmethod
def icon_name(cls): def icon_name(cls):
@ -35,7 +35,7 @@ class ConditionUI(RuleComponentUI):
class ProcessUI(ConditionUI): class ProcessUI(ConditionUI):
CLASS = _DIV.Process CLASS = diversion.Process
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}
@ -65,7 +65,7 @@ class ProcessUI(ConditionUI):
class MouseProcessUI(ConditionUI): class MouseProcessUI(ConditionUI):
CLASS = _DIV.MouseProcess CLASS = diversion.MouseProcess
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}
@ -95,17 +95,17 @@ class MouseProcessUI(ConditionUI):
class FeatureUI(ConditionUI): class FeatureUI(ConditionUI):
CLASS = _DIV.Feature CLASS = diversion.Feature
FEATURES_WITH_DIVERSION = [ FEATURES_WITH_DIVERSION = [
str(_ALL_FEATURES.CROWN), str(FEATURE.CROWN),
str(_ALL_FEATURES.THUMB_WHEEL), str(FEATURE.THUMB_WHEEL),
str(_ALL_FEATURES.LOWRES_WHEEL), str(FEATURE.LOWRES_WHEEL),
str(_ALL_FEATURES.HIRES_WHEEL), str(FEATURE.HIRES_WHEEL),
str(_ALL_FEATURES.GESTURE_2), str(FEATURE.GESTURE_2),
str(_ALL_FEATURES.REPROG_CONTROLS_V4), str(FEATURE.REPROG_CONTROLS_V4),
str(_ALL_FEATURES.GKEY), str(FEATURE.GKEY),
str(_ALL_FEATURES.MKEYS), str(FEATURE.MKEYS),
str(_ALL_FEATURES.MR), str(FEATURE.MR),
] ]
def create_widgets(self): def create_widgets(self):
@ -118,10 +118,9 @@ class FeatureUI(ConditionUI):
for feature in self.FEATURES_WITH_DIVERSION: for feature in self.FEATURES_WITH_DIVERSION:
self.field.append(feature, feature) self.field.append(feature, feature)
self.field.set_valign(Gtk.Align.CENTER) self.field.set_valign(Gtk.Align.CENTER)
# self.field.set_vexpand(True)
self.field.set_size_request(600, 0) self.field.set_size_request(600, 0)
self.field.connect("changed", self._on_update) self.field.connect("changed", self._on_update)
all_features = [str(f) for f in _ALL_FEATURES] all_features = [str(f) for f in FEATURE]
CompletionEntry.add_completion_to_entry(self.field.get_child(), all_features) CompletionEntry.add_completion_to_entry(self.field.get_child(), all_features)
self.widgets[self.field] = (0, 1, 1, 1) self.widgets[self.field] = (0, 1, 1, 1)
@ -151,7 +150,7 @@ class FeatureUI(ConditionUI):
class ReportUI(ConditionUI): class ReportUI(ConditionUI):
CLASS = _DIV.Report CLASS = diversion.Report
MIN_VALUE = -1 # for invalid values MIN_VALUE = -1 # for invalid values
MAX_VALUE = 15 MAX_VALUE = 15
@ -164,7 +163,6 @@ class ReportUI(ConditionUI):
self.field.set_halign(Gtk.Align.CENTER) self.field.set_halign(Gtk.Align.CENTER)
self.field.set_valign(Gtk.Align.CENTER) self.field.set_valign(Gtk.Align.CENTER)
self.field.set_hexpand(True) self.field.set_hexpand(True)
# self.field.set_vexpand(True)
self.field.connect("changed", self._on_update) self.field.connect("changed", self._on_update)
self.widgets[self.field] = (0, 1, 1, 1) self.widgets[self.field] = (0, 1, 1, 1)
@ -186,7 +184,7 @@ class ReportUI(ConditionUI):
class ModifiersUI(ConditionUI): class ModifiersUI(ConditionUI):
CLASS = _DIV.Modifiers CLASS = diversion.Modifiers
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}
@ -195,7 +193,7 @@ class ModifiersUI(ConditionUI):
self.widgets[self.label] = (0, 0, 5, 1) self.widgets[self.label] = (0, 0, 5, 1)
self.labels = {} self.labels = {}
self.switches = {} self.switches = {}
for i, m in enumerate(_DIV.MODIFIERS): for i, m in enumerate(diversion.MODIFIERS):
switch = Gtk.Switch(halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) switch = Gtk.Switch(halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True)
label = Gtk.Label(label=m, halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) label = Gtk.Label(label=m, halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True)
self.widgets[label] = (i, 1, 1, 1) self.widgets[label] = (i, 1, 1, 1)
@ -207,7 +205,7 @@ class ModifiersUI(ConditionUI):
def show(self, component, editable): def show(self, component, editable):
super().show(component, editable) super().show(component, editable)
with self.ignore_changes(): with self.ignore_changes():
for m in _DIV.MODIFIERS: for m in diversion.MODIFIERS:
self.switches[m].set_active(m in component.modifiers) self.switches[m].set_active(m in component.modifiers)
def collect_value(self): def collect_value(self):
@ -223,8 +221,8 @@ class ModifiersUI(ConditionUI):
class KeyUI(ConditionUI): class KeyUI(ConditionUI):
CLASS = _DIV.Key CLASS = diversion.Key
KEY_NAMES = map(str, _CONTROL) KEY_NAMES = map(str, CONTROL)
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}
@ -241,23 +239,23 @@ class KeyUI(ConditionUI):
self.key_field.connect("changed", self._on_update) self.key_field.connect("changed", self._on_update)
self.widgets[self.key_field] = (0, 1, 2, 1) self.widgets[self.key_field] = (0, 1, 2, 1)
self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(None, _("Key down")) self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(None, _("Key down"))
self.action_pressed_radio.connect("toggled", self._on_update, _Key.DOWN) self.action_pressed_radio.connect("toggled", self._on_update, Key.DOWN)
self.widgets[self.action_pressed_radio] = (2, 1, 1, 1) self.widgets[self.action_pressed_radio] = (2, 1, 1, 1)
self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _("Key up")) self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _("Key up"))
self.action_released_radio.connect("toggled", self._on_update, _Key.UP) self.action_released_radio.connect("toggled", self._on_update, Key.UP)
self.widgets[self.action_released_radio] = (3, 1, 1, 1) self.widgets[self.action_released_radio] = (3, 1, 1, 1)
def show(self, component, editable): def show(self, component, editable):
super().show(component, editable) super().show(component, editable)
with self.ignore_changes(): with self.ignore_changes():
self.key_field.set_text(str(component.key) if self.component.key else "") self.key_field.set_text(str(component.key) if self.component.key else "")
if not component.action or component.action == _Key.DOWN: if not component.action or component.action == Key.DOWN:
self.action_pressed_radio.set_active(True) self.action_pressed_radio.set_active(True)
else: else:
self.action_released_radio.set_active(True) self.action_released_radio.set_active(True)
def collect_value(self): def collect_value(self):
action = _Key.UP if self.action_released_radio.get_active() else _Key.DOWN action = Key.UP if self.action_released_radio.get_active() else Key.DOWN
return [self.key_field.get_text(), action] return [self.key_field.get_text(), action]
def _on_update(self, *args): def _on_update(self, *args):
@ -275,8 +273,8 @@ class KeyUI(ConditionUI):
class KeyIsDownUI(ConditionUI): class KeyIsDownUI(ConditionUI):
CLASS = _DIV.KeyIsDown CLASS = diversion.KeyIsDown
KEY_NAMES = map(str, _CONTROL) KEY_NAMES = map(str, CONTROL)
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}
@ -316,7 +314,7 @@ class KeyIsDownUI(ConditionUI):
class TestUI(ConditionUI): class TestUI(ConditionUI):
CLASS = _DIV.Test CLASS = diversion.Test
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}
@ -330,13 +328,13 @@ class TestUI(ConditionUI):
self.test = Gtk.ComboBoxText.new_with_entry() self.test = Gtk.ComboBoxText.new_with_entry()
self.test.append("", "") self.test.append("", "")
for t in _DIV.TESTS: for t in diversion.TESTS:
self.test.append(t, t) self.test.append(t, t)
self.test.set_halign(Gtk.Align.END) self.test.set_halign(Gtk.Align.END)
self.test.set_valign(Gtk.Align.CENTER) self.test.set_valign(Gtk.Align.CENTER)
self.test.set_hexpand(False) self.test.set_hexpand(False)
self.test.set_size_request(300, 0) self.test.set_size_request(300, 0)
CompletionEntry.add_completion_to_entry(self.test.get_child(), _DIV.TESTS) CompletionEntry.add_completion_to_entry(self.test.get_child(), diversion.TESTS)
self.test.connect("changed", self._on_update) self.test.connect("changed", self._on_update)
self.widgets[self.test] = (1, 1, 1, 1) self.widgets[self.test] = (1, 1, 1, 1)
@ -350,7 +348,7 @@ class TestUI(ConditionUI):
with self.ignore_changes(): with self.ignore_changes():
self.test.set_active_id(component.test) self.test.set_active_id(component.test)
self.parameter.set_text(str(component.parameter) if component.parameter is not None else "") self.parameter.set_text(str(component.parameter) if component.parameter is not None else "")
if component.test not in _DIV.TESTS: if component.test not in diversion.TESTS:
self.test.get_child().set_text(component.test) self.test.get_child().set_text(component.test)
self._change_status_icon() self._change_status_icon()
@ -367,7 +365,7 @@ class TestUI(ConditionUI):
self._change_status_icon() self._change_status_icon()
def _change_status_icon(self): def _change_status_icon(self):
icon = "dialog-warning" if (self.test.get_active_text() or "").strip() not in _DIV.TESTS else "" icon = "dialog-warning" if (self.test.get_active_text() or "").strip() not in diversion.TESTS else ""
self.test.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) self.test.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
@classmethod @classmethod
@ -395,7 +393,7 @@ class TestBytesMode:
class TestBytesUI(ConditionUI): class TestBytesUI(ConditionUI):
CLASS = _DIV.TestBytes CLASS = diversion.TestBytes
_common_elements = [ _common_elements = [
TestBytesElement("begin", _("begin (inclusive)"), 0, 16), TestBytesElement("begin", _("begin (inclusive)"), 0, 16),
@ -508,7 +506,7 @@ class TestBytesUI(ConditionUI):
class MouseGestureUI(ConditionUI): class MouseGestureUI(ConditionUI):
CLASS = _DIV.MouseGesture CLASS = diversion.MouseGesture
MOUSE_GESTURE_NAMES = [ MOUSE_GESTURE_NAMES = [
"Mouse Up", "Mouse Up",
"Mouse Down", "Mouse Down",
@ -519,7 +517,7 @@ class MouseGestureUI(ConditionUI):
"Mouse Down-left", "Mouse Down-left",
"Mouse Down-right", "Mouse Down-right",
] ]
MOVE_NAMES = list(map(str, _CONTROL)) + MOUSE_GESTURE_NAMES MOVE_NAMES = list(map(str, CONTROL)) + MOUSE_GESTURE_NAMES
def create_widgets(self): def create_widgets(self):
self.widgets = {} self.widgets = {}

View File

@ -18,7 +18,7 @@
import logging import logging
import os import os
from time import time as _timestamp from time import time
import gi import gi
@ -31,11 +31,10 @@ import solaar.gtk as gtk
from solaar import NAME from solaar import NAME
from solaar.i18n import _ from solaar.i18n import _
from . import icons as _icons from . import about
from .about import show_window as _show_about_window from . import action
from .action import make_image_menu_item from . import icons
from .window import popup as _window_popup from . import window
from .window import toggle as _window_toggle
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -52,8 +51,8 @@ def _create_menu(quit_handler):
menu.append(no_receiver) menu.append(no_receiver)
menu.append(Gtk.SeparatorMenuItem.new()) menu.append(Gtk.SeparatorMenuItem.new())
menu.append(make_image_menu_item(_("About %s") % NAME, "help-about", _show_about_window)) menu.append(action.make_image_menu_item(_("About %s") % NAME, "help-about", about.show_window))
menu.append(make_image_menu_item(_("Quit %s") % NAME, "application-exit", quit_handler)) menu.append(action.make_image_menu_item(_("Quit %s") % NAME, "application-exit", quit_handler))
menu.show_all() menu.show_all()
return menu return menu
@ -78,7 +77,7 @@ def _scroll(tray_icon, event, direction=None):
# scroll events come way too fast (at least 5-6 at once) so take a little break between them # scroll events come way too fast (at least 5-6 at once) so take a little break between them
global _last_scroll global _last_scroll
now = now or _timestamp() now = now or time()
if now - _last_scroll < 0.33: # seconds if now - _last_scroll < 0.33: # seconds
return return
_last_scroll = now _last_scroll = now
@ -162,13 +161,13 @@ try:
return icon_info.get_filename() if icon_info else icon_name return icon_info.get_filename() if icon_info else icon_name
def _create(menu): def _create(menu):
_icons._init_icon_paths() icons._init_icon_paths()
ind = AppIndicator3.Indicator.new( ind = AppIndicator3.Indicator.new(
"indicator-solaar", _icon_file(_icons.TRAY_INIT), AppIndicator3.IndicatorCategory.HARDWARE "indicator-solaar", _icon_file(icons.TRAY_INIT), AppIndicator3.IndicatorCategory.HARDWARE
) )
ind.set_title(NAME) ind.set_title(NAME)
ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE) ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
# ind.set_attention_icon_full(_icon_file(_icons.TRAY_ATTENTION), '') # works poorly for XFCE 16 # ind.set_attention_icon_full(_icon_file(icons.TRAY_ATTENTION), '') # works poorly for XFCE 16
# ind.set_label(NAME.lower(), NAME.lower()) # ind.set_label(NAME.lower(), NAME.lower())
ind.set_menu(menu) ind.set_menu(menu)
@ -187,21 +186,21 @@ try:
_ignore, _ignore, name, device = _picked_device _ignore, _ignore, name, device = _picked_device
battery_level = device.battery_info.level if device.battery_info is not None else None battery_level = device.battery_info.level if device.battery_info is not None else None
battery_charging = device.battery_info.charging() if device.battery_info is not None else None battery_charging = device.battery_info.charging() if device.battery_info is not None else None
tray_icon_name = _icons.battery(battery_level, battery_charging) tray_icon_name = icons.battery(battery_level, battery_charging)
description = f"{name}: {device.status_string()}" description = f"{name}: {device.status_string()}"
else: else:
# there may be a receiver, but no peripherals # there may be a receiver, but no peripherals
tray_icon_name = _icons.TRAY_OKAY if _devices_info else _icons.TRAY_INIT tray_icon_name = icons.TRAY_OKAY if _devices_info else icons.TRAY_INIT
description_lines = _generate_description_lines() description_lines = _generate_description_lines()
description = "\n".join(description_lines).rstrip("\n") description = "\n".join(description_lines).rstrip("\n")
# icon_file = _icons.icon_file(icon_name, _TRAY_ICON_SIZE) # icon_file = icons.icon_file(icon_name, _TRAY_ICON_SIZE)
_icon.set_icon_full(_icon_file(tray_icon_name), description) _icon.set_icon_full(_icon_file(tray_icon_name), description)
def attention(reason=None): def attention(reason=None):
if _icon.get_status() != AppIndicator3.IndicatorStatus.ATTENTION: if _icon.get_status() != AppIndicator3.IndicatorStatus.ATTENTION:
# _icon.set_attention_icon_full(_icon_file(_icons.TRAY_ATTENTION), reason or '') # works poorly for XFCe 16 # _icon.set_attention_icon_full(_icon_file(icons.TRAY_ATTENTION), reason or '') # works poorly for XFCe 16
_icon.set_status(AppIndicator3.IndicatorStatus.ATTENTION) _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)
@ -210,11 +209,11 @@ except ImportError:
logger.debug("using StatusIcon") logger.debug("using StatusIcon")
def _create(menu): def _create(menu):
icon = Gtk.StatusIcon.new_from_icon_name(_icons.TRAY_INIT) icon = Gtk.StatusIcon.new_from_icon_name(icons.TRAY_INIT)
icon.set_name(NAME.lower()) icon.set_name(NAME.lower())
icon.set_title(NAME) icon.set_title(NAME)
icon.set_tooltip_text(NAME) icon.set_tooltip_text(NAME)
icon.connect("activate", _window_toggle) icon.connect("activate", window.toggle)
icon.connect("scroll-event", _scroll) icon.connect("scroll-event", _scroll)
icon.connect("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))
@ -235,10 +234,10 @@ except ImportError:
_ignore, _ignore, name, device = _picked_device _ignore, _ignore, name, device = _picked_device
battery_level = device.battery_info.level if device.battery_info is not None else None battery_level = device.battery_info.level if device.battery_info is not None else None
battery_charging = device.battery_info.charging() if device.battery_info is not None else None battery_charging = device.battery_info.charging() if device.battery_info is not None else None
tray_icon_name = _icons.battery(battery_level, battery_charging) tray_icon_name = icons.battery(battery_level, battery_charging)
else: else:
# there may be a receiver, but no peripherals # there may be a receiver, but no peripherals
tray_icon_name = _icons.TRAY_OKAY if _devices_info else _icons.TRAY_ATTENTION tray_icon_name = icons.TRAY_OKAY if _devices_info else icons.TRAY_ATTENTION
_icon.set_from_icon_name(tray_icon_name) _icon.set_from_icon_name(tray_icon_name)
_icon_before_attention = None _icon_before_attention = None
@ -246,7 +245,7 @@ except ImportError:
def _blink(count): def _blink(count):
global _icon_before_attention global _icon_before_attention
if count % 2: if count % 2:
_icon.set_from_icon_name(_icons.TRAY_ATTENTION) _icon.set_from_icon_name(icons.TRAY_ATTENTION)
else: else:
_icon.set_from_icon_name(_icon_before_attention) _icon.set_from_icon_name(_icon_before_attention)
@ -337,7 +336,7 @@ def _add_device(device):
_devices_info.insert(index, new_device_info) _devices_info.insert(index, new_device_info)
label = (" " if device.number else "") + device.name label = (" " if device.number else "") + device.name
new_menu_item = make_image_menu_item(label, None, _window_popup, receiver_path, device.number) new_menu_item = action.make_image_menu_item(label, None, window.popup, receiver_path, device.number)
_menu.insert(new_menu_item, index) _menu.insert(new_menu_item, index)
return index return index
@ -360,8 +359,8 @@ def _add_receiver(receiver):
index = len(_devices_info) index = len(_devices_info)
new_receiver_info = (receiver.path, None, receiver.name, None) new_receiver_info = (receiver.path, None, receiver.name, None)
_devices_info.insert(index, new_receiver_info) _devices_info.insert(index, new_receiver_info)
icon_name = _icons.device_icon_name(receiver.name, receiver.kind) icon_name = icons.device_icon_name(receiver.name, receiver.kind)
new_menu_item = make_image_menu_item(receiver.name, icon_name, _window_popup, receiver.path) new_menu_item = action.make_image_menu_item(receiver.name, icon_name, window.popup, receiver.path)
_menu.insert(new_menu_item, index) _menu.insert(new_menu_item, index)
return 0 return 0
@ -385,7 +384,7 @@ def _update_menu_item(index, device):
menu_item = menu_items[index] menu_item = menu_items[index]
level = device.battery_info.level if device.battery_info is not None else None level = device.battery_info.level if device.battery_info is not None else None
charging = device.battery_info.charging() if device.battery_info is not None else None charging = device.battery_info.charging() if device.battery_info is not None else None
icon_name = _icons.battery(level, charging) icon_name = icons.battery(level, charging)
menu_item.label.set_label((" " if 0 < device.number <= 6 else "") + device.name + ": " + device.status_string()) menu_item.label.set_label((" " if 0 < device.number <= 6 else "") + device.name + ": " + device.status_string())
image_widget = menu_item.icon image_widget = menu_item.icon
image_widget.set_sensitive(bool(device.online)) image_widget.set_sensitive(bool(device.online))

View File

@ -20,22 +20,21 @@ import logging
import gi import gi
from gi.repository.GObject import TYPE_PYOBJECT from gi.repository.GObject import TYPE_PYOBJECT
from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp10_constants
from logitech_receiver.common import NamedInt as _NamedInt from logitech_receiver.common import LOGITECH_VENDOR_ID
from logitech_receiver.common import NamedInts as _NamedInts from logitech_receiver.common import NamedInt
from logitech_receiver.common import NamedInts
from solaar import NAME from solaar import NAME
from solaar.i18n import _ from solaar.i18n import _
from solaar.i18n import ngettext from solaar.i18n import ngettext
from . import action as _action from . import about
from . import config_panel as _config_panel from . import action
from . import icons as _icons from . import config_panel
from .about import show_window as _show_about_window from . import diversion_rules
from .common import ui_async as _ui_async from . import icons
from .diversion_rules import show_window as _show_diversion_window from .common import ui_async
# from solaar import __version__ as VERSION
gi.require_version("Gdk", "3.0") gi.require_version("Gdk", "3.0")
from gi.repository import Gdk # NOQA: E402 from gi.repository import Gdk # NOQA: E402
@ -56,7 +55,7 @@ except (ValueError, AttributeError):
_CAN_SET_ROW_NONE = "" _CAN_SET_ROW_NONE = ""
# tree model columns # 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) _COLUMN_TYPES = (str, int, bool, str, str, str, str, TYPE_PYOBJECT)
_TREE_SEPATATOR = (None, 0, False, None, None, None, None, None) _TREE_SEPATATOR = (None, 0, False, None, None, None, None, None)
assert len(_TREE_SEPATATOR) == len(_COLUMN_TYPES) assert len(_TREE_SEPATATOR) == len(_COLUMN_TYPES)
@ -131,7 +130,7 @@ def _create_device_panel():
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._config = _config_panel.create() p._config = config_panel.create()
p.pack_end(p._config, True, True, 4) p.pack_end(p._config, True, True, 4)
return p return p
@ -174,7 +173,7 @@ def _create_buttons_box():
assert receiver is not None assert receiver is not None
assert bool(receiver) assert bool(receiver)
assert receiver.kind is None assert receiver.kind is None
_action.pair(_window, receiver) action.pair(_window, receiver)
bb._pair = _new_button(_("Pair new device"), "list-add", clicked=_pair_new_device) bb._pair = _new_button(_("Pair new device"), "list-add", clicked=_pair_new_device)
bb.add(bb._pair) bb.add(bb._pair)
@ -184,7 +183,7 @@ def _create_buttons_box():
device = _find_selected_device() device = _find_selected_device()
assert device is not None assert device is not None
assert device.kind is not None assert device.kind is not None
_action.unpair(_window, device) action.unpair(_window, device)
bb._unpair = _new_button(_("Unpair"), "edit-delete", clicked=_unpair_current_device) bb._unpair = _new_button(_("Unpair"), "edit-delete", clicked=_unpair_current_device)
bb.add(bb._unpair) bb.add(bb._unpair)
@ -233,7 +232,6 @@ def _create_tree(model):
tree.set_headers_visible(False) tree.set_headers_visible(False)
tree.set_show_expanders(False) tree.set_show_expanders(False)
tree.set_level_indentation(20) tree.set_level_indentation(20)
# tree.set_fixed_height_mode(True)
tree.set_enable_tree_lines(True) tree.set_enable_tree_lines(True)
tree.set_reorderable(False) tree.set_reorderable(False)
tree.set_enable_search(False) tree.set_enable_search(False)
@ -307,19 +305,14 @@ def _create_window_layout():
bottom_buttons_box.set_spacing(20) bottom_buttons_box.set_spacing(20)
quit_button = _new_button(_("Quit %s") % NAME, "application-exit", _SMALL_BUTTON_ICON_SIZE, clicked=destroy) quit_button = _new_button(_("Quit %s") % NAME, "application-exit", _SMALL_BUTTON_ICON_SIZE, clicked=destroy)
bottom_buttons_box.add(quit_button) bottom_buttons_box.add(quit_button)
about_button = _new_button(_("About %s") % NAME, "help-about", _SMALL_BUTTON_ICON_SIZE, clicked=_show_about_window) about_button = _new_button(_("About %s") % NAME, "help-about", _SMALL_BUTTON_ICON_SIZE, clicked=about.show_window)
bottom_buttons_box.add(about_button) bottom_buttons_box.add(about_button)
diversion_button = _new_button( diversion_button = _new_button(
_("Rule Editor"), "", _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: _show_diversion_window(_model) _("Rule Editor"), "", _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: diversion_rules.show_window(_model)
) )
bottom_buttons_box.add(diversion_button) bottom_buttons_box.add(diversion_button)
bottom_buttons_box.set_child_secondary(diversion_button, True) bottom_buttons_box.set_child_secondary(diversion_button, True)
# solaar_version = Gtk.Label()
# solaar_version.set_markup('<small>' + NAME + ' v' + VERSION + '</small>')
# bottom_buttons_box.add(solaar_version)
# bottom_buttons_box.set_child_secondary(solaar_version, True)
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 8) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 8)
vbox.set_border_width(8) vbox.set_border_width(8)
vbox.pack_start(panel, True, True, 0) vbox.pack_start(panel, True, True, 0)
@ -335,10 +328,6 @@ def _create(delete_action):
window = Gtk.Window() window = Gtk.Window()
window.set_title(NAME) window.set_title(NAME)
window.set_role("status-window") window.set_role("status-window")
# window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
# window.set_skip_taskbar_hint(True)
# window.set_skip_pager_hint(True)
window.connect("delete-event", delete_action) window.connect("delete-event", delete_action)
vbox = _create_window_layout() vbox = _create_window_layout()
@ -373,8 +362,6 @@ def _find_selected_device_id():
def _device_selected(selection): def _device_selected(selection):
model, item = selection.get_selected() model, item = selection.get_selected()
device = model.get_value(item, _COLUMN.DEVICE) if item else None device = model.get_value(item, _COLUMN.DEVICE) if item else None
# if logger.isEnabledFor(logging.DEBUG):
# logger.debug("window tree selected device %s", device)
if device: if device:
_update_info_panel(device, full=True) _update_info_panel(device, full=True)
else: else:
@ -399,7 +386,7 @@ def _receiver_row(receiver_path, receiver=None):
item = _model.iter_next(item) item = _model.iter_next(item)
if not item and receiver: if not item and receiver:
icon_name = _icons.device_icon_name(receiver.name) icon_name = icons.device_icon_name(receiver.name)
status_text = None status_text = None
status_icon = None status_icon = None
row_data = (receiver_path, 0, True, receiver.name, icon_name, status_text, status_icon, receiver) row_data = (receiver_path, 0, True, receiver.name, icon_name, status_text, status_icon, receiver)
@ -444,7 +431,7 @@ def _device_row(receiver_path, device_number, device=None):
item = _model.iter_next(item) item = _model.iter_next(item)
if not item and device: if not item and device:
icon_name = _icons.device_icon_name(device.name, device.kind) icon_name = icons.device_icon_name(device.name, device.kind)
status_text = None status_text = None
status_icon = None status_icon = None
row_data = ( row_data = (
@ -516,8 +503,7 @@ def _update_details(button):
yield (_("Path"), device.path) yield (_("Path"), device.path)
if device.kind is None: if device.kind is None:
# 046d is the Logitech vendor id yield (_("USB ID"), f"{LOGITECH_VENDOR_ID:04x}:" + device.product_id)
yield (_("USB ID"), "046d:" + device.product_id)
if read_all: if read_all:
yield (_("Serial"), device.serial) yield (_("Serial"), device.serial)
@ -530,7 +516,7 @@ def _update_details(button):
if device.wpid: if device.wpid:
yield (_("Wireless PID"), device.wpid) yield (_("Wireless PID"), device.wpid)
if device.product_id: if device.product_id:
yield (_("Product ID"), "046d:" + device.product_id) yield (_("Product ID"), f"{LOGITECH_VENDOR_ID:04x}:" + device.product_id)
hid_version = device.protocol hid_version = device.protocol
yield (_("Protocol"), f"HID++ {hid_version:1.1f}" if hid_version else _("Unknown")) yield (_("Protocol"), f"HID++ {hid_version:1.1f}" if hid_version else _("Unknown"))
if read_all and device.polling_rate: if read_all and device.polling_rate:
@ -553,7 +539,7 @@ def _update_details(button):
flag_bits = device.notification_flags flag_bits = device.notification_flags
if flag_bits is not None: if flag_bits is not None:
flag_names = ( flag_names = (
(f"({_('none')})",) if flag_bits == 0 else _hidpp10_constants.NOTIFICATION_FLAG.flag_names(flag_bits) (f"({_('none')})",) if flag_bits == 0 else hidpp10_constants.NOTIFICATION_FLAG.flag_names(flag_bits)
) )
yield (_("Notifications"), (f"\n{' ':15}").join(flag_names)) yield (_("Notifications"), (f"\n{' ':15}").join(flag_names))
@ -588,7 +574,7 @@ def _update_details(button):
if read_all: if read_all:
_details._current_device = None _details._current_device = None
else: else:
_ui_async(_read_slow, selected_device) ui_async(_read_slow, selected_device)
_details.set_visible(visible) _details.set_visible(visible)
@ -670,7 +656,7 @@ def _update_device_panel(device, panel, buttons, full=False):
panel._battery.set_visible(True) panel._battery.set_visible(True)
battery_next_level = device.battery_info.next_level battery_next_level = device.battery_info.next_level
charging = device.battery_info.charging() if device.battery_info is not None else None charging = device.battery_info.charging() if device.battery_info is not None else None
icon_name = _icons.battery(battery_level, charging) icon_name = icons.battery(battery_level, charging)
panel._battery._icon.set_from_icon_name(icon_name, _INFO_ICON_SIZE) panel._battery._icon.set_from_icon_name(icon_name, _INFO_ICON_SIZE)
panel._battery._icon.set_sensitive(True) panel._battery._icon.set_sensitive(True)
panel._battery._text.set_sensitive(is_online) panel._battery._text.set_sensitive(is_online)
@ -686,9 +672,9 @@ def _update_device_panel(device, panel, buttons, full=False):
if battery_voltage is not None and battery_level is not None: if battery_voltage is not None and battery_level is not None:
text += ", " text += ", "
if battery_level is not None: if battery_level is not None:
text += _(str(battery_level)) if isinstance(battery_level, _NamedInt) else f"{int(battery_level)}%" text += _(str(battery_level)) if isinstance(battery_level, NamedInt) else f"{int(battery_level)}%"
if battery_next_level is not None and not charging: if battery_next_level is not None and not charging:
if isinstance(battery_next_level, _NamedInt): if isinstance(battery_next_level, NamedInt):
text += "<small> (" + _("next reported ") + _(str(battery_next_level)) + ")</small>" text += "<small> (" + _("next reported ") + _(str(battery_next_level)) + ")</small>"
else: else:
text += "<small> (" + _("next reported ") + f"{int(battery_next_level)}%" + ")</small>" text += "<small> (" + _("next reported ") + f"{int(battery_next_level)}%" + ")</small>"
@ -731,7 +717,7 @@ def _update_device_panel(device, panel, buttons, full=False):
if light_level is None: if light_level is None:
panel._lux.set_visible(False) panel._lux.set_visible(False)
else: else:
panel._lux._icon.set_from_icon_name(_icons.lux(light_level), _INFO_ICON_SIZE) panel._lux._icon.set_from_icon_name(icons.lux(light_level), _INFO_ICON_SIZE)
panel._lux._text.set_text(_("%(light_level)d lux") % {"light_level": light_level}) panel._lux._text.set_text(_("%(light_level)d lux") % {"light_level": light_level})
panel._lux.set_visible(True) panel._lux.set_visible(True)
else: else:
@ -744,7 +730,7 @@ def _update_device_panel(device, panel, buttons, full=False):
panel.set_visible(True) panel.set_visible(True)
if full: if full:
_config_panel.update(device, is_online) config_panel.update(device, is_online)
def _update_info_panel(device, full=False): def _update_info_panel(device, full=False):
@ -760,7 +746,7 @@ def _update_info_panel(device, full=False):
assert device assert device
_info._title.set_markup(f"<b>{device.name}</b>") _info._title.set_markup(f"<b>{device.name}</b>")
icon_name = _icons.device_icon_name(device.name, device.kind) icon_name = icons.device_icon_name(device.name, device.kind)
_info._icon.set_from_icon_name(icon_name, _DEVICE_ICON_SIZE) _info._icon.set_from_icon_name(icon_name, _DEVICE_ICON_SIZE)
if device.kind is None: if device.kind is None:
@ -821,7 +807,7 @@ def destroy(_ignore1=None, _ignore2=None):
w, _window = _window, None w, _window = _window, None
w.destroy() w.destroy()
w = None w = None
_config_panel.destroy() config_panel.destroy()
_empty = None _empty = None
_info = None _info = None
@ -871,7 +857,7 @@ def update(device, need_popup=False, refresh=False):
update_device(device, item, selected_device_id, need_popup, full=refresh) update_device(device, item, selected_device_id, need_popup, full=refresh)
elif item: elif item:
_model.remove(item) _model.remove(item)
_config_panel.clean(device) config_panel.clean(device)
# make sure all rows are visible # make sure all rows are visible
_tree.expand_all() _tree.expand_all()
@ -890,14 +876,14 @@ def update_device(device, item, selected_device_id, need_popup, full=False):
else: else:
if battery_voltage is not None and False: # Use levels instead of voltage here if battery_voltage is not None and False: # Use levels instead of voltage here
status_text = f"{int(battery_voltage)}mV" status_text = f"{int(battery_voltage)}mV"
elif isinstance(battery_level, _NamedInt): elif isinstance(battery_level, NamedInt):
status_text = _(str(battery_level)) status_text = _(str(battery_level))
else: else:
status_text = f"{int(battery_level)}%" status_text = f"{int(battery_level)}%"
_model.set_value(item, _COLUMN.STATUS_TEXT, status_text) _model.set_value(item, _COLUMN.STATUS_TEXT, status_text)
charging = device.battery_info.charging() if device.battery_info is not None else None charging = device.battery_info.charging() if device.battery_info is not None else None
icon_name = _icons.battery(battery_level, charging) icon_name = icons.battery(battery_level, charging)
_model.set_value(item, _COLUMN.STATUS_ICON, icon_name) _model.set_value(item, _COLUMN.STATUS_ICON, icon_name)
_model.set_value(item, _COLUMN.NAME, device.codename) _model.set_value(item, _COLUMN.NAME, device.codename)