diff --git a/app/solaar.py b/app/solaar.py index 1733a2cf..53b6c5b7 100644 --- a/app/solaar.py +++ b/app/solaar.py @@ -7,8 +7,7 @@ __version__ = '0.4' # import logging -from gi.repository import Gtk -from gi.repository import GObject +from gi.repository import (Gtk, GObject) from logitech.devices import constants as C @@ -19,32 +18,20 @@ from watcher import Watcher APP_TITLE = 'Solaar' -def _status_updated(watcher, icon, window): - while True: - watcher.status_changed.wait() - text = watcher.status_text - watcher.status_changed.clear() - - icon_name = APP_TITLE + '-fail' if watcher.rstatus.code < C.STATUS.CONNECTED else APP_TITLE - - if icon: - GObject.idle_add(ui.icon.update, icon, watcher.rstatus, text, icon_name) - - if window: - GObject.idle_add(ui.window.update, window, watcher.rstatus, dict(watcher.devices), icon_name) +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) arg_parser.add_argument('-v', '--verbose', action='count', default=0, - help='increase the logger verbosity') - arg_parser.add_argument('-N', '--disable-notifications', action='store_false', dest='notifications', - help='disable desktop notifications') - arg_parser.add_argument('-H', '--start-hidden', action='store_true', dest='start_hidden', - help='hide the application window on start') - arg_parser.add_argument('-t', '--close-to-tray', action='store_true', - help='closing the application window hides it instead of terminating the application') + help='increase the logger verbosity (may be repeated)') + arg_parser.add_argument('-s', '--systray', action='store_true', + help='embed the application into the systray') + arg_parser.add_argument('-N', '--no-notifications', action='store_false', dest='notifications', + help='disable desktop notifications (if systray is enabled)') arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) args = arg_parser.parse_args() @@ -54,23 +41,34 @@ if __name__ == '__main__': GObject.threads_init() - ui.notify.init(APP_TITLE, args.notifications) + args.notifications = args.notifications and args.systray + if args.notifications: + ui.notify.init(APP_TITLE) - watcher = Watcher(ui.notify.show) + tray_icon = None + window = None + + def _status_changed(text, rstatus, devices): + icon_name = APP_TITLE + '-fail' if rstatus.code < C.STATUS.CONNECTED else APP_TITLE + + if tray_icon: + GObject.idle_add(ui.icon.update, tray_icon, rstatus, text, icon_name) + + if window: + GObject.idle_add(ui.window.update, window, rstatus, devices, icon_name) + + watcher = Watcher(_status_changed, _notify if args.notifications else None) watcher.start() - window = ui.window.create(APP_TITLE, watcher.rstatus, not args.start_hidden, args.close_to_tray) + window = ui.window.create(APP_TITLE, watcher.rstatus, args.systray) window.set_icon_name(APP_TITLE + '-fail') - tray_icon = ui.icon.create(APP_TITLE, (ui.window.toggle, window)) - tray_icon.set_from_icon_name(APP_TITLE + '-fail') - - import threading - ui_update_thread = threading.Thread(target=_status_updated, name='ui_update', args=(watcher, tray_icon, window)) - ui_update_thread.daemon = True - ui_update_thread.start() + if args.systray: + tray_icon = ui.icon.create(APP_TITLE, (ui.window.toggle, window)) + tray_icon.set_from_icon_name(APP_TITLE + '-fail') Gtk.main() watcher.stop() - ui.notify.set_active(False) + if args.notifications: + ui.notify.set_active(False) diff --git a/app/ui/notify.py b/app/ui/notify.py index 1dfc7c06..2cd69ac6 100644 --- a/app/ui/notify.py +++ b/app/ui/notify.py @@ -11,7 +11,6 @@ try: available = True # assumed to be working since the import succeeded _active = False # not yet active _app_title = None - _notifications = {} def init(app_title, active=True): @@ -34,14 +33,6 @@ try: available = False else: if _active: - for n in list(_notifications.values()): - try: - n.close() - except Exception: - logging.exception("closing open notification %s", n) - # DBUS - pass - _notifications.clear() try: _notify.uninit() except: @@ -57,31 +48,15 @@ try: def show(status_code, title, text='', icon=None): """Show a notification with title and text.""" - if not available or not _active: - return - - if title in _notifications: - notification = _notifications[title] - else: - _notifications[title] = notification = _notify.Notification(title) - - if text == notification.message: - # there's no need to show the same notification twice in a row - return - - icon = icon or title - notification.update(title, text, title) - try: - if text: + if available and _active: + notification = _notify.Notification(title, text, icon or title) + try: notification.show() - else: - notification.close() - except Exception: - logging.exception("showing notification %s", notification) + except Exception: + logging.exception("showing notification %s", notification) except ImportError: - logging.exception("ouch") logging.warn("python-notify2 not found, desktop notifications are disabled") available = False active = False diff --git a/app/ui/pair.py b/app/ui/pair.py index 31d416b8..a91aec09 100644 --- a/app/ui/pair.py +++ b/app/ui/pair.py @@ -10,8 +10,6 @@ def create(parent_window, title): Gtk.Window.set_default_icon_name('add') window.set_resizable(False) - - # window.set_wmclass(title, 'status-window') - # window.set_role('pair') + # window.set_role('pair-device') return window diff --git a/app/ui/window.py b/app/ui/window.py index 3c0a8708..b3706b8f 100644 --- a/app/ui/window.py +++ b/app/ui/window.py @@ -2,79 +2,106 @@ # # -from gi.repository import Gtk -from gi.repository import Gdk +from gi.repository import (Gtk, Gdk) from logitech.devices import constants as C -_DEVICE_ICON_SIZE = Gtk.IconSize.DIALOG +_DEVICE_ICON_SIZE = Gtk.IconSize.DND _STATUS_ICON_SIZE = Gtk.IconSize.DIALOG _PLACEHOLDER = '~' -_MAX_DEVICES = 6 + + +def _find_children(container, *child_names): + def _iterate_children(widget, names, result, count): + wname = widget.get_name() + if wname in names: + index = names.index(wname) + names[index] = None + result[index] = widget + count -= 1 + + if count > 0 and isinstance(widget, Gtk.Container): + for w in widget: + count = _iterate_children(w, names, result, count) + if count == 0: + break + + return count + + names = list(child_names) + count = len(names) + result = [None] * count + _iterate_children(container, names, result, count) + return result if count > 1 else result[0] def _update_receiver_box(box, receiver): - icon, vbox = box.get_children() - label, buttons_box = vbox.get_children() + label, buttons_box = _find_children(box, 'receiver-status', 'receiver-buttons') label.set_text(receiver.text or '') buttons_box.set_visible(receiver.code >= C.STATUS.CONNECTED) def _update_device_box(frame, devstatus): - frame.set_visible(devstatus is not None) + if devstatus is None: + frame.set_visible(False) + frame.set_name(_PLACEHOLDER) + return - box = frame.get_child() - icon, expander = box.get_children() + frame.set_visible(True) + if frame.get_name() != devstatus.name: + frame.set_name(devstatus.name) + icon = _find_children(frame, 'device-icon') + icon.set_from_icon_name(devstatus.name, _DEVICE_ICON_SIZE) + icon.set_tooltip_text(devstatus.name) - if devstatus: - if icon.get_name() != devstatus.name: - icon.set_name(devstatus.name) - icon.set_from_icon_name(devstatus.name, _DEVICE_ICON_SIZE) + expander = _find_children(frame, 'device-expander') + if devstatus.code < C.STATUS.CONNECTED: + expander.set_sensitive(False) + expander.set_expanded(False) + expander.set_label('%s\n%s' % (devstatus.name, devstatus.text)) + return - if devstatus.code < C.STATUS.CONNECTED: - expander.set_sensitive(False) - expander.set_expanded(False) - expander.set_label('%s\n%s' % (devstatus.name, devstatus.text)) - else: - expander.set_sensitive(True) - ebox = expander.get_child() + expander.set_sensitive(True) + status_icons = expander.get_child().get_children() - texts = [] + texts = [] - light_icon = ebox.get_children()[-2] - light_level = getattr(devstatus, C.PROPS.LIGHT_LEVEL, None) - light_icon.set_visible(light_level is not None) - if light_level is not None: - texts.append('Light: %d lux' % light_level) - icon_name = 'light_%02d' % (20 * ((light_level + 50) // 100)) - light_icon.set_from_icon_name(icon_name, _STATUS_ICON_SIZE) - light_icon.set_tooltip_text(texts[-1]) - - battery_icon = ebox.get_children()[-1] - battery_level = getattr(devstatus, C.PROPS.BATTERY_LEVEL, None) - battery_icon.set_sensitive(battery_level is not None) - if battery_level is None: - battery_icon.set_from_icon_name('battery_unknown', _STATUS_ICON_SIZE) - battery_icon.set_tooltip_text('Battery: unknown') - else: - texts.append('Battery: %d%%' % battery_level) - icon_name = 'battery_%02d' % (20 * ((battery_level + 10) // 20)) - battery_icon.set_from_icon_name(icon_name, _STATUS_ICON_SIZE) - battery_icon.set_tooltip_text(texts[-1]) - - battery_status = getattr(devstatus, C.PROPS.BATTERY_STATUS, None) - if battery_status is not None: - texts.append(battery_status) - battery_icon.set_tooltip_text(battery_icon.get_tooltip_text() + '\n' + battery_status) - - if texts: - expander.set_label('%s\n%s' % (devstatus.name, ', '.join(texts))) - else: - expander.set_label('%s\n%s' % (devstatus.name, devstatus.text)) + light_icon = status_icons[-2] + light_level = getattr(devstatus, C.PROPS.LIGHT_LEVEL, None) + if light_level is None: + light_icon.set_visible(False) else: - icon.set_name(_PLACEHOLDER) - expander.set_label(_PLACEHOLDER) + light_icon.set_visible(True) + icon_name = 'light_%02d' % (20 * ((light_level + 50) // 100)) + light_icon.set_from_icon_name(icon_name, _STATUS_ICON_SIZE) + tooltip = 'Light: %d lux' % light_level + light_icon.set_tooltip_text(tooltip) + texts.append(tooltip) + + battery_icon = status_icons[-1] + battery_level = getattr(devstatus, C.PROPS.BATTERY_LEVEL, None) + if battery_level is None: + battery_icon.set_sensitive(False) + battery_icon.set_from_icon_name('battery_unknown', _STATUS_ICON_SIZE) + battery_icon.set_tooltip_text('Battery: unknown') + else: + battery_icon.set_sensitive(True) + icon_name = 'battery_%02d' % (20 * ((battery_level + 10) // 20)) + battery_icon.set_from_icon_name(icon_name, _STATUS_ICON_SIZE) + tooltip = 'Battery: %d%%' % battery_level + battery_icon.set_tooltip_text(tooltip) + texts.append(tooltip) + + battery_status = getattr(devstatus, C.PROPS.BATTERY_STATUS, None) + if battery_status is not None: + texts.append(battery_status) + battery_icon.set_tooltip_text(battery_icon.get_tooltip_text() + '\n' + battery_status) + + if texts: + expander.set_label('%s\n%s' % (devstatus.name, ', '.join(texts))) + else: + expander.set_label('%s\n%s' % (devstatus.name, devstatus.text)) def update(window, receiver, devices, icon_name=None): @@ -84,31 +111,30 @@ def update(window, receiver, devices, icon_name=None): controls = list(window.get_child().get_children()) _update_receiver_box(controls[0], receiver) - for index in range(1, 1 + _MAX_DEVICES): + for index in range(1, len(controls)): _update_device_box(controls[index], devices.get(index)) def _receiver_box(rstatus): box = Gtk.HBox(homogeneous=False, spacing=8) - box.set_border_width(8) + box.set_border_width(4) icon = Gtk.Image.new_from_icon_name(rstatus.name, _DEVICE_ICON_SIZE) icon.set_alignment(0.5, 0) - icon.set_name(rstatus.name) + icon.set_tooltip_text(rstatus.name) box.pack_start(icon, False, False, 0) - vbox = Gtk.VBox(homogeneous=False, spacing=4) - box.pack_start(vbox, True, True, 0) + label = Gtk.Label('Initializing...') + label.set_alignment(0, 0.5) + label.set_name('receiver-status') + box.pack_start(label, True, True, 0) - label = Gtk.Label() - label.set_can_focus(False) - label.set_alignment(0, 0) - vbox.pack_start(label, False, False, 0) - - buttons_box = Gtk.HButtonBox() - buttons_box.set_spacing(8) - buttons_box.set_layout(Gtk.ButtonBoxStyle.START) - vbox.pack_start(buttons_box, True, True, 0) + toolbar = Gtk.Toolbar() + toolbar.set_style(Gtk.ToolbarStyle.ICONS) + toolbar.set_name('receiver-buttons') + toolbar.set_show_arrow(False) + toolbar.set_icon_size(Gtk.IconSize.BUTTON) + box.pack_end(toolbar, False, False, 0) def _action(button, function, params): button.set_sensitive(False) @@ -116,51 +142,46 @@ def _receiver_box(rstatus): button.set_sensitive(True) def _add_button(name, icon, action): - button = Gtk.Button(name.split(' ')[0]) - button.set_image(Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON)) - button.set_relief(Gtk.ReliefStyle.HALF) + button = Gtk.ToolButton() + button.set_icon_name(icon) button.set_tooltip_text(name) - button.set_focus_on_click(False) if action: function = action[0] params = action[1:] button.connect('clicked', _action, function, params) else: button.set_sensitive(False) - buttons_box.pack_start(button, False, False, 0) + toolbar.insert(button, -1) _add_button('Scan for devices', 'reload', rstatus.refresh) _add_button('Pair new device', 'add', rstatus.pair) box.show_all() + toolbar.set_visible(False) return box def _device_box(): + box = Gtk.HBox(homogeneous=False, spacing=8) + box.set_border_width(4) + icon = Gtk.Image() icon.set_alignment(0.5, 0) - icon.set_name(_PLACEHOLDER) - - box = Gtk.HBox(homogeneous=False, spacing=8) + icon.set_name('device-icon') box.pack_start(icon, False, False, 0) - box.set_border_width(8) expander = Gtk.Expander() - expander.set_can_focus(False) - expander.set_label(_PLACEHOLDER) expander.set_use_markup(True) expander.set_spacing(4) + expander.set_name('device-expander') + box.pack_start(expander, True, True, 1) ebox = Gtk.HBox(False, 8) - battery_icon = Gtk.Image.new_from_icon_name('battery_unknown', _STATUS_ICON_SIZE) ebox.pack_end(battery_icon, False, True, 0) - light_icon = Gtk.Image.new_from_icon_name('light_unknown', _STATUS_ICON_SIZE) ebox.pack_end(light_icon, False, True, 0) - expander.add(ebox) - box.pack_start(expander, True, True, 1) frame = Gtk.Frame() frame.add(box) @@ -169,57 +190,46 @@ def _device_box(): return frame -def create(title, rstatus, show=True, close_to_tray=False): +def create(title, rstatus, systray=False): window = Gtk.Window() - window.set_title(title) - - window.set_keep_above(True) - window.set_deletable(False) - window.set_resizable(False) - # window.set_size_request(200, 50) - window.set_default_size(200, 50) - - window.set_position(Gtk.WindowPosition.MOUSE) - window.set_type_hint(Gdk.WindowTypeHint.UTILITY) - - # window.set_skip_taskbar_hint(True) - # window.set_skip_pager_hint(True) - # window.set_wmclass(title, 'status-window') - # window.set_role('status-window') + window.set_role('status-window') vbox = Gtk.VBox(homogeneous=False, spacing=4) vbox.set_border_width(4) vbox.add(_receiver_box(rstatus)) - for i in range(1, 1 + _MAX_DEVICES): + for i in range(1, 1 + rstatus.max_devices): vbox.add(_device_box()) vbox.set_visible(True) window.add(vbox) - if close_to_tray: - def _state_event(window, event): - if event.new_window_state & Gdk.WindowState.ICONIFIED: - position = window.get_position() - window.hide() - window.deiconify() - window.move(*position) - return True + geometry = Gdk.Geometry() + geometry.min_width = 300 + geometry.min_height = 40 + window.set_geometry_hints(vbox, geometry, Gdk.WindowHints.MIN_SIZE) - window.connect('window-state-event', _state_event) - window.connect('delete-event', lambda w, e: toggle(None, window) or True) + window.set_resizable(False) + window.set_default_size(geometry.min_width, geometry.min_height) + + if systray: + window.set_keep_above(True) + window.set_deletable(False) + window.set_decorated(False) + window.set_position(Gtk.WindowPosition.MOUSE) + window.set_type_hint(Gdk.WindowTypeHint.MENU) + window.set_skip_taskbar_hint(True) + window.set_skip_pager_hint(True) else: + window.set_position(Gtk.WindowPosition.CENTER) window.connect('delete-event', Gtk.main_quit) - - if show: window.present() + return window def toggle(_, window): if window.get_visible(): - position = window.get_position() window.hide() - window.move(*position) else: window.present() diff --git a/app/watcher.py b/app/watcher.py index 5d4db61d..4faee6cc 100644 --- a/app/watcher.py +++ b/app/watcher.py @@ -2,9 +2,9 @@ # # -import logging -import threading +from threading import Thread import time +from logging import getLogger as _Logger from logitech.unifying_receiver import api from logitech.unifying_receiver.listener import EventsListener @@ -14,11 +14,11 @@ from logitech.devices import constants as C import actions -_l = logging.getLogger('watcher') +_l = _Logger('watcher') - -_STATUS_TIMEOUT = 31 # seconds -_THREAD_SLEEP = 2 # seconds +_STATUS_TIMEOUT = 61 # seconds +_THREAD_SLEEP = 3 # seconds +_SLEEP_QUANT = 0.33 # seconds _UNIFYING_RECEIVER = 'Unifying Receiver' _NO_RECEIVER = 'Receiver not found.' @@ -38,20 +38,21 @@ class _DevStatus(api.AttachedDeviceInfo): return 'DevStatus(%d,%s,%d)' % (self.number, self.name, self.code) -class Watcher(threading.Thread): +class Watcher(Thread): """Keeps a map of all attached devices and their statuses.""" - def __init__(self, notify_callback=None): + def __init__(self, status_changed_callback, notify_callback=None): super(Watcher, self).__init__(group='Solaar', name='Watcher') self.daemon = True self.active = False self.notify = notify_callback self.status_text = None - self.status_changed = threading.Event() + self.status_changed_callback = status_changed_callback self.listener = None self.rstatus = _DevStatus(0, 0xFF, None, _UNIFYING_RECEIVER, None, None) + self.rstatus.max_devices = api.C.MAX_ATTACHED_DEVICES self.rstatus.refresh = (actions.full_scan, self) self.rstatus.pair = None # (actions.pair, self) @@ -62,7 +63,6 @@ class Watcher(threading.Thread): while self.active: if self.listener is None: - self._device_status_changed(self.rstatus, (C.STATUS.UNKNOWN, _SCANNING)) self._update_status_text() receiver = api.open() @@ -77,13 +77,17 @@ class Watcher(threading.Thread): self.listener = EventsListener(receiver, self._events_callback) self.listener.start() + + # need to wait for the thread to come alive + time.sleep(_SLEEP_QUANT / 2) elif not self.listener: self.listener = None self.devices.clear() if self.listener: if self.devices: - update_icon = self._check_old_statuses() + update_icon = self._device_status_changed(self.rstatus, (C.STATUS.CONNECTED, _OKAY)) + update_icon |= self._check_old_statuses() else: update_icon = self._device_status_changed(self.rstatus, (C.STATUS.CONNECTED, _NO_DEVICES)) else: @@ -92,8 +96,11 @@ class Watcher(threading.Thread): if update_icon: self._update_status_text() - if self.active: - time.sleep(_THREAD_SLEEP) + for i in range(0, int(_THREAD_SLEEP / _SLEEP_QUANT)): + if self.active: + time.sleep(_SLEEP_QUANT) + else: + break if self.listener: self.listener.stop() @@ -137,6 +144,46 @@ class Watcher(threading.Thread): self._device_status_changed(self.rstatus, (C.STATUS.CONNECTED, _OKAY)) return devstatus + def _device_status_changed(self, devstatus, status): + if status is None: + return False + + old_status_code = devstatus.code + devstatus.timestamp = time.time() + + if type(status) == int: + status_code = status + if status_code in C.STATUS_NAME: + status_text = C.STATUS_NAME[status_code] + else: + status_code = status[0] + if isinstance(status[1], str): + status_text = status[1] + elif isinstance(status[1], dict): + status_text = '' + for key, value in status[1].items(): + if key == 'text': + status_text = value + else: + setattr(devstatus, key, value) + else: + _l.warn("don't know how to handle status %s", status) + return False + + if ((status_code == old_status_code and status_text == devstatus.text) or + (status_code == C.STATUS.CONNECTED and old_status_code > C.STATUS.CONNECTED)): + # this is just successful ping for a device with an already known status + return False + + devstatus.code = status_code + devstatus.text = status_text + _l.debug("%s update %s => %s: %s", devstatus, old_status_code, status_code, status_text) + + if self.notify: + self.notify(devstatus.code, devstatus.name, devstatus.text) + + return True + def _events_callback(self, code, devnumber, data): # _l.debug("event %s", (code, devnumber, data)) @@ -161,47 +208,6 @@ class Watcher(threading.Thread): if updated: self._update_status_text() - def _device_status_changed(self, devstatus, status=None): - if status is None: - return False - - old_status_code = devstatus.code - devstatus.timestamp = time.time() - - if type(status) == int: - status_code = status - if status_code in C.STATUS_NAME: - status_text = C.STATUS_NAME[status_code] - else: - status_code = status[0] - if isinstance(status[1], str): - status_text = status[1] - elif isinstance(status[1], dict): - status_text = '' - for key, value in status[1].items(): - if key == 'text': - status_text = value - else: - setattr(devstatus, key, value) - else: - status_code = C.STATUS.UNKNOWN - status_text = '' - - if ((status_code == old_status_code and status_text == devstatus.text) or - (status_code == C.STATUS.CONNECTED and old_status_code > C.STATUS.CONNECTED)): - # this is just successful ping for a device with an already known status - return False - - devstatus.code = status_code - devstatus.text = status_text - _l.debug("%s update %s => %s: %s", devstatus, old_status_code, status_code, status_text) - - if self.notify: - if status_code < C.STATUS.CONNECTED or old_status_code < C.STATUS.CONNECTED or status_code < old_status_code: - self.notify(devstatus.code, devstatus.name, devstatus.text) - - return True - def _update_status_text(self): last_status_text = self.status_text @@ -210,12 +216,11 @@ class Watcher(threading.Thread): if self.rstatus.code < C.STATUS.CONNECTED: lines += (self.rstatus.text, '') - devstatuses = [self.devices[d] for d in range(1, 1 + api.C.MAX_ATTACHED_DEVICES) if d in self.devices] + devstatuses = [self.devices[d] for d in range(1, 1 + self.rstatus.max_devices) if d in self.devices] for devstatus in devstatuses: if devstatus.text: if ' ' in devstatus.text: - lines.append('' + devstatus.name + '') - lines.append(' ' + devstatus.text) + lines += ('' + devstatus.name + '', ' ' + devstatus.text) else: lines.append('' + devstatus.name + ' ' + devstatus.text) else: @@ -227,4 +232,4 @@ class Watcher(threading.Thread): self.status_text = self.rstatus.text if self.status_text != last_status_text: - self.status_changed.set() + self.status_changed_callback(self.status_text, self.rstatus, self.devices) diff --git a/lib/logitech/unifying_receiver/api.py b/lib/logitech/unifying_receiver/api.py index b84e51b2..f4eaa55d 100644 --- a/lib/logitech/unifying_receiver/api.py +++ b/lib/logitech/unifying_receiver/api.py @@ -2,7 +2,7 @@ # Logitech Unifying Receiver API. # -import logging +from logging import getLogger as _Logger from struct import pack as _pack from struct import unpack as _unpack from binascii import hexlify as _hexlify @@ -16,7 +16,7 @@ from . import base as _base _LOG_LEVEL = 5 -_l = logging.getLogger('lur.api') +_l = _Logger('lur.api') # # diff --git a/lib/logitech/unifying_receiver/base.py b/lib/logitech/unifying_receiver/base.py index a0c198c1..e29c135a 100644 --- a/lib/logitech/unifying_receiver/base.py +++ b/lib/logitech/unifying_receiver/base.py @@ -3,7 +3,7 @@ # Unlikely to be used directly unless you're expanding the API. # -import logging +from logging import getLogger as _Logger from struct import pack as _pack from binascii import hexlify as _hexlify @@ -14,7 +14,7 @@ import hidapi as _hid _LOG_LEVEL = 4 -_l = logging.getLogger('lur.base') +_l = _Logger('lur.base') # # These values are defined by the Logitech documentation. diff --git a/lib/logitech/unifying_receiver/listener.py b/lib/logitech/unifying_receiver/listener.py index 36fc51a6..5c468270 100644 --- a/lib/logitech/unifying_receiver/listener.py +++ b/lib/logitech/unifying_receiver/listener.py @@ -2,8 +2,8 @@ # # -import logging -import threading +from logging import getLogger as _Logger +from threading import (Thread, Event, Lock) from time import sleep as _sleep from . import base as _base @@ -11,13 +11,13 @@ from . import exceptions as E # for both Python 2 and 3 try: - import Queue as queue + from Queue import Queue except ImportError: - import queue + from queue import Queue -_LOG_LEVEL = 5 -_l = logging.getLogger('lur.listener') +_LOG_LEVEL = 4 +_l = _Logger('lur.listener') _READ_EVENT_TIMEOUT = int(_base.DEFAULT_TIMEOUT / 4) # ms @@ -30,11 +30,14 @@ def _callback_caller(listener, callback): event = listener.events.get() if _l.isEnabledFor(_LOG_LEVEL): _l.log(_LOG_LEVEL, "%s delivering event %s", listener, event) - callback.__call__(*event) + try: + callback.__call__(*event) + except: + _l.exception("callback for %s", event) # _l.log(_LOG_LEVEL, "%s stopped callback caller", listener) -class EventsListener(threading.Thread): +class EventsListener(Thread): """Listener thread for events from the Unifying Receiver. Incoming events (reply_code, devnumber, data) will be passed to the callback @@ -53,14 +56,14 @@ class EventsListener(threading.Thread): self.receiver = receiver self.task = None - self.task_processing = threading.Lock() + self.task_processing = Lock() self.task_reply = None - self.task_done = threading.Event() + self.task_done = Event() - self.events = queue.Queue(32) + self.events = Queue(16) - self.event_caller = threading.Thread(group='Unifying Receiver', name='Callback-%x' % receiver, target=_callback_caller, args=(self, events_callback)) + self.event_caller = Thread(group='Unifying Receiver', name='Callback-%x' % receiver, target=_callback_caller, args=(self, events_callback)) self.event_caller.daemon = True self.__str_cached = 'Events(%x)' % self.receiver @@ -102,6 +105,7 @@ class EventsListener(threading.Thread): """Tells the listener to stop as soon as possible.""" _l.log(_LOG_LEVEL, "%s stopping", self) self._active = False + self.join() def request(self, api_function, *args, **kwargs): """Make an UR API request through this listener's receiver. diff --git a/solaar b/solaar index 24a93a44..d7af0b9c 100755 --- a/solaar +++ b/solaar @@ -1,12 +1,12 @@ #!/bin/sh -cd -P `dirname "$0"` +cd -P `dirname "$0"` >/dev/null 2>&1 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/lib/native/`uname -m` export PYTHONPATH=$PWD/app:$PWD/lib export XDG_DATA_DIRS=$PWD/resources:$XDG_DATA_DIRS -cd - +cd - >/dev/null 2>&1 exec python -OO -m solaar "$@" -# exec python -OO -m profile -o $TMPDIR/profile.log solaar.py "$@" +#exec python -OO -m profile -o $TMPDIR/profile.log app/solaar.py "$@"