diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 711bc3639..462a3bb7a 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -198,7 +198,8 @@ def helpEvent(self, event, view, option, index): QToolTip.showText(event.globalPos(), self.tr( "The qube is prohibited from starting\n" - "See `qvm-features` manual for more information" + "Prohibition rationale is available in qube settings " + "-> Advanced tab" ), view ) @@ -721,7 +722,7 @@ def filterAcceptsRow(self, sourceRow, sourceParent): return super().filterAcceptsRow(sourceRow, sourceParent) if self.window.show_running.isChecked() and \ - vm.state['power'] != 'Halted': + not vm.state['power'] in ['Halted', 'Blocked']: return super().filterAcceptsRow(sourceRow, sourceParent) if self.window.show_halted.isChecked() and \ vm.state['power'] == 'Halted': diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index e0f08a954..d7defae60 100644 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -1020,6 +1020,18 @@ def __init_advanced_tab__(self): self.run_in_debug_mode.setVisible(False) self.run_in_debug_mode.setEnabled(False) + rationale = utils.get_feature(self.vm, "prohibit-start", "") + if rationale: + self.prohibit_start_rationale.setText(rationale) + self.prohibit_start_rationale.setVisible(bool(rationale)) + self.prohibit_start_rationale.textEdited.connect( + self.prohibit_start_rationale_edited + ) + self.prohibit_start_checkbox.setChecked(bool(rationale)) + self.prohibit_start_checkbox.clicked.connect( + self.prohibit_start_checked + ) + utils.initialize_widget( widget=self.allow_fullscreen, choices=[ @@ -1041,6 +1053,16 @@ def __init_advanced_tab__(self): 'gui-allow-utf8-titles')) self.allow_utf8_initial = self.allow_utf8.currentIndex() + def prohibit_start_checked(self, status): + self.prohibit_start_rationale.setVisible(bool(status)) + self.prohibit_start_rationale.setFocus() + + def prohibit_start_rationale_edited(self, text): + self.prohibit_start_checkbox.setChecked(bool(text)) + self.prohibit_start_rationale.setStyleSheet( + "" if bool(text) else "#prohibit_start_rationale { color: red;}" + ) + def enable_seamless(self): try: self.vm.run_service_for_stdio("qubes.SetGuiMode", input=b'SEAMLESS') @@ -1146,6 +1168,17 @@ def __apply_advanced_tab__(self): except qubesadmin.exc.QubesException as ex: msg.append(str(ex)) + 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 + ): + rationale = self.prohibit_start_rationale.text() + if bool(rationale) and self.prohibit_start_checkbox.isChecked(): + self.vm.features["prohibit-start"] = rationale + else: + del self.vm.features["prohibit-start"] + if self.allow_fullscreen_initial !=\ self.allow_fullscreen.currentIndex(): try: diff --git a/qubesmanager/tests/test_vm_settings.py b/qubesmanager/tests/test_vm_settings.py index 9cae6a3b3..9619847fd 100644 --- a/qubesmanager/tests/test_vm_settings.py +++ b/qubesmanager/tests/test_vm_settings.py @@ -1028,6 +1028,41 @@ def test_215_bootmode_appvm_nondefault(settings_fixture): "Boot mode not changed" +@check_errors +@pytest.mark.parametrize("settings_fixture", TEST_VMS, indirect=True) +def test_213_prohibit_start(settings_fixture): + settings_window, page, vm_name = settings_fixture + vm = settings_window.qubesapp.domains[vm_name] + + r = vm.features.get("prohibit-start", "") + assert settings_window.prohibit_start_checkbox.isChecked() == bool(r) + if r: + assert settings_window.prohibit_start_rationale.text() == r + + settings_window.prohibit_start_checkbox.setChecked(not bool(r)) + + if bool(r): + settings_window.prohibit_start_rationale.setText("") + expected_call = ( + vm.name, + "admin.vm.feature.Remove", + "prohibit-start", + None, + ) + else: + settings_window.prohibit_start_rationale.setText("RATIONALE") + expected_call = ( + vm.name, + "admin.vm.feature.Set", + "prohibit-start", + "RATIONALE".encode(), + ) + + assert expected_call not in settings_window.qubesapp.actual_calls + settings_window.qubesapp.expected_calls[expected_call] = b"0\x00" + settings_window.accept() + + # FIREWALL TAB @check_errors diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui index 85dfd7946..84b455dd1 100644 --- a/ui/settingsdlg.ui +++ b/ui/settingsdlg.ui @@ -742,14 +742,35 @@ border-width: 1px; 15 - + + + + + + Prohibit start + + + + + + + Rationale for start prohibition... + + + Start prohibition rationale is mandatory! + + + + + + Provides network - + <html><head/><body><p>Allows using this qube as a template for disposables. Disposables will inherit the qube's state (configuration, installed programs, etc.), but their state will not persist between restarts. </p><p>Setting this option will cause this qube to be listed as an option in the &quot;Default disposable template&quot; list for all other qubes.</p><p>Only AppVMs and StandaloneVMs could be turned in to Disposable Templates</p></body></html>