diff --git a/app/listener.py b/app/listener.py index 9ed252e7..c552a233 100644 --- a/app/listener.py +++ b/app/listener.py @@ -34,7 +34,7 @@ del namedtuple # # -_POLL_TICK = 30 # seconds +_POLL_TICK = 60 # seconds class ReceiverListener(_listener.EventsListener): diff --git a/app/ui/__init__.py b/app/ui/__init__.py index fb1d4467..fb09b31f 100644 --- a/app/ui/__init__.py +++ b/app/ui/__init__.py @@ -2,8 +2,6 @@ # # -# from gi import pygtkcompat -# pygtkcompat.enable_gtk() from gi.repository import GObject, Gtk GObject.threads_init() diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 67b8c9dc..7f9ae4c8 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -219,22 +219,23 @@ def create(title, name, max_devices, systray=False): # def _update_device_info_label(label, dev): - items = [('Wireless PID', dev.wpid), ('Serial', dev.serial)] + items = [] hid = dev.protocol if hid: items += [('Protocol', 'HID++ %1.1f' % dev.protocol)] + items += [('Wireless PID', dev.wpid), ('Serial', dev.serial)] firmware = dev.firmware if firmware: - items += [(f.kind, f.name + ' ' + f.version) for f in firmware] + items += [(f.kind, (f.name + ' ' + f.version).strip()) for f in firmware] - label.set_markup('' + '\n'.join('%-12s: %s' % item for item in items) + '') + label.set_markup('' + '\n'.join('%-13s: %s' % item for item in items) + '') def _update_receiver_info_label(label, dev): if label.get_visible() and '\n' not in label.get_text(): - items = [('Serial', dev.serial)] + \ + items = [('Path', dev.path), ('Serial', dev.serial)] + \ [(f.kind, f.version) for f in dev.firmware] - label.set_markup('' + '\n'.join('%-10s: %s' % item for item in items) + '') + label.set_markup('' + '\n'.join('%-13s: %s' % item for item in items) + '') def _toggle_info_box(action, box, frame, update_function): @@ -268,18 +269,22 @@ def _update_receiver_box(frame, receiver): pairing_icon.set_visible(False) pairing_icon.set_sensitive(True) pairing_icon._tick = 0 - toolbar.set_visible(True) + toolbar.set_sensitive(True) else: frame._device = None icon.set_sensitive(False) pairing_icon.set_visible(False) - toolbar.set_visible(False) + toolbar.set_sensitive(False) toolbar.get_children()[0].set_active(False) info_label.set_text('') def _update_device_box(frame, dev): - # print (dev.name, dev.kind) + if dev is None: + frame.set_visible(False) + frame.set_name(_PLACEHOLDER) + frame._device = None + return icon, label, toolbar, info_label = ui.find_children(frame, 'icon', 'label', 'toolbar', 'info-label') @@ -356,17 +361,9 @@ def update(window, receiver, device=None): assert len(frames) == 1 + receiver.max_devices, frames if device: - frame = frames[device.number] - if device.status is None: - frame.set_visible(False) - frame.set_name(_PLACEHOLDER) - frame._device = None - else: - _update_device_box(frame, device) + _update_device_box(frames[device.number], None if device.status is None else device) else: _update_receiver_box(frames[0], receiver) if not receiver: for frame in frames[1:]: - frame.set_visible(False) - frame.set_name(_PLACEHOLDER) - frame._device = None + _update_device_box(frame, None) diff --git a/lib/logitech/unifying_receiver/base.py b/lib/logitech/unifying_receiver/base.py index 7b5847a9..d3a6ceb9 100644 --- a/lib/logitech/unifying_receiver/base.py +++ b/lib/logitech/unifying_receiver/base.py @@ -239,7 +239,7 @@ def make_event(devnumber, data): return _Event(devnumber, sub_id, ord(data[1:2]), data[2:]) else: address = ord(data[1:2]) - if sub_id > 0x00 and sub_id < 0x80 and (address & 0x01) == 0: + if sub_id > 0x00 and (sub_id >= 0x40 or (address & 0x01 == 0)): return _Event(devnumber, sub_id, address, data[2:]) diff --git a/lib/logitech/unifying_receiver/devices.py b/lib/logitech/unifying_receiver/devices.py index 772b5d18..74f626fd 100644 --- a/lib/logitech/unifying_receiver/devices.py +++ b/lib/logitech/unifying_receiver/devices.py @@ -3,28 +3,64 @@ # from collections import namedtuple -_D = namedtuple('_DeviceDescriptor', ['codename', 'name', 'kind']) -del namedtuple -DEVICES = ( _D('M315', 'Wireless Mouse M315', 'mouse'), - _D('M325', 'Wireless Mouse M325', 'mouse'), - _D('M505', 'Wireless Mouse M505', 'mouse'), - _D('M510', 'Wireless Mouse M510', 'mouse'), - _D('M515', 'Couch Mouse M515', 'mouse'), - _D('M525', 'Wireless Mouse M525', 'mouse'), - _D('M570', 'Wireless Trackball M570', 'trackball'), - _D('M600', 'Touch Mouse M600', 'mouse'), - _D('M705', 'Marathon Mouse M705', 'mouse'), - _D('K270', 'Wireless Keyboard K270', 'keyboard'), - _D('K350', 'Wireless Keyboard K350', 'keyboard'), - _D('K360', 'Wireless Keyboard K360', 'keyboard'), - _D('K400', 'Wireless Touch Keyboard K400', 'keyboard'), - _D('K750', 'Wireless Solar Keyboard K750', 'keyboard'), - _D('K800', 'Wireless Illuminated Keyboard K800', 'keyboard'), - _D('T400', 'Zone Touch Mouse T400', 'mouse'), - _D('T650', 'Wireless Rechargeable Touchpad T650', 'touchpad'), - _D('Cube', 'Logitech Cube', 'mouse'), - _D('Anywhere MX', 'Anywhere Mouse MX', 'mouse'), - _D('Performance MX', 'Performance Mouse MX', 'mouse'), - ) -DEVICES = { d.codename: d for d in DEVICES } +_DeviceDescriptor = namedtuple('_DeviceDescriptor', + ['name', 'kind', 'codename', 'settings']) + +DEVICES = {} + +def _D(name, codename=None, kind=None): + if kind is None: + kind = ('mouse' if 'Mouse' in name + else 'keyboard' if 'Keyboard' in name + else 'touchpad' if 'Touchpad' in name + else 'trackball' if 'Trackball' in name + else None) + assert kind is not None + + if codename is None: + codename = name.split(' ')[-1] + assert codename is not None + + DEVICES[codename] = _DeviceDescriptor(name, kind, codename, None) + + +_D('Wireless Mouse M315') +_D('Wireless Mouse M325') +_D('Wireless Mouse M505') +_D('Wireless Mouse M510') +_D('Couch Mouse M515') +_D('Wireless Mouse M525') +_D('Wireless Trackball M570') +_D('Touch Mouse M600') +_D('Marathon Mouse M705') +_D('Wireless Keyboard K270') +_D('Wireless Keyboard K350') +_D('Wireless Keyboard K360') +_D('Wireless Touch Keyboard K400') +_D('Wireless Solar Keyboard K750') +_D('Wireless Illuminated Keyboard K800') +_D('Zone Touch Mouse T400') +_D('Wireless Rechargeable Touchpad T650') +_D('Logitech Cube', kind='mouse') +_D('Anywhere Mouse MX', codename='Anywhere MX') +_D('Performance Mouse MX', codename='Performance MX') + # DPI=(0x64, {0x80: 100, + # 0x81: 200, + # 0x82: 300, + # 0x83: 400, + # 0x84: 500, + # 0x85: 600, + # 0x86: 800, + # 0x87: 900, + # 0x88: 1000, + # 0x89: 1100, + # 0x8A: 1200, + # 0x8B: 1300, + # 0x8C: 1400, + # 0x8D: 1500}), + # Leds=(0x51, {}), + +del _D +del _DeviceDescriptor +del namedtuple diff --git a/lib/logitech/unifying_receiver/hidpp10.py b/lib/logitech/unifying_receiver/hidpp10.py index 8fb77564..dfb9b075 100644 --- a/lib/logitech/unifying_receiver/hidpp10.py +++ b/lib/logitech/unifying_receiver/hidpp10.py @@ -74,23 +74,33 @@ def get_battery(device): return charge, None -def get_receiver_serial(receiver): - serial = receiver.request(0x83B5, 0x03) +def get_serial(device): + if device.kind is None: + dev_id = 0x03 + receiver = device + else: + dev_id = 0x30 + device.number - 1 + receiver = device.receiver + + serial = receiver.request(0x83B5, dev_id) if serial: return _strhex(serial[1:5]) -def get_receiver_firmware(receiver): +def get_firmware(device): firmware = [] - reply = receiver.request(0x83B5, 0x02) + reply = device.request(0x81F1, 0x01) 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_version = _strhex(reply[1:3]) + fw_version = '%s.%s' % (fw_version[0:2], fw_version[2:4]) + reply = device.request(0x81F1, 0x02) + if reply: + fw_version += '.B' + _strhex(reply[1:3]) fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, '', fw_version, None) firmware.append(fw) - reply = receiver.request(0x81F1, 0x04) + reply = device.request(0x81F1, 0x04) if reply: bl_version = _strhex(reply[1:3]) bl_version = '%s.%s' % (bl_version[0:2], bl_version[2:4]) diff --git a/lib/logitech/unifying_receiver/receiver.py b/lib/logitech/unifying_receiver/receiver.py index ae42dfdb..6ec970e6 100644 --- a/lib/logitech/unifying_receiver/receiver.py +++ b/lib/logitech/unifying_receiver/receiver.py @@ -63,7 +63,10 @@ class PairedDevice(object): @property def power_switch_location(self): if self._power_switch is None: - self.serial + ps = self.receiver.request(0x83B5, 0x30 + self.number - 1) + if ps: + ps = ord(ps[9:10]) & 0x0F + self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps] return self._power_switch @property @@ -79,7 +82,7 @@ class PairedDevice(object): def name(self): if self._name is None: if self.codename in _DEVICES: - _, self._name, self._kind = _DEVICES[self._codename] + self._name, self._kind = _DEVICES[self._codename][:2] elif self.protocol >= 2.0: self._name = _hidpp20.get_name(self) return self._name or self.codename or '?' @@ -95,27 +98,25 @@ class PairedDevice(object): self._wpid = _strhex(pair_info[3:5]) if self._kind is None: if self.codename in _DEVICES: - _, self._name, self._kind = _DEVICES[self._codename] + self._name, self._kind = _DEVICES[self._codename][:2] elif self.protocol >= 2.0: self._kind = _hidpp20.get_kind(self) return self._kind or '?' @property def firmware(self): - if self._firmware is None and self.protocol >= 2.0: - self._firmware = _hidpp20.get_firmware(self) - # _log.debug("device %d firmware %s", self.number, self._firmware) + if self._firmware is None: + p = self.protocol + if p >= 2.0: + self._firmware = _hidpp20.get_firmware(self) + elif p >= 1.0: + self._firmware = _hidpp10.get_firmware(self) return self._firmware or () @property def serial(self): if self._serial is None: - serial = self.receiver.request(0x83B5, 0x30 + self.number - 1) - if serial: - self._serial = _strhex(serial[1:5]) - # _log.debug("device %d serial %s", self.number, self._serial) - ps_location = ord(serial[9:10]) & 0x0F - self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps_location] + self._serial = _hidpp10.get_serial(self) return self._serial or '?' @property @@ -185,13 +186,13 @@ class Receiver(object): @property def serial(self): if self._serial is None and self.handle: - self._serial = _hidpp10.get_receiver_serial(self) + self._serial = _hidpp10.get_serial(self) return self._serial @property def firmware(self): if self._firmware is None and self.handle: - self._firmware = _hidpp10.get_receiver_firmware(self) + self._firmware = _hidpp10.get_firmware(self) return self._firmware def enable_notifications(self, enable=True): diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index 6944fb9f..5692e9fd 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -27,8 +27,9 @@ BATTERY_STATUS='battery-status' LIGHT_LEVEL='light-level' ERROR='error' -# make sure we try to update the device status at least once a minute -_STATUS_TIMEOUT = 60 # seconds +# if not updates have been receiver from the device for a while, assume +# it has gone offline and clear all its know properties. +_STATUS_TIMEOUT = 180 # seconds # # @@ -179,12 +180,15 @@ class DeviceStatus(dict): return True - if event.sub_id >= 0x40: + if event.sub_id >= 0x80: # this can't possibly be an event, can it? if _log.isEnabledFor(_DEBUG): _log.debug("ignoring non-event %s", event) return False + if event.sub_id >= 0x40: + _log.warn("don't know how to handle event %s", event) + # this must be a feature event, assuming no device has more than 0x40 features if event.sub_id >= len(self._device.features): _log.warn("device %d got event from unknown feature index %02X", self._device.number, event.sub_id) @@ -243,8 +247,8 @@ class DeviceStatus(dict): _log.debug("Solar key pressed") # first cancel any reporting self._device.feature_request(_hidpp20.FEATURE.SOLAR_CHARGE) - reports_count = 10 - reports_period = 3 # seconds + reports_count = 15 + reports_period = 2 # seconds self._changed(alert=ALERT.MED) # trigger a new report chain self._device.feature_request(_hidpp20.FEATURE.SOLAR_CHARGE, 0x00, reports_count, reports_period)