Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions winit-win32/src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,26 @@ use windows_sys::Win32::UI::Input::{
MOUSE_MOVE_RELATIVE, RAWINPUT, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
};
use windows_sys::Win32::UI::WindowsAndMessaging::{
CREATESTRUCTW, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GWL_STYLE,
GWL_USERDATA, GetClientRect, GetCursorPos, GetMenu, HTCAPTION, HTCLIENT, LoadCursorW,
MINMAXINFO, MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, MsgWaitForMultipleObjectsEx,
NCCALCSIZE_PARAMS, PEN_FLAG_BARREL, PEN_FLAG_ERASER, PEN_MASK_PRESSURE, PEN_MASK_ROTATION,
PEN_MASK_TILT_X, PEN_MASK_TILT_Y, PM_REMOVE, PT_PEN, PT_TOUCH, PeekMessageW, PostMessageW,
QS_ALLINPUT, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, RegisterClassExW, RegisterWindowMessageA,
SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES,
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SetCursor, SetWindowPos,
SystemParametersInfoW, TranslateMessage, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE,
WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO,
WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION,
WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS,
WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH,
WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM,
WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT,
WMSZ_TOPRIGHT, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,
WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
CREATESTRUCTW, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW,
ENDSESSION_CLOSEAPP, GWL_STYLE, GWL_USERDATA, GetClientRect, GetCursorPos, GetMenu, HTCAPTION,
HTCLIENT, LoadCursorW, MINMAXINFO, MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE,
MsgWaitForMultipleObjectsEx, NCCALCSIZE_PARAMS, PEN_FLAG_BARREL, PEN_FLAG_ERASER,
PEN_MASK_PRESSURE, PEN_MASK_ROTATION, PEN_MASK_TILT_X, PEN_MASK_TILT_Y, PM_REMOVE, PT_PEN,
PT_TOUCH, PeekMessageW, PostMessageW, QS_ALLINPUT, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL,
RegisterClassExW, RegisterWindowMessageA, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
SWP_NOZORDER, SetCursor, SetWindowPos, SystemParametersInfoW, TranslateMessage, WHEEL_DELTA,
WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENDSESSION,
WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS,
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL,
WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN,
WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND,
WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING,
WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT,
WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, WNDCLASSEXW, WS_EX_LAYERED,
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
Expand Down Expand Up @@ -249,12 +249,12 @@ impl EventLoop {

pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,
app: A,
) -> Result<(), EventLoopError> {
self.runner.clear_exit();

// SAFETY: The resetter is not leaked.
let _app_resetter = unsafe { self.runner.set_app(&mut app) };
let _app_resetter = unsafe { self.runner.set_app(app) };

let exit_code = loop {
self.wait_for_messages(None);
Expand All @@ -281,10 +281,10 @@ impl EventLoop {
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
mut app: A,
app: A,
) -> PumpStatus {
// SAFETY: The resetter is not leaked.
let _app_resetter = unsafe { self.runner.set_app(&mut app) };
let _app_resetter = unsafe { self.runner.set_app(app) };

self.runner.wakeup();

Expand Down Expand Up @@ -2384,6 +2384,28 @@ unsafe extern "system" fn thread_event_target_callback(

unsafe { DefWindowProcW(window, msg, wparam, lparam) }
},
WM_ENDSESSION => {
// `wParam` is `FALSE` is for if the shutdown gets canceled,
// and we don't need to handle that case since we didn't do anything prior in response
// to `WM_QUERYENDSESSION`
if wparam == true as usize {
// Sent from Restart Manager
// > https://learn.microsoft.com/en-us/windows/win32/rstmgr/guidelines-for-applications
if lparam == ENDSESSION_CLOSEAPP as isize {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To help testing this, you could try https://github.com/Legend-Master/windows-restart-manager-test, replace let file_path = HSTRING::from(r""); to the executable path you want to terminate, and cargo run

// Mark the event loop to exit is enough
// since Restart Manager doesn't terminate us right after returning here
userdata.event_loop_runner.set_exit_code(0);
}
// Treat everything else as system shutdown
else {
userdata.event_loop_runner.loop_destroyed();
// Windows will terminate us anytime after we return `0` here,
// just do that ourselves here
std::process::exit(0)
}
}
0
},

_ if msg == USER_EVENT_MSG_ID.get() => {
// synthesis a placeholder UserEvent, so that if the callback is
Expand Down
25 changes: 13 additions & 12 deletions winit-win32/src/event_loop/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use super::{ActiveEventLoop, ControlFlow, EventLoopThreadExecutor};
use crate::event_loop::{GWL_USERDATA, WindowData};
use crate::util::get_window_long;

type EventHandler = Cell<Option<&'static mut (dyn ApplicationHandler + 'static)>>;
type EventHandler = Cell<Option<Box<dyn ApplicationHandler>>>;

pub(crate) struct EventLoopRunner {
pub(super) thread_id: u32,
Expand Down Expand Up @@ -96,19 +96,16 @@ impl EventLoopRunner {
///
/// The returned type must not be leaked (as that would allow the application to be associated
/// with the runner for too long).
pub(crate) unsafe fn set_app<'app>(
&self,
app: &'app mut (dyn ApplicationHandler + 'app),
) -> impl Drop + 'app {
pub(crate) unsafe fn set_app<A: ApplicationHandler>(&self, app: A) -> impl Drop + use<A> {
let app = Box::new(app);
// Erase app lifetime, to allow storing on the event loop runner.
//
// SAFETY: Caller upholds that the lifetime of the closure is upheld, by not dropping the
// return type which resets it.
let f = unsafe {
mem::transmute::<
&'app mut (dyn ApplicationHandler + 'app),
&'static mut (dyn ApplicationHandler + 'static),
>(app)
mem::transmute::<Box<dyn ApplicationHandler>, Box<dyn ApplicationHandler + 'static>>(
app,
)
};

let old_event_handler = self.event_handler.replace(Some(f));
Expand Down Expand Up @@ -260,12 +257,12 @@ impl EventLoopRunner {
closure: impl FnOnce(&mut dyn ApplicationHandler, &dyn RootActiveEventLoop),
) {
self.catch_unwind(|| {
let event_handler = self.event_handler.take().expect(
let mut event_handler = self.event_handler.take().expect(
"either event handler is re-entrant (likely), or no event handler is registered \
(very unlikely)",
);

closure(event_handler, ActiveEventLoop::from_ref(self));
closure(&mut event_handler, ActiveEventLoop::from_ref(self));

assert!(self.event_handler.replace(Some(event_handler)).is_none());
});
Expand Down Expand Up @@ -333,14 +330,17 @@ impl EventLoopRunner {
self.call_new_events(true);
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.last_events_cleared.set(Instant::now());
drop(self.event_handler.take());
},
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),

// State transitions that start the event handling process.
(Idle, HandlingMainEvents) => {
self.call_new_events(false);
},
(Idle, Destroyed) => {},
(Idle, Destroyed) => {
drop(self.event_handler.take());
},

(HandlingMainEvents, Idle) => {
// This is always the last event we dispatch before waiting for new events
Expand All @@ -350,6 +350,7 @@ impl EventLoopRunner {
(HandlingMainEvents, Destroyed) => {
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
self.last_events_cleared.set(Instant::now());
drop(self.event_handler.take());
},

(Destroyed, _) => panic!("cannot move state from Destroyed"),
Expand Down
6 changes: 6 additions & 0 deletions winit/src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ changelog entry.
### Fixed

- On X11, fix `set_hittest` not working on some window managers.


### Changed

- On Windows, the `ApplicationHandler` passed to `EventLoop::run_app` is now dropped on receiving `WM_ENDSESSION` message in system shutdown
- On Windows, the event loop will now exit on receiving `WM_ENDSESSION` in reaction to Restart Manager
5 changes: 4 additions & 1 deletion winit/src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ impl EventLoop {
mut app: A,
) -> Result<(), EventLoopError> {
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
orbital_platform,
Expand All @@ -232,6 +231,10 @@ impl EventLoop {
drop(app);
result
}
#[cfg(windows_platform)]
{
self.event_loop.run_app_on_demand(app)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this a separate call on Windows to pass in the ownership of app instead of &mut app so we can drop it later, but I am not exactly sure why it was done like this

let result = self.event_loop.run_app_on_demand(&mut app);
// SAFETY: unsure that the state is dropped before the exit from the event loop.
drop(app);
result

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was done like that, because run_on_demand borrows, so you can not drop from it, since you'll be dropping & more likely, which won't work, but in run_app it's :static thus the actual value is being dropped.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure that it's wrong too? run_app_on_demand doesn't borrow, and passing a reference prevents it from Drop-ing when e.g. NSApplicationDelegate is terminated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So should I change this for all platforms?

}
#[cfg(web_platform)]
{
self.event_loop.register_app(app);
Expand Down
Loading