2024-04-11 14:12:39 -04:00
|
|
|
// Copyright 2014-2021 The winit contributors
|
|
|
|
// Copyright 2021-2023 Tauri Programme within The Commons Conservancy
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
use super::{
|
|
|
|
event_loop::S_U_TASKBAR_RESTART,
|
|
|
|
menu::{subclass_proc as menu_subclass_proc, Menu, MenuHandler},
|
|
|
|
util, OsError,
|
|
|
|
};
|
|
|
|
use crate::{
|
|
|
|
dpi::{PhysicalPosition, PhysicalSize},
|
|
|
|
error::OsError as RootOsError,
|
|
|
|
event::{Event, Rectangle, TrayEvent},
|
|
|
|
event_loop::EventLoopWindowTarget,
|
|
|
|
menu::MenuType,
|
|
|
|
system_tray::{Icon, SystemTray as RootSystemTray},
|
|
|
|
TrayId,
|
|
|
|
};
|
|
|
|
use windows::{
|
|
|
|
core::PCWSTR,
|
|
|
|
Win32::{
|
|
|
|
Foundation::{HWND, LPARAM, LRESULT, POINT, WPARAM},
|
|
|
|
System::LibraryLoader::*,
|
|
|
|
UI::{
|
|
|
|
Shell::*,
|
|
|
|
WindowsAndMessaging::{self as win32wm, *},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const TRAYICON_UID: u32 = 6001;
|
|
|
|
const TRAY_SUBCLASS_ID: usize = 6002;
|
|
|
|
const TRAY_MENU_SUBCLASS_ID: usize = 6003;
|
|
|
|
const WM_USER_TRAYICON: u32 = 6004;
|
|
|
|
const WM_USER_UPDATE_TRAYMENU: u32 = 6005;
|
|
|
|
const WM_USER_UPDATE_TRAYICON: u32 = 6006;
|
|
|
|
const WM_USER_UPDATE_TOOLTIP: u32 = 6007;
|
|
|
|
|
|
|
|
struct TrayLoopData {
|
|
|
|
id: TrayId,
|
|
|
|
hwnd: HWND,
|
|
|
|
hmenu: Option<HMENU>,
|
|
|
|
icon: Icon,
|
|
|
|
tooltip: Option<String>,
|
|
|
|
sender: Box<dyn Fn(Event<'static, ()>)>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct SystemTrayBuilder {
|
|
|
|
pub(crate) icon: Icon,
|
|
|
|
pub(crate) tray_menu: Option<Menu>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SystemTrayBuilder {
|
|
|
|
#[inline]
|
|
|
|
pub fn new(icon: Icon, tray_menu: Option<Menu>) -> Self {
|
|
|
|
Self { icon, tray_menu }
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn build<T: 'static>(
|
|
|
|
self,
|
|
|
|
window_target: &EventLoopWindowTarget<T>,
|
|
|
|
tray_id: TrayId,
|
|
|
|
tooltip: Option<String>,
|
|
|
|
) -> Result<RootSystemTray, RootOsError> {
|
|
|
|
let hmenu: Option<HMENU> = self.tray_menu.map(|m| m.hmenu());
|
|
|
|
|
|
|
|
let class_name = util::encode_wide("tao_system_tray_app");
|
|
|
|
unsafe {
|
|
|
|
let hinstance = GetModuleHandleW(PCWSTR::null()).unwrap_or_default();
|
|
|
|
|
|
|
|
let wnd_class = WNDCLASSW {
|
|
|
|
lpfnWndProc: Some(util::call_default_window_proc),
|
|
|
|
lpszClassName: PCWSTR::from_raw(class_name.as_ptr()),
|
|
|
|
hInstance: hinstance,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
RegisterClassW(&wnd_class);
|
|
|
|
|
|
|
|
let hwnd = CreateWindowExW(
|
|
|
|
WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED |
|
|
|
|
// WS_EX_TOOLWINDOW prevents this window from ever showing up in the taskbar, which
|
|
|
|
// we want to avoid. If you remove this style, this window won't show up in the
|
|
|
|
// taskbar *initially*, but it can show up at some later point. This can sometimes
|
|
|
|
// happen on its own after several hours have passed, although this has proven
|
|
|
|
// difficult to reproduce. Alternatively, it can be manually triggered by killing
|
|
|
|
// `explorer.exe` and then starting the process back up.
|
|
|
|
// It is unclear why the bug is triggered by waiting for several hours.
|
|
|
|
WS_EX_TOOLWINDOW,
|
|
|
|
PCWSTR::from_raw(class_name.as_ptr()),
|
|
|
|
PCWSTR::null(),
|
|
|
|
WS_OVERLAPPED,
|
|
|
|
CW_USEDEFAULT,
|
|
|
|
0,
|
|
|
|
CW_USEDEFAULT,
|
|
|
|
0,
|
|
|
|
HWND::default(),
|
|
|
|
HMENU::default(),
|
|
|
|
hinstance,
|
|
|
|
std::ptr::null_mut(),
|
|
|
|
);
|
|
|
|
if !IsWindow(hwnd).as_bool() {
|
|
|
|
return Err(os_error!(OsError::CreationError(
|
|
|
|
"Unable to get valid mutable pointer for CreateWindowEx"
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
|
|
|
let hicon = self.icon.inner.as_raw_handle();
|
|
|
|
|
|
|
|
if !register_tray_icon(hwnd, hicon, tooltip.clone()) {
|
|
|
|
return Err(os_error!(OsError::CreationError(
|
|
|
|
"Error with shellapi::Shell_NotifyIconW"
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
|
|
|
let system_tray = SystemTray { hwnd: hwnd.clone() };
|
|
|
|
|
|
|
|
// system_tray event handler
|
|
|
|
let event_loop_runner = window_target.p.runner_shared.clone();
|
|
|
|
let traydata = TrayLoopData {
|
|
|
|
id: tray_id,
|
|
|
|
hwnd,
|
|
|
|
hmenu,
|
|
|
|
icon: self.icon,
|
|
|
|
tooltip,
|
|
|
|
sender: Box::new(move |event| {
|
|
|
|
if let Ok(e) = event.map_nonuser_event() {
|
|
|
|
event_loop_runner.send_event(e)
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
SetWindowSubclass(
|
|
|
|
hwnd,
|
|
|
|
Some(tray_subclass_proc),
|
|
|
|
TRAY_SUBCLASS_ID,
|
|
|
|
Box::into_raw(Box::new(traydata)) as _,
|
|
|
|
);
|
|
|
|
|
|
|
|
// system_tray menu event handler
|
|
|
|
let event_loop_runner = window_target.p.runner_shared.clone();
|
|
|
|
let menu_handler = MenuHandler::new(
|
|
|
|
Box::new(move |event| {
|
|
|
|
if let Ok(e) = event.map_nonuser_event() {
|
|
|
|
event_loop_runner.send_event(e)
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
MenuType::ContextMenu,
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
SetWindowSubclass(
|
|
|
|
hwnd,
|
|
|
|
Some(menu_subclass_proc),
|
|
|
|
TRAY_MENU_SUBCLASS_ID,
|
|
|
|
Box::into_raw(Box::new(menu_handler)) as _,
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(RootSystemTray(system_tray))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct SystemTray {
|
|
|
|
hwnd: HWND,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SystemTray {
|
|
|
|
pub fn set_icon(&mut self, icon: Icon) {
|
|
|
|
unsafe {
|
|
|
|
let mut nid = NOTIFYICONDATAW {
|
|
|
|
uFlags: NIF_ICON,
|
|
|
|
hWnd: self.hwnd,
|
|
|
|
hIcon: icon.inner.as_raw_handle(),
|
|
|
|
uID: TRAYICON_UID,
|
|
|
|
..std::mem::zeroed()
|
|
|
|
};
|
|
|
|
if !Shell_NotifyIconW(NIM_MODIFY, &mut nid as _).as_bool() {
|
|
|
|
debug!("Error setting icon");
|
|
|
|
}
|
|
|
|
|
|
|
|
// send the new icon to the subclass proc to store it in the tray data
|
|
|
|
SendMessageW(
|
|
|
|
self.hwnd,
|
|
|
|
WM_USER_UPDATE_TRAYICON,
|
|
|
|
WPARAM(Box::into_raw(Box::new(icon)) as _),
|
|
|
|
LPARAM(0),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_tooltip(&self, tooltip: &str) {
|
|
|
|
let mut tip = util::encode_wide(tooltip);
|
|
|
|
tip.resize(128, 0);
|
|
|
|
|
|
|
|
let mut sz_tip: [u16; 128] = [0; 128];
|
|
|
|
sz_tip.copy_from_slice(&tip);
|
|
|
|
unsafe {
|
|
|
|
let mut tip = util::encode_wide(tooltip);
|
|
|
|
tip.resize(128, 0);
|
|
|
|
|
|
|
|
let mut nid = NOTIFYICONDATAW {
|
|
|
|
uFlags: NIF_TIP,
|
|
|
|
hWnd: self.hwnd,
|
|
|
|
uID: TRAYICON_UID,
|
|
|
|
szTip: sz_tip,
|
|
|
|
..std::mem::zeroed()
|
|
|
|
};
|
|
|
|
|
|
|
|
if !Shell_NotifyIconW(NIM_MODIFY, &mut nid as _).as_bool() {
|
|
|
|
debug!("Error setting icon");
|
|
|
|
}
|
|
|
|
|
|
|
|
// send the new tooltip to the subclass proc to store it in the tray data
|
|
|
|
SendMessageW(
|
|
|
|
self.hwnd,
|
|
|
|
WM_USER_UPDATE_TOOLTIP,
|
|
|
|
WPARAM(Box::into_raw(Box::new(tooltip.to_string())) as _),
|
|
|
|
LPARAM(0),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_menu(&mut self, tray_menu: &Menu) {
|
|
|
|
unsafe {
|
|
|
|
// send the new menu to the subclass proc where we will update there
|
|
|
|
SendMessageW(
|
|
|
|
self.hwnd,
|
|
|
|
WM_USER_UPDATE_TRAYMENU,
|
|
|
|
WPARAM(tray_menu.hmenu().0 as _),
|
|
|
|
LPARAM(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: TRAYICON_UID,
|
|
|
|
..std::mem::zeroed()
|
|
|
|
};
|
|
|
|
if !Shell_NotifyIconW(NIM_DELETE, &mut nid as _).as_bool() {
|
|
|
|
debug!("Error removing system tray icon");
|
|
|
|
}
|
|
|
|
|
|
|
|
// destroy the hidden window used by the tray
|
|
|
|
DestroyWindow(self.hwnd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe extern "system" fn tray_subclass_proc(
|
|
|
|
hwnd: HWND,
|
|
|
|
msg: u32,
|
|
|
|
wparam: WPARAM,
|
|
|
|
lparam: LPARAM,
|
|
|
|
_id: usize,
|
|
|
|
subclass_input_ptr: usize,
|
|
|
|
) -> LRESULT {
|
|
|
|
let subclass_input_ptr = subclass_input_ptr as *mut TrayLoopData;
|
|
|
|
let mut subclass_input = &mut *(subclass_input_ptr);
|
|
|
|
|
|
|
|
if msg == WM_DESTROY {
|
|
|
|
drop(Box::from_raw(subclass_input_ptr));
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg == WM_USER_UPDATE_TRAYMENU {
|
|
|
|
subclass_input.hmenu = Some(HMENU(wparam.0 as _));
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg == WM_USER_UPDATE_TRAYICON {
|
|
|
|
let icon = wparam.0 as *mut Icon;
|
|
|
|
subclass_input.icon = *Box::from_raw(icon);
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg == WM_USER_UPDATE_TOOLTIP {
|
|
|
|
let tooltip = wparam.0 as *mut String;
|
|
|
|
subclass_input.tooltip = Some(*Box::from_raw(tooltip));
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg == *S_U_TASKBAR_RESTART {
|
|
|
|
register_tray_icon(
|
|
|
|
subclass_input.hwnd,
|
|
|
|
subclass_input.icon.inner.as_raw_handle(),
|
|
|
|
subclass_input.tooltip.clone(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg == WM_USER_TRAYICON
|
|
|
|
&& matches!(
|
|
|
|
lparam.0 as u32,
|
|
|
|
WM_LBUTTONUP | WM_RBUTTONUP | WM_LBUTTONDBLCLK
|
|
|
|
)
|
|
|
|
{
|
|
|
|
let nid = NOTIFYICONIDENTIFIER {
|
|
|
|
hWnd: hwnd,
|
|
|
|
cbSize: std::mem::size_of::<NOTIFYICONIDENTIFIER>() as _,
|
|
|
|
uID: TRAYICON_UID,
|
|
|
|
..std::mem::zeroed()
|
|
|
|
};
|
|
|
|
let icon_rect = Shell_NotifyIconGetRect(&nid).unwrap_or_default();
|
|
|
|
|
|
|
|
let mut cursor = POINT { x: 0, y: 0 };
|
|
|
|
GetCursorPos(&mut cursor as _);
|
|
|
|
|
|
|
|
let position = PhysicalPosition::new(cursor.x as f64, cursor.y as f64);
|
|
|
|
let bounds = Rectangle {
|
|
|
|
position: PhysicalPosition::new(icon_rect.left as f64, icon_rect.top as f64),
|
|
|
|
size: PhysicalSize::new(
|
|
|
|
(icon_rect.right - icon_rect.left) as f64,
|
|
|
|
(icon_rect.bottom - icon_rect.top) as f64,
|
|
|
|
),
|
|
|
|
};
|
|
|
|
|
|
|
|
match lparam.0 as u32 {
|
|
|
|
win32wm::WM_LBUTTONUP => {
|
2025-06-21 14:49:28 -04:00
|
|
|
// println!("left click");
|
2024-04-11 14:12:39 -04:00
|
|
|
(subclass_input.sender)(Event::TrayEvent {
|
|
|
|
id: subclass_input.id,
|
|
|
|
event: TrayEvent::LeftClick,
|
|
|
|
position,
|
|
|
|
bounds,
|
|
|
|
});
|
2025-06-21 13:44:20 -04:00
|
|
|
|
|
|
|
// Check environment variable to see if we should show context menu on left click
|
|
|
|
let enable_left_click_menu = std::env::var("PASTEBAR_ENABLE_LEFT_CLICK_MENU")
|
|
|
|
.unwrap_or_else(|_| "false".to_string())
|
|
|
|
.parse::<bool>()
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
|
|
// Only show context menu on left click if not disabled
|
|
|
|
if !enable_left_click_menu {
|
|
|
|
if let Some(menu) = subclass_input.hmenu {
|
|
|
|
show_tray_menu(hwnd, menu, cursor.x, cursor.y);
|
|
|
|
}
|
2024-04-11 14:12:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
win32wm::WM_RBUTTONUP => {
|
2025-06-21 14:49:28 -04:00
|
|
|
// println!("right click");
|
2024-04-11 14:12:39 -04:00
|
|
|
(subclass_input.sender)(Event::TrayEvent {
|
|
|
|
id: subclass_input.id,
|
|
|
|
event: TrayEvent::RightClick,
|
|
|
|
position,
|
|
|
|
bounds,
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Some(menu) = subclass_input.hmenu {
|
|
|
|
show_tray_menu(hwnd, menu, cursor.x, cursor.y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
win32wm::WM_LBUTTONDBLCLK => {
|
|
|
|
(subclass_input.sender)(Event::TrayEvent {
|
|
|
|
id: subclass_input.id,
|
|
|
|
event: TrayEvent::DoubleClick,
|
|
|
|
position,
|
|
|
|
bounds,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DefSubclassProc(hwnd, msg, wparam, lparam)
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn show_tray_menu(hwnd: HWND, menu: HMENU, x: i32, y: i32) {
|
|
|
|
// bring the hidden window to the foreground so the pop up menu
|
|
|
|
// would automatically hide on click outside
|
|
|
|
SetForegroundWindow(hwnd);
|
|
|
|
// track the click
|
|
|
|
TrackPopupMenu(
|
|
|
|
menu,
|
|
|
|
// align bottom / right, maybe we could expose this later..
|
|
|
|
TPM_BOTTOMALIGN | TPM_LEFTALIGN,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
0,
|
|
|
|
hwnd,
|
|
|
|
std::ptr::null_mut(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn register_tray_icon(hwnd: HWND, hicon: HICON, tooltip: Option<String>) -> bool {
|
|
|
|
let mut flags = NIF_MESSAGE | NIF_ICON;
|
|
|
|
let mut sz_tip: [u16; 128] = [0; 128];
|
|
|
|
|
|
|
|
if let Some(tooltip) = tooltip {
|
|
|
|
let mut tip = util::encode_wide(tooltip);
|
|
|
|
tip.resize(128, 0);
|
|
|
|
|
|
|
|
flags |= NIF_TIP;
|
|
|
|
sz_tip.copy_from_slice(&tip);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut nid = NOTIFYICONDATAW {
|
|
|
|
uFlags: flags,
|
|
|
|
hWnd: hwnd,
|
|
|
|
hIcon: hicon,
|
|
|
|
uID: TRAYICON_UID,
|
|
|
|
uCallbackMessage: WM_USER_TRAYICON,
|
|
|
|
szTip: sz_tip,
|
|
|
|
..std::mem::zeroed()
|
|
|
|
};
|
|
|
|
|
|
|
|
Shell_NotifyIconW(NIM_ADD, &mut nid as _).as_bool()
|
|
|
|
}
|