From bdc01fee1a54470b07a897592927c26d1af60341 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Fri, 13 Apr 2018 01:12:15 +0800 Subject: [PATCH] Implement set_maximized, get_current_monitor, set_fullscreen and set_decorations for windows (#457) * Implement set_fullscreen for windows * Implement get_current_monitor for windows * Implement set_maximized * Implement set_decorations for windows * Update CHANGELOG.md * Fixed minor syntax bug for stable rust version * Added support for WindowBuilder::with_maximized * Move all window sized related functions to main thread * Refactor and formatting force_window_active * Remove unused code * Update CHANGELOG.md * Refactor and change keyboard handling code * Reformatting and refactoring * Added back missing link for comment * Fixed set_maximized and set_fullscreen wrong order bug * Call ShowWindow(SW_RESTORE) when restore_saved_window * Sync system maximized status when set_fullscreen * Fixed wrong function name --- CHANGELOG.md | 2 + Cargo.toml | 3 + examples/fullscreen.rs | 45 ++- src/lib.rs | 1 + src/platform/windows/events_loop.rs | 66 ++-- src/platform/windows/monitor.rs | 33 +- src/platform/windows/window.rs | 486 +++++++++++++++++++++++----- 7 files changed, 536 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db367c4a..9f676f7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for Windows. +- On Windows, `WindowBuilder::with_dimensions` no longer changing monitor display resolution. - Overhauled X11 window geometry calculations. `get_position` and `set_position` are more universally accurate across different window managers, and `get_outer_size` actually works now. - Fixed SIGSEGV/SIGILL crashes on macOS caused by stabilization of the `!` (never) type. - Implement `WindowEvent::HiDPIFactorChanged` for macOS diff --git a/Cargo.toml b/Cargo.toml index b3a64539..a5e201fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,9 @@ features = [ "libloaderapi", "windowsx", "hidusage", + "combaseapi", + "objbase", + "unknwnbase", ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies] diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 933eceba..481ba223 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -25,24 +25,51 @@ fn main() { monitor }; - let _window = winit::WindowBuilder::new() + let window = winit::WindowBuilder::new() .with_title("Hello world!") .with_fullscreen(Some(monitor)) .build(&events_loop) .unwrap(); + let mut is_fullscreen = true; + let mut is_maximized = false; + let mut decorations = true; + events_loop.run_forever(|event| { println!("{:?}", event); match event { - Event::WindowEvent { event, .. } => { - match event { - WindowEvent::Closed => return ControlFlow::Break, - WindowEvent::KeyboardInput { - input: winit::KeyboardInput { virtual_keycode: Some(winit::VirtualKeyCode::Escape), .. }, .. - } => return ControlFlow::Break, - _ => () - } + Event::WindowEvent { event, .. } => match event { + WindowEvent::Closed => return ControlFlow::Break, + WindowEvent::KeyboardInput { + input: + winit::KeyboardInput { + virtual_keycode: Some(virtual_code), + state, + .. + }, + .. + } => match (virtual_code, state) { + (winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break, + (winit::VirtualKeyCode::F11, winit::ElementState::Pressed) => { + is_fullscreen = !is_fullscreen; + if !is_fullscreen { + window.set_fullscreen(None); + } else { + window.set_fullscreen(Some(window.get_current_monitor())); + } + } + (winit::VirtualKeyCode::M, winit::ElementState::Pressed) => { + is_maximized = !is_maximized; + window.set_maximized(is_maximized); + } + (winit::VirtualKeyCode::D, winit::ElementState::Pressed) => { + decorations = !decorations; + window.set_decorations(decorations); + } + _ => (), + }, + _ => (), }, _ => {} } diff --git a/src/lib.rs b/src/lib.rs index 389df43e..acff673d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,7 @@ extern crate lazy_static; extern crate libc; #[cfg(target_os = "windows")] +#[macro_use] extern crate winapi; #[cfg(any(target_os = "macos", target_os = "ios"))] #[macro_use] diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs index 8d056719..402f1d28 100644 --- a/src/platform/windows/events_loop.rs +++ b/src/platform/windows/events_loop.rs @@ -27,9 +27,10 @@ use std::sync::Condvar; use std::thread; use winapi::shared::minwindef::{LOWORD, HIWORD, DWORD, WPARAM, LPARAM, INT, UINT, LRESULT, MAX_PATH}; -use winapi::shared::windef::{HWND, POINT}; +use winapi::shared::windef::{HWND, POINT, RECT}; use winapi::shared::windowsx; use winapi::um::{winuser, shellapi, processthreadsapi}; +use winapi::um::winnt::LONG; use platform::platform::event; use platform::platform::Cursor; @@ -46,6 +47,16 @@ use WindowEvent; use WindowId as SuperWindowId; use events::{Touch, TouchPhase}; +/// Contains saved window info for switching between fullscreen +#[derive(Clone)] +pub struct SavedWindowInfo { + /// Window style + pub style: LONG, + /// Window ex-style + pub ex_style: LONG, + /// Window position and size + pub rect: RECT, +} /// Contains information about states and the window that the callback is going to use. #[derive(Clone)] @@ -58,6 +69,8 @@ pub struct WindowState { pub attributes: WindowAttributes, /// Will contain `true` if the mouse is hovering the window. pub mouse_in_window: bool, + /// Saved window info for fullscreen restored + pub saved_window_info: Option, } /// Dummy object that allows inserting a window's state. @@ -227,18 +240,7 @@ impl EventsLoop { 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 = winuser::PostThreadMessageA(self.thread_id, *EXEC_MSG_ID, - raw as *mut () as usize as 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?"); - } + self.create_proxy().execute_in_thread(function) } } @@ -273,6 +275,38 @@ impl EventsLoopProxy { } } } + + /// 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 fn execute_in_thread(&self, function: F) + where + F: FnMut(Inserter) + Send + 'static, + { + unsafe { + // We are using double-boxing here because it make casting back much easier + let boxed = Box::new(function) as Box; + let boxed2 = Box::new(boxed); + let raw = Box::into_raw(boxed2); + + let res = winuser::PostThreadMessageA( + self.thread_id, + *EXEC_MSG_ID, + raw as *mut () as usize as 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?" + ); + } + } } lazy_static! { @@ -361,11 +395,7 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT, context_stash.as_mut().unwrap().windows.remove(&window); }); winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_ERASEBKGND => { - 1 - }, + }, winuser::WM_PAINT => { use events::WindowEvent::Refresh; diff --git a/src/platform/windows/monitor.rs b/src/platform/windows/monitor.rs index 43129389..4f9eaadd 100644 --- a/src/platform/windows/monitor.rs +++ b/src/platform/windows/monitor.rs @@ -1,6 +1,6 @@ use winapi::ctypes::wchar_t; use winapi::shared::minwindef::{DWORD, LPARAM, BOOL, TRUE}; -use winapi::shared::windef::{HMONITOR, HDC, LPRECT}; +use winapi::shared::windef::{HMONITOR, HDC, LPRECT, HWND}; use winapi::um::winuser; use std::collections::VecDeque; @@ -87,6 +87,37 @@ impl EventsLoop { } } + pub fn get_current_monitor(handle: HWND) -> MonitorId { + unsafe { + let mut monitor_info: winuser::MONITORINFOEXW = mem::zeroed(); + monitor_info.cbSize = mem::size_of::() as DWORD; + + let hmonitor = winuser::MonitorFromWindow(handle, winuser::MONITOR_DEFAULTTONEAREST); + + winuser::GetMonitorInfoW( + hmonitor, + &mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO, + ); + + let place = monitor_info.rcMonitor; + let position = (place.left as i32, place.top as i32); + let dimensions = ( + (place.right - place.left) as u32, + (place.bottom - place.top) as u32, + ); + + MonitorId { + adapter_name: monitor_info.szDevice, + hmonitor: super::monitor::HMonitor(hmonitor), + monitor_name: wchar_as_string(&monitor_info.szDevice), + primary: monitor_info.dwFlags & winuser::MONITORINFOF_PRIMARY != 0, + position, + dimensions, + hidpi_factor: 1.0, + } + } + } + pub fn get_primary_monitor(&self) -> MonitorId { // we simply get all available monitors and return the one with the `MONITORINFOF_PRIMARY` flag // TODO: it is possible to query the win32 API for the primary monitor, this should be done diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index 7aab3fca..5d328679 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -9,11 +9,11 @@ use std::ptr; use std::sync::Arc; use std::sync::Mutex; use std::sync::mpsc::channel; +use std::cell::Cell; use platform::platform::events_loop; use platform::platform::EventsLoop; use platform::platform::PlatformSpecificWindowBuilderAttributes; -use platform::platform::MonitorId; use platform::platform::WindowId; use CreationError; @@ -22,11 +22,14 @@ use MouseCursor; use WindowAttributes; use MonitorId as RootMonitorId; -use winapi::shared::minwindef::{UINT, WORD, DWORD, BOOL}; +use winapi::shared::minwindef::{UINT, DWORD, BOOL}; use winapi::shared::windef::{HWND, HDC, RECT, POINT}; use winapi::shared::hidusage; -use winapi::um::{winuser, dwmapi, wingdi, libloaderapi, processthreadsapi}; -use winapi::um::winnt::{LPCWSTR, LONG}; +use winapi::um::{winuser, dwmapi, libloaderapi, processthreadsapi}; +use winapi::um::winnt::{LPCWSTR, LONG, HRESULT}; +use winapi::um::combaseapi; +use winapi::um::objbase::{COINIT_MULTITHREADED}; +use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl}; /// The Win32 implementation of the main `Window` object. pub struct Window { @@ -35,11 +38,39 @@ pub struct Window { /// The current window state. window_state: Arc>, + + // The events loop proxy. + events_loop_proxy: events_loop::EventsLoopProxy, } unsafe impl Send for Window {} unsafe impl Sync for Window {} +// https://blogs.msdn.microsoft.com/oldnewthing/20131017-00/?p=2903 +// The idea here is that we use the Adjust­Window­Rect­Ex function to calculate how much additional +// non-client area gets added due to the styles we passed. To make the math simple, +// we ask for a zero client rectangle, so that the resulting window is all non-client. +// And then we pass in the empty rectangle represented by the dot in the middle, +// and the Adjust­Window­Rect­Ex expands the rectangle in all dimensions. +// We see that it added ten pixels to the left, right, and bottom, +// and it added fifty pixels to the top. +// From this we can perform the reverse calculation: Instead of expanding the rectangle, we shrink it. +unsafe fn unjust_window_rect(prc: &mut RECT, style: DWORD, ex_style: DWORD) -> BOOL { + let mut rc: RECT = mem::zeroed(); + + winuser::SetRectEmpty(&mut rc); + + let frc = winuser::AdjustWindowRectEx(&mut rc, style, 0, ex_style); + if frc != 0 { + prc.left -= rc.left; + prc.top -= rc.top; + prc.right -= rc.right; + prc.bottom -= rc.bottom; + } + + frc +} + impl Window { pub fn new(events_loop: &EventsLoop, w_attr: &WindowAttributes, pl_attr: &PlatformSpecificWindowBuilderAttributes) -> Result @@ -49,9 +80,11 @@ impl Window { let (tx, rx) = channel(); + let proxy = events_loop.create_proxy(); + 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 win = unsafe { init(w_attr.take().unwrap(), pl_attr.take().unwrap(), inserter, proxy.clone()) }; let _ = tx.send(win); }); @@ -326,23 +359,244 @@ impl Window { } #[inline] - pub fn set_maximized(&self, _maximized: bool) { - unimplemented!() + pub fn set_maximized(&self, maximized: bool) { + let mut window_state = self.window_state.lock().unwrap(); + + window_state.attributes.maximized = maximized; + // we only maximized if we are not in fullscreen + if window_state.attributes.fullscreen.is_some() { + return; + } + + let window = self.window.clone(); + unsafe { + // And because ShowWindow will resize the window + // We call it in the main thread + self.events_loop_proxy.execute_in_thread(move |_| { + winuser::ShowWindow( + window.0, + if maximized { + winuser::SW_MAXIMIZE + } else { + winuser::SW_RESTORE + }, + ); + }); + } + } + + unsafe fn set_fullscreen_style(&self) -> (LONG, LONG) { + let mut window_state = self.window_state.lock().unwrap(); + + if window_state.attributes.fullscreen.is_none() || window_state.saved_window_info.is_none() { + let mut rect: RECT = mem::zeroed(); + + winuser::GetWindowRect(self.window.0, &mut rect); + + window_state.saved_window_info = Some(events_loop::SavedWindowInfo { + style: winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE), + ex_style: winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE), + rect, + }); + } + + // We sync the system maximized state here, it will be used when restoring + let mut placement: winuser::WINDOWPLACEMENT = mem::zeroed(); + placement.length = mem::size_of::() as u32; + winuser::GetWindowPlacement(self.window.0, &mut placement); + window_state.attributes.maximized = + placement.showCmd == (winuser::SW_SHOWMAXIMIZED as u32); + let saved_window_info = window_state.saved_window_info.as_ref().unwrap(); + + (saved_window_info.style, saved_window_info.ex_style) + } + + unsafe fn restore_saved_window(&self) { + let window_state = self.window_state.lock().unwrap(); + // Reset original window style and size. The multiple window size/moves + // here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be + // repainted. Better-looking methods welcome. + let saved_window_info = window_state.saved_window_info.as_ref().unwrap(); + + let rect = saved_window_info.rect.clone(); + let window = self.window.clone(); + let (style, ex_style) = (saved_window_info.style, saved_window_info.ex_style); + + let maximized = window_state.attributes.maximized; + + // On restore, resize to the previous saved rect size. + // And because SetWindowPos will resize the window + // We call it in the main thread + self.events_loop_proxy.execute_in_thread(move |_| { + winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style); + winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style); + + winuser::SetWindowPos( + window.0, + ptr::null_mut(), + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE + | winuser::SWP_FRAMECHANGED, + ); + + // if it was set to maximized when it were fullscreened, we restore it as well + winuser::ShowWindow( + window.0, + if maximized { + winuser::SW_MAXIMIZE + } else { + winuser::SW_RESTORE + }, + ); + + mark_fullscreen(window.0, false); + }); } #[inline] - pub fn set_fullscreen(&self, _monitor: Option) { - unimplemented!() + pub fn set_fullscreen(&self, monitor: Option) { + unsafe { + match &monitor { + &Some(RootMonitorId { ref inner }) => { + let pos = inner.get_position(); + let dim = inner.get_dimensions(); + let window = self.window.clone(); + + let (style, ex_style) = self.set_fullscreen_style(); + + self.events_loop_proxy.execute_in_thread(move |_| { + winuser::SetWindowLongW( + window.0, + winuser::GWL_STYLE, + ((style as DWORD) & !(winuser::WS_CAPTION | winuser::WS_THICKFRAME)) + as LONG, + ); + + winuser::SetWindowLongW( + window.0, + winuser::GWL_EXSTYLE, + ((ex_style as DWORD) + & !(winuser::WS_EX_DLGMODALFRAME | winuser::WS_EX_WINDOWEDGE + | winuser::WS_EX_CLIENTEDGE + | winuser::WS_EX_STATICEDGE)) + as LONG, + ); + + winuser::SetWindowPos( + window.0, + ptr::null_mut(), + pos.0, + pos.1, + dim.0 as i32, + dim.1 as i32, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER + | winuser::SWP_NOACTIVATE + | winuser::SWP_FRAMECHANGED, + ); + + mark_fullscreen(window.0, true); + }); + } + &None => { + self.restore_saved_window(); + } + } + } + + let mut window_state = self.window_state.lock().unwrap(); + window_state.attributes.fullscreen = monitor; } #[inline] - pub fn set_decorations(&self, _decorations: bool) { - unimplemented!() + pub fn set_decorations(&self, decorations: bool) { + if let Ok(mut window_state) = self.window_state.lock() { + if window_state.attributes.decorations == decorations { + return; + } + + let style_flags = (winuser::WS_CAPTION | winuser::WS_THICKFRAME) as LONG; + let ex_style_flags = (winuser::WS_EX_WINDOWEDGE) as LONG; + + // if we are in fullscreen mode, we only change the saved window info + if window_state.attributes.fullscreen.is_some() { + { + let mut saved = window_state.saved_window_info.as_mut().unwrap(); + + unsafe { + unjust_window_rect(&mut saved.rect, saved.style as _, saved.ex_style as _); + } + + if decorations { + saved.style = saved.style | style_flags; + saved.ex_style = saved.ex_style | ex_style_flags; + } else { + saved.style = saved.style & !style_flags; + saved.ex_style = saved.ex_style & !ex_style_flags; + } + + unsafe { + winuser::AdjustWindowRectEx( + &mut saved.rect, + saved.style as _, + 0, + saved.ex_style as _, + ); + } + } + + window_state.attributes.decorations = decorations; + return; + } + + unsafe { + let mut rect: RECT = mem::zeroed(); + winuser::GetWindowRect(self.window.0, &mut rect); + + let mut style = winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE); + let mut ex_style = winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE); + unjust_window_rect(&mut rect, style as _, ex_style as _); + + if decorations { + style = style | style_flags; + ex_style = ex_style | ex_style_flags; + } else { + style = style & !style_flags; + ex_style = ex_style & !ex_style_flags; + } + + let window = self.window.clone(); + + self.events_loop_proxy.execute_in_thread(move |_| { + winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style); + winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style); + winuser::AdjustWindowRectEx(&mut rect, style as _, 0, ex_style as _); + + winuser::SetWindowPos( + window.0, + ptr::null_mut(), + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER + | winuser::SWP_NOACTIVATE + | winuser::SWP_FRAMECHANGED, + ); + }); + } + + window_state.attributes.decorations = decorations; + } } #[inline] pub fn get_current_monitor(&self) -> RootMonitorId { - unimplemented!() + RootMonitorId { + inner: EventsLoop::get_current_monitor(self.window.0), + } } } @@ -359,10 +613,17 @@ impl Drop for Window { /// A simple non-owning wrapper around a window. #[doc(hidden)] +#[derive(Clone)] pub struct WindowWrapper(HWND, HDC); +// Send is not implemented for HWND and HDC, we have to wrap it and implement it manually. +// For more info see: +// https://github.com/retep998/winapi-rs/issues/360 +// https://github.com/retep998/winapi-rs/issues/396 +unsafe impl Send for WindowWrapper {} + unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, - inserter: events_loop::Inserter) -> Result { + inserter: events_loop::Inserter, events_loop_proxy: events_loop::EventsLoopProxy) -> Result { let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter()) .collect::>(); @@ -375,18 +636,8 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild top: 0, bottom: window.dimensions.unwrap_or((1024, 768)).1 as 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 - let fullscreen = if let Some(RootMonitorId { ref inner }) = window.fullscreen { - try!(switch_to_fullscreen(&mut rect, inner)); - true - } else { - false - }; - // computing the style and extended style of the window - let (ex_style, style) = if fullscreen || !window.decorations { + let (ex_style, style) = if !window.decorations { (winuser::WS_EX_APPWINDOW, //winapi::WS_POPUP is incompatible with winapi::WS_CHILD if pl_attribs.parent.is_some() { @@ -406,7 +657,7 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild // creating the real window this time, by using the functions in `extra_functions` let real_window = { - let (width, height) = if fullscreen || window.dimensions.is_some() { + let (width, height) = if window.dimensions.is_some() { let min_dimensions = window.min_dimensions .map(|d| (d.0 as raw::c_int, d.1 as raw::c_int)) .unwrap_or((0, 0)); @@ -422,12 +673,6 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild (None, None) }; - let (x, y) = if fullscreen { - (Some(rect.left), Some(rect.top)) - } else { - (None, None) - }; - let mut style = if !window.visible { style } else { @@ -442,7 +687,7 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild class_name.as_ptr(), title.as_ptr() as LPCWSTR, style | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN, - x.unwrap_or(winuser::CW_USEDEFAULT), y.unwrap_or(winuser::CW_USEDEFAULT), + winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT, width.unwrap_or(winuser::CW_USEDEFAULT), height.unwrap_or(winuser::CW_USEDEFAULT), pl_attribs.parent.unwrap_or(ptr::null_mut()), ptr::null_mut(), libloaderapi::GetModuleHandleW(ptr::null()), @@ -481,17 +726,15 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild } } - // Creating a mutex to track the current window state let window_state = Arc::new(Mutex::new(events_loop::WindowState { cursor: winuser::IDC_ARROW, // use arrow by default cursor_state: CursorState::Normal, attributes: window.clone(), mouse_in_window: false, + saved_window_info: None, })); - inserter.insert(real_window.0, window_state.clone()); - // making the window transparent if window.transparent { let bb = dwmapi::DWM_BLURBEHIND { @@ -503,17 +746,22 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb); } - - // calling SetForegroundWindow if fullscreen - if fullscreen { - winuser::SetForegroundWindow(real_window.0); - } - - // Building the struct. - Ok(Window { + + let win = Window { window: real_window, window_state: window_state, - }) + events_loop_proxy + }; + + win.set_maximized(window.maximized); + if let Some(_) = window.fullscreen { + win.set_fullscreen(window.fullscreen); + force_window_active(win.window.0); + } + + inserter.insert(win.window.0, win.window_state.clone()); + + Ok(win) } unsafe fn register_window_class() -> Vec { @@ -544,33 +792,127 @@ unsafe fn register_window_class() -> Vec { class_name } -unsafe fn switch_to_fullscreen(rect: &mut RECT, monitor: &MonitorId) - -> Result<(), CreationError> -{ - // adjusting the rect - { - let pos = monitor.get_position(); - rect.left += pos.0 as LONG; - rect.right += pos.0 as LONG; - rect.top += pos.1 as LONG; - rect.bottom += pos.1 as LONG; + +struct ComInitialized(*mut ()); +impl Drop for ComInitialized { + fn drop(&mut self) { + unsafe { combaseapi::CoUninitialize() }; } - - // changing device settings - let mut screen_settings: wingdi::DEVMODEW = mem::zeroed(); - screen_settings.dmSize = mem::size_of::() as WORD; - screen_settings.dmPelsWidth = (rect.right - rect.left) as DWORD; - screen_settings.dmPelsHeight = (rect.bottom - rect.top) as DWORD; - screen_settings.dmBitsPerPel = 32; // TODO: ? - screen_settings.dmFields = wingdi::DM_BITSPERPEL | wingdi::DM_PELSWIDTH | wingdi::DM_PELSHEIGHT; - - let result = winuser::ChangeDisplaySettingsExW(monitor.get_adapter_name().as_ptr(), - &mut screen_settings, ptr::null_mut(), - winuser::CDS_FULLSCREEN, ptr::null_mut()); - - if result != winuser::DISP_CHANGE_SUCCESSFUL { - return Err(CreationError::OsError(format!("ChangeDisplaySettings failed: {}", result))); - } - - Ok(()) +} + +thread_local!{ + static COM_INITIALIZED: ComInitialized = { + unsafe { + combaseapi::CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED); + ComInitialized(ptr::null_mut()) + } + }; + + static TASKBAR_LIST: Cell<*mut taskbar::ITaskbarList2> = Cell::new(ptr::null_mut()); +} + +pub fn com_initialized() { + COM_INITIALIZED.with(|_| {}); +} + +// TODO: remove these when they get added to winapi +// https://github.com/retep998/winapi-rs/pull/592 +#[allow(non_upper_case_globals)] +#[allow(non_snake_case)] +#[allow(dead_code)] +mod taskbar { + use super::{IUnknown,IUnknownVtbl,HRESULT, HWND,BOOL}; + DEFINE_GUID!{CLSID_TaskbarList, + 0x56fdf344, 0xfd6d, 0x11d0, 0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90} + + RIDL!(#[uuid(0x56fdf342, 0xfd6d, 0x11d0, 0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90)] + interface ITaskbarList(ITaskbarListVtbl): IUnknown(IUnknownVtbl) { + fn HrInit() -> HRESULT, + fn AddTab( + hwnd: HWND, + ) -> HRESULT, + fn DeleteTab( + hwnd: HWND, + ) -> HRESULT, + fn ActivateTab( + hwnd: HWND, + ) -> HRESULT, + fn SetActiveAlt( + hwnd: HWND, + ) -> HRESULT, + }); + + RIDL!(#[uuid(0x602d4995, 0xb13a, 0x429b, 0xa6, 0x6e, 0x19, 0x35, 0xe4, 0x4f, 0x43, 0x17)] + interface ITaskbarList2(ITaskbarList2Vtbl): ITaskbarList(ITaskbarListVtbl) { + fn MarkFullscreenWindow( + hwnd: HWND, + fFullscreen: BOOL, + ) -> HRESULT, + }); +} + +// Reference Implementation: +// https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc +// +// As per MSDN marking the window as fullscreen should ensure that the +// taskbar is moved to the bottom of the Z-order when the fullscreen window +// is activated. If the window is not fullscreen, the Shell falls back to +// heuristics to determine how the window should be treated, which means +// that it could still consider the window as fullscreen. :( +unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) { + com_initialized(); + + TASKBAR_LIST.with(|task_bar_list_ptr| { + let mut task_bar_list = task_bar_list_ptr.get(); + + if task_bar_list == ptr::null_mut() { + use winapi::shared::winerror::S_OK; + use winapi::Interface; + + let hr = combaseapi::CoCreateInstance( + &taskbar::CLSID_TaskbarList, + ptr::null_mut(), + combaseapi::CLSCTX_ALL, + &taskbar::ITaskbarList2::uuidof(), + &mut task_bar_list as *mut _ as *mut _, + ); + + if hr != S_OK || (*task_bar_list).HrInit() != S_OK { + // In some old windows, the taskbar object could not be created, we just ignore it + return; + } + task_bar_list_ptr.set(task_bar_list) + } + + task_bar_list = task_bar_list_ptr.get(); + (*task_bar_list).MarkFullscreenWindow(handle, if fullscreen { 1 } else { 0 }); + }) +} + +unsafe fn force_window_active(handle: HWND) { + // In some situation, calling SetForegroundWindow could not bring up the window, + // This is a little hack which can "steal" the foreground window permission + // We only call this function in the window creation, so it should be fine. + // See : https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open + let alt_sc = winuser::MapVirtualKeyW(winuser::VK_MENU as _, winuser::MAPVK_VK_TO_VSC); + + let mut inputs: [winuser::INPUT; 2] = mem::zeroed(); + inputs[0].type_ = winuser::INPUT_KEYBOARD; + inputs[0].u.ki_mut().wVk = winuser::VK_LMENU as _; + inputs[0].u.ki_mut().wScan = alt_sc as _; + inputs[0].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY; + + inputs[1].type_ = winuser::INPUT_KEYBOARD; + inputs[1].u.ki_mut().wVk = winuser::VK_LMENU as _; + inputs[1].u.ki_mut().wScan = alt_sc as _; + inputs[1].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY | winuser::KEYEVENTF_KEYUP; + + // Simulate a key press and release + winuser::SendInput( + inputs.len() as _, + inputs.as_mut_ptr(), + mem::size_of::() as _, + ); + + winuser::SetForegroundWindow(handle); }