diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bf79edb7..26c4a06f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,10 +21,11 @@ checks:tests: stage: checks variables: PYTEST_ADDOPTS: "--color=yes" + BLACK_ARGS: -l88 -v --diff --color --check before_script: &before-script - "PATH=$PATH:$HOME/.local/bin" - sudo dnf install -y python3-gobject python3-pytest python3-pytest-asyncio - python3-coverage sequoia-sqv python3-pip python3-pytest-qt + python3-coverage sequoia-sqv python3-pip python3-pytest-qt xorg-x11-server-Xvfb python3-PyQt6-devel python3-pyqt6 - pip3 install --quiet -r ci/requirements.txt - git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client diff --git a/.pylintrc b/.pylintrc index 23ee66f2..48f91cc2 100644 --- a/.pylintrc +++ b/.pylintrc @@ -95,7 +95,7 @@ notes=FIXME,FIX,XXX,TODO [FORMAT] # Maximum number of characters on a single line. -max-line-length=80 +max-line-length=88 # Maximum number of lines in a module max-module-lines=3000 diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 3db64643..481df06f 100644 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -65,12 +65,8 @@ def get_default_bootmode_name(vm, bootmode): if bootmode == "default": - return vm.features.check_with_template( - "boot-mode.name.default", "" - ) - return vm.features.check_with_template( - f"boot-mode.name.{bootmode}", bootmode - ) + return vm.features.check_with_template("boot-mode.name.default", "") + return vm.features.check_with_template(f"boot-mode.name.{bootmode}", bootmode) # pylint: disable=too-few-public-methods @@ -86,7 +82,7 @@ def run(self): failed_props = [] - for (holder, prop) in self.dependencies: + for holder, prop in self.dependencies: try: if holder is None: setattr(self.vm.app, prop, new_vm) @@ -98,14 +94,17 @@ def run(self): del self.vm.app.domains[self.vm.name] else: list_text = utils.format_dependencies_list(failed_props) - self.msg = (self.tr("Warning: rename partially unsuccessful!"), - self.tr("Some properties could not be changed to " - "the new name. The system has now both {} " - "and {} qubes. To resolve this, please " - "check and change the following properties " - "and remove the qube {} manually.
" - ).format(self.vm.name, self.new_vm_name, - self.vm.name) + list_text) + self.msg = ( + self.tr("Warning: rename partially unsuccessful!"), + self.tr( + "Some properties could not be changed to " + "the new name. The system has now both {} " + "and {} qubes. To resolve this, please " + "check and change the following properties " + "and remove the qube {} manually.
" + ).format(self.vm.name, self.new_vm_name, self.vm.name) + + list_text, + ) except qubesadmin.exc.QubesException as ex: self.msg = (self.tr("Rename error!"), str(ex)) @@ -121,14 +120,16 @@ def __init__(self, vm, button): def run(self): vms_to_refresh = [self.vm] - template = getattr(self.vm, 'template', None) + template = getattr(self.vm, "template", None) if template: vms_to_refresh.append(template) for vm in vms_to_refresh: self.button.setText( - self.tr('Refresh in progress (refreshing applications ' - 'from {})').format(vm.name)) + self.tr("Refresh in progress (refreshing applications from {})").format( + vm.name + ) + ) try: if not utils.is_running(vm, True): not_running = True @@ -136,7 +137,7 @@ def run(self): else: not_running = False - subprocess.check_call(['qvm-sync-appmenus', vm.name]) + subprocess.check_call(["qvm-sync-appmenus", vm.name]) if not_running: vm.shutdown() @@ -146,19 +147,22 @@ def run(self): # pylint: disable=too-many-instance-attributes class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog): - tabs_indices = collections.OrderedDict(( - ('basic', 0), - ('advanced', 1), - ('firewall', 2), - ('devices', 3), - ('applications', 4), - ('services', 5), - ('notes', 6), - )) + tabs_indices = collections.OrderedDict( + ( + ("basic", 0), + ("advanced", 1), + ("firewall", 2), + ("devices", 3), + ("applications", 4), + ("services", 5), + ("notes", 6), + ) + ) # pylint: disable=too-many-positional-arguments - def __init__(self, vm_name, init_page="basic", qapp=None, qubesapp=None, - parent=None): + def __init__( + self, vm_name, init_page="basic", qapp=None, qubesapp=None, parent=None + ): super().__init__(parent) self.vm = qubesapp.domains[vm_name] @@ -170,17 +174,19 @@ def __init__(self, vm_name, init_page="basic", qapp=None, qubesapp=None, self.setupUi(self) self.setWindowTitle(self.tr("Settings: {vm}").format(vm=self.vm.name)) - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowType.WindowMaximizeButtonHint | - QtCore.Qt.WindowType.WindowMinimizeButtonHint) + self.setWindowFlags( + self.windowFlags() + | QtCore.Qt.WindowType.WindowMaximizeButtonHint + | QtCore.Qt.WindowType.WindowMinimizeButtonHint + ) if init_page in self.tabs_indices: idx = self.tabs_indices[init_page] assert idx in range(self.tabWidget.count()) self.tabWidget.setCurrentIndex(idx) self.buttonBox.button( - QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect( - self.apply) + QtWidgets.QDialogButtonBox.StandardButton.Apply + ).clicked.connect(self.apply) self.tabWidget.currentChanged.connect(self.current_tab_changed) @@ -197,15 +203,22 @@ def __init__(self, vm_name, init_page="basic", qapp=None, qubesapp=None, ###### advanced tab self.__init_advanced_tab__() self.include_in_balancing.stateChanged.connect( - self.include_in_balancing_changed) + self.include_in_balancing_changed + ) self.init_mem.editingFinished.connect(self.check_mem_changes) self.max_mem_size.editingFinished.connect(self.check_mem_changes) self.check_mem_changes() + + self.dvm_template_checkbox.stateChanged.connect( + self.dvm_template_checkbox_changed + ) + self.boot_from_device_button.clicked.connect( - self.boot_from_cdrom_button_pressed) + self.boot_from_cdrom_button_pressed + ) ###### firewall tab - if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']): + if self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]): model = firewall.QubesFirewallRulesModel() try: model.set_vm(self.vm) @@ -214,23 +227,20 @@ def __init__(self, vm_name, init_page="basic", qapp=None, qubesapp=None, except firewall.FirewallModifiedOutsideError: self.disable_all_fw_conf() except qubesadmin.exc.QubesException: - self.tabWidget.setTabEnabled( - self.tabs_indices['firewall'], False) + self.tabWidget.setTabEnabled(self.tabs_indices["firewall"], False) self.new_rule_button.clicked.connect(self.new_rule_button_pressed) self.edit_rule_button.clicked.connect(self.edit_rule_button_pressed) - self.delete_rule_button.clicked.connect( - self.delete_rule_button_pressed) + self.delete_rule_button.clicked.connect(self.delete_rule_button_pressed) self.policy_deny_radio_button.clicked.connect(self.policy_changed) self.policy_allow_radio_button.clicked.connect(self.policy_changed) - if init_page == 'firewall': + if init_page == "firewall": self.check_network_availability() ####### devices tab self.__init_devices_tab__() self.dev_list.selectedChanged.connect(self.devices_selection_changed) - self.no_strict_reset_button.clicked.connect( - self.strict_reset_button_pressed) + self.no_strict_reset_button.clicked.connect(self.strict_reset_button_pressed) self.current_strict_reset_list = [] self.new_strict_reset_list = [] self.define_strict_reset_devices() @@ -245,11 +255,11 @@ def __init__(self, vm_name, init_page="basic", qapp=None, qubesapp=None, self.app_list = multiselectwidget.MultiSelectWidget(self) self.app_list.change_labels( available="All available applications", - selected="Applications shown in App Menu") + selected="Applications shown in App Menu", + ) self.apps_layout.addWidget(self.app_list) self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list) - self.refresh_apps_button.clicked.connect( - self.refresh_apps_button_pressed) + self.refresh_apps_button.clicked.connect(self.refresh_apps_button_pressed) # Enable Drag & Drop between between two panels # ToDo: Disable D&D between multiple instances of qubes-vm-settings @@ -257,22 +267,28 @@ def __init__(self, vm_name, init_page="basic", qapp=None, qubesapp=None, self.app_list.available_list.setDragEnabled(True) self.app_list.available_list.setAcceptDrops(True) self.app_list.available_list.setDragDropMode( - QtWidgets.QListWidget.DragDropMode.DragDrop) + QtWidgets.QListWidget.DragDropMode.DragDrop + ) self.app_list.available_list.setDefaultDropAction( - QtCore.Qt.DropAction.MoveAction) + QtCore.Qt.DropAction.MoveAction + ) self.app_list.selected_list.setDragEnabled(True) self.app_list.selected_list.setAcceptDrops(True) self.app_list.selected_list.setDragDropMode( - QtWidgets.QListWidget.DragDropMode.DragDrop) + QtWidgets.QListWidget.DragDropMode.DragDrop + ) self.app_list.selected_list.setDefaultDropAction( - QtCore.Qt.DropAction.MoveAction) + QtCore.Qt.DropAction.MoveAction + ) # template change if self.template_name.isEnabled(): self.template_name.currentIndexChanged.connect( - self.template_apps_change) + self.template_apps_change + ) self.warn_template_missing_apps.setVisible( - self.app_list_manager.has_missing) + self.app_list_manager.has_missing + ) ####### notes tab self.__init_notes_tab__() @@ -290,10 +306,7 @@ def clear_threads(self): if thread.msg: (title, msg) = thread.msg - QtWidgets.QMessageBox.warning( - self, - title, - msg) + QtWidgets.QMessageBox.warning(self, title, msg) self.threads_list.remove(thread) @@ -302,11 +315,13 @@ def clear_threads(self): return - raise RuntimeError(self.tr('No finished thread found')) + raise RuntimeError(self.tr("No finished thread found")) def keyPressEvent(self, event): # pylint: disable=invalid-name - if event.key() == QtCore.Qt.Key.Key_Enter \ - or event.key() == QtCore.Qt.Key.Key_Return: + if ( + event.key() == QtCore.Qt.Key.Key_Enter + or event.key() == QtCore.Qt.Key.Key_Return + ): return super().keyPressEvent(event) @@ -320,9 +335,9 @@ def save_changes(self): if error: QtWidgets.QMessageBox.warning( self, - self.tr("Error while changing settings for {0}!" - ).format(self.vm.name), - self.tr("ERROR: {0}").format('\n'.join(error))) + self.tr("Error while changing settings for {0}!").format(self.vm.name), + self.tr("ERROR: {0}").format("\n".join(error)), + ) def apply(self): self.save_changes() @@ -362,17 +377,20 @@ def __save_changes__(self): if ret_tmp: ret += [self.tr("Notes tab:")] + ret_tmp except qubesadmin.exc.QubesException as qex: - ret.append(self.tr('Error while saving changes: ') + str(qex)) + ret.append(self.tr("Error while saving changes: ") + str(qex)) except Exception as ex: # pylint: disable=broad-except ret.append(repr(ex)) try: - if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']) and \ - self.policy_allow_radio_button.isEnabled(): + if ( + self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]) + and self.policy_allow_radio_button.isEnabled() + ): self.fw_model.apply_rules( self.policy_allow_radio_button.isChecked(), self.temp_full_access.isChecked(), - self.temp_full_access_time.value()) + self.temp_full_access_time.value(), + ) except qubesadmin.exc.QubesException as qex: ret += [self.tr("Firewall tab:"), str(qex)] except Exception as ex: # pylint: disable=broad-except @@ -386,14 +404,14 @@ def __save_changes__(self): except Exception as ex: # pylint: disable=broad-except ret += [self.tr("Applications tab:"), repr(ex)] - utils.debug('\n'.join(ret)) + utils.debug("\n".join(ret)) return ret def check_network_availability(self): # this should attempt to use whatever is currently selected, not VM # settings if self.netVM.currentData() == qubesadmin.DEFAULT: - netvm = self.vm.property_get_default('netvm') + netvm = self.vm.property_get_default("netvm") else: netvm = self.netVM.currentData() provides_network = self.provides_network_checkbox.isChecked() @@ -401,9 +419,10 @@ def check_network_availability(self): self.no_netvm_label.setVisible(netvm is None and not provides_network) try: - no_firewall_state = \ - netvm is not None and \ - not netvm.features.check_with_template('qubes-firewall', False) + no_firewall_state = ( + netvm is not None + and not netvm.features.check_with_template("qubes-firewall", False) + ) except qubesadmin.exc.QubesDaemonAccessError: no_firewall_state = False @@ -412,20 +431,23 @@ def check_network_availability(self): def collect_bootmode_data(self): bootmode_ids = [ - x.split('.')[2] for x in self.vm.features \ - if x.startswith("boot-mode.kernelopts.") + x.split(".")[2] + for x in self.vm.features + if x.startswith("boot-mode.kernelopts.") ] subject = self.vm while hasattr(subject, "template"): - bootmode_ids.extend([ - x.split('.')[2] for x in subject.template.features \ + bootmode_ids.extend( + [ + x.split(".")[2] + for x in subject.template.features if x.startswith("boot-mode.kernelopts.") - ]) + ] + ) subject = subject.template bootmode_names = [ - self.vm.features.check_with_template( - f"boot-mode.name.{x}", x - ) for x in bootmode_ids + self.vm.features.check_with_template(f"boot-mode.name.{x}", x) + for x in bootmode_ids ] return bootmode_names, bootmode_ids @@ -450,50 +472,56 @@ def __init_basic_tab__(self): QtGui.QRegularExpressionValidator( QtCore.QRegularExpression( "[a-zA-Z0-9_-]*", - QtCore.QRegularExpression. - PatternOption.CaseInsensitiveOption), None)) + QtCore.QRegularExpression.PatternOption.CaseInsensitiveOption, + ), + None, + ) + ) self.vmname.setEnabled(False) self.rename_vm_button.setEnabled(not self.vm.is_running()) self.delete_vm_button.setEnabled(not self.vm.is_running()) if utils.is_running(self.vm, False): self.delete_vm_button.setText( - self.tr('Delete qube (cannot delete a running qube)')) + self.tr("Delete qube (cannot delete a running qube)") + ) - if self.vm.klass == 'AdminVM': + if self.vm.klass == "AdminVM": self.vmlabel.setVisible(False) else: try: utils.initialize_widget_with_labels( - widget=self.vmlabel, - qubes_app=self.qubesapp, - holder=self.vm) + widget=self.vmlabel, qubes_app=self.qubesapp, holder=self.vm + ) self.vmlabel.setVisible(True) self.vmlabel.setEnabled(not utils.is_running(self.vm, False)) except qubesadmin.exc.QubesDaemonAccessError: self.vmlabel.setEnabled(False) - if self.vm.klass == 'AppVM': + if self.vm.klass == "AppVM": try: utils.initialize_widget_with_vms( widget=self.template_name, qubes_app=self.qubesapp, - filter_function=(lambda vm: vm.klass == 'TemplateVM'), + filter_function=(lambda vm: vm.klass == "TemplateVM"), holder=self.vm, - property_name='template') + property_name="template", + ) except qubesadmin.exc.QubesDaemonAccessError: self.template_name.setCurrentIndex(-1) self.template_name.setEnabled(False) - elif self.vm.klass == 'DispVM': + elif self.vm.klass == "DispVM": try: utils.initialize_widget_with_vms( widget=self.template_name, qubes_app=self.qubesapp, filter_function=( - lambda vm: getattr(vm, 'template_for_dispvms', False)), + lambda vm: getattr(vm, "template_for_dispvms", False) + ), holder=self.vm, - property_name='template') + property_name="template", + ) except qubesadmin.exc.QubesDaemonAccessError: self.template_name.setCurrentIndex(-1) self.template_name.setEnabled(False) @@ -508,12 +536,12 @@ def __init_basic_tab__(self): utils.initialize_widget_with_vms( widget=self.netVM, qubes_app=self.qubesapp, - filter_function=(lambda vm: - getattr(vm, 'provides_network', False)), + filter_function=(lambda vm: getattr(vm, "provides_network", False)), holder=self.vm, - property_name='netvm', + property_name="netvm", allow_default=True, - allow_none=True) + allow_none=True, + ) except qubesadmin.exc.QubesDaemonAccessError: self.netVM.setEnabled(False) self.netVM.setCurrentIndex(-1) @@ -530,26 +558,32 @@ def __init_basic_tab__(self): try: has_shutdown_idle = self.vm.features.check_with_template( - IDLE_SUPPORTED_SERVICE, False) + IDLE_SUPPORTED_SERVICE, False + ) if has_shutdown_idle: self.idle_shutdown_checkbox.setChecked( - bool(self.vm.features.get(IDLE_SERVICE, False))) + bool(self.vm.features.get(IDLE_SERVICE, False)) + ) else: text = "Shut down when idle " - if getattr(self.vm, 'template', None): - additional_text = "(unavailable: package " \ - "qubes-app-shutdown-idle missing " \ - "in the template)" + if getattr(self.vm, "template", None): + additional_text = ( + "(unavailable: package " + "qubes-app-shutdown-idle missing " + "in the template)" + ) else: - additional_text = "(unavailable: package " \ - "qubes-app-shutdown-idle missing " \ - "in the qube)" + additional_text = ( + "(unavailable: package " + "qubes-app-shutdown-idle missing " + "in the qube)" + ) self.idle_shutdown_checkbox.setText(text + additional_text) self.idle_shutdown_checkbox.setEnabled(False) except qubesadmin.exc.QubesDaemonCommunicationError: self.idle_shutdown_checkbox.setText( - self.idle_shutdown_checkbox.text() + - " (unavailable: permission denied)") + self.idle_shutdown_checkbox.text() + " (unavailable: permission denied)" + ) self.idle_shutdown_checkbox.setEnabled(False) try: @@ -566,46 +600,57 @@ def __init_basic_tab__(self): # installed by rpm self.rpm_label.setText( - 'Yes' if getattr(self.vm, 'installed_by_rpm', False) else 'No') + "Yes" if getattr(self.vm, "installed_by_rpm", False) else "No" + ) # networking info - if getattr(self.vm, 'netvm', None): + if getattr(self.vm, "netvm", None): self.networking_groupbox.setEnabled(True) - self.ip_label.setText(getattr(self.vm, 'ip', None) or "none") + self.ip_label.setText(getattr(self.vm, "ip", None) or "none") self.netmask_label.setText( - getattr(self.vm, 'visible_netmask', None) or "none") + getattr(self.vm, "visible_netmask", None) or "none" + ) self.gateway_label.setText( - getattr(self.vm, 'visible_gateway', None) or "none") - dns_list = getattr(self.vm, 'dns', '10.139.1.1 10.139.1.2') - self.dns_label.setText(dns_list.replace(' ', ', ')) + getattr(self.vm, "visible_gateway", None) or "none" + ) + dns_list = getattr(self.vm, "dns", "10.139.1.1 10.139.1.2") + self.dns_label.setText(dns_list.replace(" ", ", ")) else: self.networking_groupbox.setEnabled(False) # max priv storage try: - self.priv_img_size = self.vm.volumes['private'].size // 1024**2 + self.priv_img_size = self.vm.volumes["private"].size // 1024**2 self.max_priv_storage.setMinimum(self.priv_img_size) self.max_priv_storage.setValue(self.priv_img_size) self.max_priv_storage.setMaximum( - max(self.priv_img_size, - self.qubesapp.pools[self.vm.volumes['private'].pool].size - // 1024**2)) + max( + self.priv_img_size, + self.qubesapp.pools[self.vm.volumes["private"].pool].size + // 1024**2, + ) + ) except qubesadmin.exc.QubesException: self.max_priv_storage.setEnabled(False) try: - self.root_img_size = self.vm.volumes['root'].size // 1024**2 + self.root_img_size = self.vm.volumes["root"].size // 1024**2 self.root_resize.setValue(self.root_img_size) self.root_resize.setMinimum(self.root_img_size) self.root_resize.setMaximum( - max(self.root_img_size, - self.qubesapp.pools[self.vm.volumes['root'].pool].size - // 1024**2)) - self.root_resize.setEnabled(self.vm.volumes['root'].save_on_stop) + max( + self.root_img_size, + self.qubesapp.pools[self.vm.volumes["root"].pool].size // 1024**2, + ) + ) + self.root_resize.setEnabled(self.vm.volumes["root"].save_on_stop) if not self.root_resize.isEnabled(): self.root_resize.setToolTip( - self.tr("To change system storage size, change properties " - "of the underlying template.")) + self.tr( + "To change system storage size, change properties " + "of the underlying template." + ) + ) self.root_resize_label.setEnabled(self.root_resize.isEnabled()) except qubesadmin.exc.QubesException: self.root_resize.setEnabled(False) @@ -632,19 +677,26 @@ def __apply_basic_tab__(self): try: if utils.did_widget_selection_change(self.netVM): if self.netVM.currentData() == qubesadmin.DEFAULT: - netvm = self.vm.property_get_default('netvm') + netvm = self.vm.property_get_default("netvm") else: netvm = self.netVM.currentData() - if self.vm.get_power_state() == 'Running' and netvm and \ - netvm.get_power_state() != 'Running': + if ( + self.vm.get_power_state() == "Running" + and netvm + and netvm.get_power_state() != "Running" + ): reply = QtWidgets.QMessageBox.question( - self, self.tr("Qube Start Confirmation"), - self.tr("
Can not change netvm of a running qube" - "to a halted Qube.
" - "Do you want to start the Qube" - " '{0}'?").format(netvm.name), - QtWidgets.QMessageBox.StandardButton.Yes | - QtWidgets.QMessageBox.StandardButton.Cancel) + self, + self.tr("Qube Start Confirmation"), + self.tr( + "
Can not change netvm of a running qube" + "to a halted Qube.
" + "Do you want to start the Qube" + " '{0}'?" + ).format(netvm.name), + QtWidgets.QMessageBox.StandardButton.Yes + | QtWidgets.QMessageBox.StandardButton.Cancel, + ) if reply == QtWidgets.QMessageBox.StandardButton.Yes: netvm.start() @@ -656,9 +708,10 @@ def __apply_basic_tab__(self): # include in backups try: - if self.include_in_backups.isEnabled() and\ - self.vm.include_in_backups != \ - self.include_in_backups.isChecked(): + if ( + self.include_in_backups.isEnabled() + and self.vm.include_in_backups != self.include_in_backups.isChecked() + ): self.vm.include_in_backups = self.include_in_backups.isChecked() except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -674,10 +727,11 @@ def __apply_basic_tab__(self): # shutdown-idle try: current_idle = self.vm.features.get(IDLE_SERVICE, False) - if self.idle_shutdown_checkbox.isEnabled() and \ - self.idle_shutdown_checkbox.isChecked() != current_idle: - self.vm.features[IDLE_SERVICE] = \ - self.idle_shutdown_checkbox.isChecked() + if ( + self.idle_shutdown_checkbox.isEnabled() + and self.idle_shutdown_checkbox.isChecked() != current_idle + ): + self.vm.features[IDLE_SERVICE] = self.idle_shutdown_checkbox.isChecked() except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -686,7 +740,7 @@ def __apply_basic_tab__(self): priv_size = self.max_priv_storage.value() if self.priv_img_size != priv_size: try: - self.vm.volumes['private'].resize(priv_size * 1024**2) + self.vm.volumes["private"].resize(priv_size * 1024**2) self.priv_img_size = priv_size except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -696,7 +750,7 @@ def __apply_basic_tab__(self): sys_size = self.root_resize.value() if self.root_img_size != sys_size: try: - self.vm.volumes['root'].resize(sys_size * 1024**2) + self.vm.volumes["root"].resize(sys_size * 1024**2) self.root_img_size = sys_size except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -716,24 +770,25 @@ def check_mem_changes(self): QtWidgets.QMessageBox.warning( self, self.tr("Warning!"), - self.tr("Max memory can not be less than initial memory.
" - "Setting max memory to equal initial memory.")) + self.tr( + "Max memory can not be less than initial memory.
" + "Setting max memory to equal initial memory." + ), + ) self.max_mem_size.setValue(self.init_mem.value()) # Linux specific limit: init memory must not be below # max_mem_size/10.79 in order to allow scaling up to # max_mem_size (or else "add_memory() failed: -17" problem) try: - is_linux = self.vm.features.check_with_template('os', None) == \ - 'Linux' + is_linux = self.vm.features.check_with_template("os", None) == "Linux" except qubesadmin.exc.QubesException: is_linux = False - if is_linux and \ - self.init_mem.value() * 10 < self.max_mem_size.value(): + if is_linux and 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': + if self.vm.klass != "TemplateVM": return current_netvm = self.netVM.currentData() @@ -748,10 +803,12 @@ def check_warn_templatenetvm(self): "Connecting a TemplateVM directly to a network is highly" " 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.")) + " to do so. Continue at your own risk." + ), + ) def check_warn_dispvmnetvm(self): - if not hasattr(self.vm, 'default_dispvm'): + if not hasattr(self.vm, "default_dispvm"): self.warn_netvm_dispvm.setVisible(False) return dispvm = self.default_dispvm.currentData() @@ -759,7 +816,7 @@ def check_warn_dispvmnetvm(self): if dispvm == qubesadmin.DEFAULT: try: - dispvm = self.vm.property_get_default('default_dispvm') + dispvm = self.vm.property_get_default("default_dispvm") except qubesadmin.exc.QubesDaemonAccessError: pass @@ -767,11 +824,11 @@ def check_warn_dispvmnetvm(self): self.warn_netvm_dispvm.setVisible(False) return - dispvm_netvm = getattr(dispvm, 'netvm', None) + dispvm_netvm = getattr(dispvm, "netvm", None) if own_netvm == qubesadmin.DEFAULT: try: - own_netvm = self.vm.property_get_default('netvm') + own_netvm = self.vm.property_get_default("netvm") except qubesadmin.exc.QubesDaemonAccessError: # no point in warning if we don't know what we're warning about self.warn_netvm_dispvm.setVisible(False) @@ -783,7 +840,7 @@ def check_warn_dispvmnetvm(self): self.warn_netvm_dispvm.setVisible(False) def check_warn_anonnetvm(self): - if 'anon-vm' not in self.vm.tags: + if "anon-vm" not in self.vm.tags: return current_net_vm = self.netVM.currentData() @@ -792,9 +849,9 @@ def check_warn_anonnetvm(self): return if current_net_vm == qubesadmin.DEFAULT: - current_net_vm = self.vm.property_get_default('netvm') + current_net_vm = self.vm.property_get_default("netvm") - if 'anon-gateway' not in current_net_vm.tags: + if "anon-gateway" not in current_net_vm.tags: QtWidgets.QMessageBox.warning( self, self.tr("Warning!"), @@ -803,16 +860,19 @@ def check_warn_anonnetvm(self): "to ensure privacy and anonymity. By changing the net qube " "to a gateway that does not provide anonymity, your IP " "address will be leaked on the Internet. Continue at your " - "own risk.") + "own risk." + ), ) def rename_vm(self): dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm) - running_dependencies = [vm.name for (vm, prop) in dependencies - if vm and prop == 'template' - and utils.is_running(vm, False)] + running_dependencies = [ + vm.name + for (vm, prop) in dependencies + if vm and prop == "template" and utils.is_running(vm, False) + ] if running_dependencies: QtWidgets.QMessageBox.warning( @@ -821,15 +881,17 @@ def rename_vm(self): self.tr( "The following qubes using this qube as a template are " "running:
{}.
In order to rename this qube, you " - "must first shut them down.").format( - ", ".join(running_dependencies))) + "must first shut them down." + ).format(", ".join(running_dependencies)), + ) return new_vm_name, ok = QtWidgets.QInputDialog.getText( self, - self.tr('Rename qube'), - self.tr('New name: (WARNING: all other changes will be discarded)'), - text=self.vm.name) + self.tr("Rename qube"), + self.tr("New name: (WARNING: all other changes will be discarded)"), + text=self.vm.name, + ) if ok: thread = RenameVMThread(self.vm, new_vm_name, dependencies) @@ -837,7 +899,8 @@ def rename_vm(self): thread.finished.connect(self.clear_threads) self.progress = QtWidgets.QProgressDialog( - self.tr("Renaming Qube..."), "", 0, 0) + self.tr("Renaming Qube..."), "", 0, 0 + ) self.progress.setCancelButton(None) self.progress.setModal(True) self.thread_closes = True @@ -854,20 +917,26 @@ def remove_vm(self): QtWidgets.QMessageBox.warning( self, self.tr("Qube cannot be removed!"), - self.tr("This qube cannot be removed. It is used as:" - "
{} If you want to remove this qube, " - "you should remove or change settings of each qube " - "or setting that uses it.").format(list_text)) + self.tr( + "This qube cannot be removed. It is used as:" + "
{} If you want to remove this qube, " + "you should remove or change settings of each qube " + "or setting that uses it." + ).format(list_text), + ) return answer, ok = QtWidgets.QInputDialog.getText( self, - self.tr('Delete qube'), - self.tr('Are you absolutely sure you want to delete this qube? ' - '
All qube settings and data will be irrevocably' - ' deleted.
If you are sure, please enter this ' - 'qube\'s name below.')) + self.tr("Delete qube"), + self.tr( + "Are you absolutely sure you want to delete this qube? " + "
All qube settings and data will be irrevocably" + " deleted.
If you are sure, please enter this " + "qube's name below." + ), + ) if ok and answer == self.vm.name: thread = common_threads.RemoveVMThread(self.vm) @@ -875,7 +944,8 @@ def remove_vm(self): self.threads_list.append(thread) self.progress = QtWidgets.QProgressDialog( - self.tr("Deleting Qube..."), "", 0, 0) + self.tr("Deleting Qube..."), "", 0, 0 + ) self.progress.setCancelButton(None) self.progress.setModal(True) self.thread_closes = True @@ -887,20 +957,20 @@ def remove_vm(self): QtWidgets.QMessageBox.warning( self, self.tr("Removal cancelled"), - self.tr("The qube will not be removed.")) + self.tr("The qube will not be removed."), + ) def clone_vm(self): with common_threads.busy_cursor(): - clone_window = clone_vm.CloneVMDlg( - self.qapp, self.qubesapp, src_vm=self.vm) + clone_window = clone_vm.CloneVMDlg(self.qapp, self.qubesapp, src_vm=self.vm) clone_window.exec() ######### advanced tab - def __init_advanced_tab__(self): + def __init_advanced_tab__(self): # pylint: disable=too-many-statements - vm_memory = getattr(self.vm, 'memory', None) - vm_maxmem = getattr(self.vm, 'maxmem', None) + vm_memory = getattr(self.vm, "memory", None) + vm_maxmem = getattr(self.vm, "maxmem", None) if vm_memory is None: self.init_mem.setEnabled(False) @@ -913,21 +983,20 @@ def __init_advanced_tab__(self): self.max_mem_size.setValue(int(vm_maxmem)) else: try: - maxmem = self.vm.property_get_default('maxmem') + maxmem = self.vm.property_get_default("maxmem") except qubesadmin.exc.QubesDaemonAccessError: maxmem = 0 if maxmem == 0: maxmem = vm_memory self.max_mem_size.setValue( - int(utils.get_feature( - self.vm, 'qubesmanager.maxmem_value', maxmem))) + int(utils.get_feature(self.vm, "qubesmanager.maxmem_value", maxmem)) + ) self.vcpus.setMinimum(1) - self.vcpus.setValue(int(getattr(self.vm, 'vcpus', 1))) + self.vcpus.setValue(int(getattr(self.vm, "vcpus", 1))) self.include_in_balancing.setEnabled(True) - self.include_in_balancing.setChecked( - int(getattr(self.vm, 'maxmem', 0)) > 0) + self.include_in_balancing.setChecked(int(getattr(self.vm, "maxmem", 0)) > 0) self.max_mem_size.setEnabled(self.include_in_balancing.isChecked()) # in case VM is HVM @@ -940,9 +1009,10 @@ def __init_advanced_tab__(self): allow_none=True, allow_default=True, holder=self.vm, - property_name='kernel') + property_name="kernel", + ) self.kernel.currentIndexChanged.connect(self.kernel_changed) - self.kernel_opts.setText(getattr(self.vm, 'kernelopts', '-')) + self.kernel_opts.setText(getattr(self.vm, "kernelopts", "-")) # load bootmode information from features if hasattr(self.vm, "appvm_default_bootmode"): self.appvm_default_bootmode_desc.setVisible(True) @@ -950,11 +1020,8 @@ def __init_advanced_tab__(self): else: self.appvm_default_bootmode_desc.setVisible(False) self.appvm_default_bootmode.setVisible(False) - self.bootmode_names, self.bootmode_ids \ - = self.collect_bootmode_data() - bootmode_widget_data = list(zip( - self.bootmode_names, self.bootmode_ids - )) + self.bootmode_names, self.bootmode_ids = self.collect_bootmode_data() + bootmode_widget_data = list(zip(self.bootmode_names, self.bootmode_ids)) bootmode_widget_data.sort() utils.initialize_widget_for_property( widget=self.bootmode, @@ -962,7 +1029,7 @@ def __init_advanced_tab__(self): property_name="bootmode", holder=self.vm, allow_default=True, - default_text_provider=get_default_bootmode_name + default_text_provider=get_default_bootmode_name, ) if hasattr(self.vm, "appvm_default_bootmode"): utils.initialize_widget_for_property( @@ -971,13 +1038,12 @@ def __init_advanced_tab__(self): property_name="appvm_default_bootmode", holder=self.vm, allow_default=True, - default_text_provider=get_default_bootmode_name + default_text_provider=get_default_bootmode_name, ) if self.vm.bootmode != "default": self.bootmode_kernel_opts.setText( self.vm.features.check_with_template( - f"boot-mode.kernelopts.{self.vm.bootmode}", - "" + f"boot-mode.kernelopts.{self.vm.bootmode}", "" ) ) else: @@ -990,7 +1056,7 @@ def __init_advanced_tab__(self): self.kernel.setEnabled(False) self.kernel_groupbox.setVisible(False) - if not hasattr(self.vm, 'default_dispvm'): + if not hasattr(self.vm, "default_dispvm"): self.other_groupbox.setVisible(False) else: try: @@ -998,16 +1064,17 @@ def __init_advanced_tab__(self): utils.initialize_widget_with_vms( widget=self.default_dispvm, qubes_app=self.qubesapp, - filter_function=(lambda vm: - getattr( - vm, 'template_for_dispvms', False)), + filter_function=( + lambda vm: getattr(vm, "template_for_dispvms", False) + ), allow_none=True, allow_default=True, holder=self.vm, - property_name='default_dispvm' + property_name="default_dispvm", ) self.default_dispvm.currentIndexChanged.connect( - self.check_warn_dispvmnetvm) + self.check_warn_dispvmnetvm + ) except qubesadmin.exc.QubesDaemonAccessError: self.other_groupbox.setVisible(False) @@ -1015,9 +1082,10 @@ def __init_advanced_tab__(self): self.update_virt_mode_list() try: - windows_running = \ - self.vm.features.check_with_template('os', None) == 'Windows' \ + windows_running = ( + self.vm.features.check_with_template("os", None) == "Windows" and self.vm.is_running() + ) except qubesadmin.exc.QubesException: windows_running = False @@ -1028,22 +1096,43 @@ def __init_advanced_tab__(self): self.seamless_off_button.clicked.connect(self.disable_seamless) self.dvm_template_checkbox.setChecked( - getattr(self.vm, 'template_for_dispvms', False)) + getattr(self.vm, "template_for_dispvms", False) + ) - if not hasattr(self.vm, 'template_for_dispvms'): + if not hasattr(self.vm, "template_for_dispvms"): self.dvm_template_checkbox.setEnabled(False) + self.preload_dispvm.setMinimum(0) + self.preload_dispvm.setMaximum(50) + if ( + self.vm.name == getattr(self.vm.app, "default_dispvm", None) + and self.vm.app.domains["dom0"].features.get("preload-dispvm-max", None) + is not None + ): + self.warn_default_dispvm_preload_label.setVisible(True) + else: + self.warn_default_dispvm_preload_label.setVisible(False) + self.preload_dispvm.setEnabled(self.dvm_template_checkbox.isChecked()) + if self.preload_dispvm.isEnabled(): + vm_preload_dispvm = int(self.vm.features.get("preload-dispvm-max") or 0) + else: + vm_preload_dispvm = 0 + self.preload_dispvm.setValue(vm_preload_dispvm) + self.provides_network_checkbox.setChecked( - getattr(self.vm, 'provides_network', False)) + getattr(self.vm, "provides_network", False) + ) if self.provides_network_checkbox.isChecked(): - domains_using = [vm.name for vm - in getattr(self.vm, 'connected_vms', [])] + domains_using = [vm.name for vm in getattr(self.vm, "connected_vms", [])] if domains_using: self.provides_network_checkbox.setEnabled(False) - self.provides_network_checkbox.setToolTip(self.tr( - "Cannot change this setting while this qube is used as a " - "NetVM by the following qubes:\n") + - "\n".join(domains_using)) + self.provides_network_checkbox.setToolTip( + self.tr( + "Cannot change this setting while this qube is used as " + "a NetVM by the following qubes:\n" + ) + + "\n".join(domains_using) + ) try: self.run_in_debug_mode.setChecked(self.vm.debug) @@ -1060,29 +1149,27 @@ def __init_advanced_tab__(self): self.prohibit_start_rationale_edited ) self.prohibit_start_checkbox.setChecked(bool(rationale)) - self.prohibit_start_checkbox.clicked.connect( - self.prohibit_start_checked - ) + self.prohibit_start_checkbox.clicked.connect(self.prohibit_start_checked) utils.initialize_widget( widget=self.allow_fullscreen, choices=[ - ('(use system default)', None), - ('allow', True), - ('disallow', False) + ("(use system default)", None), + ("allow", True), + ("disallow", False), ], - selected_value=utils.get_boolean_feature(self.vm, - 'gui-allow-fullscreen')) + selected_value=utils.get_boolean_feature(self.vm, "gui-allow-fullscreen"), + ) self.allow_fullscreen_initial = self.allow_fullscreen.currentIndex() utils.initialize_widget( widget=self.allow_utf8, choices=[ - ('(use system default)', None), - ('allow', True), - ('disallow', False) + ("(use system default)", None), + ("allow", True), + ("disallow", False), ], - selected_value=utils.get_boolean_feature(self.vm, - 'gui-allow-utf8-titles')) + selected_value=utils.get_boolean_feature(self.vm, "gui-allow-utf8-titles"), + ) self.allow_utf8_initial = self.allow_utf8.currentIndex() def prohibit_start_checked(self, status): @@ -1097,35 +1184,41 @@ def prohibit_start_rationale_edited(self, text): def enable_seamless(self): try: - self.vm.run_service_for_stdio("qubes.SetGuiMode", input=b'SEAMLESS') - except (qubesadmin.exc.QubesException, - subprocess.CalledProcessError) as ex: + self.vm.run_service_for_stdio("qubes.SetGuiMode", input=b"SEAMLESS") + except ( + qubesadmin.exc.QubesException, + subprocess.CalledProcessError, + ) as ex: QtWidgets.QMessageBox.warning( self, self.tr("Failed to set seamless mode"), - self.tr("Error occurred: {}".format(str(ex)))) + self.tr("Error occurred: {}".format(str(ex))), + ) def disable_seamless(self): try: - self.vm.run_service_for_stdio("qubes.SetGuiMode", - input=b'FULLSCREEN') - except (qubesadmin.exc.QubesException, - subprocess.CalledProcessError) as ex: + self.vm.run_service_for_stdio("qubes.SetGuiMode", input=b"FULLSCREEN") + except ( + qubesadmin.exc.QubesException, + subprocess.CalledProcessError, + ) as ex: QtWidgets.QMessageBox.warning( self, self.tr("Failed to set fullscreen mode"), - self.tr("Error occurred: {}".format(str(ex)))) + self.tr("Error occurred: {}".format(str(ex))), + ) def __apply_advanced_tab__(self): msg = [] # mem/cpu try: - if self.init_mem.isEnabled() and \ - self.init_mem.value() != int(self.vm.memory): + if self.init_mem.isEnabled() and self.init_mem.value() != int( + self.vm.memory + ): self.vm.memory = self.init_mem.value() - curr_maxmem = int(getattr(self.vm, 'maxmem', 0)) + curr_maxmem = int(getattr(self.vm, "maxmem", 0)) if not self.include_in_balancing.isChecked(): maxmem = 0 @@ -1134,14 +1227,24 @@ def __apply_advanced_tab__(self): if maxmem != curr_maxmem: if curr_maxmem > 0: - self.vm.features['qubesmanager.maxmem_value'] = curr_maxmem + self.vm.features["qubesmanager.maxmem_value"] = curr_maxmem if maxmem == 0 or self.max_mem_size.isEnabled(): self.vm.maxmem = maxmem - if self.vcpus.isEnabled() and \ - self.vcpus.value() != int(self.vm.vcpus): + if self.vcpus.isEnabled() and self.vcpus.value() != int(self.vm.vcpus): self.vm.vcpus = self.vcpus.value() + if ( + self.dvm_template_checkbox.isChecked() + and self.preload_dispvm.isEnabled() + ): + curr_preload_dispvm = ( + int(self.vm.features.get("preload-dispvm-max") or 0) + ) + preload_dispvm = self.preload_dispvm.value() + if preload_dispvm != curr_preload_dispvm: + self.vm.features["preload-dispvm-max"] = preload_dispvm + except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -1153,10 +1256,10 @@ def __apply_advanced_tab__(self): if utils.did_widget_selection_change(self.bootmode): self.vm.bootmode = self.bootmode.currentData() if hasattr(self.vm, "appvm_default_bootmode"): - if utils.did_widget_selection_change( - self.appvm_default_bootmode): - self.vm.appvm_default_bootmode \ - = self.appvm_default_bootmode.currentData() + if utils.did_widget_selection_change(self.appvm_default_bootmode): + self.vm.appvm_default_bootmode = ( + self.appvm_default_bootmode.currentData() + ) except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -1172,11 +1275,12 @@ def __apply_advanced_tab__(self): except Exception as ex: # pylint: disable=broad-except msg.append(str(ex)) - if getattr(self.vm, "template_for_dispvms", False) != \ - self.dvm_template_checkbox.isChecked(): + if ( + getattr(self.vm, "template_for_dispvms", False) + != self.dvm_template_checkbox.isChecked() + ): try: - self.vm.template_for_dispvms = \ - self.dvm_template_checkbox.isChecked() + self.vm.template_for_dispvms = self.dvm_template_checkbox.isChecked() if self.dvm_template_checkbox.isChecked(): self.vm.features["appmenus-dispvm"] = True else: @@ -1184,11 +1288,12 @@ def __apply_advanced_tab__(self): except Exception as ex: # pylint: disable=broad-except msg.append(str(ex)) - if getattr(self.vm, 'provides_network', False) != \ - self.provides_network_checkbox.isChecked(): + if ( + getattr(self.vm, "provides_network", False) + != self.provides_network_checkbox.isChecked() + ): try: - self.vm.provides_network = \ - self.provides_network_checkbox.isChecked() + self.vm.provides_network = self.provides_network_checkbox.isChecked() except Exception as ex: # pylint: disable=broad-except msg.append(str(ex)) @@ -1202,8 +1307,7 @@ def __apply_advanced_tab__(self): rationale = self.vm.features.get("prohibit-start", "") if (self.prohibit_start_checkbox.isChecked() != bool(rationale)) or ( - bool(rationale) - and self.prohibit_start_rationale.text() != rationale + bool(rationale) and self.prohibit_start_rationale.text() != rationale ): rationale = self.prohibit_start_rationale.text() if bool(rationale) and self.prohibit_start_checkbox.isChecked(): @@ -1211,25 +1315,25 @@ def __apply_advanced_tab__(self): else: del self.vm.features["prohibit-start"] - if self.allow_fullscreen_initial !=\ - self.allow_fullscreen.currentIndex(): + if self.allow_fullscreen_initial != self.allow_fullscreen.currentIndex(): try: if self.allow_fullscreen.currentData() is None: - del self.vm.features['gui-allow-fullscreen'] + del self.vm.features["gui-allow-fullscreen"] else: - self.vm.features['gui-allow-fullscreen'] = \ + self.vm.features["gui-allow-fullscreen"] = ( self.allow_fullscreen.currentData() + ) except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) - if self.allow_utf8_initial !=\ - self.allow_utf8.currentIndex(): + if self.allow_utf8_initial != self.allow_utf8.currentIndex(): try: if self.allow_utf8.currentData() is None: - del self.vm.features['gui-allow-utf8-titles'] + del self.vm.features["gui-allow-utf8-titles"] else: - self.vm.features['gui-allow-utf8-titles'] = \ + self.vm.features["gui-allow-utf8-titles"] = ( self.allow_utf8.currentData() + ) except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) @@ -1247,14 +1351,16 @@ def include_in_balancing_changed(self, state): if self.include_in_balancing.isChecked(): self.check_mem_changes() + def dvm_template_checkbox_changed(self, state): # pylint: disable=unused-argument + self.preload_dispvm.setEnabled(self.dvm_template_checkbox.isChecked()) + def boot_from_cdrom_button_pressed(self): boot_dialog = bootfromdevice.VMBootFromDeviceWindow( - vm=self.vm.name, qapp=self.qapp, qubesapp=self.qubesapp, - parent=self) + vm=self.vm.name, qapp=self.qapp, qubesapp=self.qubesapp, parent=self + ) if boot_dialog.exec(): self.save_and_apply() - qvm_start.main( - ['--cdrom', boot_dialog.cdrom_location, self.vm.name]) + qvm_start.main(["--cdrom", boot_dialog.cdrom_location, self.vm.name]) def virt_mode_changed(self, new_idx): # pylint: disable=unused-argument self.update_pv_warning() @@ -1262,22 +1368,22 @@ def virt_mode_changed(self, new_idx): # pylint: disable=unused-argument self.update_pvh_kernel_ver_warning() def update_pv_warning(self): - if self.virt_mode.currentData() == 'pv': + if self.virt_mode.currentData() == "pv": self.pv_warning.show() else: self.pv_warning.hide() def update_virt_mode_list(self): - choices = [('HVM', 'hvm'), - ('PV', 'pv')] + choices = [("HVM", "hvm"), ("PV", "pv")] if hasattr(self, "dev_list"): devs_attached = self.dev_list.selected_list.count() != 0 else: try: - devs_attached = bool(list( - self.vm.devices['pci'].get_assigned_devices( - required_only=True)) + devs_attached = bool( + list( + self.vm.devices["pci"].get_assigned_devices(required_only=True) + ) ) except qubesadmin.exc.QubesException: devs_attached = False @@ -1285,7 +1391,7 @@ def update_virt_mode_list(self): if devs_attached: self.pvh_mode_hidden.show() else: - choices.insert(0, ('PVH', 'pvh')) + choices.insert(0, ("PVH", "pvh")) self.pvh_mode_hidden.hide() old_mode = self.virt_mode.currentData() @@ -1295,20 +1401,25 @@ def update_virt_mode_list(self): # due to how virtualization mode has uniquely different displayed and # actual name of the default value, I will add it manually try: - choices.insert(0, ( - "default ({})".format( - self.vm.property_get_default('virt_mode').upper()), - qubesadmin.DEFAULT)) + choices.insert( + 0, + ( + "default ({})".format( + self.vm.property_get_default("virt_mode").upper() + ), + qubesadmin.DEFAULT, + ), + ) except qubesadmin.exc.QubesException: - choices.insert(0, - ("default ({SYSTEM DEFAULT})", qubesadmin.DEFAULT)) + choices.insert(0, ("default ({SYSTEM DEFAULT})", qubesadmin.DEFAULT)) try: utils.initialize_widget_for_property( widget=self.virt_mode, choices=choices, holder=self.vm, - property_name='virt_mode') + property_name="virt_mode", + ) except qubesadmin.exc.QubesDaemonAccessError: self.virt_mode.setEnabled(False) @@ -1321,7 +1432,7 @@ def update_virt_mode_list(self): self.update_pvh_kernel_ver_warning() def update_pvh_kernel_ver_warning(self): - if self.virt_mode.currentData() != 'pvh': + if self.virt_mode.currentData() != "pvh": self.pvh_kernel_version_warning.hide() return @@ -1345,9 +1456,9 @@ def pvh_kernel_version_ok(self, name): return False if name is qubesadmin.DEFAULT: - name = getattr(self.vm.app, 'default_kernel', None) + name = getattr(self.vm.app, "default_kernel", None) - m = re.search(r'(\d+)\.(\d+)', name) + m = re.search(r"(\d+)\.(\d+)", name) if m is None: return False @@ -1359,8 +1470,7 @@ def update_bootmode_kernel_opts(self): if isinstance(active_bootmode, str): self.bootmode_kernel_opts.setText( self.vm.features.check_with_template( - f"boot-mode.kernelopts.{active_bootmode}", - "" + f"boot-mode.kernelopts.{active_bootmode}", "" ) ) else: @@ -1370,8 +1480,7 @@ def update_bootmode_kernel_opts(self): else: self.bootmode_kernel_opts.setText( self.vm.features.check_with_template( - f"boot-mode.kernelopts.{default_bootmode}", - "" + f"boot-mode.kernelopts.{default_bootmode}", "" ) ) @@ -1383,7 +1492,8 @@ def __init_devices_tab__(self): self.dev_list = multiselectwidget.MultiSelectWidget(self) self.dev_list.change_labels( available="Available devices", - selected="Devices always connected to this qube") + selected="Devices always connected to this qube", + ) self.dev_list.add_all_button.setVisible(False) self.devices_layout.addWidget(self.dev_list) @@ -1397,10 +1507,11 @@ def __init_devices_tab__(self): != device_protocol.DeviceCategory.PCI_Bridge ) attached = list( - self.vm.devices['pci'].get_assigned_devices(required_only=True)) + self.vm.devices["pci"].get_assigned_devices(required_only=True) + ) except qubesadmin.exc.QubesException: # no permission to access devices - self.tabWidget.setTabEnabled(self.tabs_indices['devices'], False) + self.tabWidget.setTabEnabled(self.tabs_indices["devices"], False) return # pylint: disable=too-few-public-methods @@ -1413,7 +1524,7 @@ def __init__(self, dev, unknown=False, parent=None): + dev.description ) if unknown: - name += ' (unknown)' + name += " (unknown)" self.setText(name) self.dev = dev @@ -1425,10 +1536,13 @@ def __init__(self, dev, unknown=False, parent=None): for ass in attached: if not any(ass.matches(dev) for dev in dom0_devs): self.dev_list.selected_list.addItem( - DevListWidgetItem(ass.device, unknown=True)) + DevListWidgetItem(ass.device, unknown=True) + ) - if self.dev_list.selected_list.count() > 0\ - and self.include_in_balancing.isChecked(): + if ( + self.dev_list.selected_list.count() > 0 + and self.include_in_balancing.isChecked() + ): self.dmm_warning_adv.show() self.dmm_warning_dev.show() else: @@ -1450,45 +1564,48 @@ def __init__(self, dev, unknown=False, parent=None): def __apply_devices_tab__(self): msg = [] - if not self.tabWidget.isTabEnabled(self.tabs_indices['devices']): + if not self.tabWidget.isTabEnabled(self.tabs_indices["devices"]): return msg try: old_devs = list( - self.vm.devices['pci'].get_assigned_devices(required_only=True)) + self.vm.devices["pci"].get_assigned_devices(required_only=True) + ) - new_devs = [self.dev_list.selected_list.item(i).dev - for i in range(self.dev_list.selected_list.count())] + new_devs = [ + self.dev_list.selected_list.item(i).dev + for i in range(self.dev_list.selected_list.count()) + ] for dev in new_devs: - old_assignments = [old for old in old_devs - if old.matches(dev)] + old_assignments = [old for old in old_devs if old.matches(dev)] if not old_assignments: options = {} if dev.port_id in self.new_strict_reset_list: - options['no-strict-reset'] = True + options["no-strict-reset"] = True ass = device_protocol.DeviceAssignment.new( - backend_domain=self.vm.app.domains['dom0'], + backend_domain=self.vm.app.domains["dom0"], port_id=dev.port_id, - devclass='pci', - mode='required', + devclass="pci", + mode="required", options=options, ) - self.vm.devices['pci'].assign(ass) - elif (dev.port_id in self.current_strict_reset_list) != \ - (dev.port_id in self.new_strict_reset_list): + self.vm.devices["pci"].assign(ass) + elif (dev.port_id in self.current_strict_reset_list) != ( + dev.port_id in self.new_strict_reset_list + ): current_assignment = old_assignments[0] - self.vm.devices['pci'].unassign(current_assignment) + self.vm.devices["pci"].unassign(current_assignment) - current_assignment.options['no-strict-reset'] = \ + current_assignment.options["no-strict-reset"] = ( dev.port_id in self.new_strict_reset_list + ) - self.vm.devices['pci'].assign(current_assignment) + self.vm.devices["pci"].assign(current_assignment) - for ass in self.vm.devices['pci'].get_assigned_devices( - required_only=True): + for ass in self.vm.devices["pci"].get_assigned_devices(required_only=True): if ass.device not in new_devs: - self.vm.devices['pci'].unassign(ass) + self.vm.devices["pci"].unassign(ass) except qubesadmin.exc.QubesException as ex: if utils.is_debug(): @@ -1511,7 +1628,7 @@ def devices_selection_changed(self): def update_pvh_dont_support_devs(self): # this is the easiest way to check for both normal 'PVH' and # default (PVH) options - if 'PVH' in self.virt_mode.currentText().upper(): + if "PVH" in self.virt_mode.currentText().upper(): self.dev_list.setEnabled(False) self.pvh_dont_support_devs.setVisible(True) else: @@ -1519,17 +1636,23 @@ def update_pvh_dont_support_devs(self): self.pvh_dont_support_devs.setVisible(False) def define_strict_reset_devices(self): - for assignment in self.vm.devices['pci'].get_assigned_devices( - required_only=True): - if assignment.options.get('no-strict-reset', False): + for assignment in self.vm.devices["pci"].get_assigned_devices( + required_only=True + ): + if assignment.options.get("no-strict-reset", False): self.current_strict_reset_list.append( - assignment.port_id.replace('_', ':')) + assignment.port_id.replace("_", ":") + ) self.new_strict_reset_list = self.current_strict_reset_list.copy() def strict_reset_button_pressed(self): device_list_window = device_list.PCIDeviceListWindow( - vm=self.vm, qapp=self.qapp, dev_list=self.dev_list, - no_strict_reset_list=self.new_strict_reset_list, parent=self) + vm=self.vm, + qapp=self.qapp, + dev_list=self.dev_list, + no_strict_reset_list=self.new_strict_reset_list, + parent=self, + ) device_list_window.exec() ######## applications tab @@ -1537,7 +1660,7 @@ def strict_reset_button_pressed(self): def refresh_apps_button_pressed(self): self.refresh_apps_button.setEnabled(False) - self.refresh_apps_button.setText(self.tr('Refresh in progress...')) + self.refresh_apps_button.setText(self.tr("Refresh in progress...")) thread = RefreshAppsVMThread(self.vm, self.refresh_apps_button) thread.finished.connect(self.clear_threads) @@ -1548,15 +1671,17 @@ def refresh_apps_button_pressed(self): def refresh_finished(self): self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list) self.refresh_apps_button.setEnabled(True) - self.refresh_apps_button.setText(self.tr('Refresh applications')) + self.refresh_apps_button.setText(self.tr("Refresh applications")) def template_apps_change(self): if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]): self.app_list_manager.fill_apps_list( - template=self.template_name.currentData()) + template=self.template_name.currentData() + ) # add a label to show self.warn_template_missing_apps.setVisible( - self.app_list_manager.has_missing) + self.app_list_manager.has_missing + ) ######## services tab @@ -1564,15 +1689,18 @@ def __init_services_tab__(self): self.new_srv_dict = {} try: for feature in self.vm.features: - if not feature.startswith(SERVICE_PREFIX) or \ - feature in INTERNAL_SERVICE_FEATURES: + if ( + not feature.startswith(SERVICE_PREFIX) + or feature in INTERNAL_SERVICE_FEATURES + ): continue - service = feature[len(SERVICE_PREFIX):] + service = feature[len(SERVICE_PREFIX) :] item = QtWidgets.QListWidgetItem(service) item.setCheckState( ui_settingsdlg.QtCore.Qt.CheckState.Checked if self.vm.features[feature] - else ui_settingsdlg.QtCore.Qt.CheckState.Unchecked) + else ui_settingsdlg.QtCore.Qt.CheckState.Unchecked + ) self.services_list.addItem(item) self.new_srv_dict[service] = self.vm.features[feature] except qubesadmin.exc.QubesDaemonAccessError: @@ -1584,32 +1712,33 @@ def __init_services_tab__(self): supported_services = set() for feature in self.vm.features: - if feature.startswith(SUPPORTED_SERVICE_PREFIX) and \ - feature not in INTERNAL_SUPPORTED_FEATURES: - supported_services.add(feature[len(SUPPORTED_SERVICE_PREFIX):]) + if ( + feature.startswith(SUPPORTED_SERVICE_PREFIX) + and feature not in INTERNAL_SUPPORTED_FEATURES + ): + supported_services.add(feature[len(SUPPORTED_SERVICE_PREFIX) :]) if getattr(self.vm, "template", None): try: for feature in self.vm.template.features: - if feature.startswith(SUPPORTED_SERVICE_PREFIX) and \ - feature not in INTERNAL_SUPPORTED_FEATURES: - supported_services.add( - feature[len(SUPPORTED_SERVICE_PREFIX):]) + if ( + feature.startswith(SUPPORTED_SERVICE_PREFIX) + and feature not in INTERNAL_SUPPORTED_FEATURES + ): + supported_services.add(feature[len(SUPPORTED_SERVICE_PREFIX) :]) except qubesadmin.exc.QubesDaemonAccessError: pass for service in sorted(supported_services): self.service_line_edit.addItem(service) - self.service_line_edit.addItem(self.tr('(custom...)')) + self.service_line_edit.addItem(self.tr("(custom...)")) self.service_line_edit.setEditText("") def __init_notes_tab__(self): try: self.notes.setPlainText(self.vm.get_notes()) except qubesadmin.exc.QubesException: - self.notes.setPlainText( - "Could not retrieve notes for this qube " - ) + self.notes.setPlainText("Could not retrieve notes for this qube ") self.notes.setEnabled(False) def __apply_notes_tab__(self): @@ -1624,21 +1753,20 @@ def __add_service__(self): srv = str(self.service_line_edit.currentText()).strip() if srv != "": - if self.service_line_edit.currentIndex() == \ - len(self.service_line_edit) - 1: + if self.service_line_edit.currentIndex() == len(self.service_line_edit) - 1: (custom_name, ok) = QtWidgets.QInputDialog.getText( - self, self.tr("Custom service name"), - self.tr( - "Name of the service:")) + self, + self.tr("Custom service name"), + self.tr("Name of the service:"), + ) if ok: srv = custom_name.strip() else: return if srv in self.new_srv_dict: QtWidgets.QMessageBox.information( - self, - '', - self.tr('Service already on the list!')) + self, "", self.tr("Service already on the list!") + ) return item = QtWidgets.QListWidgetItem(srv) item.setCheckState(ui_settingsdlg.QtCore.Qt.CheckState.Checked) @@ -1658,27 +1786,29 @@ def __remove_service__(self): def __apply_services_tab__(self): msg = [] - if not self.tabWidget.isTabEnabled(self.tabs_indices['services']): + if not self.tabWidget.isTabEnabled(self.tabs_indices["services"]): return msg try: for i in range(self.services_list.count()): item = self.services_list.item(i) - self.new_srv_dict[str(item.text())] = \ - (item.checkState() == - QtCore.Qt.CheckState.Checked) + self.new_srv_dict[str(item.text())] = ( + item.checkState() == QtCore.Qt.CheckState.Checked + ) for service, v in self.new_srv_dict.items(): feature = SERVICE_PREFIX + service - val = '1' if v else '' + val = "1" if v else "" if val != self.vm.features.get(feature, object()): self.vm.features[feature] = val for feature in self.vm.features: - if not feature.startswith(SERVICE_PREFIX) or \ - feature in INTERNAL_SERVICE_FEATURES: + if ( + not feature.startswith(SERVICE_PREFIX) + or feature in INTERNAL_SERVICE_FEATURES + ): continue - service = feature[len(SERVICE_PREFIX):] + service = feature[len(SERVICE_PREFIX) :] if service not in self.new_srv_dict: del self.vm.features[feature] except qubesadmin.exc.QubesException as ex: @@ -1691,14 +1821,17 @@ def set_fw_model(self, model): self.fw_model = model self.rulesTreeView.setModel(model) self.rulesTreeView.header().setSectionResizeMode( - QtWidgets.QHeaderView.ResizeMode.ResizeToContents) + QtWidgets.QHeaderView.ResizeMode.ResizeToContents + ) self.rulesTreeView.header().setSectionResizeMode( - 0, QtWidgets.QHeaderView.ResizeMode.Stretch) + 0, QtWidgets.QHeaderView.ResizeMode.Stretch + ) self.set_allow(model.allow) if model.temp_full_access_expire_time: self.temp_full_access.setChecked(True) - expire_time = model.temp_full_access_expire_time - \ - datetime.datetime.now().timestamp() + expire_time = ( + model.temp_full_access_expire_time - datetime.datetime.now().timestamp() + ) self.temp_full_access_time.setValue(int(expire_time / 60)) def disable_all_fw_conf(self): @@ -1718,18 +1851,12 @@ def set_allow(self, allow): self.policy_changed() def policy_changed(self): - self.rulesTreeView.setEnabled( - self.policy_deny_radio_button.isChecked()) - self.new_rule_button.setEnabled( - self.policy_deny_radio_button.isChecked()) - self.edit_rule_button.setEnabled( - self.policy_deny_radio_button.isChecked()) - self.delete_rule_button.setEnabled( - self.policy_deny_radio_button.isChecked()) - self.firewal_rules_label.setEnabled( - self.policy_deny_radio_button.isChecked()) - self.tempFullAccessWidget.setEnabled( - self.policy_deny_radio_button.isChecked()) + self.rulesTreeView.setEnabled(self.policy_deny_radio_button.isChecked()) + self.new_rule_button.setEnabled(self.policy_deny_radio_button.isChecked()) + self.edit_rule_button.setEnabled(self.policy_deny_radio_button.isChecked()) + self.delete_rule_button.setEnabled(self.policy_deny_radio_button.isChecked()) + self.firewal_rules_label.setEnabled(self.policy_deny_radio_button.isChecked()) + self.tempFullAccessWidget.setEnabled(self.policy_deny_radio_button.isChecked()) def new_rule_button_pressed(self): dialog = firewall.NewFwRuleDlg(parent=self) @@ -1747,34 +1874,39 @@ def edit_rule_button_pressed(self): self.fw_model.run_rule_dialog(dialog, row) def delete_rule_button_pressed(self): - for i in {index.row() for index - in self.rulesTreeView.selectedIndexes()}: + for i in {index.row() for index in self.rulesTreeView.selectedIndexes()}: self.fw_model.remove_child(i) parser = QubesArgumentParser(vmname_nargs=1) -parser.add_argument('--tab', metavar='TAB', - action='store', - choices=VMSettingsWindow.tabs_indices.keys()) +parser.add_argument( + "--tab", + metavar="TAB", + action="store", + choices=VMSettingsWindow.tabs_indices.keys(), +) parser.set_defaults( - tab='basic', + tab="basic", ) def main(args=None): args = parser.parse_args(args) vm = args.domains.pop() - if vm.klass == 'AdminVM': - print("This tool cannot be used to change properties of an " - f"AdminVM ({vm.name}).") - print("You can use command-line tools such as qvm-prefs " - "and qvm-features to change properties of an AdminVM") + if vm.klass == "AdminVM": + print( + "This tool cannot be used to change properties of an " + f"AdminVM ({vm.name})." + ) + print( + "You can use command-line tools such as qvm-prefs " + "and qvm-features to change properties of an AdminVM" + ) return 1 - utils.run_synchronous(functools.partial(VMSettingsWindow, vm.name, - args.tab)) + utils.run_synchronous(functools.partial(VMSettingsWindow, vm.name, args.tab)) if __name__ == "__main__": diff --git a/qubesmanager/tests/test_vm_settings.py b/qubesmanager/tests/test_vm_settings.py index 846fc1f3..37d6bf4a 100644 --- a/qubesmanager/tests/test_vm_settings.py +++ b/qubesmanager/tests/test_vm_settings.py @@ -31,8 +31,14 @@ PAGES = ["basic", "advanced", "firewall", "devices", "applications", "services"] # just vms -TEST_VMS = ["test-red", "test-blue", "sys-net", - "test-standalone", "test-old", "test-vm-set"] +TEST_VMS = [ + "test-red", + "test-blue", + "sys-net", + "test-standalone", + "test-old", + "test-vm-set", +] # with a template ALL_TEST_VMS = TEST_VMS + ["fedora-35"] @@ -46,140 +52,192 @@ def mock_subprocess_complex(command): vm_name = command[-1] - if command[1] == '--get-available': - if vm_name == 'test-vm-set': - return (b'test.desktop|Test App||\n' - b'test2.desktop|Test2 App| test2|\n' - b'test3.desktop|Test3 App||\n' - b'myvm.desktop|My VM app||\n') - elif vm_name == 'fedora-36': - return b'tpl.desktop|Template App||\n' + if command[1] == "--get-available": + if vm_name == "test-vm-set": + return ( + b"test.desktop|Test App||\n" + b"test2.desktop|Test2 App| test2|\n" + b"test3.desktop|Test3 App||\n" + b"myvm.desktop|My VM app||\n" + ) + elif vm_name == "fedora-36": + return b"tpl.desktop|Template App||\n" else: - return (b'test.desktop|Test App||\n' - b'test2.desktop|Test2 App| test2|\n' - b'test3.desktop|Test3 App||\n') - elif command[1] == '--get-whitelist': - if vm_name == 'test-vm-set': - return b'test.desktop\nmissing.desktop' + return ( + b"test.desktop|Test App||\n" + b"test2.desktop|Test2 App| test2|\n" + b"test3.desktop|Test3 App||\n" + ) + elif command[1] == "--get-whitelist": + if vm_name == "test-vm-set": + return b"test.desktop\nmissing.desktop" else: - return b'' - return b'' + return b"" + return b"" @pytest.fixture -def settings_fixture(request, qapp, test_qubes_app) -> Tuple[ - vm_settings.VMSettingsWindow, str, str]: +def settings_fixture( + request, qapp, test_qubes_app +) -> Tuple[vm_settings.VMSettingsWindow, str, str]: # add a frankenqube with worst possible settings fw_rules = [ {"action": "accept", "dsthost": "qubes-os.org"}, - {"action": "accept", "specialtarget":"dns"}, + {"action": "accept", "specialtarget": "dns"}, {"action": "accept", "proto": "icmp"}, - {"action": "drop"} + {"action": "drop"}, ] - test_qubes_app._qubes['test-vm-set'] = MockQube( - name="test-vm-set", qapp=test_qubes_app, label="green", - template='fedora-36', include_in_backups=False, autostart=True, - kernel='1.1', virt_mode='hvm', provides_network=True, - usage=0.5, template_for_dispvms=True, features={ - 'gui-allow-fullscreen': '1', 'gui-allow-utf8-titles': '1', - 'supported-service.qubes-u2f-proxy': '1', - 'service.qubes-u2f-proxy': '1', 'supported-service.clocksync': '1'}, - firewall_rules=fw_rules + test_qubes_app._qubes["test-vm-set"] = MockQube( + name="test-vm-set", + qapp=test_qubes_app, + label="green", + template="fedora-36", + include_in_backups=False, + autostart=True, + kernel="1.1", + virt_mode="hvm", + provides_network=True, + usage=0.5, + template_for_dispvms=True, + features={ + "gui-allow-fullscreen": "1", + "gui-allow-utf8-titles": "1", + "supported-service.qubes-u2f-proxy": "1", + "service.qubes-u2f-proxy": "1", + "supported-service.clocksync": "1", + }, + firewall_rules=fw_rules, ) # and add a qube that has a connected pci device - test_qubes_app._qubes['test-pci-dev'] = MockQube( - name="test-pci-dev", qapp=test_qubes_app, label="green", - virt_mode='hvm' + test_qubes_app._qubes["test-pci-dev"] = MockQube( + name="test-pci-dev", qapp=test_qubes_app, label="green", virt_mode="hvm" ) - test_qubes_app._qubes['sys-whonix'] = MockQube( - name="sys-whonix", qapp=test_qubes_app, tags=['anon-gateway']) + test_qubes_app._qubes["sys-whonix"] = MockQube( + name="sys-whonix", qapp=test_qubes_app, tags=["anon-gateway"] + ) - test_qubes_app._qubes['anon-whonix'] = MockQube( - name="anon-whonix", qapp=test_qubes_app, tags=['anon-vm']) + test_qubes_app._qubes["anon-whonix"] = MockQube( + name="anon-whonix", qapp=test_qubes_app, tags=["anon-vm"] + ) test_qubes_app._devices.append( MockDevice( - test_qubes_app, dev_class='pci', - device_id='0x8008:0x1234::p040000', backend_vm='dom0', - port='00_04.3', product='Important Device', vendor='Test', - assigned=[('test-pci-dev', 'required', None)])) + test_qubes_app, + dev_class="pci", + device_id="0x8008:0x1234::p040000", + backend_vm="dom0", + port="00_04.3", + product="Important Device", + vendor="Test", + assigned=[("test-pci-dev", "required", None)], + ) + ) # add a TemplateVM with some boot modes - test_qubes_app._qubes['fedora-36-bootmodes'] = MockQube( - name="fedora-36-bootmodes", qapp=test_qubes_app, klass="TemplateVM", - netvm="", features={'boot-mode.kernelopts.mode1': 'mode1kern', - 'boot-mode.name.mode1': 'Mode One', - 'boot-mode.kernelopts.mode2': 'mode2kern1 mode2kern2', - 'boot-mode.active': 'mode1', 'boot-mode.appvm-default': 'mode2'} + test_qubes_app._qubes["fedora-36-bootmodes"] = MockQube( + name="fedora-36-bootmodes", + qapp=test_qubes_app, + klass="TemplateVM", + netvm="", + features={ + "boot-mode.kernelopts.mode1": "mode1kern", + "boot-mode.name.mode1": "Mode One", + "boot-mode.kernelopts.mode2": "mode2kern1 mode2kern2", + "boot-mode.active": "mode1", + "boot-mode.appvm-default": "mode2", + }, ) # add an AppVM on top of the bootmode-enabled template - test_qubes_app._qubes['test-vm-bootmodes'] = MockQube( - name="test-vm-bootmodes", qapp=test_qubes_app, - template="fedora-36-bootmodes", features={ - 'boot-mode.kernelopts.mode1': 'mode1kern', - 'boot-mode.name.mode1': 'Mode One', - 'boot-mode.kernelopts.mode2': 'mode2kern1 mode2kern2', - 'boot-mode.active': 'mode1', 'boot-mode.appvm-default': 'mode2'} + test_qubes_app._qubes["test-vm-bootmodes"] = MockQube( + name="test-vm-bootmodes", + qapp=test_qubes_app, + template="fedora-36-bootmodes", + features={ + "boot-mode.kernelopts.mode1": "mode1kern", + "boot-mode.name.mode1": "Mode One", + "boot-mode.kernelopts.mode2": "mode2kern1 mode2kern2", + "boot-mode.active": "mode1", + "boot-mode.appvm-default": "mode2", + }, ) # add another AppVM with a non-default bootmode set - test_qubes_app._qubes['test-vm-bootmodes-nondefault'] = MockQube( - name="test-vm-bootmodes-nondefault", qapp=test_qubes_app, - template="fedora-36-bootmodes", bootmode="mode2", features={ - 'boot-mode.kernelopts.mode1': 'mode1kern', - 'boot-mode.name.mode1': 'Mode One', - 'boot-mode.kernelopts.mode2': 'mode2kern1 mode2kern2', - 'boot-mode.active': 'mode1', 'boot-mode.appvm-default': 'mode2'} + test_qubes_app._qubes["test-vm-bootmodes-nondefault"] = MockQube( + name="test-vm-bootmodes-nondefault", + qapp=test_qubes_app, + template="fedora-36-bootmodes", + bootmode="mode2", + features={ + "boot-mode.kernelopts.mode1": "mode1kern", + "boot-mode.name.mode1": "Mode One", + "boot-mode.kernelopts.mode2": "mode2kern1 mode2kern2", + "boot-mode.active": "mode1", + "boot-mode.appvm-default": "mode2", + }, ) test_qubes_app.update_vm_calls() if isinstance(request.param, dict): - vm = test_qubes_app.domains[request.param['vm']] - page = request.param['page'] + vm = test_qubes_app.domains[request.param["vm"]] + page = request.param["page"] else: vm = test_qubes_app.domains[request.param] - page = 'basic' - with mock.patch('subprocess.check_output') as mock_subprocess: + page = "basic" + with mock.patch("subprocess.check_output") as mock_subprocess: mock_subprocess.side_effect = mock_subprocess_complex - expected_call = (vm.name, 'admin.vm.notes.Get', None, None) - test_qubes_app.expected_calls[expected_call] = b'0\x00Some Notes\x00' - vms = vm_settings.VMSettingsWindow(vm, page, qapp, - test_qubes_app) - + expected_call = (vm.name, "admin.vm.notes.Get", None, None) + test_qubes_app.expected_calls[expected_call] = b"0\x00Some Notes\x00" + expected_call_preload = ( + vm.name, + "admin.vm.feature.Get", + "preload-dispvm-max", + None, + ) + test_qubes_app.expected_calls[expected_call_preload] = b"0\x00" + expected_call_preload = ( + "dom0", + "admin.vm.feature.Get", + "preload-dispvm-max", + None, + ) + test_qubes_app.expected_calls[expected_call_preload] = b"0\x00" + vms = vm_settings.VMSettingsWindow(vm, page, qapp, test_qubes_app) yield vms, page, vm.name + + # TODO: found a bug: firewall warning does not update def check_errors(test_function): @wraps(test_function) def wrapper(*args, **kwargs): - with mock.patch('PyQt6.QtWidgets.QMessageBox.warning') as mock_warning: + with mock.patch("PyQt6.QtWidgets.QMessageBox.warning") as mock_warning: result = test_function(*args, **kwargs) if mock_warning.call_count > 0: err = mock_warning.mock_calls[0][1][2] assert False, err assert mock_warning.call_count == 0 return result + return wrapper -def _select_item(combobox: QtWidgets.QComboBox, text: str, - match_strict: bool = False): +def _select_item(combobox: QtWidgets.QComboBox, text: str, match_strict: bool = False): """ select a given item in the combobox; if match_strict is True, will only match exact matches, otherwise, will match any item that contains provided string. """ for i in range(combobox.count()): - item_text = str(combobox.itemData(i, - QtCore.Qt.ItemDataRole.DisplayRole)) - if (match_strict and item_text == text) or (not match_strict and text - in item_text): + item_text = str(combobox.itemData(i, QtCore.Qt.ItemDataRole.DisplayRole)) + if (match_strict and item_text == text) or ( + not match_strict and text in item_text + ): combobox.setCurrentIndex(i) return assert False, "Failed to find " + text @@ -212,7 +270,7 @@ def test_002_data(settings_fixture): # check if contents are reasonable # basic tab - if hasattr(vm, 'template'): + if hasattr(vm, "template"): assert str(vm.template) in settings_window.template_name.currentText() else: assert settings_window.template_name.currentText() == "" @@ -229,50 +287,57 @@ def test_002_data(settings_fixture): assert str(vm.label) in settings_window.vmlabel.currentText() - assert (settings_window.include_in_backups.isChecked() == - getattr(vm, "include_in_backups", False)) + assert settings_window.include_in_backups.isChecked() == getattr( + vm, "include_in_backups", False + ) - assert (settings_window.autostart_vm.isChecked() == - getattr(vm, "autostart", False)) + assert settings_window.autostart_vm.isChecked() == getattr(vm, "autostart", False) # advanced tab assert settings_window.run_in_debug_mode.isChecked() == vm.debug - assert (settings_window.provides_network_checkbox.isChecked() == - getattr(vm, "provides_network", False)) - assert settings_window.dvm_template_checkbox.isChecked() == \ - getattr(vm, 'template_for_dispvms', False) - - if hasattr(vm, 'default_dispvm'): - if vm.property_is_default('default_dispvm'): - assert 'default' in settings_window.default_dispvm.currentText() + assert settings_window.provides_network_checkbox.isChecked() == getattr( + vm, "provides_network", False + ) + assert settings_window.dvm_template_checkbox.isChecked() == getattr( + vm, "template_for_dispvms", False + ) + assert ( + settings_window.preload_dispvm.isEnabled() + == settings_window.dvm_template_checkbox.isChecked() + ) + + if hasattr(vm, "default_dispvm"): + if vm.property_is_default("default_dispvm"): + assert "default" in settings_window.default_dispvm.currentText() if vm.default_dispvm: - assert (str(vm.default_dispvm) in - settings_window.default_dispvm.currentText()) + assert ( + str(vm.default_dispvm) in settings_window.default_dispvm.currentText() + ) else: - assert ("none" in - settings_window.default_dispvm.currentText().lower()) + assert "none" in settings_window.default_dispvm.currentText().lower() else: assert not settings_window.default_dispvm.isEnabled() - if hasattr(vm, 'kernel'): + if hasattr(vm, "kernel"): assert vm.kernel in settings_window.kernel.currentText() - if hasattr(vm, 'virt_mode'): + if hasattr(vm, "virt_mode"): assert vm.virt_mode.upper() in settings_window.virt_mode.currentText() # BASIC TAB # changing label + @check_errors @pytest.mark.parametrize("settings_fixture", ALL_TEST_VMS, indirect=True) def test_100_change_label(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] - change_needed = str(vm.label) != 'red' + change_needed = str(vm.label) != "red" if vm.is_running(): assert not settings_window.vmlabel.isEnabled() @@ -284,19 +349,21 @@ def test_100_change_label(settings_fixture): # is red, while others are green _select_item(settings_window.vmlabel, "red") - expected_call = (vm_name, 'admin.vm.property.Set', 'label', b'red') + expected_call = (vm_name, "admin.vm.property.Set", "label", b"red") assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() if change_needed: - assert expected_call in settings_window.qubesapp.actual_calls, \ - "Label not changed" + assert ( + expected_call in settings_window.qubesapp.actual_calls + ), "Label not changed" else: - assert expected_call not in settings_window.qubesapp.actual_calls, \ - "Unnecessary label change" + assert ( + expected_call not in settings_window.qubesapp.actual_calls + ), "Unnecessary label change" @check_errors @@ -309,26 +376,28 @@ def test_101_change_template(settings_fixture): assert not settings_window.template_name.isEnabled() return - change_needed = str(vm.template) != 'fedora-35' + change_needed = str(vm.template) != "fedora-35" assert settings_window.template_name.isEnabled() # one of the vms (test-old) already has this template _select_item(settings_window.template_name, "fedora-35") - expected_call = (vm_name, 'admin.vm.property.Set', 'template', b'fedora-35') + expected_call = (vm_name, "admin.vm.property.Set", "template", b"fedora-35") assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() if change_needed: - assert expected_call in settings_window.qubesapp.actual_calls, \ - "Template not changed" + assert ( + expected_call in settings_window.qubesapp.actual_calls + ), "Template not changed" else: - assert expected_call not in settings_window.qubesapp.actual_calls, \ - "Unnecessary template change" + assert ( + expected_call not in settings_window.qubesapp.actual_calls + ), "Unnecessary template change" @check_errors @@ -337,27 +406,30 @@ def test_102_change_netvm(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] - change_needed = str(vm.netvm) != 'sys-net' + change_needed = str(vm.netvm) != "sys-net" assert settings_window.netVM.isEnabled() _select_item(settings_window.netVM, "sys-net") - expected_call = (vm_name, 'admin.vm.property.Set', 'netvm', b'sys-net') + expected_call = (vm_name, "admin.vm.property.Set", "netvm", b"sys-net") assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() if change_needed: - assert expected_call in settings_window.qubesapp.actual_calls, \ - "NetVM not changed" + assert ( + expected_call in settings_window.qubesapp.actual_calls + ), "NetVM not changed" else: - assert expected_call not in settings_window.qubesapp.actual_calls, \ - "Unnecessary NetVM change" + assert ( + expected_call not in settings_window.qubesapp.actual_calls + ), "Unnecessary NetVM change" -@mock.patch('PyQt6.QtWidgets.QMessageBox.warning') + +@mock.patch("PyQt6.QtWidgets.QMessageBox.warning") @pytest.mark.parametrize("settings_fixture", ["fedora-35"], indirect=True) def test_103_change_netvm_tpl(mock_warning, settings_fixture): settings_window, page, vm_name = settings_fixture @@ -367,18 +439,18 @@ def test_103_change_netvm_tpl(mock_warning, settings_fixture): _select_item(settings_window.netVM, "sys-net") - expected_call = (vm_name, 'admin.vm.property.Set', 'netvm', b'sys-net') + expected_call = (vm_name, "admin.vm.property.Set", "netvm", b"sys-net") assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() - assert mock_warning.call_count == 1, ("Didn't warn for changing netvm " - "on a template") + assert mock_warning.call_count == 1, ( + "Didn't warn for changing netvm " "on a template" + ) - assert expected_call in settings_window.qubesapp.actual_calls, \ - "NetVM not changed" + assert expected_call in settings_window.qubesapp.actual_calls, "NetVM not changed" @check_errors @@ -393,25 +465,27 @@ def test_104_change_netvm_default(settings_fixture): _select_item(settings_window.netVM, "default") - expected_call = (vm_name, 'admin.vm.property.Reset', 'netvm', None) + expected_call = (vm_name, "admin.vm.property.Reset", "netvm", None) assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" # when asking to change netvm of a running vm to halted vm, complaints will # ensue - with mock.patch('PyQt6.QtWidgets.QMessageBox.question') as mock_question: + with mock.patch("PyQt6.QtWidgets.QMessageBox.question") as mock_question: mock_question.return_value = QtWidgets.QMessageBox.StandardButton.No settings_window.accept() if change_needed: return if change_needed: - assert expected_call in settings_window.qubesapp.actual_calls, \ - "NetVM not changed" + assert ( + expected_call in settings_window.qubesapp.actual_calls + ), "NetVM not changed" else: - assert expected_call not in settings_window.qubesapp.actual_calls, \ - "Unnecessary NetVM change" + assert ( + expected_call not in settings_window.qubesapp.actual_calls + ), "Unnecessary NetVM change" @check_errors @@ -421,21 +495,26 @@ def test_105_incl_in_backups(settings_fixture): vm = settings_window.qubesapp.domains[vm_name] assert settings_window.include_in_backups.isEnabled() - assert (settings_window.include_in_backups.isChecked() == - vm.include_in_backups) + assert settings_window.include_in_backups.isChecked() == vm.include_in_backups - expected_call = (vm_name, 'admin.vm.property.Set', 'include_in_backups', - str(not vm.include_in_backups).encode()) + expected_call = ( + vm_name, + "admin.vm.property.Set", + "include_in_backups", + str(not vm.include_in_backups).encode(), + ) assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.include_in_backups.setChecked( - not settings_window.include_in_backups.isChecked()) + not settings_window.include_in_backups.isChecked() + ) settings_window.accept() - assert expected_call in settings_window.qubesapp.actual_calls, \ - "Include in backups not changed" + assert ( + expected_call in settings_window.qubesapp.actual_calls + ), "Include in backups not changed" @check_errors @@ -445,21 +524,26 @@ def test_106_autostart(settings_fixture): vm = settings_window.qubesapp.domains[vm_name] assert settings_window.autostart_vm.isEnabled() - assert (settings_window.autostart_vm.isChecked() == - vm.autostart) + assert settings_window.autostart_vm.isChecked() == vm.autostart - expected_call = (vm_name, 'admin.vm.property.Set', 'autostart', - str(not vm.autostart).encode()) + expected_call = ( + vm_name, + "admin.vm.property.Set", + "autostart", + str(not vm.autostart).encode(), + ) assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.autostart_vm.setChecked( - not settings_window.autostart_vm.isChecked()) + not settings_window.autostart_vm.isChecked() + ) settings_window.accept() - assert expected_call in settings_window.qubesapp.actual_calls, \ - "Autostart not changed" + assert ( + expected_call in settings_window.qubesapp.actual_calls + ), "Autostart not changed" @check_errors @@ -472,13 +556,12 @@ def test_107_misc_info(settings_fixture): assert settings_window.ip_label.text() == vm.ip assert settings_window.netmask_label.text() == vm.visible_netmask assert settings_window.gateway_label.text() == vm.visible_gateway - assert settings_window.dns_label.text() == vm.dns.replace(' ', ', ') + assert settings_window.dns_label.text() == vm.dns.replace(" ", ", ") else: assert not settings_window.networking_groupbox.isEnabled() assert settings_window.type_label.text() == vm.klass - assert settings_window.rpm_label.text() == "Yes" if vm.installed_by_rpm \ - else "No" + assert settings_window.rpm_label.text() == "Yes" if vm.installed_by_rpm else "No" @check_errors @@ -487,7 +570,7 @@ def test_108_disk_space(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] - if vm.klass in ['TemplateVM', 'StandaloneVM']: + if vm.klass in ["TemplateVM", "StandaloneVM"]: assert settings_window.root_resize.isEnabled() else: assert not settings_window.root_resize.isEnabled() @@ -495,19 +578,23 @@ def test_108_disk_space(settings_fixture): assert settings_window.max_priv_storage.isEnabled() # try to increase one of them - if vm.klass in ['TemplateVM', 'StandaloneVM']: + if vm.klass in ["TemplateVM", "StandaloneVM"]: expected_value = settings_window.root_resize.value() + 10 - expected_volume = 'root' + expected_volume = "root" settings_window.root_resize.setValue(expected_value) else: expected_value = settings_window.max_priv_storage.value() + 10 - expected_volume = 'private' + expected_volume = "private" settings_window.max_priv_storage.setValue(expected_value) - expected_call = (vm.name, 'admin.vm.volume.Resize', expected_volume, - str(expected_value * 1024**2).encode()) + expected_call = ( + vm.name, + "admin.vm.volume.Resize", + expected_volume, + str(expected_value * 1024**2).encode(), + ) - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" assert expected_call not in settings_window.qubesapp.actual_calls @@ -518,12 +605,12 @@ def test_108_disk_space(settings_fixture): # the following tests don't use check_errors fixture, because we want to # check for errors -@mock.patch('PyQt6.QtWidgets.QInputDialog.getText') -@mock.patch('qubesmanager.settings.RenameVMThread') -@mock.patch('PyQt6.QtWidgets.QMessageBox.warning') -@pytest.mark.parametrize("settings_fixture", ['fedora-36', 'test-vm-set', - 'test-blue'], - indirect=True) +@mock.patch("PyQt6.QtWidgets.QInputDialog.getText") +@mock.patch("qubesmanager.settings.RenameVMThread") +@mock.patch("PyQt6.QtWidgets.QMessageBox.warning") +@pytest.mark.parametrize( + "settings_fixture", ["fedora-36", "test-vm-set", "test-blue"], indirect=True +) def test_109_renamevm(mock_warning, mock_thread, mock_input, settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] @@ -538,11 +625,11 @@ def test_109_renamevm(mock_warning, mock_thread, mock_input, settings_fixture): mock_input.return_value = ("renamed-vm", True) settings_window.rename_vm_button.click() - if vm.name == 'fedora-36': + if vm.name == "fedora-36": assert mock_warning.call_count == 1 assert mock_thread.call_count == 0 return - elif vm.name == 'test-vm-set': + elif vm.name == "test-vm-set": mock_thread.assert_called_with(vm, "renamed-vm", mock.ANY) mock_thread().start.assert_called_with() assert mock_warning.call_count == 0 @@ -550,12 +637,12 @@ def test_109_renamevm(mock_warning, mock_thread, mock_input, settings_fixture): assert mock_warning.call_count == 0 -@mock.patch('PyQt6.QtWidgets.QInputDialog.getText') -@mock.patch('qubesmanager.common_threads.RemoveVMThread') -@mock.patch('PyQt6.QtWidgets.QMessageBox.warning') -@pytest.mark.parametrize("settings_fixture", ['fedora-36', 'test-vm-set', - 'test-blue'], - indirect=True) +@mock.patch("PyQt6.QtWidgets.QInputDialog.getText") +@mock.patch("qubesmanager.common_threads.RemoveVMThread") +@mock.patch("PyQt6.QtWidgets.QMessageBox.warning") +@pytest.mark.parametrize( + "settings_fixture", ["fedora-36", "test-vm-set", "test-blue"], indirect=True +) def test_110_deletevm(mock_warning, mock_thread, mock_input, settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] @@ -570,11 +657,11 @@ def test_110_deletevm(mock_warning, mock_thread, mock_input, settings_fixture): mock_input.return_value = (vm.name, True) settings_window.delete_vm_button.click() - if vm.name == 'fedora-36': + if vm.name == "fedora-36": assert mock_warning.call_count == 1 assert mock_thread.call_count == 0 return - elif vm.name == 'test-vm-set': + elif vm.name == "test-vm-set": mock_thread.assert_called_with(vm) mock_thread().start.assert_called_with() assert mock_warning.call_count == 0 @@ -582,19 +669,19 @@ def test_110_deletevm(mock_warning, mock_thread, mock_input, settings_fixture): assert mock_warning.call_count == 0 -@mock.patch('PyQt6.QtWidgets.QInputDialog.getText') -@mock.patch('qubesmanager.common_threads.RemoveVMThread') -@mock.patch('PyQt6.QtWidgets.QMessageBox.warning') -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], - indirect=True) -def test_111_deletevm_wrong_name(mock_warning, mock_thread, mock_input, - settings_fixture): +@mock.patch("PyQt6.QtWidgets.QInputDialog.getText") +@mock.patch("qubesmanager.common_threads.RemoveVMThread") +@mock.patch("PyQt6.QtWidgets.QMessageBox.warning") +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) +def test_111_deletevm_wrong_name( + mock_warning, mock_thread, mock_input, settings_fixture +): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] assert settings_window.delete_vm_button.isEnabled() - mock_input.return_value = (vm.name + 'pomidorek', True) + mock_input.return_value = (vm.name + "pomidorek", True) settings_window.delete_vm_button.click() assert mock_thread.call_count == 0 @@ -607,11 +694,11 @@ def test_111_deletevm_wrong_name(mock_warning, mock_thread, mock_input, assert mock_warning.call_count == 1 # no warning, the user cancelled out -@mock.patch('qubesmanager.clone_vm.CloneVMDlg') +@mock.patch("qubesmanager.clone_vm.CloneVMDlg") @check_errors -@pytest.mark.parametrize("settings_fixture", ['fedora-36', 'test-vm-set', - 'test-blue'], - indirect=True) +@pytest.mark.parametrize( + "settings_fixture", ["fedora-36", "test-vm-set", "test-blue"], indirect=True +) def test_112_clonevm(mock_clone, settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] @@ -623,14 +710,14 @@ def test_112_clonevm(mock_clone, settings_fixture): mock_clone.assert_called_with(mock.ANY, mock.ANY, src_vm=vm) -@mock.patch('PyQt6.QtWidgets.QMessageBox.warning') +@mock.patch("PyQt6.QtWidgets.QMessageBox.warning") @pytest.mark.parametrize("settings_fixture", ["anon-whonix"], indirect=True) def test_113_change_netvm_anon(mock_warning, settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] - change_netvm_call = (vm_name, 'admin.vm.property.Set', 'netvm', b'sys-net') - get_tag_call = (vm_name, 'admin.vm.tag.Get', 'anon-vm', None) + change_netvm_call = (vm_name, "admin.vm.property.Set", "netvm", b"sys-net") + get_tag_call = (vm_name, "admin.vm.tag.Get", "anon-vm", None) assert settings_window.netVM.isEnabled() @@ -639,20 +726,20 @@ def test_113_change_netvm_anon(mock_warning, settings_fixture): assert get_tag_call in settings_window.qubesapp.actual_calls assert change_netvm_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[change_netvm_call] = b'0\x00' + settings_window.qubesapp.expected_calls[change_netvm_call] = b"0\x00" settings_window.accept() - settings_window.qubesapp.expected_calls[change_netvm_call] = b'0\x00' + settings_window.qubesapp.expected_calls[change_netvm_call] = b"0\x00" - assert mock_warning.call_count == 1, ("Didn't warn for changing netvm " - "on anon-vm") + assert mock_warning.call_count == 1, "Didn't warn for changing netvm " "on anon-vm" assert change_netvm_call in settings_window.qubesapp.actual_calls # ADVANCED TAB + @check_errors @pytest.mark.parametrize("settings_fixture", TEST_VMS, indirect=True) def test_200_init_memory(settings_fixture): @@ -666,10 +753,14 @@ def test_200_init_memory(settings_fixture): settings_window.init_mem.setValue(expected_value) - expected_call = (vm.name, 'admin.vm.property.Set', 'memory', - str(expected_value).encode()) + expected_call = ( + vm.name, + "admin.vm.property.Set", + "memory", + str(expected_value).encode(), + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -694,16 +785,28 @@ def test_201_max_memory(settings_fixture): assert not settings_window.warn_too_much_mem_label.isVisible() expected_calls = [ - (vm.name, 'admin.vm.property.Set', 'memory', - str(expected_init).encode()), - (vm.name, 'admin.vm.property.Set', 'maxmem', - str(expected_max).encode()), - (vm.name, 'admin.vm.feature.Set', 'qubesmanager.maxmem_value', - str(expected_max - 100).encode()) + ( + vm.name, + "admin.vm.property.Set", + "memory", + str(expected_init).encode(), + ), + ( + vm.name, + "admin.vm.property.Set", + "maxmem", + str(expected_max).encode(), + ), + ( + vm.name, + "admin.vm.feature.Set", + "qubesmanager.maxmem_value", + str(expected_max - 100).encode(), + ), ] for call in expected_calls: assert call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[call] = b'0\x00' + settings_window.qubesapp.expected_calls[call] = b"0\x00" settings_window.accept() @@ -722,10 +825,14 @@ def test_202_vcpus(settings_fixture): expected_value = settings_window.vcpus.value() + 1 settings_window.vcpus.setValue(expected_value) - expected_call = (vm.name, 'admin.vm.property.Set', 'vcpus', - str(expected_value).encode()) + expected_call = ( + vm.name, + "admin.vm.property.Set", + "vcpus", + str(expected_value).encode(), + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -742,19 +849,27 @@ def test_203_mem_balancing(settings_fixture): settings_window.include_in_balancing.setChecked(False) - expected_call = (vm.name, 'admin.vm.feature.Set', - 'qubesmanager.maxmem_value', str(vm.maxmem).encode()) + expected_call = ( + vm.name, + "admin.vm.feature.Set", + "qubesmanager.maxmem_value", + str(vm.maxmem).encode(), + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" expected_calls = [ - (vm.name, 'admin.vm.feature.Set', 'qubesmanager.maxmem_value', - str(vm.maxmem).encode()), - (vm.name, 'admin.vm.property.Set', 'maxmem', b'0') + ( + vm.name, + "admin.vm.feature.Set", + "qubesmanager.maxmem_value", + str(vm.maxmem).encode(), + ), + (vm.name, "admin.vm.property.Set", "maxmem", b"0"), ] for call in expected_calls: assert call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[call] = b'0\x00' + settings_window.qubesapp.expected_calls[call] = b"0\x00" settings_window.accept() @@ -772,10 +887,14 @@ def test_204_debug_mode(settings_fixture): settings_window.run_in_debug_mode.setChecked(not vm.debug) - expected_call = (vm.name, 'admin.vm.property.Set', 'debug', - str(not vm.debug).encode()) + expected_call = ( + vm.name, + "admin.vm.property.Set", + "debug", + str(not vm.debug).encode(), + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -788,16 +907,18 @@ def test_205_povides_network(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] - assert (settings_window.provides_network_checkbox.isChecked() == - vm.provides_network) + assert settings_window.provides_network_checkbox.isChecked() == vm.provides_network - settings_window.provides_network_checkbox.setChecked( - not vm.provides_network) + settings_window.provides_network_checkbox.setChecked(not vm.provides_network) - expected_call = (vm.name, 'admin.vm.property.Set', 'provides_network', - str(not vm.provides_network).encode()) + expected_call = ( + vm.name, + "admin.vm.property.Set", + "provides_network", + str(not vm.provides_network).encode(), + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -812,27 +933,40 @@ def test_206_dispvmtempl(settings_fixture): assert settings_window.dvm_template_checkbox.isEnabled() - assert (settings_window.dvm_template_checkbox.isChecked() == - vm.template_for_dispvms) + assert settings_window.dvm_template_checkbox.isChecked() == vm.template_for_dispvms + assert ( + settings_window.preload_dispvm.isEnabled() + == settings_window.dvm_template_checkbox.isChecked() + ) - settings_window.dvm_template_checkbox.setChecked( - not vm.template_for_dispvms) + settings_window.dvm_template_checkbox.setChecked(not vm.template_for_dispvms) + + assert ( + settings_window.preload_dispvm.isEnabled() + == settings_window.dvm_template_checkbox.isChecked() + ) expected_calls = [ - (vm.name, 'admin.vm.property.Set', 'template_for_dispvms', - str(not vm.template_for_dispvms).encode()) + ( + vm.name, + "admin.vm.property.Set", + "template_for_dispvms", + str(not vm.template_for_dispvms).encode(), + ) ] if vm.template_for_dispvms: # remove existis menus - expected_calls.append((vm.name, 'admin.vm.feature.Remove', - 'appmenus-dispvm', None)) + expected_calls.append( + (vm.name, "admin.vm.feature.Remove", "appmenus-dispvm", None) + ) else: - expected_calls.append((vm.name, 'admin.vm.feature.Set', - 'appmenus-dispvm', b'1')) + expected_calls.append( + (vm.name, "admin.vm.feature.Set", "appmenus-dispvm", b"1") + ) for call in expected_calls: assert call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[call] = b'0\x00' + settings_window.qubesapp.expected_calls[call] = b"0\x00" settings_window.accept() @@ -846,12 +980,16 @@ def test_207_def_dispvm(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] - _select_item(settings_window.default_dispvm, 'test-vm-set') + _select_item(settings_window.default_dispvm, "test-vm-set") - expected_call = (vm.name, 'admin.vm.property.Set', 'default_dispvm', - b'test-vm-set') + expected_call = ( + vm.name, + "admin.vm.property.Set", + "default_dispvm", + b"test-vm-set", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -866,17 +1004,21 @@ def test_208_fullscreen(settings_fixture): assert "current" in settings_window.allow_fullscreen.currentText() - if 'gui-allow-fullscreen' not in vm.features: + if "gui-allow-fullscreen" not in vm.features: assert "default" in settings_window.allow_fullscreen.currentText() - if vm.features.get('gui-allow-fullscreen', None) == '1': - assert 'allow' in settings_window.allow_fullscreen.currentText() + if vm.features.get("gui-allow-fullscreen", None) == "1": + assert "allow" in settings_window.allow_fullscreen.currentText() _select_item(settings_window.allow_fullscreen, "disallow") - expected_call = (vm.name, 'admin.vm.feature.Set', 'gui-allow-fullscreen', - b'') + expected_call = ( + vm.name, + "admin.vm.feature.Set", + "gui-allow-fullscreen", + b"", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -891,17 +1033,21 @@ def test_209_utf8_titles(settings_fixture): assert "current" in settings_window.allow_utf8.currentText() - if 'gui-allow-utf8-titles' not in vm.features: + if "gui-allow-utf8-titles" not in vm.features: assert "default" in settings_window.allow_utf8.currentText() - if vm.features.get('gui-allow-utf8-titles', None) == '1': - assert 'allow' in settings_window.allow_utf8.currentText() + if vm.features.get("gui-allow-utf8-titles", None) == "1": + assert "allow" in settings_window.allow_utf8.currentText() _select_item(settings_window.allow_utf8, "disallow") - expected_call = (vm.name, 'admin.vm.feature.Set', 'gui-allow-utf8-titles', - b'') + expected_call = ( + vm.name, + "admin.vm.feature.Set", + "gui-allow-utf8-titles", + b"", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -918,15 +1064,14 @@ def test_210_kernel(settings_fixture): _select_item(settings_window.kernel, "misc") - if not vm.property_is_default('kernel'): + if not vm.property_is_default("kernel"): _select_item(settings_window.kernel, "default") - expected_call = (vm.name, 'admin.vm.property.Reset', 'kernel', None) + expected_call = (vm.name, "admin.vm.property.Reset", "kernel", None) else: _select_item(settings_window.kernel, "misc") - expected_call = (vm.name, 'admin.vm.property.Set', 'kernel', - b'misc') + expected_call = (vm.name, "admin.vm.property.Set", "kernel", b"misc") assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -941,23 +1086,23 @@ def test_211_virtmode(settings_fixture): assert str(vm.virt_mode).upper() in settings_window.virt_mode.currentText() - if vm.virt_mode == 'pvh': - _select_item(settings_window.virt_mode, 'HVM') - expected_call = (vm.name, 'admin.vm.property.Set', 'virt_mode', b'hvm') + if vm.virt_mode == "pvh": + _select_item(settings_window.virt_mode, "HVM") + expected_call = (vm.name, "admin.vm.property.Set", "virt_mode", b"hvm") else: - _select_item(settings_window.virt_mode, 'PV', match_strict=True) - expected_call = (vm.name, 'admin.vm.property.Set', 'virt_mode', b'pv') + _select_item(settings_window.virt_mode, "PV", match_strict=True) + expected_call = (vm.name, "admin.vm.property.Set", "virt_mode", b"pv") assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() assert expected_call in settings_window.qubesapp.actual_calls -@mock.patch('qubesadmin.tools.qvm_start.main') -@mock.patch('qubesmanager.bootfromdevice.VMBootFromDeviceWindow') +@mock.patch("qubesadmin.tools.qvm_start.main") +@mock.patch("qubesmanager.bootfromdevice.VMBootFromDeviceWindow") @check_errors @pytest.mark.parametrize("settings_fixture", TEST_VMS, indirect=True) def test_212_boot_from_device(mock_boot, mock_start, settings_fixture): @@ -969,10 +1114,14 @@ def test_212_boot_from_device(mock_boot, mock_start, settings_fixture): settings_window.boot_from_device_button.click() mock_boot.assert_called_with( - vm=vm.name, qapp=settings_window.qapp, - qubesapp=settings_window.qubesapp, parent=settings_window) + vm=vm.name, + qapp=settings_window.qapp, + qubesapp=settings_window.qubesapp, + parent=settings_window, + ) + + mock_start.assert_called_with(["--cdrom", mock.ANY, vm.name]) - mock_start.assert_called_with(['--cdrom', mock.ANY, vm.name]) @check_errors @pytest.mark.parametrize("settings_fixture", ["fedora-36-bootmodes"], indirect=True) @@ -990,30 +1139,39 @@ def test_213_bootmode_template(settings_fixture): assert settings_window.bootmode_kernel_opts.text() == "" _select_item(settings_window.bootmode, "Mode One") assert settings_window.bootmode_kernel_opts.text() == "mode1kern" - with mock.patch('qubesadmin.base.PropertyHolder.property_get_default', - return_value='mode1'): + with mock.patch( + "qubesadmin.base.PropertyHolder.property_get_default", + return_value="mode1", + ): _select_item(settings_window.bootmode, "default") assert settings_window.bootmode_kernel_opts.text() == "mode1kern" _select_item(settings_window.bootmode, "mode2") _select_item(settings_window.appvm_default_bootmode, "Mode One") - expected_call_bm = (vm_name, 'admin.vm.property.Set', 'bootmode', b'mode2') + expected_call_bm = (vm_name, "admin.vm.property.Set", "bootmode", b"mode2") assert expected_call_bm not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call_bm] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call_bm] = b"0\x00" - expected_call_adbm = (vm_name, 'admin.vm.property.Set', \ - 'appvm_default_bootmode', b'mode1') + expected_call_adbm = ( + vm_name, + "admin.vm.property.Set", + "appvm_default_bootmode", + b"mode1", + ) assert expected_call_adbm not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call_adbm] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call_adbm] = b"0\x00" settings_window.accept() - assert expected_call_bm in settings_window.qubesapp.actual_calls, \ - "Boot mode not changed" - assert expected_call_adbm in settings_window.qubesapp.actual_calls, \ - "AppVM default boot mode not changed" + assert ( + expected_call_bm in settings_window.qubesapp.actual_calls + ), "Boot mode not changed" + assert ( + expected_call_adbm in settings_window.qubesapp.actual_calls + ), "AppVM default boot mode not changed" + @check_errors @pytest.mark.parametrize("settings_fixture", ["test-vm-bootmodes"], indirect=True) @@ -1031,17 +1189,21 @@ def test_214_bootmode_appvm(settings_fixture): assert settings_window.bootmode_kernel_opts.text() == "mode2kern1 mode2kern2" _select_item(settings_window.bootmode, "Mode One") - expected_call = (vm_name, 'admin.vm.property.Set', 'bootmode', b'mode1') + expected_call = (vm_name, "admin.vm.property.Set", "bootmode", b"mode1") assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() - assert expected_call in settings_window.qubesapp.actual_calls, \ - "Boot mode not changed" + assert ( + expected_call in settings_window.qubesapp.actual_calls + ), "Boot mode not changed" + @check_errors -@pytest.mark.parametrize("settings_fixture", ["test-vm-bootmodes-nondefault"], indirect=True) +@pytest.mark.parametrize( + "settings_fixture", ["test-vm-bootmodes-nondefault"], indirect=True +) def test_215_bootmode_appvm_nondefault(settings_fixture): settings_window, page, vm_name = settings_fixture @@ -1057,14 +1219,15 @@ def test_215_bootmode_appvm_nondefault(settings_fixture): assert settings_window.bootmode_kernel_opts.text() == "mode2kern1 mode2kern2" _select_item(settings_window.bootmode, "Mode One") - expected_call = (vm_name, 'admin.vm.property.Set', 'bootmode', b'mode1') + expected_call = (vm_name, "admin.vm.property.Set", "bootmode", b"mode1") assert expected_call not in settings_window.qubesapp.expected_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() - assert expected_call in settings_window.qubesapp.actual_calls, \ - "Boot mode not changed" + assert ( + expected_call in settings_window.qubesapp.actual_calls + ), "Boot mode not changed" @check_errors @@ -1104,6 +1267,7 @@ def test_213_prohibit_start(settings_fixture): # FIREWALL TAB + @check_errors @pytest.mark.parametrize("settings_fixture", ["test-blue"], indirect=True) def test_300_firewall_start_limiting(settings_fixture): @@ -1118,11 +1282,13 @@ def test_300_firewall_start_limiting(settings_fixture): settings_window.policy_deny_radio_button.setChecked(True) expected_call = ( - 'test-blue', 'admin.vm.firewall.Set', None, - b'action=accept specialtarget=dns\naction=accept ' - b'proto=icmp\naction=drop\n') + "test-blue", + "admin.vm.firewall.Set", + None, + b"action=accept specialtarget=dns\naction=accept " b"proto=icmp\naction=drop\n", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1144,10 +1310,13 @@ def test_301_firewall_unlimit(settings_fixture): settings_window.policy_allow_radio_button.setChecked(True) expected_call = ( - 'test-vm-set', 'admin.vm.firewall.Set', None, - b'action=accept dsthost=qubes-os.org\naction=accept\n') + "test-vm-set", + "admin.vm.firewall.Set", + None, + b"action=accept dsthost=qubes-os.org\naction=accept\n", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1165,16 +1334,17 @@ def test_302_firewall_remove_rule(settings_fixture): assert settings_window.policy_allow_radio_button.isEnabled() assert settings_window.policy_deny_radio_button.isChecked() - settings_window.rulesTreeView.setCurrentIndex( - settings_window.fw_model.index(0, 0)) + settings_window.rulesTreeView.setCurrentIndex(settings_window.fw_model.index(0, 0)) settings_window.delete_rule_button.click() expected_call = ( - 'test-vm-set', 'admin.vm.firewall.Set', None, - b'action=accept specialtarget=dns\naction=accept ' - b'proto=icmp\naction=drop\n') + "test-vm-set", + "admin.vm.firewall.Set", + None, + b"action=accept specialtarget=dns\naction=accept " b"proto=icmp\naction=drop\n", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1193,20 +1363,23 @@ def test_303_firewall_add_rule(settings_fixture): assert settings_window.policy_deny_radio_button.isChecked() settings_window.new_rule_button.click() - settings_window.fw_model.current_dialog.addressComboBox.setCurrentText( - "test_stuff") + settings_window.fw_model.current_dialog.addressComboBox.setCurrentText("test_stuff") settings_window.fw_model.current_dialog.buttonBox.button( - QtWidgets.QDialogButtonBox.StandardButton.Ok).click() + QtWidgets.QDialogButtonBox.StandardButton.Ok + ).click() expected_call = ( - 'test-vm-set', 'admin.vm.firewall.Set', None, - b'action=accept dsthost=qubes-os.org\n' - b'action=accept dsthost=test_stuff\n' - b'action=accept specialtarget=dns\n' - b'action=accept proto=icmp\n' - b'action=drop\n') + "test-vm-set", + "admin.vm.firewall.Set", + None, + b"action=accept dsthost=qubes-os.org\n" + b"action=accept dsthost=test_stuff\n" + b"action=accept specialtarget=dns\n" + b"action=accept proto=icmp\n" + b"action=drop\n", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1225,23 +1398,25 @@ def test_304_firewall_add_rule_complex(settings_fixture): assert settings_window.policy_deny_radio_button.isChecked() settings_window.new_rule_button.click() - settings_window.fw_model.current_dialog.addressComboBox.setCurrentText( - "test_stuff") + settings_window.fw_model.current_dialog.addressComboBox.setCurrentText("test_stuff") settings_window.fw_model.current_dialog.udp_radio.setChecked(True) - _select_item(settings_window.fw_model.current_dialog.serviceComboBox, - "http") + _select_item(settings_window.fw_model.current_dialog.serviceComboBox, "http") settings_window.fw_model.current_dialog.buttonBox.button( - QtWidgets.QDialogButtonBox.StandardButton.Ok).click() + QtWidgets.QDialogButtonBox.StandardButton.Ok + ).click() expected_call = ( - 'test-vm-set', 'admin.vm.firewall.Set', None, - b'action=accept dsthost=qubes-os.org\n' - b'action=accept proto=udp dsthost=test_stuff dstports=80-80\n' - b'action=accept specialtarget=dns\n' - b'action=accept proto=icmp\n' - b'action=drop\n') + "test-vm-set", + "admin.vm.firewall.Set", + None, + b"action=accept dsthost=qubes-os.org\n" + b"action=accept proto=udp dsthost=test_stuff dstports=80-80\n" + b"action=accept specialtarget=dns\n" + b"action=accept proto=icmp\n" + b"action=drop\n", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1259,23 +1434,25 @@ def test_305_firewall_edit_rule(settings_fixture): assert settings_window.policy_allow_radio_button.isEnabled() assert settings_window.policy_deny_radio_button.isChecked() - settings_window.rulesTreeView.setCurrentIndex( - settings_window.fw_model.index(0, 0)) + settings_window.rulesTreeView.setCurrentIndex(settings_window.fw_model.index(0, 0)) settings_window.edit_rule_button.click() settings_window.fw_model.current_dialog.tcp_radio.setChecked(True) - _select_item(settings_window.fw_model.current_dialog.serviceComboBox, - "printer") + _select_item(settings_window.fw_model.current_dialog.serviceComboBox, "printer") settings_window.fw_model.current_dialog.buttonBox.button( - QtWidgets.QDialogButtonBox.StandardButton.Ok).click() + QtWidgets.QDialogButtonBox.StandardButton.Ok + ).click() expected_call = ( - 'test-vm-set', 'admin.vm.firewall.Set', None, - b'action=accept proto=tcp dsthost=qubes-os.org dstports=515-515\n' - b'action=accept specialtarget=dns\naction=accept ' - b'proto=icmp\naction=drop\n') + "test-vm-set", + "admin.vm.firewall.Set", + None, + b"action=accept proto=tcp dsthost=qubes-os.org dstports=515-515\n" + b"action=accept specialtarget=dns\naction=accept " + b"proto=icmp\naction=drop\n", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1296,17 +1473,21 @@ def test_306_firewall_unlimit(settings_fixture): settings_window.temp_full_access.setChecked(True) # add 5 minutes to now - expiration_date = str((int(datetime.datetime.now().strftime( - "%s")) + 5 * 60)).encode() + expiration_date = str( + (int(datetime.datetime.now().strftime("%s")) + 5 * 60) + ).encode() expected_call = ( - 'test-vm-set', 'admin.vm.firewall.Set', None, - b'action=accept dsthost=qubes-os.org\n' - b'action=accept expire=' + expiration_date + b'\n' - b'action=accept specialtarget=dns\naction=accept ' - b'proto=icmp\naction=drop\n') + "test-vm-set", + "admin.vm.firewall.Set", + None, + b"action=accept dsthost=qubes-os.org\n" + b"action=accept expire=" + expiration_date + b"\n" + b"action=accept specialtarget=dns\naction=accept " + b"proto=icmp\naction=drop\n", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1314,7 +1495,7 @@ def test_306_firewall_unlimit(settings_fixture): @check_errors -@mock.patch('subprocess.check_output') +@mock.patch("subprocess.check_output") def test_307_open_with_limit(mock_subprocess, qapp, test_qubes_app): # this test is supposed to check if the settings open even when there is # an unlimited FW access set @@ -1322,27 +1503,30 @@ def test_307_open_with_limit(mock_subprocess, qapp, test_qubes_app): mock_subprocess.result = [] # add 2 minutes to now - expiration_date = str((int(datetime.datetime.now().strftime( - "%s")) + 2 * 60)) + expiration_date = str((int(datetime.datetime.now().strftime("%s")) + 2 * 60)) fw_rules = [ {"action": "accept", "expire": expiration_date}, {"action": "accept", "specialtarget": "dns"}, {"action": "accept", "proto": "icmp"}, - {"action": "drop"} + {"action": "drop"}, ] - test_qubes_app._qubes['test-vm-fw'] = MockQube( - name="test-vm-fw", qapp=test_qubes_app, label="green", - firewall_rules=fw_rules + test_qubes_app._qubes["test-vm-fw"] = MockQube( + name="test-vm-fw", + qapp=test_qubes_app, + label="green", + firewall_rules=fw_rules, + ) + expected_call = ("test-vm-fw", "admin.vm.notes.Get", None, None) + test_qubes_app.expected_calls[expected_call] = ( + "2\0QubesNotesException\0\0Notes not available!\0" ) - expected_call = ("test-vm-fw", 'admin.vm.notes.Get', None, None) - test_qubes_app.expected_calls[expected_call] = \ - '2\0QubesNotesException\0\0Notes not available!\0' test_qubes_app.update_vm_calls() settings_window = vm_settings.VMSettingsWindow( - 'test-vm-fw', 'basic', qapp, test_qubes_app) + "test-vm-fw", "basic", qapp, test_qubes_app + ) assert settings_window.policy_deny_radio_button.isEnabled() assert settings_window.policy_allow_radio_button.isEnabled() @@ -1354,89 +1538,89 @@ def test_307_open_with_limit(mock_subprocess, qapp, test_qubes_app): @check_errors -@pytest.mark.parametrize("settings_fixture", - [{'vm': 'vault', 'page': 'firewall'}], - indirect=True) +@pytest.mark.parametrize( + "settings_fixture", [{"vm": "vault", "page": "firewall"}], indirect=True +) def test_308_firewall_none(settings_fixture): settings_window, page, vm_name = settings_fixture assert settings_window.no_netvm_label.isVisibleTo(settings_window) - _select_item(settings_window.netVM, 'sys-net') + _select_item(settings_window.netVM, "sys-net") assert not settings_window.no_netvm_label.isVisibleTo(settings_window) - _select_item(settings_window.netVM, 'none') + _select_item(settings_window.netVM, "none") assert settings_window.no_netvm_label.isVisibleTo(settings_window) @check_errors -@pytest.mark.parametrize("settings_fixture", - [{'vm': 'vault', 'page': 'firewall'}], - indirect=True) +@pytest.mark.parametrize( + "settings_fixture", [{"vm": "vault", "page": "firewall"}], indirect=True +) def test_309_firewall_warn(settings_fixture): settings_window, page, vm_name = settings_fixture assert not settings_window.sysnet_warning_label.isVisibleTo(settings_window) - settings_window.tabWidget.setCurrentIndex(settings_window.tabs_indices[ - 'advanced']) + settings_window.tabWidget.setCurrentIndex(settings_window.tabs_indices["advanced"]) settings_window.provides_network_checkbox.setChecked(True) - settings_window.tabWidget.setCurrentIndex(settings_window.tabs_indices[ - 'firewall']) + settings_window.tabWidget.setCurrentIndex(settings_window.tabs_indices["firewall"]) assert settings_window.sysnet_warning_label.isVisibleTo(settings_window) @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-blue'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-blue"], indirect=True) def test_310_stupid_netvm(settings_fixture): settings_window, page, vm_name = settings_fixture - assert not settings_window.netvm_no_firewall_label.isVisibleTo( - settings_window) + assert not settings_window.netvm_no_firewall_label.isVisibleTo(settings_window) - _select_item(settings_window.netVM, 'test-vm-set') + _select_item(settings_window.netVM, "test-vm-set") - settings_window.tabWidget.setCurrentIndex(settings_window.tabs_indices[ - 'firewall']) + settings_window.tabWidget.setCurrentIndex(settings_window.tabs_indices["firewall"]) assert settings_window.netvm_no_firewall_label.isVisibleTo(settings_window) @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], - indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_400_services(settings_fixture): settings_window, page, vm_name = settings_fixture - enabled_services = [settings_window.services_list.item(i).text() for i in - range(settings_window.services_list.count())] + enabled_services = [ + settings_window.services_list.item(i).text() + for i in range(settings_window.services_list.count()) + ] available_services = [] for i in range(settings_window.service_line_edit.count()): - item_text = str(settings_window.service_line_edit.itemData(i, - QtCore.Qt.ItemDataRole.DisplayRole)) + item_text = str( + settings_window.service_line_edit.itemData( + i, QtCore.Qt.ItemDataRole.DisplayRole + ) + ) available_services.append(item_text) - assert enabled_services == ['qubes-u2f-proxy'] + assert enabled_services == ["qubes-u2f-proxy"] assert sorted(available_services) == sorted( - ['', 'qubes-u2f-proxy', 'clocksync', '(custom...)']) + ["", "qubes-u2f-proxy", "clocksync", "(custom...)"] + ) @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], - indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_401_services_remove(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] for i in range(settings_window.services_list.count()): item_text = settings_window.services_list.item(i).text() - if item_text == 'qubes-u2f-proxy': + if item_text == "qubes-u2f-proxy": settings_window.services_list.setCurrentRow(i) break else: @@ -1444,10 +1628,14 @@ def test_401_services_remove(settings_fixture): settings_window.remove_srv_button.click() - expected_call = (vm.name, 'admin.vm.feature.Remove', - 'service.qubes-u2f-proxy', None) + expected_call = ( + vm.name, + "admin.vm.feature.Remove", + "service.qubes-u2f-proxy", + None, + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1455,24 +1643,25 @@ def test_401_services_remove(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], - indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_402_services_add(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] for i in range(settings_window.service_line_edit.count()): - item_text = str(settings_window.service_line_edit.itemData(i, - QtCore.Qt.ItemDataRole.DisplayRole)) - if item_text == 'clocksync': + item_text = str( + settings_window.service_line_edit.itemData( + i, QtCore.Qt.ItemDataRole.DisplayRole + ) + ) + if item_text == "clocksync": settings_window.service_line_edit.setCurrentIndex(i) settings_window.add_srv_button.click() - expected_call = (vm.name, 'admin.vm.feature.Set', - 'service.clocksync', b'1') + expected_call = (vm.name, "admin.vm.feature.Set", "service.clocksync", b"1") assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1480,26 +1669,34 @@ def test_402_services_add(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], - indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_403_services_add_custom(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] for i in range(settings_window.service_line_edit.count()): - item_text = str(settings_window.service_line_edit.itemData(i, - QtCore.Qt.ItemDataRole.DisplayRole)) - if 'custom' in item_text: + item_text = str( + settings_window.service_line_edit.itemData( + i, QtCore.Qt.ItemDataRole.DisplayRole + ) + ) + if "custom" in item_text: settings_window.service_line_edit.setCurrentIndex(i) - with mock.patch('PyQt6.QtWidgets.QInputDialog.getText', - return_value=('shutdown-idle', True)): + with mock.patch( + "PyQt6.QtWidgets.QInputDialog.getText", + return_value=("shutdown-idle", True), + ): settings_window.add_srv_button.click() - expected_call = (vm.name, 'admin.vm.feature.Set', - 'service.shutdown-idle', b'1') + expected_call = ( + vm.name, + "admin.vm.feature.Set", + "service.shutdown-idle", + b"1", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1507,24 +1704,27 @@ def test_403_services_add_custom(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], - indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_404_services_disable(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] for i in range(settings_window.services_list.count()): item = settings_window.services_list.item(i) - if item.text() == 'qubes-u2f-proxy': + if item.text() == "qubes-u2f-proxy": item.setCheckState(QtCore.Qt.CheckState.Unchecked) break else: assert False, "Failed to find service" - expected_call = (vm.name, 'admin.vm.feature.Set', - 'service.qubes-u2f-proxy', b'') + expected_call = ( + vm.name, + "admin.vm.feature.Set", + "service.qubes-u2f-proxy", + b"", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1532,55 +1732,52 @@ def test_404_services_disable(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-red'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-red"], indirect=True) def test_500_applications_list(settings_fixture): settings_window, page, vm_name = settings_fixture available = [] selected = [] for i in range(settings_window.app_list.available_list.count()): - available.append( - settings_window.app_list.available_list.item(i).text()) + available.append(settings_window.app_list.available_list.item(i).text()) for i in range(settings_window.app_list.selected_list.count()): - selected.append( - settings_window.app_list.selected_list.item(i).text()) + selected.append(settings_window.app_list.selected_list.item(i).text()) assert not selected - assert available == ['Test App', 'Test2 App', 'Test3 App'] + assert available == ["Test App", "Test2 App", "Test3 App"] @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_501_applications_list_existing(settings_fixture): settings_window, page, vm_name = settings_fixture available = [] selected = [] for i in range(settings_window.app_list.available_list.count()): - available.append( - settings_window.app_list.available_list.item(i).text()) + available.append(settings_window.app_list.available_list.item(i).text()) for i in range(settings_window.app_list.selected_list.count()): - selected.append( - settings_window.app_list.selected_list.item(i).text()) + selected.append(settings_window.app_list.selected_list.item(i).text()) # some apps are present, some are missing - assert available == ['My VM app', 'Test2 App', 'Test3 App'] - assert selected == ['Application missing in template! (missing.desktop)', - 'Test App'] + assert available == ["My VM app", "Test2 App", "Test3 App"] + assert selected == [ + "Application missing in template! (missing.desktop)", + "Test App", + ] @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-red', - 'test-vm-set'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-red", "test-vm-set"], indirect=True) def test_502_application_add(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] for i in range(settings_window.app_list.available_list.count()): item = settings_window.app_list.available_list.item(i) - if item.text() == 'Test3 App': + if item.text() == "Test3 App": item.setSelected(True) break else: @@ -1588,17 +1785,23 @@ def test_502_application_add(settings_fixture): settings_window.app_list.add_selected_button.click() - if vm.name == 'test-vm-set': + if vm.name == "test-vm-set": expected_call = ( - vm.name, 'admin.vm.feature.Set', 'menu-items', - b'missing.desktop test.desktop test3.desktop') + vm.name, + "admin.vm.feature.Set", + "menu-items", + b"missing.desktop test.desktop test3.desktop", + ) else: expected_call = ( - vm.name, 'admin.vm.feature.Set', 'menu-items', - b'test3.desktop') + vm.name, + "admin.vm.feature.Set", + "menu-items", + b"test3.desktop", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1606,14 +1809,14 @@ def test_502_application_add(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_503_application_remove(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] for i in range(settings_window.app_list.selected_list.count()): item = settings_window.app_list.selected_list.item(i) - if item.text() == 'Test App': + if item.text() == "Test App": item.setSelected(True) break else: @@ -1622,10 +1825,13 @@ def test_503_application_remove(settings_fixture): settings_window.app_list.remove_selected_button.click() expected_call = ( - vm.name, 'admin.vm.feature.Set', 'menu-items', - b'missing.desktop') + vm.name, + "admin.vm.feature.Set", + "menu-items", + b"missing.desktop", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1633,7 +1839,7 @@ def test_503_application_remove(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_504_application_remove_missing(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] @@ -1649,10 +1855,13 @@ def test_504_application_remove_missing(settings_fixture): settings_window.app_list.remove_selected_button.click() expected_call = ( - vm.name, 'admin.vm.feature.Set', 'menu-items', - b'test.desktop') + vm.name, + "admin.vm.feature.Set", + "menu-items", + b"test.desktop", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1660,26 +1869,31 @@ def test_504_application_remove_missing(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-red', - 'test-vm-set'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-red", "test-vm-set"], indirect=True) def test_505_application_add_all(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] settings_window.app_list.add_all_button.click() - if vm.name == 'test-vm-set': + if vm.name == "test-vm-set": expected_call = ( - vm.name, 'admin.vm.feature.Set', 'menu-items', - b'missing.desktop myvm.desktop test.desktop ' - b'test2.desktop test3.desktop') + vm.name, + "admin.vm.feature.Set", + "menu-items", + b"missing.desktop myvm.desktop test.desktop " + b"test2.desktop test3.desktop", + ) else: expected_call = ( - vm.name, 'admin.vm.feature.Set', 'menu-items', - b'test.desktop test2.desktop test3.desktop') + vm.name, + "admin.vm.feature.Set", + "menu-items", + b"test.desktop test2.desktop test3.desktop", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1687,23 +1901,21 @@ def test_505_application_add_all(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-red', - 'test-vm-set'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-red", "test-vm-set"], indirect=True) def test_506_application_remove_all(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] settings_window.app_list.remove_all_button.click() - expected_call = ( - vm.name, 'admin.vm.feature.Set', 'menu-items', b'') + expected_call = (vm.name, "admin.vm.feature.Set", "menu-items", b"") assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() - if vm.name == 'test-vm-set': + if vm.name == "test-vm-set": assert expected_call in settings_window.qubesapp.actual_calls else: # that VM had no apps to begin with @@ -1711,7 +1923,7 @@ def test_506_application_remove_all(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_600_devices(settings_fixture): settings_window, page, vm_name = settings_fixture @@ -1724,14 +1936,14 @@ def test_600_devices(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-vm-set'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True) def test_601_device_add(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] for i in range(settings_window.dev_list.available_list.count()): item = settings_window.dev_list.available_list.item(i) - if 'USB' in item.text(): + if "USB" in item.text(): item.setSelected(True) break else: @@ -1740,12 +1952,15 @@ def test_601_device_add(settings_fixture): settings_window.dev_list.add_selected_button.click() expected_call = ( - vm.name, 'admin.vm.device.pci.Assign', 'dom0+00_03.0:*', + vm.name, + "admin.vm.device.pci.Assign", + "dom0+00_03.0:*", b"device_id='*' port_id='00_03.0' devclass='pci' " b"backend_domain='dom0' mode='required' " - b"frontend_domain='test-vm-set'") + b"frontend_domain='test-vm-set'", + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1753,7 +1968,7 @@ def test_601_device_add(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-pci-dev'], indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-pci-dev"], indirect=True) def test_602_device_remove(settings_fixture): settings_window, page, vm_name = settings_fixture vm = settings_window.qubesapp.domains[vm_name] @@ -1775,10 +1990,14 @@ def test_602_device_remove(settings_fixture): settings_window.dev_list.remove_selected_button.click() - expected_call = (vm.name, 'admin.vm.device.pci.Unassign', - 'dom0+00_04.3:0x8008:0x1234::p040000', None) + expected_call = ( + vm.name, + "admin.vm.device.pci.Unassign", + "dom0+00_04.3:0x8008:0x1234::p040000", + None, + ) assert expected_call not in settings_window.qubesapp.actual_calls - settings_window.qubesapp.expected_calls[expected_call] = b'0\x00' + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" settings_window.accept() @@ -1786,18 +2005,18 @@ def test_602_device_remove(settings_fixture): @check_errors -@pytest.mark.parametrize("settings_fixture", ['test-pci-dev'], - indirect=True) +@pytest.mark.parametrize("settings_fixture", ["test-pci-dev"], indirect=True) def test_603_virtmode_limitation(settings_fixture): settings_window, page, vm_name = settings_fixture available_virtmodes = [] for i in range(settings_window.virt_mode.count()): - item_text = str(settings_window.virt_mode.itemData( - i, QtCore.Qt.ItemDataRole.DisplayRole)) + item_text = str( + settings_window.virt_mode.itemData(i, QtCore.Qt.ItemDataRole.DisplayRole) + ) available_virtmodes.append(item_text) - assert len(available_virtmodes) == 3 # HVM, PV, default - assert 'PVH' not in available_virtmodes - assert 'HVM (current)' in available_virtmodes - assert 'PV' in available_virtmodes + assert len(available_virtmodes) == 3 # HVM, PV, default + assert "PVH" not in available_virtmodes + assert "HVM (current)" in available_virtmodes + assert "PV" in available_virtmodes diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index ea986609..06970737 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -742,6 +742,13 @@ border-width: 1px; 15 + + + + Run in debug mode + + + @@ -780,40 +787,143 @@ border-width: 1px; - - + + + + <html><head/><body><p>Configure how many disposables this template must preload.</p></body></html> + - Boot qube from DISC or block device + Preload disposables: - - - - - - Windows (with Qubes Windows Tools installed) only. -The qube must be running to enable seamless mode. This setting is not persistent. - - - Enable seamless mode - - - - - - - Windows (with Qubes Windows Tools installed) only. -The qube must be running to disable seamless mode. This setting is not persistent. - - - Disable seamless mode - - - - + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 50 + + + + + + + + + + 239 + 41 + 41 + + + + + + + 239 + 41 + 41 + + + + + + + 239 + 41 + 41 + + + + + + + + + 239 + 41 + 41 + + + + + + + 239 + 41 + 41 + + + + + + + 239 + 41 + 41 + + + + + + + + + 190 + 190 + 190 + + + + + + + 190 + 190 + 190 + + + + + + + 0 + 0 + 0 + + + + + + + + + true + true + + + + This qube is the default disposable template the global preload feature is set, therefore, it will honor the Global Config preference until the feature is deleted. + + + Qt::AlignJustify|Qt::AlignVCenter + + + true + + + + @@ -843,13 +953,39 @@ The qube must be running to disable seamless mode. This setting is not persisten - - + + - Run in debug mode + Boot qube from DISC or block device + + + + + + Windows (with Qubes Windows Tools installed) only. +The qube must be running to enable seamless mode. This setting is not persistent. + + + Enable seamless mode + + + + + + + Windows (with Qubes Windows Tools installed) only. +The qube must be running to disable seamless mode. This setting is not persistent. + + + Disable seamless mode + + + + + @@ -1933,6 +2069,7 @@ For user reference only. run_in_debug_mode provides_network_checkbox dvm_template_checkbox + preload_dispvm default_dispvm boot_from_device_button seamless_on_button