ui: handle wired devices
This commit is contained in:
parent
aeb8588e06
commit
58823763ea
|
@ -55,6 +55,7 @@ DeviceInfo = namedtuple(
|
|||
'product',
|
||||
'interface',
|
||||
'driver',
|
||||
'isDevice',
|
||||
]
|
||||
)
|
||||
del namedtuple
|
||||
|
@ -89,6 +90,7 @@ def _match(action, device, filter):
|
|||
product_id = filter.get('product_id')
|
||||
interface_number = filter.get('usb_interface')
|
||||
hid_driver = filter.get('hid_driver')
|
||||
isDevice = filter.get('isDevice')
|
||||
|
||||
usb_device = device.find_parent('usb', 'usb_device')
|
||||
# print ("* parent", action, device, "usb:", usb_device)
|
||||
|
@ -134,7 +136,8 @@ def _match(action, device, filter):
|
|||
manufacturer=attrs.get('manufacturer'),
|
||||
product=attrs.get('product'),
|
||||
interface=usb_interface,
|
||||
driver=hid_driver_name
|
||||
driver=hid_driver_name,
|
||||
isDevice=isDevice
|
||||
)
|
||||
return d_info
|
||||
|
||||
|
@ -150,7 +153,8 @@ def _match(action, device, filter):
|
|||
manufacturer=None,
|
||||
product=None,
|
||||
interface=None,
|
||||
driver=None
|
||||
driver=None,
|
||||
isDevice=isDevice
|
||||
)
|
||||
return d_info
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ def wired_devices():
|
|||
|
||||
def notify_on_receivers_glib(callback):
|
||||
"""Watch for matching devices and notifies the callback on the GLib thread."""
|
||||
_hid.monitor_glib(callback, *_RECEIVER_USB_IDS)
|
||||
_hid.monitor_glib(callback, *_RECEIVER_USB_IDS, *_WIRED_DEVICE_IDS)
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -99,7 +99,7 @@ _ex100_receiver = lambda product_id: {
|
|||
'ex100_27mhz_wpid_fix': True
|
||||
}
|
||||
|
||||
_wired_device = lambda product_id: {'vendor_id': 0x046d, 'product_id': product_id, 'usb_interface': 2}
|
||||
_wired_device = lambda product_id: {'vendor_id': 0x046d, 'product_id': product_id, 'usb_interface': 2, 'isDevice': True}
|
||||
|
||||
# standard Unifying receivers (marked with the orange Unifying logo)
|
||||
UNIFYING_RECEIVER_C52B = _unifying_receiver(0xc52b)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import errno as _errno
|
||||
|
||||
from logging import INFO as _INFO
|
||||
from logging import getLogger
|
||||
|
||||
|
@ -31,6 +33,7 @@ class Device(object):
|
|||
def __init__(self, receiver, number, link_notification=None, info=None):
|
||||
assert receiver or info
|
||||
self.receiver = receiver
|
||||
self.may_unpair = False
|
||||
self.isDevice = True # some devices act as receiver so we need a property to distinguish them
|
||||
|
||||
if receiver:
|
||||
|
@ -153,6 +156,7 @@ class Device(object):
|
|||
self.handle = _hid.open_path(self.path)
|
||||
self.product_id = info.product_id
|
||||
self._serial = ''.join(info.serial.split('-')).upper()
|
||||
self.online = True
|
||||
|
||||
if self._protocol is not None:
|
||||
self.features = None if self._protocol < 2.0 else _hidpp20.FeaturesArray(self)
|
||||
|
@ -182,7 +186,7 @@ class Device(object):
|
|||
# if _log.isEnabledFor(_DEBUG):
|
||||
# _log.debug("device %d codename %s", self.number, self._codename)
|
||||
else:
|
||||
self._codename = '? (%s)' % self.wpid
|
||||
self._codename = '? (%s)' % (self.wpid or self.product_id)
|
||||
return self._codename
|
||||
|
||||
@property
|
||||
|
@ -190,7 +194,7 @@ class Device(object):
|
|||
if not self._name:
|
||||
if self.online and self.protocol >= 2.0:
|
||||
self._name = _hidpp20.get_name(self)
|
||||
return self._name or self.codename or ('Unknown device %s' % self.wpid)
|
||||
return self._name or self.codename or ('Unknown device %s' % (self.wpid or self.product_id))
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
|
@ -369,7 +373,10 @@ class Device(object):
|
|||
def __hash__(self):
|
||||
return self.wpid.__hash__()
|
||||
|
||||
__bool__ = __nonzero__ = lambda self: self.wpid is not None and self.number in self.receiver
|
||||
def __bool__(self):
|
||||
return self.wpid is not None and self.number in self.receiver if self.receiver else self.handle is not None
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __str__(self):
|
||||
return '<Device(%d,%s,%s,%s)>' % (
|
||||
|
@ -377,3 +384,29 @@ class Device(object):
|
|||
)
|
||||
|
||||
__unicode__ = __repr__ = __str__
|
||||
|
||||
def notify_devices(self): # no need to notify, as there are none
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def open(self, device_info):
|
||||
"""Opens a Logitech Device found attached to the machine, by Linux device path.
|
||||
:returns: An open file handle for the found receiver, or ``None``.
|
||||
"""
|
||||
try:
|
||||
handle = _base.open_path(device_info.path)
|
||||
if handle:
|
||||
return Device(None, 0, info=device_info)
|
||||
except OSError as e:
|
||||
_log.exception('open %s', device_info)
|
||||
if e.errno == _errno.EACCES:
|
||||
raise
|
||||
except Exception:
|
||||
_log.exception('open %s', device_info)
|
||||
|
||||
def close(self):
|
||||
handle, self.handle = self.handle, None
|
||||
return (handle and _base.close(handle))
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
|
|
@ -26,15 +26,16 @@ from logging import INFO as _INFO
|
|||
from logging import WARNING as _WARNING
|
||||
from logging import getLogger
|
||||
|
||||
from logitech_receiver import Receiver
|
||||
from logitech_receiver import Device, Receiver
|
||||
from logitech_receiver import base as _base
|
||||
from logitech_receiver import listener as _listener
|
||||
from logitech_receiver import notifications as _notifications
|
||||
from logitech_receiver import status as _status
|
||||
from solaar.i18n import _
|
||||
|
||||
from . import configuration
|
||||
|
||||
# from solaar.i18n import _
|
||||
|
||||
_log = getLogger(__name__)
|
||||
del getLogger
|
||||
|
||||
|
@ -94,7 +95,7 @@ class ReceiverListener(_listener.EventsListener):
|
|||
# make sure to clean up in _all_listeners
|
||||
_all_listeners.pop(r.path, None)
|
||||
|
||||
r.status = _('The receiver was unplugged.')
|
||||
# this causes problems but what is it doing (pfps) - r.status = _('The receiver was unplugged.')
|
||||
if r:
|
||||
try:
|
||||
r.close()
|
||||
|
@ -159,7 +160,7 @@ class ReceiverListener(_listener.EventsListener):
|
|||
self.status_changed_callback(device, alert, reason)
|
||||
return
|
||||
|
||||
assert device.receiver == self.receiver
|
||||
# not true for wired devices - assert device.receiver == self.receiver
|
||||
if not device:
|
||||
# Device was unpaired, and isn't valid anymore.
|
||||
# We replace it with a ghost so that the UI has something to work
|
||||
|
@ -270,11 +271,19 @@ _all_listeners = {}
|
|||
|
||||
def _start(device_info):
|
||||
assert _status_callback
|
||||
receiver = Receiver.open(device_info)
|
||||
isDevice = device_info.isDevice
|
||||
if not isDevice:
|
||||
receiver = Receiver.open(device_info)
|
||||
else:
|
||||
receiver = Device.open(device_info)
|
||||
configuration.attach_to(receiver)
|
||||
|
||||
if receiver:
|
||||
rl = ReceiverListener(receiver, _status_callback)
|
||||
rl.start()
|
||||
_all_listeners[device_info.path] = rl
|
||||
if isDevice: # (wired) devices start as active
|
||||
receiver.status.changed(True)
|
||||
return rl
|
||||
|
||||
_log.warn('failed to open %s', device_info)
|
||||
|
@ -288,6 +297,8 @@ def start_all():
|
|||
_log.info('starting receiver listening threads')
|
||||
for device_info in _base.receivers():
|
||||
_process_receiver_event('add', device_info)
|
||||
for device_info in _base.wired_devices():
|
||||
_process_receiver_event('add', device_info)
|
||||
|
||||
|
||||
def stop_all():
|
||||
|
|
|
@ -509,7 +509,7 @@ def create():
|
|||
def update(device, is_online=None):
|
||||
assert _box is not None
|
||||
assert device
|
||||
device_id = (device.receiver.path, device.number)
|
||||
device_id = (device.receiver.path if device.receiver else device.path, device.number)
|
||||
if is_online is None:
|
||||
is_online = bool(device.online)
|
||||
|
||||
|
@ -541,7 +541,7 @@ def clean(device):
|
|||
Needed after the device has been unpaired.
|
||||
"""
|
||||
assert _box is not None
|
||||
device_id = (device.receiver.path, device.number)
|
||||
device_id = (device.receiver.path if device.receiver else device.path, device.number)
|
||||
for k in list(_items.keys()):
|
||||
if k[0:2] == device_id:
|
||||
_box.remove(_items[k])
|
||||
|
|
|
@ -351,28 +351,29 @@ def _pick_device_with_lowest_battery():
|
|||
|
||||
def _add_device(device):
|
||||
assert device
|
||||
assert device.receiver
|
||||
receiver_path = device.receiver.path
|
||||
assert receiver_path
|
||||
# not true for wired devices - assert device.receiver
|
||||
receiver_path = device.receiver.path if device.receiver is not None else device.path
|
||||
# not true for wired devices - assert receiver_path
|
||||
|
||||
index = None
|
||||
index = 0
|
||||
for idx, (path, _ignore, _ignore, _ignore) in enumerate(_devices_info):
|
||||
if path == receiver_path:
|
||||
# the first entry matching the receiver serial should be for the receiver itself
|
||||
index = idx + 1
|
||||
break
|
||||
assert index is not None
|
||||
# assert index is not None
|
||||
|
||||
# proper ordering (according to device.number) for a receiver's devices
|
||||
while True:
|
||||
path, number, _ignore, _ignore = _devices_info[index]
|
||||
if path == _RECEIVER_SEPARATOR[0]:
|
||||
break
|
||||
assert path == receiver_path
|
||||
assert number != device.number
|
||||
if number > device.number:
|
||||
break
|
||||
index = index + 1
|
||||
if device.receiver:
|
||||
# proper ordering (according to device.number) for a receiver's devices
|
||||
while True:
|
||||
path, number, _ignore, _ignore = _devices_info[index]
|
||||
if path == _RECEIVER_SEPARATOR[0]:
|
||||
break
|
||||
assert path == receiver_path
|
||||
assert number != device.number
|
||||
if number > device.number:
|
||||
break
|
||||
index = index + 1
|
||||
|
||||
new_device_info = (receiver_path, device.number, device.name, device.status)
|
||||
assert len(new_device_info) == len(_RECEIVER_SEPARATOR)
|
||||
|
@ -381,7 +382,7 @@ def _add_device(device):
|
|||
# label_prefix = b'\xE2\x94\x84 '.decode('utf-8')
|
||||
label_prefix = ' '
|
||||
|
||||
new_menu_item = Gtk.ImageMenuItem.new_with_label(label_prefix + device.name)
|
||||
new_menu_item = Gtk.ImageMenuItem.new_with_label((label_prefix if device.number else '') + device.name)
|
||||
new_menu_item.set_image(Gtk.Image())
|
||||
new_menu_item.show_all()
|
||||
new_menu_item.connect('activate', _window_popup, receiver_path, device.number)
|
||||
|
@ -519,7 +520,7 @@ def update(device=None):
|
|||
else:
|
||||
# peripheral
|
||||
is_paired = bool(device)
|
||||
receiver_path = device.receiver.path
|
||||
receiver_path = device.receiver.path if device.receiver is not None else device.path
|
||||
index = None
|
||||
for idx, (path, number, _ignore, _ignore) in enumerate(_devices_info):
|
||||
if path == receiver_path and number == device.number:
|
||||
|
@ -529,9 +530,8 @@ def update(device=None):
|
|||
if index is None:
|
||||
index = _add_device(device)
|
||||
_update_menu_item(index, device)
|
||||
else:
|
||||
# was just unpaired
|
||||
if index:
|
||||
else: # was just unpaired or unplugged
|
||||
if index is not None:
|
||||
_remove_device(index)
|
||||
|
||||
menu_items = _menu.get_children()
|
||||
|
|
|
@ -433,6 +433,9 @@ def _device_row(receiver_path, device_number, device=None):
|
|||
assert device_number is not None
|
||||
|
||||
receiver_row = _receiver_row(receiver_path, None if device is None else device.receiver)
|
||||
if receiver_row and device_number == 0: # wired device, receiver row is device row
|
||||
return receiver_row
|
||||
|
||||
item = _model.iter_children(receiver_row)
|
||||
new_child_index = 0
|
||||
while item:
|
||||
|
@ -450,9 +453,10 @@ def _device_row(receiver_path, device_number, device=None):
|
|||
icon_name = _icons.device_icon_name(device.name, device.kind)
|
||||
status_text = None
|
||||
status_icon = None
|
||||
row_data = (
|
||||
receiver_path, device_number, bool(device.online), device.codename, icon_name, status_text, status_icon, device
|
||||
codename = device.codename if device.codename and device.codename[0] != '?' else (
|
||||
device.name.split()[0] if device.name.split() else device.codename
|
||||
)
|
||||
row_data = (receiver_path, device_number, bool(device.online), codename, icon_name, status_text, status_icon, device)
|
||||
assert len(row_data) == len(_TREE_SEPATATOR)
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug('new device row %s at index %d', row_data, new_child_index)
|
||||
|
@ -533,7 +537,10 @@ def _update_details(button):
|
|||
else:
|
||||
# yield ('Codename', device.codename)
|
||||
yield (_('Index'), device.number)
|
||||
yield (_('Wireless PID'), device.wpid)
|
||||
if device.wpid:
|
||||
yield (_('Wireless PID'), device.wpid)
|
||||
if device.product_id:
|
||||
yield (_('USB id'), '046d:' + device.product_id)
|
||||
hid_version = device.protocol
|
||||
yield (_('Protocol'), 'HID++ %1.1f' % hid_version if hid_version else _('Unknown'))
|
||||
if read_all and device.polling_rate:
|
||||
|
@ -654,6 +661,9 @@ def _update_device_panel(device, panel, buttons, full=False):
|
|||
is_online = bool(device.online)
|
||||
panel.set_sensitive(is_online)
|
||||
|
||||
if device.status.get(_K.BATTERY_LEVEL) is None:
|
||||
device.status.read_battery(device)
|
||||
|
||||
battery_level = device.status.get(_K.BATTERY_LEVEL)
|
||||
battery_next_level = device.status.get(_K.BATTERY_NEXT_LEVEL)
|
||||
battery_voltage = device.status.get(_K.BATTERY_VOLTAGE)
|
||||
|
@ -736,7 +746,7 @@ def _update_device_panel(device, panel, buttons, full=False):
|
|||
panel._lux.set_visible(False)
|
||||
|
||||
buttons._pair.set_visible(False)
|
||||
buttons._unpair.set_sensitive(device.receiver.may_unpair)
|
||||
buttons._unpair.set_sensitive(device.receiver.may_unpair if device.receiver else False)
|
||||
buttons._unpair.set_visible(True)
|
||||
|
||||
panel.set_visible(True)
|
||||
|
@ -841,14 +851,14 @@ def update(device, need_popup=False):
|
|||
|
||||
selected_device_id = _find_selected_device_id()
|
||||
|
||||
if device.kind is None:
|
||||
if device.kind is None: # receiver
|
||||
# receiver
|
||||
is_alive = bool(device)
|
||||
item = _receiver_row(device.path, device if is_alive else None)
|
||||
|
||||
if is_alive and item:
|
||||
was_pairing = bool(_model.get_value(item, _COLUMN.STATUS_ICON))
|
||||
is_pairing = bool(device.status.lock_open)
|
||||
is_pairing = (not device.isDevice) and bool(device.status.lock_open)
|
||||
_model.set_value(item, _COLUMN.STATUS_ICON, 'network-wireless' if is_pairing else _CAN_SET_ROW_NONE)
|
||||
|
||||
if selected_device_id == (device.path, 0):
|
||||
|
@ -862,44 +872,45 @@ def update(device, need_popup=False):
|
|||
_model.remove(item)
|
||||
|
||||
else:
|
||||
# peripheral
|
||||
is_paired = bool(device)
|
||||
assert device.receiver
|
||||
assert device.number is not None and device.number > 0, 'invalid device number' + str(device.number)
|
||||
item = _device_row(device.receiver.path, device.number, device if is_paired else None)
|
||||
|
||||
if is_paired and item:
|
||||
was_online = _model.get_value(item, _COLUMN.ACTIVE)
|
||||
is_online = bool(device.online)
|
||||
_model.set_value(item, _COLUMN.ACTIVE, is_online)
|
||||
|
||||
battery_level = device.status.get(_K.BATTERY_LEVEL)
|
||||
battery_voltage = device.status.get(_K.BATTERY_VOLTAGE)
|
||||
if battery_level is None:
|
||||
_model.set_value(item, _COLUMN.STATUS_TEXT, _CAN_SET_ROW_NONE)
|
||||
_model.set_value(item, _COLUMN.STATUS_ICON, _CAN_SET_ROW_NONE)
|
||||
else:
|
||||
if battery_voltage is not None:
|
||||
status_text = '%(battery_voltage)dmV' % {'battery_voltage': battery_voltage}
|
||||
elif isinstance(battery_level, _NamedInt):
|
||||
status_text = _(str(battery_level))
|
||||
else:
|
||||
status_text = '%(battery_percent)d%%' % {'battery_percent': battery_level}
|
||||
_model.set_value(item, _COLUMN.STATUS_TEXT, status_text)
|
||||
|
||||
charging = device.status.get(_K.BATTERY_CHARGING)
|
||||
icon_name = _icons.battery(battery_level, charging)
|
||||
_model.set_value(item, _COLUMN.STATUS_ICON, icon_name)
|
||||
|
||||
if selected_device_id is None or need_popup:
|
||||
select(device.receiver.path, device.number)
|
||||
elif selected_device_id == (device.receiver.path, device.number):
|
||||
full_update = need_popup or was_online != is_online
|
||||
_update_info_panel(device, full=full_update)
|
||||
path = device.receiver.path if device.receiver else device.path
|
||||
assert device.number is not None and device.number >= 0, 'invalid device number' + str(device.number)
|
||||
item = _device_row(path, device.number, device if bool(device) else None)
|
||||
|
||||
if bool(device) and item:
|
||||
update_device(device, item, selected_device_id, need_popup)
|
||||
elif item:
|
||||
_model.remove(item)
|
||||
_config_panel.clean(device)
|
||||
|
||||
# make sure all rows are visible
|
||||
_tree.expand_all()
|
||||
|
||||
|
||||
def update_device(device, item, selected_device_id, need_popup):
|
||||
was_online = _model.get_value(item, _COLUMN.ACTIVE)
|
||||
is_online = bool(device.online)
|
||||
_model.set_value(item, _COLUMN.ACTIVE, is_online)
|
||||
|
||||
battery_level = device.status.get(_K.BATTERY_LEVEL)
|
||||
battery_voltage = device.status.get(_K.BATTERY_VOLTAGE)
|
||||
if battery_level is None:
|
||||
_model.set_value(item, _COLUMN.STATUS_TEXT, _CAN_SET_ROW_NONE)
|
||||
_model.set_value(item, _COLUMN.STATUS_ICON, _CAN_SET_ROW_NONE)
|
||||
else:
|
||||
if battery_voltage is not None:
|
||||
status_text = '%(battery_voltage)dmV' % {'battery_voltage': battery_voltage}
|
||||
elif isinstance(battery_level, _NamedInt):
|
||||
status_text = _(str(battery_level))
|
||||
else:
|
||||
status_text = '%(battery_percent)d%%' % {'battery_percent': battery_level}
|
||||
_model.set_value(item, _COLUMN.STATUS_TEXT, status_text)
|
||||
|
||||
charging = device.status.get(_K.BATTERY_CHARGING)
|
||||
icon_name = _icons.battery(battery_level, charging)
|
||||
_model.set_value(item, _COLUMN.STATUS_ICON, icon_name)
|
||||
|
||||
if selected_device_id is None or need_popup:
|
||||
select(device.receiver.path if device.receiver else device.path, device.number)
|
||||
elif selected_device_id == (device.receiver.path if device.receiver else device.path, device.number):
|
||||
full_update = need_popup or was_online != is_online
|
||||
_update_info_panel(device, full=full_update)
|
||||
|
|
Loading…
Reference in New Issue