From d1865219622d43894a74975c75d02b081764cedc Mon Sep 17 00:00:00 2001 From: donoban Date: Sat, 7 Nov 2020 16:04:11 +0100 Subject: [PATCH 01/24] Added template_menu --- qubesmanager/qube_manager.py | 36 +++++++++++++++++++++++++++++++++++- ui/qubemanager.ui | 10 ++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 91023d1..c2d0dc3 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -37,7 +37,7 @@ from PyQt5.QtCore import (Qt, QAbstractTableModel, QObject, pyqtSlot, QEvent, # pylint: disable=import-error from PyQt5.QtWidgets import (QLineEdit, QStyledItemDelegate, QToolTip, QMenu, QInputDialog, QMainWindow, QProgressDialog, QStyleOptionViewItem, - QAbstractItemView, QMessageBox) + QAbstractItemView, QMessageBox, QAction) # pylint: disable=import-error from PyQt5.QtGui import (QIcon, QPixmap, QRegExpValidator, QFont, QColor) @@ -646,6 +646,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): # suppress saving settings while initializing widgets settings_loaded = False + def change_template(self, template): + for info in self.get_selected_vms(): + info.vm.template = template + def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): super().__init__() self.setupUi(self) @@ -668,7 +672,15 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.context_menu = QMenu(self) + for vm in self.qubes_app.domains: + if vm.klass == 'TemplateVM': + action = self.template_menu.addAction(vm.name) + action.setData(vm.name) + action.setCheckable(True) + action.triggered.connect(partial(self.change_template, vm.name)) + self.context_menu.addAction(self.action_settings) + self.context_menu.addMenu(self.template_menu) self.context_menu.addAction(self.action_editfwrules) self.context_menu.addAction(self.action_appmenus) self.context_menu.addAction(self.action_set_keyboard_layout) @@ -720,6 +732,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.proxy.setFilterKeyColumn(2) self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) self.proxy.layoutChanged.connect(self.save_sorting) + self.proxy.layoutChanged.connect(self.update_template_menu) self.table.setModel(self.proxy) self.table.setItemDelegateForColumn(3, StateIconDelegate()) @@ -977,6 +990,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): def table_selection_changed(self): # Since selection could have multiple domains # enable all first and then filter them + self.template_menu.setEnabled(True) for action in self.toolbar.actions() + self.context_menu.actions(): action.setEnabled(True) @@ -987,17 +1001,20 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): ['Running', 'Transient', 'Halting', 'Dying']: self.action_resumevm.setEnabled(False) self.action_removevm.setEnabled(False) + self.template_menu.setEnabled(False) elif vm.state['power'] == 'Paused': self.action_removevm.setEnabled(False) self.action_pausevm.setEnabled(False) self.action_set_keyboard_layout.setEnabled(False) self.action_restartvm.setEnabled(False) self.action_open_console.setEnabled(False) + self.template_menu.setEnabled(False) elif vm.state['power'] == 'Suspend': self.action_set_keyboard_layout.setEnabled(False) self.action_removevm.setEnabled(False) self.action_pausevm.setEnabled(False) self.action_open_console.setEnabled(False) + self.template_menu.setEnabled(False) elif vm.state['power'] == 'Halted': self.action_set_keyboard_layout.setEnabled(False) self.action_pausevm.setEnabled(False) @@ -1020,9 +1037,13 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.action_editfwrules.setEnabled(False) self.action_set_keyboard_layout.setEnabled(False) self.action_run_command_in_vm.setEnabled(False) + self.template_menu.setEnabled(False) elif vm.klass == 'DispVM': self.action_appmenus.setEnabled(False) self.action_restartvm.setEnabled(False) + self.template_menu.setEnabled(False) + elif vm.klass == 'TemplateVM': + self.template_menu.setEnabled(False) if vm.vm.features.get('internal', False): self.action_appmenus.setEnabled(False) @@ -1031,6 +1052,19 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.action_updatevm.setEnabled(False) self.update_logs_menu() + self.update_template_menu() + + def update_template_menu(self): + if not self.template_menu.isEnabled(): + return + + for entry in self.template_menu.actions(): + entry.setChecked(False) + + for vm in self.get_selected_vms(): + for entry in self.template_menu.actions(): + if entry.data() == vm.template: + entry.setChecked(True) # noinspection PyArgumentList @pyqtSlot(name='on_action_createvm_triggered') diff --git a/ui/qubemanager.ui b/ui/qubemanager.ui index 47fd249..fdde9bc 100644 --- a/ui/qubemanager.ui +++ b/ui/qubemanager.ui @@ -279,6 +279,15 @@ Template :/log.png:/log.png + + + Template + + + + :/templatevm.png:/templatevm.png + + @@ -290,6 +299,7 @@ Template + From 62489e5c7803920f800d9439e914576f995b3249 Mon Sep 17 00:00:00 2001 From: donoban Date: Tue, 10 Nov 2020 00:12:13 +0100 Subject: [PATCH 02/24] Added properly handling when templates are added and removed --- qubesmanager/qube_manager.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index c2d0dc3..a41f180 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -670,15 +670,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.frame_width = 0 self.frame_height = 0 + self.init_template_menu() self.context_menu = QMenu(self) - for vm in self.qubes_app.domains: - if vm.klass == 'TemplateVM': - action = self.template_menu.addAction(vm.name) - action.setData(vm.name) - action.setCheckable(True) - action.triggered.connect(partial(self.change_template, vm.name)) - self.context_menu.addAction(self.action_settings) self.context_menu.addMenu(self.template_menu) self.context_menu.addAction(self.action_editfwrules) @@ -831,6 +825,15 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): progress.setValue(row_no) + def init_template_menu(self): + self.template_menu.clear() + for vm in self.qubes_app.domains: + if vm.klass == 'TemplateVM': + action = self.template_menu.addAction(vm.name) + action.setData(vm.name) + action.setCheckable(True) + action.triggered.connect(partial(self.change_template, vm.name)) + def setup_application(self): self.qt_app.setApplicationName(self.tr("Qube Manager")) self.qt_app.setWindowIcon(QIcon.fromTheme("qubes-manager")) @@ -888,12 +891,15 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): domain = self.qubes_app.domains[vm] self.qubes_cache.add_vm(domain) self.proxy.invalidate() + if domain.klass == 'TemplateVM': + self.init_template_menu() except (exc.QubesException, KeyError): pass def on_domain_removed(self, _submitter, _event, **kwargs): self.qubes_cache.remove_vm(name=kwargs['vm']) self.proxy.invalidate() + self.init_template_menu() def on_domain_status_changed(self, vm, event, **_kwargs): try: From 5f4526a35c0839ff0d388af8167f9e4b45562d00 Mon Sep 17 00:00:00 2001 From: donoban Date: Tue, 10 Nov 2020 00:50:43 +0100 Subject: [PATCH 03/24] added network_menu --- qubesmanager/qube_manager.py | 15 +++++++++++++++ ui/qubemanager.ui | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index a41f180..110a1eb 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -650,6 +650,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): for info in self.get_selected_vms(): info.vm.template = template + def change_network(self, netvm): + for info in self.get_selected_vms(): + info.vm.netvm = netvm + def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): super().__init__() self.setupUi(self) @@ -671,10 +675,12 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.frame_height = 0 self.init_template_menu() + self.init_network_menu() self.context_menu = QMenu(self) self.context_menu.addAction(self.action_settings) self.context_menu.addMenu(self.template_menu) + self.context_menu.addMenu(self.network_menu) self.context_menu.addAction(self.action_editfwrules) self.context_menu.addAction(self.action_appmenus) self.context_menu.addAction(self.action_set_keyboard_layout) @@ -834,6 +840,15 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): action.setCheckable(True) action.triggered.connect(partial(self.change_template, vm.name)) + def init_network_menu(self): + self.network_menu.clear() + for vm in self.qubes_app.domains: + if vm.qid != 0 and vm.provides_network: + action = self.network_menu.addAction(vm.name) + action.setData(vm.name) + action.setCheckable(True) + action.triggered.connect(partial(self.change_network, vm.name)) + def setup_application(self): self.qt_app.setApplicationName(self.tr("Qube Manager")) self.qt_app.setWindowIcon(QIcon.fromTheme("qubes-manager")) diff --git a/ui/qubemanager.ui b/ui/qubemanager.ui index fdde9bc..204d07f 100644 --- a/ui/qubemanager.ui +++ b/ui/qubemanager.ui @@ -288,6 +288,15 @@ Template :/templatevm.png:/templatevm.png + + + Network + + + + :/netvm.png:/netvm.png + + @@ -300,6 +309,7 @@ Template + From ed98a1ea6eb4ef82e18df80c31a49a1fbb3b5e0d Mon Sep 17 00:00:00 2001 From: donoban Date: Tue, 10 Nov 2020 00:55:55 +0100 Subject: [PATCH 04/24] Added 'None' netvm option --- qubesmanager/qube_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 110a1eb..022ad2d 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -842,6 +842,9 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): def init_network_menu(self): self.network_menu.clear() + action = self.network_menu.addAction("None") + action.setCheckable(True) + action.triggered.connect(partial(self.change_network, None)) for vm in self.qubes_app.domains: if vm.qid != 0 and vm.provides_network: action = self.network_menu.addAction(vm.name) From 95c74714d7ce30ec67394da48bfa8e398e2f0533 Mon Sep 17 00:00:00 2001 From: donoban Date: Fri, 13 Nov 2020 23:35:14 +0100 Subject: [PATCH 05/24] Added try/except for change_network --- qubesmanager/qube_manager.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 022ad2d..f0b0233 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -651,8 +651,14 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): info.vm.template = template def change_network(self, netvm): - for info in self.get_selected_vms(): - info.vm.netvm = netvm + try: + for info in self.get_selected_vms(): + info.vm.netvm = netvm + except exc.QubesValueError as ex: + QMessageBox.warning( + self, + self.tr("Change Network Error"), + self.tr((str(ex)))) def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): super().__init__() From 32e400661c286b1152cf91a5f00955044b443e40 Mon Sep 17 00:00:00 2001 From: donoban Date: Fri, 13 Nov 2020 23:52:49 +0100 Subject: [PATCH 06/24] Added network_menu updates --- qubesmanager/qube_manager.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index f0b0233..9e91195 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -739,6 +739,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) self.proxy.layoutChanged.connect(self.save_sorting) self.proxy.layoutChanged.connect(self.update_template_menu) + self.proxy.layoutChanged.connect(self.update_network_menu) self.table.setModel(self.proxy) self.table.setItemDelegateForColumn(3, StateIconDelegate()) @@ -1021,6 +1022,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): # Since selection could have multiple domains # enable all first and then filter them self.template_menu.setEnabled(True) + self.network_menu.setEnabled(True) for action in self.toolbar.actions() + self.context_menu.actions(): action.setEnabled(True) @@ -1068,6 +1070,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.action_set_keyboard_layout.setEnabled(False) self.action_run_command_in_vm.setEnabled(False) self.template_menu.setEnabled(False) + self.network_menu.setEnabled(False) elif vm.klass == 'DispVM': self.action_appmenus.setEnabled(False) self.action_restartvm.setEnabled(False) @@ -1083,6 +1086,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.update_logs_menu() self.update_template_menu() + self.update_network_menu() def update_template_menu(self): if not self.template_menu.isEnabled(): @@ -1096,6 +1100,21 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): if entry.data() == vm.template: entry.setChecked(True) + def update_network_menu(self): + if not self.network_menu.isEnabled(): + return + + for entry in self.network_menu.actions(): + entry.setChecked(False) + + for vm in self.get_selected_vms(): + if vm.netvm == "n/a": + self.network_menu.actions()[0].setChecked(True) + else: + for entry in self.network_menu.actions(): + if entry.data() == vm.netvm: + entry.setChecked(True) + # noinspection PyArgumentList @pyqtSlot(name='on_action_createvm_triggered') def action_createvm_triggered(self): From 56528aaece9e667217aa74e136158377728aebcc Mon Sep 17 00:00:00 2001 From: donoban Date: Sat, 14 Nov 2020 12:39:21 +0100 Subject: [PATCH 07/24] Added QMessageBox if netvm is halted and user wants to start it --- qubesmanager/qube_manager.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 9e91195..7b357af 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -650,10 +650,27 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): for info in self.get_selected_vms(): info.vm.template = template - def change_network(self, netvm): + def change_network(self, netvm_name): try: + check_power = any(info.state['power'] == 'Running' for info + in self.get_selected_vms()) + netvm = self.qubes_cache.get_vm(name=netvm_name) + if check_power and netvm.state['power'] != 'Running': + reply = QMessageBox.question( + self, self.tr("Qube Start Confirmation"), + self.tr("
Can not change netvm to a halted Qube.
" + "Do you want to start the Qube '{0}'?").format( + netvm_name), + QMessageBox.Yes | QMessageBox.Cancel) + + if reply == QMessageBox.Yes: + with common_threads.busy_cursor(): + netvm.vm.start() + else: + return + for info in self.get_selected_vms(): - info.vm.netvm = netvm + info.vm.netvm = netvm_name except exc.QubesValueError as ex: QMessageBox.warning( self, From 306bb85b069feb57013c884fa1f84dc2aba2b088 Mon Sep 17 00:00:00 2001 From: donoban Date: Sat, 14 Nov 2020 12:40:23 +0100 Subject: [PATCH 08/24] Moved change_* funcs after __init__() --- qubesmanager/qube_manager.py | 62 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 7b357af..b59451d 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -646,37 +646,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): # suppress saving settings while initializing widgets settings_loaded = False - def change_template(self, template): - for info in self.get_selected_vms(): - info.vm.template = template - - def change_network(self, netvm_name): - try: - check_power = any(info.state['power'] == 'Running' for info - in self.get_selected_vms()) - netvm = self.qubes_cache.get_vm(name=netvm_name) - if check_power and netvm.state['power'] != 'Running': - reply = QMessageBox.question( - self, self.tr("Qube Start Confirmation"), - self.tr("
Can not change netvm to a halted Qube.
" - "Do you want to start the Qube '{0}'?").format( - netvm_name), - QMessageBox.Yes | QMessageBox.Cancel) - - if reply == QMessageBox.Yes: - with common_threads.busy_cursor(): - netvm.vm.start() - else: - return - - for info in self.get_selected_vms(): - info.vm.netvm = netvm_name - except exc.QubesValueError as ex: - QMessageBox.warning( - self, - self.tr("Change Network Error"), - self.tr((str(ex)))) - def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): super().__init__() self.setupUi(self) @@ -831,6 +800,37 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.check_updates() + def change_template(self, template): + for info in self.get_selected_vms(): + info.vm.template = template + + def change_network(self, netvm_name): + try: + check_power = any(info.state['power'] == 'Running' for info + in self.get_selected_vms()) + netvm = self.qubes_cache.get_vm(name=netvm_name) + if check_power and netvm.state['power'] != 'Running': + reply = QMessageBox.question( + self, self.tr("Qube Start Confirmation"), + self.tr("
Can not change netvm to a halted Qube.
" + "Do you want to start the Qube '{0}'?").format( + netvm_name), + QMessageBox.Yes | QMessageBox.Cancel) + + if reply == QMessageBox.Yes: + with common_threads.busy_cursor(): + netvm.vm.start() + else: + return + + for info in self.get_selected_vms(): + info.vm.netvm = netvm_name + except exc.QubesValueError as ex: + QMessageBox.warning( + self, + self.tr("Change Network Error"), + self.tr((str(ex)))) + def save_sorting(self): self.manager_settings.setValue('view/sort_column', self.proxy.sortColumn()) From 3e5893e6cf2645c90711bf5fecbb4d55f2bd7b2e Mon Sep 17 00:00:00 2001 From: donoban Date: Wed, 18 Nov 2020 12:22:48 +0100 Subject: [PATCH 09/24] Added Template Change Confirmation --- qubesmanager/qube_manager.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index b59451d..bc280f5 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -801,8 +801,17 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.check_updates() def change_template(self, template): - for info in self.get_selected_vms(): - info.vm.template = template + selected_vms = self.get_selected_vms() + reply = QMessageBox.question( + self, self.tr("Template Change Confirmation"), + self.tr("Do you want to change '{0}'
" + "to Template '{1}'?").format( + ', '.join(vm.name for vm in selected_vms), template), + QMessageBox.Yes | QMessageBox.Cancel) + + if reply == QMessageBox.Yes: + for info in selected_vms: + info.vm.template = template def change_network(self, netvm_name): try: From 54d523de6cdf6b9e6499b6702a740987e0671df9 Mon Sep 17 00:00:00 2001 From: donoban Date: Wed, 18 Nov 2020 12:27:59 +0100 Subject: [PATCH 10/24] Added change network confirmation --- qubesmanager/qube_manager.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index bc280f5..d6dcb9f 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -814,6 +814,17 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): info.vm.template = template def change_network(self, netvm_name): + selected_vms = self.get_selected_vms() + reply = QMessageBox.question( + self, self.tr("Network Change Confirmation"), + self.tr("Do you want to change '{0}'
" + "to Network '{1}'?").format( + ', '.join(vm.name for vm in selected_vms), netvm_name), + QMessageBox.Yes | QMessageBox.Cancel) + + if reply != QMessageBox.Yes: + return + try: check_power = any(info.state['power'] == 'Running' for info in self.get_selected_vms()) From 9dd9ba17ce0a02efef00b5a58e212a1537c4fcb6 Mon Sep 17 00:00:00 2001 From: donoban Date: Thu, 26 Nov 2020 00:04:10 +0100 Subject: [PATCH 11/24] Changed checkboxes to icons --- qubesmanager/qube_manager.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index d6dcb9f..2550435 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -742,7 +742,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): column = self.qubes_model.columns_indices[col_no] action = self.menu_view.addAction(column) action.setData(column) - action.setCheckable(True) action.toggled.connect(partial(self.showhide_column, col_no)) self.menu_view.addSeparator() @@ -881,19 +880,16 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): if vm.klass == 'TemplateVM': action = self.template_menu.addAction(vm.name) action.setData(vm.name) - action.setCheckable(True) action.triggered.connect(partial(self.change_template, vm.name)) def init_network_menu(self): self.network_menu.clear() action = self.network_menu.addAction("None") - action.setCheckable(True) action.triggered.connect(partial(self.change_network, None)) for vm in self.qubes_app.domains: if vm.qid != 0 and vm.provides_network: action = self.network_menu.addAction(vm.name) action.setData(vm.name) - action.setCheckable(True) action.triggered.connect(partial(self.change_network, vm.name)) def setup_application(self): @@ -1130,27 +1126,36 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): return for entry in self.template_menu.actions(): - entry.setChecked(False) + entry.setIcon(QIcon()) - for vm in self.get_selected_vms(): + vms = self.get_selected_vms() + for vm in vms: for entry in self.template_menu.actions(): if entry.data() == vm.template: - entry.setChecked(True) + if len(vms) == 1: + entry.setIcon(QIcon(":/on.png")) + else: + entry.setIcon(QIcon(":/transient.png")) def update_network_menu(self): if not self.network_menu.isEnabled(): return for entry in self.network_menu.actions(): - entry.setChecked(False) + entry.setIcon(QIcon()) + + if len(self.get_selected_vms()) == 1: + icon = QIcon(":/on.png") + else: + icon = QIcon(":/transient.png") for vm in self.get_selected_vms(): if vm.netvm == "n/a": - self.network_menu.actions()[0].setChecked(True) + self.network_menu.actions()[0].setIcon(QIcon(icon)) else: for entry in self.network_menu.actions(): if entry.data() == vm.netvm: - entry.setChecked(True) + entry.setIcon(icon) # noinspection PyArgumentList @pyqtSlot(name='on_action_createvm_triggered') From 0cb89e611aef8949adac0f7c9bd21948e4c25bad Mon Sep 17 00:00:00 2001 From: donoban Date: Fri, 11 Dec 2020 00:38:06 +0100 Subject: [PATCH 12/24] Added proper error handling and Check netvm_name is not None --- qubesmanager/qube_manager.py | 60 ++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 64bd2dd..95664e8 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -820,8 +820,20 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): QMessageBox.Yes | QMessageBox.Cancel) if reply == QMessageBox.Yes: + failed = [] for info in selected_vms: - info.vm.template = template + try: + info.vm.template = template + except: + failed.append(info.name) + + if failed: + info_dialog = QMessageBox(self) + info_dialog.setWindowTitle(self.tr("Warning!")) + info_dialog.setText( + self.tr("Some template change failed: {0} " + ).format(", ".join(failed))) + info_dialog.show() def change_network(self, netvm_name): selected_vms = self.get_selected_vms() @@ -836,25 +848,39 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): return try: - check_power = any(info.state['power'] == 'Running' for info - in self.get_selected_vms()) - netvm = self.qubes_cache.get_vm(name=netvm_name) - if check_power and netvm.state['power'] != 'Running': - reply = QMessageBox.question( - self, self.tr("Qube Start Confirmation"), - self.tr("
Can not change netvm to a halted Qube.
" - "Do you want to start the Qube '{0}'?").format( - netvm_name), - QMessageBox.Yes | QMessageBox.Cancel) + if netvm_name is not None: + check_power = any(info.state['power'] == 'Running' for info + in self.get_selected_vms()) + netvm = self.qubes_cache.get_vm(name=netvm_name) + if check_power and netvm.state['power'] != 'Running': + reply = QMessageBox.question( + self, self.tr("Qube Start Confirmation"), + self.tr("
Can not change netvm to a halted Qube.
" + "Do you want to start the Qube '{0}'?").format( + netvm_name), + QMessageBox.Yes | QMessageBox.Cancel) - if reply == QMessageBox.Yes: - with common_threads.busy_cursor(): - netvm.vm.start() - else: - return + if reply == QMessageBox.Yes: + with common_threads.busy_cursor(): + netvm.vm.start() + else: + return + failed = [] for info in self.get_selected_vms(): - info.vm.netvm = netvm_name + try: + info.vm.netvm = netvm_name + except: + failed.append(info.name) + + if failed: + info_dialog = QMessageBox(self) + info_dialog.setWindowTitle(self.tr("Warning!")) + info_dialog.setText( + self.tr("Some network change failed: {0} " + ).format(", ".join(failed))) + info_dialog.show() + except exc.QubesValueError as ex: QMessageBox.warning( self, From b29934b8985d1a39e00262f91edddc4dfd275e45 Mon Sep 17 00:00:00 2001 From: donoban Date: Fri, 11 Dec 2020 23:48:16 +0100 Subject: [PATCH 13/24] Added error message to dialogs --- qubesmanager/qube_manager.py | 78 +++++++++++++++++------------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 95664e8..81cb9bd 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -820,19 +820,19 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): QMessageBox.Yes | QMessageBox.Cancel) if reply == QMessageBox.Yes: - failed = [] + errors = [] for info in selected_vms: try: info.vm.template = template - except: - failed.append(info.name) + except exc.QubesValueError as ex: + errors.append((info.name, ex)) - if failed: + for error in errors: info_dialog = QMessageBox(self) - info_dialog.setWindowTitle(self.tr("Warning!")) + info_dialog.setWindowTitle(self.tr("Error!")) info_dialog.setText( - self.tr("Some template change failed: {0} " - ).format(", ".join(failed))) + self.tr("Template change failed for '{0}':
{1}" + ).format(error[0], error[1])) info_dialog.show() def change_network(self, netvm_name): @@ -847,45 +847,39 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): if reply != QMessageBox.Yes: return - try: - if netvm_name is not None: - check_power = any(info.state['power'] == 'Running' for info - in self.get_selected_vms()) - netvm = self.qubes_cache.get_vm(name=netvm_name) - if check_power and netvm.state['power'] != 'Running': - reply = QMessageBox.question( - self, self.tr("Qube Start Confirmation"), - self.tr("
Can not change netvm to a halted Qube.
" - "Do you want to start the Qube '{0}'?").format( - netvm_name), - QMessageBox.Yes | QMessageBox.Cancel) + if netvm_name is not None: + check_power = any(info.state['power'] == 'Running' for info + in self.get_selected_vms()) + netvm = self.qubes_cache.get_vm(name=netvm_name) + if check_power and netvm.state['power'] != 'Running': + reply = QMessageBox.question( + self, self.tr("Qube Start Confirmation"), + self.tr("
Can not change netvm to a halted Qube.
" + "Do you want to start the Qube '{0}'?").format( + netvm_name), + QMessageBox.Yes | QMessageBox.Cancel) - if reply == QMessageBox.Yes: - with common_threads.busy_cursor(): - netvm.vm.start() - else: - return + if reply == QMessageBox.Yes: + with common_threads.busy_cursor(): + netvm.vm.start() + else: + return - failed = [] - for info in self.get_selected_vms(): - try: - info.vm.netvm = netvm_name - except: - failed.append(info.name) + errors = [] + for info in self.get_selected_vms(): + try: + info.vm.netvm = netvm_name + except exc.QubesValueError as ex: + errors.append((info.name, ex)) - if failed: - info_dialog = QMessageBox(self) - info_dialog.setWindowTitle(self.tr("Warning!")) - info_dialog.setText( - self.tr("Some network change failed: {0} " - ).format(", ".join(failed))) - info_dialog.show() + for error in errors: + info_dialog = QMessageBox(self) + info_dialog.setWindowTitle(self.tr("Error!")) + info_dialog.setText( + self.tr("Network change failed for '{0'}:
{1]" + ).format(error[0], error[1])) + info_dialog.show() - except exc.QubesValueError as ex: - QMessageBox.warning( - self, - self.tr("Change Network Error"), - self.tr((str(ex)))) def __init_context_menu(self): self.context_menu = QMenu(self) From 81d95f3de9aa85722b72baa02d4133a662ec623b Mon Sep 17 00:00:00 2001 From: donoban Date: Sat, 12 Dec 2020 00:08:49 +0100 Subject: [PATCH 14/24] Added try/except for starting netvm --- qubesmanager/qube_manager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 81cb9bd..3c77ad8 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -861,7 +861,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): if reply == QMessageBox.Yes: with common_threads.busy_cursor(): - netvm.vm.start() + try: + netvm.vm.start() + except exc.QubesException as ex: + QMessageBox.warning(self, "Error starting Qube!", str(ex)) + return else: return From 4343691b928529d43f07ccb7d3ac7b61c6997142 Mon Sep 17 00:00:00 2001 From: donoban Date: Sat, 12 Dec 2020 00:19:30 +0100 Subject: [PATCH 15/24] Better dialog creation --- qubesmanager/qube_manager.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 3c77ad8..1ff123b 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -825,15 +825,12 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): try: info.vm.template = template except exc.QubesValueError as ex: - errors.append((info.name, ex)) + errors.append((info.name, str(ex))) for error in errors: - info_dialog = QMessageBox(self) - info_dialog.setWindowTitle(self.tr("Error!")) - info_dialog.setText( - self.tr("Template change failed for '{0}':
{1}" - ).format(error[0], error[1])) - info_dialog.show() + QMessageBox.warning(self, "{0} template change failed!" + .format(error[0]), error[1]) + def change_network(self, netvm_name): selected_vms = self.get_selected_vms() @@ -874,15 +871,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): try: info.vm.netvm = netvm_name except exc.QubesValueError as ex: - errors.append((info.name, ex)) + errors.append((info.name, str(ex))) for error in errors: - info_dialog = QMessageBox(self) - info_dialog.setWindowTitle(self.tr("Error!")) - info_dialog.setText( - self.tr("Network change failed for '{0'}:
{1]" - ).format(error[0], error[1])) - info_dialog.show() + QMessageBox.warning(self, "{0} network change failed!" + .format(error[0]), error[1]) def __init_context_menu(self): From f95bcbb1bc4c818bd9c1c6c34a23c42cb4792f59 Mon Sep 17 00:00:00 2001 From: donoban Date: Sat, 12 Dec 2020 00:34:41 +0100 Subject: [PATCH 16/24] Added wait argument to start_vm Easier error handling for start vm's from other places --- qubesmanager/qube_manager.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 1ff123b..cdf37fd 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -857,12 +857,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): QMessageBox.Yes | QMessageBox.Cancel) if reply == QMessageBox.Yes: - with common_threads.busy_cursor(): - try: - netvm.vm.start() - except exc.QubesException as ex: - QMessageBox.warning(self, "Error starting Qube!", str(ex)) - return + self.start_vm(netvm.vm, True) else: return @@ -1332,7 +1327,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.start_vm(vm) - def start_vm(self, vm): + def start_vm(self, vm, wait=False): if manager_utils.is_running(vm, False): return @@ -1341,6 +1336,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): thread.finished.connect(self.clear_threads) thread.start() + if wait: + with common_threads.busy_cursor(): + thread.wait() + # noinspection PyArgumentList @pyqtSlot(name='on_action_startvm_tools_install_triggered') # TODO: replace with boot from device From b72cc34a83c67494ecb35d5dda21e793fb93b2b6 Mon Sep 17 00:00:00 2001 From: donoban Date: Wed, 16 Dec 2020 11:30:39 +0100 Subject: [PATCH 17/24] Wrap warnings message in self.tr() --- qubesmanager/qube_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 4b023d2..7fda60d 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -827,7 +827,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): errors.append((info.name, str(ex))) for error in errors: - QMessageBox.warning(self, "{0} template change failed!" + QMessageBox.warning(self, self.tr("{0} template change failed!") .format(error[0]), error[1]) @@ -868,7 +868,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): errors.append((info.name, str(ex))) for error in errors: - QMessageBox.warning(self, "{0} network change failed!" + QMessageBox.warning(self, self.tr("{0} network change failed!") .format(error[0]), error[1]) From 9f44f7413da78209bd48237c042ed02d155b2c08 Mon Sep 17 00:00:00 2001 From: donoban Date: Sat, 9 Jan 2021 00:42:47 +0100 Subject: [PATCH 18/24] Added default option for network change --- qubesmanager/qube_manager.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 7fda60d..ac0d73b 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -843,7 +843,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): if reply != QMessageBox.Yes: return - if netvm_name is not None: + if netvm_name not in [None, 'default']: check_power = any(info.state['power'] == 'Running' for info in self.get_selected_vms()) netvm = self.qubes_cache.get_vm(name=netvm_name) @@ -863,7 +863,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): errors = [] for info in self.get_selected_vms(): try: - info.vm.netvm = netvm_name + if netvm_name == 'default': + delattr(info.vm, 'netvm') + else: + info.vm.netvm = netvm_name except exc.QubesValueError as ex: errors.append((info.name, str(ex))) @@ -945,10 +948,19 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): action.setData(vm.name) action.triggered.connect(partial(self.change_template, vm.name)) + def _get_default_netvm(self): + for vm in self.qubes_app.domains: + if vm.klass == 'AppVM': + return vm.property_get_default('netvm') + def init_network_menu(self): + default = self._get_default_netvm().name self.network_menu.clear() action = self.network_menu.addAction("None") action.triggered.connect(partial(self.change_network, None)) + action = self.network_menu.addAction("default ({0})".format(default)) + action.triggered.connect(partial(self.change_network, 'default')) + for vm in self.qubes_app.domains: if vm.qid != 0 and vm.provides_network: action = self.network_menu.addAction(vm.name) From a02aeb4c25ff18931b1f9684cf977595e86b35a9 Mon Sep 17 00:00:00 2001 From: donoban Date: Sun, 10 Jan 2021 23:39:33 +0100 Subject: [PATCH 19/24] Fix possible 'None' default error --- qubesmanager/qube_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index ac0d73b..fddfd98 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -954,7 +954,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): return vm.property_get_default('netvm') def init_network_menu(self): - default = self._get_default_netvm().name + default = self._get_default_netvm() self.network_menu.clear() action = self.network_menu.addAction("None") action.triggered.connect(partial(self.change_network, None)) From 94ede9800754b94e1a3cb7566bfb0e6cc4c90b12 Mon Sep 17 00:00:00 2001 From: donoban Date: Mon, 25 Jan 2021 21:27:50 +0100 Subject: [PATCH 20/24] Display default netvm --- qubesmanager/qube_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index fddfd98..7948bfb 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -1243,6 +1243,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): for vm in self.get_selected_vms(): if vm.netvm == "n/a": self.network_menu.actions()[0].setIcon(QIcon(icon)) + elif vm.vm.property_is_default("netvm"): + self.network_menu.actions()[1].setIcon(QIcon(icon)) else: for entry in self.network_menu.actions(): if entry.data() == vm.netvm: From 37513dec7e019b43a28f1be984480d01e20f7ebe Mon Sep 17 00:00:00 2001 From: donoban Date: Wed, 3 Feb 2021 00:04:30 +0100 Subject: [PATCH 21/24] Disable network menu for templates --- qubesmanager/qube_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 7948bfb..42d4b6c 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -1202,6 +1202,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.template_menu.setEnabled(False) elif vm.klass == 'TemplateVM': self.template_menu.setEnabled(False) + self.network_menu.setEnabled(False) if vm.vm.features.get('internal', False): self.action_appmenus.setEnabled(False) From 1edc4effd8c2383fe102a38b5a0700307cf71b5c Mon Sep 17 00:00:00 2001 From: donoban Date: Wed, 3 Feb 2021 00:05:37 +0100 Subject: [PATCH 22/24] Add warning if trying to change template VM --- qubesmanager/settings.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index c55a266..9a2439f 100644 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -432,6 +432,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog): self.netVM.setCurrentIndex(-1) self.netVM.currentIndexChanged.connect(self.check_warn_dispvmnetvm) + self.netVM.currentIndexChanged.connect(self.check_warn_templatenetvm) try: self.include_in_backups.setChecked(self.vm.include_in_backups) @@ -625,6 +626,16 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog): self.init_mem.value() * 10 < self.max_mem_size.value(): self.warn_too_much_mem_label.setVisible(True) + def check_warn_templatenetvm(self): + if self.vm.klass == 'TemplateVM': + QtWidgets.QMessageBox.warning( + self, + self.tr("Warning!"), + self.tr("Connecting a TemplateVM directly to a network is higly " + "discouraged!
You are breaking a basic part" + " of Qubes security and there is probably no real need " + "to do so. Continue at your own risk.")) + def check_warn_dispvmnetvm(self): if not hasattr(self.vm, 'default_dispvm'): self.warn_netvm_dispvm.setVisible(False) From 4e6cf91f630fc0aca61064644fb13dde454db6ea Mon Sep 17 00:00:00 2001 From: donoban Date: Wed, 3 Feb 2021 00:07:41 +0100 Subject: [PATCH 23/24] Fix too long line --- qubesmanager/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 9a2439f..ce77905 100644 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -631,10 +631,10 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog): QtWidgets.QMessageBox.warning( self, self.tr("Warning!"), - self.tr("Connecting a TemplateVM directly to a network is higly " - "discouraged!
You are breaking a basic part" - " of Qubes security and there is probably no real need " - "to do so. Continue at your own risk.")) + self.tr("Connecting a TemplateVM directly to a network is higly" + " discouraged!
You are breaking a basic par" + "t of Qubes security and there is probably no real need" + " to do so. Continue at your own risk.")) def check_warn_dispvmnetvm(self): if not hasattr(self.vm, 'default_dispvm'): From 803406089649d179a3ad99d68d725a23260c06f6 Mon Sep 17 00:00:00 2001 From: donoban Date: Tue, 9 Feb 2021 21:11:35 +0100 Subject: [PATCH 24/24] Fix coherence in network menu when adding/removing domains --- qubesmanager/qube_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 42d4b6c..e7505da 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -1033,6 +1033,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): self.qubes_cache.remove_vm(name=kwargs['vm']) self.proxy.invalidate() self.init_template_menu() + self.init_network_menu() def on_domain_status_changed(self, vm, event, **_kwargs): try: @@ -1062,6 +1063,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): return try: + if event.endswith(':provides_network'): + self.init_network_menu() self.qubes_cache.get_vm(qid=vm.qid).update(event=event) self.proxy.invalidate() except exc.QubesDaemonAccessError: