Skip to content

Commit 8bf0ffc

Browse files
committed
feat: attach/detach bound devices in tray context menu
1 parent 761d61e commit 8bf0ffc

File tree

3 files changed

+110
-25
lines changed

3 files changed

+110
-25
lines changed

src/gui/connected_tab/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub struct ConnectedTab {
4242
/// A notice sender to notify the auto attach tab to refresh
4343
pub auto_attach_notice: Cell<Option<nwg::NoticeSender>>,
4444

45-
connected_devices: RefCell<Vec<usbipd::UsbDevice>>,
45+
pub connected_devices: RefCell<Vec<usbipd::UsbDevice>>,
4646

4747
#[nwg_layout(flex_direction: FlexDirection::Row)]
4848
connected_tab_layout: nwg::FlexboxLayout,

src/gui/usbipd_gui.rs

Lines changed: 108 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use native_windows_gui as nwg;
99
use super::auto_attach_tab::AutoAttachTab;
1010
use super::connected_tab::ConnectedTab;
1111
use super::persisted_tab::PersistedTab;
12+
use crate::usbipd::UsbDevice;
1213
use crate::{
1314
auto_attach::AutoAttacher,
1415
win_utils::{self, DeviceNotification},
@@ -25,6 +26,7 @@ pub(super) trait GuiTab {
2526
#[derive(Default, NwgUi)]
2627
pub struct UsbipdGui {
2728
device_notification: Cell<DeviceNotification>,
29+
menu_tray_event_handler: RefCell<Option<nwg::EventHandler>>,
2830

2931
#[nwg_resource]
3032
embed: nwg::EmbedResource,
@@ -75,37 +77,22 @@ 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(RC_SELF)], MousePressLeftUp: [UsbipdGui::show(RC_SELF)])]
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,
9986

10087
#[nwg_control(parent: menu_file, text: "Refresh")]
101-
#[nwg_events(OnMenuItemSelected: [UsbipdGui::refresh])]
88+
#[nwg_events(OnMenuItemSelected: [UsbipdGui::refresh(RC_SELF)])]
10289
menu_file_refresh: nwg::MenuItem,
10390

10491
#[nwg_control(parent: menu_file)]
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

@@ -150,22 +137,120 @@ impl UsbipdGui {
150137
self.window.set_visible(false);
151138
}
152139

153-
fn show(&self) {
140+
fn show(self: &Rc<UsbipdGui>) {
154141
self.window.set_visible(true);
155142
}
156143

157-
fn show_tray_menu(&self) {
144+
fn show_menu_tray(self: &Rc<UsbipdGui>) {
145+
if let Some(handler) = self.menu_tray_event_handler.borrow().as_ref() {
146+
nwg::unbind_event_handler(handler);
147+
}
148+
149+
let mut menu_tray = nwg::Menu::default();
150+
nwg::Menu::builder()
151+
.popup(true)
152+
.parent(self.window.handle)
153+
.build(&mut menu_tray)
154+
.unwrap();
155+
156+
let devices = self
157+
.connected_tab_content
158+
.connected_devices
159+
.borrow()
160+
.iter()
161+
.cloned()
162+
.collect::<Vec<_>>();
163+
164+
let mut menu_items: Vec<(nwg::MenuItem, Rc<UsbDevice>)> = Vec::with_capacity(devices.len());
165+
for device in devices {
166+
let device_name = device.description.as_deref();
167+
let vid_pid = device.vid_pid();
168+
let description = device_name.map(|s| s.to_string()).unwrap_or(
169+
vid_pid
170+
.clone()
171+
.unwrap_or_else(|| "Unknown Device".to_string()),
172+
);
173+
174+
if device.is_bound() {
175+
let menu_item = self
176+
.new_menu_item(menu_tray.handle, &description, device.is_attached())
177+
.unwrap();
178+
179+
menu_items.push((menu_item, Rc::new(device.clone())));
180+
}
181+
}
182+
183+
self.new_menu_separator(menu_tray.handle).unwrap();
184+
let open_item = self.new_menu_item(menu_tray.handle, "Open", false).unwrap();
185+
self.new_menu_separator(menu_tray.handle).unwrap();
186+
let exit_item = self.new_menu_item(menu_tray.handle, "Exit", false).unwrap();
187+
188+
let rc_self_weak = Rc::downgrade(&self);
189+
*self.menu_tray_event_handler.borrow_mut() = Some(nwg::full_bind_event_handler(
190+
&self.window.handle,
191+
move |evt, _evt_data, handle| {
192+
if let Some(rc_self) = rc_self_weak.upgrade() {
193+
match evt {
194+
nwg::Event::OnMenuItemSelected => {
195+
if handle == open_item.handle {
196+
UsbipdGui::show(&rc_self);
197+
} else if handle == exit_item.handle {
198+
UsbipdGui::exit();
199+
} else {
200+
for (menu_item, device) in menu_items.iter() {
201+
if handle == menu_item.handle {
202+
if device.is_attached() {
203+
device.detach().ok();
204+
} else {
205+
device.attach().ok();
206+
}
207+
}
208+
}
209+
}
210+
}
211+
_ => (),
212+
}
213+
}
214+
},
215+
));
216+
158217
let (x, y) = nwg::GlobalCursor::position();
159-
self.menu_tray.popup(x, y);
218+
menu_tray.popup(x, y);
219+
}
220+
221+
fn new_menu_item(
222+
self: &Rc<UsbipdGui>,
223+
parent: nwg::ControlHandle,
224+
text: &str,
225+
check: bool,
226+
) -> Result<nwg::MenuItem, nwg::NwgError> {
227+
let mut menu_item = nwg::MenuItem::default();
228+
nwg::MenuItem::builder()
229+
.text(text)
230+
.parent(parent)
231+
.check(check)
232+
.build(&mut menu_item)
233+
.map(|_| menu_item)
234+
}
235+
236+
fn new_menu_separator(
237+
self: &Rc<UsbipdGui>,
238+
parent: nwg::ControlHandle,
239+
) -> Result<nwg::MenuSeparator, nwg::NwgError> {
240+
let mut sep = nwg::MenuSeparator::default();
241+
nwg::MenuSeparator::builder()
242+
.parent(parent)
243+
.build(&mut sep)
244+
.map(|_| sep)
160245
}
161246

162-
fn refresh(&self) {
247+
fn refresh(self: &Rc<UsbipdGui>) {
163248
self.connected_tab_content.refresh();
164249
self.persisted_tab_content.refresh();
165250
self.auto_attach_tab_content.refresh();
166251
}
167252

168-
fn exit(&self) {
253+
fn exit() {
169254
nwg::stop_thread_dispatch();
170255
}
171256
}

src/usbipd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ impl Display for UsbipState {
4747
}
4848

4949
/// A struct representing a USB device as returned by `usbipd`.
50-
#[derive(Debug, Deserialize)]
50+
#[derive(Debug, Deserialize, Clone)]
5151
pub struct UsbDevice {
5252
#[serde(rename = "BusId")]
5353
pub bus_id: Option<String>,

0 commit comments

Comments
 (0)