@@ -316,6 +316,34 @@ async def perform_action(self):
316316 )
317317
318318
319+ class RunDebugConsoleItem (VMActionMenuItem ):
320+ """Run Debug Console menu Item. When activated runs a qvm-console-dispvm."""
321+
322+ def __init__ (self , vm , icon_cache ):
323+ img = Gtk .Image .new_from_file (
324+ "/usr/share/icons/HighContrast/16x16/apps/logviewer.png"
325+ )
326+ super ().__init__ (
327+ vm ,
328+ label = _ ("Debug Console" ),
329+ img = img ,
330+ )
331+ self .visible = False
332+ self .connect ("show" , self .on_show_event )
333+
334+ def on_show_event (self , widget ):
335+ if self .visible :
336+ widget .show ()
337+ else :
338+ widget .hide ()
339+
340+ async def perform_action (self ):
341+ # pylint: disable=consider-using-with
342+ await asyncio .create_subprocess_exec (
343+ "qvm-console-dispvm" , self .vm .name , stderr = subprocess .PIPE
344+ )
345+
346+
319347class OpenFileManagerItem (VMActionMenuItem ):
320348 """Attempts to open a file manager in the VM. If fails, displays an
321349 error message."""
@@ -368,15 +396,37 @@ def __init__(self, vm, app, icon_cache):
368396
369397 self .add (OpenFileManagerItem (self .vm , icon_cache ))
370398 self .add (RunTerminalItem (self .vm , icon_cache ))
399+
400+ # Debug console for developers, troubleshooting, headless qubes
401+ self .debug_console = RunDebugConsoleItem (self .vm , icon_cache )
402+ self .add (self .debug_console )
403+
371404 self .add (PreferencesItem (self .vm , icon_cache ))
372405 self .add (PauseItem (self .vm , icon_cache ))
373406 self .add (ShutdownItem (self .vm , icon_cache ))
374407 if self .vm .klass != "DispVM" or not self .vm .auto_cleanup :
375408 self .add (RestartItem (self .vm , icon_cache ))
376409
377410 self .set_reserve_toggle_size (False )
411+ self .debug_console_update ()
378412 self .show_all ()
379413
414+ def debug_console_update (self , * _args , ** _kwargs ):
415+ # Debug console is shown only if debug property is set, no GUIVM is set
416+ # ... or with `wizard-mode` feature per qube or per entire GUIVM.
417+ if (
418+ self .app .wizard_mode
419+ or getattr (self .vm , "debug" )
420+ or not getattr (self .vm , "guivm" )
421+ or not self .vm .features .check_with_template ("gui" , False )
422+ or self .vm .features .get ("wizard-mode" , False )
423+ ):
424+ self .debug_console .visible = True
425+ self .debug_console .show ()
426+ else :
427+ self .debug_console .visible = False
428+ self .debug_console .hide ()
429+
380430
381431class PausedMenu (Gtk .Menu ):
382432 """The sub-menu for a paused domain"""
@@ -668,6 +718,11 @@ def __init__(self, app_name, qapp, dispatcher, stats_dispatcher):
668718 self .set_application_id (app_name )
669719 self .register () # register Gtk Application
670720
721+ # to display debug console for all qubes
722+ self .wizard_mode = self .qapp .domains [self .qapp .local_name ].features .get (
723+ "wizard-mode" , False
724+ )
725+
671726 def register_events (self ):
672727 self .dispatcher .add_handler ("connection-established" , self .refresh_all )
673728 self .dispatcher .add_handler ("domain-pre-start" , self .update_domain_item )
@@ -717,6 +772,32 @@ def register_events(self):
717772
718773 self .stats_dispatcher .add_handler ("vm-stats" , self .update_stats )
719774
775+ self .dispatcher .add_handler ("property-set:debug" , self .debug_change )
776+ self .dispatcher .add_handler ("property-set:guivm" , self .debug_change )
777+ self .dispatcher .add_handler ("domain-feature-set:gui" , self .debug_change )
778+ self .dispatcher .add_handler (
779+ "domain-feature-delete:gui" , self .debug_change
780+ )
781+ self .dispatcher .add_handler (
782+ "domain-feature-set:wizard-mode" , self .debug_change
783+ )
784+ self .dispatcher .add_handler (
785+ "domain-feature-delete:wizard-mode" , self .debug_change
786+ )
787+
788+ def debug_change (self , vm , * _args , ** _kwargs ):
789+ if vm == self .qapp .local_name :
790+ self .wizard_mode = self .qapp .domains [
791+ self .qapp .local_name
792+ ].features .get ("wizard-mode" , False )
793+ vms = self .menu_items
794+ else :
795+ vms = {vm }
796+ for menu in vms :
797+ submenu = self .menu_items [menu ].get_submenu ()
798+ if isinstance (submenu , StartedMenu ):
799+ submenu .debug_console_update ()
800+
720801 def show_menu (self , _unused , event ):
721802 self .tray_menu .popup_at_pointer (event ) # None means current event
722803
@@ -1069,6 +1150,21 @@ def _disconnect_signals(self, _event):
10691150
10701151 self .stats_dispatcher .remove_handler ("vm-stats" , self .update_stats )
10711152
1153+ self .dispatcher .remove_handler ("property-set:debug" , self .debug_change )
1154+ self .dispatcher .remove_handler ("property-set:guivm" , self .debug_change )
1155+ self .dispatcher .remove_handler (
1156+ "domain-feature-set:gui" , self .debug_change
1157+ )
1158+ self .dispatcher .remove_handler (
1159+ "domain-feature-delete:gui" , self .debug_change
1160+ )
1161+ self .dispatcher .remove_handler (
1162+ "domain-feature-set:wizard-mode" , self .debug_change
1163+ )
1164+ self .dispatcher .remove_handler (
1165+ "domain-feature-delete:wizard-mode" , self .debug_change
1166+ )
1167+
10721168
10731169def main ():
10741170 """main function"""
0 commit comments