From 61ba6dce7fa369506b785f1ce037378784a30a7c Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Mon, 26 Jun 2017 21:21:13 +0200 Subject: [PATCH] Switch win32 implementation to new design --- src/os/windows.rs | 2 +- src/platform/windows/callback.rs | 426 ----------------- src/platform/windows/events_loop.rs | 700 ++++++++++++++++++++++++++++ src/platform/windows/init.rs | 259 ---------- src/platform/windows/mod.rs | 387 +-------------- src/platform/windows/window.rs | 483 +++++++++++++++++++ 6 files changed, 1203 insertions(+), 1054 deletions(-) delete mode 100644 src/platform/windows/callback.rs create mode 100644 src/platform/windows/events_loop.rs delete mode 100644 src/platform/windows/init.rs create mode 100644 src/platform/windows/window.rs diff --git a/src/os/windows.rs b/src/os/windows.rs index 6d8b7981..9b52c2de 100644 --- a/src/os/windows.rs +++ b/src/os/windows.rs @@ -18,7 +18,7 @@ pub trait WindowExt { impl WindowExt for Window { #[inline] fn get_hwnd(&self) -> *mut libc::c_void { - self.window.platform_window() + self.window.hwnd() as *mut _ } } diff --git a/src/platform/windows/callback.rs b/src/platform/windows/callback.rs deleted file mode 100644 index c11aac13..00000000 --- a/src/platform/windows/callback.rs +++ /dev/null @@ -1,426 +0,0 @@ -use std::mem; -use std::ptr; -use std::cell::RefCell; -use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; -use std::ffi::OsString; -use std::os::windows::ffi::OsStringExt; - -use CursorState; -use WindowEvent as Event; -use KeyboardInput; -use events::ModifiersState; -use super::event; -use super::WindowState; - -use user32; -use shell32; -use winapi; - -/// There's no parameters passed to the callback function, so it needs to get -/// its context (the HWND, the Sender for events, etc.) stashed in -/// a thread-local variable. -thread_local!(pub static CONTEXT_STASH: RefCell> = RefCell::new(None)); - -pub struct ThreadLocalData { - pub win: winapi::HWND, - pub sender: Sender, - pub window_state: Arc>, - pub mouse_in_window: bool -} - -/// Equivalent to the windows api [MINMAXINFO](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632605%28v=vs.85%29.aspx) -/// struct. Used because winapi-rs doesn't have this declared. -#[repr(C)] -#[allow(dead_code)] -struct MinMaxInfo { - reserved: winapi::POINT, // Do not use/change - max_size: winapi::POINT, - max_position: winapi::POINT, - min_track: winapi::POINT, - max_track: winapi::POINT -} - -/// Checks that the window is the good one, and if so send the event to it. -fn send_event(input_window: winapi::HWND, event: Event) { - CONTEXT_STASH.with(|context_stash| { - let context_stash = context_stash.borrow(); - let stored = match *context_stash { - None => return, - Some(ref v) => v - }; - - let &ThreadLocalData { ref win, ref sender, .. } = stored; - - if win != &input_window { - return; - } - - sender.send(event).ok(); // ignoring if closed - }); -} - -/// This is the callback that is called by `DispatchMessage` in the events loop. -/// -/// 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(window: winapi::HWND, msg: winapi::UINT, - wparam: winapi::WPARAM, lparam: winapi::LPARAM) - -> winapi::LRESULT -{ - match msg { - winapi::WM_DESTROY => { - use events::WindowEvent::Closed; - - CONTEXT_STASH.with(|context_stash| { - let context_stash = context_stash.borrow(); - let stored = match *context_stash { - None => return, - Some(ref v) => v - }; - - let &ThreadLocalData { ref win, .. } = stored; - - if win == &window { - user32::PostQuitMessage(0); - } - }); - - send_event(window, Closed); - 0 - }, - - winapi::WM_ERASEBKGND => { - 1 - }, - - winapi::WM_SIZE => { - use events::WindowEvent::Resized; - let w = winapi::LOWORD(lparam as winapi::DWORD) as u32; - let h = winapi::HIWORD(lparam as winapi::DWORD) as u32; - send_event(window, Resized(w, h)); - 0 - }, - - winapi::WM_MOVE => { - use events::WindowEvent::Moved; - let x = winapi::LOWORD(lparam as winapi::DWORD) as i32; - let y = winapi::HIWORD(lparam as winapi::DWORD) as i32; - send_event(window, Moved(x, y)); - 0 - }, - - winapi::WM_CHAR => { - use std::mem; - use events::WindowEvent::ReceivedCharacter; - let chr: char = mem::transmute(wparam as u32); - send_event(window, ReceivedCharacter(chr)); - 0 - }, - - // Prevents default windows menu hotkeys playing unwanted - // "ding" sounds. Alternatively could check for WM_SYSCOMMAND - // with wparam being SC_KEYMENU, but this may prevent some - // other unwanted default hotkeys as well. - winapi::WM_SYSCHAR => { - 0 - } - - winapi::WM_MOUSEMOVE => { - use events::WindowEvent::{MouseEntered, MouseMoved}; - let mouse_outside_window = CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - if !context_stash.mouse_in_window { - context_stash.mouse_in_window = true; - return true; - } - } - - false - }); - - if mouse_outside_window { - send_event(window, MouseEntered { device_id: DEVICE_ID }); - - // Calling TrackMouseEvent in order to receive mouse leave events. - user32::TrackMouseEvent(&mut winapi::TRACKMOUSEEVENT { - cbSize: mem::size_of::() as winapi::DWORD, - dwFlags: winapi::TME_LEAVE, - hwndTrack: window, - dwHoverTime: winapi::HOVER_DEFAULT, - }); - } - - let x = winapi::GET_X_LPARAM(lparam) as f64; - let y = winapi::GET_Y_LPARAM(lparam) as f64; - - send_event(window, MouseMoved { device_id: DEVICE_ID, position: (x, y) }); - - 0 - }, - - winapi::WM_MOUSELEAVE => { - use events::WindowEvent::MouseLeft; - let mouse_in_window = CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - if context_stash.mouse_in_window { - context_stash.mouse_in_window = false; - return true; - } - } - - false - }); - - if mouse_in_window { - send_event(window, MouseLeft { device_id: DEVICE_ID }); - } - - 0 - }, - - winapi::WM_MOUSEWHEEL => { - use events::WindowEvent::MouseWheel; - use events::MouseScrollDelta::LineDelta; - use events::TouchPhase; - - let value = (wparam >> 16) as i16; - let value = value as i32; - let value = value as f32 / winapi::WHEEL_DELTA as f32; - - send_event(window, MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved }); - - 0 - }, - - winapi::WM_KEYDOWN | winapi::WM_SYSKEYDOWN => { - use events::ElementState::Pressed; - if msg == winapi::WM_SYSKEYDOWN && wparam as i32 == winapi::VK_F4 { - user32::DefWindowProcW(window, msg, wparam, lparam) - } else { - let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam); - send_event(window, Event::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Pressed, - scancode: scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - } - }); - 0 - } - }, - - winapi::WM_KEYUP | winapi::WM_SYSKEYUP => { - use events::ElementState::Released; - let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam); - send_event(window, Event::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Released, - scancode: scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - }); - 0 - }, - - winapi::WM_LBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Left; - use events::ElementState::Pressed; - send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left }); - 0 - }, - - winapi::WM_LBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Left; - use events::ElementState::Released; - send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Left }); - 0 - }, - - winapi::WM_RBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Right; - use events::ElementState::Pressed; - send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right }); - 0 - }, - - winapi::WM_RBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Right; - use events::ElementState::Released; - send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Right }); - 0 - }, - - winapi::WM_MBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Middle; - use events::ElementState::Pressed; - send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle }); - 0 - }, - - winapi::WM_MBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Middle; - use events::ElementState::Released; - send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Middle }); - 0 - }, - - winapi::WM_XBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Other; - use events::ElementState::Pressed; - let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; // waiting on PR for winapi to add GET_XBUTTON_WPARAM - send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Other(xbutton as u8) }); - 0 - }, - - winapi::WM_XBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Other; - use events::ElementState::Released; - let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; - send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8) }); - 0 - }, - - winapi::WM_INPUT => { - let mut data: winapi::RAWINPUT = mem::uninitialized(); - let mut data_size = mem::size_of::() as winapi::UINT; - user32::GetRawInputData(mem::transmute(lparam), winapi::RID_INPUT, - mem::transmute(&mut data), &mut data_size, - mem::size_of::() as winapi::UINT); - - if data.header.dwType == winapi::RIM_TYPEMOUSE { - let _x = data.mouse.lLastX; // FIXME: this is not always the relative movement - let _y = data.mouse.lLastY; - // TODO: - //send_event(window, Event::MouseRawMovement { x: x, y: y }); - - 0 - - } else { - user32::DefWindowProcW(window, msg, wparam, lparam) - } - }, - - winapi::WM_SETFOCUS => { - use events::WindowEvent::Focused; - send_event(window, Focused(true)); - 0 - }, - - winapi::WM_KILLFOCUS => { - use events::WindowEvent::Focused; - send_event(window, Focused(false)); - 0 - }, - - winapi::WM_SETCURSOR => { - let call_def_window_proc = CONTEXT_STASH.with(|context_stash| { - let cstash = context_stash.borrow(); - let mut call_def_window_proc = false; - if let Some(cstash) = cstash.as_ref() { - if let Ok(window_state) = cstash.window_state.lock() { - if cstash.mouse_in_window { - match window_state.cursor_state { - CursorState::Normal => { - user32::SetCursor(user32::LoadCursorW( - ptr::null_mut(), - window_state.cursor)); - }, - CursorState::Grab | CursorState::Hide => { - user32::SetCursor(ptr::null_mut()); - } - } - } else { - call_def_window_proc = true; - } - } - } - - call_def_window_proc - }); - - if call_def_window_proc { - user32::DefWindowProcW(window, msg, wparam, lparam) - } else { - 0 - } - }, - - winapi::WM_DROPFILES => { - use events::WindowEvent::DroppedFile; - - let hdrop = wparam as winapi::HDROP; - let mut pathbuf: [u16; winapi::MAX_PATH] = mem::uninitialized(); - let num_drops = shell32::DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0); - - for i in 0..num_drops { - let nch = shell32::DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), - winapi::MAX_PATH as u32) as usize; - if nch > 0 { - send_event(window, DroppedFile(OsString::from_wide(&pathbuf[0..nch]).into())); - } - } - - shell32::DragFinish(hdrop); - 0 - }, - - winapi::WM_GETMINMAXINFO => { - let mmi = lparam as *mut MinMaxInfo; - //(*mmi).max_position = winapi::POINT { x: -8, y: -8 }; // The upper left corner of the window if it were maximized on the primary monitor. - //(*mmi).max_size = winapi::POINT { x: .., y: .. }; // The dimensions of the primary monitor. - - CONTEXT_STASH.with(|context_stash| { - match context_stash.borrow().as_ref() { - Some(cstash) => { - let window_state = cstash.window_state.lock().unwrap(); - - match window_state.attributes.min_dimensions { - Some((width, height)) => { - (*mmi).min_track = winapi::POINT { x: width as i32, y: height as i32 }; - }, - None => { } - } - - match window_state.attributes.max_dimensions { - Some((width, height)) => { - (*mmi).max_track = winapi::POINT { x: width as i32, y: height as i32 }; - }, - None => { } - } - }, - None => { } - } - }); - 0 - }, - - x if x == *super::WAKEUP_MSG_ID => { - // TODO: `Awakened` has been moved from the `WindowEvent` enum to the `Event` enum. - // This code needs to be updated to reflect this change. - //send_event(window, ::Event::Awakened); - 0 - }, - - _ => { - user32::DefWindowProcW(window, msg, wparam, lparam) - } - } -} - -// Constant device ID, to be removed when this backend is updated to report real device IDs. -const DEVICE_ID: ::DeviceId = ::DeviceId(super::DeviceId); diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs new file mode 100644 index 00000000..beb756d5 --- /dev/null +++ b/src/platform/windows/events_loop.rs @@ -0,0 +1,700 @@ +//! An events loop on Win32 is a background thread. +//! +//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop. +//! Destroying the events loop stops the thread. +//! +//! You can use the `execute_in_thread` method to execute some code in the background thread. +//! Since Win32 requires you to create a window in the right thread, you must use this method +//! to create a window. +//! +//! If you create a window whose class is set to `callback`, the window's events will be +//! propagated with `run_forever` and `poll_events`. +//! 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 std::cell::RefCell; +use std::collections::HashMap; +use std::ffi::OsString; +use std::mem; +use std::os::windows::ffi::OsStringExt; +use std::os::windows::io::AsRawHandle; +use std::ptr; +use std::sync::mpsc; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; + +use kernel32; +use shell32; +use user32; +use winapi; + +use platform::platform::event; +use platform::platform::Cursor; +use platform::platform::WindowId; +use platform::platform::DEVICE_ID; + +use ControlFlow; +use CursorState; +use Event; +use EventsLoopClosed; +use KeyboardInput; +use WindowAttributes; +use WindowEvent; +use WindowId as SuperWindowId; + +/// Contains information about states and the window that the callback is going to use. +#[derive(Clone)] +pub struct WindowState { + /// Cursor to set at the next `WM_SETCURSOR` event received. + pub cursor: Cursor, + /// Cursor state to set at the next `WM_SETCURSOR` event received. + pub cursor_state: CursorState, + /// Used by `WM_GETMINMAXINFO`. + pub attributes: WindowAttributes, + /// Will contain `true` if the mouse is hovering the window. + pub mouse_in_window: bool, +} + +/// Dummy object that allows inserting a window's state. +// We store a pointer in order to !impl Send and Sync. +pub struct Inserter(*mut u8); + +impl Inserter { + /// Inserts a window's state for the callback to use. The state is removed automatically if the + /// callback receives a `WM_CLOSE` message for the window. + pub fn insert(&self, window: winapi::HWND, state: Arc>) { + CONTEXT_STASH.with(|context_stash| { + let mut context_stash = context_stash.borrow_mut(); + let was_in = context_stash.as_mut().unwrap().windows.insert(window, state); + assert!(was_in.is_none()); + }); + } +} + +pub struct EventsLoop { + // Id of the background thread from the Win32 API. + thread_id: winapi::DWORD, + // Receiver for the events. The sender is in the background thread. + receiver: mpsc::Receiver, +} + +impl EventsLoop { + pub fn new() -> EventsLoop { + // The main events transfer channel. + let (tx, rx) = mpsc::channel(); + + // Local channel in order to block the `new()` function until the background thread has + // an events queue. + let (local_block_tx, local_block_rx) = mpsc::channel(); + + let thread = thread::spawn(move || { + CONTEXT_STASH.with(|context_stash| { + *context_stash.borrow_mut() = Some(ThreadLocalData { + sender: tx, + windows: HashMap::with_capacity(4), + }); + }); + + unsafe { + let mut msg = mem::uninitialized(); + + // Calling `PostThreadMessageA` on a thread that does not have an events queue yet + // will fail. In order to avoid this situation, we call `PeekMessage` to initialize + // it. + user32::PeekMessageA(&mut msg, ptr::null_mut(), 0, 0, 0); + // Then only we unblock the `new()` function. We are sure that we don't call + // `PostThreadMessageA()` before `new()` returns. + local_block_tx.send(()).unwrap(); + + loop { + if user32::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 { + // Only happens if the message is `WM_QUIT`. + debug_assert_eq!(msg.message, winapi::WM_QUIT); + break; + } + + match msg.message { + x if x == *EXEC_MSG_ID => { + let mut function: Box> = Box::from_raw(msg.wParam as usize as *mut _); + function(Inserter(ptr::null_mut())); + }, + x if x == *WAKEUP_MSG_ID => { + send_event(Event::Awakened); + }, + _ => { + // Calls `callback` below. + user32::TranslateMessage(&msg); + user32::DispatchMessageW(&msg); + } + } + } + } + }); + + // Blocks this function until the background thread has an events loop. See other comments. + local_block_rx.recv().unwrap(); + + EventsLoop { + thread_id: unsafe { kernel32::GetThreadId(thread.as_raw_handle()) }, + receiver: rx, + } + } + + pub fn poll_events(&mut self, mut callback: F) + where F: FnMut(Event) + { + loop { + let event = match self.receiver.try_recv() { + Ok(e) => e, + Err(_) => return + }; + + callback(event); + } + } + + pub fn run_forever(&mut self, mut callback: F) + where F: FnMut(Event) -> ControlFlow + { + loop { + let event = match self.receiver.recv() { + Ok(e) => e, + Err(_) => return + }; + + let flow = callback(event); + match flow { + ControlFlow::Continue => continue, + ControlFlow::Break => break, + } + } + } + + pub fn create_proxy(&self) -> EventsLoopProxy { + EventsLoopProxy { + thread_id: self.thread_id, + } + } + + /// Executes a function in the background thread. + /// + /// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent + /// to the unstable FnBox. + /// + /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is + /// removed automatically if the callback receives a `WM_CLOSE` message for the window. + pub(super) fn execute_in_thread(&self, function: F) + where F: FnMut(Inserter) + Send + 'static + { + unsafe { + let boxed = Box::new(function) as Box; + let boxed2 = Box::new(boxed); + + let raw = Box::into_raw(boxed2); + + let res = user32::PostThreadMessageA(self.thread_id, *EXEC_MSG_ID, + raw as *mut () as usize as winapi::WPARAM, 0); + // PostThreadMessage can only fail if the thread ID is invalid (which shouldn't happen + // as the events loop is still alive) or if the queue is full. + assert!(res != 0, "PostThreadMessage failed ; is the messages queue full?"); + } + } +} + +impl Drop for EventsLoop { + fn drop(&mut self) { + unsafe { + // Posting `WM_QUIT` will cause `GetMessage` to stop. + user32::PostThreadMessageA(self.thread_id, winapi::WM_QUIT, 0, 0); + } + } +} + +pub struct EventsLoopProxy { + thread_id: winapi::DWORD, +} + +impl EventsLoopProxy { + pub fn wakeup(&self) -> Result<(), EventsLoopClosed> { + unsafe { + if user32::PostThreadMessageA(self.thread_id, *WAKEUP_MSG_ID, 0, 0) != 0 { + 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(EventsLoopClosed) + } + } + } +} + +lazy_static! { + // Message sent by the `EventsLoopProxy` when we want to wake up the thread. + // WPARAM and LPARAM are unused. + static ref WAKEUP_MSG_ID: u32 = { + unsafe { + user32::RegisterWindowMessageA("Winit::WakeupMsg".as_ptr() as *const i8) + } + }; + // Message sent when we want to execute a closure in the thread. + // WPARAM contains a Box> that must be retreived with `Box::from_raw`, + // and LPARAM is unused. + static ref EXEC_MSG_ID: u32 = { + unsafe { + user32::RegisterWindowMessageA("Winit::ExecMsg".as_ptr() as *const i8) + } + }; +} + +// There's no parameters passed to the callback function, so it needs to get its context stashed +// in a thread-local variable. +thread_local!(static CONTEXT_STASH: RefCell> = RefCell::new(None)); +struct ThreadLocalData { + sender: mpsc::Sender, + windows: HashMap>>, +} + +// Utility function that dispatches an event on the current thread. +fn send_event(event: Event) { + CONTEXT_STASH.with(|context_stash| { + let context_stash = context_stash.borrow(); + let _ = context_stash.as_ref().unwrap().sender.send(event); // Ignoring if closed + }); +} + +/// Any window whose callback is configured to this function will have its events propagated +/// through the events loop of the thread the window was created in. +// +// This is the callback that is called by `DispatchMessage` in the events loop. +// +// 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(window: winapi::HWND, msg: winapi::UINT, + wparam: winapi::WPARAM, lparam: winapi::LPARAM) + -> winapi::LRESULT +{ + match msg { + winapi::WM_DESTROY => { + use events::WindowEvent::Closed; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: Closed + }); + CONTEXT_STASH.with(|context_stash| { + let mut context_stash = context_stash.borrow_mut(); + context_stash.as_mut().unwrap().windows.remove(&window); + }); + 0 + }, + + winapi::WM_ERASEBKGND => { + 1 + }, + + winapi::WM_SIZE => { + use events::WindowEvent::Resized; + let w = winapi::LOWORD(lparam as winapi::DWORD) as u32; + let h = winapi::HIWORD(lparam as winapi::DWORD) as u32; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: Resized(w, h), + }); + 0 + }, + + winapi::WM_MOVE => { + use events::WindowEvent::Moved; + let x = winapi::LOWORD(lparam as winapi::DWORD) as i32; + let y = winapi::HIWORD(lparam as winapi::DWORD) as i32; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: Moved(x, y), + }); + 0 + }, + + winapi::WM_CHAR => { + use std::mem; + use events::WindowEvent::ReceivedCharacter; + let chr: char = mem::transmute(wparam as u32); + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: ReceivedCharacter(chr), + }); + 0 + }, + + // Prevents default windows menu hotkeys playing unwanted + // "ding" sounds. Alternatively could check for WM_SYSCOMMAND + // with wparam being SC_KEYMENU, but this may prevent some + // other unwanted default hotkeys as well. + winapi::WM_SYSCHAR => { + 0 + } + + winapi::WM_MOUSEMOVE => { + use events::WindowEvent::{MouseEntered, MouseMoved}; + let mouse_outside_window = CONTEXT_STASH.with(|context_stash| { + let mut context_stash = context_stash.borrow_mut(); + if let Some(context_stash) = context_stash.as_mut() { + if let Some(w) = context_stash.windows.get_mut(&window) { + let mut w = w.lock().unwrap(); + if !w.mouse_in_window { + w.mouse_in_window = true; + return true; + } + } + } + + false + }); + + if mouse_outside_window { + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseEntered { device_id: DEVICE_ID }, + }); + + // Calling TrackMouseEvent in order to receive mouse leave events. + user32::TrackMouseEvent(&mut winapi::TRACKMOUSEEVENT { + cbSize: mem::size_of::() as winapi::DWORD, + dwFlags: winapi::TME_LEAVE, + hwndTrack: window, + dwHoverTime: winapi::HOVER_DEFAULT, + }); + } + + let x = winapi::GET_X_LPARAM(lparam) as f64; + let y = winapi::GET_Y_LPARAM(lparam) as f64; + + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseMoved { device_id: DEVICE_ID, position: (x, y) }, + }); + + 0 + }, + + winapi::WM_MOUSELEAVE => { + use events::WindowEvent::MouseLeft; + let mouse_in_window = CONTEXT_STASH.with(|context_stash| { + let mut context_stash = context_stash.borrow_mut(); + if let Some(context_stash) = context_stash.as_mut() { + if let Some(w) = context_stash.windows.get_mut(&window) { + let mut w = w.lock().unwrap(); + if w.mouse_in_window { + w.mouse_in_window = false; + return true; + } + } + } + + false + }); + + if mouse_in_window { + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseLeft { device_id: DEVICE_ID } + }); + } + + 0 + }, + + winapi::WM_MOUSEWHEEL => { + use events::WindowEvent::MouseWheel; + use events::MouseScrollDelta::LineDelta; + use events::TouchPhase; + + let value = (wparam >> 16) as i16; + let value = value as i32; + let value = value as f32 / winapi::WHEEL_DELTA as f32; + + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved }, + }); + + 0 + }, + + winapi::WM_KEYDOWN | winapi::WM_SYSKEYDOWN => { + use events::ElementState::Pressed; + if msg == winapi::WM_SYSKEYDOWN && wparam as i32 == winapi::VK_F4 { + user32::DefWindowProcW(window, msg, wparam, lparam) + } else { + let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam); + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: Pressed, + scancode: scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + } + } + }); + 0 + } + }, + + winapi::WM_KEYUP | winapi::WM_SYSKEYUP => { + use events::ElementState::Released; + let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam); + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: Released, + scancode: scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + }, + } + }); + 0 + }, + + winapi::WM_LBUTTONDOWN => { + use events::WindowEvent::MouseInput; + use events::MouseButton::Left; + use events::ElementState::Pressed; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left } + }); + 0 + }, + + winapi::WM_LBUTTONUP => { + use events::WindowEvent::MouseInput; + use events::MouseButton::Left; + use events::ElementState::Released; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseInput { device_id: DEVICE_ID, state: Released, button: Left } + }); + 0 + }, + + winapi::WM_RBUTTONDOWN => { + use events::WindowEvent::MouseInput; + use events::MouseButton::Right; + use events::ElementState::Pressed; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right } + }); + 0 + }, + + winapi::WM_RBUTTONUP => { + use events::WindowEvent::MouseInput; + use events::MouseButton::Right; + use events::ElementState::Released; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseInput { device_id: DEVICE_ID, state: Released, button: Right } + }); + 0 + }, + + winapi::WM_MBUTTONDOWN => { + use events::WindowEvent::MouseInput; + use events::MouseButton::Middle; + use events::ElementState::Pressed; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle } + }); + 0 + }, + + winapi::WM_MBUTTONUP => { + use events::WindowEvent::MouseInput; + use events::MouseButton::Middle; + use events::ElementState::Released; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseInput { device_id: DEVICE_ID, state: Released, button: Middle } + }); + 0 + }, + + winapi::WM_XBUTTONDOWN => { + use events::WindowEvent::MouseInput; + use events::MouseButton::Other; + use events::ElementState::Pressed; + let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; // waiting on PR for winapi to add GET_XBUTTON_WPARAM + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Other(xbutton as u8) } + }); + 0 + }, + + winapi::WM_XBUTTONUP => { + use events::WindowEvent::MouseInput; + use events::MouseButton::Other; + use events::ElementState::Released; + let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8) } + }); + 0 + }, + + winapi::WM_INPUT => { + let mut data: winapi::RAWINPUT = mem::uninitialized(); + let mut data_size = mem::size_of::() as winapi::UINT; + user32::GetRawInputData(mem::transmute(lparam), winapi::RID_INPUT, + mem::transmute(&mut data), &mut data_size, + mem::size_of::() as winapi::UINT); + + if data.header.dwType == winapi::RIM_TYPEMOUSE { + let _x = data.mouse.lLastX; // FIXME: this is not always the relative movement + let _y = data.mouse.lLastY; + // TODO: + //send_event(window, Event::MouseRawMovement { x: x, y: y }); + + 0 + + } else { + user32::DefWindowProcW(window, msg, wparam, lparam) + } + }, + + winapi::WM_SETFOCUS => { + use events::WindowEvent::Focused; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: Focused(true) + }); + 0 + }, + + winapi::WM_KILLFOCUS => { + use events::WindowEvent::Focused; + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: Focused(false) + }); + 0 + }, + + winapi::WM_SETCURSOR => { + let call_def_window_proc = CONTEXT_STASH.with(|context_stash| { + let cstash = context_stash.borrow(); + let mut call_def_window_proc = false; + if let Some(cstash) = cstash.as_ref() { + if let Some(w_stash) = cstash.windows.get(&window) { + if let Ok(window_state) = w_stash.lock() { + if window_state.mouse_in_window { + match window_state.cursor_state { + CursorState::Normal => { + user32::SetCursor(user32::LoadCursorW( + ptr::null_mut(), + window_state.cursor)); + }, + CursorState::Grab | CursorState::Hide => { + user32::SetCursor(ptr::null_mut()); + } + } + } else { + call_def_window_proc = true; + } + } + } + } + + call_def_window_proc + }); + + if call_def_window_proc { + user32::DefWindowProcW(window, msg, wparam, lparam) + } else { + 0 + } + }, + + winapi::WM_DROPFILES => { + use events::WindowEvent::DroppedFile; + + let hdrop = wparam as winapi::HDROP; + let mut pathbuf: [u16; winapi::MAX_PATH] = mem::uninitialized(); + let num_drops = shell32::DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0); + + for i in 0..num_drops { + let nch = shell32::DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), + winapi::MAX_PATH as u32) as usize; + if nch > 0 { + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: DroppedFile(OsString::from_wide(&pathbuf[0..nch]).into()) + }); + } + } + + shell32::DragFinish(hdrop); + 0 + }, + + winapi::WM_GETMINMAXINFO => { + // Equivalent to the windows api [MINMAXINFO](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632605%28v=vs.85%29.aspx) + // struct. Used because winapi-rs doesn't have this declared. + // TODO: replace with struct from winapi-rs + #[repr(C)] + #[allow(dead_code)] + struct MinMaxInfo { + reserved: winapi::POINT, // Do not use/change + max_size: winapi::POINT, + max_position: winapi::POINT, + min_track: winapi::POINT, + max_track: winapi::POINT + } + + let mmi = lparam as *mut MinMaxInfo; + //(*mmi).max_position = winapi::POINT { x: -8, y: -8 }; // The upper left corner of the window if it were maximized on the primary monitor. + //(*mmi).max_size = winapi::POINT { x: .., y: .. }; // The dimensions of the primary monitor. + + CONTEXT_STASH.with(|context_stash| { + if let Some(cstash) = context_stash.borrow().as_ref() { + if let Some(wstash) = cstash.windows.get(&window) { + let window_state = wstash.lock().unwrap(); + + match window_state.attributes.min_dimensions { + Some((width, height)) => { + (*mmi).min_track = winapi::POINT { x: width as i32, y: height as i32 }; + }, + None => { } + } + + match window_state.attributes.max_dimensions { + Some((width, height)) => { + (*mmi).max_track = winapi::POINT { x: width as i32, y: height as i32 }; + }, + None => { } + } + } + } + }); + + 0 + }, + + _ => { + user32::DefWindowProcW(window, msg, wparam, lparam) + } + } +} diff --git a/src/platform/windows/init.rs b/src/platform/windows/init.rs deleted file mode 100644 index 2d3b1537..00000000 --- a/src/platform/windows/init.rs +++ /dev/null @@ -1,259 +0,0 @@ -use std::sync::{Arc, Mutex}; -use std::io; -use std::ptr; -use std::mem; -use std::thread; - -use super::callback; -use super::WindowState; -use super::Window; -use super::MonitorId; -use super::WindowWrapper; -use super::PlatformSpecificWindowBuilderAttributes; - -use CreationError; -use CreationError::OsError; -use CursorState; -use WindowAttributes; - -use std::ffi::{OsStr}; -use std::os::windows::ffi::OsStrExt; -use std::sync::mpsc::channel; - -use winapi; -use kernel32; -use dwmapi; -use user32; - -pub fn new_window(window: &WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result { - let window = window.clone(); - let attribs = pl_attribs.clone(); - // initializing variables to be sent to the task - - let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter()) - .collect::>(); - - let (tx, rx) = channel(); - - // `GetMessage` must be called in the same thread as CreateWindow, so we create a new thread - // dedicated to this window. - thread::spawn(move || { - unsafe { - // creating and sending the `Window` - match init(title, &window, attribs) { - Ok(w) => tx.send(Ok(w)).ok(), - Err(e) => { - tx.send(Err(e)).ok(); - return; - } - }; - - // now that the `Window` struct is initialized, the main `Window::new()` function will - // return and this events loop will run in parallel - loop { - let mut msg = mem::uninitialized(); - - if user32::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 { - break; - } - - user32::TranslateMessage(&msg); - user32::DispatchMessageW(&msg); // calls `callback` (see the callback module) - } - } - }); - - rx.recv().unwrap() -} - -unsafe fn init(title: Vec, window: &WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result { - // registering the window class - let class_name = register_window_class(); - - // building a RECT object with coordinates - let mut rect = winapi::RECT { - left: 0, right: window.dimensions.unwrap_or((1024, 768)).0 as winapi::LONG, - top: 0, bottom: window.dimensions.unwrap_or((1024, 768)).1 as winapi::LONG, - }; - - // switching to fullscreen if necessary - // this means adjusting the window's position so that it overlaps the right monitor, - // and change the monitor's resolution if necessary - if window.monitor.is_some() { - let monitor = window.monitor.as_ref().unwrap(); - try!(switch_to_fullscreen(&mut rect, monitor)); - } - - // computing the style and extended style of the window - let (ex_style, style) = if window.monitor.is_some() || !window.decorations { - (winapi::WS_EX_APPWINDOW, - //winapi::WS_POPUP is incompatible with winapi::WS_CHILD - if pl_attribs.parent.is_some() { - winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN - } - else { - winapi::WS_POPUP | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN - } - ) - } else { - (winapi::WS_EX_APPWINDOW | winapi::WS_EX_WINDOWEDGE, - winapi::WS_OVERLAPPEDWINDOW | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN) - }; - - // adjusting the window coordinates using the style - user32::AdjustWindowRectEx(&mut rect, style, 0, ex_style); - - // creating the real window this time, by using the functions in `extra_functions` - let real_window = { - let (width, height) = if window.monitor.is_some() || window.dimensions.is_some() { - (Some(rect.right - rect.left), Some(rect.bottom - rect.top)) - } else { - (None, None) - }; - - let (x, y) = if window.monitor.is_some() { - (Some(rect.left), Some(rect.top)) - } else { - (None, None) - }; - - let mut style = if !window.visible { - style - } else { - style | winapi::WS_VISIBLE - }; - - if pl_attribs.parent.is_some() { - style |= winapi::WS_CHILD; - } - - let handle = user32::CreateWindowExW(ex_style | winapi::WS_EX_ACCEPTFILES, - class_name.as_ptr(), - title.as_ptr() as winapi::LPCWSTR, - style | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN, - x.unwrap_or(winapi::CW_USEDEFAULT), y.unwrap_or(winapi::CW_USEDEFAULT), - width.unwrap_or(winapi::CW_USEDEFAULT), height.unwrap_or(winapi::CW_USEDEFAULT), - pl_attribs.parent.unwrap_or(ptr::null_mut()), - ptr::null_mut(), kernel32::GetModuleHandleW(ptr::null()), - ptr::null_mut()); - - if handle.is_null() { - return Err(OsError(format!("CreateWindowEx function failed: {}", - format!("{}", io::Error::last_os_error())))); - } - - let hdc = user32::GetDC(handle); - if hdc.is_null() { - return Err(OsError(format!("GetDC function failed: {}", - format!("{}", io::Error::last_os_error())))); - } - - WindowWrapper(handle, hdc) - }; - - // making the window transparent - if window.transparent { - let bb = winapi::DWM_BLURBEHIND { - dwFlags: 0x1, // FIXME: DWM_BB_ENABLE; - fEnable: 1, - hRgnBlur: ptr::null_mut(), - fTransitionOnMaximized: 0, - }; - - dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb); - } - - // calling SetForegroundWindow if fullscreen - if window.monitor.is_some() { - user32::SetForegroundWindow(real_window.0); - } - - // Creating a mutex to track the current window state - let window_state = Arc::new(Mutex::new(WindowState { - cursor: winapi::IDC_ARROW, // use arrow by default - cursor_state: CursorState::Normal, - attributes: window.clone() - })); - - // filling the CONTEXT_STASH task-local storage so that we can start receiving events - let events_receiver = { - let (tx, rx) = channel(); - let mut tx = Some(tx); - callback::CONTEXT_STASH.with(|context_stash| { - let data = callback::ThreadLocalData { - win: real_window.0, - sender: tx.take().unwrap(), - window_state: window_state.clone(), - mouse_in_window: false - }; - (*context_stash.borrow_mut()) = Some(data); - }); - rx - }; - - // building the struct - Ok(Window { - window: real_window, - events_receiver: events_receiver, - window_state: window_state, - }) -} - -unsafe fn register_window_class() -> Vec { - let class_name = OsStr::new("Window Class").encode_wide().chain(Some(0).into_iter()) - .collect::>(); - - let class = winapi::WNDCLASSEXW { - cbSize: mem::size_of::() as winapi::UINT, - style: winapi::CS_HREDRAW | winapi::CS_VREDRAW | winapi::CS_OWNDC, - lpfnWndProc: Some(callback::callback), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: kernel32::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(), - }; - - // We ignore errors because registering the same window class twice would trigger - // an error, and because errors here are detected during CreateWindowEx anyway. - // Also since there is no weird element in the struct, there is no reason for this - // call to fail. - user32::RegisterClassExW(&class); - - class_name -} - -unsafe fn switch_to_fullscreen(rect: &mut winapi::RECT, monitor: &MonitorId) - -> Result<(), CreationError> -{ - // adjusting the rect - { - let pos = monitor.get_position(); - rect.left += pos.0 as winapi::LONG; - rect.right += pos.0 as winapi::LONG; - rect.top += pos.1 as winapi::LONG; - rect.bottom += pos.1 as winapi::LONG; - } - - // changing device settings - let mut screen_settings: winapi::DEVMODEW = mem::zeroed(); - screen_settings.dmSize = mem::size_of::() as winapi::WORD; - screen_settings.dmPelsWidth = (rect.right - rect.left) as winapi::DWORD; - screen_settings.dmPelsHeight = (rect.bottom - rect.top) as winapi::DWORD; - screen_settings.dmBitsPerPel = 32; // TODO: ? - screen_settings.dmFields = winapi::DM_BITSPERPEL | winapi::DM_PELSWIDTH | winapi::DM_PELSHEIGHT; - - let result = user32::ChangeDisplaySettingsExW(monitor.get_adapter_name().as_ptr(), - &mut screen_settings, ptr::null_mut(), - winapi::CDS_FULLSCREEN, ptr::null_mut()); - - if result != winapi::DISP_CHANGE_SUCCESSFUL { - return Err(OsError(format!("ChangeDisplaySettings failed: {}", result))); - } - - Ok(()) -} diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index fe6ac385..52e77525 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1,21 +1,10 @@ #![cfg(target_os = "windows")] -use std::mem; -use std::ptr; -use std::ffi::OsStr; -use std::os::windows::ffi::OsStrExt; -use std::os::raw::c_int; -use std::sync::{ - Arc, - Mutex -}; -use std::sync::mpsc::Receiver; -use {CreationError, WindowEvent as Event, MouseCursor}; -use CursorState; +use winapi; -use WindowAttributes; - -gen_api_transition!(); +pub use self::events_loop::{EventsLoop, EventsLoopProxy}; +pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor}; +pub use self::window::Window; #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes { @@ -25,361 +14,23 @@ pub struct PlatformSpecificWindowBuilderAttributes { unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} -#[derive(Clone, Default)] -pub struct PlatformSpecificHeadlessBuilderAttributes; - -pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor}; - -use winapi; -use user32; -use kernel32; - -mod callback; -mod event; -mod init; -mod monitor; - -lazy_static! { - static ref WAKEUP_MSG_ID: u32 = unsafe { user32::RegisterWindowMessageA("Glutin::EventID".as_ptr() as *const i8) }; -} - -/// Cursor +// TODO: document what this means pub type Cursor = *const winapi::wchar_t; -/// Contains information about states and the window for the callback. -#[derive(Clone)] -pub struct WindowState { - pub cursor: Cursor, - pub cursor_state: CursorState, - pub attributes: WindowAttributes -} +// TODO: remove +pub type Window2 = Window; -/// The Win32 implementation of the main `Window` object. -pub struct Window { - /// Main handle for the window. - window: WindowWrapper, +// Constant device ID, to be removed when this backend is updated to report real device IDs. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId; +const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); - /// Receiver for the events dispatched by the window callback. - events_receiver: Receiver, +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId(winapi::HWND); +unsafe impl Send for WindowId {} +unsafe impl Sync for WindowId {} - /// The current window state. - window_state: Arc>, -} - -unsafe impl Send for Window {} -unsafe impl Sync for Window {} - -/// A simple wrapper that destroys the window when it is destroyed. -#[doc(hidden)] -pub struct WindowWrapper(winapi::HWND, winapi::HDC); - -impl Drop for WindowWrapper { - #[inline] - fn drop(&mut self) { - unsafe { - user32::DestroyWindow(self.0); - } - } -} - -#[derive(Clone)] -pub struct WindowProxy { - hwnd: winapi::HWND, -} - -unsafe impl Send for WindowProxy {} -unsafe impl Sync for WindowProxy {} - -impl WindowProxy { - #[inline] - pub fn wakeup_event_loop(&self) { - unsafe { - user32::PostMessageA(self.hwnd, *WAKEUP_MSG_ID, 0, 0); - } - } -} - -impl Window { - /// See the docs in the crate root file. - pub fn new(window: &WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes) - -> Result - { - init::new_window(window, pl_attribs) - } - - /// See the docs in the crate root file. - /// - /// Calls SetWindowText on the HWND. - pub fn set_title(&self, text: &str) { - let text = OsStr::new(text).encode_wide().chain(Some(0).into_iter()) - .collect::>(); - - unsafe { - user32::SetWindowTextW(self.window.0, text.as_ptr() as winapi::LPCWSTR); - } - } - - #[inline] - pub fn show(&self) { - unsafe { - user32::ShowWindow(self.window.0, winapi::SW_SHOW); - } - } - - #[inline] - pub fn hide(&self) { - unsafe { - user32::ShowWindow(self.window.0, winapi::SW_HIDE); - } - } - - /// See the docs in the crate root file. - pub fn get_position(&self) -> Option<(i32, i32)> { - use std::mem; - - let mut placement: winapi::WINDOWPLACEMENT = unsafe { mem::zeroed() }; - placement.length = mem::size_of::() as winapi::UINT; - - if unsafe { user32::GetWindowPlacement(self.window.0, &mut placement) } == 0 { - return None - } - - let ref rect = placement.rcNormalPosition; - Some((rect.left as i32, rect.top as i32)) - } - - /// See the docs in the crate root file. - pub fn set_position(&self, x: i32, y: i32) { - unsafe { - user32::SetWindowPos(self.window.0, ptr::null_mut(), x as c_int, y as c_int, - 0, 0, winapi::SWP_NOZORDER | winapi::SWP_NOSIZE); - user32::UpdateWindow(self.window.0); - } - } - - /// See the docs in the crate root file. - #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { - let mut rect: winapi::RECT = unsafe { mem::uninitialized() }; - - if unsafe { user32::GetClientRect(self.window.0, &mut rect) } == 0 { - return None - } - - Some(( - (rect.right - rect.left) as u32, - (rect.bottom - rect.top) as u32 - )) - } - - /// See the docs in the crate root file. - #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { - let mut rect: winapi::RECT = unsafe { mem::uninitialized() }; - - if unsafe { user32::GetWindowRect(self.window.0, &mut rect) } == 0 { - return None - } - - Some(( - (rect.right - rect.left) as u32, - (rect.bottom - rect.top) as u32 - )) - } - - /// See the docs in the crate root file. - pub fn set_inner_size(&self, x: u32, y: u32) { - unsafe { - // Calculate the outer size based upon the specified inner size - let mut rect = winapi::RECT { top: 0, left: 0, bottom: y as winapi::LONG, right: x as winapi::LONG }; - let dw_style = user32::GetWindowLongA(self.window.0, winapi::GWL_STYLE) as winapi::DWORD; - let b_menu = !user32::GetMenu(self.window.0).is_null() as winapi::BOOL; - let dw_style_ex = user32::GetWindowLongA(self.window.0, winapi::GWL_EXSTYLE) as winapi::DWORD; - user32::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); - let outer_x = (rect.right - rect.left).abs() as c_int; - let outer_y = (rect.top - rect.bottom).abs() as c_int; - - user32::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y, - winapi::SWP_NOZORDER | winapi::SWP_NOREPOSITION | winapi::SWP_NOMOVE); - user32::UpdateWindow(self.window.0); - } - } - - #[inline] - pub fn create_window_proxy(&self) -> WindowProxy { - WindowProxy { hwnd: self.window.0 } - } - - /// See the docs in the crate root file. - #[inline] - pub fn poll_events(&self) -> PollEventsIterator { - PollEventsIterator { - window: self, - } - } - - /// See the docs in the crate root file. - #[inline] - pub fn wait_events(&self) -> WaitEventsIterator { - WaitEventsIterator { - window: self, - } - } - - #[inline] - pub fn platform_display(&self) -> *mut ::libc::c_void { - // What should this return on win32? - // It could be GetDC(NULL), but that requires a ReleaseDC() - // to avoid leaking the DC. - ptr::null_mut() - } - - #[inline] - pub fn platform_window(&self) -> *mut ::libc::c_void { - self.window.0 as *mut ::libc::c_void - } - - #[inline] - pub fn set_window_resize_callback(&mut self, _: Option) { - } - - #[inline] - pub fn set_cursor(&self, _cursor: MouseCursor) { - let cursor_id = match _cursor { - MouseCursor::Arrow | MouseCursor::Default => winapi::IDC_ARROW, - MouseCursor::Hand => winapi::IDC_HAND, - MouseCursor::Crosshair => winapi::IDC_CROSS, - MouseCursor::Text | MouseCursor::VerticalText => winapi::IDC_IBEAM, - MouseCursor::NotAllowed | MouseCursor::NoDrop => winapi::IDC_NO, - MouseCursor::EResize => winapi::IDC_SIZEWE, - MouseCursor::NResize => winapi::IDC_SIZENS, - MouseCursor::WResize => winapi::IDC_SIZEWE, - MouseCursor::SResize => winapi::IDC_SIZENS, - MouseCursor::EwResize | MouseCursor::ColResize => winapi::IDC_SIZEWE, - MouseCursor::NsResize | MouseCursor::RowResize => winapi::IDC_SIZENS, - MouseCursor::Wait | MouseCursor::Progress => winapi::IDC_WAIT, - MouseCursor::Help => winapi::IDC_HELP, - _ => winapi::IDC_ARROW, // use arrow for the missing cases. - }; - - let mut cur = self.window_state.lock().unwrap(); - cur.cursor = cursor_id; - } - - - pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> { - let mut current_state = self.window_state.lock().unwrap(); - - let foreground_thread_id = unsafe { user32::GetWindowThreadProcessId(self.window.0, ptr::null_mut()) }; - let current_thread_id = unsafe { kernel32::GetCurrentThreadId() }; - - unsafe { user32::AttachThreadInput(foreground_thread_id, current_thread_id, 1) }; - - let res = match (state, current_state.cursor_state) { - (CursorState::Normal, CursorState::Normal) => Ok(()), - (CursorState::Hide, CursorState::Hide) => Ok(()), - (CursorState::Grab, CursorState::Grab) => Ok(()), - - (CursorState::Hide, CursorState::Normal) => { - current_state.cursor_state = CursorState::Hide; - Ok(()) - }, - - (CursorState::Normal, CursorState::Hide) => { - current_state.cursor_state = CursorState::Normal; - Ok(()) - }, - - (CursorState::Grab, CursorState::Normal) | (CursorState::Grab, CursorState::Hide) => { - unsafe { - let mut rect = mem::uninitialized(); - if user32::GetClientRect(self.window.0, &mut rect) == 0 { - return Err(format!("GetWindowRect failed")); - } - user32::ClientToScreen(self.window.0, mem::transmute(&mut rect.left)); - user32::ClientToScreen(self.window.0, mem::transmute(&mut rect.right)); - if user32::ClipCursor(&rect) == 0 { - return Err(format!("ClipCursor failed")); - } - current_state.cursor_state = CursorState::Grab; - Ok(()) - } - }, - - (CursorState::Normal, CursorState::Grab) => { - unsafe { - if user32::ClipCursor(ptr::null()) == 0 { - return Err(format!("ClipCursor failed")); - } - current_state.cursor_state = CursorState::Normal; - Ok(()) - } - }, - - _ => unimplemented!(), - }; - - unsafe { user32::AttachThreadInput(foreground_thread_id, current_thread_id, 0) }; - - res - } - - #[inline] - pub fn hidpi_factor(&self) -> f32 { - 1.0 - } - - pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { - let mut point = winapi::POINT { - x: x, - y: y, - }; - - unsafe { - if user32::ClientToScreen(self.window.0, &mut point) == 0 { - return Err(()); - } - - if user32::SetCursorPos(point.x, point.y) == 0 { - return Err(()); - } - } - - Ok(()) - } -} - -impl Drop for Window { - #[inline] - fn drop(&mut self) { - unsafe { - user32::PostMessageW(self.window.0, winapi::WM_DESTROY, 0, 0); - } - } -} - -pub struct PollEventsIterator<'a> { - window: &'a Window, -} - -impl<'a> Iterator for PollEventsIterator<'a> { - type Item = Event; - - #[inline] - fn next(&mut self) -> Option { - self.window.events_receiver.try_recv().ok() - } -} - -pub struct WaitEventsIterator<'a> { - window: &'a Window, -} - -impl<'a> Iterator for WaitEventsIterator<'a> { - type Item = Event; - - #[inline] - fn next(&mut self) -> Option { - self.window.events_receiver.recv().ok() - } -} +mod event; +mod events_loop; +mod monitor; +mod window; diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs new file mode 100644 index 00000000..fc08a5d3 --- /dev/null +++ b/src/platform/windows/window.rs @@ -0,0 +1,483 @@ +#![cfg(target_os = "windows")] + +use std::ffi::OsStr; +use std::io; +use std::mem; +use std::os::raw; +use std::os::windows::ffi::OsStrExt; +use std::ptr; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::mpsc::channel; + +use platform::platform::events_loop; +use platform::platform::EventsLoop; +use platform::platform::PlatformSpecificWindowBuilderAttributes; +use platform::platform::MonitorId; +use platform::platform::WindowId; + +use CreationError; +use CursorState; +use MouseCursor; +use WindowAttributes; + +use dwmapi; +use kernel32; +use user32; +use winapi; + +/// The Win32 implementation of the main `Window` object. +pub struct Window { + /// Main handle for the window. + window: WindowWrapper, + + /// The current window state. + window_state: Arc>, +} + +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +impl Window { + pub fn new(events_loop: &EventsLoop, w_attr: &WindowAttributes, + pl_attr: &PlatformSpecificWindowBuilderAttributes) -> Result + { + let mut w_attr = Some(w_attr.clone()); + let mut pl_attr = Some(pl_attr.clone()); + + let (tx, rx) = channel(); + + events_loop.execute_in_thread(move |inserter| { + // We dispatch an `init` function because of code style. + let win = unsafe { init(w_attr.take().unwrap(), pl_attr.take().unwrap(), inserter) }; + let _ = tx.send(win); + }); + + rx.recv().unwrap() + } + + pub fn set_title(&self, text: &str) { + unsafe { + let text = OsStr::new(text).encode_wide().chain(Some(0).into_iter()) + .collect::>(); + + user32::SetWindowTextW(self.window.0, text.as_ptr() as winapi::LPCWSTR); + } + } + + #[inline] + pub fn show(&self) { + unsafe { + user32::ShowWindow(self.window.0, winapi::SW_SHOW); + } + } + + #[inline] + pub fn hide(&self) { + unsafe { + user32::ShowWindow(self.window.0, winapi::SW_HIDE); + } + } + + /// See the docs in the crate root file. + pub fn get_position(&self) -> Option<(i32, i32)> { + use std::mem; + + let mut placement: winapi::WINDOWPLACEMENT = unsafe { mem::zeroed() }; + placement.length = mem::size_of::() as winapi::UINT; + + if unsafe { user32::GetWindowPlacement(self.window.0, &mut placement) } == 0 { + return None + } + + let ref rect = placement.rcNormalPosition; + Some((rect.left as i32, rect.top as i32)) + } + + /// See the docs in the crate root file. + pub fn set_position(&self, x: i32, y: i32) { + unsafe { + user32::SetWindowPos(self.window.0, ptr::null_mut(), x as raw::c_int, y as raw::c_int, + 0, 0, winapi::SWP_NOZORDER | winapi::SWP_NOSIZE); + user32::UpdateWindow(self.window.0); + } + } + + /// See the docs in the crate root file. + #[inline] + pub fn get_inner_size(&self) -> Option<(u32, u32)> { + let mut rect: winapi::RECT = unsafe { mem::uninitialized() }; + + if unsafe { user32::GetClientRect(self.window.0, &mut rect) } == 0 { + return None + } + + Some(( + (rect.right - rect.left) as u32, + (rect.bottom - rect.top) as u32 + )) + } + + /// See the docs in the crate root file. + #[inline] + pub fn get_outer_size(&self) -> Option<(u32, u32)> { + let mut rect: winapi::RECT = unsafe { mem::uninitialized() }; + + if unsafe { user32::GetWindowRect(self.window.0, &mut rect) } == 0 { + return None + } + + Some(( + (rect.right - rect.left) as u32, + (rect.bottom - rect.top) as u32 + )) + } + + /// See the docs in the crate root file. + pub fn set_inner_size(&self, x: u32, y: u32) { + unsafe { + // Calculate the outer size based upon the specified inner size + let mut rect = winapi::RECT { top: 0, left: 0, bottom: y as winapi::LONG, right: x as winapi::LONG }; + let dw_style = user32::GetWindowLongA(self.window.0, winapi::GWL_STYLE) as winapi::DWORD; + let b_menu = !user32::GetMenu(self.window.0).is_null() as winapi::BOOL; + let dw_style_ex = user32::GetWindowLongA(self.window.0, winapi::GWL_EXSTYLE) as winapi::DWORD; + user32::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); + let outer_x = (rect.right - rect.left).abs() as raw::c_int; + let outer_y = (rect.top - rect.bottom).abs() as raw::c_int; + + user32::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y, + winapi::SWP_NOZORDER | winapi::SWP_NOREPOSITION | winapi::SWP_NOMOVE); + user32::UpdateWindow(self.window.0); + } + } + + // TODO: remove + pub fn platform_display(&self) -> *mut ::libc::c_void { + panic!() // Deprecated function ; we don't care anymore + } + // TODO: remove + pub fn platform_window(&self) -> *mut ::libc::c_void { + panic!() // Deprecated function ; we don't care anymore + } + + /// Returns the `hwnd` of this window. + #[inline] + pub fn hwnd(&self) -> winapi::HWND { + self.window.0 + } + + #[inline] + pub fn set_cursor(&self, cursor: MouseCursor) { + let cursor_id = match cursor { + MouseCursor::Arrow | MouseCursor::Default => winapi::IDC_ARROW, + MouseCursor::Hand => winapi::IDC_HAND, + MouseCursor::Crosshair => winapi::IDC_CROSS, + MouseCursor::Text | MouseCursor::VerticalText => winapi::IDC_IBEAM, + MouseCursor::NotAllowed | MouseCursor::NoDrop => winapi::IDC_NO, + MouseCursor::EResize => winapi::IDC_SIZEWE, + MouseCursor::NResize => winapi::IDC_SIZENS, + MouseCursor::WResize => winapi::IDC_SIZEWE, + MouseCursor::SResize => winapi::IDC_SIZENS, + MouseCursor::EwResize | MouseCursor::ColResize => winapi::IDC_SIZEWE, + MouseCursor::NsResize | MouseCursor::RowResize => winapi::IDC_SIZENS, + MouseCursor::Wait | MouseCursor::Progress => winapi::IDC_WAIT, + MouseCursor::Help => winapi::IDC_HELP, + _ => winapi::IDC_ARROW, // use arrow for the missing cases. + }; + + let mut cur = self.window_state.lock().unwrap(); + cur.cursor = cursor_id; + } + + // TODO: it should be possible to rework this function by using the `execute_in_thread` method + // of the events loop. + pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> { + let mut current_state = self.window_state.lock().unwrap(); + + let foreground_thread_id = unsafe { user32::GetWindowThreadProcessId(self.window.0, ptr::null_mut()) }; + let current_thread_id = unsafe { kernel32::GetCurrentThreadId() }; + + unsafe { user32::AttachThreadInput(foreground_thread_id, current_thread_id, 1) }; + + let res = match (state, current_state.cursor_state) { + (CursorState::Normal, CursorState::Normal) => Ok(()), + (CursorState::Hide, CursorState::Hide) => Ok(()), + (CursorState::Grab, CursorState::Grab) => Ok(()), + + (CursorState::Hide, CursorState::Normal) => { + current_state.cursor_state = CursorState::Hide; + Ok(()) + }, + + (CursorState::Normal, CursorState::Hide) => { + current_state.cursor_state = CursorState::Normal; + Ok(()) + }, + + (CursorState::Grab, CursorState::Normal) | (CursorState::Grab, CursorState::Hide) => { + unsafe { + let mut rect = mem::uninitialized(); + if user32::GetClientRect(self.window.0, &mut rect) == 0 { + return Err(format!("GetWindowRect failed")); + } + user32::ClientToScreen(self.window.0, mem::transmute(&mut rect.left)); + user32::ClientToScreen(self.window.0, mem::transmute(&mut rect.right)); + if user32::ClipCursor(&rect) == 0 { + return Err(format!("ClipCursor failed")); + } + current_state.cursor_state = CursorState::Grab; + Ok(()) + } + }, + + (CursorState::Normal, CursorState::Grab) => { + unsafe { + if user32::ClipCursor(ptr::null()) == 0 { + return Err(format!("ClipCursor failed")); + } + current_state.cursor_state = CursorState::Normal; + Ok(()) + } + }, + + _ => unimplemented!(), + }; + + unsafe { user32::AttachThreadInput(foreground_thread_id, current_thread_id, 0) }; + + res + } + + #[inline] + pub fn hidpi_factor(&self) -> f32 { + 1.0 + } + + pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { + let mut point = winapi::POINT { + x: x, + y: y, + }; + + unsafe { + if user32::ClientToScreen(self.window.0, &mut point) == 0 { + return Err(()); + } + + if user32::SetCursorPos(point.x, point.y) == 0 { + return Err(()); + } + } + + Ok(()) + } + + #[inline] + pub fn id(&self) -> WindowId { + WindowId(self.window.0) + } +} + +impl Drop for Window { + #[inline] + fn drop(&mut self) { + unsafe { + user32::PostMessageW(self.window.0, winapi::WM_DESTROY, 0, 0); + } + } +} + +/// A simple wrapper that destroys the window when it is destroyed. +#[doc(hidden)] +pub struct WindowWrapper(winapi::HWND, winapi::HDC); + +impl Drop for WindowWrapper { + #[inline] + fn drop(&mut self) { + unsafe { + user32::DestroyWindow(self.0); + } + } +} + +unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, + inserter: events_loop::Inserter) -> Result { + let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter()) + .collect::>(); + + // registering the window class + let class_name = register_window_class(); + + // building a RECT object with coordinates + let mut rect = winapi::RECT { + left: 0, right: window.dimensions.unwrap_or((1024, 768)).0 as winapi::LONG, + top: 0, bottom: window.dimensions.unwrap_or((1024, 768)).1 as winapi::LONG, + }; + + // switching to fullscreen if necessary + // this means adjusting the window's position so that it overlaps the right monitor, + // and change the monitor's resolution if necessary + if window.monitor.is_some() { + let monitor = window.monitor.as_ref().unwrap(); + try!(switch_to_fullscreen(&mut rect, monitor)); + } + + // computing the style and extended style of the window + let (ex_style, style) = if window.monitor.is_some() || !window.decorations { + (winapi::WS_EX_APPWINDOW, + //winapi::WS_POPUP is incompatible with winapi::WS_CHILD + if pl_attribs.parent.is_some() { + winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN + } + else { + winapi::WS_POPUP | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN + } + ) + } else { + (winapi::WS_EX_APPWINDOW | winapi::WS_EX_WINDOWEDGE, + winapi::WS_OVERLAPPEDWINDOW | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN) + }; + + // adjusting the window coordinates using the style + user32::AdjustWindowRectEx(&mut rect, style, 0, ex_style); + + // creating the real window this time, by using the functions in `extra_functions` + let real_window = { + let (width, height) = if window.monitor.is_some() || window.dimensions.is_some() { + (Some(rect.right - rect.left), Some(rect.bottom - rect.top)) + } else { + (None, None) + }; + + let (x, y) = if window.monitor.is_some() { + (Some(rect.left), Some(rect.top)) + } else { + (None, None) + }; + + let mut style = if !window.visible { + style + } else { + style | winapi::WS_VISIBLE + }; + + if pl_attribs.parent.is_some() { + style |= winapi::WS_CHILD; + } + + let handle = user32::CreateWindowExW(ex_style | winapi::WS_EX_ACCEPTFILES, + class_name.as_ptr(), + title.as_ptr() as winapi::LPCWSTR, + style | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN, + x.unwrap_or(winapi::CW_USEDEFAULT), y.unwrap_or(winapi::CW_USEDEFAULT), + width.unwrap_or(winapi::CW_USEDEFAULT), height.unwrap_or(winapi::CW_USEDEFAULT), + pl_attribs.parent.unwrap_or(ptr::null_mut()), + ptr::null_mut(), kernel32::GetModuleHandleW(ptr::null()), + ptr::null_mut()); + + if handle.is_null() { + return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}", + format!("{}", io::Error::last_os_error())))); + } + + let hdc = user32::GetDC(handle); + if hdc.is_null() { + return Err(CreationError::OsError(format!("GetDC function failed: {}", + format!("{}", io::Error::last_os_error())))); + } + + WindowWrapper(handle, hdc) + }; + + // Creating a mutex to track the current window state + let window_state = Arc::new(Mutex::new(events_loop::WindowState { + cursor: winapi::IDC_ARROW, // use arrow by default + cursor_state: CursorState::Normal, + attributes: window.clone(), + mouse_in_window: false, + })); + + inserter.insert(real_window.0, window_state.clone()); + + // making the window transparent + if window.transparent { + let bb = winapi::DWM_BLURBEHIND { + dwFlags: 0x1, // FIXME: DWM_BB_ENABLE; + fEnable: 1, + hRgnBlur: ptr::null_mut(), + fTransitionOnMaximized: 0, + }; + + dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb); + } + + // calling SetForegroundWindow if fullscreen + if window.monitor.is_some() { + user32::SetForegroundWindow(real_window.0); + } + + // Building the struct. + Ok(Window { + window: real_window, + window_state: window_state, + }) +} + +unsafe fn register_window_class() -> Vec { + let class_name = OsStr::new("Window Class").encode_wide().chain(Some(0).into_iter()) + .collect::>(); + + let class = winapi::WNDCLASSEXW { + cbSize: mem::size_of::() as winapi::UINT, + style: winapi::CS_HREDRAW | winapi::CS_VREDRAW | winapi::CS_OWNDC, + lpfnWndProc: Some(events_loop::callback), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: kernel32::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(), + }; + + // We ignore errors because registering the same window class twice would trigger + // an error, and because errors here are detected during CreateWindowEx anyway. + // Also since there is no weird element in the struct, there is no reason for this + // call to fail. + user32::RegisterClassExW(&class); + + class_name +} + +unsafe fn switch_to_fullscreen(rect: &mut winapi::RECT, monitor: &MonitorId) + -> Result<(), CreationError> +{ + // adjusting the rect + { + let pos = monitor.get_position(); + rect.left += pos.0 as winapi::LONG; + rect.right += pos.0 as winapi::LONG; + rect.top += pos.1 as winapi::LONG; + rect.bottom += pos.1 as winapi::LONG; + } + + // changing device settings + let mut screen_settings: winapi::DEVMODEW = mem::zeroed(); + screen_settings.dmSize = mem::size_of::() as winapi::WORD; + screen_settings.dmPelsWidth = (rect.right - rect.left) as winapi::DWORD; + screen_settings.dmPelsHeight = (rect.bottom - rect.top) as winapi::DWORD; + screen_settings.dmBitsPerPel = 32; // TODO: ? + screen_settings.dmFields = winapi::DM_BITSPERPEL | winapi::DM_PELSWIDTH | winapi::DM_PELSHEIGHT; + + let result = user32::ChangeDisplaySettingsExW(monitor.get_adapter_name().as_ptr(), + &mut screen_settings, ptr::null_mut(), + winapi::CDS_FULLSCREEN, ptr::null_mut()); + + if result != winapi::DISP_CHANGE_SUCCESSFUL { + return Err(CreationError::OsError(format!("ChangeDisplaySettings failed: {}", result))); + } + + Ok(()) +}