From 8f5fa0cf9afcea3aee6239c94be53c2ce1649b93 Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Sat, 1 Dec 2012 15:49:52 +0200 Subject: [PATCH] code clean-ups, the app starts faster now --- app/listener.py | 64 ++++++++-------------- app/ui/__init__.py | 2 + app/ui/action.py | 13 ++++- app/ui/main_window.py | 4 +- app/ui/pair_window.py | 13 ++--- app/ui/status_icon.py | 19 +++++-- lib/logitech/unifying_receiver/base.py | 9 ++- lib/logitech/unifying_receiver/hidpp10.py | 31 ++++++++++- lib/logitech/unifying_receiver/hidpp20.py | 11 +--- lib/logitech/unifying_receiver/listener.py | 13 +++-- lib/logitech/unifying_receiver/receiver.py | 62 +++++++-------------- lib/logitech/unifying_receiver/status.py | 18 ++---- 12 files changed, 134 insertions(+), 125 deletions(-) diff --git a/app/listener.py b/app/listener.py index ea74b2ff..0e21c7e4 100644 --- a/app/listener.py +++ b/app/listener.py @@ -6,12 +6,13 @@ from logging import getLogger, DEBUG as _DEBUG _log = getLogger('listener') del getLogger -from logitech.unifying_receiver import ( - Receiver, PairedDevice, - listener as _listener, - hidpp10 as _hidpp10, - hidpp20 as _hidpp20, - status as _status) +from types import MethodType as _MethodType + +from logitech.unifying_receiver import (Receiver, + listener as _listener, + hidpp10 as _hidpp10, + hidpp20 as _hidpp20, + status as _status) # # @@ -35,16 +36,6 @@ DUMMY = _DUMMY_RECEIVER() _DEVICE_STATUS_POLL = 30 # seconds _DEVICE_TIMEOUT = 2 * _DEVICE_STATUS_POLL # seconds -# def _fake_device(listener): -# dev = _lur.PairedDevice(listener.receiver, 6) -# dev._wpid = '1234' -# dev._kind = 'touchpad' -# dev._codename = 'T650' -# dev._name = 'Wireless Rechargeable Touchpad T650' -# dev._serial = '0123456789' -# dev._protocol = 2.0 -# dev.status = _lur.status.DeviceStatus(dev, listener._status_changed) -# return dev class ReceiverListener(_listener.EventsListener): """Keeps the status of a Unifying Receiver. @@ -57,29 +48,23 @@ class ReceiverListener(_listener.EventsListener): self.status_changed_callback = status_changed_callback receiver.status = _status.ReceiverStatus(receiver, self._status_changed) - Receiver.create_device = self.create_device - def create_device(self, receiver, number): - if bool(self): - dev = PairedDevice(receiver, number) - if dev.wpid: - dev.status = _status.DeviceStatus(dev, self._status_changed) - _log.info("new device %s", dev) - return dev + # enhance the original register_new_device + def _register_with_status(r, number): + if bool(self): + dev = r.__register_new_device(number) + if dev is not None: + # read these as soon as possible, they will be used everywhere + dev.protocol, dev.codename + dev.status = _status.DeviceStatus(dev, self._status_changed) + return dev + receiver.__register_new_device = receiver.register_new_device + receiver.register_new_device = _MethodType(_register_with_status, receiver) def has_started(self): _log.info("events listener has started") - # self._status_changed(self.receiver) + self._status_changed(self.receiver) self.receiver.enable_notifications() - - for dev in self.receiver: - dev.codename, dev.kind, dev.name - # dev.status._changed(dev.protocol > 0) - - # fake = _fake_device(self) - # self.receiver._devices[fake.number] = fake - # self._status_changed(fake, _status.ALERT.LOW) - self.receiver.notify_devices() self._status_changed(self.receiver, _status.ALERT.LOW) @@ -88,13 +73,12 @@ class ReceiverListener(_listener.EventsListener): if self.receiver: self.receiver.enable_notifications(False) self.receiver.close() - self.receiver = None self._status_changed(None, alert=_status.ALERT.LOW) def tick(self, timestamp): if _log.isEnabledFor(_DEBUG): - _log.debug("tick: polling status: %s %s", self.receiver, self.receiver._devices) + _log.debug("tick: polling status: %s %s", self.receiver, list(iter(self.receiver))) if self._last_tick > 0 and timestamp - self._last_tick > _DEVICE_STATUS_POLL * 2: # if we missed a couple of polls, most likely the computer went into @@ -114,7 +98,7 @@ class ReceiverListener(_listener.EventsListener): for dev in self.receiver: if dev.status: # read these in case they haven't been read already - dev.wpid, dev.serial, dev.protocol, dev.firmware + dev.protocol, dev.serial, dev.firmware if _status.BATTERY_LEVEL not in dev.status: battery = _hidpp20.get_battery(dev) or _hidpp10.get_battery(dev) @@ -130,12 +114,12 @@ class ReceiverListener(_listener.EventsListener): if _log.isEnabledFor(_DEBUG): _log.debug("status_changed %s: %s (%X) %s", device, None if device is None else device.status, alert, reason or '') if self.status_changed_callback: - if device is None or device is self.receiver: + if device is None or device.kind is None: self.status_changed_callback(self.receiver or DUMMY, None, alert, reason) else: self.status_changed_callback(self.receiver or DUMMY, device, alert, reason) - if device.status is None: - self.status_changed_callback(self.receiver, None) + # if device.status is None: + # self.status_changed_callback(self.receiver, None) def _events_handler(self, event): assert self.receiver diff --git a/app/ui/__init__.py b/app/ui/__init__.py index fb09b31f..fb1d4467 100644 --- a/app/ui/__init__.py +++ b/app/ui/__init__.py @@ -2,6 +2,8 @@ # # +# from gi import pygtkcompat +# pygtkcompat.enable_gtk() from gi.repository import GObject, Gtk GObject.threads_init() diff --git a/app/ui/action.py b/app/ui/action.py index 7e9c5aa1..20fa0070 100644 --- a/app/ui/action.py +++ b/app/ui/action.py @@ -2,7 +2,7 @@ # # -# from sys import version as PYTTHON_VERSION +# from sys import version as PYTHON_VERSION from gi.repository import Gtk, Gdk import ui @@ -46,13 +46,19 @@ def _show_about_window(action): about.set_version(_VERSION) about.set_comments('Shows status of devices connected\nto a Logitech Unifying Receiver.') - about.set_license_type(Gtk.License.GPL_2_0) about.set_copyright(b'\xC2\xA9'.decode('utf-8') + ' 2012 Daniel Pavel') + about.set_license_type(Gtk.License.GPL_2_0) about.set_authors(('Daniel Pavel http://github.com/pwr',)) try: about.add_credit_section('Testing', ('Douglas Wagner',)) - except Exception: + about.add_credit_section('Technical specifications\nprovided by', + ('Julien Danjou http://julien.danjou.info/blog/2012/logitech-unifying-upower',)) + except TypeError: + # gtk3 < 3.6 has incorrect gi bindings + pass + except: + # is the Gtk3 version too old? pass about.set_website('http://github.com/pwr/Solaar/wiki') @@ -73,6 +79,7 @@ def _pair_device(action, frame): pair_dialog = ui.pair_window.create(action, frame._device) pair_dialog.set_transient_for(window) + pair_dialog.set_destroy_with_parent(True) pair_dialog.set_modal(True) pair_dialog.set_type_hint(Gdk.WindowTypeHint.DIALOG) pair_dialog.set_position(Gtk.WindowPosition.CENTER) diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 22007624..bb5eb8d8 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -268,7 +268,7 @@ def _update_receiver_info_label(label, dev): def _toggle_info_box(action, label_widget, box_widget, frame, update_function): if action.get_active(): box_widget.set_visible(True) - GObject.timeout_add(60, update_function, label_widget, frame._device) + GObject.timeout_add(50, update_function, label_widget, frame._device) else: box_widget.set_visible(False) @@ -375,8 +375,8 @@ def _update_device_box(frame, dev): def update(window, receiver, device=None): - # print ("update", receiver, receiver.status, device) assert receiver is not None + # print ("update", receiver, receiver.status, device, None if device is None else device.number) window.set_icon_name(ui.appicon(receiver.status)) vbox = window._vbox diff --git a/app/ui/pair_window.py b/app/ui/pair_window.py index 3b4e1713..62d83391 100644 --- a/app/ui/pair_window.py +++ b/app/ui/pair_window.py @@ -64,15 +64,13 @@ def _check_lock_state(assistant, receiver): return False if receiver.status.get(_status.ERROR): - # fake = _fake_device(receiver) - # receiver._devices[fake.number] = fake - # receiver.status.new_device = fake - # fake.status._changed() + # receiver.status.new_device = _fake_device(receiver) _pairing_failed(assistant, receiver, receiver.status.pop(_status.ERROR)) return False if receiver.status.new_device: - _pairing_succeeded(assistant, receiver) + device, receiver.status.new_device = receiver.status.new_device, None + _pairing_succeeded(assistant, receiver, device) return False if not receiver.status.lock_open: @@ -129,8 +127,7 @@ def _pairing_failed(assistant, receiver, error): assistant.commit() -def _pairing_succeeded(assistant, receiver): - device, receiver.status.new_device = receiver.status.new_device, None +def _pairing_succeeded(assistant, receiver, device): assert device if _log.isEnabledFor(_DEBUG): _log.debug("%s success: %s", receiver, device) @@ -173,7 +170,7 @@ def create(action, receiver): assistant.set_title(action.get_label()) assistant.set_icon_name(action.get_icon_name()) - assistant.set_size_request(420, 240) + assistant.set_size_request(400, 240) assistant.set_resizable(False) assistant.set_role('pair-device') diff --git a/app/ui/status_icon.py b/app/ui/status_icon.py index 4e8098a0..ed2de42c 100644 --- a/app/ui/status_icon.py +++ b/app/ui/status_icon.py @@ -14,6 +14,7 @@ def create(window, menu_actions=None): icon.set_title(name) icon.set_name(name) icon.set_from_icon_name(ui.appicon(False)) + icon._devices = {} icon.set_tooltip_text(name) icon.connect('activate', window.toggle_visible) @@ -44,6 +45,7 @@ def _icon_with_battery(s): assert mask mask = GdkPixbuf.Pixbuf.new_from_file(mask) assert mask.get_width() == 128 and mask.get_height() == 128 + mask.saturate_and_pixelate(mask, 0.8, False) battery = ui.icon_file(battery_icon, 128) assert battery @@ -59,15 +61,22 @@ def _icon_with_battery(s): return _PIXMAPS[name] def update(icon, receiver, device=None): - # print "icon update", receiver, receiver._devices, device + # print ("icon update", receiver, receiver.status, len(receiver._devices), device) battery_status = None + if device is not None: + icon._devices[device.number] = device + lines = [ui.NAME + ': ' + str(receiver.status), ''] - if receiver and receiver._devices: - for dev in receiver: + if receiver: + for k in range(1, 1+ receiver.max_devices): + dev = icon._devices.get(k) + if dev is None: + continue + lines.append('' + dev.name + '') - assert dev.status is not None + assert hasattr(dev, 'status') and dev.status is not None p = str(dev.status) if p: if not dev.status: @@ -86,6 +95,8 @@ def update(icon, receiver, device=None): if battery_status is None and dev.status.get(_status.BATTERY_LEVEL): battery_status = dev.status + else: + icon._devices.clear() icon.set_tooltip_markup('\n'.join(lines).rstrip('\n')) diff --git a/lib/logitech/unifying_receiver/base.py b/lib/logitech/unifying_receiver/base.py index b09a3f65..7b5847a9 100644 --- a/lib/logitech/unifying_receiver/base.py +++ b/lib/logitech/unifying_receiver/base.py @@ -130,7 +130,7 @@ def write(handle, devnumber, data): unloaded. The handle will be closed automatically. """ # the data is padded to either 5 or 18 bytes - if len(data) > _SHORT_MESSAGE_SIZE - 2: + if len(data) > _SHORT_MESSAGE_SIZE - 2 or data[:1] == b'\x82': wdata = _pack('!BB18s', 0x11, devnumber, data) else: wdata = _pack('!BB5s', 0x10, devnumber, data) @@ -306,6 +306,13 @@ def request(handle, devnumber, request_id, *params): raise _hidpp20.FeatureCallError(number=devnumber, request=request_id, error=error, params=params) if reply_data[:2] == request_str: + if request_id & 0xFF00 == 0x8300: + # long registry r/w should return a long reply + assert report_id == 0x11 + elif request_id & 0xF000 == 0x8000: + # short registry r/w should return a short reply + assert report_id == 0x10 + if devnumber == 0xFF: if request_id == 0x83B5 or request_id == 0x81F1: # these replies have to match the first parameter as well diff --git a/lib/logitech/unifying_receiver/hidpp10.py b/lib/logitech/unifying_receiver/hidpp10.py index 4f2ae120..a7496135 100644 --- a/lib/logitech/unifying_receiver/hidpp10.py +++ b/lib/logitech/unifying_receiver/hidpp10.py @@ -2,7 +2,10 @@ # # -from .common import NamedInts as _NamedInts +from .common import (strhex as _strhex, + NamedInts as _NamedInts, + FirmwareInfo as _FirmwareInfo) +from .hidpp20 import FIRMWARE_KIND # # constants @@ -64,3 +67,29 @@ def get_battery(device): if reply: charge = ord(reply[:1]) return charge, None + + +def get_receiver_serial(receiver): + serial = receiver.request(0x83B5, 0x03) + if serial: + return _strhex(serial[1:5]) + + +def get_receiver_firmware(receiver): + firmware = [] + + reply = receiver.request(0x83B5, 0x02) + if reply: + fw_version = _strhex(reply[1:5]) + fw_version = '%s.%s.B%s' % (fw_version[0:2], fw_version[2:4], fw_version[4:8]) + fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, '', fw_version, None) + firmware.append(fw) + + reply = receiver.request(0x81F1, 0x04) + if reply: + bl_version = _strhex(reply[1:3]) + bl_version = '%s.%s' % (bl_version[0:2], bl_version[2:4]) + bl = _FirmwareInfo(FIRMWARE_KIND.Bootloader, '', bl_version, None) + firmware.append(bl) + + return tuple(firmware) diff --git a/lib/logitech/unifying_receiver/hidpp20.py b/lib/logitech/unifying_receiver/hidpp20.py index 85405b32..350cd132 100644 --- a/lib/logitech/unifying_receiver/hidpp20.py +++ b/lib/logitech/unifying_receiver/hidpp20.py @@ -3,6 +3,7 @@ # from struct import pack as _pack, unpack as _unpack +from weakref import proxy as _proxy from logging import getLogger, DEBUG as _DEBUG _log = getLogger('LUR').getChild('hidpp20') @@ -123,14 +124,12 @@ class FeaturesArray(object): def __init__(self, device): assert device is not None - self.device = device + self.device = _proxy(device) self.supported = True self.features = None def __del__(self): self.supported = False - self.features = None - self.device = None def _check(self): # print ("%s check" % self.device) @@ -251,13 +250,9 @@ class KeysArray(object): def __init__(self, device, count): assert device is not None - self.device = device + self.device = _proxy(device) self.keys = [None] * count - def __del__(self): - self.keys = None - self.device = None - def __getitem__(self, index): assert type(index) == int if index < 0 or index >= len(self.keys): diff --git a/lib/logitech/unifying_receiver/listener.py b/lib/logitech/unifying_receiver/listener.py index ed219e2c..c057a676 100644 --- a/lib/logitech/unifying_receiver/listener.py +++ b/lib/logitech/unifying_receiver/listener.py @@ -83,6 +83,7 @@ class ThreadedHandle(object): # _EVENT_READ_TIMEOUT = 500 +_IDLE_READS = 5 class EventsListener(_threading.Thread): @@ -111,6 +112,7 @@ class EventsListener(_threading.Thread): self.has_started() last_tick = 0 + idle_reads = 0 while self._active: if self._queued_events.empty(): @@ -136,10 +138,13 @@ class EventsListener(_threading.Thread): except: _log.exception("processing event %s", event) elif self.tick_period: - now = _timestamp() - if now - last_tick >= self.tick_period: - last_tick = now - self.tick(now) + idle_reads += 1 + if idle_reads % _IDLE_READS == 0: + idle_reads = 0 + now = _timestamp() + if now - last_tick >= self.tick_period: + last_tick = now + self.tick(now) _base.unhandled_hook = None del self._queued_events diff --git a/lib/logitech/unifying_receiver/receiver.py b/lib/logitech/unifying_receiver/receiver.py index 18bda89d..b89e399f 100644 --- a/lib/logitech/unifying_receiver/receiver.py +++ b/lib/logitech/unifying_receiver/receiver.py @@ -3,6 +3,7 @@ # import errno as _errno +from weakref import proxy as _proxy from logging import getLogger _log = getLogger('LUR').getChild('receiver') @@ -25,7 +26,7 @@ MAX_PAIRED_DEVICES = 6 class PairedDevice(object): def __init__(self, receiver, number): assert receiver - self.receiver = receiver + self.receiver = _proxy(receiver) assert number > 0 and number <= MAX_PAIRED_DEVICES self.number = number @@ -41,11 +42,6 @@ class PairedDevice(object): self.features = _hidpp20.FeaturesArray(self) - def __del__(self): - del self.receiver - del self.features - del self._keys - @property def protocol(self): if self._protocol is None: @@ -82,10 +78,9 @@ class PairedDevice(object): @property def name(self): if self._name is None: - if self.protocol < 2.0: - if self.codename in _DEVICES: - _, self._name, self._kind = _DEVICES[self._codename] - else: + if self.codename in _DEVICES: + _, self._name, self._kind = _DEVICES[self._codename] + elif self.protocol >= 2.0: self._name = _hidpp20.get_name(self) return self._name or self.codename or '?' @@ -99,10 +94,9 @@ class PairedDevice(object): if self._wpid is None: self._wpid = _strhex(pair_info[3:5]) if self._kind is None: - if self.protocol < 2.0: - if self.codename in _DEVICES: - _, self._name, self._kind = _DEVICES[self._codename] - else: + if self.codename in _DEVICES: + _, self._name, self._kind = _DEVICES[self._codename] + elif self.protocol >= 2.0: self._kind = _hidpp20.get_kind(self) return self._kind or '?' @@ -168,7 +162,6 @@ class Receiver(object): name = 'Unifying Receiver' kind = None max_devices = MAX_PAIRED_DEVICES - create_device = PairedDevice def __init__(self, handle, path=None): assert handle @@ -192,30 +185,13 @@ class Receiver(object): @property def serial(self): if self._serial is None and self.handle: - serial = self.request(0x83B5, 0x03) - if serial: - self._serial = _strhex(serial[1:5]) + self._serial = _hidpp10.get_receiver_serial(self) return self._serial @property def firmware(self): if self._firmware is None and self.handle: - firmware = [] - - reply = self.request(0x83B5, 0x02) - if reply: - fw_version = _strhex(reply[1:5]) - fw_version = '%s.%s.B%s' % (fw_version[0:2], fw_version[2:4], fw_version[4:8]) - firmware.append(_FirmwareInfo(_hidpp20.FIRMWARE_KIND.Firmware, '', fw_version, None)) - - reply = self.request(0x81F1, 0x04) - if reply: - bl_version = _strhex(reply[1:3]) - bl_version = '%s.%s' % (bl_version[0:2], bl_version[2:4]) - firmware.append(_FirmwareInfo(_hidpp20.FIRMWARE_KIND.Bootloader, '', bl_version, None)) - - self._firmware = tuple(firmware) - + self._firmware = _hidpp10.get_receiver_firmware(self) return self._firmware def enable_notifications(self, enable=True): @@ -241,6 +217,16 @@ class Receiver(object): if not self.request(0x8002, 0x02): _log.warn("failed to trigger device events") + def register_new_device(self, number): + if self._devices.get(number) is not None: + raise IndexError("device number %d already registered" % number) + dev = PairedDevice(self, number) + if dev.wpid: + _log.info("registered new device %d (%s)", number, dev.wpid) + self._devices[number] = dev + return dev + self._devices[number] = None + def set_lock(self, lock_closed=True, device=0, timeout=0): if self.handle: lock = 0x02 if lock_closed else 0x01 @@ -271,13 +257,7 @@ class Receiver(object): if key < 1 or key > MAX_PAIRED_DEVICES: raise IndexError(key) - dev = Receiver.create_device(self, key) - if dev is not None and dev.wpid: - self._devices[key] = dev - return dev - - # no paired device at this index - self._devices[key] = None + return self.register_new_device(key) def __delitem__(self, key): if self._devices.get(key) is None: diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index d3dd1579..a3109fcd 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -4,6 +4,7 @@ from time import time as _timestamp from struct import unpack as _unpack +from weakref import proxy as _proxy from logging import getLogger, DEBUG as _DEBUG _log = getLogger('LUR.status') @@ -33,7 +34,7 @@ ERROR='error' class ReceiverStatus(dict): def __init__(self, receiver, changed_callback): assert receiver - self._receiver = receiver + self._receiver = _proxy(receiver) assert changed_callback self._changed_callback = changed_callback @@ -52,9 +53,7 @@ class ReceiverStatus(dict): def device_paired(self, number): _log.info("new device paired") - dev = self._receiver.create_device(self._receiver, number) - self._receiver._devices[number] = dev - self.new_device = dev + dev = self.new_device = self._receiver.register_new_device(number) return dev def _changed(self, alert=ALERT.LOW, reason=None): @@ -88,7 +87,7 @@ class ReceiverStatus(dict): class DeviceStatus(dict): def __init__(self, device, changed_callback): assert device - self._device = device + self._device = _proxy(device) assert changed_callback self._changed_callback = changed_callback @@ -126,13 +125,6 @@ class DeviceStatus(dict): # _log.debug("device %d changed: active=%s %s", self._device.number, self._active, dict(self)) self._changed_callback(self._device, alert, reason) - # @property - # def battery(self): - # battery = _hidpp10.get_battery_level(self) - # if battery is None: - # battery = _hidpp20.get_battery_level(self) - # return battery - def process_event(self, event): if event.sub_id == 0x40: if event.address == 0x02: @@ -241,7 +233,7 @@ class DeviceStatus(dict): else: self._changed() else: - _log.warn("SOLAR_CHARGE event not GOOD? %s", event) + _log.warn("SOLAR CHARGE event not GOOD? %s", event) return True if feature == _hidpp20.FEATURE.TOUCH_MOUSE: