diff --git a/app/solaar.py b/app/solaar.py index 53b6c5b7..cb2e51e5 100644 --- a/app/solaar.py +++ b/app/solaar.py @@ -18,11 +18,6 @@ from watcher import Watcher APP_TITLE = 'Solaar' -def _notify(status_code, title, text=''): - if text: - ui.notify.show(status_code, title, text) - - if __name__ == '__main__': import argparse arg_parser = argparse.ArgumentParser(prog=APP_TITLE) @@ -41,7 +36,7 @@ if __name__ == '__main__': GObject.threads_init() - args.notifications = args.notifications and args.systray + args.notifications &= args.systray if args.notifications: ui.notify.init(APP_TITLE) @@ -57,7 +52,7 @@ if __name__ == '__main__': if window: GObject.idle_add(ui.window.update, window, rstatus, devices, icon_name) - watcher = Watcher(_status_changed, _notify if args.notifications else None) + watcher = Watcher(_status_changed, ui.notify.show if args.notifications else None) watcher.start() window = ui.window.create(APP_TITLE, watcher.rstatus, args.systray) diff --git a/app/ui/notify.py b/app/ui/notify.py index cfe82194..90816271 100644 --- a/app/ui/notify.py +++ b/app/ui/notify.py @@ -72,7 +72,7 @@ try: n.update(title, text, icon or title) n.timestamp = timestamp() try: - logging.debug("showing notification %s", n) + # logging.debug("showing notification %s", n) n.show() except Exception: logging.exception("showing notification %s", n) diff --git a/app/watcher.py b/app/watcher.py index cbb6388b..d8a63ff9 100644 --- a/app/watcher.py +++ b/app/watcher.py @@ -104,7 +104,6 @@ class Watcher(Thread): if self.listener: self.listener.stop() - api.close(self.listener.receiver) self.listener = None def stop(self): diff --git a/lib/logitech/devices/__init__.py b/lib/logitech/devices/__init__.py index 09f72357..c0f1e1ef 100644 --- a/lib/logitech/devices/__init__.py +++ b/lib/logitech/devices/__init__.py @@ -9,22 +9,53 @@ from . import constants as C from ..unifying_receiver import api as _api +# +# +# + +_REQUEST_STATUS_FUNCTIONS = { + C.NAME.K750: k750.request_status, + } + +_PROCESS_EVENT_FUNCTIONS = { + C.NAME.K750: k750.process_event, + } + +# +# +# + +def ping(devinfo, listener=None): + if listener is None: + reply = _api.ping(devinfo.number) + elif listener: + reply = listener.request(_api.ping, devinfo.number) + else: + return None -def ping(devinfo, listener): - reply = listener.request(_api.ping, devinfo.number) return C.STATUS.CONNECTED if reply else C.STATUS.UNAVAILABLE -def default_request_status(devinfo, listener): +def default_request_status(devinfo, listener=None): if _api.C.FEATURE.BATTERY in devinfo.features: - reply = listener.request(_api.get_device_battery_level, devinfo.number, features=devinfo.features) + if listener is None: + reply = _api.get_device_battery_level(devinfo.handle, devinfo.number, features=devinfo.features) + elif listener: + reply = listener.request(_api.get_device_battery_level, devinfo.number, features=devinfo.features) + else: + reply = None + if reply: discharge, dischargeNext, status = reply return C.STATUS.CONNECTED, {C.PROPS.BATTERY_LEVEL: discharge} -def default_process_event(devinfo, data): +def default_process_event(devinfo, data, listener=None): feature_index = ord(data[0:1]) + if feature_index >= len(devinfo.features): + logging.warn("mistery event %s for %s", repr(data), devinfo) + return None + feature = devinfo.features[feature_index] feature_function = ord(data[1:2]) & 0xF0 @@ -48,39 +79,27 @@ def default_process_event(devinfo, data): # ? -_REQUEST_STATUS_FUNCTIONS = { - k750.NAME: k750.request_status - } - -def request_status(devinfo, listener): +def request_status(devinfo, listener=None): """Trigger a status request for a device. :param devinfo: the device info tuple. :param listener: the EventsListener that will be used to send the request, and which will receive the status events from the device. """ - if listener: - if devinfo.name in _REQUEST_STATUS_FUNCTIONS: - return _REQUEST_STATUS_FUNCTIONS[devinfo.name](devinfo, listener) - return default_request_status(devinfo, listener) or ping(devinfo, listener) + if devinfo.name in _REQUEST_STATUS_FUNCTIONS: + return _REQUEST_STATUS_FUNCTIONS[devinfo.name](devinfo, listener) + return default_request_status(devinfo, listener) or ping(devinfo, listener) -_PROCESS_EVENT_FUNCTIONS = { - k750.NAME: k750.process_event - } - -def process_event(devinfo, data): +def process_event(devinfo, data, listener=None): """Process an event received for a device. - When using an EventsListener, it is assumed this event was received through - its callback, where you may call LUR APIs directly. - :param devinfo: the device info tuple. :param data: the event data (event packet sans the first two bytes: reply code and device number) """ - default_result = default_process_event(devinfo, data) + default_result = default_process_event(devinfo, data, listener) if default_result is not None: return default_result if devinfo.name in _PROCESS_EVENT_FUNCTIONS: - return _PROCESS_EVENT_FUNCTIONS[devinfo.name](devinfo, data) + return _PROCESS_EVENT_FUNCTIONS[devinfo.name](devinfo, data, listener) diff --git a/lib/logitech/devices/constants.py b/lib/logitech/devices/constants.py index 5174f963..17e1f1e1 100644 --- a/lib/logitech/devices/constants.py +++ b/lib/logitech/devices/constants.py @@ -24,3 +24,34 @@ PROPS = type('PROPS', (), BATTERY_STATUS='battery_status', LIGHT_LEVEL='light_level', )) + + +NAME = type('NAME', (), + dict( + M315='Wireless Mouse M315', + M325='Wireless Mouse M325', + M510='Wireless Mouse M510', + M515='Couch Mouse M515', + M570='Wireless Trackball M570', + K270='Wireless Keyboard K270', + K350='Wireless Keyboard K350', + K750='Wireless Solar Keyboard K750', + K800='Wireless Illuminated Keyboard K800', + )) + +from ..unifying_receiver.common import FallbackDict + +FULL_NAME = FallbackDict(lambda x: x, + dict( + M315=NAME.M315, + M325=NAME.M325, + M510=NAME.M510, + M515=NAME.M515, + M570=NAME.M570, + K270=NAME.K270, + K350=NAME.K350, + K750=NAME.K750, + K800=NAME.K800, + )) + +del FallbackDict diff --git a/lib/logitech/devices/k750.py b/lib/logitech/devices/k750.py index eb6bc934..8c87d442 100644 --- a/lib/logitech/devices/k750.py +++ b/lib/logitech/devices/k750.py @@ -12,18 +12,6 @@ from . import constants as C # # -NAME = 'Wireless Solar Keyboard K750' - -# -# -# - -def _trigger_solar_charge_events(handle, devinfo): - return _api.request(handle, devinfo.number, - feature=_api.C.FEATURE.SOLAR_CHARGE, function=b'\x03', params=b'\x78\x01', - features=devinfo.features) - - def _charge_status(data, hasLux=False): charge, lux = _unpack('!BH', data[2:5]) @@ -47,16 +35,25 @@ def _charge_status(data, hasLux=False): return 0x10 << charge_index, d -def request_status(devinfo, listener): - if listener: +def request_status(devinfo, listener=None): + def _trigger_solar_charge_events(handle, devinfo): + return _api.request(handle, devinfo.number, + feature=_api.C.FEATURE.SOLAR_CHARGE, function=b'\x03', params=b'\x78\x01', + features=devinfo.features) + if listener is None: + reply = _trigger_solar_charge_events(devinfo.handle, devinfo) + elif listener: reply = listener.request(_trigger_solar_charge_events, devinfo) - if reply is None: - return C.STATUS.UNAVAILABLE + else: + reply = 0 + + if reply is None: + return C.STATUS.UNAVAILABLE -def process_event(devinfo, data): +def process_event(devinfo, data, listener=None): if data[:2] == b'\x09\x00' and data[7:11] == b'GOOD': - # usually sent after the keyboard is turned on + # usually sent after the keyboard is turned on or just connected return _charge_status(data) if data[:2] == b'\x09\x10' and data[7:11] == b'GOOD': @@ -65,9 +62,7 @@ def process_event(devinfo, data): if data[:2] == b'\x09\x20' and data[7:11] == b'GOOD': logging.debug("Solar key pressed") - if _trigger_solar_charge_events(devinfo.handle, devinfo) is None: - return C.STATUS.UNAVAILABLE - return _charge_status(data) + return request_status(devinfo, listener) or _charge_status(data) if data[:2] == b'\x05\x00': # wireless device status diff --git a/lib/logitech/unifying_receiver/base.py b/lib/logitech/unifying_receiver/base.py index 8e777078..1dc5204d 100644 --- a/lib/logitech/unifying_receiver/base.py +++ b/lib/logitech/unifying_receiver/base.py @@ -254,7 +254,7 @@ def request(handle, devnumber, feature_index_function, params=b'', features=None if reply_devnumber != devnumber: # this message not for the device we're interested in - _l.log(_LOG_LEVEL, "(%d) request got reply for unexpected device %d: [%s]", devnumber, reply_devnumber, _hexlify(reply_data)) + # _l.log(_LOG_LEVEL, "(%d) request got reply for unexpected device %d: [%s]", devnumber, reply_devnumber, _hexlify(reply_data)) # worst case scenario, this is a reply for a concurrent request # on this receiver if unhandled_hook: @@ -290,6 +290,6 @@ def request(handle, devnumber, feature_index_function, params=b'', features=None # _l.log(_LOG_LEVEL, "(%d) matched reply with feature-index-function [%s]", devnumber, _hexlify(reply_data[2:])) return reply_data[2:] - _l.log(_LOG_LEVEL, "(%d) unmatched reply {%s} (expected {%s})", devnumber, _hexlify(reply_data[:2]), _hexlify(feature_index_function)) + # _l.log(_LOG_LEVEL, "(%d) unmatched reply {%s} (expected {%s})", devnumber, _hexlify(reply_data[:2]), _hexlify(feature_index_function)) if unhandled_hook: unhandled_hook(reply_code, reply_devnumber, reply_data) diff --git a/lib/logitech/unifying_receiver/listener.py b/lib/logitech/unifying_receiver/listener.py index 5c468270..4066266f 100644 --- a/lib/logitech/unifying_receiver/listener.py +++ b/lib/logitech/unifying_receiver/listener.py @@ -20,16 +20,15 @@ _LOG_LEVEL = 4 _l = _Logger('lur.listener') -_READ_EVENT_TIMEOUT = int(_base.DEFAULT_TIMEOUT / 4) # ms -_IDLE_SLEEP = _base.DEFAULT_TIMEOUT / 4 # ms +_READ_EVENT_TIMEOUT = int(_base.DEFAULT_TIMEOUT / 5) # ms +_IDLE_SLEEP = _base.DEFAULT_TIMEOUT / 5 # ms def _callback_caller(listener, callback): # _l.log(_LOG_LEVEL, "%s starting callback caller", listener) while listener._active: event = listener.events.get() - if _l.isEnabledFor(_LOG_LEVEL): - _l.log(_LOG_LEVEL, "%s delivering event %s", listener, event) + _l.log(_LOG_LEVEL, "%s delivering event %s", listener, event) try: callback.__call__(*event) except: @@ -41,12 +40,11 @@ class EventsListener(Thread): """Listener thread for events from the Unifying Receiver. Incoming events (reply_code, devnumber, data) will be passed to the callback - function. The callback is called in a separate thread. + function in sequence, by a separate thread. While this listener is running, you should use the request() method to make - regular UR API calls, otherwise the replies are very likely to be captured - by the listener and delivered as events to the callback. As an exception, - you can make API calls in the events callback. + regular UR API calls, otherwise the expected API replies are most likely to + be captured by the listener and delivered as events to the callback. """ def __init__(self, receiver, events_callback): super(EventsListener, self).__init__(group='Unifying Receiver', name='Events-%x' % receiver) @@ -97,6 +95,7 @@ class EventsListener(Thread): self.task_reply = self._make_request(*self.task) self.task_done.set() + _base.close(self.receiver) self.__str_cached = 'Events(%x)' % self.receiver _base.unhandled_hook = last_hook @@ -113,8 +112,7 @@ class EventsListener(Thread): The api_function must have a receiver handle as a first agument, all other passed args and kwargs will follow. """ - # if _l.isEnabledFor(_LOG_LEVEL): - # _l.log(_LOG_LEVEL, "%s request '%s.%s' with %s, %s", self, api_function.__module__, api_function.__name__, args, kwargs) + # _l.log(_LOG_LEVEL, "%s request '%s.%s' with %s, %s", self, api_function.__module__, api_function.__name__, args, kwargs) self.task_processing.acquire() self.task_done.clear() @@ -125,15 +123,13 @@ class EventsListener(Thread): self.task = self.task_reply = None self.task_processing.release() - # if _l.isEnabledFor(_LOG_LEVEL): - # _l.log(_LOG_LEVEL, "%s request '%s.%s' => %s", self, api_function.__module__, api_function.__name__, repr(reply)) + # _l.log(_LOG_LEVEL, "%s request '%s.%s' => %s", self, api_function.__module__, api_function.__name__, repr(reply)) if isinstance(reply, Exception): raise reply return reply def _make_request(self, api_function, args, kwargs): - if _l.isEnabledFor(_LOG_LEVEL): - _l.log(_LOG_LEVEL, "%s calling '%s.%s' with %s, %s", self, api_function.__module__, api_function.__name__, args, kwargs) + _l.log(_LOG_LEVEL, "%s calling '%s.%s' with %s, %s", self, api_function.__module__, api_function.__name__, args, kwargs) try: return api_function.__call__(self.receiver, *args, **kwargs) except E.NoReceiver as nr: