From 5f46c820e6a44faaa2e148db3a644d7148ebb4a6 Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Fri, 17 May 2013 15:50:58 +0300 Subject: [PATCH] re-work the status icon updating --- lib/solaar/gtk.py | 1 + lib/solaar/listener.py | 4 +- lib/solaar/ui/status_icon.py | 178 ++++++++++++++++++++++++----------- 3 files changed, 126 insertions(+), 57 deletions(-) diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py index 63896529..3fd7cb0c 100644 --- a/lib/solaar/gtk.py +++ b/lib/solaar/gtk.py @@ -103,6 +103,7 @@ def _run(args): def _startup_check_receiver(): if not listeners: ui.notify.alert('No receiver found.') + ui.status_icon.update(status_icon) GLib.timeout_add(1000, _startup_check_receiver) from logitech.unifying_receiver import base as _base diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index ef492128..34b232f3 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -17,13 +17,13 @@ from logitech.unifying_receiver import (Receiver, # from collections import namedtuple -_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['receiver', 'number', 'name', 'kind', 'status']) +_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ['receiver', 'number', 'name', 'kind', 'serial', 'status']) _GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ del namedtuple def _ghost(device): - return _GHOST_DEVICE(receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, status=None) + return _GHOST_DEVICE(receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, serial=device.serial, status=None) # # diff --git a/lib/solaar/ui/status_icon.py b/lib/solaar/ui/status_icon.py index 0dee2bd4..3f7537ae 100644 --- a/lib/solaar/ui/status_icon.py +++ b/lib/solaar/ui/status_icon.py @@ -14,9 +14,6 @@ from logitech.unifying_receiver import status as _status # # -_NO_DEVICES = [None] * 6 - - def create(activate_callback): assert activate_callback @@ -24,23 +21,22 @@ def create(activate_callback): icon.set_title(NAME) icon.set_name(NAME) icon.set_from_icon_name(_icons.APP_ICON[0]) - icon._devices = list(_NO_DEVICES) + icon._devices_info = [] + icon._receivers = set() icon.set_tooltip_text(NAME) icon.connect('activate', activate_callback) menu = Gtk.Menu() - menu.append(Gtk.SeparatorMenuItem.new()) + # per-device menu entries will be generated as-needed + menu.append(Gtk.SeparatorMenuItem.new()) menu.append(_action.about.create_menu_item()) menu.append(_action.make('application-exit', 'Quit', Gtk.main_quit).create_menu_item()) menu.show_all() - for x in _NO_DEVICES: - m = Gtk.ImageMenuItem() - m.set_sensitive(False) - menu.insert(m, 0) + icon._menu = menu icon.connect('popup_menu', lambda icon, button, time, menu: @@ -48,6 +44,30 @@ def create(activate_callback): menu) return icon +# +# +# + +def _generate_tooltip_lines(icon): + yield '%s' % NAME + yield '' + + for _, serial, name, status in icon._devices_info: + yield '%s' % name + + p = str(status) + if p: # does it have any properties to print? + if status: + yield '\t%s' % p + else: + yield '\t%s (inactive)' % p + else: + if status: + yield '\tno status' + else: + yield '\t(inactive)' + yield '' + _PIXMAPS = {} def _icon_with_battery(level, active): @@ -73,57 +93,105 @@ def _icon_with_battery(level, active): return _PIXMAPS[name] -def update(icon, device): - assert device is not None - # print ("icon update", device) - - if device.kind is None: - receiver = device - if not device: - icon._devices[:] = _NO_DEVICES - else: - icon._devices[device.number] = None if device.status is None else device - receiver = device.receiver - - if not icon.is_embedded(): +def _update_image(icon): + if not icon._receivers: + icon.set_from_icon_name(_icons.APP_ICON[-1]) return - def _lines(r, devices): - yield '%s: %s' % (NAME, r.status) - yield '' - - for dev in devices: - if dev is None: - continue - - yield '%s' % dev.name - - assert hasattr(dev, 'status') and dev.status is not None - p = str(dev.status) - if p: # does it have any properties to print? - if dev.status: - yield '\t%s' % p - else: - yield '\t%s (inactive)' % p - else: - if dev.status: - yield '\tno status' - else: - yield '\t(inactive)' - yield '' - - icon.set_tooltip_markup('\n'.join(_lines(receiver, icon._devices)).rstrip('\n')) - battery_status = None battery_level = 1000 - for dev in icon._devices: - if dev is not None: - level = dev.status.get(_status.BATTERY_LEVEL) - if level is not None and level < battery_level: - battery_status = dev.status - battery_level = level + + for _, serial, name, status in icon._devices_info: + level = status.get(_status.BATTERY_LEVEL) + if level is not None and level < battery_level: + battery_status = status + battery_level = level if battery_status is None: - icon.set_from_icon_name(_icons.APP_ICON[1 if receiver else -1]) + icon.set_from_icon_name(_icons.APP_ICON[1]) else: icon.set_from_pixbuf(_icon_with_battery(battery_level, bool(battery_status))) + +# +# +# + +def _device_index(icon, device): + if device.receiver.serial in icon._receivers: + for index, (rserial, serial, name, _) in enumerate(icon._devices_info): + if rserial == device.receiver.serial and serial == device.serial: + return index + + +def _add_device(icon, device): + index = len(icon._devices_info) + device_info = (device.receiver.serial, device.serial, device.name, device.status) + icon._devices_info.append(device_info) + + menu_item = Gtk.ImageMenuItem.new_with_label(device.name) + icon._menu.insert(menu_item, index) + menu_item.set_image(Gtk.Image()) + menu_item.show_all() + + return index + + +def _remove_device(icon, index): + # print ("remove device", index) + assert index is not None + del icon._devices_info[index] + menu_items = icon._menu.get_children() + icon._menu.remove(menu_items[index]) + + +def _remove_receiver(icon, receiver): + icon._receivers.remove(receiver.serial) + index = 0 + while index < len(icon._devices_info): + rserial, _, _, _ = icon._devices_info[index] + # print ("remove receiver", index, rserial) + if rserial == receiver.serial: + _remove_device(icon, index) + else: + index += 1 + + +def _update_menu_item(icon, index, device_status): + menu_items = icon._menu.get_children() + menu_item = menu_items[index] + + image = menu_item.get_image() + battery_level = device_status.get(_status.BATTERY_LEVEL) if device_status else None + image.set_from_icon_name(_icons.battery(battery_level), Gtk.IconSize.MENU) + image.set_sensitive(battery_level is not None) + +# +# +# + +def update(icon, device=None): + # print ("icon update", device) + + if device is not None: + if device.kind is None: + # receiver + receiver = device + if receiver: + icon._receivers.add(receiver.serial) + else: + _remove_receiver(icon, receiver) + else: + # peripheral + index = _device_index(icon, device) + if device.status is None: + # was just unpaired + assert index is not None + _remove_device(icon, index) + else: + if index is None: + index = _add_device(icon, device) + _update_menu_item(icon, index, device.status) + + tooltip_lines = _generate_tooltip_lines(icon) + icon.set_tooltip_markup('\n'.join(tooltip_lines).rstrip('\n')) + _update_image(icon)