#![allow(clippy::field_reassign_with_default)]
use core::ffi::c_void;
use core::mem::{size_of, ManuallyDrop, MaybeUninit};
use core::ptr;
use core::slice;
use std::ffi::OsStr;
use std::io;
use std::thread;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
use windows_sys::Win32::Foundation::{FALSE, HWND, LPARAM, LRESULT, WPARAM};
use windows_sys::Win32::System::DataExchange::AddClipboardFormatListener;
use windows_sys::Win32::System::DataExchange::COPYDATASTRUCT;
use windows_sys::Win32::UI::Shell as shellapi;
use windows_sys::Win32::UI::WindowsAndMessaging as winuser;
use crate::convert::ToWide;
use crate::error::ErrorKind::*;
use crate::error::{Error, WindowError};
use crate::event::{ClipboardEvent, MouseButtons, MouseEvent};
use crate::window_loop::messages;
use crate::AreaId;
use crate::Result;
use super::{AreaHandle, ClipboardManager, MenuManager, WindowClassHandle, WindowHandle};
#[derive(Debug)]
pub(crate) enum WindowEvent {
MenuItemClicked(AreaId, u32, MouseEvent),
Shutdown,
Clipboard(ClipboardEvent),
IconClicked(AreaId, MouseEvent),
NotificationClicked(AreaId, MouseEvent),
NotificationDismissed(AreaId),
CopyData(usize, Vec<u8>),
Error(Error),
}
unsafe extern "system" fn window_proc(
hwnd: HWND,
msg: u32,
w_param: WPARAM,
l_param: LPARAM,
) -> LRESULT {
match msg {
messages::ICON_ID => {
if matches!(
l_param as u32,
shellapi::NIN_BALLOONUSERCLICK
| shellapi::NIN_BALLOONTIMEOUT
| winuser::WM_LBUTTONUP
| winuser::WM_RBUTTONUP
) {
winuser::PostMessageW(hwnd, msg, w_param, l_param);
return 0;
}
}
winuser::WM_MENUCOMMAND => {
winuser::PostMessageW(hwnd, msg, w_param, l_param);
return 0;
}
winuser::WM_CLIPBOARDUPDATE => {
winuser::PostMessageW(hwnd, msg, w_param, l_param);
return 0;
}
winuser::WM_DESTROY => {
winuser::PostMessageW(hwnd, msg, w_param, l_param);
return 0;
}
winuser::WM_COPYDATA => {
let data = &*(l_param as *const COPYDATASTRUCT);
let len = data.cbData as usize;
let mut vec = Vec::with_capacity(len + size_of::<usize>());
vec.extend_from_slice(slice::from_raw_parts(data.lpData.cast::<u8>(), len));
vec.extend_from_slice(&data.dwData.to_ne_bytes());
let mut vec = ManuallyDrop::new(vec);
let bytes = vec.as_mut_ptr();
winuser::PostMessageW(hwnd, messages::BYTES_ID, len, bytes as isize);
return 0;
}
_ => {}
}
winuser::DefWindowProcW(hwnd, msg, w_param, l_param)
}
unsafe fn init_window(
class_name: Vec<u16>,
window_name: Option<Vec<u16>>,
) -> io::Result<(WindowClassHandle, WindowHandle)> {
let wnd = winuser::WNDCLASSW {
style: 0,
lpfnWndProc: Some(window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: ptr::null_mut(),
hIcon: ptr::null_mut(),
hCursor: ptr::null_mut(),
hbrBackground: ptr::null_mut(),
lpszMenuName: ptr::null(),
lpszClassName: class_name.as_ptr(),
};
if winuser::RegisterClassW(&wnd) == 0 {
return Err(io::Error::last_os_error());
}
let class = WindowClassHandle { class_name };
let hwnd = winuser::CreateWindowExW(
0,
class.class_name.as_ptr(),
window_name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null),
winuser::WS_DISABLED,
0,
0,
0,
0,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null(),
);
if hwnd.is_null() {
return Err(io::Error::last_os_error());
}
let window = WindowHandle { hwnd };
Ok((class, window))
}
#[repr(C)]
pub(crate) struct WindowLoop {
pub(crate) areas: Vec<AreaHandle>,
pub(crate) window: WindowHandle,
window_class: WindowClassHandle,
events_rx: mpsc::UnboundedReceiver<WindowEvent>,
thread: Option<thread::JoinHandle<Result<(), WindowError>>>,
}
impl WindowLoop {
pub(crate) async fn new(
class_name: &OsStr,
window_name: Option<&OsStr>,
clipboard_events: bool,
areas: Vec<AreaHandle>,
) -> Result<WindowLoop, WindowError> {
let class_name = class_name.to_wide_null();
let window_name = window_name.map(|n| n.to_wide_null());
if class_name.len() > 256 {
return Err(WindowError::ClassNameTooLong(class_name.len()));
}
let (return_tx, return_rx) = oneshot::channel();
let (events_tx, events_rx) = mpsc::unbounded_channel();
let mut hmenus = Vec::with_capacity(areas.len());
for menu in &areas {
hmenus.push(
menu.popup_menu
.as_ref()
.map(|p| (p.hmenu, p.open_menu.copy_data())),
);
}
let runner = Runner {
class_name,
window_name,
clipboard_events,
hmenus,
events_tx,
return_tx,
};
let thread = thread::spawn(move || unsafe { runner.run() });
let Some((window_class, window)) = return_rx.await.ok() else {
thread.join().map_err(|_| WindowError::ThreadPanicked)??;
return Err(WindowError::ThreadExited);
};
Ok(WindowLoop {
areas,
window,
window_class,
events_rx,
thread: Some(thread),
})
}
pub(crate) async fn tick(&mut self) -> WindowEvent {
self.events_rx.recv().await.unwrap_or(WindowEvent::Shutdown)
}
pub(crate) fn is_closed(&self) -> bool {
self.thread.is_none()
}
pub(crate) fn join(&mut self) -> Result<()> {
if self.thread.is_none() {
return Ok(());
}
let result = unsafe { winuser::PostMessageW(self.window.hwnd, winuser::WM_DESTROY, 0, 0) };
if result == FALSE {
return Err(Error::new(PostMessageDestroy));
}
if let Some(thread) = self.thread.take() {
thread
.join()
.map_err(|_| ThreadError(WindowError::ThreadPanicked))?
.map_err(ThreadError)?;
}
Ok(())
}
}
impl Drop for WindowLoop {
fn drop(&mut self) {
for menu in &self.areas {
_ = self.window.delete_notification(menu.area_id);
}
}
}
struct Runner {
class_name: Vec<u16>,
window_name: Option<Vec<u16>>,
clipboard_events: bool,
hmenus: Vec<Option<(*mut c_void, MouseButtons)>>,
events_tx: mpsc::UnboundedSender<WindowEvent>,
return_tx: oneshot::Sender<(WindowClassHandle, WindowHandle)>,
}
impl Runner {
unsafe fn run(self) -> Result<(), WindowError> {
let (window_class, window) =
init_window(self.class_name, self.window_name).map_err(WindowError::Init)?;
let mut clipboard_manager = if self.clipboard_events {
if AddClipboardFormatListener(window.hwnd) == FALSE {
return Err(WindowError::AddClipboardFormatListener(
io::Error::last_os_error(),
));
}
Some(ClipboardManager::new(&self.events_tx))
} else {
None
};
let mut menu_manager =
(!self.hmenus.is_empty()).then(|| MenuManager::new(&self.events_tx, &self.hmenus));
let hwnd = window.hwnd;
if self.return_tx.send((window_class, window)).is_err() {
return Ok(());
}
let mut msg = MaybeUninit::zeroed();
while winuser::GetMessageW(msg.as_mut_ptr(), hwnd, 0, 0) != FALSE {
let msg = &*msg.as_ptr();
if let Some(clipboard_manager) = &mut clipboard_manager {
if clipboard_manager.dispatch(msg) {
continue;
}
}
if let Some(menu_manager) = &mut menu_manager {
if menu_manager.dispatch(msg) {
continue;
}
}
match msg.message {
winuser::WM_QUIT | winuser::WM_DESTROY => {
break;
}
messages::BYTES_ID => {
let len = msg.wParam;
let bytes =
Vec::from_raw_parts(msg.lParam as *mut u8, len, len + size_of::<usize>());
let ty = bytes
.as_ptr()
.add(bytes.len())
.cast::<usize>()
.read_unaligned();
_ = self.events_tx.send(WindowEvent::CopyData(ty, bytes));
continue;
}
_ => {}
}
winuser::TranslateMessage(msg);
winuser::DispatchMessageW(msg);
}
Ok(())
}
}
unsafe impl Send for Runner {}