Skip to content

Commit fbf5050

Browse files
committed
Add Debug Console to Qui Domains systray widget
fixes: QubesOS/qubes-issues#9788
1 parent aeb21cb commit fbf5050

File tree

4 files changed

+107
-6
lines changed

4 files changed

+107
-6
lines changed

icons/scalable/bug-play.svg

Lines changed: 1 addition & 0 deletions
Loading

icons/scalable/scroll-text.svg

Lines changed: 1 addition & 0 deletions
Loading

qui/tray/domains.py

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ def __init__(self):
5858
"unpause": "qubes-vm-unpause",
5959
"files": "qubes-files",
6060
"restart": "qubes-vm-restart",
61+
"debug": "bug-play",
62+
"logs": "scroll-text",
6163
}
6264
self.icons = {}
6365

@@ -283,11 +285,12 @@ async def perform_action(self):
283285

284286

285287
class LogItem(ActionMenuItem):
286-
def __init__(self, name, path):
287-
img = Gtk.Image.new_from_file(
288-
"/usr/share/icons/HighContrast/16x16/apps/logviewer.png"
288+
def __init__(self, name, path, icon_cache):
289+
super().__init__(
290+
label=name,
291+
icon_cache=icon_cache,
292+
icon_name="logs",
289293
)
290-
super().__init__(label=name, img=img)
291294
self.path = path
292295

293296
async def perform_action(self):
@@ -338,6 +341,32 @@ async def perform_action(self):
338341
)
339342

340343

344+
class RunDebugConsoleItem(VMActionMenuItem):
345+
"""Run Debug Console menu Item. When activated runs a qvm-console-dispvm."""
346+
347+
def __init__(self, vm, icon_cache):
348+
super().__init__(
349+
vm,
350+
label=_("Debug Console"),
351+
icon_cache=icon_cache,
352+
icon_name="debug",
353+
)
354+
self.visible = False
355+
self.connect("show", self.on_show_event)
356+
357+
def on_show_event(self, widget):
358+
if self.visible:
359+
widget.show()
360+
else:
361+
widget.hide()
362+
363+
async def perform_action(self):
364+
# pylint: disable=consider-using-with
365+
await asyncio.create_subprocess_exec(
366+
"qvm-console-dispvm", self.vm.name, stderr=subprocess.PIPE
367+
)
368+
369+
341370
class OpenFileManagerItem(VMActionMenuItem):
342371
"""Attempts to open a file manager in the VM. If fails, displays an
343372
error message."""
@@ -392,15 +421,37 @@ def __init__(self, vm, app, icon_cache):
392421
self.add(
393422
RunTerminalItem(self.vm, icon_cache, as_root=app.terminal_as_root)
394423
)
424+
425+
# Debug console for developers, troubleshooting, headless qubes
426+
self.debug_console = RunDebugConsoleItem(self.vm, icon_cache)
427+
self.add(self.debug_console)
428+
395429
self.add(PreferencesItem(self.vm, icon_cache))
396430
self.add(PauseItem(self.vm, icon_cache))
397431
self.add(ShutdownItem(self.vm, icon_cache))
398432
if self.vm.klass != "DispVM" or not self.vm.auto_cleanup:
399433
self.add(RestartItem(self.vm, icon_cache))
400434

401435
self.set_reserve_toggle_size(False)
436+
self.debug_console_update()
402437
self.show_all()
403438

439+
def debug_console_update(self, *_args, **_kwargs):
440+
# Debug console is shown only if debug property is set, no GUIVM is set
441+
# ... or with `expert-mode` feature per qube or per entire GUIVM.
442+
if (
443+
self.app.wizard_mode
444+
or getattr(self.vm, "debug")
445+
or not getattr(self.vm, "guivm")
446+
or not self.vm.features.check_with_template("gui", False)
447+
or self.vm.features.get("expert-mode", False)
448+
):
449+
self.debug_console.visible = True
450+
self.debug_console.show()
451+
else:
452+
self.debug_console.visible = False
453+
self.debug_console.hide()
454+
404455

405456
class PausedMenu(Gtk.Menu):
406457
"""The sub-menu for a paused domain"""
@@ -439,7 +490,7 @@ def __init__(self, vm, icon_cache):
439490

440491
for name, path in logs:
441492
if os.path.isfile(path):
442-
self.add(LogItem(name, path))
493+
self.add(LogItem(name, path, icon_cache=icon_cache))
443494

444495
self.add(KillItem(self.vm, icon_cache))
445496

@@ -475,7 +526,7 @@ def __init__(self, vm, icon_cache, working_correctly=True):
475526

476527
for name, path in logs:
477528
if os.path.isfile(path):
478-
self.add(LogItem(name, path))
529+
self.add(LogItem(name, path, icon_cache=icon_cache))
479530

480531
if working_correctly:
481532
self.add(ShutdownItem(self.vm, icon_cache))
@@ -696,6 +747,11 @@ def __init__(self, app_name, qapp, dispatcher, stats_dispatcher):
696747
self.set_application_id(app_name)
697748
self.register() # register Gtk Application
698749

750+
# to display debug console for all qubes
751+
self.wizard_mode = self.qapp.domains[self.qapp.local_name].features.get(
752+
"expert-mode", False
753+
)
754+
699755
def register_events(self):
700756
self.dispatcher.add_handler("connection-established", self.refresh_all)
701757
self.dispatcher.add_handler("domain-pre-start", self.update_domain_item)
@@ -743,8 +799,34 @@ def register_events(self):
743799
self.dispatcher.add_handler("property-set:netvm", self.property_change)
744800
self.dispatcher.add_handler("property-set:label", self.property_change)
745801

802+
self.dispatcher.add_handler("property-set:debug", self.debug_change)
803+
self.dispatcher.add_handler("property-set:guivm", self.debug_change)
804+
self.dispatcher.add_handler("domain-feature-set:gui", self.debug_change)
805+
self.dispatcher.add_handler(
806+
"domain-feature-delete:gui", self.debug_change
807+
)
808+
self.dispatcher.add_handler(
809+
"domain-feature-set:expert-mode", self.debug_change
810+
)
811+
self.dispatcher.add_handler(
812+
"domain-feature-delete:expert-mode", self.debug_change
813+
)
814+
746815
self.stats_dispatcher.add_handler("vm-stats", self.update_stats)
747816

817+
def debug_change(self, vm, *_args, **_kwargs):
818+
if vm == self.qapp.local_name:
819+
self.wizard_mode = self.qapp.domains[
820+
self.qapp.local_name
821+
].features.get("expert-mode", False)
822+
vms = self.menu_items
823+
else:
824+
vms = {vm}
825+
for menu in vms:
826+
submenu = self.menu_items[menu].get_submenu()
827+
if isinstance(submenu, StartedMenu):
828+
submenu.debug_console_update()
829+
748830
def show_menu(self, _unused, event):
749831
self.terminal_as_root = False
750832
self.tray_menu.popup_at_pointer(event) # None means current event
@@ -1096,6 +1178,21 @@ def _disconnect_signals(self, _event):
10961178
"property-set:label", self.property_change
10971179
)
10981180

1181+
self.dispatcher.remove_handler("property-set:debug", self.debug_change)
1182+
self.dispatcher.remove_handler("property-set:guivm", self.debug_change)
1183+
self.dispatcher.remove_handler(
1184+
"domain-feature-set:gui", self.debug_change
1185+
)
1186+
self.dispatcher.remove_handler(
1187+
"domain-feature-delete:gui", self.debug_change
1188+
)
1189+
self.dispatcher.remove_handler(
1190+
"domain-feature-set:expert-mode", self.debug_change
1191+
)
1192+
self.dispatcher.remove_handler(
1193+
"domain-feature-delete:expert-mode", self.debug_change
1194+
)
1195+
10991196
self.stats_dispatcher.remove_handler("vm-stats", self.update_stats)
11001197

11011198
@property

rpm_spec/qubes-desktop-linux-manager.spec.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ gtk-update-icon-cache %{_datadir}/icons/Adwaita &>/dev/null || :
266266
/usr/share/icons/hicolor/scalable/apps/mic-light.svg
267267
/usr/share/icons/hicolor/scalable/apps/mouse-dark.svg
268268
/usr/share/icons/hicolor/scalable/apps/mouse-light.svg
269+
/usr/share/icons/hicolor/scalable/apps/bug-play.svg
270+
/usr/share/icons/hicolor/scalable/apps/scroll-text.svg
269271
/usr/share/icons/hicolor/scalable/apps/qubes-ask.svg
270272
/usr/share/icons/hicolor/scalable/apps/qubes-check-maybe.svg
271273
/usr/share/icons/hicolor/scalable/apps/qubes-check-yes.svg

0 commit comments

Comments
 (0)