diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 99353128..4a47047d 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -14,7 +14,7 @@ use super::drop_target::DropTarget; use super::*; use crate::handler::WindowHandlerBuilder; use crate::host::Host; -use crate::platform::win::window_state::{WindowSharedState, WindowState}; +use crate::platform::win::window_state::{RedrawTimer, WindowSharedState, WindowState}; use crate::platform::PlatformError; use crate::utils::SizingStrategy; use crate::window::WindowInitializer; @@ -22,7 +22,7 @@ use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; use crate::wrappers::win32::{ ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessGuard, LibraryModule, Rect, - TimerId, WindowStyle, + TimerId, TimerType, WindowStyle, }; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSize}; @@ -359,12 +359,6 @@ impl BaseviewWindow { handler.poll(); } - pub(crate) fn handle_timer(&self, timer: &TimerHandle) { - let Some(handler) = self.handler.get() else { return }; - - handler.on_timer(timer.into()); - } - pub(crate) fn handle_event(&self, event: Event) -> EventStatus { let Some(handler) = self.handler.get() else { return EventStatus::Ignored; @@ -372,6 +366,26 @@ impl BaseviewWindow { handler.on_event(event) } + + pub(crate) fn handle_delayed_redraw_wm_timer(&self, id: TimerId) { + match self.shared_state.delayed_redraw_timers.remove_if_exists(id) { + Ok(false) => {} + Err(e) => { + warn!("Could not remove timer: {}", e); + } + Ok(true) => { + self.window_state.request_redraw(); + } + } + } + + pub(crate) fn handle_user_wm_timer(&self, id: TimerId) { + if self.shared_state.user_timers.exists(id) { + let Some(handler) = self.handler.get() else { return }; + + handler.on_timer((&id).into()); + } + } } impl Drop for BaseviewWindow { @@ -626,7 +640,7 @@ unsafe fn wnd_proc_inner( WM_TIMER => { let timer_id = TimerId::from_raw(wparam)?; - if window_state.redraw_timer.matches_id(timer_id) { + if timer_id == RedrawTimer::ID { if !window_state.redraw_timer.is_running() { return None; }; @@ -634,24 +648,7 @@ unsafe fn wnd_proc_inner( return Some(0); } - match window_state.shared.delayed_redraw_timers.remove_if_exists(window, timer_id) { - Ok(false) => {} - Err(e) => { - warn!("Could not remove timer: {}", e); - return None; - } - Ok(true) => { - window_state.request_redraw(); - return Some(0); - } - } - - if window_state.shared.user_timers.exists(timer_id) { - window_bv.handle_timer(&timer_id); - Some(0) - } else { - None - } + None } WM_CLOSE => { window_bv.handle_event(Event::Window(WindowEvent::WillClose)); diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 4e2c6e78..395e6917 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -2,14 +2,14 @@ use crate::dpi::{PhysicalSize, Size}; use crate::platform::win::dpi::DpiScalingStrategy; use crate::platform::win::keyboard::KeyboardState; use crate::platform::win::waker::WindowWakerSource; -use crate::platform::{PlatformHandle, TimerHandle, WindowWaker}; +use crate::platform::{BaseviewWindow, PlatformHandle, TimerHandle, WindowWaker}; use crate::utils::SizingStrategy; use crate::window::WindowInitializer; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::{HWnd, PostMessageExt}; use crate::wrappers::win32::{ - Dpi, DpiAwarenessGuard, ExtendedUser32, LibraryModule, TimerList, TimerSlot, + Dpi, DpiAwarenessGuard, ExtendedUser32, LibraryModule, TimerId, TimerList, TimerSlot, TimerType, }; use crate::{MouseCursor, WindowSize}; use raw_window_handle::{DisplayHandle, Win32WindowHandle}; @@ -18,7 +18,12 @@ use std::num::NonZeroIsize; use std::rc::Rc; use std::time::Duration; -const REDRAW_TIMER_DELAY_MSEC: u32 = 15; +pub(crate) struct RedrawTimer; + +impl TimerType for RedrawTimer { + const ID: TimerId = TimerId::from_const(0x42691); + const TIMEOUT_MSEC: u32 = 15; +} /// All data associated with the window. pub(crate) struct WindowState { @@ -31,7 +36,7 @@ pub(crate) struct WindowState { pub user32: LibraryModule, pub shared: Rc, - pub(crate) redraw_timer: TimerSlot, + pub(crate) redraw_timer: TimerSlot, pub redraw_requested: Cell, #[cfg(feature = "opengl")] @@ -131,13 +136,13 @@ impl WindowState { pub fn request_redraw(&self) { self.redraw_requested.set(true); - self.redraw_timer.start_if_not_running(REDRAW_TIMER_DELAY_MSEC); + self.redraw_timer.start_if_not_running(); } pub fn setup_redraw_request_for_next_frame(&self) { let should_redraw_next_frame = self.redraw_requested.take(); - self.redraw_timer.set_running(should_redraw_next_frame, REDRAW_TIMER_DELAY_MSEC); + self.redraw_timer.set_running(should_redraw_next_frame); } pub fn request_redraw_after(&self, duration: Duration) { @@ -180,11 +185,15 @@ impl WindowSharedState { let state = Self { sizing_strategy: SizingStrategy::from_settings(&init.settings), user32, + hwnd: None.into(), // Internal state is_alive: true.into(), resize_host_originated: false.into(), destroy_host_originated: false.into(), + delayed_redraw_timers: TimerList::new(BaseviewWindow::handle_delayed_redraw_wm_timer), + user_timers: TimerList::new(BaseviewWindow::handle_user_wm_timer), + window_waker_source: WindowWakerSource::new(), // Overriden by init() below parented: false.into(), @@ -192,10 +201,6 @@ impl WindowSharedState { current_size: init.settings.size.to_physical(1.0).into(), fallback_scale_factor: init.settings.fallback_scale_factor.into(), dpi_scaling_strategy: DpiScalingStrategy::default().into(), - delayed_redraw_timers: TimerList::new(), - user_timers: TimerList::new(), - window_waker_source: WindowWakerSource::new(), - hwnd: None.into(), }; state.init(init); @@ -265,15 +270,6 @@ impl WindowSharedState { } } -impl Drop for WindowSharedState { - fn drop(&mut self) { - if let Some(hwnd) = self.hwnd.get() { - self.user_timers.stop_and_destroy_all(hwnd); - self.delayed_redraw_timers.stop_and_destroy_all(hwnd); - } - } -} - struct Guard<'a>(&'a Cell); impl<'a> Drop for Guard<'a> { fn drop(&mut self) { diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index 079974d3..aadb950e 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -265,6 +265,10 @@ impl<'a> DpiAwarenessGuard<'a> { } } + pub fn dummy() -> Self { + Self { inner: None } + } + pub fn with_guard_optional( user32: &'a ExtendedUser32, strategy: DpiScalingStrategy, handler: impl FnOnce() -> T, ) -> T { diff --git a/src/wrappers/win32/style.rs b/src/wrappers/win32/style.rs index 9abf4bb0..18cee6c3 100644 --- a/src/wrappers/win32/style.rs +++ b/src/wrappers/win32/style.rs @@ -24,4 +24,8 @@ impl WindowStyle { style } + + pub const fn dummy() -> Self { + Self { style: 0, style_ex: 0 } + } } diff --git a/src/wrappers/win32/timer.rs b/src/wrappers/win32/timer.rs index 44d1456b..5e8406dc 100644 --- a/src/wrappers/win32/timer.rs +++ b/src/wrappers/win32/timer.rs @@ -1,9 +1,14 @@ -use crate::wrappers::win32::window::HWnd; +use crate::platform::{BaseviewWindow, PlatformError}; +use crate::wrappers::win32::window::{create_window, use_window, HWnd, WindowImpl}; +use crate::wrappers::win32::{DpiAwarenessGuard, WindowStyle}; +use dpi::PhysicalSize; use std::cell::{Cell, RefCell}; +use std::marker::PhantomData; use std::num::NonZeroUsize; use std::time::Duration; -use windows_core::Error; -use windows_sys::Win32::Foundation::WPARAM; +use windows::Win32::UI::WindowsAndMessaging::WM_TIMER; +use windows_core::{Error, HSTRING}; +use windows_sys::Win32::Foundation::{LPARAM, LRESULT, WPARAM}; #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct TimerId(NonZeroUsize); @@ -16,72 +21,116 @@ impl TimerId { pub fn as_raw(&self) -> usize { self.0.get() } + + pub const fn from_const(value: usize) -> Self { + let Some(value) = NonZeroUsize::new(value) else { unreachable!() }; + Self(value) + } +} + +pub trait TimerType { + const ID: TimerId; + const TIMEOUT_MSEC: u32; } #[derive(PartialEq, Eq)] -pub struct TimerSlot { - id: Cell>, +pub struct TimerSlot { + running: Cell, hwnd: HWnd, + _timer: PhantomData, } -impl TimerSlot { +impl TimerSlot { pub fn empty(hwnd: HWnd) -> Self { - Self { hwnd, id: None.into() } + Self { hwnd, running: false.into(), _timer: PhantomData } } pub fn is_running(&self) -> bool { - self.id.get().is_some() + self.running.get() } - pub fn start_if_not_running(&self, timeout_msec: u32) { + pub fn start_if_not_running(&self) { if !self.is_running() { - self.start(timeout_msec); + self.start() } } - fn start(&self, timeout_msec: u32) { - match self.hwnd.create_timer(timeout_msec) { - Ok(timer_id) => self.id.set(Some(timer_id)), + fn start(&self) { + match self.hwnd.reset_timer(T::ID, T::TIMEOUT_MSEC) { + Ok(()) => self.running.set(true), Err(e) => crate::warn!("Failed to start timer: {}", e), } } - pub fn set_running(&self, running: bool, timeout_msec: u32) { + pub fn set_running(&self, running: bool) { match (running, self.is_running()) { (true, true) | (false, false) => (), // Nothing to do (false, true) => self.kill(), - (true, false) => self.start(timeout_msec), + (true, false) => self.start(), } } pub fn kill(&self) { - if let Some(timer_id) = self.id.take() { - if let Err(e) = self.hwnd.kill_timer(timer_id) { + if self.running.take() { + if let Err(e) = self.hwnd.kill_timer(T::ID) { crate::warn!("Failed to kill timer: {}", e); } } } - - pub fn matches_id(&self, other: TimerId) -> bool { - match self.id.get() { - None => false, - Some(id) => id == other, - } - } } pub struct TimerList { timers: RefCell>, + timer_window: Cell>, + timer_fn: fn(&BaseviewWindow, TimerId), } impl TimerList { - pub fn new() -> Self { - Self { timers: Vec::new().into() } + pub fn new(timer_fn: fn(&BaseviewWindow, TimerId)) -> Self { + Self { timers: Vec::new().into(), timer_window: None.into(), timer_fn } + } + + fn get_or_create_timer_window(&self, baseview_window: HWnd) -> Result { + if let Some(timer_window) = self.timer_window.get() { + return Ok(timer_window); + } + + let timer_fn = self.timer_fn; + let timer_window = create_window( + &HSTRING::new(), + WindowStyle::dummy(), + PhysicalSize::new(1, 1), + None, + &DpiAwarenessGuard::dummy(), + move |_| TimerWindow { baseview_window, timer_fn }, + )?; + self.timer_window.set(Some(timer_window)); + + Ok(timer_window) + } + + pub fn get_new_timer_id(&self) -> TimerId { + let Some(mut id) = NonZeroUsize::new(1) else { unreachable!() }; + + let timers = self.timers.borrow(); + + while timers.contains(&TimerId(id)) { + let Some(new_id) = id.checked_add(1) else { panic!("Too many timers") }; + id = new_id; + } + + TimerId(id) } - pub fn add_new_timer(&self, window: HWnd, timeout: Duration) -> Result { + pub fn add_new_timer( + &self, baseview_window: HWnd, timeout: Duration, + ) -> Result { let timeout_msec = timeout.as_millis().try_into().unwrap_or(u32::MAX); - let new_timer_id = window.create_timer(timeout_msec)?; + let timer_window = self.get_or_create_timer_window(baseview_window)?; + let new_timer_id = self.get_new_timer_id(); + + timer_window.reset_timer(new_timer_id, timeout_msec)?; + self.timers.borrow_mut().push(new_timer_id); Ok(new_timer_id) } @@ -90,11 +139,12 @@ impl TimerList { self.timers.borrow().contains(&timer_id) } - pub fn remove_if_exists(&self, window: HWnd, id: TimerId) -> Result { + pub fn remove_if_exists(&self, id: TimerId) -> Result { if !self.pop_if_exists(id) { return Ok(false); }; + let Some(window) = self.timer_window.get() else { unreachable!() }; window.kill_timer(id)?; Ok(true) @@ -111,11 +161,58 @@ impl TimerList { self.timers.borrow_mut().pop() } - pub fn stop_and_destroy_all(&self, window: HWnd) { + fn stop_and_destroy_all(&self) { + let Some(window) = self.timer_window.get() else { return }; + while let Some(timer) = self.pop() { if let Err(e) = window.kill_timer(timer) { crate::warn!("Failed to kill timer: {}", e); } } + + let _ = window.destroy(); + self.timer_window.set(None); + } +} + +impl Drop for TimerList { + fn drop(&mut self) { + self.stop_and_destroy_all() + } +} + +struct TimerWindow { + baseview_window: HWnd, + timer_fn: fn(&BaseviewWindow, TimerId), +} + +impl WindowImpl for TimerWindow { + fn non_client_create(&self, _: HWnd) -> Result<(), PlatformError> { + Ok(()) + } + + fn after_create(&self, _: HWnd) -> Result<(), PlatformError> { + Ok(()) + } + + unsafe fn handle_message( + &self, _: HWnd, message_code: u32, wparam: WPARAM, _: LPARAM, + ) -> Option { + if message_code != WM_TIMER { + return None; + } + + let timer_id = TimerId::from_raw(wparam)?; + + // SAFETY: baseview_window is of type BaseviewWindow + unsafe { + use_window::(self.baseview_window, |hwnd| { + (self.timer_fn)(hwnd, timer_id) + }) + }; + + Some(0) } + + fn before_destroy(&self, _: HWnd) {} } diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 53e3bb6e..da2cb8a2 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -5,8 +5,10 @@ mod window_class; #[cfg(feature = "opengl")] mod dc; + #[cfg(feature = "opengl")] pub use dc::*; +use std::mem::ManuallyDrop; #[cfg(feature = "opengl")] mod wgl; #[cfg(feature = "opengl")] @@ -55,6 +57,21 @@ pub trait WindowImpl: 'static { fn before_destroy(&self, window: HWnd); } +/// # Safety +/// +/// Must only be called on a window created with create_window, and W must match the inner window type. +pub unsafe fn use_window(hwnd: HWnd, handler: impl FnOnce(&W)) { + let Some(raw_data) = hwnd.get_userdata_ptr::>() else { return }; + // This is actually borrowed from the window itself + let raw = ManuallyDrop::new(Rc::from_raw(raw_data.as_ptr())); + + // We keep this alive for as long as inner() runs, to guard against possible re-entrancy + let window_data = Rc::clone(&raw); + if let Some(inner) = window_data.inner() { + handler(inner); + } +} + /// Creates a window from the given settings, with a given [`WindowImpl`] type to handle the message, /// and an initializer function for that type. /// diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index 303446c9..72d855b7 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -222,15 +222,6 @@ impl HWnd { Ok(()) } - pub fn create_timer(&self, elapse: u32) -> Result { - let result = unsafe { SetTimer(null_mut(), 0, elapse, None) }; - let timer_id = TimerId::from_raw(result).ok_or_else(Error::from_thread)?; - - self.reset_timer(timer_id, elapse)?; - - Ok(timer_id) - } - pub fn reset_timer(&self, timer_id: TimerId, elapse: u32) -> Result<()> { let result = unsafe { SetTimer(self.as_raw(), timer_id.as_raw(), elapse, None) };