use std::sync::Once;
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::UI::Shell::{IContextMenu2, IContextMenu3};
use windows::Win32::UI::WindowsAndMessaging::*;
use crate::error::{Error, Result};
static REGISTER_CLASS: Once = Once::new();
fn class_name() -> windows::core::PCWSTR {
static CLASS_NAME_WIDE: std::sync::LazyLock<Vec<u16>> = std::sync::LazyLock::new(|| {
"WinContextMenuHidden"
.encode_utf16()
.chain(std::iter::once(0))
.collect()
});
windows::core::PCWSTR(CLASS_NAME_WIDE.as_ptr())
}
pub(crate) struct WndProcData {
pub ctx_menu2: Option<IContextMenu2>,
pub ctx_menu3: Option<IContextMenu3>,
}
pub(crate) struct HiddenWindow {
pub hwnd: HWND,
}
impl HiddenWindow {
pub fn new() -> Result<Self> {
let cn = class_name();
REGISTER_CLASS.call_once(|| {
let wc = WNDCLASSEXW {
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
lpfnWndProc: Some(hidden_wnd_proc),
lpszClassName: cn,
..Default::default()
};
unsafe {
RegisterClassExW(&wc);
}
});
let hwnd = unsafe {
CreateWindowExW(
WINDOW_EX_STYLE::default(),
cn,
windows::core::PCWSTR::null(),
WS_POPUP,
0,
0,
0,
0,
None,
None,
None,
None,
)
.map_err(Error::CreateWindow)?
};
Ok(Self { hwnd })
}
pub fn set_context_menu_handlers(
&self,
ctx2: Option<IContextMenu2>,
ctx3: Option<IContextMenu3>,
) {
let data = Box::new(WndProcData {
ctx_menu2: ctx2,
ctx_menu3: ctx3,
});
unsafe {
SetWindowLongPtrW(self.hwnd, GWLP_USERDATA, Box::into_raw(data) as isize);
}
}
pub fn clear_context_menu_handlers(&self) {
let ptr = unsafe { GetWindowLongPtrW(self.hwnd, GWLP_USERDATA) };
if ptr != 0 {
unsafe {
let _ = Box::from_raw(ptr as *mut WndProcData);
SetWindowLongPtrW(self.hwnd, GWLP_USERDATA, 0);
}
}
}
}
impl Drop for HiddenWindow {
fn drop(&mut self) {
self.clear_context_menu_handlers();
unsafe {
let _ = DestroyWindow(self.hwnd);
}
}
}
unsafe extern "system" fn hidden_wnd_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if matches!(msg, WM_INITMENUPOPUP | WM_DRAWITEM | WM_MEASUREITEM) {
let ptr = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) };
if ptr != 0 {
let data = unsafe { &*(ptr as *const WndProcData) };
if let Some(ref ctx3) = data.ctx_menu3 {
let mut result = LRESULT(0);
if unsafe {
ctx3.HandleMenuMsg2(msg, wparam, lparam, Some(&mut result as *mut _))
}
.is_ok()
{
return result;
}
}
if let Some(ref ctx2) = data.ctx_menu2 {
if unsafe { ctx2.HandleMenuMsg(msg, wparam, lparam) }.is_ok() {
return LRESULT(0);
}
}
}
}
unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
}