diff --git a/lib/logitech/unifying_receiver/hidpp10.py b/lib/logitech/unifying_receiver/hidpp10.py index 055d1497..65ba3c50 100644 --- a/lib/logitech/unifying_receiver/hidpp10.py +++ b/lib/logitech/unifying_receiver/hidpp10.py @@ -105,8 +105,8 @@ def get_register(device, name, default_number=-1): if reply: return reply - if not known_register and device.ping(): - _log.warn("%s: failed to read '%s' from default register 0x%02X, blacklisting", + if not known_register and device.kind is not None and device.online: + _log.warn("%s: failed to read register '%s' (0x%02X), blacklisting", device, name, default_number) device.registers[name] = -default_number diff --git a/lib/logitech/unifying_receiver/hidpp20.py b/lib/logitech/unifying_receiver/hidpp20.py index b7b428ef..0fe1feb1 100644 --- a/lib/logitech/unifying_receiver/hidpp20.py +++ b/lib/logitech/unifying_receiver/hidpp20.py @@ -147,7 +147,7 @@ class FeaturesArray(object): if self.features is not None: return True - if hasattr(self.device, 'status') and not bool(self.device.status): + if not self.device.online: # device is not connected right now, will have to try later return False diff --git a/lib/logitech/unifying_receiver/receiver.py b/lib/logitech/unifying_receiver/receiver.py index 154c6ec3..094d7932 100644 --- a/lib/logitech/unifying_receiver/receiver.py +++ b/lib/logitech/unifying_receiver/receiver.py @@ -26,11 +26,12 @@ MAX_PAIRED_DEVICES = 6 class PairedDevice(object): - def __init__(self, receiver, number): + def __init__(self, receiver, number, link_notification=None): assert receiver self.receiver = _proxy(receiver) assert number > 0 and number <= receiver.max_devices self.number = number + self.online = None self.wpid = None self.polling_rate = 0 @@ -38,56 +39,61 @@ class PairedDevice(object): self._kind = None self._codename = None self._name = None + self._protocol = None + self._serial = None unifying = self.receiver.unifying_supported - if unifying: - # force a reading of the codename - if self.codename is None: - raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read codename") + if link_notification is None: + if unifying: + # force a reading of the codename + pair_info = receiver.read_register(0x2B5, 0x20 + number - 1) + if pair_info is None: + raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read pair info") - pair_info = receiver.request(0x83B5, 0x20 + number - 1) - if pair_info is None: - raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read pair info") - - self.wpid = _strhex(pair_info[3:5]) - kind = ord(pair_info[7:8]) & 0x0F - self._kind = _hidpp10.DEVICE_KIND[kind] - self.polling_rate = ord(pair_info[2:3]) - else: - # guesswork... - descriptor = _descriptors.DEVICES.get(self.receiver.product_id) - if descriptor is None: - self._codename = self.receiver.product_id - # actually there IS a device, just that we can't identify it - # raise _base.NoSuchDevice(nuber=number, receiver=receiver, product_id=receiver.product_id, failed="no descriptor") - self._name = 'Unknown device ' + self._codename + self.wpid = _strhex(pair_info[3:5]) + kind = ord(pair_info[7:8]) & 0x0F + self._kind = _hidpp10.DEVICE_KIND[kind] + self.polling_rate = ord(pair_info[2:3]) else: - self._codename = descriptor.codename - self._name = descriptor.name + # guesswork... look for the product id in the descriptors + descriptor = _descriptors.DEVICES.get(self.receiver.product_id) + if descriptor is None: + self._codename = self.receiver.product_id + # actually there IS a device, just that we can't identify it + # raise _base.NoSuchDevice(nuber=number, receiver=receiver, product_id=receiver.product_id, failed="no descriptor") + self._name = 'Unknown device ' + self._codename + else: + self._codename = descriptor.codename + self._name = descriptor.name - device_info = self.receiver.request(0x83B5, 0x04) - assert device_info, "failed to read Nano device info" - self.wpid = _strhex(device_info[3:5]) - # self._kind = descriptor.kind - self.polling_rate = 0 + device_info = self.receiver.read_register(0x2B5, 0x04) + if device_info is None: + raise _base.NoSuchDevice(nuber=number, receiver=receiver, error="read Nano wpid") + self.wpid = _strhex(device_info[3:5]) + # self._kind = descriptor.kind + self.serial = self.receiver.serial + else: + self.wpid = _strhex(link_notification.data[2:3] + link_notification.data[1:2]) + assert link_notification.address == (0x04 if unifying else 0x03) + kind = ord(link_notification.data[1:2]) & 0x0F + self._kind = _hidpp10.DEVICE_KIND[kind] + self.online = bool(ord(link_notification.data[0:1]) & 0x40) # the wpid is necessary to properly identify wireless link on/off notifications + # also it gets set to None when the device is unpaired assert self.wpid is not None, "failed to read wpid: device %d of %s" % (number, receiver) - # the codename is necessary to guess all other info - assert self._codename is not None, "failed to read codename: device %d of %s" % (number, receiver) # knowing the protocol as soon as possible helps reading all other info # and avoids an unecessary ping - descriptor = _descriptors.DEVICES.get(self.codename) - if descriptor is None: - _log.warn("device without descriptor found: %s (%d of %s)", self.codename, number, receiver) - self._protocol = None if unifying else 1.0 - else: - self._protocol = descriptor.protocol if unifying else 1.0 # may be None + if self._codename is not None: + descriptor = _descriptors.DEVICES.get(self._codename) + if descriptor is None: + _log.warn("device without descriptor found: %s (%d of %s)", self._codename, number, receiver) + self._protocol = None if unifying else 1.0 + else: + self._protocol = descriptor.protocol if unifying else 1.0 # may be None self._power_switch = None if unifying else '(unknown)' - self.serial = _hidpp10.get_serial(self) if unifying else self.receiver.serial - self._firmware = None self._keys = None @@ -106,10 +112,22 @@ class PairedDevice(object): def protocol(self): if self._protocol is None: self._protocol = _base.ping(self.receiver.handle, self.number) + # if the ping failed, the peripheral is (almost) certainly offline + self.online = self._protocol is not None + + # use the descriptor only as a fallback, because it may not be 100% correct + descriptor = _descriptors.DEVICES.get(self.codename) if self._protocol is None: - descriptor = _descriptors.DEVICES.get(self.codename) if descriptor and descriptor.protocol is not None: self._protocol = descriptor.protocol + else: + if descriptor: + if descriptor.protocol is None: + _log.info("%s: descriptor has no protocol, should be %0.1f", self, self._protocol) + elif descriptor.protocol != self._protocol: + _log.error("%s: descriptor has wrong protocol %0.1f, should be %0.1f", + self, descriptor.protocol, self._protocol) + # _log.debug("device %d protocol %s", self.number, self._protocol) return self._protocol or 0 @@ -131,12 +149,12 @@ class PairedDevice(object): if codename: self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') # _log.debug("device %d codename %s", self.number, self._codename) - return self._codename + return self._codename or '?' @property def name(self): if self._name is None: - if self.protocol >= 2.0: + if self.protocol >= 2.0 and self.online: self._name = _hidpp20.get_name(self) if self._name is None: descriptor = _descriptors.DEVICES.get(self.codename) @@ -155,7 +173,7 @@ class PairedDevice(object): # self._kind = _hidpp10.DEVICE_KIND[kind] # if self.wpid is None: # self.wpid = _strhex(pair_info[3:5]) - if self.protocol >= 2.0: + if self.protocol >= 2.0 and self.online: self._kind = _hidpp20.get_kind(self) if self._kind is None: descriptor = _descriptors.DEVICES.get(self.codename) @@ -165,26 +183,25 @@ class PairedDevice(object): @property def firmware(self): - if self._firmware is None: + if self._firmware is None and self.online: if self.protocol < 2.0: self._firmware = _hidpp10.get_firmware(self) else: self._firmware = _hidpp20.get_firmware(self) return self._firmware or () - # @property - # def serial(self): - # if self._serial is None: - # if self.receiver.unifying_supported: - # self._serial = _hidpp10.get_serial(self) - # else: - # self._serial = self.receiver.serial - # return self._serial or '?' + @property + def serial(self): + if self._serial is None: + assert self.receiver.unifying_supported + # otherwise it should have been set in the constructor + self._serial = _hidpp10.get_serial(self) + return self._serial or '?' @property def keys(self): if self._keys is None: - if self.protocol >= 2.0: + if self.protocol >= 2.0 and self.online: self._keys = _hidpp20.get_keys(self) or () return self._keys @@ -207,14 +224,14 @@ class PairedDevice(object): else: self._settings = [s(self) for s in descriptor.settings] - if self.features: + if self.online and self.features: _descriptors.check_features(self, self._settings) return self._settings def enable_notifications(self, enable=True): """Enable or disable device (dis)connection notifications on this receiver.""" - if not self.receiver or not self.receiver.handle or self.protocol >= 2.0: + if not bool(self.receiver) or self.protocol >= 2.0: return False if enable: @@ -245,7 +262,10 @@ class PairedDevice(object): return _hidpp20.feature_request(self, feature, function, *params) def ping(self): - return _base.ping(self.receiver.handle, self.number) is not None + """Checks if the device is online, returns True of False""" + protocol = _base.ping(self.receiver.handle, self.number) + self.online = protocol is not None + return self.online def __index__(self): return self.number @@ -260,6 +280,8 @@ class PairedDevice(object): def __hash__(self): return self.serial.__hash__() + __bool__ = __nonzero__ = lambda self: self.wpid is not None and self.number in self.receiver + def __str__(self): return '' % (self.number, self.wpid, self.codename or '?') __unicode__ = __repr__ = __str__ @@ -349,12 +371,15 @@ class Receiver(object): if not self.write_register(0x02, 0x02): _log.warn("%s: failed to trigger device link notifications", self) - def register_new_device(self, number): + def register_new_device(self, number, notification=None): if self._devices.get(number) is not None: raise IndexError("%s: device number %d already registered" % (self, number)) + assert notification is None or notification.devnumber == number + assert notification is None or notification.sub_id == 0x41 + try: - dev = PairedDevice(self, number) + dev = PairedDevice(self, number, notification) assert dev.wpid _log.info("%s: found new device %d (%s)", self, number, dev.wpid) self._devices[number] = dev @@ -420,6 +445,7 @@ class Receiver(object): if reply: # invalidate the device dev.wpid = None + dev.online = False del self._devices[key] _log.warn("%s unpaired device %s", self, dev) else: diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index 8186bbeb..ab7855d1 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -190,8 +190,10 @@ class DeviceStatus(dict): self._changed(alert=alert, reason=reason, timestamp=timestamp) def read_battery(self, timestamp=None): - d = self._device - if d and self._active: + if self._active: + d = self._device + assert d + if d.protocol < 2.0: battery = _hidpp10.get_battery(d) else: @@ -213,29 +215,32 @@ class DeviceStatus(dict): self[KEYS.BATTERY_CHARGING] = None self._changed() - def _changed(self, active=True, alert=ALERT.NONE, reason=None, timestamp=None): + def _changed(self, active=None, alert=ALERT.NONE, reason=None, timestamp=None): assert self._changed_callback - assert self._device d = self._device - was_active, self._active = self._active, active - if active: - if not was_active: - # Make sure to set notification flags on the device, they - # get cleared when the device is turned off (but not when the device - # goes idle, and we can't tell the difference right now). - self[KEYS.NOTIFICATION_FLAGS] = d.enable_notifications() - if self.configuration: - self.configuration.attach_to(d) - else: - if was_active: - battery = self.get(KEYS.BATTERY_LEVEL) - self.clear() - # If we had a known battery level before, assume it's not going - # to change much while the device is offline. - if battery is not None: - self[KEYS.BATTERY_LEVEL] = battery + assert d - if self.updated == 0 and active: + if active is not None: + d.online = active + was_active, self._active = self._active, active + if active: + if not was_active: + # Make sure to set notification flags on the device, they + # get cleared when the device is turned off (but not when the device + # goes idle, and we can't tell the difference right now). + self[KEYS.NOTIFICATION_FLAGS] = d.enable_notifications() + if self.configuration: + self.configuration.attach_to(d) + else: + if was_active: + battery = self.get(KEYS.BATTERY_LEVEL) + self.clear() + # If we had a known battery level before, assume it's not going + # to change much while the device is offline. + if battery is not None: + self[KEYS.BATTERY_LEVEL] = battery + + if self.updated == 0 and active == True: # if the device is active on the very first status notification, # (meaning just when the program started or a new receiver was just # detected), pop-up a notification about it @@ -330,8 +335,10 @@ class DeviceStatus(dict): if n.address == 0x02: # device un-paired self.clear() - self._device.status = None - self._changed(False, ALERT.ALL, 'unpaired') + dev = self._device + dev.wpid = None + dev.status = None + self._changed(active=False, alert=ALERT.ALL, reason='unpaired') else: _log.warn("%s: disconnection with unknown type %02X: %s", self._device, n.address, n) return True @@ -342,6 +349,10 @@ class DeviceStatus(dict): else 'eQuad' if n.address == 0x03 else None) if protocol_name: + if _log.isEnabledFor(_DEBUG): + wpid = _strhex(n.data[2:3] + n.data[1:2]) + assert wpid == self._device.wpid, "%s wpid mismatch, got %s" % (self._device, wpid) + flags = ord(n.data[:1]) & 0xF0 link_encrypyed = bool(flags & 0x20) link_established = not (flags & 0x40) @@ -351,21 +362,21 @@ class DeviceStatus(dict): _log.debug("%s: %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s", self._device, protocol_name, sw_present, link_encrypyed, link_established, has_payload) self[KEYS.LINK_ENCRYPTED] = link_encrypyed - self._changed(link_established) + self._changed(active=link_established) - if protocol_name == 'eQuad': - # some Nano devices might not have been initialized fully - if self._device._kind is None: - kind = ord(n.data[:1]) & 0x0F - self._device._kind = _hidpp10.DEVICE_KIND[kind] - assert self._device.wpid == _strhex(n.data[2:3] + n.data[1:2]) + # if protocol_name == 'eQuad': + # # some Nano devices might not have been initialized fully + # if self._device._kind is None: + # kind = ord(n.data[:1]) & 0x0F + # self._device._kind = _hidpp10.DEVICE_KIND[kind] + # assert self._device.wpid == _strhex(n.data[2:3] + n.data[1:2]) + + # if the device just came online, read the battery charge + if self._active and KEYS.BATTERY_LEVEL not in self: + self.read_battery() else: _log.warn("%s: connection notification with unknown protocol %02X: %s", self._device.number, n.address, n) - # if the device just came online, read the battery charge - if self._active and KEYS.BATTERY_LEVEL not in self: - self.read_battery() - return True if n.sub_id == 0x49: @@ -380,7 +391,7 @@ class DeviceStatus(dict): if _log.isEnabledFor(_DEBUG): _log.debug("%s: device powered on", self._device) reason = str(self) or 'powered on' - self._changed(alert=ALERT.NOTIFICATION, reason=reason) + self._changed(active=True, alert=ALERT.NOTIFICATION, reason=reason) else: _log.info("%s: unknown %s", self._device, n) return True @@ -410,7 +421,7 @@ class DeviceStatus(dict): if _log.isEnabledFor(_DEBUG): _log.debug("wireless status: %s", n) if n.data[0:3] == b'\x01\x01\x01': - self._changed(alert=ALERT.NOTIFICATION, reason='powered on') + self._changed(active=True, alert=ALERT.NOTIFICATION, reason='powered on') else: _log.info("%s: unknown WIRELESS %s", self._device, n) else: @@ -426,11 +437,11 @@ class DeviceStatus(dict): if n.address == 0x00: self[KEYS.LIGHT_LEVEL] = None self[KEYS.BATTERY_CHARGING] = None - self._changed() + self._changed(active=True) elif n.address == 0x10: self[KEYS.LIGHT_LEVEL] = lux self[KEYS.BATTERY_CHARGING] = lux > 200 - self._changed() + self._changed(active=True) elif n.address == 0x20: _log.debug("%s: Light Check button pressed", self._device) self._changed(alert=ALERT.SHOW_WINDOW) diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index 2e9ca64c..621565c1 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -18,7 +18,7 @@ from logitech.unifying_receiver import (Receiver, # from collections import namedtuple -_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['receiver', 'number', 'name', 'kind', 'serial', 'status']) +_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['receiver', 'number', 'name', 'kind', 'serial', 'status', 'online']) _GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ del namedtuple @@ -30,7 +30,8 @@ def _ghost(device): name=device.name, kind=device.kind, serial=device.serial, - status=None) + status=None, + online=False) # # @@ -109,8 +110,7 @@ class ReceiverListener(_listener.EventsListener): for number in range(1, 6): if number in self.receiver: dev = self.receiver[number] - assert dev - if dev.status is not None: + if dev and dev.status is not None: dev.status.poll(timestamp) except Exception as e: _log.exception("polling", e) @@ -118,9 +118,15 @@ class ReceiverListener(_listener.EventsListener): def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None): assert device is not None if _log.isEnabledFor(_DEBUG): - _log.debug("%s: status_changed %s: %s, %s (%X) %s", self.receiver, device, - 'active' if device.status else 'inactive', - device.status, alert, reason or '') + if device.kind is None: + _log.debug("status_changed %s: %s, %s (%X) %s", device, + 'present' if bool(device) else 'removed', + device.status, alert, reason or '') + else: + _log.debug("status_changed %s: %s %s, %s (%X) %s", device, + 'paired' if bool(device) else 'unpaired', + 'online' if device.online else 'offline', + device.status, alert, reason or '') if device.kind is None: assert device == self.receiver @@ -129,16 +135,15 @@ class ReceiverListener(_listener.EventsListener): return assert device.receiver == self.receiver - - if device.status is None: + if not device: # device was unpaired, and since the object is weakref'ed # it won't be valid for much longer - _log.info("device %s was unpaired, ghosting", device) + _log.warn("device %s was unpaired, ghosting", device) device = _ghost(device) self.status_changed_callback(device, alert, reason) - if device.status is None: + if not device: # the device was just unpaired, need to update the # status of the receiver as well self.status_changed_callback(self.receiver) @@ -155,28 +160,33 @@ class ReceiverListener(_listener.EventsListener): # a device notification assert n.devnumber > 0 and n.devnumber <= self.receiver.max_devices already_known = n.devnumber in self.receiver - dev = self.receiver[n.devnumber] + if not already_known and n.sub_id == 0x41: + dev = self.receiver.register_new_device(n.devnumber, n) + else: + dev = self.receiver[n.devnumber] if not dev: _log.warn("%s: received %s for invalid device %d: %r", self.receiver, n, n.devnumber, dev) return if not already_known: - # read these as soon as possible, they will be used everywhere - dev.protocol, dev.codename + # _log.info("%s triggered new device %s", n, dev) dev.status = _status.DeviceStatus(dev, self._status_changed) dev.status.configuration = configuration # the receiver changed status as well self._status_changed(self.receiver) - # status may be None if the device has just been unpaired - if dev.status is not None: - dev.status.process_notification(n) - if self.receiver.status.lock_open and not already_known: - # this should be the first notification after a device was paired - assert n.sub_id == 0x41 and n.address == 0x04 - _log.info("%s: pairing detected new device", self.receiver) - self.receiver.status.new_device = dev + assert dev + assert dev.status is not None + dev.status.process_notification(n) + if self.receiver.status.lock_open and not already_known: + # this should be the first notification after a device was paired + assert n.sub_id == 0x41 and n.address == 0x04 + _log.info("%s: pairing detected new device", self.receiver) + self.receiver.status.new_device = dev + else: + if dev.online is None: + dev.ping() def __str__(self): return '' % (self.receiver.path, self.receiver.handle) diff --git a/lib/solaar/ui/__init__.py b/lib/solaar/ui/__init__.py index e3bfcb03..8fcc6d2b 100644 --- a/lib/solaar/ui/__init__.py +++ b/lib/solaar/ui/__init__.py @@ -63,7 +63,7 @@ def destroy(): from logitech.unifying_receiver.status import ALERT def _status_changed(device, alert, reason): assert device is not None - _log.info("status changed: %s, %s, %s", device, alert, reason) + _log.info("status changed: %s (%s) %s", device, alert, reason) tray.update(device) if alert & ALERT.ATTENTION: diff --git a/lib/solaar/ui/config_panel.py b/lib/solaar/ui/config_panel.py index d376f2d1..3c207ac5 100644 --- a/lib/solaar/ui/config_panel.py +++ b/lib/solaar/ui/config_panel.py @@ -127,7 +127,7 @@ def _create_sbox(s): return sbox -def _update_setting_item(sbox, value, is_active=True): +def _update_setting_item(sbox, value, is_online=True): _, failed, spinner, control = sbox.get_children() spinner.set_visible(False) spinner.stop() @@ -135,7 +135,7 @@ def _update_setting_item(sbox, value, is_active=True): # print ("update", control, "with new value", value) if value is None: control.set_sensitive(False) - failed.set_visible(is_active) + failed.set_visible(is_online) return failed.set_visible(False) diff --git a/lib/solaar/ui/tray.py b/lib/solaar/ui/tray.py index e51c720b..4c68a68c 100644 --- a/lib/solaar/ui/tray.py +++ b/lib/solaar/ui/tray.py @@ -391,16 +391,19 @@ def _remove_receiver(receiver): index += 1 -def _update_menu_item(index, device_status): +def _update_menu_item(index, device): + assert device + assert device.status is not None + menu_items = _menu.get_children() menu_item = menu_items[index] - level = device_status.get(_K.BATTERY_LEVEL) - charging = device_status.get(_K.BATTERY_CHARGING) + level = device.status.get(_K.BATTERY_LEVEL) + charging = device.status.get(_K.BATTERY_CHARGING) icon_name = _icons.battery(level, charging) image_widget = menu_item.get_image() - image_widget.set_sensitive(bool(device_status)) + image_widget.set_sensitive(bool(device.online)) _update_menu_icon(image_widget, icon_name) # @@ -455,17 +458,17 @@ def update(device=None): else: # peripheral - is_alive = device.status is not None + is_paired = bool(device) receiver_path = device.receiver.path index = None for idx, (path, serial, name, _, _) in enumerate(_devices_info): if path == receiver_path and serial == device.serial: index = idx - if is_alive: + if is_paired: if index is None: index = _add_device(device) - _update_menu_item(index, device.status) + _update_menu_item(index, device) else: # was just unpaired if index: diff --git a/lib/solaar/ui/window.py b/lib/solaar/ui/window.py index 11344b25..1704ca34 100644 --- a/lib/solaar/ui/window.py +++ b/lib/solaar/ui/window.py @@ -431,6 +431,7 @@ def _update_details(button): assert button visible = button.get_active() device = _find_selected_device() + assert device if visible: _details._text.set_markup('reading...') @@ -465,6 +466,8 @@ def _update_details(button): def _update_receiver_panel(receiver, panel, buttons, full=False): + assert receiver + devices_count = len(receiver) if receiver.max_devices > 1: if devices_count == 0: @@ -498,8 +501,9 @@ def _update_receiver_panel(receiver, panel, buttons, full=False): def _update_device_panel(device, panel, buttons, full=False): - is_active = bool(device.status) - panel.set_sensitive(is_active) + assert device + is_online = bool(device.online) + panel.set_sensitive(is_online) battery_level = device.status.get(_K.BATTERY_LEVEL) if battery_level is None: @@ -515,15 +519,15 @@ def _update_device_panel(device, panel, buttons, full=False): panel._battery._icon.set_sensitive(True) text = '%d%%' % battery_level - if is_active: + if is_online: if charging: text += ' (charging)' else: text += ' (last known)' - panel._battery._text.set_sensitive(is_active) + panel._battery._text.set_sensitive(is_online) panel._battery._text.set_markup(text) - if is_active: + if is_online: not_secure = device.status.get(_K.LINK_ENCRYPTED) == False if not_secure: panel._secure._text.set_text('not encrypted') @@ -539,7 +543,7 @@ def _update_device_panel(device, panel, buttons, full=False): panel._secure._icon.set_visible(False) panel._secure.set_tooltip_text('') - if is_active: + if is_online: light_level = device.status.get(_K.LIGHT_LEVEL) if light_level is None: panel._lux.set_visible(False) @@ -557,7 +561,7 @@ def _update_device_panel(device, panel, buttons, full=False): panel.set_visible(True) if full: - _config_panel.update(panel._config, device, is_active) + _config_panel.update(device, is_online) def _update_info_panel(device, full=False): @@ -567,19 +571,24 @@ def _update_info_panel(device, full=False): _empty.set_visible(True) return - is_active = bool(device.status) + # a receiver must be valid + # a device must be paired + assert device _info._title.set_markup('%s' % device.name) - _info._title.set_sensitive(is_active) icon_name = _icons.device_icon_name(device.name, device.kind) _info._icon.set_from_icon_name(icon_name, _DEVICE_ICON_SIZE) - _info._icon.set_sensitive(is_active) if device.kind is None: _info._device.set_visible(False) + _info._icon.set_sensitive(True) + _info._title.set_sensitive(True) _update_receiver_panel(device, _info._receiver, _info._buttons, full) else: _info._receiver.set_visible(False) + is_online = bool(device.online) + _info._icon.set_sensitive(is_online) + _info._title.set_sensitive(is_online) _update_device_panel(device, _info._device, _info._buttons, full) _empty.set_visible(False) @@ -664,10 +673,16 @@ def update(device, need_popup=False): else: # peripheral - is_alive = device.status is not None - item = _device_row(device.receiver.path, device.serial, device if is_alive else None) - if is_alive and item: - _model.set_value(item, _COLUMN.ACTIVE, bool(device.status)) + is_paired = bool(device) + assert device.receiver + assert device.serial + item = _device_row(device.receiver.path, device.serial, 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) if battery_level is None: _model.set_value(item, _COLUMN.STATUS_ICON, '') @@ -679,7 +694,8 @@ def update(device, need_popup=False): if selected_device_id is None: select(device.receiver.path, device.serial) elif selected_device_id == device.serial: - _update_info_panel(device, need_popup) + full_update = need_popup or was_online != is_online + _update_info_panel(device, full=full_update) elif item: _model.remove(item)