Fully invert windows control flow so win32 calls into winit's callback

This commit is contained in:
Osspial
2018-08-20 01:47:11 -04:00
parent 2c607ff87f
commit a0fef1a5fa
6 changed files with 511 additions and 205 deletions

View File

@@ -1,5 +1,6 @@
extern crate winit;
use std::time::{Duration, Instant};
use winit::{Event, WindowEvent, StartCause, ControlFlow};
fn main() {
let events_loop = winit::EventLoop::new();
@@ -13,20 +14,16 @@ fn main() {
println!("{:?}", event);
match event {
winit::Event::NewEvents(winit::StartCause::Init) =>
*control_flow = winit::ControlFlow::WaitTimeout(Duration::new(1, 0)),
winit::Event::NewEvents(winit::StartCause::TimeoutExpired{..}) => {
*control_flow = winit::ControlFlow::WaitTimeout(Duration::new(1, 0));
Event::NewEvents(StartCause::Init) =>
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0)),
Event::NewEvents(StartCause::ResumeTimeReached{..}) => {
*control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0));
println!("\nTimer\n");
},
winit::Event::NewEvents(winit::StartCause::WaitCancelled{start, requested_duration}) => {
println!("{:?}", Instant::now() - start);
*control_flow = winit::ControlFlow::WaitTimeout(requested_duration.unwrap().checked_sub(Instant::now() - start).unwrap_or(Duration::new(0, 0)));
}
winit::Event::WindowEvent {
event: winit::WindowEvent::CloseRequested,
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = winit::ControlFlow::Exit,
} => *control_flow = ControlFlow::Exit,
_ => ()
}
});

View File

@@ -1,4 +1,4 @@
use std::time::{Duration, Instant};
use std::time::Instant;
use std::path::PathBuf;
use {DeviceId, LogicalPosition, LogicalSize, WindowId};
@@ -48,19 +48,19 @@ impl<T> Event<T> {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StartCause {
/// Sent if the time specified by `ControlFlow::WaitTimeout` has been elapsed. Contains the
/// moment the timeout was requested and the requested duration of the timeout. The actual
/// duration is guaranteed to be greater than or equal to the requested timeout.
TimeoutExpired {
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
/// moment the timeout was requested and the requested resume time. The actual resume time is
/// guaranteed to be equal to or after the requested resume time.
ResumeTimeReached {
start: Instant,
requested_duration: Duration,
requested_resume: Instant
},
/// Sent if the OS has new events to send to the window, after a wait was requested. Contains
/// the moment the wait was requested, and if a wait timout was requested, its duration.
/// the moment the wait was requested and the resume time, if requested.
WaitCancelled {
start: Instant,
requested_duration: Option<Duration>
requested_resume: Option<Instant>
},
/// Sent if the event loop is being resumed after the loop's control flow was set to

View File

@@ -93,7 +93,7 @@ extern crate percent_encoding;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate smithay_client_toolkit as sctk;
use std::time::Duration;
use std::time::Instant;
pub(crate) use dpi::*; // TODO: Actually change the imports throughout the codebase.
pub use events::*;
pub use window::{AvailableMonitorsIter, MonitorId};
@@ -186,8 +186,8 @@ pub enum ControlFlow {
/// When the current loop iteration finishes, suspend the thread until another event arrives.
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the timeout expires.
WaitTimeout(Duration),
/// arrives or the given time is reached.
WaitUntil(Instant),
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
Poll,

View File

@@ -24,7 +24,7 @@ pub struct FileDropHandlerData {
pub interface: IDropTarget,
refcount: AtomicUsize,
window: HWND,
event_sender: Sender<Event<()>>
// event_sender: Sender<Event<()>>
}
pub struct FileDropHandler {
@@ -33,14 +33,14 @@ pub struct FileDropHandler {
#[allow(non_snake_case)]
impl FileDropHandler {
pub fn new(window: HWND, event_sender: Sender<Event<()>>) -> FileDropHandler {
pub fn new(window: HWND/*, event_sender: Sender<Event<()>>*/) -> FileDropHandler {
let data = Box::new(FileDropHandlerData {
interface: IDropTarget {
lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl,
},
refcount: AtomicUsize::new(1),
window,
event_sender,
// event_sender,
});
FileDropHandler {
data: Box::into_raw(data),
@@ -186,7 +186,7 @@ impl FileDropHandler {
impl FileDropHandlerData {
fn send_event(&self, event: Event<()>) {
self.event_sender.send(event).ok();
// self.event_sender.send(event).ok();
}
}

View File

@@ -17,6 +17,8 @@ use winapi::shared::basetsd::UINT_PTR;
use std::{mem, ptr};
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::rc::Rc;
use std::cell::RefCell;
use parking_lot::Mutex;
use crossbeam_channel::{self, Sender, Receiver};
@@ -34,7 +36,7 @@ use winapi::shared::minwindef::{
};
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::shared::windowsx;
use winapi::um::{winuser, winbase, processthreadsapi, ole2, commctrl};
use winapi::um::{winuser, winbase, ole2, processthreadsapi, commctrl, libloaderapi};
use winapi::um::winnt::{LONG, LPCSTR, SHORT};
use {
@@ -102,7 +104,8 @@ pub struct WindowState {
pub always_on_top: bool,
pub maximized: bool,
pub resizable: bool,
pub mouse_buttons_down: u32
pub mouse_buttons_down: u32,
pub modal_timer_handle: UINT_PTR
}
impl WindowState {
@@ -118,27 +121,45 @@ impl WindowState {
}
}
pub(crate) struct SubclassInput {
pub(crate) struct SubclassInput<T> {
pub window_state: Arc<Mutex<WindowState>>,
pub event_send: Sender<Event<()>>,
pub file_drop_handler: FileDropHandler
pub event_loop_runner: EventLoopRunnerShared<T>,
pub file_drop_handler: FileDropHandler,
}
impl SubclassInput {
fn send_event(&self, event: Event<()>) {
self.event_send.send(event).ok();
impl<T> SubclassInput<T> {
unsafe fn send_event(&self, event: Event<T>) {
let runner = self.event_loop_runner.borrow_mut();
if let Some(runner) = *runner {
(*runner).process_event(event);
} else {
panic!("Attempted to send event without active runner");
}
}
}
struct ThreadMsgTargetSubclassInput<T> {
event_loop_runner: EventLoopRunnerShared<T>,
user_event_receiver: Receiver<T>
}
impl<T> ThreadMsgTargetSubclassInput<T> {
unsafe fn send_event(&self, event: Event<T>) {
let runner = self.event_loop_runner.borrow_mut();
if let Some(runner) = *runner {
(*runner).process_event(event);
} else {
panic!("Attempted to send event without active runner");
}
}
}
pub struct EventLoop<T> {
// Id of the background thread from the Win32 API.
thread_id: DWORD,
pub(crate) event_send: Sender<Event<()>>,
pub(crate) user_event_send: Sender<T>,
event_recv: Receiver<Event<()>>,
user_event_recv: Receiver<T>
thread_msg_target: HWND,
thread_msg_sender: Sender<T>,
pub(crate) runner_shared: EventLoopRunnerShared<T>,
}
impl<T> EventLoop<T> {
@@ -150,153 +171,88 @@ impl<T> EventLoop<T> {
become_dpi_aware(dpi_aware);
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
let (event_send, event_recv) = crossbeam_channel::unbounded();
let (user_event_send, user_event_recv) = crossbeam_channel::unbounded();
let runner_shared = Rc::new(RefCell::new(None));
let (thread_msg_target, thread_msg_sender) = thread_event_target_window(runner_shared.clone());
EventLoop {
thread_id,
event_send,
event_recv,
user_event_send,
user_event_recv,
thread_msg_target, thread_msg_sender,
runner_shared
}
}
pub fn run<F>(self, mut event_handler: F) -> !
where F: 'static + FnMut(Event<T>, &::EventLoop<T>, &mut ControlFlow)
{
let event_loop = ::EventLoop {
events_loop: self,
_marker: ::std::marker::PhantomData
};
let mut control_flow = ControlFlow::default();
let mut timer_handle = 0;
unsafe {
// Calling `PostThreadMessageA` on a thread that does not have an events queue yet
// will fail. In order to avoid this situation, we call `IsGuiThread` to initialize
// it.
winuser::IsGUIThread(1);
let mut runner = EventLoopRunner {
event_loop: ::EventLoop {
events_loop: self,
_marker: ::std::marker::PhantomData
},
control_flow: ControlFlow::default(),
runner_state: RunnerState::New,
modal_loop_data: None,
event_handler: &mut event_handler
};
*runner.event_loop.events_loop.runner_shared.borrow_mut() = Some(&mut runner);
let timer_handle = winuser::SetTimer(ptr::null_mut(), 0, 0x7FFFFFFF, None);
let mut msg = mem::uninitialized();
let mut msg_unprocessed = false;
event_handler(Event::NewEvents(StartCause::Init), &event_loop, &mut control_flow);
'main: loop {
macro_rules! call_event_handler {
($event:expr) => {{
event_handler($event, &event_loop, &mut control_flow);
if ControlFlow::Exit == control_flow {
break 'main;
}
}}
}
let mut has_message = true;
let new_events_cause: StartCause;
match control_flow {
ControlFlow::Wait => {
let wait_start = Instant::now();
if winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
// Only happens if the message is `WM_QUIT`.
debug_assert_eq!(msg.message, winuser::WM_QUIT);
break 'main;
}
new_events_cause =
StartCause::WaitCancelled {
start: wait_start,
requested_duration: None
};
}
ControlFlow::WaitTimeout(duration) => {
let new_handle = winuser::SetTimer(ptr::null_mut(), timer_handle, dur2timeout(duration), None);
if timer_handle == 0 {
timer_handle = new_handle;
}
let wait_start = Instant::now();
if winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
// Only happens if the message is `WM_QUIT`.
debug_assert_eq!(msg.message, winuser::WM_QUIT);
break 'main;
}
if msg.message == winuser::WM_TIMER && msg.wParam == timer_handle {
new_events_cause =
StartCause::TimeoutExpired {
start: wait_start,
requested_duration: duration,
};
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) {
has_message = false;
}
} else {
new_events_cause =
StartCause::WaitCancelled {
start: wait_start,
requested_duration: Some(duration)
};
}
}
ControlFlow::Poll => {
runner.new_events();
loop {
if !msg_unprocessed {
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) {
has_message = false;
break;
}
new_events_cause = StartCause::Poll;
}
ControlFlow::Exit => break 'main
winuser::TranslateMessage(&mut msg);
winuser::DispatchMessageW(&mut msg);
msg_unprocessed = false;
}
call_event_handler!(Event::NewEvents(new_events_cause));
runner.events_cleared();
while has_message {
match msg.message {
// Handler is called in loop below.
x if x == *WAKEUP_MSG_ID => (),
x if x == *EXEC_MSG_ID => {
let mut function: Box<Box<FnMut()>> = Box::from_raw(msg.wParam as usize as *mut _);
function()
match runner.control_flow {
ControlFlow::Exit => break 'main,
ControlFlow::Wait => {
if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) {
break 'main
}
_ => {
// Calls `event_handler` below.
winuser::TranslateMessage(&msg);
winuser::DispatchMessageW(&msg);
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;
}
}
loop {
let full_event: Option<Event<T>> = select! {
recv(event_loop.events_loop.event_recv) -> event =>
event.ok().map(|e| e.map_nonuser_event().expect("User event sent through nonuser channel")),
recv(event_loop.events_loop.user_event_recv) -> user_event =>
user_event.ok().map(|e| Event::UserEvent(e)),
default => break
};
if let Some(full_event) = full_event {
call_event_handler!(full_event);
}
}
if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) {
has_message = false;
}
},
ControlFlow::Poll => ()
}
call_event_handler!(Event::EventsCleared);
}
if timer_handle != 0 {
winuser::KillTimer(ptr::null_mut(), timer_handle);
}
runner.call_event_handler(Event::LoopDestroyed);
*runner.event_loop.events_loop.runner_shared.borrow_mut() = None;
}
event_handler(Event::LoopDestroyed, &event_loop, &mut control_flow);
drop(event_handler);
::std::process::exit(0);
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
thread_id: self.thread_id,
event_send: self.user_event_send.clone()
target_window: self.thread_msg_target,
event_send: self.thread_msg_sender.clone()
}
}
@@ -308,8 +264,201 @@ impl<T> EventLoop<T> {
}
}
pub(crate) type EventLoopRunnerShared<T> = Rc<RefCell<Option<*mut EventLoopRunner<T>>>>;
pub(crate) struct EventLoopRunner<T> {
event_loop: ::EventLoop<T>,
control_flow: ControlFlow,
runner_state: RunnerState,
modal_loop_data: Option<ModalLoopData>,
event_handler: *mut FnMut(Event<T>, &::EventLoop<T>, &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.
New,
/// The event loop is idling, and began idling at the given instant.
Idle(Instant),
/// The event loop has received a signal from the OS that the loop may resume, but no winit
/// events have been generated yet. We're waiting for an event to be processed or the events
/// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`.
DeferredNewEvents(Instant),
/// The event loop is handling the OS's events and sending them to the user's callback.
/// `NewEvents` has been sent, and `EventsCleared` hasn't.
HandlingEvents,
}
impl<T> EventLoopRunner<T> {
unsafe fn new_events(&mut self) {
self.runner_state = match self.runner_state {
// If we're already handling events or have deferred `NewEvents`, we don't need to do
// do any processing.
RunnerState::HandlingEvents |
RunnerState::DeferredNewEvents(..) => self.runner_state,
// Send the `Init` `NewEvents` and immediately move into event processing.
RunnerState::New => {
self.call_event_handler(Event::NewEvents(StartCause::Init));
RunnerState::HandlingEvents
},
// When `NewEvents` gets sent after an idle depends on the control flow...
RunnerState::Idle(wait_start) => {
match self.control_flow {
// If we're polling, send `NewEvents` and immediately move into event processing.
ControlFlow::Poll => {
self.call_event_handler(Event::NewEvents(StartCause::Poll));
RunnerState::HandlingEvents
},
// If the user was waiting until a specific time, the `NewEvents` call gets sent
// at varying times depending on the current time.
ControlFlow::WaitUntil(resume_time) => {
match Instant::now() >= resume_time {
// If the current time is later than the requested resume time, we can tell the
// user that the resume time has been reached with `NewEvents` and immdiately move
// into event processing.
true => {
self.call_event_handler(Event::NewEvents(StartCause::ResumeTimeReached {
start: wait_start,
requested_resume: resume_time
}));
RunnerState::HandlingEvents
},
// However, if the current time is EARLIER than the requested resume time, we
// don't want to send the `WaitCancelled` event until we know an event is being
// sent. Defer.
false => RunnerState::DeferredNewEvents(wait_start)
}
},
// If we're waiting, `NewEvents` doesn't get sent until winit gets an event, so
// we defer.
ControlFlow::Wait |
// `Exit` shouldn't really ever get sent here, but if it does do something somewhat sane.
ControlFlow::Exit => RunnerState::DeferredNewEvents(wait_start),
}
}
};
}
unsafe fn process_event(&mut self, event: Event<T>) {
if let Some(ModalLoopData{hwnd, timer_handle}) = self.modal_loop_data {
if !self.event_processing_active() {
winuser::SetTimer(hwnd, timer_handle, 0, None);
}
}
// If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're
// already in processing nothing happens with this call.
self.new_events();
// Now that an event has been received, we have to send any `NewEvents` calls that were
// deferred.
if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state {
match self.control_flow {
ControlFlow::Wait => self.call_event_handler(
Event::NewEvents(StartCause::WaitCancelled {
start: wait_start,
requested_resume: None
})
),
ControlFlow::WaitUntil(resume_time) => {
let start_cause = match Instant::now() >= resume_time {
// If the current time is later than the requested resume time, the resume time
// has been reached.
true => StartCause::ResumeTimeReached {
start: wait_start,
requested_resume: resume_time
},
// Otherwise, the requested resume time HASN'T been reached and we send a WaitCancelled.
false => StartCause::WaitCancelled {
start: wait_start,
requested_resume: Some(resume_time)
},
};
self.call_event_handler(Event::NewEvents(start_cause));
},
ControlFlow::Poll |
ControlFlow::Exit => unreachable!()
}
self.runner_state = RunnerState::HandlingEvents;
}
self.call_event_handler(event);
}
unsafe fn events_cleared(&mut self) {
match self.runner_state {
// If we were handling events, send the EventsCleared message.
RunnerState::HandlingEvents => {
self.call_event_handler(Event::EventsCleared);
self.runner_state = RunnerState::Idle(Instant::now());
},
// If we *weren't* handling events, we don't have to do anything.
RunnerState::New |
RunnerState::Idle(..) => (),
// Some control flows require a NewEvents call even if no events were received. This
// branch handles those.
RunnerState::DeferredNewEvents(wait_start) => {
match self.control_flow {
// If we had deferred a Poll, send the Poll NewEvents and EventsCleared.
ControlFlow::Poll => {
self.call_event_handler(Event::NewEvents(StartCause::Poll));
self.call_event_handler(Event::EventsCleared);
},
// If we had deferred a WaitUntil and the resume time has since been reached,
// send the resume notification and EventsCleared event.
ControlFlow::WaitUntil(resume_time) => {
if Instant::now() >= resume_time {
self.call_event_handler(Event::NewEvents(StartCause::ResumeTimeReached {
start: wait_start,
requested_resume: resume_time
}));
self.call_event_handler(Event::EventsCleared);
}
},
// If we deferred a wait and no events were received, the user doesn't have to
// get an event.
ControlFlow::Wait |
ControlFlow::Exit => ()
}
// Mark that we've entered an idle state.
self.runner_state = RunnerState::Idle(wait_start)
},
}
}
unsafe fn call_event_handler(&mut self, event: Event<T>) {
if self.event_handler != mem::zeroed() {
if self.control_flow != ControlFlow::Exit {
(*self.event_handler)(event, &self.event_loop, &mut self.control_flow);
} else {
(*self.event_handler)(event, &self.event_loop, &mut ControlFlow::Exit);
}
} else {
panic!("Tried to call event handler with null handler");
}
}
fn event_processing_active(&self) -> bool {
match self.runner_state {
RunnerState::HandlingEvents => true,
RunnerState::DeferredNewEvents(..) |
RunnerState::New |
RunnerState::Idle(..) => false
}
}
}
// Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs
pub fn dur2timeout(dur: Duration) -> DWORD {
fn dur2timeout(dur: Duration) -> DWORD {
// Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the
// timeouts in windows APIs are typically u32 milliseconds. To translate, we
// have two pieces to take care of:
@@ -333,6 +482,7 @@ pub fn dur2timeout(dur: Duration) -> DWORD {
impl<T> Drop for EventLoop<T> {
fn drop(&mut self) {
unsafe {
winuser::DestroyWindow(self.thread_msg_target);
// Posting `WM_QUIT` will cause `GetMessage` to stop.
winuser::PostThreadMessageA(self.thread_id, winuser::WM_QUIT, 0, 0);
}
@@ -388,24 +538,18 @@ impl EventLoopThreadExecutor {
#[derive(Clone)]
pub struct EventLoopProxy<T> {
thread_id: DWORD,
target_window: HWND,
event_send: Sender<T>
}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
unsafe {
if winuser::PostThreadMessageA(self.thread_id, *WAKEUP_MSG_ID, 0, 0) != 0 {
if winuser::PostMessageW(self.target_window, *USER_EVENT_MSG_ID, 0, 0) != 0 {
self.event_send.send(event).ok();
Ok(())
} else {
// https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms644946(v=vs.85).aspx
// > If the function fails, the return value is zero. To get extended error
// > information, call GetLastError. GetLastError returns ERROR_INVALID_THREAD_ID
// > if idThread is not a valid thread identifier, or if the thread specified by
// > idThread does not have a message queue. GetLastError returns
// > ERROR_NOT_ENOUGH_QUOTA when the message limit is hit.
// TODO: handle ERROR_NOT_ENOUGH_QUOTA
Err(EventLoopClosed)
}
}
@@ -415,7 +559,7 @@ impl<T> EventLoopProxy<T> {
lazy_static! {
// Message sent by the `EventLoopProxy` when we want to wake up the thread.
// WPARAM and LPARAM are unused.
static ref WAKEUP_MSG_ID: u32 = {
static ref USER_EVENT_MSG_ID: u32 = {
unsafe {
winuser::RegisterWindowMessageA("Winit::WakeupMsg\0".as_ptr() as LPCSTR)
}
@@ -442,6 +586,68 @@ lazy_static! {
winuser::RegisterWindowMessageA("Winit::InitialDpiMsg\0".as_ptr() as LPCSTR)
}
};
static ref THREAD_EVENT_TARGET_WINDOW_CLASS: Vec<u16> = unsafe {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
let class_name: Vec<_> = OsStr::new("Winit Thread Event Target")
.encode_wide()
.chain(Some(0).into_iter())
.collect();
let class = winuser::WNDCLASSEXW {
cbSize: mem::size_of::<winuser::WNDCLASSEXW>() as UINT,
style: 0,
lpfnWndProc: Some(winuser::DefWindowProcW),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: libloaderapi::GetModuleHandleW(ptr::null()),
hIcon: ptr::null_mut(),
hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly
hbrBackground: ptr::null_mut(),
lpszMenuName: ptr::null(),
lpszClassName: class_name.as_ptr(),
hIconSm: ptr::null_mut(),
};
winuser::RegisterClassExW(&class);
class_name
};
}
fn thread_event_target_window<T>(event_loop_runner: EventLoopRunnerShared<T>) -> (HWND, Sender<T>) {
unsafe {
let window = winuser::CreateWindowExW(
0,
THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(),
ptr::null_mut(),
0,
0, 0,
0, 0,
ptr::null_mut(),
ptr::null_mut(),
libloaderapi::GetModuleHandleW(ptr::null()),
ptr::null_mut()
);
let (tx, rx) = crossbeam_channel::unbounded();
let subclass_input = ThreadMsgTargetSubclassInput {
event_loop_runner,
user_event_receiver: rx
};
let input_ptr = Box::into_raw(Box::new(subclass_input));
let subclass_result = commctrl::SetWindowSubclass(
window,
Some(thread_event_target_callback::<T>),
THREAD_EVENT_TARGET_SUBCLASS_ID,
input_ptr as DWORD_PTR
);
assert_eq!(subclass_result, 1);
(window, tx)
}
}
/// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of
@@ -461,11 +667,12 @@ unsafe fn release_mouse(window_state: &mut WindowState) {
}
const WINDOW_SUBCLASS_ID: UINT_PTR = 0;
pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) {
const THREAD_EVENT_TARGET_SUBCLASS_ID: UINT_PTR = 1;
pub(crate) fn subclass_window<T>(window: HWND, subclass_input: SubclassInput<T>) {
let input_ptr = Box::into_raw(Box::new(subclass_input));
let subclass_result = unsafe{ commctrl::SetWindowSubclass(
window,
Some(callback),
Some(public_window_callback::<T>),
WINDOW_SUBCLASS_ID,
input_ptr as DWORD_PTR
) };
@@ -479,7 +686,7 @@ pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) {
//
// Returning 0 tells the Win32 API that the message has been processed.
// FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary
pub unsafe extern "system" fn callback(
unsafe extern "system" fn public_window_callback<T>(
window: HWND,
msg: UINT,
wparam: WPARAM,
@@ -487,9 +694,70 @@ pub unsafe extern "system" fn callback(
_: UINT_PTR,
subclass_input_ptr: DWORD_PTR
) -> LRESULT {
let subclass_input = &mut*(subclass_input_ptr as *mut SubclassInput);
let subclass_input = &mut*(subclass_input_ptr as *mut SubclassInput<T>);
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 Some(runner) = *subclass_input.event_loop_runner.borrow_mut() {
(*runner).modal_loop_data = Some(ModalLoopData {
hwnd: window,
timer_handle: modal_timer_handle
});
}
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 Some(runner) = *subclass_input.event_loop_runner.borrow_mut() {
(*runner).modal_loop_data = None;
}
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 Some(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)
@@ -507,6 +775,12 @@ pub unsafe extern "system" fn callback(
winuser::WM_DESTROY => {
use events::WindowEvent::Destroyed;
ole2::RevokeDragDrop(window);
{
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: SuperWindowId(WindowId(window)),
event: Destroyed
@@ -1169,3 +1443,33 @@ pub unsafe extern "system" fn callback(
}
}
}
unsafe extern "system" fn thread_event_target_callback<T>(
window: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
_: UINT_PTR,
subclass_input_ptr: DWORD_PTR
) -> LRESULT {
let subclass_input = &mut*(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput<T>);
match msg {
winuser::WM_DESTROY => {
Box::from_raw(subclass_input);
drop(subclass_input);
0
},
_ if msg == *USER_EVENT_MSG_ID => {
if let Ok(event) = subclass_input.user_event_receiver.recv() {
subclass_input.send_event(Event::UserEvent(event));
}
0
}
_ if msg == *EXEC_MSG_ID => {
let mut function: Box<Box<FnMut()>> = Box::from_raw(wparam as usize as *mut _);
function();
0
}
_ => commctrl::DefSubclassProc(window, msg, wparam, lparam)
}
}

View File

@@ -75,7 +75,7 @@ unsafe fn unjust_window_rect(prc: &mut RECT, style: DWORD, ex_style: DWORD) -> B
impl Window {
pub fn new<T>(
events_loop: &EventLoop<T>,
event_loop: &EventLoop<T>,
w_attr: WindowAttributes,
pl_attr: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
@@ -83,7 +83,37 @@ impl Window {
// First person to remove the need for cloning here gets a cookie!
//
// done. you owe me -- ossi
unsafe { init(w_attr, pl_attr, events_loop) }
unsafe {
init(w_attr, pl_attr, event_loop).map(|win| {
let file_drop_handler = {
use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE, S_OK};
let ole_init_result = ole2::OleInitialize(ptr::null_mut());
// It is ok if the initialize result is `S_FALSE` because it might happen that
// multiple windows are created on the same thread.
if ole_init_result == OLE_E_WRONGCOMPOBJ {
panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`");
} else if ole_init_result == RPC_E_CHANGED_MODE {
panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`");
}
let file_drop_handler = FileDropHandler::new(win.window.0/*, event_loop.event_send.clone()*/);
let handler_interface_ptr = &mut (*file_drop_handler.data).interface as LPDROPTARGET;
assert_eq!(ole2::RegisterDragDrop(win.window.0, handler_interface_ptr), S_OK);
file_drop_handler
};
let subclass_input = events_loop::SubclassInput {
window_state: win.window_state.clone(),
event_loop_runner: event_loop.runner_shared.clone(),
file_drop_handler,
};
events_loop::subclass_window(win.window.0, subclass_input);
win
})
}
}
pub fn set_title(&self, text: &str) {
@@ -1004,22 +1034,24 @@ unsafe fn init<T>(
let min_size = attributes.min_dimensions
.map(|logical_size| PhysicalSize::from_logical(logical_size, dpi_factor));
let mut window_state = events_loop::WindowState {
cursor: Cursor(winuser::IDC_ARROW), // use arrow by default
cursor_grabbed: false,
cursor_hidden: false,
max_size,
min_size,
mouse_in_window: false,
saved_window_info: None,
dpi_factor,
fullscreen: attributes.fullscreen.clone(),
window_icon,
taskbar_icon,
fullscreen: attributes.fullscreen.clone(),
decorations: attributes.decorations,
maximized: attributes.maximized,
resizable: attributes.resizable,
always_on_top: attributes.always_on_top,
mouse_buttons_down: 0
cursor: Cursor(winuser::IDC_ARROW), // use arrow by default
cursor_grabbed: false,
cursor_hidden: false,
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))
@@ -1064,33 +1096,6 @@ unsafe fn init<T>(
force_window_active(win.window.0);
}
let file_drop_handler = {
use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE, S_OK};
let ole_init_result = ole2::OleInitialize(ptr::null_mut());
// It is ok if the initialize result is `S_FALSE` because it might happen that
// multiple windows are created on the same thread.
if ole_init_result == OLE_E_WRONGCOMPOBJ {
panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`");
} else if ole_init_result == RPC_E_CHANGED_MODE {
panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`");
}
let file_drop_handler = FileDropHandler::new(win.window.0, event_loop.event_send.clone());
let handler_interface_ptr = &mut (*file_drop_handler.data).interface as LPDROPTARGET;
assert_eq!(ole2::RegisterDragDrop(win.window.0, handler_interface_ptr), S_OK);
file_drop_handler
};
let subclass_input = events_loop::SubclassInput {
window_state: win.window_state.clone(),
event_send: event_loop.event_send.clone(),
file_drop_handler,
};
events_loop::subclass_window(win.window.0, subclass_input);
Ok(win)
}