create and destroy windows on demand
based on receiver added/removed events generated by udev
This commit is contained in:
parent
ad577d22d0
commit
4eeca12d6a
|
@ -44,24 +44,24 @@ 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:
|
||||
|
@ -75,47 +75,36 @@ def _run(args):
|
|||
# '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()
|
||||
|
||||
# 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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,21 +299,41 @@ 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):
|
||||
|
@ -332,15 +350,6 @@ def _update_receiver_box(frame, receiver):
|
|||
frame._pairing_icon.set_visible(False)
|
||||
frame._pairing_icon.set_sensitive(True)
|
||||
frame._pairing_icon._tick = 0
|
||||
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)
|
||||
frame._toolbar.get_children()[0].set_active(False)
|
||||
frame._info_label.set_text('')
|
||||
|
||||
|
||||
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()
|
||||
receiver = device if device.kind is None else device.receiver
|
||||
w = _windows.get(receiver.path)
|
||||
if receiver and not w:
|
||||
w = _create(receiver)
|
||||
|
||||
if w:
|
||||
if receiver:
|
||||
if popup:
|
||||
w.present()
|
||||
vbox = w.get_child()
|
||||
frames = list(vbox.get_children())
|
||||
|
||||
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])
|
||||
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:
|
||||
_destroy(receiver)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
Loading…
Reference in New Issue