Improved Global Settings with more readable widget handling

This commit is contained in:
Marta Marczykowska-Górecka 2020-07-08 22:31:50 +02:00
parent 31eb6f9df7
commit ab5e3dcfea
No known key found for this signature in database
GPG Key ID: 9A752C30B26FD04B
4 changed files with 188 additions and 122 deletions

View File

@ -62,16 +62,15 @@ def _run_qrexec_repo(service, arg=''):
return p.stdout.decode('utf-8') return p.stdout.decode('utf-8')
# pylint: disable=too-many-instance-attributes
class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
QtWidgets.QDialog): QtWidgets.QDialog):
def __init__(self, app, qvm_collection, parent=None): def __init__(self, app, qubes_app, parent=None):
super(GlobalSettingsWindow, self).__init__(parent) super(GlobalSettingsWindow, self).__init__(parent)
self.app = app self.app = app
self.qvm_collection = qvm_collection self.qubes_app = qubes_app
self.vm = self.qvm_collection.domains[self.qvm_collection.local_name] self.vm = self.qubes_app.domains[self.qubes_app.local_name]
self.setupUi(self) self.setupUi(self)
@ -90,101 +89,95 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
def __init_system_defaults__(self): def __init_system_defaults__(self):
# set up updatevm choice # set up updatevm choice
self.update_vm_vmlist, self.update_vm_idx = utils.prepare_vm_choice( utils.initialize_widget_with_vms(
self.update_vm_combo, self.qvm_collection, 'updatevm', widget=self.update_vm_combo,
None, allow_none=True, qubes_app=self.qubes_app,
filter_function=(lambda vm: vm.klass != 'TemplateVM') filter_function=(lambda vm: vm.klass != 'TemplateVM'),
allow_none=True,
holder=self.qubes_app,
property_name="updatevm"
) )
# set up clockvm choice # set up clockvm choice
self.clock_vm_vmlist, self.clock_vm_idx = utils.prepare_vm_choice( utils.initialize_widget_with_vms(
self.clock_vm_combo, self.qvm_collection, 'clockvm', widget=self.clock_vm_combo,
None, allow_none=True, qubes_app=self.qubes_app,
filter_function=(lambda vm: vm.klass != 'TemplateVM') filter_function=(lambda vm: vm.klass != 'TemplateVM'),
allow_none=True,
holder=self.qubes_app,
property_name="clockvm"
) )
# set up default netvm # set up default netvm
self.default_netvm_vmlist, self.default_netvm_idx = \ utils.initialize_widget_with_vms(
utils.prepare_vm_choice( widget=self.default_netvm_combo,
self.default_netvm_combo, qubes_app=self.qubes_app,
self.qvm_collection, 'default_netvm', filter_function=(lambda vm: vm.provides_network),
None, allow_none=True,
filter_function=(lambda vm: vm.provides_network), holder=self.qubes_app,
allow_none=True) property_name="default_netvm"
)
# default template # default template
self.default_template_vmlist, self.default_template_idx = \ utils.initialize_widget_with_vms(
utils.prepare_vm_choice( widget=self.default_template_combo,
self.default_template_combo, qubes_app=self.qubes_app,
self.qvm_collection, 'default_template', filter_function=(lambda vm: vm.klass == 'TemplateVM'),
None, allow_none=True,
filter_function=(lambda vm: vm.klass == 'TemplateVM'), holder=self.qubes_app,
allow_none=True property_name="default_template"
) )
# default dispvm # default dispvm
self.default_dispvm_vmlist, self.default_dispvm_idx = \ utils.initialize_widget_with_vms(
utils.prepare_vm_choice( widget=self.default_dispvm_combo,
self.default_dispvm_combo, qubes_app=self.qubes_app,
self.qvm_collection, 'default_dispvm', filter_function=(lambda vm: getattr(
None, vm, 'template_for_dispvms', False)),
(lambda vm: getattr(vm, 'template_for_dispvms', False)), allow_none=True,
allow_none=True holder=self.qubes_app,
) property_name="default_dispvm"
)
def __apply_system_defaults__(self): def __apply_system_defaults__(self):
# updatevm # updatevm
if self.qvm_collection.updatevm != \ if utils.did_widget_selection_change(self.update_vm_combo):
self.update_vm_vmlist[self.update_vm_combo.currentIndex()]: self.qubes_app.updatevm = self.update_vm_combo.currentData()
self.qvm_collection.updatevm = \
self.update_vm_vmlist[self.update_vm_combo.currentIndex()]
# clockvm # clockvm
if self.qvm_collection.clockvm != \ if utils.did_widget_selection_change(self.clock_vm_combo):
self.clock_vm_vmlist[self.clock_vm_combo.currentIndex()]: self.qubes_app.clockvm = self.clock_vm_combo.currentData()
self.qvm_collection.clockvm = \
self.clock_vm_vmlist[self.clock_vm_combo.currentIndex()]
# default netvm # default netvm
if self.qvm_collection.default_netvm != \ if utils.did_widget_selection_change(self.default_netvm_combo):
self.default_netvm_vmlist[ self.qubes_app.default_netvm = \
self.default_netvm_combo.currentIndex()]: self.default_netvm_combo.currentData()
self.qvm_collection.default_netvm = \
self.default_netvm_vmlist[
self.default_netvm_combo.currentIndex()]
# default template # default template
if self.qvm_collection.default_template != \ if utils.did_widget_selection_change(self.default_template_combo):
self.default_template_vmlist[ self.qubes_app.default_template = \
self.default_template_combo.currentIndex()]: self.default_template_combo.currentData()
self.qvm_collection.default_template = \
self.default_template_vmlist[
self.default_template_combo.currentIndex()]
# default_dispvm # default_dispvm
if self.qvm_collection.default_dispvm != \ if utils.did_widget_selection_change(self.default_dispvm_combo):
self.default_dispvm_vmlist[ self.qubes_app.default_dispvm = \
self.default_dispvm_combo.currentIndex()]: self.default_dispvm_combo.currentData()
self.qvm_collection.default_dispvm = \
self.default_dispvm_vmlist[
self.default_dispvm_combo.currentIndex()]
def __init_kernel_defaults__(self): def __init_kernel_defaults__(self):
self.kernels_list, self.kernels_idx = utils.prepare_kernel_choice( utils.initialize_widget_with_kernels(
self.default_kernel_combo, self.qvm_collection, 'default_kernel', widget=self.default_kernel_combo,
None, qubes_app=self.qubes_app,
allow_none=True allow_none=True,
) holder=self.qubes_app,
property_name='default_kernel')
def __apply_kernel_defaults__(self): def __apply_kernel_defaults__(self):
if self.qvm_collection.default_kernel != \ if utils.did_widget_selection_change(self.default_kernel_combo):
self.kernels_list[self.default_kernel_combo.currentIndex()]: self.qubes_app.default_kernel = \
self.qvm_collection.default_kernel = \ self.default_kernel_combo.currentData()
self.kernels_list[self.default_kernel_combo.currentIndex()]
def __init_gui_defaults(self): def __init_gui_defaults(self):
utils.initialize_widget(
utils.prepare_choice_data(
widget=self.allow_fullscreen, widget=self.allow_fullscreen,
choices=[ choices=[
('default (disallow)', None), ('default (disallow)', None),
@ -194,9 +187,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
selected_value=utils.get_boolean_feature( selected_value=utils.get_boolean_feature(
self.vm, self.vm,
'gui-default-allow-fullscreen')) 'gui-default-allow-fullscreen'))
self.allow_fullscreen_initial = self.allow_fullscreen.currentIndex()
utils.prepare_choice_data( utils.initialize_widget(
widget=self.allow_utf8, widget=self.allow_utf8,
choices=[ choices=[
('default (disallow)', None), ('default (disallow)', None),
@ -206,9 +198,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
selected_value=utils.get_boolean_feature( selected_value=utils.get_boolean_feature(
self.vm, self.vm,
'gui-default-allow-utf8-titles')) 'gui-default-allow-utf8-titles'))
self.allow_utf8_initial = self.allow_utf8.currentIndex()
utils.prepare_choice_data( utils.initialize_widget(
widget=self.trayicon, widget=self.trayicon,
choices=[ choices=[
('default (thin border)', None), ('default (thin border)', None),
@ -221,9 +212,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
], ],
selected_value=self.vm.features.get('gui-default-trayicon-mode', selected_value=self.vm.features.get('gui-default-trayicon-mode',
None)) None))
self.trayicon_initial = self.trayicon.currentIndex()
utils.prepare_choice_data( utils.initialize_widget(
widget=self.securecopy, widget=self.securecopy,
choices=[ choices=[
('default (Ctrl+Shift+C)', None), ('default (Ctrl+Shift+C)', None),
@ -232,9 +222,8 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
], ],
selected_value=self.vm.features.get( selected_value=self.vm.features.get(
'gui-default-secure-copy-sequence', None)) 'gui-default-secure-copy-sequence', None))
self.securecopy_initial = self.securecopy.currentIndex()
utils.prepare_choice_data( utils.initialize_widget(
widget=self.securepaste, widget=self.securepaste,
choices=[ choices=[
('default (Ctrl+Shift+V)', None), ('default (Ctrl+Shift+V)', None),
@ -244,10 +233,9 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
], ],
selected_value=self.vm.features.get( selected_value=self.vm.features.get(
'gui-default-secure-paste-sequence', None)) 'gui-default-secure-paste-sequence', None))
self.securepaste_initial = self.securepaste.currentIndex()
def __apply_feature_change(self, widget, feature, inital_index): def __apply_feature_change(self, widget, feature):
if inital_index != widget.currentIndex(): if utils.did_widget_selection_change(widget):
if widget.currentData() is None: if widget.currentData() is None:
del self.vm.features[feature] del self.vm.features[feature]
else: else:
@ -255,20 +243,15 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
def __apply_gui_defaults(self): def __apply_gui_defaults(self):
self.__apply_feature_change(widget=self.allow_fullscreen, self.__apply_feature_change(widget=self.allow_fullscreen,
feature='gui-default-allow-fullscreen', feature='gui-default-allow-fullscreen')
inital_index=self.allow_fullscreen_initial)
self.__apply_feature_change(widget=self.allow_utf8, self.__apply_feature_change(widget=self.allow_utf8,
feature='gui-default-allow-utf8-titles', feature='gui-default-allow-utf8-titles')
inital_index=self.allow_utf8_initial)
self.__apply_feature_change(widget=self.trayicon, self.__apply_feature_change(widget=self.trayicon,
feature='gui-default-trayicon-mode', feature='gui-default-trayicon-mode')
inital_index=self.trayicon_initial)
self.__apply_feature_change(widget=self.securecopy, self.__apply_feature_change(widget=self.securecopy,
feature='gui-default-secure-copy-sequence', feature='gui-default-secure-copy-sequence')
inital_index=self.securecopy_initial)
self.__apply_feature_change(widget=self.securepaste, self.__apply_feature_change(widget=self.securepaste,
feature='gui-default-secure-paste-sequence', feature='gui-default-secure-paste-sequence')
inital_index=self.securepaste_initial)
def __init_mem_defaults__(self): def __init_mem_defaults__(self):
# qmemman settings # qmemman settings
@ -350,16 +333,13 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
qmemman_config_file.close() qmemman_config_file.close()
def __init_updates__(self): def __init_updates__(self):
try: self.updates_dom0_val = bool(
self.updates_dom0_val = bool(self.qvm_collection.domains[ self.qubes_app.domains['dom0'].features.get(
'dom0'].features[ 'service.qubes-update-check', True))
'service.qubes-update-check'])
except KeyError:
self.updates_dom0_val = True
self.updates_dom0.setChecked(self.updates_dom0_val) self.updates_dom0.setChecked(self.updates_dom0_val)
self.updates_vm.setChecked(self.qvm_collection.check_updates_vm) self.updates_vm.setChecked(self.qubes_app.check_updates_vm)
self.enable_updates_all.clicked.connect(self.__enable_updates_all) self.enable_updates_all.clicked.connect(self.__enable_updates_all)
self.disable_updates_all.clicked.connect(self.__disable_updates_all) self.disable_updates_all.clicked.connect(self.__disable_updates_all)
@ -421,18 +401,18 @@ class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
self.__set_updates_all(False) self.__set_updates_all(False)
def __set_updates_all(self, state): def __set_updates_all(self, state):
for vm in self.qvm_collection.domains: for vm in self.qubes_app.domains:
if vm.klass != "AdminVM": if vm.klass != "AdminVM":
vm.features['service.qubes-update-check'] = state vm.features['service.qubes-update-check'] = state
def __apply_updates__(self): def __apply_updates__(self):
if self.updates_dom0.isChecked() != self.updates_dom0_val: if self.updates_dom0.isChecked() != self.updates_dom0_val:
self.qvm_collection.domains['dom0'].features[ self.qubes_app.domains['dom0'].features[
'service.qubes-update-check'] = \ 'service.qubes-update-check'] = \
self.updates_dom0.isChecked() self.updates_dom0.isChecked()
if self.qvm_collection.check_updates_vm != self.updates_vm.isChecked(): if self.qubes_app.check_updates_vm != self.updates_vm.isChecked():
self.qvm_collection.check_updates_vm = self.updates_vm.isChecked() self.qubes_app.check_updates_vm = self.updates_vm.isChecked()
def _manage_repos(self, repolist, action): def _manage_repos(self, repolist, action):
for name in repolist: for name in repolist:

View File

@ -775,7 +775,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog):
except AttributeError: except AttributeError:
self.run_in_debug_mode.setVisible(False) self.run_in_debug_mode.setVisible(False)
utils.prepare_choice_data( utils.initialize_widget(
widget=self.allow_fullscreen, widget=self.allow_fullscreen,
choices=[ choices=[
('(use system default)', None), ('(use system default)', None),
@ -785,7 +785,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog):
selected_value=utils.get_boolean_feature(self.vm, selected_value=utils.get_boolean_feature(self.vm,
'gui-allow-fullscreen')) 'gui-allow-fullscreen'))
self.allow_fullscreen_initial = self.allow_fullscreen.currentIndex() self.allow_fullscreen_initial = self.allow_fullscreen.currentIndex()
utils.prepare_choice_data( utils.initialize_widget(
widget=self.allow_utf8, widget=self.allow_utf8,
choices=[ choices=[
('(use system default)', None), ('(use system default)', None),

View File

@ -39,7 +39,7 @@ class GlobalSettingsTest(unittest.TestCase):
self.qapp) self.qapp)
self.setattr_patcher = unittest.mock.patch.object( self.setattr_patcher = unittest.mock.patch.object(
type(self.dialog.qvm_collection), "__setattr__") type(self.dialog.qubes_app), "__setattr__")
self.setattr_mock = self.setattr_patcher.start() self.setattr_mock = self.setattr_patcher.start()
self.addCleanup(self.setattr_patcher.stop) self.addCleanup(self.setattr_patcher.stop)
@ -244,7 +244,7 @@ class GlobalSettingsTest(unittest.TestCase):
self.dialog.updates_dom0.setChecked(not current_state) self.dialog.updates_dom0.setChecked(not current_state)
with unittest.mock.patch.object( with unittest.mock.patch.object(
type(self.dialog.qvm_collection.domains['dom0'].features), type(self.dialog.qubes_app.domains['dom0'].features),
'__setitem__') as mock_features: '__setitem__') as mock_features:
self.__click_ok() self.__click_ok()
mock_features.assert_called_once_with('service.qubes-update-check', mock_features.assert_called_once_with('service.qubes-update-check',

View File

@ -33,11 +33,22 @@ from qubesadmin import events
from PyQt5 import QtWidgets, QtCore, QtGui # pylint: disable=import-error from PyQt5 import QtWidgets, QtCore, QtGui # pylint: disable=import-error
#TODO: remove
def _filter_internal(vm): def _filter_internal(vm):
return (not vm.klass == 'AdminVM' return (not vm.klass == 'AdminVM'
and not vm.features.get('internal', False)) and not vm.features.get('internal', False))
def is_internal(vm):
return (vm.klass == 'AdminVM'
or vm.features.get('internal', False))
def translate(string):
return QtCore.QCoreApplication.translate(
"ManagerUtils", string)
class SizeSpinBox(QtWidgets.QSpinBox): class SizeSpinBox(QtWidgets.QSpinBox):
# pylint: disable=invalid-name, no-self-use # pylint: disable=invalid-name, no-self-use
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -71,34 +82,109 @@ class SizeSpinBox(QtWidgets.QSpinBox):
def get_boolean_feature(vm, feature_name): def get_boolean_feature(vm, feature_name):
result = vm.features.get(feature_name, None) result = vm.features.get(feature_name, None)
if result is not None: if result is not None:
try: result = bool(result)
result = bool(result)
except ValueError:
result = None
return result return result
def prepare_choice_data(widget, # TODO: doublecheck translation
choices,
selected_value=None):
def did_widget_selection_change(widget):
return not translate(" (current)") in widget.currentText()
def initialize_widget(widget, choices,
selected_value=None):
""" """
populates widget (ListBox or ComboBox) with items populates widget (ListBox or ComboBox) with items. Previous widget contents
are erased.
:param widget: widget to populate :param widget: widget to populate
:param choices: list of tuples (text, value) to use to populate widget :param choices: list of tuples (text, value) to use to populate widget
:param selected_value: value to populate widget with :param selected_value: value to populate widget with
:return: :return:
""" """
while widget.count() > 0: widget.clear()
widget.removeItem(0) selected_item = None
for (name, value) in choices: for (name, value) in choices:
if value == selected_value:
selected_item = name
widget.addItem(name, value) widget.addItem(name, value)
if widget.findData(selected_value) > -1: if selected_item is not None:
widget.setCurrentIndex(widget.findData(selected_value)) widget.setCurrentIndex(widget.findText(selected_item))
else: else:
widget.addItem(selected_value, selected_value) widget.addItem(str(selected_value), selected_value)
widget.setCurrentIndex(widget.findData(selected_value)) widget.setCurrentIndex(widget.findText(str(selected_value)))
widget.setItemText(widget.currentIndex(),
widget.currentText() + translate(" (current)"))
def initialize_widget_for_property(
widget, choices, holder, property_name, allow_default=False):
# potentially add default
if allow_default:
default_property = holder.property_get_default(property_name)
if default_property is None:
default_property = "none"
choices.append(
(translate("default ({})").format(default_property),
qubesadmin.DEFAULT))
# calculate current (can be default)
if holder.property_is_default(property_name):
current_value = qubesadmin.DEFAULT
else:
current_value = getattr(holder, property_name)
initialize_widget(widget, choices, selected_value=current_value)
def initialize_widget_with_vms(widget,
qubes_app,
filter_function=(lambda x: True),
allow_none=False,
holder=None,
property_name=None,
allow_default=False,
allow_internal=False):
choices = []
for vm in qubes_app.domains:
if not allow_internal and is_internal(vm):
continue
if not filter_function(vm):
continue
else:
choices.append((vm.name, vm))
if allow_none:
choices.append((translate("(none)"), None))
initialize_widget_for_property(
widget=widget, choices=choices, holder=holder,
property_name=property_name, allow_default=allow_default)
def initialize_widget_with_kernels(widget,
qubes_app,
allow_none=False,
holder=None,
property_name=None,
allow_default=False
):
kernels = [kernel.vid for kernel in qubes_app.pools['linux-kernel'].volumes]
kernels = sorted(kernels, key=KernelVersion)
choices = [(kernel, kernel) for kernel in kernels]
if allow_none:
choices.append((translate("(none)"), None))
initialize_widget_for_property(
widget=widget, choices=choices, holder=holder,
property_name=property_name, allow_default=allow_default)
def prepare_choice(widget, holder, propname, choice, default, def prepare_choice(widget, holder, propname, choice, default,