diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3c56bb36f16..1ccbadf4371 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -12,7 +12,7 @@ //! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to //! add a `WindowState` entry to a list of window to be used by the callback. -use winapi::shared::basetsd::DWORD_PTR; +use winapi::shared::basetsd::{DWORD_PTR, LONG_PTR}; use winapi::shared::basetsd::UINT_PTR; use std::{mem, ptr}; use std::ffi::OsString; @@ -101,7 +101,6 @@ pub struct WindowState { pub maximized: bool, pub resizable: bool, pub mouse_buttons_down: u32, - pub modal_timer_handle: UINT_PTR } impl WindowState { @@ -191,7 +190,8 @@ impl EventLoop { event_loop: self, control_flow: ControlFlow::default(), runner_state: RunnerState::New, - modal_loop_data: None, + in_modal_loop: false, + modal_redraw_window: self.thread_msg_target, event_handler: unsafe { // Transmute used to erase lifetimes. mem::transmute::< @@ -214,8 +214,6 @@ impl EventLoop { } unsafe { - let timer_handle = winuser::SetTimer(ptr::null_mut(), 0, 0x7FFFFFFF, None); - let mut msg = mem::uninitialized(); let mut msg_unprocessed = false; @@ -242,16 +240,7 @@ impl EventLoop { msg_unprocessed = true; } ControlFlow::WaitUntil(resume_time) => { - let now = Instant::now(); - if now <= resume_time { - let duration = resume_time - now; - winuser::SetTimer(ptr::null_mut(), timer_handle, dur2timeout(duration), None); - if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { - break 'main - } - winuser::SetTimer(ptr::null_mut(), timer_handle, 0x7FFFFFFF, None); - msg_unprocessed = true; - } + wait_until_time_or_msg(resume_time); }, ControlFlow::Poll => () } @@ -287,15 +276,11 @@ pub(crate) struct EventLoopRunner { event_loop: *const EventLoop, control_flow: ControlFlow, runner_state: RunnerState, - modal_loop_data: Option, + modal_redraw_window: HWND, + in_modal_loop: bool, event_handler: *mut FnMut(Event, &RootEventLoop, &mut ControlFlow) } -struct ModalLoopData { - hwnd: HWND, - timer_handle: UINT_PTR -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum RunnerState { /// The event loop has just been created, and an `Init` event must be sent. @@ -364,12 +349,18 @@ impl EventLoopRunner { } unsafe fn process_event(&mut self, event: Event) { - // If we're in the middle of a modal loop, only set the timer for zero if it hasn't been - // reset in a prior call to `process_event`. - if let Some(ModalLoopData{hwnd, timer_handle}) = self.modal_loop_data { - if self.runner_state != RunnerState::HandlingEvents { - winuser::SetTimer(hwnd, timer_handle, 0, None); - } + // If we're in the modal loop, we need to have some mechanism for finding when the event + // queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities + // for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have + // been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when + // the events queue has been emptied. + if self.in_modal_loop { + winuser::RedrawWindow( + self.modal_redraw_window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT + ); } // If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're @@ -476,6 +467,29 @@ impl EventLoopRunner { } } +// Returns true if the wait time was reached, and false if a message must be processed. +unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool { + let mut msg = mem::uninitialized(); + let now = Instant::now(); + if now <= wait_until { + // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond + // from the requested time and spinlock for the remainder to compensate for that. + winuser::MsgWaitForMultipleObjects( + 0, + ptr::null(), + 1, + dur2timeout(wait_until - now).saturating_sub(1), + winuser::QS_ALLINPUT + ); + while Instant::now() < wait_until { + if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { + return false; + } + } + } + + return true; +} // Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs fn dur2timeout(dur: Duration) -> DWORD { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the @@ -649,7 +663,7 @@ lazy_static! { fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> (HWND, Sender) { unsafe { let window = winuser::CreateWindowExW( - 0, + winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED, THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(), ptr::null_mut(), 0, @@ -660,6 +674,14 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> libloaderapi::GetModuleHandleW(ptr::null()), ptr::null_mut() ); + winuser::SetWindowLongPtrW( + window, + winuser::GWL_STYLE, + // The window technically has to be visible to receive WM_PAINT messages (which are used + // for delivering events during resizes), but it isn't displayed to the user because of + // the LAYERED style. + (winuser::WS_VISIBLE | winuser::WS_POPUP) as LONG_PTR + ); let (tx, rx) = mpsc::channel(); @@ -727,67 +749,20 @@ unsafe extern "system" fn public_window_callback( let subclass_input = &mut*(subclass_input_ptr as *mut SubclassInput); match msg { - winuser::WM_SYSCOMMAND => { - { - let mut window_state = subclass_input.window_state.lock(); - if window_state.modal_timer_handle == 0 { - window_state.modal_timer_handle = winuser::SetTimer(window, 0, 0x7FFFFFFF, None); - } - } - commctrl::DefSubclassProc(window, msg, wparam, lparam) - } winuser::WM_ENTERSIZEMOVE => { - let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle; - if let ELRSharedOption::Runner(runner) = *subclass_input.event_loop_runner.borrow_mut() { - (*runner).modal_loop_data = Some(ModalLoopData { - hwnd: window, - timer_handle: modal_timer_handle - }); + let runner = subclass_input.event_loop_runner.borrow_mut(); + if let ELRSharedOption::Runner(runner) = *runner { + (*runner).in_modal_loop = true; } - winuser::SetTimer(window, modal_timer_handle, 0, None); 0 }, winuser::WM_EXITSIZEMOVE => { - let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle; - if let ELRSharedOption::Runner(runner) = *subclass_input.event_loop_runner.borrow_mut() { - (*runner).modal_loop_data = None; + let runner = subclass_input.event_loop_runner.borrow_mut(); + if let ELRSharedOption::Runner(runner) = *runner { + (*runner).in_modal_loop = false; } - winuser::SetTimer(window, modal_timer_handle, 0x7FFFFFFF, None); 0 }, - winuser::WM_TIMER => { - let modal_timer_handle = subclass_input.window_state.lock().modal_timer_handle; - if wparam == modal_timer_handle { - let runner = subclass_input.event_loop_runner.borrow_mut(); - if let ELRSharedOption::Runner(runner) = *runner { - let runner = &mut *runner; - if runner.modal_loop_data.is_some() { - runner.events_cleared(); - match runner.control_flow { - ControlFlow::Exit => (), - ControlFlow::Wait => { - winuser::SetTimer(window, modal_timer_handle, 0x7FFFFFFF, None); - }, - ControlFlow::WaitUntil(resume_time) => { - let now = Instant::now(); - let duration = match now <= resume_time { - true => dur2timeout(resume_time - now), - false => 0 - }; - winuser::SetTimer(window, modal_timer_handle, duration, None); - }, - ControlFlow::Poll => { - winuser::SetTimer(window, modal_timer_handle, 0, None); - } - } - - runner.new_events(); - } - } - } - 0 - } - winuser::WM_NCCREATE => { enable_non_client_dpi_scaling(window); commctrl::DefSubclassProc(window, msg, wparam, lparam) @@ -804,12 +779,6 @@ unsafe extern "system" fn public_window_callback( winuser::WM_DESTROY => { use event::WindowEvent::Destroyed; - { - let window_state = subclass_input.window_state.lock(); - if window_state.modal_timer_handle != 0 { - winuser::KillTimer(window, window_state.modal_timer_handle); - } - } subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Destroyed @@ -1537,6 +1506,76 @@ unsafe extern "system" fn thread_event_target_callback( drop(subclass_input); 0 }, + // Because WM_PAINT comes after all other messages, we use it during modal loops to detect + // when the event queue has been emptied. See `process_event` for more details. + winuser::WM_PAINT => { + winuser::ValidateRect(window, ptr::null()); + let queue_call_again = || { + winuser::RedrawWindow( + window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT + ); + }; + let in_modal_loop = { + let runner = subclass_input.event_loop_runner.borrow_mut(); + if let ELRSharedOption::Runner(runner) = *runner { + (*runner).in_modal_loop + } else { + false + } + }; + if in_modal_loop { + let mut msg = mem::uninitialized(); + loop { + if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { + break; + } + // Clear all paint/timer messages from the queue before sending the events cleared message. + match msg.message { + // Flush the event queue of WM_PAINT messages. + winuser::WM_PAINT | + winuser::WM_TIMER => { + // Remove the message from the message queue. + winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1); + + if msg.hwnd != window { + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + } + }, + // If the message isn't one of those three, it may be handled by the modal + // loop so we should return control flow to it. + _ => { + queue_call_again(); + return 0; + } + } + } + + let runner = subclass_input.event_loop_runner.borrow_mut(); + if let ELRSharedOption::Runner(runner) = *runner { + let runner = &mut *runner; + runner.events_cleared(); + match runner.control_flow { + // Waiting is handled by the modal loop. + ControlFlow::Exit | + ControlFlow::Wait => runner.new_events(), + ControlFlow::WaitUntil(resume_time) => { + wait_until_time_or_msg(resume_time); + runner.new_events(); + queue_call_again(); + }, + ControlFlow::Poll => { + runner.new_events(); + queue_call_again(); + } + } + } + } + 0 + } _ if msg == *USER_EVENT_MSG_ID => { if let Ok(event) = subclass_input.user_event_receiver.recv() { subclass_input.send_event(Event::UserEvent(event)); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 82ca4e9377c..7b610d30ecf 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -1023,7 +1023,6 @@ unsafe fn init( mouse_in_window: false, saved_window_info: None, mouse_buttons_down: 0, - modal_timer_handle: 0 }; // Creating a mutex to track the current window state Arc::new(Mutex::new(window_state))