diff --git a/lib/logitech_receiver/listener.py b/lib/logitech_receiver/listener.py index f571ae01..20f6e95d 100644 --- a/lib/logitech_receiver/listener.py +++ b/lib/logitech_receiver/listener.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## 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 @@ -17,29 +16,16 @@ ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import logging -import threading as _threading +import queue +import threading -from . import base as _base -from . import exceptions - -# from time import time as _timestamp - -# for both Python 2 and 3 -try: - from Queue import Queue as _Queue -except ImportError: - from queue import Queue as _Queue +from . import base, exceptions logger = logging.getLogger(__name__) -# -# -# - class _ThreadedHandle: """A thread-local wrapper with different open handles for each thread. - Closing a ThreadedHandle will close all handles. """ @@ -53,13 +39,13 @@ class _ThreadedHandle: self._listener = listener self.path = path - self._local = _threading.local() + self._local = threading.local() # take over the current handle for the thread doing the replacement self._local.handle = handle self._handles = [handle] def _open(self): - handle = _base.open_path(self.path) + handle = base.open_path(self.path) if handle is None: logger.error("%r failed to open new handle", self) else: @@ -76,13 +62,13 @@ class _ThreadedHandle: if logger.isEnabledFor(logging.DEBUG): logger.debug("%r closing %s", self, handles) for h in handles: - _base.close(h) + base.close(h) @property def notifications_hook(self): if self._listener: - assert isinstance(self._listener, _threading.Thread) - if _threading.current_thread() == self._listener: + assert isinstance(self._listener, threading.Thread) + if threading.current_thread() == self._listener: return self._listener._notifications_hook def __del__(self): @@ -113,26 +99,15 @@ class _ThreadedHandle: __nonzero__ = __bool__ -# -# -# - -# How long to wait during a read for the next packet, in seconds -# Ideally this should be rather long (10s ?), but the read is blocking -# and this means that when the thread is signalled to stop, it would take -# a while for it to acknowledge it. -# Forcibly closing the file handle on another thread does _not_ interrupt the -# read on Linux systems. +# How long to wait during a read for the next packet, in seconds. +# Ideally this should be rather long (10s ?), but the read is blocking and this means that when the thread +# is signalled to stop, it would take a while for it to acknowledge it. +# Forcibly closing the file handle on another thread does _not_ interrupt the read on Linux systems. _EVENT_READ_TIMEOUT = 1.0 # in seconds -# After this many reads that did not produce a packet, call the tick() method. -# This only happens if tick_period is enabled (>0) for the Listener instance. -# _IDLE_READS = 1 + int(5 // _EVENT_READ_TIMEOUT) # wait at least 5 seconds between ticks - -class EventsListener(_threading.Thread): +class EventsListener(threading.Thread): """Listener thread for notifications from the Unifying Receiver. - Incoming packets will be passed to the callback function in sequence. """ @@ -145,7 +120,7 @@ class EventsListener(_threading.Thread): self.daemon = True self._active = False self.receiver = receiver - self._queued_notifications = _Queue(16) + self._queued_notifications = queue.Queue(16) self._notifications_callback = notifications_callback def run(self): @@ -163,13 +138,13 @@ class EventsListener(_threading.Thread): while self._active: if self._queued_notifications.empty(): try: - n = _base.read(self.receiver.handle, _EVENT_READ_TIMEOUT) + n = base.read(self.receiver.handle, _EVENT_READ_TIMEOUT) except exceptions.NoReceiver: logger.warning("%s disconnected", self.receiver.name) self.receiver.close() break if n: - n = _base.make_notification(*n) + n = base.make_notification(*n) else: n = self._queued_notifications.get() # deliver any queued notifications if n: @@ -194,15 +169,11 @@ class EventsListener(_threading.Thread): """Called right before the thread stops.""" pass - # def tick(self, timestamp): - # """Called about every tick_period seconds.""" - # pass - def _notifications_hook(self, n): # Only consider unhandled notifications that were sent from this thread, # i.e. triggered by a callback handling a previous notification. - assert _threading.current_thread() == self - if self._active: # and _threading.current_thread() == self: + assert threading.current_thread() == self + if self._active: # and threading.current_thread() == self: # if logger.isEnabledFor(logging.DEBUG): # logger.debug("queueing unhandled %s", n) if not self._queued_notifications.full(): diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 91f6ae00..50268894 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -1,4 +1,5 @@ ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## 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 @@ -39,9 +40,6 @@ logger = logging.getLogger(__name__) _R = _hidpp10.REGISTERS _F = _hidpp20_constants.FEATURE -# -# -# notification_lock = _threading.Lock() @@ -60,11 +58,6 @@ def process(device, notification): return _process_device_notification(device, status, notification) -# -# -# - - def _process_receiver_notification(receiver, status, n): # supposedly only 0x4x notifications arrive for the receiver assert n.sub_id & 0x40 == 0x40 @@ -151,21 +144,14 @@ def _process_receiver_notification(receiver, status, n): logger.warning("%s: unhandled notification %s", receiver, n) -# -# -# - - def _process_device_notification(device, status, n): - # incoming packets with SubId >= 0x80 are supposedly replies from - # HID++ 1.0 requests, should never get here + # incoming packets with SubId >= 0x80 are supposedly replies from HID++ 1.0 requests, should never get here assert n.sub_id & 0x80 == 0 if n.sub_id == 00: # no-op feature notification, dispose of it quickly return False - # Allow the device object to handle the notification using custom - # per-device state. + # Allow the device object to handle the notification using custom per-device state. handling_ret = device.handle_notification(n) if handling_ret is not None: return handling_ret @@ -179,8 +165,7 @@ def _process_device_notification(device, status, n): # These notifications are from the device itself, so it must be active device.online = True - # At this point, we need to know the device's protocol, otherwise it's - # possible to not know how to handle it. + # At this point, we need to know the device's protocol, otherwise it's possible to not know how to handle it. assert device.protocol is not None # some custom battery events for HID++ 1.0 devices @@ -238,19 +223,11 @@ def _process_hidpp10_custom_notification(device, status, n): status.set_battery_info(charge, next_charge, status_text, voltage) return True - if n.sub_id == _R.keyboard_illumination: - # message layout: 10 ix 17("address") - # TODO anything we can do with this? - if logger.isEnabledFor(logging.INFO): - logger.info("illumination event: %s", n) - return True - logger.warning("%s: unrecognized %s", device, n) def _process_hidpp10_notification(device, status, n): - # device unpairing - if n.sub_id == 0x40: + if n.sub_id == 0x40: # device unpairing if n.address == 0x02: # device un-paired status.clear() @@ -263,8 +240,7 @@ def _process_hidpp10_notification(device, status, n): logger.warning("%s: disconnection with unknown type %02X: %s", device, n.address, n) return True - # device connection (and disconnection) - if n.sub_id == 0x41: + if n.sub_id == 0x41: # device connection (and disconnection) flags = ord(n.data[:1]) & 0xF0 if n.address == 0x02: # very old 27 MHz protocol wpid = "00" + _strhex(n.data[2:3]) @@ -296,12 +272,10 @@ def _process_hidpp10_notification(device, status, n): if n.sub_id == 0x49: # raw input event? just ignore it # if n.address == 0x01, no idea what it is, but they keep on coming - # if n.address == 0x03, appears to be an actual input event, - # because they only come when input happents + # if n.address == 0x03, appears to be an actual input event, because they only come when input happents return True - # power notification - if n.sub_id == 0x4B: + if n.sub_id == 0x4B: # power notification if n.address == 0x01: if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: device powered on", device)