19
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20
20
# USA.
21
21
import asyncio
22
+ import subprocess
22
23
23
24
import qubes
24
25
26
+ from typing import Type
27
+
28
+ from qubes import device_protocol
29
+ from qubes .device_protocol import VirtualDevice
30
+
25
31
26
32
def device_list_change (
27
33
ext : qubes .ext .Extension , current_devices ,
28
- vm , path , device_class
34
+ vm , path , device_class : Type [ qubes . device_protocol . DeviceInfo ]
29
35
):
30
36
devclass = device_class .__name__ [:- len ('Device' )].lower ()
31
37
@@ -39,7 +45,7 @@ def device_list_change(
39
45
for dev_id , front_vm in detached .items ():
40
46
dev = device_class (vm , dev_id )
41
47
asyncio .ensure_future (front_vm .fire_event_async (
42
- f'device-detach:{ devclass } ' , port = dev ))
48
+ f'device-detach:{ devclass } ' , port = dev . port ))
43
49
for dev_id in removed :
44
50
device = device_class (vm , dev_id )
45
51
vm .fire_event (f'device-removed:{ devclass } ' , device = device )
@@ -54,17 +60,42 @@ def device_list_change(
54
60
55
61
ext .devices_cache [vm .name ] = current_devices
56
62
63
+ to_attach = {}
57
64
for front_vm in vm .app .domains :
58
65
if not front_vm .is_running ():
59
66
continue
60
- for assignment in front_vm .devices [devclass ].assignments (
61
- persistent = True ):
62
- if (assignment .backend_domain == vm
63
- and assignment .port_id in added
64
- and assignment .port_id not in attached
65
- ):
66
- asyncio .ensure_future (ext .attach_and_notify (
67
- front_vm , assignment .device , assignment .options ))
67
+ for assignment in front_vm .devices [devclass ].get_assigned_devices ():
68
+ for device in assignment .devices :
69
+ if (assignment .matches (device )
70
+ and device .port_id in added
71
+ and device .port_id not in attached
72
+ ):
73
+ frontends = to_attach .get (device .port_id , {})
74
+ # make it unique
75
+ frontends [front_vm ] = assignment .clone (
76
+ device = VirtualDevice (device .port , device .device_id ))
77
+ to_attach [device .port_id ] = frontends
78
+
79
+ for port_id , frontends in to_attach .items ():
80
+ if len (frontends ) > 1 :
81
+ # unique
82
+ device = tuple (frontends .values ())[0 ].devices [0 ]
83
+ target_name = confirm_device_attachment (device , frontends )
84
+ for front in frontends :
85
+ if front .name == target_name :
86
+ target = front
87
+ assignment = frontends [front ]
88
+ # already asked
89
+ if assignment .mode .value == "ask-to-attach" :
90
+ assignment .mode = device_protocol .AssignmentMode .AUTO
91
+ break
92
+ else :
93
+ return
94
+ else :
95
+ target = tuple (frontends .keys ())[0 ]
96
+ assignment = frontends [target ]
97
+
98
+ asyncio .ensure_future (ext .attach_and_notify (target , assignment ))
68
99
69
100
70
101
def compare_device_cache (vm , devices_cache , current_devices ):
@@ -101,3 +132,17 @@ def compare_device_cache(vm, devices_cache, current_devices):
101
132
if cached_front is not None :
102
133
detached [dev_id ] = cached_front
103
134
return added , attached , detached , removed
135
+
136
+
137
+ def confirm_device_attachment (device , frontends ) -> str :
138
+ guivm = 'dom0' # TODO
139
+ # TODO: guivm rpc?
140
+
141
+ proc = subprocess .Popen (
142
+ ["attach-confirm" , guivm ,
143
+ device .backend_domain .name , device .port_id ,
144
+ device .description ,
145
+ * [f .name for f in frontends .keys ()]],
146
+ stdout = subprocess .PIPE , stderr = subprocess .PIPE )
147
+ (target_name , _ ) = proc .communicate ()
148
+ return target_name .decode ()
0 commit comments