simplified window/icon code, reworked how device updates are signalled

This commit is contained in:
Daniel Pavel 2013-04-30 19:44:03 +02:00
parent 2397c6c0ea
commit e5a28ac64e
7 changed files with 149 additions and 156 deletions

View File

@ -45,67 +45,61 @@ def _run(args):
ui.notify.init()
from solaar.listener import DUMMY, ReceiverListener
window = ui.main_window.create(NAME, DUMMY.name, 6, True)
from solaar.listener import DUMMY_RECEIVER, ReceiverListener
window = ui.main_window.create(NAME)
assert window
menu_actions = (ui.action.toggle_notifications,
ui.action.about)
icon = ui.status_icon.create(window, menu_actions)
icon = ui.status_icon.create(window)
assert icon
listener = [None]
listeners = {}
# initializes the receiver listener
def check_for_listener(notify=False):
# print ("check_for_listener", notify)
listener[0] = None
try:
listener[0] = ReceiverListener.open(status_changed)
l = ReceiverListener.open(status_changed)
except OSError:
l = None
ui.error_dialog(window, 'Permissions error',
'Found a possible Unifying Receiver device,\n'
'but did not have permission to open it.')
if listener[0] is None:
listeners.clear()
if l:
listeners[l.receiver.serial] = l
else:
if notify:
status_changed(DUMMY)
status_changed(DUMMY_RECEIVER)
else:
return True
from gi.repository import Gtk, GObject
from logitech.unifying_receiver import status
from gi.repository import Gtk, GLib
from logitech.unifying_receiver.status import ALERT
# callback delivering status notifications from the receiver/devices to the UI
def status_changed(receiver, device=None, alert=status.ALERT.NONE, reason=None):
if alert & status.ALERT.SHOW_WINDOW:
GObject.idle_add(window.present)
if window:
GObject.idle_add(ui.main_window.update, window, receiver, device)
if icon:
GObject.idle_add(ui.status_icon.update, icon, receiver, device)
def status_changed(device, alert=ALERT.NONE, reason=None):
assert device is not None
if alert & ALERT.SHOW_WINDOW:
GLib.idle_add(window.present)
GLib.idle_add(ui.main_window.update, window, device)
GLib.idle_add(ui.status_icon.update, icon, device)
if ui.notify.available:
# always notify on receiver updates
if device is None or alert & status.ALERT.NOTIFICATION:
GObject.idle_add(ui.notify.show, device or receiver, reason)
if device is DUMMY_RECEIVER or alert & ALERT.NOTIFICATION:
GLib.idle_add(ui.notify.show, device, reason)
if receiver is DUMMY:
GObject.timeout_add(3000, check_for_listener)
if device is DUMMY_RECEIVER:
GLib.timeout_add(3000, check_for_listener)
GObject.timeout_add(10, check_for_listener, True)
if icon:
GObject.timeout_add(1000, ui.status_icon.check_systray, icon, window)
GLib.timeout_add(10, check_for_listener, True)
Gtk.main()
if listener[0]:
listener[0].stop()
map(ReceiverListener.stop, listeners.values())
ui.notify.uninit()
if listener[0]:
listener[0].join()
listener[0] = None
map(ReceiverListener.join, listeners.values())
def main():

View File

@ -25,7 +25,7 @@ del namedtuple
def _ghost(device):
return _GHOST_DEVICE(number=device.number, name=device.name, kind=device.kind, status=None, max_devices=None)
DUMMY = _GHOST_DEVICE(Receiver.number, 'dialog-error', None, 'Receiver not found.', 6)
DUMMY_RECEIVER = _GHOST_DEVICE(0xFF, 'Solaar', None, 'Receiver not found.', 0)
#
#
@ -52,15 +52,15 @@ class ReceiverListener(_listener.EventsListener):
_log.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle)
self.receiver.enable_notifications()
self.receiver.notify_devices()
self._status_changed(self.receiver, _status.ALERT.NOTIFICATION)
# self._status_changed(self.receiver, _status.ALERT.NOTIFICATION)
def has_stopped(self):
_log.info("%s: notifications listener has stopped", self.receiver)
if self.receiver:
self.receiver.enable_notifications(False)
self.receiver.close()
self._status_changed(self.receiver, _status.ALERT.NOTIFICATION)
self.receiver = None
self.status_changed_callback(DUMMY_RECEIVER, _status.ALERT.NOTIFICATION)
def tick(self, timestamp):
if _log.isEnabledFor(_DEBUG):
@ -76,7 +76,7 @@ class ReceiverListener(_listener.EventsListener):
self._last_tick = timestamp
# read these in case they haven't been read already
self.receiver.serial, self.receiver.firmware
# self.receiver.serial, self.receiver.firmware
if self.receiver.status.lock_open:
# don't mess with stuff while pairing
return
@ -86,27 +86,36 @@ class ReceiverListener(_listener.EventsListener):
dev.status.poll(timestamp)
def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None):
assert device is not None
if _log.isEnabledFor(_DEBUG):
_log.debug("status_changed %s: %s %s (%X) %s", device,
None if device is None else 'active' if device.status else 'inactive',
None if device is None else device.status,
alert, reason or '')
if self.status_changed_callback:
r = self.receiver or DUMMY
if device is None or device.kind is None:
# the status of the receiver changed
self.status_changed_callback(r, None, alert, reason)
else:
if device.status is None:
# device was unpaired, and since the object is weakref'ed
# it won't be valid for much longer
device = _ghost(device)
_log.debug("%s: status_changed %s: %s, %s (%X) %s", self.receiver, device,
'active' if device.status else 'inactive',
device.status, alert, reason or '')
self.status_changed_callback(r, device, alert, reason)
if device.kind is None:
# print ("self.receiver: ", self.receiver, id(self.receiver))
# print ("device: ", device, id(device))
assert device == self.receiver
# the status of the receiver changed
self.status_changed_callback(device, alert, reason)
else:
if device.status is None:
# the device may be paired later, possibly to another receiver?
# so maybe we shouldn't forget about it
# configuration.forget(device)
if device.status is None:
# the receiver changed status as well
self.status_changed_callback(r)
# device was unpaired, and since the object is weakref'ed
# it won't be valid for much longer
device = _ghost(device)
# elif device.status:
# configuration.sync(device)
self.status_changed_callback(device, alert, reason)
if device.status is None:
# the receiver changed status as well
self.status_changed_callback(self.receiver)
def _notifications_handler(self, n):
assert self.receiver

View File

@ -44,4 +44,5 @@ def error_dialog(window, title, text):
m.destroy()
from . import notify, status_icon, main_window
from . import status_icon
from . import notify, main_window

View File

@ -66,10 +66,12 @@ def _show_about_window(action):
))
except TypeError:
# gtk3 < 3.6 has incorrect gi bindings
pass
import logging
logging.exception("failed to fully create the about dialog")
except:
# is the Gtk3 version too old?
pass
import logging
logging.exception("failed to fully create the about dialog")
about.set_website('http://pwr.github.io/Solaar/')
about.set_website_label('Solaar')
@ -78,8 +80,6 @@ def _show_about_window(action):
about.destroy()
about = make('help-about', 'About ' + _NAME, _show_about_window)
quit = make('application-exit', 'Quit', Gtk.main_quit)
#
#
#

View File

@ -41,7 +41,7 @@ def lux(level):
_ICON_SETS = {}
def device_icon_set(name, kind=None):
def device_icon_set(name='_', kind=None):
icon_set = _ICON_SETS.get(name)
if icon_set is None:
icon_set = Gtk.IconSet.new()
@ -56,10 +56,7 @@ def device_icon_set(name, kind=None):
elif str(kind) == 'trackball':
names += ('input-mouse',)
names += ('input-' + str(kind),)
theme = Gtk.IconTheme.get_default()
if theme.has_icon(name):
names += (name,)
names += (name,)
source = Gtk.IconSource.new()
for n in names:

View File

@ -20,17 +20,17 @@ _STATUS_ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR
_TOOLBAR_ICON_SIZE = Gtk.IconSize.MENU
_PLACEHOLDER = '~'
_FALLBACK_ICON = 'preferences-desktop-peripherals'
_MAX_DEVICES = 7
#
#
#
def _make_receiver_box(name):
def _make_receiver_box():
frame = Gtk.Frame()
frame._device = None
frame.set_name(name)
icon_set = _icons.device_icon_set(name)
icon_set = _icons.device_icon_set()
icon = Gtk.Image.new_from_icon_set(icon_set, _RECEIVER_ICON_SIZE)
icon.set_padding(2, 2)
frame._icon = icon
@ -39,7 +39,7 @@ def _make_receiver_box(name):
label.set_alignment(0, 0.5)
frame._label = label
pairing_icon = Gtk.Image.new_from_icon_name('network-wireless', _RECEIVER_ICON_SIZE)
pairing_icon = Gtk.Image.new_from_icon_name('network-wireless', _TOOLBAR_ICON_SIZE)
pairing_icon.set_tooltip_text('The pairing lock is open.')
pairing_icon._tick = 0
frame._pairing_icon = pairing_icon
@ -242,18 +242,48 @@ def _make_device_box(index):
return frame
def create(title, name, max_devices, systray=False):
def hide(w, trigger):
position = w.get_position()
w.hide()
w.move(*position)
return True
def toggle(trigger, w):
if w.get_visible():
return hide(w, trigger)
if isinstance(trigger, Gtk.StatusIcon):
x, y = w.get_position()
if x == 0 and y == 0:
# if the window hasn't been shown yet, position it next to the status icon
x, y, _ = Gtk.StatusIcon.position_menu(Gtk.Menu(), trigger)
w.move(x, y)
w.present()
return True
def set_icon_name(window, icon_name):
icon_file = _icons.icon_file(icon_name)
if icon_file:
window.set_icon_from_file(icon_file)
else:
window.set_icon_name(icon_name)
def create(title):
window = Gtk.Window()
window.set_title(title)
window.set_icon_name(_icons.APP_ICON[0])
set_icon_name(window, _icons.APP_ICON[0])
window.set_role('status-window')
window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
vbox = Gtk.VBox(homogeneous=False, spacing=12)
vbox.set_border_width(4)
rbox = _make_receiver_box(name)
rbox = _make_receiver_box()
vbox.add(rbox)
for i in range(1, 1 + max_devices):
for i in range(1, _MAX_DEVICES):
dbox = _make_device_box(i)
vbox.add(dbox)
vbox.set_visible(True)
@ -264,53 +294,13 @@ def create(title, name, max_devices, systray=False):
geometry.min_width = 320
geometry.min_height = 32
window.set_geometry_hints(vbox, geometry, Gdk.WindowHints.MIN_SIZE)
window.set_resizable(False)
def _toggle_visible(w, trigger):
if w.get_visible():
# hiding moves the window to 0,0
position = w.get_position()
w.hide()
w.move(*position)
else:
if isinstance(trigger, Gtk.StatusIcon):
x, y = w.get_position()
if x == 0 and y == 0:
# if the window hasn't been shown yet, position it next to the status icon
x, y, _ = Gtk.StatusIcon.position_menu(Gtk.Menu(), trigger)
w.move(x, y)
w.present()
return True
def _set_has_systray(w, systray):
# print ("set has systray", systray, w._has_systray)
if systray != w._has_systray:
w._has_systray = systray
if systray:
if w._delete_event_connection is None or not w.get_skip_taskbar_hint():
w.set_skip_taskbar_hint(True)
w.set_skip_pager_hint(True)
if w._delete_event_connection:
w.disconnect(w._delete_event_connection)
w._delete_event_connection = w.connect('delete-event', _toggle_visible)
else:
if w._delete_event_connection is None or w.get_skip_taskbar_hint():
w.set_skip_taskbar_hint(False)
w.set_skip_pager_hint(False)
if w._delete_event_connection:
w.disconnect(w._delete_event_connection)
w._delete_event_connection = w.connect('delete-event', Gtk.main_quit)
w.present()
from types import MethodType
window.toggle_visible = MethodType(_toggle_visible, window)
window.set_has_systray = MethodType(_set_has_systray, window)
del MethodType
window.set_skip_taskbar_hint(True)
window.set_skip_pager_hint(True)
window.set_keep_above(True)
window._delete_event_connection = None
window._has_systray = None
window.set_has_systray(systray)
# window.set_decorations(Gdk.DECOR_BORDER | Gdk.DECOR_TITLE)
window.connect('delete-event', hide)
return window
@ -322,6 +312,8 @@ def _update_receiver_box(frame, receiver):
frame._label.set_text(str(receiver.status))
if receiver:
frame._device = receiver
icon_set = _icons.device_icon_set(receiver.name)
frame._icon.set_from_icon_set(icon_set, _RECEIVER_ICON_SIZE)
frame._icon.set_sensitive(True)
if receiver.status.lock_open:
if frame._pairing_icon._tick == 0:
@ -342,6 +334,7 @@ def _update_receiver_box(frame, receiver):
frame._toolbar.set_sensitive(True)
else:
frame._device = None
frame._icon.set_from_icon_name('dialog-error', _RECEIVER_ICON_SIZE)
frame._icon.set_sensitive(False)
frame._pairing_icon.set_visible(False)
frame._toolbar.set_sensitive(False)
@ -420,19 +413,21 @@ def _update_device_box(frame, dev):
_config_panel.update(frame)
def update(window, receiver, device=None):
assert receiver is not None
# print ("update", receiver, receiver.status, len(receiver), device)
window.set_icon_name(_icons.APP_ICON[1 if receiver else -1])
def update(window, device):
assert device is not None
# print ("main_window.update", device)
vbox = window.get_child()
frames = list(vbox.get_children())
assert len(frames) == 1 + receiver.max_devices, frames
if device is None:
_update_receiver_box(frames[0], receiver)
if not receiver:
if device.kind is None:
# update on the receiver
_update_receiver_box(frames[0], device)
if device:
set_icon_name(window, _icons.APP_ICON[1])
else:
for frame in frames[1:]:
_update_device_box(frame, None)
set_icon_name(window, _icons.APP_ICON[-1])
else:
_update_device_box(frames[device.number], None if device.status is None else device)

View File

@ -4,9 +4,11 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from gi.repository import Gtk, GLib, GdkPixbuf
from gi.repository import Gtk, GdkPixbuf
from . import action as _action, icons as _icons
from . import (action as _action,
icons as _icons,
main_window as _main_window)
from logitech.unifying_receiver import status as _status
#
@ -15,8 +17,9 @@ from logitech.unifying_receiver import status as _status
_NO_DEVICES = [None] * 6
def create(window, menu_actions=None):
def create(window):
name = window.get_title()
icon = Gtk.StatusIcon()
icon.set_title(name)
icon.set_name(name)
@ -24,16 +27,21 @@ def create(window, menu_actions=None):
icon._devices = list(_NO_DEVICES)
icon.set_tooltip_text(name)
icon.connect('activate', window.toggle_visible)
icon.connect('activate', _main_window.toggle, window)
menu = Gtk.Menu()
for a in menu_actions or ():
if a:
menu.append(a.create_menu_item())
menu.append(_action.quit.create_menu_item())
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.connect('popup_menu',
lambda icon, button, time, menu:
menu.popup(None, None, icon.position_menu, icon, button, time),
@ -41,23 +49,6 @@ def create(window, menu_actions=None):
return icon
def check_systray(icon, window):
# use size-changed to detect if the systray is available or not
def _size_changed(i, size, w):
import logging
logging.info("size-chagend %s %s", size, w)
def _check_systray(i2, w2):
logging.info("check_systray %s %s", i2.is_embedded(), i2.get_visible())
w2.set_has_systray(i2.is_embedded() and i2.get_visible())
# first guess
GLib.timeout_add(250, _check_systray, i, w)
# just to make sure...
# GLib.timeout_add(1000, _check_systray, i, w)
_size_changed(icon, None, window)
icon.connect('size-changed', _size_changed, window)
_PIXMAPS = {}
def _icon_with_battery(level, active):
battery_icon = _icons.battery(level)
@ -82,12 +73,18 @@ def _icon_with_battery(level, active):
return _PIXMAPS[name]
def update(icon, receiver, device=None):
# print ("icon update", receiver, receiver.status, len(receiver), device)
if device is not None:
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
if not receiver:
icon._devices[:] = _NO_DEVICES
receiver = device.receiver
if not icon.is_embedded():
return