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
This commit is contained in:
Edwin Cheng 2018-04-13 01:12:15 +08:00 committed by Francesca Frangipane
parent 457c0b7208
commit bdc01fee1a
7 changed files with 536 additions and 100 deletions

View file

@ -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

View file

@ -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]

View file

@ -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);
}
_ => (),
},
_ => (),
},
_ => {}
}

View file

@ -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]

View file

@ -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<SavedWindowInfo>,
}
/// Dummy object that allows inserting a window's state.
@ -227,18 +240,7 @@ impl EventsLoop {
pub(super) fn execute_in_thread<F>(&self, function: F)
where F: FnMut(Inserter) + Send + 'static
{
unsafe {
let boxed = Box::new(function) as Box<FnMut(_)>;
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<F>(&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<FnMut(_)>;
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;

View file

@ -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::<winuser::MONITORINFOEXW>() 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

View file

@ -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<Mutex<events_loop::WindowState>>,
// 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<Window, CreationError>
@ -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::<winuser::WINDOWPLACEMENT>() 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<RootMonitorId>) {
unimplemented!()
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
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<Window, CreationError> {
inserter: events_loop::Inserter, events_loop_proxy: events_loop::EventsLoopProxy) -> Result<Window, CreationError> {
let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter())
.collect::<Vec<_>>();
@ -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<u16> {
@ -544,33 +792,127 @@ unsafe fn register_window_class() -> Vec<u16> {
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::<wingdi::DEVMODEW>() 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::<winuser::INPUT>() as _,
);
winuser::SetForegroundWindow(handle);
}