use a single separate thread for all possibly long-running stuff in GUI
This commit is contained in:
parent
cd437c3809
commit
edce56cd20
|
|
@ -127,7 +127,7 @@ def _print_device(dev, verbose=False):
|
||||||
print (" Polling rate :", dev.polling_rate, "ms")
|
print (" Polling rate :", dev.polling_rate, "ms")
|
||||||
print (" Serial number:", dev.serial)
|
print (" Serial number:", dev.serial)
|
||||||
for fw in dev.firmware:
|
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:
|
if dev.power_switch_location:
|
||||||
print (" The power switch is located on the %s." % dev.power_switch_location)
|
print (" The power switch is located on the %s." % dev.power_switch_location)
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,45 @@ def error_dialog(reason, object):
|
||||||
GLib.idle_add(_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
|
from . import notify, tray, window
|
||||||
|
|
||||||
|
|
@ -57,6 +93,10 @@ def run_loop():
|
||||||
Gtk.main()
|
Gtk.main()
|
||||||
|
|
||||||
def destroy():
|
def destroy():
|
||||||
|
# stop the async UI processor
|
||||||
|
_queue_processor.alive = False
|
||||||
|
async(None)
|
||||||
|
|
||||||
tray.destroy()
|
tray.destroy()
|
||||||
window.destroy()
|
window.destroy()
|
||||||
notify.uninit()
|
notify.uninit()
|
||||||
|
|
|
||||||
|
|
@ -6,45 +6,34 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
||||||
|
|
||||||
from gi.repository import Gtk, GLib
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
|
|
||||||
|
from solaar.ui import async as _ui_async
|
||||||
from logitech.unifying_receiver.settings import KIND as _SETTING_KIND
|
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:
|
def _read_async(setting, force_read, sbox, device_is_online):
|
||||||
from Queue import Queue as _Queue
|
def _do_read(s, force, sb, online):
|
||||||
except ImportError:
|
v = s.read(not force)
|
||||||
from queue import Queue as _Queue
|
GLib.idle_add(_update_setting_item, sb, v, online, priority=99)
|
||||||
_apply_queue = _Queue(8)
|
|
||||||
|
|
||||||
def _process_apply_queue():
|
_ui_async(_do_read, setting, force_read, sbox, device_is_online)
|
||||||
def _write_start(sbox):
|
|
||||||
_, failed, spinner, control = sbox.get_children()
|
|
||||||
control.set_sensitive(False)
|
|
||||||
failed.set_visible(False)
|
|
||||||
spinner.set_visible(True)
|
|
||||||
spinner.start()
|
|
||||||
|
|
||||||
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
|
def _write_async(setting, value, sbox):
|
||||||
_queue_processor = _Thread(name='SettingsProcessor', target=_process_apply_queue)
|
_, failed, spinner, control = sbox.get_children()
|
||||||
_queue_processor.daemon = True
|
control.set_sensitive(False)
|
||||||
_queue_processor.start()
|
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 _create_toggle_control(setting):
|
||||||
def _switch_notify(switch, _, s):
|
def _switch_notify(switch, _, s):
|
||||||
if switch.get_sensitive():
|
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 = Gtk.Switch()
|
||||||
c.connect('notify::active', _switch_notify, setting)
|
c.connect('notify::active', _switch_notify, setting)
|
||||||
|
|
@ -62,7 +51,7 @@ def _create_toggle_control(setting):
|
||||||
def _create_choice_control(setting):
|
def _create_choice_control(setting):
|
||||||
def _combo_notify(cbbox, s):
|
def _combo_notify(cbbox, s):
|
||||||
if cbbox.get_sensitive():
|
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()
|
c = Gtk.ComboBoxText()
|
||||||
for entry in setting.choices:
|
for entry in setting.choices:
|
||||||
|
|
@ -181,7 +170,7 @@ def update(device, is_online=None):
|
||||||
sbox = _items[k] = _create_sbox(s)
|
sbox = _items[k] = _create_sbox(s)
|
||||||
_box.pack_start(sbox, False, False, 0)
|
_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)
|
_box.set_visible(True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,12 @@ from logging import getLogger, DEBUG as _DEBUG
|
||||||
_log = getLogger(__name__)
|
_log = getLogger(__name__)
|
||||||
del getLogger
|
del getLogger
|
||||||
|
|
||||||
from gi.repository import Gtk, Gdk
|
from gi.repository import Gtk, Gdk, GLib
|
||||||
from gi.repository.GObject import TYPE_PYOBJECT
|
from gi.repository.GObject import TYPE_PYOBJECT
|
||||||
|
|
||||||
from solaar import NAME
|
from solaar import NAME
|
||||||
# from solaar import __version__ as VERSION
|
# 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 import hidpp10 as _hidpp10
|
||||||
from logitech.unifying_receiver.common import NamedInts as _NamedInts, NamedInt as _NamedInt
|
from logitech.unifying_receiver.common import NamedInts as _NamedInts, NamedInt as _NamedInt
|
||||||
from logitech.unifying_receiver.status import KEYS as _K
|
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 . import action as _action, icons as _icons
|
||||||
from .about import show_window as _show_about_window
|
from .about import show_window as _show_about_window
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# constants
|
# constants
|
||||||
#
|
#
|
||||||
|
|
@ -480,7 +480,10 @@ def _update_details(button):
|
||||||
if visible:
|
if visible:
|
||||||
# _details._text.set_markup('<small>reading...</small>')
|
# _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:
|
if device.kind is None:
|
||||||
yield ('Path', device.path)
|
yield ('Path', device.path)
|
||||||
# 046d is the Logitech vendor id
|
# 046d is the Logitech vendor id
|
||||||
|
|
@ -491,13 +494,15 @@ def _update_details(button):
|
||||||
yield ('Wireless PID', device.wpid)
|
yield ('Wireless PID', device.wpid)
|
||||||
hid_version = device.protocol
|
hid_version = device.protocol
|
||||||
yield ('Protocol', 'HID++ %1.1f' % hid_version if hid_version else 'unknown')
|
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 ('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):
|
if read_all:
|
||||||
yield (fw.kind, (fw.name + ' ' + fw.version).strip())
|
for fw in list(device.firmware):
|
||||||
|
yield (' ' + str(fw.kind), (fw.name + ' ' + fw.version).strip())
|
||||||
|
|
||||||
flag_bits = device.status.get(_K.NOTIFICATION_FLAGS)
|
flag_bits = device.status.get(_K.NOTIFICATION_FLAGS)
|
||||||
if flag_bits is None and device.kind is not None:
|
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)
|
flag_names = ('(none)',) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits)
|
||||||
yield ('Notifications', ('\n%15s' % ' ').join(flag_names))
|
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()
|
selected_device = _find_selected_device()
|
||||||
assert selected_device
|
assert selected_device
|
||||||
items = _details_items(selected_device)
|
_details._current_device = selected_device
|
||||||
text = '\n'.join('%-13s: %s' % i for i in items if i)
|
|
||||||
_details._text.set_markup('<small><tt>' + text + '</tt></small>')
|
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)
|
_details.set_visible(visible)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue