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 "Default disposable template" list for all other qubes.</p><p>Only AppVMs and StandaloneVMs could be turned in to Disposable Templates</p></body></html>