3636import qubesadmin .tests
3737import qubesadmin .tests .mock_app
3838
39+ from qubesadmin .device_protocol import DeviceCategory
40+
3941import qui
4042import qui .utils
4143
6870DEV_TYPES = ["block" , "usb" , "mic" ]
6971
7072
73+ def _is_usbvm (vm : qubesadmin .vm .QubesVM ) -> bool :
74+ """Helper function to detect USBVMS"""
75+ for dev in vm .devices ["pci" ].get_assigned_devices ():
76+ for interface in dev .device .interfaces :
77+ if interface .category == DeviceCategory .PCI_USB :
78+ return True
79+ return False
80+
81+
7182class DeviceMenu (Gtk .Menu ):
7283 """Menu for handling a single device"""
7384
@@ -112,7 +123,7 @@ def __init__(self, app_name, qapp, dispatcher):
112123 self .vms : Set [backend .VM ] = set ()
113124 self .dispvm_templates : Set [backend .VM ] = set ()
114125 self .parent_ports_to_hide = []
115- self .sysusb : backend .VM | None = None
126+ self .dormant_usbvms : Set [ backend .VM ] = set ()
116127 self .dev_update_queue : Set = set ()
117128 self .vm_update_queue : Set = set ()
118129
@@ -144,6 +155,10 @@ def __init__(self, app_name, qapp, dispatcher):
144155 "device-unassign:" + devclass , self .device_unassigned
145156 )
146157
158+ self .dispatcher .add_handler ("device-assign:pci" , self .pci_assigned )
159+ self .dispatcher .add_handler ("device-unassign:pci" , self .pci_unassigned )
160+ self .dispatcher .add_handler ("domain-delete" , self .remove_domain_item )
161+
147162 self .dispatcher .add_handler ("domain-shutdown" , self .vm_shutdown )
148163 self .dispatcher .add_handler ("domain-start-failed" , self .vm_shutdown )
149164 self .dispatcher .add_handler ("domain-start" , self .vm_start )
@@ -242,9 +257,8 @@ def initialize_vm_data(self):
242257 self .vms .add (wrapped_vm )
243258 if wrapped_vm .is_dispvm_template :
244259 self .dispvm_templates .add (wrapped_vm )
245- if vm .name == "sys-usb" :
246- self .sysusb = wrapped_vm
247- self .sysusb .is_running = vm .is_running ()
260+ if not vm .is_running () and _is_usbvm (vm ):
261+ self .dormant_usbvms .add (wrapped_vm )
248262 except qubesadmin .exc .QubesException :
249263 # we don't have access to VM state
250264 pass
@@ -360,6 +374,23 @@ def device_unassigned(self, vm, _event, device, **_kwargs):
360374 # assigned. Cheers!
361375 return
362376
377+ def pci_assigned (self , vm , _event , device , ** _kwargs ):
378+ if not vm .is_running () and _is_usbvm (vm ):
379+ wrapped_vm = backend .VM (vm )
380+ self .dormant_usbvms .add (wrapped_vm )
381+
382+ def pci_unassigned (self , vm , _event , device , ** _kwargs ):
383+ wrapped_vm = backend .VM (vm )
384+ if wrapped_vm in self .dormant_usbvms and not _is_usbvm (vm ):
385+ self .dormant_usbvms .discard (wrapped_vm )
386+
387+ def remove_domain_item (self , _submitter , _event , vm , ** _kwargs ):
388+ # In a pefect world, core should trigger `device-unassign:pci` event and
389+ # this method should not be necessary. But we are not certain :/
390+ wrapped_vm = backend .VM (vm )
391+ if wrapped_vm in self .dormant_usbvms :
392+ self .dormant_usbvms .discard (wrapped_vm )
393+
363394 def update_single_feature (self , _vm , _event , feature , value = None , oldvalue = None ):
364395 if not value :
365396 new = set ()
@@ -533,8 +564,8 @@ def vm_start(self, vm, _event, **_kwargs):
533564 internal , attachable = False , False
534565 if attachable and not internal :
535566 self .vms .add (wrapped_vm )
536- if wrapped_vm == self .sysusb :
537- self .sysusb . is_running = True
567+ if wrapped_vm in self .dormant_usbvms :
568+ self .dormant_usbvms . discard ( wrapped_vm )
538569
539570 for devclass in DEV_TYPES :
540571 try :
@@ -548,8 +579,8 @@ def vm_start(self, vm, _event, **_kwargs):
548579
549580 def vm_shutdown (self , vm , _event , ** _kwargs ):
550581 wrapped_vm = backend .VM (vm )
551- if wrapped_vm == self . sysusb :
552- self .sysusb . is_running = False
582+ if _is_usbvm ( vm ) :
583+ self .dormant_usbvms . add ( wrapped_vm )
553584
554585 self .vms .discard (wrapped_vm )
555586 self .dispvm_templates .discard (wrapped_vm )
@@ -633,13 +664,13 @@ def show_menu(self, _unused, _event):
633664
634665 menu_items .append (device_item )
635666
636- if not self .sysusb . is_running :
637- sysusb_item = actionable_widgets .generate_wrapper_widget (
667+ for wrapped_vm in self .dormant_usbvms :
668+ usbvm_item = actionable_widgets .generate_wrapper_widget (
638669 Gtk .MenuItem ,
639670 "activate" ,
640- actionable_widgets .StartSysUsb ( self . sysusb , theme ),
671+ actionable_widgets .StartUSBVM ( wrapped_vm , theme ),
641672 )
642- menu_items .append (sysusb_item )
673+ menu_items .append (usbvm_item )
643674
644675 for item in menu_items :
645676 tray_menu .add (item )
0 commit comments