diff --git a/.changes/remove-tray-remove-windows.md b/.changes/remove-tray-remove-windows.md new file mode 100644 index 0000000000..c5fdaf21ec --- /dev/null +++ b/.changes/remove-tray-remove-windows.md @@ -0,0 +1,5 @@ +--- +"tao": patch +--- + +Removed `SystemTrayExtWindows::remove()`, the icon will be automatically removed when `SystemTray` is dropped. \ No newline at end of file diff --git a/examples/system_tray.rs b/examples/system_tray.rs index 359f4001a4..67558908e8 100644 --- a/examples/system_tray.rs +++ b/examples/system_tray.rs @@ -12,8 +12,6 @@ fn main() { use std::path::Path; #[cfg(target_os = "macos")] use tao::platform::macos::{CustomMenuItemExtMacOS, NativeImage}; - #[cfg(target_os = "windows")] - use tao::platform::windows::SystemTrayExtWindows; use tao::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -152,11 +150,6 @@ fn main() { } // click on `quit` item if menu_id == quit_element.clone().id() { - // on windows, we make sure to remove the icon from the tray - // it require the `SystemTrayExtWindows` - #[cfg(target_os = "windows")] - system_tray.remove(); - // tell our app to close at the end of the loop. *control_flow = ControlFlow::Exit; } diff --git a/src/menu.rs b/src/menu.rs index 7664d24a3b..74127ddaef 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -29,10 +29,16 @@ use crate::{ }; /// Object that allows you to create a `ContextMenu`. +/// +/// ## Platform-specific +/// pub struct ContextMenu(pub(crate) Menu); /// Object that allows you to create a `MenuBar`, menu. /// -/// Used by the **Window** menu in Windows and Linux and the **Menu bar** on macOS +/// ## Platform-specific +/// +/// **macOs:** The menu will show in the **Menu Bar**. +/// **Linux / Windows:** The menu will be show at the top of the window. pub struct MenuBar(pub(crate) Menu); /// A custom menu item. diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 9d19ce9357..3b10ccdc64 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -348,18 +348,3 @@ impl IconExtWindows for Icon { Ok(Icon { inner: win_icon }) } } - -/// Additional methods on `SystemTray` that are specific to Windows. -#[cfg(feature = "tray")] -pub trait SystemTrayExtWindows { - fn remove(&mut self); -} - -#[cfg(feature = "tray")] -impl SystemTrayExtWindows for crate::system_tray::SystemTray { - /// Remove the tray icon. - /// Call this when your application is goind to close, to make sure the icon is correctly removed from the system tray. - fn remove(&mut self) { - self.0.remove() - } -} diff --git a/src/platform_impl/windows/menu.rs b/src/platform_impl/windows/menu.rs index 60a939ed79..f8b305e441 100644 --- a/src/platform_impl/windows/menu.rs +++ b/src/platform_impl/windows/menu.rs @@ -122,7 +122,7 @@ impl MenuItemAttributes { #[derive(Debug, Clone)] pub struct Menu { - pub(crate) hmenu: windef::HMENU, + hmenu: windef::HMENU, accels: HashMap, } @@ -156,10 +156,8 @@ impl Menu { } } - pub fn into_hmenu(self) -> windef::HMENU { - let hmenu = self.hmenu; - std::mem::forget(self); - hmenu + pub fn hmenu(&self) -> windef::HMENU { + self.hmenu } // Get the accels table @@ -226,7 +224,7 @@ impl Menu { winuser::AppendMenuW( self.hmenu, flags, - submenu.into_hmenu() as _, + submenu.hmenu() as _, to_wstring(&title).as_mut_ptr(), ); } @@ -341,7 +339,7 @@ pub fn initialize( ) -> Option { if let RawWindowHandle::Windows(handle) = window_handle { let sender: *mut MenuHandler = Box::into_raw(Box::new(menu_handler)); - let menu = menu_builder.clone().into_hmenu(); + let menu = menu_builder.clone().hmenu(); unsafe { commctrl::SetWindowSubclass( @@ -376,35 +374,41 @@ pub(crate) unsafe extern "system" fn subclass_proc( wparam: minwindef::WPARAM, lparam: minwindef::LPARAM, _id: basetsd::UINT_PTR, - data: basetsd::DWORD_PTR, + subclass_input_ptr: basetsd::DWORD_PTR, ) -> minwindef::LRESULT { - let proxy = &mut *(data as *mut MenuHandler); + let subclass_input_ptr = subclass_input_ptr as *mut MenuHandler; + let subclass_input = &*(subclass_input_ptr); + + if msg == winuser::WM_DESTROY { + Box::from_raw(subclass_input_ptr); + } + match msg { winuser::WM_COMMAND => { match wparam { CUT_ID => { - execute_edit_command(EditCommands::Cut); + execute_edit_command(EditCommand::Cut); } COPY_ID => { - execute_edit_command(EditCommands::Copy); + execute_edit_command(EditCommand::Copy); } PASTE_ID => { - execute_edit_command(EditCommands::Paste); + execute_edit_command(EditCommand::Paste); } SELECT_ALL_ID => { - execute_edit_command(EditCommands::SelectAll); + execute_edit_command(EditCommand::SelectAll); } HIDE_ID => { winuser::ShowWindow(hwnd, winuser::SW_HIDE); } CLOSE_ID => { - proxy.send_event(Event::WindowEvent { + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(hwnd)), event: WindowEvent::CloseRequested, }); } QUIT_ID => { - proxy.send_event(Event::LoopDestroyed); + subclass_input.send_event(Event::LoopDestroyed); } MINIMIZE_ID => { winuser::ShowWindow(hwnd, winuser::SW_MINIMIZE); @@ -412,7 +416,7 @@ pub(crate) unsafe extern "system" fn subclass_proc( _ => { let menu_id = minwindef::LOWORD(wparam as _); if MENU_IDS.lock().unwrap().contains(&menu_id) { - proxy.send_menu_event(menu_id); + subclass_input.send_menu_event(menu_id); } } } @@ -422,18 +426,18 @@ pub(crate) unsafe extern "system" fn subclass_proc( } } -enum EditCommands { +enum EditCommand { Copy, Cut, Paste, SelectAll, } -fn execute_edit_command(command: EditCommands) { +fn execute_edit_command(command: EditCommand) { let key = match command { - EditCommands::Copy => 0x43, // c - EditCommands::Cut => 0x58, // x - EditCommands::Paste => 0x56, // v - EditCommands::SelectAll => 0x41, // a + EditCommand::Copy => 0x43, // c + EditCommand::Cut => 0x58, // x + EditCommand::Paste => 0x56, // v + EditCommand::SelectAll => 0x41, // a }; unsafe { diff --git a/src/platform_impl/windows/system_tray.rs b/src/platform_impl/windows/system_tray.rs index 29b0831c8b..86b380c523 100644 --- a/src/platform_impl/windows/system_tray.rs +++ b/src/platform_impl/windows/system_tray.rs @@ -1,11 +1,9 @@ // Copyright 2019-2021 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 -use std::cell::RefCell; - use super::{ dpi::{dpi_to_scale_factor, hwnd_dpi}, - menu::{subclass_proc, to_wstring, Menu, MenuHandler}, + menu::{subclass_proc as menu_subclass_proc, to_wstring, Menu, MenuHandler}, util, OsError, }; use crate::{ @@ -18,20 +16,27 @@ use crate::{ }; use winapi::{ shared::{ + basetsd::{DWORD_PTR, UINT_PTR}, minwindef::{LPARAM, LRESULT, UINT, WPARAM}, windef::{HICON, HMENU, HWND, POINT, RECT}, }, um::{ - commctrl::SetWindowSubclass, - libloaderapi, + commctrl, libloaderapi, shellapi::{self, NIF_ICON, NIF_MESSAGE, NIM_ADD, NIM_DELETE, NIM_MODIFY, NOTIFYICONDATAW}, winuser::{self, CW_USEDEFAULT, WNDCLASSW, WS_OVERLAPPEDWINDOW}, }, }; -const WM_USER_TRAYICON: u32 = 0x400 + 1111; -const WM_USER_TRAYICON_UID: u32 = 0x855 + 1111; -thread_local!(static SYSTEM_TRAY_STASH: RefCell> = RefCell::new(None)); +const WM_USER_TRAYICON: u32 = 6001; +const WM_USER_UPDATE_TRAYMENU: u32 = 6002; +const TRAYICON_UID: u32 = 6003; +const TRAY_SUBCLASS_ID: usize = 6004; +const TRAY_MENU_SUBCLASS_ID: usize = 6005; + +struct TrayLoopData { + hmenu: Option, + sender: Box)>, +} pub struct SystemTrayBuilder { pub(crate) icon: Vec, @@ -57,26 +62,19 @@ impl SystemTrayBuilder { self, window_target: &EventLoopWindowTarget, ) -> Result { - let mut hmenu: Option = None; - if let Some(menu) = self.tray_menu { - hmenu = Some(menu.into_hmenu()); - } + let hmenu: Option = self.tray_menu.map(|m| m.hmenu()); let class_name = to_wstring("tao_system_tray_app"); unsafe { let hinstance = libloaderapi::GetModuleHandleA(std::ptr::null_mut()); + let wnd_class = WNDCLASSW { - style: 0, - lpfnWndProc: Some(window_proc), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: hinstance, - hIcon: winuser::LoadIconW(hinstance, winuser::IDI_APPLICATION), - hCursor: winuser::LoadCursorW(hinstance, winuser::IDI_APPLICATION), - hbrBackground: 16 as _, - lpszMenuName: 0 as _, + lpfnWndProc: Some(winuser::DefWindowProcW), lpszClassName: class_name.as_ptr(), + hInstance: hinstance, + ..Default::default() }; + if winuser::RegisterClassW(&wnd_class) == 0 { return Err(os_error!(OsError::CreationError( "Error with winuser::RegisterClassW" @@ -107,7 +105,7 @@ impl SystemTrayBuilder { let mut nid = NOTIFYICONDATAW { uFlags: NIF_MESSAGE, hWnd: hwnd, - uID: WM_USER_TRAYICON_UID, + uID: TRAYICON_UID, uCallbackMessage: WM_USER_TRAYICON, ..Default::default() }; @@ -118,30 +116,27 @@ impl SystemTrayBuilder { ))); } - let app_system_tray = SystemTray { hwnd, hmenu }; - app_system_tray.set_icon_from_buffer(&self.icon, 32, 32); + let system_tray = SystemTray { hwnd }; + system_tray.set_icon_from_buffer(&self.icon, 32, 32); - // system tray handler + // system_tray event handler let event_loop_runner = window_target.p.runner_shared.clone(); - let menu_handler = MenuHandler::new( - Box::new(move |event| { + let traydata = TrayLoopData { + hmenu, + sender: Box::new(move |event| { if let Ok(e) = event.map_nonuser_event() { event_loop_runner.send_event(e) } }), - MenuType::ContextMenu, - None, + }; + commctrl::SetWindowSubclass( + hwnd, + Some(tray_subclass_proc), + TRAY_SUBCLASS_ID, + Box::into_raw(Box::new(traydata)) as _, ); - SYSTEM_TRAY_STASH.with(|stash| { - let data = WindowsLoopData { - system_tray: SystemTray { hwnd, hmenu }, - sender: menu_handler, - }; - (*stash.borrow_mut()) = Some(data); - }); - - // create the handler for tray menu events + // system_tray menu event handler let event_loop_runner = window_target.p.runner_shared.clone(); let menu_handler = MenuHandler::new( Box::new(move |event| { @@ -152,23 +147,20 @@ impl SystemTrayBuilder { MenuType::ContextMenu, None, ); + commctrl::SetWindowSubclass( + hwnd as _, + Some(menu_subclass_proc), + TRAY_MENU_SUBCLASS_ID, + Box::into_raw(Box::new(menu_handler)) as _, + ); - let sender: *mut MenuHandler = Box::into_raw(Box::new(menu_handler)); - SetWindowSubclass(hwnd as _, Some(subclass_proc), 0, sender as _); - - Ok(RootSystemTray(app_system_tray)) + Ok(RootSystemTray(system_tray)) } } } pub struct SystemTray { hwnd: HWND, - hmenu: Option, -} - -struct WindowsLoopData { - system_tray: SystemTray, - sender: MenuHandler, } impl SystemTray { @@ -182,14 +174,13 @@ impl SystemTray { } } - // set the icon for our main instance fn set_hicon(&self, icon: HICON) { unsafe { let mut nid = NOTIFYICONDATAW { uFlags: NIF_ICON, hWnd: self.hwnd, hIcon: icon, - uID: WM_USER_TRAYICON_UID, + uID: TRAYICON_UID, ..Default::default() }; if shellapi::Shell_NotifyIconW(NIM_MODIFY, &mut nid as _) == 0 { @@ -198,119 +189,123 @@ impl SystemTray { } } - pub fn remove(&self) { + pub fn set_menu(&mut self, tray_menu: &Menu) { unsafe { + // send the new menu to the subclass proc where we will update there + winuser::SendMessageW( + self.hwnd, + WM_USER_UPDATE_TRAYMENU, + tray_menu.hmenu() as _, + 0, + ); + } + } +} + +impl Drop for SystemTray { + fn drop(&mut self) { + unsafe { + // remove the icon from system tray let mut nid = NOTIFYICONDATAW { uFlags: NIF_ICON, hWnd: self.hwnd, - uID: WM_USER_TRAYICON_UID, + uID: TRAYICON_UID, ..Default::default() }; if shellapi::Shell_NotifyIconW(NIM_DELETE, &mut nid as _) == 0 { - debug!("Error removing icon"); + debug!("Error removing system tray icon"); } - } - } - pub fn set_menu(&mut self, tray_menu: &Menu) { - let new_menu = Some(tray_menu.hmenu); - self.hmenu = new_menu; - SYSTEM_TRAY_STASH.with(|stash| { - if let Some(ref mut data) = *stash.borrow_mut() { - data.system_tray.hmenu = new_menu; - } - }); + // destroy the hidden window used by the tray + winuser::DestroyWindow(self.hwnd); + } } } -unsafe extern "system" fn window_proc( +unsafe extern "system" fn tray_subclass_proc( hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, + _id: UINT_PTR, + subclass_input_ptr: DWORD_PTR, ) -> LRESULT { + let subclass_input_ptr = subclass_input_ptr as *mut TrayLoopData; + let mut subclass_input = &mut *(subclass_input_ptr); + if msg == winuser::WM_DESTROY { - winuser::PostQuitMessage(0); - return 0; + Box::from_raw(subclass_input_ptr); } - // click on the icon - if msg == WM_USER_TRAYICON { - let mut rect = RECT::default(); + if msg == WM_USER_UPDATE_TRAYMENU { + subclass_input.hmenu = Some(wparam as HMENU); + } + + if msg == WM_USER_TRAYICON + && matches!( + lparam as u32, + winuser::WM_LBUTTONUP | winuser::WM_RBUTTONUP | winuser::WM_LBUTTONDBLCLK + ) + { + let mut icon_rect = RECT::default(); let nid = shellapi::NOTIFYICONIDENTIFIER { hWnd: hwnd, cbSize: std::mem::size_of::() as _, - uID: WM_USER_TRAYICON_UID, + uID: TRAYICON_UID, ..Default::default() }; - - shellapi::Shell_NotifyIconGetRect(&nid, &mut rect); + shellapi::Shell_NotifyIconGetRect(&nid, &mut icon_rect); let dpi = hwnd_dpi(hwnd); let scale_factor = dpi_to_scale_factor(dpi); let mut cursor = POINT { x: 0, y: 0 }; winuser::GetCursorPos(&mut cursor as _); - SYSTEM_TRAY_STASH.with(|stash| { - if let Some(ref data) = *stash.borrow() { - match lparam as u32 { - // Left click tray icon - winuser::WM_LBUTTONUP => { - data.sender.send_event(Event::TrayEvent { - event: TrayEvent::LeftClick, - position: LogicalPosition::new(cursor.x, cursor.y).to_physical(scale_factor), - bounds: Rectangle { - position: LogicalPosition::new(rect.left, rect.top).to_physical(scale_factor), - size: LogicalSize::new(rect.right - rect.left, rect.bottom - rect.top) - .to_physical(scale_factor), - }, - }); - } - // Right click tray icon - winuser::WM_RBUTTONUP => { - data.sender.send_event(Event::TrayEvent { - event: TrayEvent::RightClick, - position: LogicalPosition::new(cursor.x, cursor.y).to_physical(scale_factor), - bounds: Rectangle { - position: LogicalPosition::new(rect.left, rect.top).to_physical(scale_factor), - size: LogicalSize::new(rect.right - rect.left, rect.bottom - rect.top) - .to_physical(scale_factor), - }, - }); - - // show menu on right click - if let Some(menu) = data.system_tray.hmenu { - show_tray_menu(hwnd, menu, cursor.x, cursor.y); - } - } + let position = LogicalPosition::new(cursor.x, cursor.y).to_physical(scale_factor); + let bounds = Rectangle { + position: LogicalPosition::new(icon_rect.left, icon_rect.top).to_physical(scale_factor), + size: LogicalSize::new( + icon_rect.right - icon_rect.left, + icon_rect.bottom - icon_rect.top, + ) + .to_physical(scale_factor), + }; - // Double click tray icon - winuser::WM_LBUTTONDBLCLK => { - data.sender.send_event(Event::TrayEvent { - event: TrayEvent::DoubleClick, - position: LogicalPosition::new(cursor.x, cursor.y).to_physical(scale_factor), - bounds: Rectangle { - position: LogicalPosition::new(rect.left, rect.top).to_physical(scale_factor), - size: LogicalSize::new(rect.right - rect.left, rect.bottom - rect.top) - .to_physical(scale_factor), - }, - }); - } + match lparam as u32 { + winuser::WM_LBUTTONUP => { + (subclass_input.sender)(Event::TrayEvent { + event: TrayEvent::LeftClick, + position, + bounds, + }); + } + + winuser::WM_RBUTTONUP => { + (subclass_input.sender)(Event::TrayEvent { + event: TrayEvent::RightClick, + position, + bounds, + }); - _ => {} + if let Some(menu) = subclass_input.hmenu { + show_tray_menu(hwnd, menu, cursor.x, cursor.y); } } - }); - } - winuser::DefWindowProcW(hwnd, msg, wparam, lparam) -} + winuser::WM_LBUTTONDBLCLK => { + (subclass_input.sender)(Event::TrayEvent { + event: TrayEvent::DoubleClick, + position, + bounds, + }); + } -impl Drop for WindowsLoopData { - fn drop(&mut self) { - self.system_tray.remove(); + _ => {} + } } + + commctrl::DefSubclassProc(hwnd, msg, wparam, lparam) } unsafe fn show_tray_menu(hwnd: HWND, menu: HMENU, x: i32, y: i32) { diff --git a/src/system_tray.rs b/src/system_tray.rs index 38fc57341b..125bc144cd 100644 --- a/src/system_tray.rs +++ b/src/system_tray.rs @@ -76,15 +76,11 @@ impl SystemTrayBuilder { /// Builds the SystemTray. /// /// Possible causes of error include denied permission, incompatible system, and lack of memory. - /// - /// ## Platform-specific - /// - /// - **Windows:**: The icon is not removed automatically. Use `SystemTrayExtWindows` and use the `remove()` function when your application is closing. pub fn build( self, - _window_target: &EventLoopWindowTarget, + window_target: &EventLoopWindowTarget, ) -> Result { - self.0.build(_window_target) + self.0.build(window_target) } }