Solaar/lib/solaar/cli/show.py

337 lines
16 KiB
Python

## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from logitech_receiver import exceptions, hidpp10, hidpp20
from logitech_receiver import hidpp10_constants as _hidpp10_constants
from logitech_receiver import hidpp20_constants as _hidpp20_constants
from logitech_receiver import receiver as _receiver
from logitech_receiver import settings_templates as _settings_templates
from logitech_receiver.common import NamedInt as _NamedInt
from logitech_receiver.common import strhex as _strhex
from solaar import NAME, __version__
_hidpp10 = hidpp10.Hidpp10()
_hidpp20 = hidpp20.Hidpp20()
def _print_receiver(receiver):
paired_count = receiver.count()
print(receiver.name)
print(" Device path :", receiver.path)
print(f" USB id : 046d:{receiver.product_id}")
print(" Serial :", receiver.serial)
pending = _hidpp10.get_configuration_pending_flags(receiver)
if pending:
print(f" C Pending : {pending:02x}")
if receiver.firmware:
for f in receiver.firmware:
print(" %-11s: %s" % (f.kind, f.version))
print(" Has", paired_count, "paired device(s) out of a maximum of %d." % receiver.max_devices)
if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0:
print(" Has %d successful pairing(s) remaining." % receiver.remaining_pairings())
notification_flags = _hidpp10.get_notification_flags(receiver)
if notification_flags is not None:
if notification_flags:
notification_names = _hidpp10_constants.NOTIFICATION_FLAG.flag_names(notification_flags)
print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags:06X})")
else:
print(" Notifications: (none)")
activity = receiver.read_register(_hidpp10_constants.REGISTERS.devices_activity)
if activity:
activity = [(d, ord(activity[d - 1 : d])) for d in range(1, receiver.max_devices)]
activity_text = ", ".join(("%d=%d" % (d, a)) for d, a in activity if a > 0)
print(" Device activity counters:", activity_text or "(empty)")
def _battery_text(level):
if level is None:
return "N/A"
elif isinstance(level, _NamedInt):
return str(level)
else:
return "%d%%" % level
def _battery_line(dev):
battery = dev.battery()
if battery is not None:
level, nextLevel, status, voltage = battery.level, battery.next_level, battery.status, battery.voltage
text = _battery_text(level)
if voltage is not None:
text = text + f" {voltage}mV "
nextText = "" if nextLevel is None else ", next level " + _battery_text(nextLevel)
print(f" Battery: {text}, {status}{nextText}.")
else:
print(" Battery status unavailable.")
def _print_device(dev, num=None):
assert dev is not None
# try to ping the device to see if it actually exists and to wake it up
try:
dev.ping()
except exceptions.NoSuchDevice:
print(f" {num}: Device not found" or dev.number)
return
if num or dev.number < 8:
print(" %d: %s" % (num or dev.number, dev.name))
else:
print(f"{dev.name}")
print(" Device path :", dev.path)
if dev.wpid:
print(f" WPID : {dev.wpid}")
if dev.product_id:
print(f" USB id : 046d:{dev.product_id}")
print(" Codename :", dev.codename)
print(" Kind :", dev.kind)
if dev.protocol:
print(f" Protocol : HID++ {dev.protocol:1.1f}")
else:
print(" Protocol : unknown (device is offline)")
if dev.polling_rate:
print(" Report Rate :", dev.polling_rate)
print(" Serial number:", dev.serial)
if dev.modelId:
print(" Model ID: ", dev.modelId)
if dev.unitId:
print(" Unit ID: ", dev.unitId)
if dev.firmware:
for fw in dev.firmware:
print(" %11s:" % fw.kind, (fw.name + " " + fw.version).strip())
if dev.power_switch_location:
print(f" The power switch is located on the {dev.power_switch_location}.")
if dev.online:
notification_flags = _hidpp10.get_notification_flags(dev)
if notification_flags is not None:
if notification_flags:
notification_names = _hidpp10_constants.NOTIFICATION_FLAG.flag_names(notification_flags)
print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags:06X}).")
else:
print(" Notifications: (none).")
device_features = _hidpp10.get_device_features(dev)
if device_features is not None:
if device_features:
device_features_names = _hidpp10_constants.DEVICE_FEATURES.flag_names(device_features)
print(f" Features: {', '.join(device_features_names)} (0x{device_features:06X})")
else:
print(" Features: (none)")
if dev.online and dev.features:
print(f" Supports {len(dev.features)} HID++ 2.0 features:")
dev_settings = []
_settings_templates.check_feature_settings(dev, dev_settings)
for feature, index in dev.features.enumerate():
flags = dev.request(0x0000, feature.bytes(2))
flags = 0 if flags is None else ord(flags[1:2])
flags = _hidpp20_constants.FEATURE_FLAG.flag_names(flags)
version = dev.features.get_feature_version(int(feature))
version = version if version else 0
print(" %2d: %-22s {%04X} V%s %s " % (index, feature, feature, version, ", ".join(flags)))
if feature == _hidpp20_constants.FEATURE.HIRES_WHEEL:
wheel = _hidpp20.get_hires_wheel(dev)
if wheel:
multi, has_invert, has_switch, inv, res, target, ratchet = wheel
print(f" Multiplier: {multi}")
if has_invert:
print(" Has invert:", "Inverse wheel motion" if inv else "Normal wheel motion")
if has_switch:
print(" Has ratchet switch:", "Normal wheel mode" if ratchet else "Free wheel mode")
if res:
print(" High resolution mode")
else:
print(" Low resolution mode")
if target:
print(" HID++ notification")
else:
print(" HID notification")
elif feature == _hidpp20_constants.FEATURE.MOUSE_POINTER:
mouse_pointer = _hidpp20.get_mouse_pointer_info(dev)
if mouse_pointer:
print(f" DPI: {mouse_pointer['dpi']}")
print(f" Acceleration: {mouse_pointer['acceleration']}")
if mouse_pointer["suggest_os_ballistics"]:
print(" Use OS ballistics")
else:
print(" Override OS ballistics")
if mouse_pointer["suggest_vertical_orientation"]:
print(" Provide vertical tuning, trackball")
else:
print(" No vertical tuning, standard mice")
elif feature == _hidpp20_constants.FEATURE.VERTICAL_SCROLLING:
vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev)
if vertical_scrolling_info:
print(f" Roller type: {vertical_scrolling_info['roller']}")
print(f" Ratchet per turn: {vertical_scrolling_info['ratchet']}")
print(f" Scroll lines: {vertical_scrolling_info['lines']}")
elif feature == _hidpp20_constants.FEATURE.HI_RES_SCROLLING:
scrolling_mode, scrolling_resolution = _hidpp20.get_hi_res_scrolling_info(dev)
if scrolling_mode:
print(" Hi-res scrolling enabled")
else:
print(" Hi-res scrolling disabled")
if scrolling_resolution:
print(f" Hi-res scrolling multiplier: {scrolling_resolution}")
elif feature == _hidpp20_constants.FEATURE.POINTER_SPEED:
pointer_speed = _hidpp20.get_pointer_speed_info(dev)
if pointer_speed:
print(f" Pointer Speed: {pointer_speed}")
elif feature == _hidpp20_constants.FEATURE.LOWRES_WHEEL:
wheel_status = _hidpp20.get_lowres_wheel_status(dev)
if wheel_status:
print(f" Wheel Reports: {wheel_status}")
elif feature == _hidpp20_constants.FEATURE.NEW_FN_INVERSION:
inversion = _hidpp20.get_new_fn_inversion(dev)
if inversion:
inverted, default_inverted = inversion
print(" Fn-swap:", "enabled" if inverted else "disabled")
print(" Fn-swap default:", "enabled" if default_inverted else "disabled")
elif feature == _hidpp20_constants.FEATURE.HOSTS_INFO:
host_names = _hidpp20.get_host_names(dev)
for host, (paired, name) in host_names.items():
print(f" Host {host} ({'paired' if paired else 'unpaired'}): {name}")
elif feature == _hidpp20_constants.FEATURE.DEVICE_NAME:
print(f" Name: {_hidpp20.get_name(dev)}")
print(f" Kind: {_hidpp20.get_kind(dev)}")
elif feature == _hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME:
print(f" Friendly Name: {_hidpp20.get_friendly_name(dev)}")
elif feature == _hidpp20_constants.FEATURE.DEVICE_FW_VERSION:
for fw in _hidpp20.get_firmware(dev):
extras = _strhex(fw.extras) if fw.extras else ""
print(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}")
ids = _hidpp20.get_ids(dev)
if ids:
unitId, modelId, tid_map = ids
print(f" Unit ID: {unitId} Model ID: {modelId} Transport IDs: {tid_map}")
elif (
feature == _hidpp20_constants.FEATURE.REPORT_RATE
or feature == _hidpp20_constants.FEATURE.EXTENDED_ADJUSTABLE_REPORT_RATE
):
print(f" Report Rate: {_hidpp20.get_polling_rate(dev)}")
elif feature == _hidpp20_constants.FEATURE.CONFIG_CHANGE:
response = dev.feature_request(_hidpp20_constants.FEATURE.CONFIG_CHANGE, 0x10)
print(f" Configuration: {response.hex()}")
elif feature == _hidpp20_constants.FEATURE.REMAINING_PAIRING:
print(" Remaining Pairings: %d" % _hidpp20.get_remaining_pairing(dev))
elif feature == _hidpp20_constants.FEATURE.ONBOARD_PROFILES:
if _hidpp20.get_onboard_mode(dev) == _hidpp20_constants.ONBOARD_MODES.MODE_HOST:
mode = "Host"
else:
mode = "On-Board"
print(f" Device Mode: {mode}")
elif hidpp20.battery_functions.get(feature, None):
print("", end=" ")
_battery_line(dev)
for setting in dev_settings:
if setting.feature == feature:
if (
setting._device
and getattr(setting._device, "persister", None)
and setting._device.persister.get(setting.name) is not None
):
v = setting.val_to_string(setting._device.persister.get(setting.name))
print(f" {setting.label} (saved): {v}")
try:
v = setting.val_to_string(setting.read(False))
except exceptions.FeatureCallError as e:
v = "HID++ error " + str(e)
except AssertionError as e:
v = "AssertionError " + str(e)
print(f" {setting.label} : {v}")
if dev.online and dev.keys:
print(f" Has {len(dev.keys)} reprogrammable keys:")
for k in dev.keys:
# TODO: add here additional variants for other REPROG_CONTROLS
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)))
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))
gmask_fmt = ",".join(k.group_mask)
gmask_fmt = gmask_fmt if gmask_fmt else "empty"
print(" %s, pos:%d, group:%1d, group mask:%s" % (", ".join(k.flags), k.pos, k.group, gmask_fmt))
report_fmt = ", ".join(k.mapping_flags)
report_fmt = report_fmt if report_fmt else "default"
print(f" reporting: {report_fmt}")
if dev.online and dev.remap_keys:
print(f" Has {len(dev.remap_keys)} persistent remappable keys:")
for k in dev.remap_keys:
print(" %2d: %-26s => %s%s" % (k.index, k.key, k.action, " (remapped)" if k.cidStatus else ""))
if dev.online and dev.gestures:
print(
" Has %d gesture(s), %d param(s) and %d spec(s):"
% (len(dev.gestures.gestures), len(dev.gestures.params), len(dev.gestures.specs))
)
for k in dev.gestures.gestures.values():
print(
" %-26s Enabled(%4s): %-5s Diverted:(%4s) %s"
% (k.gesture, k.index, k.enabled(), k.diversion_index, k.diverted())
)
for k in dev.gestures.params.values():
print(" %-26s Value (%4s): %s [Default: %s]" % (k.param, k.index, k.value, k.default_value))
for k in dev.gestures.specs.values():
print(" %-26s Spec (%4s): %s" % (k.spec, k.id, k.value))
if dev.online:
_battery_line(dev)
else:
print(" Battery: unknown (device is offline).")
def run(devices, args, find_receiver, find_device):
assert devices
assert args.device
print(f"{NAME.lower()} version {__version__}")
print("")
device_name = args.device.lower()
if device_name == "all":
for d in devices:
if isinstance(d, _receiver.Receiver):
_print_receiver(d)
count = d.count()
if count:
for dev in d:
print("")
_print_device(dev, dev.number)
count -= 1
if not count:
break
print("")
else:
print("")
_print_device(d)
return
dev = find_receiver(devices, device_name)
if dev and not dev.isDevice:
_print_receiver(dev)
return
dev = next(find_device(devices, device_name), None)
if not dev:
raise Exception(f"no device found matching '{device_name}'")
_print_device(dev)