Skip to content

Commit eccb827

Browse files
committed
Show "List USB Devices" option if sys-usb exists
Also show individual menu items if user has more than one usbvm resolves: QubesOS/qubes-issues#10131 resolves: QubesOS/qubes-issues#10202
1 parent 1789756 commit eccb827

2 files changed

Lines changed: 50 additions & 19 deletions

File tree

qui/devices/actionable_widgets.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -361,20 +361,20 @@ async def widget_action(self, *_args):
361361
)
362362

363363

364-
#### Start sys-usb
364+
#### Start sys-usb (or other custom USBVMs)
365365

366366

367-
class StartSysUsb(ActionableWidget, SimpleActionWidget):
368-
def __init__(self, sysusb: backend.VM, variant: str = "dark"):
367+
class StartUSBVM(ActionableWidget, SimpleActionWidget):
368+
def __init__(self, usbvm: backend.VM, variant: str = "dark"):
369369
super().__init__(
370-
icon_name=sysusb.icon_name,
371-
text="<b>List USB Devices " "(start sys-usb)</b>",
370+
icon_name=usbvm.icon_name,
371+
text="<b>List USB Devices " "(start {})</b>".format(usbvm.name),
372372
variant=variant,
373373
)
374-
self.sysusb = sysusb
374+
self.usbvm = usbvm
375375

376376
async def widget_action(self, *_args):
377-
self.sysusb.vm_object.start()
377+
self.usbvm.vm_object.start()
378378

379379

380380
#### Configuration-related actions

qui/devices/device_widget.py

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import qubesadmin.tests
3737
import qubesadmin.tests.mock_app
3838

39+
from qubesadmin.device_protocol import DeviceCategory
40+
3941
import qui
4042
import qui.utils
4143

@@ -68,6 +70,15 @@
6870
DEV_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+
7182
class 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

Comments
 (0)