diff --git a/app/listener.py b/app/listener.py index bb9f2071..e576142c 100644 --- a/app/listener.py +++ b/app/listener.py @@ -16,24 +16,23 @@ from logitech.unifying_receiver import (Receiver, # # -class _DUMMY_RECEIVER(object): - __slots__ = [] - name = Receiver.name - kind = None - max_devices = Receiver.max_devices - status = 'Receiver not found.' - __bool__ = __nonzero__ = lambda self: False - __unicode__ = __str__ = __repr__ = lambda self: 'DUMMY' -DUMMY = _DUMMY_RECEIVER() - from collections import namedtuple -_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['number', 'name', 'kind', 'status']) +_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['number', 'name', 'kind', 'status', 'max_devices']) +_GHOST_DEVICE.__bool__ = lambda self: False +_GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ del namedtuple +def _ghost(device): + return _GHOST_DEVICE(number=device.number, name=device.name, kind=device.kind, status=None, max_devices=None) + +DUMMY = _GHOST_DEVICE(Receiver.number, Receiver.name, None, 'Receiver not found.', Receiver.max_devices) + # # # +# how often to poll devices that haven't updated their statuses on their own +# (through notifications) _POLL_TICK = 60 # seconds @@ -46,6 +45,11 @@ class ReceiverListener(_listener.EventsListener): self._last_tick = 0 self.status_changed_callback = status_changed_callback + + # make it a bit similar with the regular devices + receiver.kind = None + # replace the + receiver.handle = _listener.ThreadedHandle(receiver.handle, receiver.path) receiver.status = _status.ReceiverStatus(receiver, self._status_changed) def has_started(self): @@ -100,9 +104,12 @@ class ReceiverListener(_listener.EventsListener): if device.status is None: # device was unpaired, and since the object is weakref'ed # it won't be valid for much longer - device = _GHOST_DEVICE(number=device.number, name=device.name, kind=device.kind, status=None) + device = _ghost(device) + self.status_changed_callback(r, device, alert, reason) + if device.status is None: + # the receiver changed status as well self.status_changed_callback(r) def _notifications_handler(self, n): @@ -117,21 +124,25 @@ class ReceiverListener(_listener.EventsListener): already_known = n.devnumber in self.receiver dev = self.receiver[n.devnumber] - if dev and not already_known: + if not dev: + _log.warn("received %s for invalid device %d: %s", n, n.devnumber, repr(dev)) + return + + if not already_known: # read these as soon as possible, they will be used everywhere dev.protocol, dev.codename dev.status = _status.DeviceStatus(dev, self._status_changed) + # the receiver changed status as well self._status_changed(self.receiver) - if dev and dev.status is not None: + # 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("pairing detected new device") self.receiver.status.new_device = dev - else: - _log.warn("received notification %s for invalid device %d: %s", n, n.devnumber, dev) def __str__(self): return '' % (self.receiver.path, self.receiver.handle) @@ -141,8 +152,6 @@ class ReceiverListener(_listener.EventsListener): def open(self, status_changed_callback=None): receiver = Receiver.open() if receiver: - receiver.handle = _listener.ThreadedHandle(receiver.handle, receiver.path) - receiver.kind = None rl = ReceiverListener(receiver, status_changed_callback) rl.start() return rl diff --git a/app/ui/config_panel.py b/app/ui/config_panel.py index f3bfae3a..b1b5b340 100644 --- a/app/ui/config_panel.py +++ b/app/ui/config_panel.py @@ -183,6 +183,7 @@ def update(frame): return device_active = bool(device.status) + # if the device just became active, re-read the settings force_read |= device_active and not box.get_sensitive() box.set_sensitive(device_active) if device_active: diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 81062443..e51485b0 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -424,10 +424,10 @@ def update(window, receiver, device=None): frames = list(vbox.get_children()) assert len(frames) == 1 + receiver.max_devices, frames - if device: - _update_device_box(frames[device.number], None if device.status is None else device) - else: + if device is None: _update_receiver_box(frames[0], receiver) if not receiver: for frame in frames[1:]: _update_device_box(frame, None) + else: + _update_device_box(frames[device.number], None if device.status is None else device) diff --git a/app/ui/status_icon.py b/app/ui/status_icon.py index a14c5b94..8344ca49 100644 --- a/app/ui/status_icon.py +++ b/app/ui/status_icon.py @@ -78,7 +78,7 @@ def _icon_with_battery(level, active): def update(icon, receiver, device=None): # print ("icon update", receiver, receiver.status, len(receiver), device) - if device: + if device is not None: icon._devices[device.number] = None if device.status is None else device if not receiver: icon._devices[:] = _NO_DEVICES diff --git a/lib/logitech/unifying_receiver/listener.py b/lib/logitech/unifying_receiver/listener.py index e217831a..dff59c73 100644 --- a/lib/logitech/unifying_receiver/listener.py +++ b/lib/logitech/unifying_receiver/listener.py @@ -148,11 +148,11 @@ class EventsListener(_threading.Thread): if n: # if _log.isEnabledFor(_DEBUG): - # _log.debug("processing notification %s", n) + # _log.debug("processing %s", n) try: self._notifications_callback(n) except: - _log.exception("processing notification %s", n) + _log.exception("processing %s", n) elif self.tick_period: idle_reads += 1 if idle_reads % _IDLE_READS == 0: @@ -189,7 +189,7 @@ class EventsListener(_threading.Thread): # i.e. triggered by a callback handling a previous notification. if self._active and _threading.current_thread() == self: if _log.isEnabledFor(_DEBUG): - _log.debug("queueing unhandled notification %s", n) + _log.debug("queueing unhandled %s", n) self._queued_notifications.put(n) def __bool__(self): diff --git a/lib/logitech/unifying_receiver/receiver.py b/lib/logitech/unifying_receiver/receiver.py index c59469ac..92da6c83 100644 --- a/lib/logitech/unifying_receiver/receiver.py +++ b/lib/logitech/unifying_receiver/receiver.py @@ -176,26 +176,14 @@ class PairedDevice(object): return self.number __int__ = __index__ - def __lt__(self, other): - return self.number < other.number - - def __le__(self, other): - return self.number <= other.number - - def __gt__(self, other): - return self.number > other.number - - def __ge__(self, other): - return self.number >= other.number - def __eq__(self, other): - return self.receiver == other.receiver and self.number == other.number + return self.serial == other.serial def __ne__(self, other): - return self.receiver != other.receiver or self.number != other.number + return self.serial != other.serial def __hash__(self): - return self.number + return self.serial.__hash__() def __str__(self): return '' % (self.number, self.codename or '?') @@ -210,6 +198,7 @@ class Receiver(object): The paired devices are available through the sequence interface. """ + number = 0xFF name = 'Unifying Receiver' kind = None max_devices = MAX_PAIRED_DEVICES @@ -220,7 +209,6 @@ class Receiver(object): assert path self.path = path - self.number = 0xFF self._serial = None self._firmware = None self._devices = {} diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index 0f504935..0f6ae9fc 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -186,11 +186,7 @@ class DeviceStatus(dict): if n.sub_id >= 0x40: return self._process_hidpp10_notification(n) - # if n.sub_id >= len(self._device.features): - # _log.warn("%s: notification from invalid feature index %02X", self._device, n.sub_id) - # return False - - # assuming 0x00 to 0x3F are device feature (HID++ 2.0) notifications + # assuming 0x00 to 0x3F are feature (HID++ 2.0) notifications try: feature = self._device.features[n.sub_id] except IndexError: