diff --git a/icons/bluetooth.svg b/icons/bluetooth.svg
new file mode 100644
index 00000000..2ac08b6d
--- /dev/null
+++ b/icons/bluetooth.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/camera.svg b/icons/camera.svg
new file mode 100644
index 00000000..45a9f78d
--- /dev/null
+++ b/icons/camera.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/card-sim.svg b/icons/card-sim.svg
new file mode 100644
index 00000000..4067fae6
--- /dev/null
+++ b/icons/card-sim.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/circuit-board.svg b/icons/circuit-board.svg
new file mode 100644
index 00000000..8bf3cf83
--- /dev/null
+++ b/icons/circuit-board.svg
@@ -0,0 +1 @@
+
diff --git a/icons/clapperboard.svg b/icons/clapperboard.svg
new file mode 100644
index 00000000..1bc2e6cb
--- /dev/null
+++ b/icons/clapperboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/cpu.svg b/icons/cpu.svg
new file mode 100644
index 00000000..f4b75b2f
--- /dev/null
+++ b/icons/cpu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/dock.svg b/icons/dock.svg
new file mode 100644
index 00000000..0a321512
--- /dev/null
+++ b/icons/dock.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/gpu.svg b/icons/gpu.svg
new file mode 100644
index 00000000..e04f99ba
--- /dev/null
+++ b/icons/gpu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/keyboard.svg b/icons/keyboard.svg
new file mode 100644
index 00000000..5cbeef43
--- /dev/null
+++ b/icons/keyboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/memory-stick.svg b/icons/memory-stick.svg
new file mode 100644
index 00000000..c85b07bb
--- /dev/null
+++ b/icons/memory-stick.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/monitor.svg b/icons/monitor.svg
new file mode 100644
index 00000000..c333b796
--- /dev/null
+++ b/icons/monitor.svg
@@ -0,0 +1 @@
+
diff --git a/icons/mouse.svg b/icons/mouse.svg
new file mode 100644
index 00000000..2f92332b
--- /dev/null
+++ b/icons/mouse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/speaker.svg b/icons/speaker.svg
new file mode 100644
index 00000000..e4cd50ab
--- /dev/null
+++ b/icons/speaker.svg
@@ -0,0 +1 @@
+
diff --git a/icons/usb.svg b/icons/usb.svg
new file mode 100644
index 00000000..122642f0
--- /dev/null
+++ b/icons/usb.svg
@@ -0,0 +1 @@
+
diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py
index 9028ddef..daa3c2b1 100644
--- a/qubesmanager/settings.py
+++ b/qubesmanager/settings.py
@@ -32,6 +32,7 @@
import datetime
from qubesadmin.tools import QubesArgumentParser
from qubesadmin import device_protocol
+from qubesadmin.device_protocol import DeviceCategory
from qubesadmin import utils as admin_utils
from qubesadmin.tools import qvm_start
import qubesadmin.exc
@@ -159,6 +160,23 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog):
)
)
+ device_icons = {
+ DeviceCategory.Network: "networking",
+ DeviceCategory.Keyboard: "keyboard",
+ DeviceCategory.Mouse: "mouse",
+ DeviceCategory.Camera: "camera",
+ DeviceCategory.Audio: "speaker",
+ DeviceCategory.Multimedia: "clapperboard",
+ DeviceCategory.Storage: "storage",
+ DeviceCategory.Bluetooth: "Bluetooth",
+ DeviceCategory.Smart_Card_Readers: "card-sim",
+ DeviceCategory.Display: "monitor",
+ DeviceCategory.Memory: "memory-stick",
+ DeviceCategory.Docking_Station: "dock",
+ DeviceCategory.Processor: "cpu",
+ DeviceCategory.PCI_USB: "usb",
+ }
+
# pylint: disable=too-many-positional-arguments
def __init__(
self, vm_name, init_page="basic", qapp=None, qubesapp=None, parent=None
@@ -244,6 +262,17 @@ def __init__(
self.current_strict_reset_list = []
self.new_strict_reset_list = []
self.define_strict_reset_devices()
+ self.device_radio_buttons = {
+ self.dev_network: DeviceCategory.Network,
+ self.dev_display: DeviceCategory.Display,
+ self.dev_audio: DeviceCategory.Audio,
+ self.dev_storage: DeviceCategory.Storage,
+ self.dev_usb_controller: DeviceCategory.PCI_USB,
+ }
+ self.dev_search.textChanged.connect(self.filter_devices)
+ for button in self.device_radio_buttons:
+ button.toggled.connect(self.filter_devices)
+ self.dev_other.toggled.connect(self.filter_devices)
####### services tab
self.__init_services_tab__()
@@ -1493,6 +1522,8 @@ def bootmode_changed(self):
######## devices tab
def __init_devices_tab__(self):
self.dev_list = multiselectwidget.MultiSelectWidget(self)
+ self.dev_list.selected_list.setSpacing(5)
+ self.dev_list.available_list.setSpacing(5)
self.dev_list.change_labels(
available="Available devices",
selected="Devices always connected to this qube",
@@ -1507,7 +1538,7 @@ def __init_devices_tab__(self):
.devices["pci"]
.get_exposed_devices()
if dev.interfaces[0].category
- != device_protocol.DeviceCategory.PCI_Bridge
+ != DeviceCategory.PCI_Bridge
)
attached = list(
self.vm.devices["pci"].get_assigned_devices(required_only=True)
@@ -1530,6 +1561,15 @@ def __init__(self, dev, unknown=False, parent=None):
name += " (unknown)"
self.setText(name)
self.dev = dev
+ intfs = list({i.category for i in dev.interfaces})
+ if len(intfs) != 1 or intfs[0] not in VMSettingsWindow.device_icons:
+ self.setIcon(QtGui.QIcon(":/circuit-board"))
+ else:
+ self.setIcon(
+ QtGui.QIcon(
+ ":/{}".format(VMSettingsWindow.device_icons[intfs[0]])
+ )
+ )
for dev in dom0_devs:
if any(attached_dev.matches(dev) for attached_dev in attached):
@@ -1658,6 +1698,59 @@ def strict_reset_button_pressed(self):
)
device_list_window.exec()
+ def filter_devices(self):
+ keywords = [word for word in self.dev_search.text().split(" ") if word]
+ for i in range(self.dev_list.available_list.count()):
+ device = self.dev_list.available_list.item(i)
+ hide = False
+ for button, category in self.device_radio_buttons.items():
+ if button.isChecked():
+ hide = True
+ for i in device.dev.interfaces:
+ if i.category == category:
+ hide = False
+ break
+ if self.dev_other.isChecked():
+ hide = True
+ for i in device.dev.interfaces:
+ if i.category not in self.device_radio_buttons.values():
+ hide = False
+ break
+ for word in keywords:
+ if not re.search(
+ word,
+ device.text(),
+ re.IGNORECASE
+ ):
+ hide = True
+ break
+ device.setHidden(hide)
+ for i in range(self.dev_list.selected_list.count()):
+ device = self.dev_list.selected_list.item(i)
+ hide = False
+ for button, category in self.device_radio_buttons.items():
+ if button.isChecked():
+ hide = True
+ for i in device.dev.interfaces:
+ if i.category == category:
+ hide = False
+ break
+ if self.dev_other.isChecked():
+ hide = True
+ for i in device.dev.interfaces:
+ if i.category not in self.device_radio_buttons.values():
+ hide = False
+ break
+ for word in keywords:
+ if not re.search(
+ word,
+ device.text(),
+ re.IGNORECASE
+ ):
+ hide = True
+ break
+ device.setHidden(hide)
+
######## applications tab
def refresh_apps_button_pressed(self):
diff --git a/qubesmanager/tests/test_vm_settings.py b/qubesmanager/tests/test_vm_settings.py
index 6a44610e..2b685d04 100644
--- a/qubesmanager/tests/test_vm_settings.py
+++ b/qubesmanager/tests/test_vm_settings.py
@@ -2044,3 +2044,35 @@ def test_603_virtmode_limitation(settings_fixture):
assert "PVH" not in available_virtmodes
assert "HVM (current)" in available_virtmodes
assert "PV" in available_virtmodes
+
+
+@check_errors
+@pytest.mark.parametrize("settings_fixture", ["test-pci-dev"], indirect=True)
+def test_604_device_filter(settings_fixture):
+ settings_window, page, vm_name = settings_fixture
+ vm = settings_window.qubesapp.domains[vm_name]
+ settings_window.dev_search.setText("usb")
+ settings_window.dev_usb_controller.setChecked(True)
+
+ for i in range(settings_window.dev_list.available_list.count()):
+ item = settings_window.dev_list.available_list.item(i)
+ assert item.isHidden() != ("USB" in item.text())
+
+ for i in range(settings_window.dev_list.selected_list.count()):
+ item = settings_window.dev_list.selected_list.item(i)
+ assert item.isHidden() != ("USB" in item.text())
+
+ settings_window.dev_search.setText("")
+ settings_window.dev_other.setChecked(True)
+
+ for i in range(settings_window.dev_list.available_list.count()):
+ item = settings_window.dev_list.available_list.item(i)
+ if not item.isHidden():
+ for i in item.dev.interfaces:
+ assert i.category not in settings_window.device_radio_buttons.values()
+
+ for i in range(settings_window.dev_list.selected_list.count()):
+ item = settings_window.dev_list.selected_list.item(i)
+ if not item.isHidden():
+ for i in item.dev.interfaces:
+ assert i.category not in settings_window.device_radio_buttons.values()
diff --git a/resources.qrc b/resources.qrc
index 16f40cd9..5f3180fb 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -47,5 +47,19 @@
icons/fast-rewind.svg
icons/step-back.svg
icons/step-forward.svg
+ icons/bluetooth.svg
+ icons/camera.svg
+ icons/card-sim.svg
+ icons/circuit-board.svg
+ icons/clapperboard.svg
+ icons/cpu.svg
+ icons/dock.svg
+ icons/gpu.svg
+ icons/keyboard.svg
+ icons/memory-stick.svg
+ icons/monitor.svg
+ icons/mouse.svg
+ icons/speaker.svg
+ icons/usb.svg
diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui
index 905a1c67..608f52bd 100644
--- a/ui/settingsdlg.ui
+++ b/ui/settingsdlg.ui
@@ -1849,6 +1849,103 @@ The qube must be running to disable seamless mode. This setting is not persisten
+ -
+
+
-
+
+
+ Filter:
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+ Class:
+
+
+
+ -
+
+
+ All
+
+
+ true
+
+
+
+ -
+
+
+ Network
+
+
+
+ :/networking:/networking
+
+
+
+ -
+
+
+ Display
+
+
+
+ :/monitor:/monitor
+
+
+
+ -
+
+
+ Audio
+
+
+
+ :/speaker:/speaker
+
+
+
+ -
+
+
+ Storage
+
+
+
+ :/storage:/storage
+
+
+
+ -
+
+
+ USB Controller
+
+
+
+ :/usb:/usb
+
+
+
+ -
+
+
+ Other
+
+
+
+
+
-