Add exclusive fullscreen mode (#925)

* Add exclusive fullscreen mode

* Add `WindowExtMacOS::set_fullscreen_presentation_options`

* Capture display for exclusive fullscreen on macOS

* Fix applying video mode on macOS after a fullscreen cycle

* Fix compilation on iOS

* Set monitor appropriately for fullscreen on macOS

* Fix exclusive to borderless fullscreen transitions on macOS

* Fix borderless to exclusive fullscreen transition on macOS

* Sort video modes on Windows

* Fix fullscreen issues on Windows

* Fix video mode changes during exclusive fullscreen on Windows

* Add video mode sorting for macOS and iOS

* Fix monitor `ns_screen` returning `None` after video mode change

* Fix "multithreaded" example on macOS

* Restore video mode upon closing an exclusive fullscreen window

* Fix "multithreaded" example closing multiple windows at once

* Fix compilation on Linux

* Update FEATURES.md

* Don't care about logical monitor groups on X11

* Add exclusive fullscreen for X11

* Update FEATURES.md

* Fix transitions between exclusive and borderless fullscreen on X11

* Update CHANGELOG.md

* Document that Wayland doesn't support exclusive fullscreen

* Replace core-graphics display mode bindings on macOS

* Use `panic!()` instead of `unreachable!()` in "fullscreen" example

* Fix fullscreen "always on top" flag on Windows

* Track current monitor for fullscreen in "multithreaded" example

* Fix exclusive fullscreen sometimes not positioning window properly

* Format

* More formatting and fix CI issues

* Fix formatting

* Fix changelog formatting
This commit is contained in:
Aleksi Juvani 2019-07-29 21:16:14 +03:00 committed by Osspial
parent 131e67ddc1
commit 5bc3cf18d9
31 changed files with 1452 additions and 605 deletions

View file

@ -46,7 +46,7 @@ use crate::{
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId,
},
window::{CursorIcon, Icon, WindowAttributes},
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
};
/// The Win32 implementation of the main `Window` object.
@ -327,7 +327,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::RESIZABLE, resizable)
});
});
@ -421,80 +421,177 @@ impl Window {
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::MAXIMIZED, maximized)
});
});
}
#[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
pub fn fullscreen(&self) -> Option<Fullscreen> {
let window_state = self.window_state.lock();
window_state.fullscreen.clone()
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
unsafe {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
match &monitor {
&Some(RootMonitorHandle { ref inner }) => {
let (x, y): (i32, i32) = inner.position().into();
let (width, height): (u32, u32) = inner.size().into();
let mut window_state_lock = window_state.lock();
let old_fullscreen = window_state_lock.fullscreen.clone();
if window_state_lock.fullscreen == fullscreen {
return;
}
window_state_lock.fullscreen = fullscreen.clone();
drop(window_state_lock);
let mut monitor = monitor.clone();
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
let client_rect =
util::get_client_rect(window.0).expect("get client rect failed!");
window_state_lock.saved_window = Some(SavedWindow {
client_rect,
dpi_factor: window_state_lock.dpi_factor,
});
window_state_lock.fullscreen = monitor.take();
WindowState::refresh_window_state(
window_state_lock,
window.0,
Some(RECT {
left: x,
top: y,
right: x + width as c_int,
bottom: y + height as c_int,
}),
);
mark_fullscreen(window.0, true);
// Save window bounds before entering fullscreen
match (&old_fullscreen, &fullscreen) {
(&None, &Some(_)) => {
let client_rect = util::get_client_rect(window.0).unwrap();
window_state_lock.saved_window = Some(SavedWindow {
client_rect,
dpi_factor: window_state_lock.dpi_factor,
});
}
&None => {
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
window_state_lock.fullscreen = None;
_ => (),
}
if let Some(SavedWindow {
client_rect,
dpi_factor,
}) = window_state_lock.saved_window
{
window_state_lock.dpi_factor = dpi_factor;
window_state_lock.saved_window = None;
// Change video mode if we're transitioning to or from exclusive
// fullscreen
match (&old_fullscreen, &fullscreen) {
(&None, &Some(Fullscreen::Exclusive(ref video_mode)))
| (
&Some(Fullscreen::Borderless(_)),
&Some(Fullscreen::Exclusive(ref video_mode)),
)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) =>
{
let monitor = video_mode.monitor();
WindowState::refresh_window_state(
window_state_lock,
let mut display_name = OsStr::new(&monitor.inner.native_identifier())
.encode_wide()
.collect::<Vec<_>>();
// `encode_wide` does not add a null-terminator but
// `ChangeDisplaySettingsExW` requires a null-terminated
// string, so add it
display_name.push(0);
let mut native_video_mode = video_mode.video_mode.native_video_mode.clone();
let res = unsafe {
winuser::ChangeDisplaySettingsExW(
display_name.as_ptr(),
&mut native_video_mode,
std::ptr::null_mut(),
winuser::CDS_FULLSCREEN,
std::ptr::null_mut(),
)
};
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
}
(&Some(Fullscreen::Exclusive(_)), &None)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
let res = unsafe {
winuser::ChangeDisplaySettingsExW(
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
winuser::CDS_FULLSCREEN,
std::ptr::null_mut(),
)
};
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
}
_ => (),
}
unsafe {
// There are some scenarios where calling `ChangeDisplaySettingsExW` takes long
// enough to execute that the DWM thinks our program has frozen and takes over
// our program's window. When that happens, the `SetWindowPos` call below gets
// eaten and the window doesn't get set to the proper fullscreen position.
//
// Calling `PeekMessageW` here notifies Windows that our process is still running
// fine, taking control back from the DWM and ensuring that the `SetWindowPos` call
// below goes through.
let mut msg = mem::zeroed();
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0);
}
// Update window style
WindowState::set_window_flags(window_state_lock, window.0, |f| {
f.set(WindowFlags::MARKER_FULLSCREEN, fullscreen.is_some())
});
// Update window bounds
match &fullscreen {
Some(fullscreen) => {
let monitor = match fullscreen {
Fullscreen::Exclusive(ref video_mode) => video_mode.monitor(),
Fullscreen::Borderless(ref monitor) => monitor.clone(),
};
let position: (i32, i32) = monitor.position().into();
let size: (u32, u32) = monitor.size().into();
unsafe {
winuser::SetWindowPos(
window.0,
ptr::null_mut(),
position.0,
position.1,
size.0 as i32,
size.1 as i32,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
);
winuser::UpdateWindow(window.0);
}
}
None => {
let mut window_state_lock = window_state.lock();
if let Some(SavedWindow {
client_rect,
dpi_factor,
}) = window_state_lock.saved_window.take()
{
window_state_lock.dpi_factor = dpi_factor;
drop(window_state_lock);
unsafe {
winuser::SetWindowPos(
window.0,
Some(client_rect),
ptr::null_mut(),
client_rect.left,
client_rect.top,
client_rect.right - client_rect.left,
client_rect.bottom - client_rect.top,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
);
winuser::UpdateWindow(window.0);
}
mark_fullscreen(window.0, false);
});
}
}
}
}
unsafe {
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
}
});
}
#[inline]
@ -503,8 +600,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
WindowState::set_window_flags(window_state.lock(), window.0, Some(client_rect), |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::DECORATIONS, decorations)
});
});
@ -516,7 +612,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top)
});
});
@ -769,9 +865,7 @@ unsafe fn init<T: 'static>(
let window_state = {
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
let window_state = Arc::new(Mutex::new(window_state));
WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| {
*f = window_flags
});
WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);
window_state
};
@ -865,7 +959,7 @@ pub fn com_initialized() {
// 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) {
unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) {
com_initialized();
TASKBAR_LIST.with(|task_bar_list_ptr| {