@@ -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
285287class 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+
341370class 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
405456class 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
0 commit comments