@@ -9,6 +9,7 @@ use native_windows_gui as nwg;
99use super :: auto_attach_tab:: AutoAttachTab ;
1010use super :: connected_tab:: ConnectedTab ;
1111use super :: persisted_tab:: PersistedTab ;
12+ use crate :: usbipd:: { list_devices, UsbDevice } ;
1213use crate :: {
1314 auto_attach:: AutoAttacher ,
1415 win_utils:: { self , DeviceNotification } ,
@@ -25,6 +26,7 @@ pub(super) trait GuiTab {
2526#[ derive( Default , NwgUi ) ]
2627pub struct UsbipdGui {
2728 device_notification : Cell < DeviceNotification > ,
29+ menu_tray_event_handler : Cell < Option < nwg:: EventHandler > > ,
2830
2931 #[ nwg_resource]
3032 embed : nwg:: EmbedResource ,
@@ -75,24 +77,9 @@ pub struct UsbipdGui {
7577
7678 // Tray icon
7779 #[ nwg_control( icon: Some ( & data. app_icon) , tip: Some ( "WSL USB Manager" ) ) ]
78- #[ nwg_events( OnContextMenu : [ UsbipdGui :: show_tray_menu ] , MousePressLeftUp : [ UsbipdGui :: show] ) ]
80+ #[ nwg_events( OnContextMenu : [ UsbipdGui :: show_menu_tray ] , MousePressLeftUp : [ UsbipdGui :: show] ) ]
7981 tray : nwg:: TrayNotification ,
8082
81- // Tray menu
82- #[ nwg_control( parent: window, popup: true ) ]
83- menu_tray : nwg:: Menu ,
84-
85- #[ nwg_control( parent: menu_tray, text: "Open" ) ]
86- #[ nwg_events( OnMenuItemSelected : [ UsbipdGui :: show] ) ]
87- menu_tray_open : nwg:: MenuItem ,
88-
89- #[ nwg_control( parent: menu_tray) ]
90- menu_tray_sep : nwg:: MenuSeparator ,
91-
92- #[ nwg_control( parent: menu_tray, text: "Exit" ) ]
93- #[ nwg_events( OnMenuItemSelected : [ UsbipdGui :: exit] ) ]
94- menu_tray_exit : nwg:: MenuItem ,
95-
9683 // File menu
9784 #[ nwg_control( parent: window, text: "File" , popup: false ) ]
9885 menu_file : nwg:: Menu ,
@@ -105,7 +92,7 @@ pub struct UsbipdGui {
10592 menu_file_sep1 : nwg:: MenuSeparator ,
10693
10794 #[ nwg_control( parent: menu_file, text: "Exit" ) ]
108- #[ nwg_events( OnMenuItemSelected : [ UsbipdGui :: exit] ) ]
95+ #[ nwg_events( OnMenuItemSelected : [ UsbipdGui :: exit( ) ] ) ]
10996 menu_file_exit : nwg:: MenuItem ,
11097}
11198
@@ -154,9 +141,137 @@ impl UsbipdGui {
154141 self . window . set_visible ( true ) ;
155142 }
156143
157- fn show_tray_menu ( & self ) {
144+ fn show_menu_tray ( self : & Rc < UsbipdGui > ) {
145+ // This prevents a memory leak in which the event handler closure is
146+ // kept alive after the menu is destroyed. An attempt was made to unbind
147+ // from the OnMenuExit event, but it seems to prevent the menu event
148+ // handlers from running at all.
149+ if let Some ( handler) = self . menu_tray_event_handler . take ( ) {
150+ nwg:: unbind_event_handler ( & handler) ;
151+ }
152+
153+ let mut menu_tray = nwg:: Menu :: default ( ) ;
154+ nwg:: Menu :: builder ( )
155+ . popup ( true )
156+ . parent ( self . window . handle )
157+ . build ( & mut menu_tray)
158+ . unwrap ( ) ;
159+
160+ let devices = list_devices ( )
161+ . into_iter ( )
162+ . filter ( |d| d. is_connected ( ) )
163+ . collect :: < Vec < _ > > ( ) ;
164+
165+ let mut menu_items: Vec < ( nwg:: MenuItem , UsbDevice ) > = Vec :: with_capacity ( devices. len ( ) ) ;
166+ for device in devices {
167+ let device_name = device. description . as_deref ( ) ;
168+ let vid_pid = device. vid_pid ( ) ;
169+ let description = device_name. map ( |s| s. to_string ( ) ) . unwrap_or (
170+ vid_pid
171+ . clone ( )
172+ . unwrap_or_else ( || "Unknown Device" . to_string ( ) ) ,
173+ ) ;
174+
175+ if device. is_bound ( ) {
176+ let menu_item = self
177+ . new_menu_item ( menu_tray. handle , & description, false , device. is_attached ( ) )
178+ . unwrap ( ) ;
179+
180+ menu_items. push ( ( menu_item, device) ) ;
181+ }
182+ }
183+
184+ if menu_items. is_empty ( ) {
185+ self . new_menu_item ( menu_tray. handle , "No bound devices" , true , false )
186+ . unwrap ( ) ;
187+ } ;
188+
189+ self . new_menu_separator ( menu_tray. handle ) . unwrap ( ) ;
190+ let open_item = self
191+ . new_menu_item ( menu_tray. handle , "Open" , false , false )
192+ . unwrap ( ) ;
193+ self . new_menu_separator ( menu_tray. handle ) . unwrap ( ) ;
194+ let exit_item = self
195+ . new_menu_item ( menu_tray. handle , "Exit" , false , false )
196+ . unwrap ( ) ;
197+
198+ let rc_self_weak = Rc :: downgrade ( self ) ;
199+ let handler =
200+ nwg:: full_bind_event_handler ( & self . window . handle , move |evt, _evt_data, handle| {
201+ // Ignore events that are not menu item selections
202+ if evt != nwg:: Event :: OnMenuItemSelected {
203+ return ;
204+ }
205+
206+ // Retrieve the GUI instance
207+ let Some ( rc_self) = rc_self_weak. upgrade ( ) else {
208+ return ;
209+ } ;
210+
211+ // Handle the menu item selection
212+ if handle == open_item. handle {
213+ // The open menu item was selected
214+ UsbipdGui :: show ( rc_self. as_ref ( ) ) ;
215+ } else if handle == exit_item. handle {
216+ // The exit menu item was selected
217+ UsbipdGui :: exit ( ) ;
218+ } else {
219+ // A device menu item was selected
220+ let Some ( device) = menu_items
221+ . iter ( )
222+ . find ( |( item, _) | item. handle == handle)
223+ . map ( |( _, d) | d)
224+ else {
225+ return ;
226+ } ;
227+
228+ if device. is_attached ( ) {
229+ // Silently ignore errors here as the device may have been unplugged
230+ device. detach ( ) . ok ( ) ;
231+ } else {
232+ // TODO: this currently blocks the UI
233+ device. attach ( ) . unwrap_or_else ( |err| {
234+ nwg:: modal_error_message (
235+ rc_self. window . handle ,
236+ "WSL USB Manager: Command Error" ,
237+ & err,
238+ ) ;
239+ } ) ;
240+ }
241+ }
242+ } ) ;
243+ self . menu_tray_event_handler . set ( Some ( handler) ) ;
244+
158245 let ( x, y) = nwg:: GlobalCursor :: position ( ) ;
159- self . menu_tray . popup ( x, y) ;
246+ menu_tray. popup ( x, y) ;
247+ }
248+
249+ fn new_menu_item (
250+ & self ,
251+ parent : nwg:: ControlHandle ,
252+ text : & str ,
253+ disabled : bool ,
254+ check : bool ,
255+ ) -> Result < nwg:: MenuItem , nwg:: NwgError > {
256+ let mut menu_item = nwg:: MenuItem :: default ( ) ;
257+ nwg:: MenuItem :: builder ( )
258+ . text ( text)
259+ . disabled ( disabled)
260+ . parent ( parent)
261+ . check ( check)
262+ . build ( & mut menu_item)
263+ . map ( |_| menu_item)
264+ }
265+
266+ fn new_menu_separator (
267+ & self ,
268+ parent : nwg:: ControlHandle ,
269+ ) -> Result < nwg:: MenuSeparator , nwg:: NwgError > {
270+ let mut sep = nwg:: MenuSeparator :: default ( ) ;
271+ nwg:: MenuSeparator :: builder ( )
272+ . parent ( parent)
273+ . build ( & mut sep)
274+ . map ( |_| sep)
160275 }
161276
162277 fn refresh ( & self ) {
@@ -165,7 +280,7 @@ impl UsbipdGui {
165280 self . auto_attach_tab_content . refresh ( ) ;
166281 }
167282
168- fn exit ( & self ) {
283+ fn exit ( ) {
169284 nwg:: stop_thread_dispatch ( ) ;
170285 }
171286}
0 commit comments