diff --git a/icons/scalable/bug-play.svg b/icons/scalable/bug-play.svg
new file mode 100644
index 00000000..07dc41e1
--- /dev/null
+++ b/icons/scalable/bug-play.svg
@@ -0,0 +1 @@
+
diff --git a/icons/scalable/scroll-text.svg b/icons/scalable/scroll-text.svg
new file mode 100644
index 00000000..427e45f8
--- /dev/null
+++ b/icons/scalable/scroll-text.svg
@@ -0,0 +1 @@
+
diff --git a/qui/tray/domains.py b/qui/tray/domains.py
index b6ac967b..dbe674e0 100644
--- a/qui/tray/domains.py
+++ b/qui/tray/domains.py
@@ -58,6 +58,8 @@ def __init__(self):
"unpause": "qubes-vm-unpause",
"files": "qubes-files",
"restart": "qubes-vm-restart",
+ "debug": "bug-play",
+ "logs": "scroll-text",
}
self.icons = {}
@@ -283,11 +285,12 @@ async def perform_action(self):
class LogItem(ActionMenuItem):
- def __init__(self, name, path):
- img = Gtk.Image.new_from_file(
- "/usr/share/icons/HighContrast/16x16/apps/logviewer.png"
+ def __init__(self, name, path, icon_cache):
+ super().__init__(
+ label=name,
+ icon_cache=icon_cache,
+ icon_name="logs",
)
- super().__init__(label=name, img=img)
self.path = path
async def perform_action(self):
@@ -338,6 +341,32 @@ async def perform_action(self):
)
+class RunDebugConsoleItem(VMActionMenuItem):
+ """Run Debug Console menu Item. When activated runs a qvm-console-dispvm."""
+
+ def __init__(self, vm, icon_cache):
+ super().__init__(
+ vm,
+ label=_("Debug Console"),
+ icon_cache=icon_cache,
+ icon_name="debug",
+ )
+ self.visible = False
+ self.connect("show", self.on_show_event)
+
+ def on_show_event(self, widget):
+ if self.visible:
+ widget.show()
+ else:
+ widget.hide()
+
+ async def perform_action(self):
+ # pylint: disable=consider-using-with
+ await asyncio.create_subprocess_exec(
+ "qvm-console-dispvm", self.vm.name, stderr=subprocess.PIPE
+ )
+
+
class OpenFileManagerItem(VMActionMenuItem):
"""Attempts to open a file manager in the VM. If fails, displays an
error message."""
@@ -392,6 +421,11 @@ def __init__(self, vm, app, icon_cache):
self.add(
RunTerminalItem(self.vm, icon_cache, as_root=app.terminal_as_root)
)
+
+ # Debug console for developers, troubleshooting, headless qubes
+ self.debug_console = RunDebugConsoleItem(self.vm, icon_cache)
+ self.add(self.debug_console)
+
self.add(PreferencesItem(self.vm, icon_cache))
self.add(PauseItem(self.vm, icon_cache))
self.add(ShutdownItem(self.vm, icon_cache))
@@ -399,8 +433,25 @@ def __init__(self, vm, app, icon_cache):
self.add(RestartItem(self.vm, icon_cache))
self.set_reserve_toggle_size(False)
+ self.debug_console_update()
self.show_all()
+ def debug_console_update(self, *_args, **_kwargs):
+ # Debug console is shown only if debug property is set, no GUIVM is set
+ # ... or with `expert-mode` feature per qube or per entire GUIVM.
+ if (
+ self.app.expert_mode
+ or getattr(self.vm, "debug")
+ or not getattr(self.vm, "guivm")
+ or not self.vm.features.check_with_template("gui", False)
+ or self.vm.features.get("expert-mode", False)
+ ):
+ self.debug_console.visible = True
+ self.debug_console.show()
+ else:
+ self.debug_console.visible = False
+ self.debug_console.hide()
+
class PausedMenu(Gtk.Menu):
"""The sub-menu for a paused domain"""
@@ -439,7 +490,7 @@ def __init__(self, vm, icon_cache):
for name, path in logs:
if os.path.isfile(path):
- self.add(LogItem(name, path))
+ self.add(LogItem(name, path, icon_cache=icon_cache))
self.add(KillItem(self.vm, icon_cache))
@@ -475,7 +526,7 @@ def __init__(self, vm, icon_cache, working_correctly=True):
for name, path in logs:
if os.path.isfile(path):
- self.add(LogItem(name, path))
+ self.add(LogItem(name, path, icon_cache=icon_cache))
if working_correctly:
self.add(ShutdownItem(self.vm, icon_cache))
@@ -696,6 +747,11 @@ def __init__(self, app_name, qapp, dispatcher, stats_dispatcher):
self.set_application_id(app_name)
self.register() # register Gtk Application
+ # to display debug console for all qubes
+ self.expert_mode = self.qapp.domains[self.qapp.local_name].features.get(
+ "expert-mode", False
+ )
+
def register_events(self):
self.dispatcher.add_handler("connection-established", self.refresh_all)
self.dispatcher.add_handler("domain-pre-start", self.update_domain_item)
@@ -743,8 +799,34 @@ def register_events(self):
self.dispatcher.add_handler("property-set:netvm", self.property_change)
self.dispatcher.add_handler("property-set:label", self.property_change)
+ self.dispatcher.add_handler("property-set:debug", self.debug_change)
+ self.dispatcher.add_handler("property-set:guivm", self.debug_change)
+ self.dispatcher.add_handler("domain-feature-set:gui", self.debug_change)
+ self.dispatcher.add_handler(
+ "domain-feature-delete:gui", self.debug_change
+ )
+ self.dispatcher.add_handler(
+ "domain-feature-set:expert-mode", self.debug_change
+ )
+ self.dispatcher.add_handler(
+ "domain-feature-delete:expert-mode", self.debug_change
+ )
+
self.stats_dispatcher.add_handler("vm-stats", self.update_stats)
+ def debug_change(self, vm, *_args, **_kwargs):
+ if vm == self.qapp.local_name:
+ self.expert_mode = self.qapp.domains[
+ self.qapp.local_name
+ ].features.get("expert-mode", False)
+ vms = self.menu_items
+ else:
+ vms = {vm}
+ for menu in vms:
+ submenu = self.menu_items[menu].get_submenu()
+ if isinstance(submenu, StartedMenu):
+ submenu.debug_console_update()
+
def show_menu(self, _unused, event):
self.terminal_as_root = False
self.tray_menu.popup_at_pointer(event) # None means current event
@@ -1096,6 +1178,21 @@ def _disconnect_signals(self, _event):
"property-set:label", self.property_change
)
+ self.dispatcher.remove_handler("property-set:debug", self.debug_change)
+ self.dispatcher.remove_handler("property-set:guivm", self.debug_change)
+ self.dispatcher.remove_handler(
+ "domain-feature-set:gui", self.debug_change
+ )
+ self.dispatcher.remove_handler(
+ "domain-feature-delete:gui", self.debug_change
+ )
+ self.dispatcher.remove_handler(
+ "domain-feature-set:expert-mode", self.debug_change
+ )
+ self.dispatcher.remove_handler(
+ "domain-feature-delete:expert-mode", self.debug_change
+ )
+
self.stats_dispatcher.remove_handler("vm-stats", self.update_stats)
@property
diff --git a/rpm_spec/qubes-desktop-linux-manager.spec.in b/rpm_spec/qubes-desktop-linux-manager.spec.in
index 7ef5c34a..30026f74 100644
--- a/rpm_spec/qubes-desktop-linux-manager.spec.in
+++ b/rpm_spec/qubes-desktop-linux-manager.spec.in
@@ -266,6 +266,8 @@ gtk-update-icon-cache %{_datadir}/icons/Adwaita &>/dev/null || :
/usr/share/icons/hicolor/scalable/apps/mic-light.svg
/usr/share/icons/hicolor/scalable/apps/mouse-dark.svg
/usr/share/icons/hicolor/scalable/apps/mouse-light.svg
+/usr/share/icons/hicolor/scalable/apps/bug-play.svg
+/usr/share/icons/hicolor/scalable/apps/scroll-text.svg
/usr/share/icons/hicolor/scalable/apps/qubes-ask.svg
/usr/share/icons/hicolor/scalable/apps/qubes-check-maybe.svg
/usr/share/icons/hicolor/scalable/apps/qubes-check-yes.svg