use a single separate thread for all possibly long-running stuff in GUI

This commit is contained in:
Daniel Pavel 2013-07-04 13:23:25 +02:00
parent cd437c3809
commit edce56cd20
4 changed files with 100 additions and 46 deletions

View File

@ -127,7 +127,7 @@ def _print_device(dev, verbose=False):
print (" Polling rate :", dev.polling_rate, "ms")
print (" Serial number:", dev.serial)
for fw in dev.firmware:
print (" %-11s:" % fw.kind, (fw.name + ' ' + fw.version).strip())
print (" %11s:" % fw.kind, (fw.name + ' ' + fw.version).strip())
if dev.power_switch_location:
print (" The power switch is located on the %s." % dev.power_switch_location)

View File

@ -42,9 +42,45 @@ def error_dialog(reason, object):
GLib.idle_add(_error_dialog, reason, object)
#
#
# A separate thread is used to read/write from the device
# so as not to block the main (GUI) thread.
#
try:
from Queue import Queue
except ImportError:
from queue import Queue
_task_queue = Queue(16)
del Queue
from threading import Thread, current_thread as _current_thread
def _process_async_queue():
t = _current_thread()
t.alive = True
while t.alive:
function, args, kwargs = _task_queue.get()
if function:
function(*args, **kwargs)
if _log.isEnabledFor(_DEBUG):
_log.debug("stopped %s", t.name)
_queue_processor = Thread(name='AsyncUI', target=_process_async_queue)
_queue_processor.daemon = True
_queue_processor.alive = False
_queue_processor.start()
del Thread
def async(function, *args, **kwargs):
task = (function, args, kwargs)
_task_queue.put(task)
#
#
#
from . import notify, tray, window
@ -57,6 +93,10 @@ def run_loop():
Gtk.main()
def destroy():
# stop the async UI processor
_queue_processor.alive = False
async(None)
tray.destroy()
window.destroy()
notify.uninit()

View File

@ -6,45 +6,34 @@ from __future__ import absolute_import, division, print_function, unicode_litera
from gi.repository import Gtk, GLib
from solaar.ui import async as _ui_async
from logitech.unifying_receiver.settings import KIND as _SETTING_KIND
#
# a separate thread is used to read/write from the device
# so as not to block the main (GUI) thread
#
#
try:
from Queue import Queue as _Queue
except ImportError:
from queue import Queue as _Queue
_apply_queue = _Queue(8)
def _read_async(setting, force_read, sbox, device_is_online):
def _do_read(s, force, sb, online):
v = s.read(not force)
GLib.idle_add(_update_setting_item, sb, v, online, priority=99)
def _process_apply_queue():
def _write_start(sbox):
_, failed, spinner, control = sbox.get_children()
control.set_sensitive(False)
failed.set_visible(False)
spinner.set_visible(True)
spinner.start()
_ui_async(_do_read, setting, force_read, sbox, device_is_online)
while True:
task = _apply_queue.get()
assert isinstance(task, tuple)
device_is_online = True
# print ("task", *task)
if task[0] == 'write':
_, setting, value, sbox = task
GLib.idle_add(_write_start, sbox, priority=0)
value = setting.write(value)
elif task[0] == 'read':
_, setting, force_read, sbox, device_is_online = task
value = setting.read(not force_read)
GLib.idle_add(_update_setting_item, sbox, value, device_is_online, priority=99)
from threading import Thread as _Thread
_queue_processor = _Thread(name='SettingsProcessor', target=_process_apply_queue)
_queue_processor.daemon = True
_queue_processor.start()
def _write_async(setting, value, sbox):
_, failed, spinner, control = sbox.get_children()
control.set_sensitive(False)
failed.set_visible(False)
spinner.set_visible(True)
spinner.start()
def _do_write(s, v, sb):
v = setting.write(v)
GLib.idle_add(_update_setting_item, sb, v, True, priority=99)
_ui_async(_do_write, setting, value, sbox)
#
#
@ -53,7 +42,7 @@ _queue_processor.start()
def _create_toggle_control(setting):
def _switch_notify(switch, _, s):
if switch.get_sensitive():
_apply_queue.put(('write', s, switch.get_active() == True, switch.get_parent()))
_write_async(s, switch.get_active() == True, switch.get_parent())
c = Gtk.Switch()
c.connect('notify::active', _switch_notify, setting)
@ -62,7 +51,7 @@ def _create_toggle_control(setting):
def _create_choice_control(setting):
def _combo_notify(cbbox, s):
if cbbox.get_sensitive():
_apply_queue.put(('write', s, cbbox.get_active_id(), cbbox.get_parent()))
_write_async(s, cbbox.get_active_id(), cbbox.get_parent())
c = Gtk.ComboBoxText()
for entry in setting.choices:
@ -181,7 +170,7 @@ def update(device, is_online=None):
sbox = _items[k] = _create_sbox(s)
_box.pack_start(sbox, False, False, 0)
_apply_queue.put(('read', s, False, sbox, is_online))
_read_async(s, False, sbox, is_online)
_box.set_visible(True)

View File

@ -8,11 +8,12 @@ from logging import getLogger, DEBUG as _DEBUG
_log = getLogger(__name__)
del getLogger
from gi.repository import Gtk, Gdk
from gi.repository import Gtk, Gdk, GLib
from gi.repository.GObject import TYPE_PYOBJECT
from solaar import NAME
# from solaar import __version__ as VERSION
from solaar.ui import async as _ui_async
from logitech.unifying_receiver import hidpp10 as _hidpp10
from logitech.unifying_receiver.common import NamedInts as _NamedInts, NamedInt as _NamedInt
from logitech.unifying_receiver.status import KEYS as _K
@ -20,7 +21,6 @@ from . import config_panel as _config_panel
from . import action as _action, icons as _icons
from .about import show_window as _show_about_window
#
# constants
#
@ -480,7 +480,10 @@ def _update_details(button):
if visible:
# _details._text.set_markup('<small>reading...</small>')
def _details_items(device):
def _details_items(device, read_all=False):
# If read_all is False, only return stuff that is ~100% already
# cached, and involves no HID++ calls.
if device.kind is None:
yield ('Path', device.path)
# 046d is the Logitech vendor id
@ -491,13 +494,15 @@ def _update_details(button):
yield ('Wireless PID', device.wpid)
hid_version = device.protocol
yield ('Protocol', 'HID++ %1.1f' % hid_version if hid_version else 'unknown')
if device.polling_rate:
if read_all and device.polling_rate:
yield ('Polling rate', '%d ms (%dHz)' % (device.polling_rate, 1000 // device.polling_rate))
yield ('Serial', device.serial)
if read_all or (device.kind is not None and device.online):
yield ('Serial', device.serial)
for fw in list(device.firmware):
yield (fw.kind, (fw.name + ' ' + fw.version).strip())
if read_all:
for fw in list(device.firmware):
yield (' ' + str(fw.kind), (fw.name + ' ' + fw.version).strip())
flag_bits = device.status.get(_K.NOTIFICATION_FLAGS)
if flag_bits is None and device.kind is not None:
@ -506,11 +511,31 @@ def _update_details(button):
flag_names = ('(none)',) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits)
yield ('Notifications', ('\n%15s' % ' ').join(flag_names))
def _set_details(text):
_details._text.set_markup(text)
def _make_text(items):
text = '\n'.join('%-13s: %s' % i for i in items)
return '<small><tt>' + text + '</tt></small>'
def _read_slow(device):
items = _details_items(selected_device, True)
text = _make_text(items)
if device == _details._current_device:
GLib.idle_add(_set_details, text)
selected_device = _find_selected_device()
assert selected_device
items = _details_items(selected_device)
text = '\n'.join('%-13s: %s' % i for i in items if i)
_details._text.set_markup('<small><tt>' + text + '</tt></small>')
_details._current_device = selected_device
read_all = not (selected_device.kind is None or selected_device.online)
items = _details_items(selected_device, read_all)
_set_details(_make_text(items))
if read_all:
_details._current_device = None
else:
_ui_async(_read_slow, selected_device)
_details.set_visible(visible)