diff --git a/qubes_config/global_config.glade b/qubes_config/global_config.glade
index 10c5d18f..19bba8e2 100644
--- a/qubes_config/global_config.glade
+++ b/qubes_config/global_config.glade
@@ -976,7 +976,7 @@
False
natural
-
+
0
- 11
+ 12
2
@@ -1085,7 +1085,7 @@
0
- 14
+ 15
@@ -1101,7 +1101,7 @@
1
- 14
+ 15
@@ -1114,7 +1114,7 @@
1
- 15
+ 16
@@ -1127,7 +1127,7 @@
1
- 16
+ 17
@@ -1175,7 +1175,7 @@
1
- 6
+ 7
@@ -1199,7 +1199,7 @@
1
- 7
+ 8
@@ -1223,7 +1223,7 @@
1
- 5
+ 6
@@ -1238,7 +1238,7 @@
0
- 18
+ 19
2
@@ -1255,7 +1255,7 @@
0
- 19
+ 20
2
@@ -1271,7 +1271,7 @@
0
- 23
+ 24
2
@@ -1283,7 +1283,7 @@
1
- 25
+ 26
@@ -1295,7 +1295,7 @@
0
- 26
+ 27
2
@@ -1312,7 +1312,7 @@
0
- 24
+ 25
2
@@ -1357,7 +1357,7 @@
0
- 5
+ 6
@@ -1401,7 +1401,7 @@
0
- 6
+ 7
@@ -1446,7 +1446,7 @@
0
- 7
+ 8
@@ -1462,7 +1462,7 @@
0
- 12
+ 13
2
@@ -1509,7 +1509,7 @@
0
- 13
+ 14
2
@@ -1554,7 +1554,7 @@
0
- 15
+ 16
@@ -1578,7 +1578,7 @@
0
- 10
+ 11
@@ -1590,7 +1590,7 @@
0
- 17
+ 18
@@ -1602,7 +1602,7 @@
0
- 22
+ 23
@@ -1633,7 +1633,7 @@
0
- 16
+ 17
@@ -1648,7 +1648,7 @@
0
- 25
+ 26
@@ -1663,7 +1663,7 @@
0
- 20
+ 21
@@ -1678,7 +1678,7 @@
0
- 21
+ 22
@@ -1718,7 +1718,7 @@
1
- 20
+ 21
@@ -1758,7 +1758,7 @@
1
- 21
+ 22
@@ -1815,7 +1815,7 @@
0
- 8
+ 9
@@ -1882,7 +1882,7 @@
1
- 8
+ 9
@@ -1925,7 +1925,7 @@
1
- 9
+ 10
@@ -1953,7 +1953,33 @@
0
- 9
+ 10
+
+
+
+
+
+ 0
+ 5
+ 2
diff --git a/qubes_config/global_config/basics_handler.py b/qubes_config/global_config/basics_handler.py
index a8f906ea..4c8b828f 100644
--- a/qubes_config/global_config/basics_handler.py
+++ b/qubes_config/global_config/basics_handler.py
@@ -43,6 +43,7 @@
get_feature,
get_boolean_feature,
apply_feature_change,
+ inadvisable_selection,
)
import gi
@@ -130,9 +131,11 @@ def __init__(
self,
qapp: qubesadmin.Qubes,
trait_holder: Any,
- trait_name: str,
widget: Gtk.ComboBox,
+ trait_name: str,
+ gtk_builder: Gtk.Builder = None,
vm_filter: Optional[Callable] = None,
+ vm_inadvisable: Optional[Callable] = None,
readable_name: Optional[str] = None,
additional_options: Dict[Any | str | None, str] | None = None,
show_internal: bool = False,
@@ -146,7 +149,10 @@ def __init__(
self.model = VMListModeler(
combobox=self.widget,
qapp=qapp,
+ gtk_builder=gtk_builder,
+ trait_name=trait_name,
filter_function=vm_filter,
+ vm_inadvisable=vm_inadvisable,
current_value=self.get_current_value(),
style_changes=True,
additional_options=additional_options,
@@ -644,7 +650,9 @@ def __init__(self, gtk_builder: Gtk.Builder, qapp: qubesadmin.Qubes):
trait_holder=self.qapp,
trait_name="clockvm",
widget=self.clockvm_combo,
+ gtk_builder=gtk_builder,
vm_filter=self._clock_vm_filter,
+ vm_inadvisable=self._clock_vm_inadvisable,
readable_name=_("Clock qube"),
additional_options=NONE_CATEGORY,
)
@@ -742,7 +750,15 @@ def __init__(self, gtk_builder: Gtk.Builder, qapp: qubesadmin.Qubes):
@staticmethod
def _clock_vm_filter(vm) -> bool:
- return vm.klass != "TemplateVM"
+ return vm.klass != "TemplateVM" and (vm.klass == "AdminVM" or vm.is_networked())
+
+ @staticmethod
+ def _clock_vm_inadvisable(vm) -> str:
+ return (
+ inadvisable_selection(
+ readable_name="clock qube", vm=vm, warn_dispvm_template=True
+ ),
+ )
@staticmethod
def _default_template_filter(vm) -> bool:
diff --git a/qubes_config/global_config/device_attachments.py b/qubes_config/global_config/device_attachments.py
index 0299821f..4974beb1 100644
--- a/qubes_config/global_config/device_attachments.py
+++ b/qubes_config/global_config/device_attachments.py
@@ -31,6 +31,7 @@
ProtocolError,
)
+from ..widgets.utils import inadvisable_selection
from ..widgets.gtk_widgets import TokenName
from ..widgets.gtk_utils import show_error
from .device_widgets import (
@@ -122,7 +123,12 @@ def __init__(
self.qapp,
"edit_device",
[],
- filter_function=lambda vm: vm.klass != "AdminVM",
+ filter_function=lambda vm: vm.klass not in ["AdminVM", "TemplateVM"],
+ vm_inadvisable=lambda vm: inadvisable_selection(
+ readable_name="qube for having devices",
+ vm=vm,
+ warn_dispvm_template=True,
+ ),
)
self.backend_vm: Optional[qubesadmin.vm.QubesVM] = None
@@ -398,7 +404,12 @@ def __init__(
self.qapp,
"required_device",
[],
- filter_function=lambda vm: vm.klass != "AdminVM",
+ filter_function=lambda vm: vm.klass not in ["AdminVM", "TemplateVM"],
+ vm_inadvisable=lambda vm: inadvisable_selection(
+ readable_name="qube for required device",
+ vm=vm,
+ warn_dispvm_template=True,
+ ),
)
self.dev_modeler = self.fill_combo_with_devices(
diff --git a/qubes_config/global_config/global_config.py b/qubes_config/global_config/global_config.py
index 8bb9292e..4df65dec 100644
--- a/qubes_config/global_config/global_config.py
+++ b/qubes_config/global_config/global_config.py
@@ -40,7 +40,7 @@
show_dialog,
)
from ..widgets.gtk_widgets import ProgressBarDialog, ViewportHandler
-from ..widgets.utils import open_url_in_disposable
+from ..widgets.utils import open_url_in_disposable, inadvisable_selection
from .page_handler import PageHandler
from .policy_handler import PolicyHandler, VMSubsetPolicyHandler
from .policy_rules import (
@@ -434,6 +434,13 @@ def perform_setup(self):
service_name="qubes.Gpg",
policy_file_name="50-config-splitgpg",
default_policy="",
+ filter_function=lambda vm: vm.klass not in ["AdminVM", "TemplateVM"]
+ and not vm.features.get("service.guivm")
+ and not vm.features.get("service.audiovm")
+ and not getattr(vm, "provides_network", False),
+ vm_inadvisable=lambda vm: inadvisable_selection(
+ readable_name="key qube", vm=vm, warn_dispvm_template=True
+ ),
main_rule_class=RuleSimpleNoAllow,
main_verb_description=SimpleVerbDescription(
{
diff --git a/qubes_config/global_config/policy_handler.py b/qubes_config/global_config/policy_handler.py
index 57ecce76..0fb63b97 100644
--- a/qubes_config/global_config/policy_handler.py
+++ b/qubes_config/global_config/policy_handler.py
@@ -44,7 +44,7 @@
import qubesadmin
import qubesadmin.vm
-from ..widgets.utils import compare_rule_lists
+from ..widgets.utils import compare_rule_lists, get_boolean_feature
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
@@ -562,6 +562,8 @@ def __init__(
main_rule_class: Type[AbstractRuleWrapper],
exception_verb_description: AbstractVerbDescription,
exception_rule_class: Type[AbstractRuleWrapper],
+ filter_function: Callable[[qubesadmin.vm.QubesVM], bool] | None = None,
+ vm_inadvisable: Callable[[qubesadmin.vm.QubesVM], bool] | None = None,
):
"""
:param qapp: Qubes object
@@ -581,6 +583,7 @@ def __init__(
the exception rules
:param exception_rule_class: class to be used for exception Rules, must
inherit from AbstractRuleWrapper
+ :param vm_inadvisable: warn if selection is not advisable
"""
self.select_qubes: Set[str] = set()
self.main_verb_description = main_verb_description
@@ -619,7 +622,25 @@ def __init__(
# populate combo
self.select_qube_model = VMListModeler(
- combobox=self.select_qube_combo, qapp=self.qapp
+ combobox=self.select_qube_combo,
+ trait_name=prefix,
+ gtk_builder=gtk_builder,
+ qapp=self.qapp,
+ filter_function=(
+ (
+ lambda vm: filter_function(vm)
+ and get_boolean_feature(
+ vm, "supported-rpc." + service_name, recurse_template=True
+ )
+ )
+ if filter_function
+ else (
+ lambda vm: get_boolean_feature(
+ vm, "supported-rpc." + service_name, recurse_template=True
+ )
+ )
+ ),
+ vm_inadvisable=vm_inadvisable,
)
# connect events
diff --git a/qubes_config/global_config/updates_handler.py b/qubes_config/global_config/updates_handler.py
index a7dfd00d..27765ff6 100644
--- a/qubes_config/global_config/updates_handler.py
+++ b/qubes_config/global_config/updates_handler.py
@@ -28,7 +28,11 @@
from qrexec.client import call as qrexec_call
from ..widgets.gtk_widgets import VMListModeler, NONE_CATEGORY
-from ..widgets.utils import get_boolean_feature, apply_feature_change
+from ..widgets.utils import (
+ get_boolean_feature,
+ apply_feature_change,
+ inadvisable_selection,
+)
from .page_handler import PageHandler
from .policy_rules import RuleTargeted, SimpleVerbDescription
from .policy_handler import PolicyHandler
@@ -681,11 +685,19 @@ def __init__(
self.dom0_updatevm_model = VMListModeler(
combobox=self.dom0_updatevm_combo,
+ trait_name="dom0_updatevm",
+ gtk_builder=gtk_builder,
qapp=self.qapp,
filter_function=(
lambda vm: vm.klass != "TemplateVM"
and vm.klass != "AdminVM"
and vm.is_networked()
+ and get_boolean_feature(
+ vm, "supported-rpc.qubes.TemplateDownload", recurse_template=True
+ )
+ ),
+ vm_inadvisable=lambda vm: inadvisable_selection(
+ readable_name="dom0 updates proxy", vm=vm, warn_dispvm_template=True
),
current_value=self.qapp.updatevm,
additional_options=NONE_CATEGORY,
diff --git a/qubes_config/global_config/usb_devices.py b/qubes_config/global_config/usb_devices.py
index 096613e8..f8910b5f 100644
--- a/qubes_config/global_config/usb_devices.py
+++ b/qubes_config/global_config/usb_devices.py
@@ -27,7 +27,12 @@
from qubesadmin.device_protocol import DeviceCategory
from ..widgets.gtk_widgets import TokenName, TextModeler, VMListModeler
-from ..widgets.utils import get_feature, apply_feature_change
+from ..widgets.utils import (
+ get_feature,
+ get_boolean_feature,
+ apply_feature_change,
+ dispvm_template_inadvisable,
+)
from ..widgets.gtk_utils import ask_question, show_error
from .page_handler import PageHandler
from .policy_rules import RuleTargetedAdminVM, Rule
@@ -317,6 +322,7 @@ def __init__(
"usb_u2f_enable_some",
self.initially_enabled_vms,
lambda vm: vm in self.available_vms,
+ vm_inadvisable=lambda vm: dispvm_template_inadvisable(vm),
)
self.register_some_handler = VMFlowboxHandler(
@@ -326,6 +332,7 @@ def __init__(
self.initial_register_vms,
lambda vm: vm in self.available_vms,
verification_callback=self._verify_additional_vm,
+ vm_inadvisable=lambda vm: dispvm_template_inadvisable(vm),
)
self.blanket_handler = VMFlowboxHandler(
@@ -335,6 +342,7 @@ def __init__(
self.initial_blanket_vms,
lambda vm: vm in self.available_vms,
verification_callback=self._verify_additional_vm,
+ vm_inadvisable=lambda vm: dispvm_template_inadvisable(vm),
)
self.widget_to_box = {
@@ -465,13 +473,16 @@ def load_rules_for_usb_qube(self):
self.error_handler.clear_all_errors()
for vm in self.qapp.domains:
- if vm.features.check_with_template(self.SUPPORTED_SERVICE_FEATURE):
- if vm == usb_qube:
- continue
+ if vm == usb_qube:
+ continue
+ if vm.features.check_with_template(self.SUPPORTED_SERVICE_FEATURE) and not (
+ vm.klass == "TemplateVM"
+ or getattr(vm, "provides_network", False)
+ or get_boolean_feature(vm, "service.audiovm")
+ or get_boolean_feature(vm, "service.guivm")
+ ):
self.available_vms.append(vm)
if get_feature(vm, self.SERVICE_FEATURE):
- if vm == usb_qube:
- continue
self.initially_enabled_vms.append(vm)
if not self.available_vms:
diff --git a/qubes_config/global_config/vm_flowbox.py b/qubes_config/global_config/vm_flowbox.py
index 03e88fd2..f20660bd 100644
--- a/qubes_config/global_config/vm_flowbox.py
+++ b/qubes_config/global_config/vm_flowbox.py
@@ -109,6 +109,7 @@ def __init__(
prefix: str,
initial_vms: List[qubesadmin.vm.QubesVM],
filter_function: Optional[Callable] = None,
+ vm_inadvisable: Optional[Callable] = None,
verification_callback: Optional[Callable[[qubesadmin.vm.QubesVM], bool]] = None,
):
"""
@@ -117,6 +118,7 @@ def __init__(
:param prefix: widget name prefix (see above)
:param initial_vms: list of initially selected vms
:param filter_function: function to filter vms available in the dropdown
+ :param vm_inadvisable: function to warn if selection is inadvisable
:param verification_callback: if provided, will be called before adding
a vm; return True if verification was successful and false if it has
failed
@@ -134,8 +136,11 @@ def __init__(
self.add_qube_model = VMListModeler(
combobox=self.qube_combo,
+ trait_name=prefix,
+ gtk_builder=gtk_builder,
qapp=self.qapp,
filter_function=filter_function,
+ vm_inadvisable=vm_inadvisable,
)
self.add_qube_model.connect_change_callback(self._check_for_add_validity)
diff --git a/qubes_config/qubes-global-config-base.css b/qubes_config/qubes-global-config-base.css
index 0b2a75e4..639954c2 100644
--- a/qubes_config/qubes-global-config-base.css
+++ b/qubes_config/qubes-global-config-base.css
@@ -187,6 +187,16 @@ separator {
background: @problem-background;
}
+.problem_box_to_previous {
+ margin: 0px 0px 10px 0px;
+ padding: 10px;
+ border-width: 2px;
+ border-style: solid;
+ border-radius: 4px;
+ border-color: @problem-frame;
+ background: @problem-background;
+}
+
.problem_list {
margin-top: 10px;
background: @problem-background;
@@ -282,4 +292,4 @@ separator {
color: @red-label;
font-style: italic;
font-weight: bold;
-}
\ No newline at end of file
+}
diff --git a/qubes_config/tests/test_policy_handler.py b/qubes_config/tests/test_policy_handler.py
index 2414d677..2c28a9be 100644
--- a/qubes_config/tests/test_policy_handler.py
+++ b/qubes_config/tests/test_policy_handler.py
@@ -886,6 +886,11 @@ def test_subset_handler(test_builder, test_qapp, test_policy_manager: PolicyMana
default_policy = """
TestService * @anyvm test-blue allow"""
+ for qube in test_qapp.domains:
+ # pylint: disable=protected-access
+ test_qapp._qubes[qube].features["supported-rpc.TestService"] = "1"
+ test_qapp.update_vm_calls()
+
handler = VMSubsetPolicyHandler(
qapp=test_qapp,
gtk_builder=test_builder,
@@ -973,6 +978,11 @@ def test_subset_handler_limited_choice(
TestService * @anyvm test-blue allow
TestService * @anyvm test-red allow"""
+ for qube in test_qapp.domains:
+ # pylint: disable=protected-access
+ test_qapp._qubes[qube].features["supported-rpc.TestService"] = "1"
+ test_qapp.update_vm_calls()
+
handler = VMSubsetPolicyHandler(
qapp=test_qapp,
gtk_builder=test_builder,
@@ -1066,6 +1076,11 @@ def test_subset_handler_remove_choice(
TestService * @anyvm test-blue allow
TestService * @anyvm vault ask"""
+ for qube in test_qapp.domains:
+ # pylint: disable=protected-access
+ test_qapp._qubes[qube].features["supported-rpc.TestService"] = "1"
+ test_qapp.update_vm_calls()
+
handler = VMSubsetPolicyHandler(
qapp=test_qapp,
gtk_builder=test_builder,
@@ -1122,6 +1137,11 @@ def test_subset_handler_duplicates(
default_policy = """
TestService * @anyvm vault ask"""
+ for qube in test_qapp.domains:
+ # pylint: disable=protected-access
+ test_qapp._qubes[qube].features["supported-rpc.TestService"] = "1"
+ test_qapp.update_vm_calls()
+
handler = VMSubsetPolicyHandler(
qapp=test_qapp,
gtk_builder=test_builder,
@@ -1161,6 +1181,11 @@ def test_subset_handler_duplicates_load(
TestService * @anyvm vault ask
"""
+ for qube in test_qapp.domains:
+ # pylint: disable=protected-access
+ test_qapp._qubes[qube].features["supported-rpc.TestService"] = "1"
+ test_qapp.update_vm_calls()
+
handler = VMSubsetPolicyHandler(
qapp=test_qapp,
gtk_builder=test_builder,
@@ -1191,6 +1216,11 @@ def test_subset_handler_unsupported(
TestService * @anyvm vault allow target=test-blue
"""
+ for qube in test_qapp.domains:
+ # pylint: disable=protected-access
+ test_qapp._qubes[qube].features["supported-rpc.TestService"] = "1"
+ test_qapp.update_vm_calls()
+
test_policy_manager.policy_client.policy_replace("c-test", current_policy, "any")
handler = VMSubsetPolicyHandler(
diff --git a/qubes_config/tests/test_usb_devices.py b/qubes_config/tests/test_usb_devices.py
index 40087c02..9d8f53c0 100644
--- a/qubes_config/tests/test_usb_devices.py
+++ b/qubes_config/tests/test_usb_devices.py
@@ -350,7 +350,7 @@ def test_u2f_handler_init(test_qapp, test_policy_manager, real_builder):
assert handler.enable_check.get_active()
assert handler.enable_some_handler.selected_vms == [testvm]
assert handler.enable_some_handler.add_qube_model.is_vm_available(testvm)
- assert handler.enable_some_handler.add_qube_model.is_vm_available(fedora35)
+ assert not handler.enable_some_handler.add_qube_model.is_vm_available(fedora35)
assert not handler.enable_some_handler.add_qube_model.is_vm_available(testred)
assert not handler.enable_some_handler.add_qube_model.is_vm_available(sysusb)
@@ -856,9 +856,9 @@ def test_u2f_handler_save_complex(test_qapp, test_policy_manager, real_builder):
)
assert (
call(test_qapp.domains["fedora-35"], handler.SERVICE_FEATURE, True)
- in mock_apply.mock_calls
+ not in mock_apply.mock_calls
)
- assert len(mock_apply.mock_calls) == 3
+ assert len(mock_apply.mock_calls) == 2
expected_rules = handler.policy_manager.text_to_rules(
"""
@@ -915,9 +915,9 @@ def test_u2f_handler_save_complex_2(test_qapp, test_policy_manager, real_builder
)
assert (
call(test_qapp.domains["fedora-35"], handler.SERVICE_FEATURE, True)
- in mock_apply.mock_calls
+ not in mock_apply.mock_calls
)
- assert len(mock_apply.mock_calls) == 3
+ assert len(mock_apply.mock_calls) == 2
expected_rules = handler.policy_manager.text_to_rules(
"""
@@ -933,14 +933,14 @@ def test_u2f_handler_save_complex_2(test_qapp, test_policy_manager, real_builder
def test_u2f_handler_add_without_service(test_qapp, test_policy_manager, real_builder):
sys_usb = test_qapp.domains["sys-usb"]
- fedora35 = test_qapp.domains["fedora-35"]
+ testblue = test_qapp.domains["test-blue"]
testvm = test_qapp.domains["test-vm"]
handler = U2FPolicyHandler(test_qapp, test_policy_manager, real_builder, {sys_usb})
assert handler.get_unsaved() == ""
# settings from conftest: only vms that have this available are 'test-vm'
- # and 'fedora-35', only test-vm can use the service, policy is default
+ # and 'test-blue', only test-vm can use the service, policy is default
handler.register_check.set_active(True)
handler.register_some_radio.set_active(True)
@@ -948,7 +948,7 @@ def test_u2f_handler_add_without_service(test_qapp, test_policy_manager, real_bu
assert not handler.register_some_handler.selected_vms
assert handler.enable_some_handler.selected_vms == [testvm]
- handler.register_some_handler.add_qube_model.select_value("fedora-35")
+ handler.register_some_handler.add_qube_model.select_value("test-blue")
# refuse
with patch("qubes_config.global_config.usb_devices.ask_question") as mock_question:
mock_question.return_value = Gtk.ResponseType.NO
@@ -962,9 +962,9 @@ def test_u2f_handler_add_without_service(test_qapp, test_policy_manager, real_bu
mock_question.return_value = Gtk.ResponseType.YES
handler.register_some_handler.add_button.clicked()
assert mock_question.mock_calls
- assert handler.register_some_handler.selected_vms == [fedora35]
+ assert handler.register_some_handler.selected_vms == [testblue]
- assert handler.enable_some_handler.selected_vms == [fedora35, testvm]
+ assert handler.enable_some_handler.selected_vms == [testblue, testvm]
def test_devices_handler_unsaved(test_qapp, test_policy_manager, real_builder):
@@ -981,6 +981,12 @@ def test_devices_handler_unsaved(test_qapp, test_policy_manager, real_builder):
b"_function='0' _bus='00' _libvirt_name='pci_0000_00_0d_0' "
b"_device='0d'\n"
)
+ test_qapp.expected_calls[
+ ("test-vm", "admin.vm.feature.Get", "service.audiovm", None)
+ ] = b"0\x00"
+ test_qapp.expected_calls[
+ ("test-vm", "admin.vm.feature.Get", "service.guivm", None)
+ ] = b"0\x00"
handler = DevicesHandler(test_qapp, test_policy_manager, real_builder)
@@ -1023,6 +1029,12 @@ def test_devices_handler_detect_usbvms(test_qapp, test_policy_manager, real_buil
b"_function='0' _bus='00' _libvirt_name='pci_0000_00_0d_0' "
b"_device='0d'\n"
)
+ test_qapp.expected_calls[
+ ("test-vm", "admin.vm.feature.Get", "service.audiovm", None)
+ ] = b"0\x00"
+ test_qapp.expected_calls[
+ ("test-vm", "admin.vm.feature.Get", "service.guivm", None)
+ ] = b"0\x00"
handler = DevicesHandler(test_qapp, test_policy_manager, real_builder)
diff --git a/qubes_config/widgets/gtk_widgets.py b/qubes_config/widgets/gtk_widgets.py
index 68689487..ab365371 100644
--- a/qubes_config/widgets/gtk_widgets.py
+++ b/qubes_config/widgets/gtk_widgets.py
@@ -230,7 +230,10 @@ def __init__(
self,
combobox: Gtk.ComboBox,
qapp: qubesadmin.Qubes,
+ trait_name: str = "",
+ gtk_builder: Gtk.Builder = None,
filter_function: Callable[[qubesadmin.vm.QubesVM], bool] | None = None,
+ vm_inadvisable: Callable[[qubesadmin.vm.QubesVM], bool] | None = None,
event_callback: Callable[[], None] | None = None,
default_value: qubesadmin.vm.QubesVM | str | None = None,
current_value: qubesadmin.vm.QubesVM | str | None = None,
@@ -241,9 +244,13 @@ def __init__(
"""
:param combobox: target ComboBox object
:param qapp: Qubes object, necessary to retrieve VM info
+ :param trait_name: trait that is being considered
+ :param gtk_builder: builder passed from upstream
:param filter_function: function used to filter VMs, must take as input
QubesVM object and return bool; caution: remember not all properties
are always available for all VMs, in particular dom0 can cause problems
+ :param vm_inadvisable: function used to return human-readable information if
+ selected qube is not recommended.
:param event_callback: function to be called whenever combobox value
changes
:param default_value: default VM (will get a (default) decoration
@@ -262,6 +269,7 @@ def __init__(
self.qapp = qapp
self.combo = combobox
self.entry_box = self.combo.get_child()
+ self.vm_inadvisable = vm_inadvisable
self.change_function = event_callback
self.style_changes = style_changes
self.show_internal = show_internal
@@ -279,6 +287,15 @@ def __init__(
self._initial_id = None
+ if gtk_builder:
+ self.warn_box = gtk_builder.get_object(trait_name + "_warn_box")
+ self.warn_label: Gtk.Label = gtk_builder.get_object(
+ trait_name + "_warn_label"
+ )
+ self.warn_box.set_visible(False)
+ else:
+ self.warn_box = None
+
if current_value:
self.select_value(current_value)
elif default_value:
@@ -397,17 +414,28 @@ def _get_valid_qube_name(self):
def _combo_change(self, _widget):
name = self._get_valid_qube_name()
+ inadvisable = ""
if name:
entry = self._entries[name]
self.entry_box.set_icon_from_pixbuf(
Gtk.EntryIconPosition.PRIMARY, entry["icon"]
)
+ vm = entry["vm"]
+ if vm and self.vm_inadvisable and (inadvisable := self.vm_inadvisable(vm)):
+ self._warn(inadvisable)
else:
self.entry_box.set_icon_from_pixbuf(
Gtk.EntryIconPosition.PRIMARY, load_icon("gtk-find", 18, 18)
)
+ if (
+ self.warn_box is not None
+ and not inadvisable
+ and self.warn_box.get_visible()
+ ):
+ self.warn_box.set_visible(False)
+
if self.change_function:
self.change_function()
@@ -500,6 +528,11 @@ def select_value(self, vm_name):
if entry["api_name"] == vm_name:
self.combo.set_active_id(display_name)
+ def _warn(self, error_descr: str):
+ self.warn_label.set_text(error_descr)
+ if self.warn_box is not None:
+ self.warn_box.set_visible(True)
+
def is_vm_available(self, vm: qubesadmin.vm.QubesVM) -> bool:
"""Check if given VM is available in the list."""
for entry in self._entries.values():
diff --git a/qubes_config/widgets/utils.py b/qubes_config/widgets/utils.py
index f413390e..1a0b8123 100644
--- a/qubes_config/widgets/utils.py
+++ b/qubes_config/widgets/utils.py
@@ -33,19 +33,25 @@
_ = t.gettext
-def get_feature(vm, feature_name, default_value=None):
+def get_feature(vm, feature_name, default_value=None, recurse_template=False):
"""Get feature, with a working default_value."""
+ if recurse_template:
+ feat = vm.features.check_with_template
+ else:
+ feat = vm.features.get
try:
- return vm.features.get(feature_name, default_value)
+ return feat(feature_name, default_value)
except qubesadmin.exc.QubesDaemonAccessError:
return default_value
-def get_boolean_feature(vm, feature_name, default=False):
+def get_boolean_feature(vm, feature_name, default=False, recurse_template=False):
"""helper function to get a feature converted to a Bool if it does exist.
Necessary because of the true/false in features being coded as 1/empty
string."""
- result = get_feature(vm, feature_name, None)
+ result = get_feature(
+ vm, feature_name, default_value=None, recurse_template=recurse_template
+ )
if result is not None:
result = bool(result)
else:
@@ -141,3 +147,21 @@ def open_url_in_disposable(url: str, qapp: qubesadmin.Qubes):
group=None, target=_open_url_in_dvm, args=[url, default_dvm]
)
open_thread.start()
+
+
+def inadvisable_selection(
+ readable_name: str, vm: qubesadmin.vm.QubesVM, warn_dispvm_template: bool = False
+) -> str:
+ inadvisable = []
+ if warn_dispvm_template:
+ inadvisable.append(dispvm_template_inadvisable(vm))
+ pretty_inadvisable = " ".join(i for i in inadvisable if i)
+ if not pretty_inadvisable:
+ return ""
+ return f"Inadvisable {readable_name.lower()}: {pretty_inadvisable}"
+
+
+def dispvm_template_inadvisable(vm: qubesadmin.vm.QubesVM) -> str:
+ if getattr(vm, "template_for_dispvms", False):
+ return "It's a disposable template, normally not intended for such cases."
+ return ""