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

View File

@ -25,15 +25,13 @@ del namedtuple
def _ghost(device):
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
# (through notifications)
_POLL_TICK = 120 # seconds
_POLL_TICK = 100 # seconds
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)
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.receiver = None
self.status_changed_callback(DUMMY_RECEIVER, _status.ALERT.NOTIFICATION)
r, self.receiver = self.receiver, None
_log.info("%s: notifications listener has stopped", r)
if r:
r.enable_notifications(False)
r.close()
r.status = 'The device was unplugged.'
self.status_changed_callback(r) #, _status.ALERT.NOTIFICATION)
def tick(self, timestamp):
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._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.set_padding(2, 2)
frame._icon = icon
@ -243,18 +243,15 @@ def _make_device_box(index):
return frame
def hide(w, trigger):
def _hide(w):
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):
def _show(w, trigger=None):
if trigger and 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
@ -264,25 +261,26 @@ def toggle(trigger, w):
return True
def set_icon_name(window, icon_name):
icon_file = _icons.icon_file(icon_name)
# all created windows will be placed here, keyed by the receiver path
_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:
window.set_icon_from_file(icon_file)
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_type_hint(Gdk.WindowTypeHint.UTILITY)
vbox = Gtk.VBox(homogeneous=False, spacing=12)
vbox.set_border_width(4)
rbox = _make_receiver_box()
rbox = _make_receiver_box(receiver)
vbox.add(rbox)
for i in range(1, _MAX_DEVICES):
dbox = _make_device_box(i)
@ -301,46 +299,57 @@ def create(title):
window.set_skip_pager_hint(True)
window.set_keep_above(True)
# 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
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):
assert frame
assert 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:
def _pairing_tick(i, s):
if s and s.lock_open:
i.set_sensitive(bool(i._tick % 2))
i._tick += 1
return True
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)
if receiver.status.lock_open:
if frame._pairing_icon._tick == 0:
def _pairing_tick(i, s):
if s and s.lock_open:
i.set_sensitive(bool(i._tick % 2))
i._tick += 1
return True
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._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)
frame._toolbar.get_children()[0].set_active(False)
frame._info_label.set_text('')
frame._pairing_icon.set_sensitive(True)
frame._pairing_icon._tick = 0
def _update_device_box(frame, dev):
@ -414,21 +423,25 @@ def _update_device_box(frame, dev):
_config_panel.update(frame)
def update(window, device):
def update(device, popup=False):
assert device is not None
# print ("main_window.update", device)
print ("main_window.update", device)
vbox = window.get_child()
frames = list(vbox.get_children())
receiver = device if device.kind is None else device.receiver
w = _windows.get(receiver.path)
if receiver and not w:
w = _create(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])
if w:
if receiver:
if popup:
w.present()
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:
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)
_destroy(receiver)

View File

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