diff --git a/qui/tray/updates.py b/qui/tray/updates.py
index db9114df..f9f87cf3 100644
--- a/qui/tray/updates.py
+++ b/qui/tray/updates.py
@@ -155,11 +155,11 @@ def domain_added(self, _submitter, _event, vmname, *_args, **_kwargs):
vm = self.qapp.domains[vmname]
updated: bool = qui.utils.check_update(vm)
supported: bool = qui.utils.check_support(vm)
+ except exc.QubesDaemonCommunicationError:
+ return
except exc.QubesException:
# a disposableVM crashed on start
return
- except exc.QubesDaemonCommunicationError:
- return
if not updated:
self.vms_needing_update.add(vm.name)
self.update_indicator_state()
diff --git a/qui/updater/intro_page.py b/qui/updater/intro_page.py
index 47cd311d..3e6dcb02 100644
--- a/qui/updater/intro_page.py
+++ b/qui/updater/intro_page.py
@@ -94,12 +94,30 @@ def populate_vm_list(self, qapp, settings):
for vm in qapp.domains:
if vm.klass == 'AdminVM':
try:
+ if settings.hide_skipped and bool(vm.features.get( \
+ 'skip-update', False)):
+ continue
state = bool(vm.features.get('updates-available', False))
except exc.QubesDaemonCommunicationError:
state = False
self.list_store.append_vm(vm, state)
+ to_update=set()
+ if settings.hide_updated:
+ cmd = ['qubes-vm-update', '--quiet', '--dry-run',
+ '--update-if-stale', str(settings.update_if_stale)]
+ to_update = self._get_stale_qubes(cmd)
+
for vm in qapp.domains:
+ try:
+ if settings.hide_skipped and bool(vm.features.get( \
+ 'skip-update', False)):
+ continue
+ if settings.hide_updated and not vm.name in to_update:
+ # TODO: Make re-filtering possible without App restart
+ continue
+ except exc.QubesDaemonCommunicationError:
+ continue
if getattr(vm, 'updateable', False) and vm.klass != 'AdminVM':
self.list_store.append_vm(vm)
diff --git a/qui/updater/tests/conftest.py b/qui/updater/tests/conftest.py
index 2025881f..16d9cc0f 100644
--- a/qui/updater/tests/conftest.py
+++ b/qui/updater/tests/conftest.py
@@ -61,6 +61,9 @@ def test_qapp_impl():
add_dom0_feature(qapp, 'gui-default-allow-utf8-titles', '')
add_dom0_feature(qapp, 'gui-default-trayicon-mode', '')
add_dom0_feature(qapp, 'qubes-vm-update-update-if-stale', None)
+ add_dom0_feature(qapp, 'skip-update', None)
+ add_dom0_feature(qapp, 'qubes-vm-update-hide-skipped', None)
+ add_dom0_feature(qapp, 'qubes-vm-update-hide-updated', None)
# setup labels
qapp.expected_calls[('dom0', 'admin.label.List', None, None)] = \
@@ -140,6 +143,7 @@ def test_qapp_impl():
add_feature_to_all(qapp, 'servicevm',
['sys-usb', 'sys-firewall', 'sys-net'])
add_feature_to_all(qapp, 'os-eol', [])
+ add_feature_to_all(qapp, 'skip-update', [])
return qapp
@@ -254,6 +258,8 @@ def __init__(self):
self.restart_service_vms = True
self.restart_other_vms = True
self.max_concurrency = None
+ self.hide_skipped = True
+ self.hide_updated = False
return MockSettings()
diff --git a/qui/updater/updater.py b/qui/updater/updater.py
index 47443f36..3075ff11 100644
--- a/qui/updater/updater.py
+++ b/qui/updater/updater.py
@@ -152,6 +152,7 @@ def perform_setup(self, *_args, **_kwargs):
self.log,
refresh_callback=self.intro_page.refresh_update_list,
overrides=overrides,
+ advanced=self.cliargs.show_advanced_settings
)
headers = [(3, "intro_name"), (3, "progress_name"), (3, "summary_name"),
@@ -418,6 +419,12 @@ def parse_args(args, app):
'Interaction will be required in the event '
'of an update error.')
+ parser.add_argument('--show-advanced-settings', action='store_true',
+ help='Show setting options for filtering-out already '
+ 'updated or skipped VMs from VM selection list. '
+ 'These options require application restart to '
+ 'take effect.')
+
args = parser.parse_args(args)
args.non_default_select = {
diff --git a/qui/updater/updater_settings.py b/qui/updater/updater_settings.py
index f80a65f7..e8ebcacc 100644
--- a/qui/updater/updater_settings.py
+++ b/qui/updater/updater_settings.py
@@ -53,6 +53,8 @@ class Settings:
MAX_UPDATE_IF_STALE = 99
DEFAULT_RESTART_SERVICEVMS = True
DEFAULT_RESTART_OTHER_VMS = False
+ DEFAULT_HIDE_SKIPPED = True
+ DEFAULT_HIDE_UPDATED = False
def __init__(
self,
@@ -61,6 +63,7 @@ def __init__(
log,
refresh_callback: Callable,
overrides: OverriddenSettings = OverriddenSettings(),
+ advanced: Optional[bool] = False
):
self.qapp = qapp
self.log = log
@@ -78,6 +81,14 @@ def __init__(
self.settings_window: Gtk.Window = self.builder.get_object(
"main_window")
+
+ # Filtering options for advanced users
+ self.builder.get_object("filtering_options").set_visible(advanced)
+ self.builder.get_object("hide_skipped").set_visible(advanced)
+ self.builder.get_object("hide_updated").set_visible(advanced)
+ self.settings_window.set_default_size(
+ self.settings_window.get_size().width, 640 if advanced else 550)
+
self.settings_window.set_transient_for(main_window)
self.settings_window.connect("delete-event", self.close_without_saving)
@@ -108,6 +119,12 @@ def __init__(
self.restart_other_checkbox.connect(
"toggled", self._show_restart_exceptions)
+ self.hide_skipped_checkbox: Gtk.CheckButton = \
+ self.builder.get_object("hide_skipped")
+
+ self.hide_updated_checkbox: Gtk.CheckButton = \
+ self.builder.get_object("hide_updated")
+
self.available_vms = [
vm for vm in self.qapp.domains
if vm.klass == 'DispVM' and not vm.auto_cleanup
@@ -138,6 +155,8 @@ def __init__(
self._init_restart_other_vms: Optional[bool] = None
self._init_limit_concurrency: Optional[bool] = None
self._init_max_concurrency: Optional[int] = None
+ self._init_hide_skipped: Optional[bool] = None
+ self._init_hide_updated: Optional[bool] = None
@property
def update_if_stale(self) -> int:
@@ -176,6 +195,18 @@ def restart_other_vms(self) -> bool:
self.vm, "qubes-vm-update-restart-other",
Settings.DEFAULT_RESTART_OTHER_VMS)
+ @property
+ def hide_skipped(self) -> bool:
+ return get_boolean_feature(
+ self.vm, "qubes-vm-update-hide-skipped",
+ Settings.DEFAULT_HIDE_SKIPPED)
+
+ @property
+ def hide_updated(self) -> bool:
+ return get_boolean_feature(
+ self.vm, "qubes-vm-update-hide-updated",
+ Settings.DEFAULT_HIDE_UPDATED)
+
@property
def max_concurrency(self) -> Optional[int]:
"""Return the current (set by this window or manually) option value."""
@@ -210,6 +241,11 @@ def load_settings(self):
if self._init_limit_concurrency:
self.max_concurrency_button.set_value(self._init_max_concurrency)
+ self._init_hide_skipped = self.hide_skipped
+ self._init_hide_updated = self.hide_updated
+ self.hide_skipped_checkbox.set_active(self._init_hide_skipped)
+ self.hide_updated_checkbox.set_active(self._init_hide_updated)
+
def _show_restart_exceptions(self, _emitter=None):
if self.restart_other_checkbox.get_active():
self.restart_exceptions_page.show_all()
@@ -226,7 +262,7 @@ def _limit_concurrency_toggled(self, _emitter=None):
def show(self):
"""Show a hidden window."""
self.load_settings()
- self.settings_window.show_all()
+ self.settings_window.show()
self._show_restart_exceptions()
self._limit_concurrency_toggled()
@@ -262,6 +298,20 @@ def save_and_close(self, _emitter):
default=Settings.DEFAULT_RESTART_OTHER_VMS
)
+ self._save_option(
+ name="hide-skipped",
+ value=self.hide_skipped_checkbox.get_active(),
+ init=self._init_hide_skipped,
+ default=Settings.DEFAULT_HIDE_SKIPPED
+ )
+
+ self._save_option(
+ name="hide-updated",
+ value=self.hide_updated_checkbox.get_active(),
+ init=self._init_hide_updated,
+ default=Settings.DEFAULT_HIDE_UPDATED
+ )
+
limit_concurrency = self.limit_concurrency_checkbox.get_active()
if self._init_limit_concurrency or limit_concurrency:
if limit_concurrency:
diff --git a/qui/updater_settings.glade b/qui/updater_settings.glade
index 1a181560..0a6a2648 100644
--- a/qui/updater_settings.glade
+++ b/qui/updater_settings.glade
@@ -9,7 +9,7 @@
Qubes OS Updater Settings
False
458
- 571
+ 640
+
+
+
+ False
+ True
+ 11
+
+
+
+
+
+ False
+ True
+ 12
+
+
+
+
+
+ False
+ True
+ 13
+
+