use crossbeam::channel;
use std::ffi::{CString, c_void};
use std::mem;
use std::ptr;
use std::sync::Weak;
use std::thread::{self, ThreadId};
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::System::{
LibraryLoader::GetModuleHandleA, Performance::QueryPerformanceCounter,
};
use windows::Win32::UI::WindowsAndMessaging::{
CREATESTRUCTA, CreateWindowExA, DefWindowProcA, DestroyWindow, GWLP_USERDATA,
GetWindowLongPtrA, PostMessageA, RegisterClassExA, SetWindowLongPtrA, UnregisterClassA,
WINDOW_EX_STYLE, WINDOW_STYLE, WM_CREATE, WM_DESTROY, WM_USER, WNDCLASSEXA,
};
use windows::core::PCSTR;
use super::{BackgroundThread, EventLoop, MainThreadExecutor};
use crate::nice_debug_assert_failure;
use crate::util::permit_alloc;
const NOTIFY_MESSAGE_ID: u32 = WM_USER;
type PollCallback = Box<dyn Fn()>;
pub(crate) struct WindowsEventLoop<T, E> {
executor: Weak<E>,
main_thread_id: ThreadId,
message_window: HwndWrapper,
message_window_class_name: CString,
tasks_sender: channel::Sender<T>,
background_thread: BackgroundThread<T, E>,
}
struct HwndWrapper(pub HWND);
unsafe impl Send for HwndWrapper {}
unsafe impl Sync for HwndWrapper {}
impl<T, E> EventLoop<T, E> for WindowsEventLoop<T, E>
where
T: Send + 'static,
E: MainThreadExecutor<T> + 'static,
{
fn new_and_spawn(executor: Weak<E>) -> Self {
let (tasks_sender, tasks_receiver) = channel::bounded(super::TASK_QUEUE_CAPACITY);
let mut ticks = 0i64;
assert!(unsafe { QueryPerformanceCounter(&mut ticks).is_ok() });
let class_name = CString::new(format!("nice-event-loop-{ticks}"))
.expect("Where did these null bytes come from?");
let class_name_ptr = PCSTR(class_name.as_bytes_with_nul().as_ptr());
let class = WNDCLASSEXA {
cbSize: mem::size_of::<WNDCLASSEXA>() as u32,
lpfnWndProc: Some(window_proc),
hInstance: unsafe { GetModuleHandleA(PCSTR(ptr::null())) }
.expect("Could not get the current module's handle")
.into(),
lpszClassName: class_name_ptr,
..Default::default()
};
assert_ne!(unsafe { RegisterClassExA(&class) }, 0);
let callback: PollCallback = {
let executor = executor.clone();
Box::new(move || {
let executor = match executor.upgrade() {
Some(e) => e,
None => {
nice_debug_assert_failure!("Executor died before the message loop exited");
return;
}
};
while let Ok(task) = tasks_receiver.try_recv() {
executor.execute(task, true);
}
})
};
let window = unsafe {
CreateWindowExA(
WINDOW_EX_STYLE(0),
class_name_ptr,
PCSTR(c"nice-plug event loop".as_ptr() as *const u8),
WINDOW_STYLE(0),
0,
0,
0,
0,
None,
None,
None,
Some(Box::into_raw(Box::new(callback)) as *const c_void),
)
}
.unwrap();
Self {
executor: executor.clone(),
main_thread_id: thread::current().id(),
message_window: HwndWrapper(window),
message_window_class_name: class_name,
tasks_sender,
background_thread: BackgroundThread::get_or_create(executor),
}
}
fn schedule_gui(&self, task: T) -> bool {
if self.is_main_thread() {
match self.executor.upgrade() {
Some(executor) => executor.execute(task, true),
None => {
nice_debug_assert_failure!("GUI task was posted after the executor was dropped")
}
}
true
} else {
let success = self.tasks_sender.try_send(task).is_ok();
if success {
unsafe {
let _ = PostMessageA(
Some(self.message_window.0),
NOTIFY_MESSAGE_ID,
WPARAM(0),
LPARAM(0),
);
}
}
success
}
}
fn schedule_background(&self, task: T) -> bool {
self.background_thread.schedule(task)
}
fn is_main_thread(&self) -> bool {
permit_alloc(|| thread::current().id() == self.main_thread_id)
}
}
impl<T, E> Drop for WindowsEventLoop<T, E> {
fn drop(&mut self) {
unsafe {
let _ = DestroyWindow(self.message_window.0);
};
unsafe {
let _ = UnregisterClassA(
PCSTR(self.message_window_class_name.as_bytes_with_nul().as_ptr()),
None,
);
};
}
}
unsafe extern "system" fn window_proc(
handle: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message {
WM_CREATE => {
let create_params = lparam.0 as *const CREATESTRUCTA;
assert!(!create_params.is_null());
let poll_callback = (*create_params).lpCreateParams as *mut PollCallback;
assert!(!poll_callback.is_null());
SetWindowLongPtrA(handle, GWLP_USERDATA, poll_callback as isize);
}
NOTIFY_MESSAGE_ID => {
let callback = GetWindowLongPtrA(handle, GWLP_USERDATA) as *mut PollCallback;
if callback.is_null() {
nice_debug_assert_failure!(
"The notify function got called before the window was created"
);
return LRESULT(0);
}
(*callback)();
}
WM_DESTROY => {
let _the_bodies_hit_the_floor =
Box::from_raw(GetWindowLongPtrA(handle, GWLP_USERDATA) as *mut PollCallback);
SetWindowLongPtrA(handle, GWLP_USERDATA, 0);
}
_ => (),
}
DefWindowProcA(handle, message, wparam, lparam)
}
}