create and destroy windows on demand

based on receiver added/removed events generated by udev
This commit is contained in:
Daniel Pavel 2013-05-04 12:20:51 +02:00
parent ad577d22d0
commit 4eeca12d6a
4 changed files with 121 additions and 120 deletions

View File

@ -44,78 +44,67 @@ def _run(args):
ui.notify.init() ui.notify.init()
window = ui.main_window.create(NAME) icon = ui.status_icon.create(ui.main_window.toggle_all)
assert window
icon = ui.status_icon.create(window)
assert icon assert icon
listeners = {} listeners = {}
from logitech.unifying_receiver import base as _base
from solaar.listener import ReceiverListener from solaar.listener import ReceiverListener
def handle_receivers_events(action, device): def handle_receivers_events(action, device):
assert action is not None assert action is not None
assert device is not None assert device is not None
# whatever the action, stop any previous receivers at this path
l = listeners.pop(device.path, None)
if l is not None:
assert isinstance(l, ReceiverListener)
l.stop()
if action == 'add': if action == 'add':
# a new receiver device was detected # a new receiver device was detected
if not listeners: try:
# handle only one receiver for now, the rest are ignored l = ReceiverListener.open(device.path, status_changed)
try: if l is not None:
l = ReceiverListener.open(device.path, status_changed) listeners[device.path] = l
if l is not None: except OSError:
listeners[device.path] = l # permission error, blacklist this path for now
except OSError: listeners.pop(device.path, None)
# permission error, blacklist this path for now import logging
listeners.pop(device.path, None) logging.exception("failed to open %s", device.path)
import logging # ui.error_dialog(window, 'Permissions error',
logging.exception("failed to open %s", device.path) # 'Found a possible Unifying Receiver device,\n'
# ui.error_dialog(window, 'Permissions error', # 'but did not have permission to open it.')
# 'Found a possible Unifying Receiver device,\n'
# 'but did not have permission to open it.')
elif action == 'remove': # elif action == 'remove':
# we'll be receiving remove events for any hidraw devices, # # we'll be receiving remove events for any hidraw devices,
# not just Logitech receivers, so it's okay if the device is not # # not just Logitech receivers, so it's okay if the device is not
# already in our listeners map # # already in our listeners map
l = listeners.pop(device.path, None) # l = listeners.pop(device.path, None)
if l is not None: # if l is not None:
assert isinstance(l, ReceiverListener) # l.stop()
l.stop()
# print ("****", action, device, listeners) print ("****", action, device, listeners)
# callback delivering status notifications from the receiver/devices to the UI # callback delivering status notifications from the receiver/devices to the UI
from gi.repository import GLib from gi.repository import GLib
from logitech.unifying_receiver.status import ALERT from logitech.unifying_receiver.status import ALERT
def status_changed(device, alert=ALERT.NONE, reason=None): def status_changed(device, alert=ALERT.NONE, reason=None):
assert device is not None assert device is not None
# print ("status changed", device, reason) print ("status changed", device, reason)
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) GLib.idle_add(ui.status_icon.update, icon, device)
GLib.idle_add(ui.main_window.update, device, alert & ALERT.SHOW_WINDOW)
if ui.notify.available: if alert & ALERT.NOTIFICATION:
if alert & ALERT.NOTIFICATION: GLib.idle_add(ui.notify.show, device, reason)
GLib.idle_add(ui.notify.show, device, reason)
elif device.kind is None and not device:
# notify when a receiver was removed
GLib.idle_add(ui.notify.show, device, reason)
# if device.kind is None and not device:
# # a receiver was removed
# listeners.clear()
# ugly... # ugly...
def _startup_check_receiver(): def _startup_check_receiver():
from solaar.listener import DUMMY_RECEIVER
if not listeners: if not listeners:
status_changed(DUMMY_RECEIVER, ALERT.NOTIFICATION) ui.notify.alert('No receiver found.')
GLib.timeout_add(1000, _startup_check_receiver) GLib.timeout_add(1000, _startup_check_receiver)
from logitech.unifying_receiver import base as _base
GLib.timeout_add(10, _base.notify_on_receivers, handle_receivers_events) GLib.timeout_add(10, _base.notify_on_receivers, handle_receivers_events)
from gi.repository import Gtk from gi.repository import Gtk
Gtk.main() Gtk.main()

View File

@ -25,15 +25,13 @@ del namedtuple
def _ghost(device): def _ghost(device):
return _GHOST_DEVICE(number=device.number, name=device.name, kind=device.kind, status=None, max_devices=None) return _GHOST_DEVICE(number=device.number, name=device.name, kind=device.kind, status=None, max_devices=None)
DUMMY_RECEIVER = _GHOST_DEVICE(0xFF, 'Solaar', None, 'Receiver not found.', 0)
# #
# #
# #
# how often to poll devices that haven't updated their statuses on their own # how often to poll devices that haven't updated their statuses on their own
# (through notifications) # (through notifications)
_POLL_TICK = 120 # seconds _POLL_TICK = 100 # seconds
class ReceiverListener(_listener.EventsListener): class ReceiverListener(_listener.EventsListener):
@ -52,15 +50,16 @@ class ReceiverListener(_listener.EventsListener):
_log.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle) _log.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle)
self.receiver.enable_notifications() self.receiver.enable_notifications()
self.receiver.notify_devices() self.receiver.notify_devices()
# self._status_changed(self.receiver, _status.ALERT.NOTIFICATION) self._status_changed(self.receiver) #, _status.ALERT.NOTIFICATION)
def has_stopped(self): def has_stopped(self):
_log.info("%s: notifications listener has stopped", self.receiver) r, self.receiver = self.receiver, None
if self.receiver: _log.info("%s: notifications listener has stopped", r)
self.receiver.enable_notifications(False) if r:
self.receiver.close() r.enable_notifications(False)
self.receiver = None r.close()
self.status_changed_callback(DUMMY_RECEIVER, _status.ALERT.NOTIFICATION) r.status = 'The device was unplugged.'
self.status_changed_callback(r) #, _status.ALERT.NOTIFICATION)
def tick(self, timestamp): def tick(self, timestamp):
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):

View File

@ -27,11 +27,11 @@ _MAX_DEVICES = 7
# #
# #
def _make_receiver_box(): def _make_receiver_box(receiver):
frame = Gtk.Frame() frame = Gtk.Frame()
frame._device = None frame._device = receiver
icon_set = _icons.device_icon_set() icon_set = _icons.device_icon_set(receiver.name)
icon = Gtk.Image.new_from_icon_set(icon_set, _RECEIVER_ICON_SIZE) icon = Gtk.Image.new_from_icon_set(icon_set, _RECEIVER_ICON_SIZE)
icon.set_padding(2, 2) icon.set_padding(2, 2)
frame._icon = icon frame._icon = icon
@ -243,18 +243,15 @@ def _make_device_box(index):
return frame return frame
def hide(w, trigger): def _hide(w):
position = w.get_position() position = w.get_position()
w.hide() w.hide()
w.move(*position) w.move(*position)
return True return True
def toggle(trigger, w): def _show(w, trigger=None):
if w.get_visible(): if trigger and isinstance(trigger, Gtk.StatusIcon):
return hide(w, trigger)
if isinstance(trigger, Gtk.StatusIcon):
x, y = w.get_position() x, y = w.get_position()
if x == 0 and y == 0: if x == 0 and y == 0:
# if the window hasn't been shown yet, position it next to the status icon # if the window hasn't been shown yet, position it next to the status icon
@ -264,25 +261,26 @@ def toggle(trigger, w):
return True return True
def set_icon_name(window, icon_name): # all created windows will be placed here, keyed by the receiver path
icon_file = _icons.icon_file(icon_name) _windows = {}
def _create(receiver):
window = Gtk.Window()
window.set_title(NAME + ': ' + receiver.name)
icon_file = _icons.icon_file(_icons.APP_ICON[1])
if icon_file: if icon_file:
window.set_icon_from_file(icon_file) window.set_icon_from_file(icon_file)
else: else:
window.set_icon_name(icon_name) window.set_icon_name(_icons.APP_ICON[1])
def create(title):
window = Gtk.Window()
window.set_title(title)
set_icon_name(window, _icons.APP_ICON[0])
window.set_role('status-window') window.set_role('status-window')
window.set_type_hint(Gdk.WindowTypeHint.UTILITY) window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
vbox = Gtk.VBox(homogeneous=False, spacing=12) vbox = Gtk.VBox(homogeneous=False, spacing=12)
vbox.set_border_width(4) vbox.set_border_width(4)
rbox = _make_receiver_box() rbox = _make_receiver_box(receiver)
vbox.add(rbox) vbox.add(rbox)
for i in range(1, _MAX_DEVICES): for i in range(1, _MAX_DEVICES):
dbox = _make_device_box(i) dbox = _make_device_box(i)
@ -301,46 +299,57 @@ def create(title):
window.set_skip_pager_hint(True) window.set_skip_pager_hint(True)
window.set_keep_above(True) window.set_keep_above(True)
# window.set_decorations(Gdk.DECOR_BORDER | Gdk.DECOR_TITLE) # window.set_decorations(Gdk.DECOR_BORDER | Gdk.DECOR_TITLE)
window.connect('delete-event', hide) window.connect('delete-event', _hide)
_windows[receiver.path] = window
return window return window
def _destroy(receiver):
w = _windows.pop(receiver.path, None)
if w:
w.destroy()
def toggle_all(trigger):
if not _windows:
return
visible = [w.get_visible() for w in _windows.values()]
if all(visible):
map(_hide, _windows.values())
else:
for w in _windows.values():
if w.get_visible():
_hide(w)
else:
_show(w, trigger)
# #
# #
# #
def _update_receiver_box(frame, receiver): def _update_receiver_box(frame, receiver):
assert frame
assert receiver
frame._label.set_text(str(receiver.status)) frame._label.set_text(str(receiver.status))
if receiver: if receiver.status.lock_open:
frame._device = receiver if frame._pairing_icon._tick == 0:
icon_set = _icons.device_icon_set(receiver.name) def _pairing_tick(i, s):
frame._icon.set_from_icon_set(icon_set, _RECEIVER_ICON_SIZE) if s and s.lock_open:
frame._icon.set_sensitive(True) i.set_sensitive(bool(i._tick % 2))
if receiver.status.lock_open: i._tick += 1
if frame._pairing_icon._tick == 0: return True
def _pairing_tick(i, s): i.set_visible(False)
if s and s.lock_open: i.set_sensitive(True)
i.set_sensitive(bool(i._tick % 2)) i._tick = 0
i._tick += 1 frame._pairing_icon.set_visible(True)
return True GLib.timeout_add(1000, _pairing_tick, frame._pairing_icon, receiver.status)
i.set_visible(False)
i.set_sensitive(True)
i._tick = 0
frame._pairing_icon.set_visible(True)
GLib.timeout_add(1000, _pairing_tick, frame._pairing_icon, receiver.status)
else:
frame._pairing_icon.set_visible(False)
frame._pairing_icon.set_sensitive(True)
frame._pairing_icon._tick = 0
frame._toolbar.set_sensitive(True)
else: 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._pairing_icon.set_visible(False)
frame._toolbar.set_sensitive(False) frame._pairing_icon.set_sensitive(True)
frame._toolbar.get_children()[0].set_active(False) frame._pairing_icon._tick = 0
frame._info_label.set_text('')
def _update_device_box(frame, dev): def _update_device_box(frame, dev):
@ -414,21 +423,25 @@ def _update_device_box(frame, dev):
_config_panel.update(frame) _config_panel.update(frame)
def update(window, device): def update(device, popup=False):
assert device is not None assert device is not None
# print ("main_window.update", device) print ("main_window.update", device)
vbox = window.get_child() receiver = device if device.kind is None else device.receiver
frames = list(vbox.get_children()) w = _windows.get(receiver.path)
if receiver and not w:
w = _create(receiver)
if device.kind is None: if w:
# update on the receiver if receiver:
_update_receiver_box(frames[0], device) if popup:
if device: w.present()
set_icon_name(window, _icons.APP_ICON[1]) vbox = w.get_child()
frames = list(vbox.get_children())
if device is receiver:
_update_receiver_box(frames[0], receiver)
else:
_update_device_box(frames[device.number], None if device.status is None else device)
else: else:
for frame in frames[1:]: _destroy(receiver)
_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

@ -6,10 +6,8 @@ from __future__ import absolute_import, division, print_function, unicode_litera
from gi.repository import Gtk, GdkPixbuf from gi.repository import Gtk, GdkPixbuf
from . import (action as _action,
icons as _icons,
main_window as _main_window)
from solaar import NAME from solaar import NAME
from . import action as _action, icons as _icons
from logitech.unifying_receiver import status as _status from logitech.unifying_receiver import status as _status
# #
@ -18,7 +16,9 @@ from logitech.unifying_receiver import status as _status
_NO_DEVICES = [None] * 6 _NO_DEVICES = [None] * 6
def create(window):
def create(activate_callback):
assert activate_callback
icon = Gtk.StatusIcon() icon = Gtk.StatusIcon()
icon.set_title(NAME) icon.set_title(NAME)
@ -26,8 +26,8 @@ def create(window):
icon.set_from_icon_name(_icons.APP_ICON[0]) icon.set_from_icon_name(_icons.APP_ICON[0])
icon._devices = list(_NO_DEVICES) icon._devices = list(_NO_DEVICES)
icon.connect('activate', _main_window.toggle, window)
icon.set_tooltip_text(NAME) icon.set_tooltip_text(NAME)
icon.connect('activate', activate_callback)
menu = Gtk.Menu() menu = Gtk.Menu()