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

@ -1,10 +1,16 @@
# Unreleased # Unreleased
- On macOS, drop the run closure on exit. - On macOS, drop the run closure on exit.
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
- On X11, fix delayed events after window redraw. - On X11, fix delayed events after window redraw.
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface. - On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
- On Windows, screen saver won't start if the window is in fullscreen mode. - On Windows, screen saver won't start if the window is in fullscreen mode.
- Change all occurrences of the `new_user_event` method to `with_user_event`. - Change all occurrences of the `new_user_event` method to `with_user_event`.
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
consists of `Fullscreen::Exclusive(VideoMode)` and
`Fullscreen::Borderless(MonitorHandle)` variants.
- Adds support for exclusive fullscreen mode.
# 0.20.0 Alpha 2 (2019-07-09) # 0.20.0 Alpha 2 (2019-07-09)

View file

@ -84,6 +84,9 @@ If your PR makes notable changes to Winit's features, please update this section
- **Fullscreen**: The windows created by winit can be put into fullscreen mode. - **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after - **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation. creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows, and if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content. - **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent - **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
@ -157,6 +160,7 @@ Legend:
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |❌ |❌ |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |

View file

@ -1,56 +1,35 @@
use std::io::{self, Write}; use std::io::{stdin, stdout, Write};
use winit::{ use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, use winit::event_loop::{ControlFlow, EventLoop};
event_loop::{ControlFlow, EventLoop}, use winit::monitor::{MonitorHandle, VideoMode};
monitor::MonitorHandle, use winit::window::{Fullscreen, WindowBuilder};
window::WindowBuilder,
};
fn main() { fn main() {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
#[cfg(target_os = "macos")] print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
let mut macos_use_simple_fullscreen = false; stdout().flush().unwrap();
let monitor = { let mut num = String::new();
// On macOS there are two fullscreen modes "native" and "simple" stdin().read_line(&mut num).unwrap();
#[cfg(target_os = "macos")] let num = num.trim().parse().ok().expect("Please enter a number");
{
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
io::stdout().flush().unwrap();
let mut num = String::new(); let fullscreen = Some(match num {
io::stdin().read_line(&mut num).unwrap(); 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
let num = num.trim().parse().ok().expect("Please enter a number"); 2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
match num { _ => panic!("Please enter a valid number"),
2 => macos_use_simple_fullscreen = true, });
_ => {}
}
// Prompt for monitor when using native fullscreen
if !macos_use_simple_fullscreen {
Some(prompt_for_monitor(&event_loop))
} else {
None
}
}
#[cfg(not(target_os = "macos"))]
Some(prompt_for_monitor(&event_loop))
};
let mut is_fullscreen = monitor.is_some();
let mut is_maximized = false; let mut is_maximized = false;
let mut decorations = true; let mut decorations = true;
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_title("Hello world!") .with_title("Hello world!")
.with_fullscreen(monitor) .with_fullscreen(fullscreen.clone())
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
println!("{:?}", event);
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;
match event { match event {
@ -67,35 +46,14 @@ fn main() {
} => match (virtual_code, state) { } => match (virtual_code, state) {
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit, (VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
(VirtualKeyCode::F, ElementState::Pressed) => { (VirtualKeyCode::F, ElementState::Pressed) => {
#[cfg(target_os = "macos")] if window.fullscreen().is_some() {
{
if macos_use_simple_fullscreen {
use winit::platform::macos::WindowExtMacOS;
if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) {
is_fullscreen = !is_fullscreen;
}
return;
}
}
is_fullscreen = !is_fullscreen;
if !is_fullscreen {
window.set_fullscreen(None); window.set_fullscreen(None);
} else { } else {
window.set_fullscreen(Some(window.current_monitor())); window.set_fullscreen(fullscreen.clone());
} }
} }
(VirtualKeyCode::S, ElementState::Pressed) => { (VirtualKeyCode::S, ElementState::Pressed) => {
println!("window.fullscreen {:?}", window.fullscreen()); println!("window.fullscreen {:?}", window.fullscreen());
#[cfg(target_os = "macos")]
{
use winit::platform::macos::WindowExtMacOS;
println!(
"window.simple_fullscreen {:?}",
WindowExtMacOS::simple_fullscreen(&window)
);
}
} }
(VirtualKeyCode::M, ElementState::Pressed) => { (VirtualKeyCode::M, ElementState::Pressed) => {
is_maximized = !is_maximized; is_maximized = !is_maximized;
@ -121,10 +79,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
} }
print!("Please write the number of the monitor to use: "); print!("Please write the number of the monitor to use: ");
io::stdout().flush().unwrap(); stdout().flush().unwrap();
let mut num = String::new(); let mut num = String::new();
io::stdin().read_line(&mut num).unwrap(); stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number"); let num = num.trim().parse().ok().expect("Please enter a number");
let monitor = event_loop let monitor = event_loop
.available_monitors() .available_monitors()
@ -135,3 +93,24 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
monitor monitor
} }
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
for (i, video_mode) in monitor.video_modes().enumerate() {
println!("Video mode #{}: {}", i, video_mode);
}
print!("Please write the number of the video mode to use: ");
stdout().flush().unwrap();
let mut num = String::new();
stdin().read_line(&mut num).unwrap();
let num = num.trim().parse().ok().expect("Please enter a number");
let video_mode = monitor
.video_modes()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {}", video_mode);
video_mode
}

View file

@ -4,7 +4,7 @@ use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use winit::{ use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, WindowBuilder}, window::{CursorIcon, Fullscreen, WindowBuilder},
}; };
const WINDOW_COUNT: usize = 3; const WINDOW_COUNT: usize = 3;
@ -19,11 +19,34 @@ fn main() {
.with_inner_size(WINDOW_SIZE.into()) .with_inner_size(WINDOW_SIZE.into())
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
let mut video_mode_id = 0usize;
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
window_senders.insert(window.id(), tx); window_senders.insert(window.id(), tx);
thread::spawn(move || { thread::spawn(move || {
while let Ok(event) = rx.recv() { while let Ok(event) = rx.recv() {
match event { match event {
WindowEvent::Moved { .. } => {
// We need to update our chosen video mode if the window
// was moved to an another monitor, so that the window
// appears on this monitor instead when we go fullscreen
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
video_modes = window.current_monitor().video_modes().collect();
video_mode_id = video_mode_id.min(video_modes.len());
let video_mode = video_modes.iter().nth(video_mode_id);
// Different monitors may support different video modes,
// and the index we chose previously may now point to a
// completely different video mode, so notify the user
if video_mode != previous_video_mode.as_ref() {
println!(
"Window moved to another monitor, picked video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
}
}
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: input:
KeyboardInput { KeyboardInput {
@ -44,9 +67,26 @@ fn main() {
false => CursorIcon::Default, false => CursorIcon::Default,
}), }),
D => window.set_decorations(!state), D => window.set_decorations(!state),
F => window.set_fullscreen(match state { // Cycle through video modes
true => Some(window.current_monitor()), Right | Left => {
false => None, video_mode_id = match key {
Left => video_mode_id.saturating_sub(1),
Right => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
println!(
"Picking video mode: {}",
video_modes.iter().nth(video_mode_id).unwrap()
);
}
F => window.set_fullscreen(match (state, modifiers.alt) {
(true, false) => {
Some(Fullscreen::Borderless(window.current_monitor()))
}
(true, true) => Some(Fullscreen::Exclusive(
video_modes.iter().nth(video_mode_id).unwrap().clone(),
)),
(false, _) => None,
}), }),
G => window.set_cursor_grab(state).unwrap(), G => window.set_cursor_grab(state).unwrap(),
H => window.set_cursor_visible(!state), H => window.set_cursor_visible(!state),
@ -56,6 +96,7 @@ fn main() {
println!("-> inner_position : {:?}", window.inner_position()); println!("-> inner_position : {:?}", window.inner_position());
println!("-> outer_size : {:?}", window.outer_size()); println!("-> outer_size : {:?}", window.outer_size());
println!("-> inner_size : {:?}", window.inner_size()); println!("-> inner_size : {:?}", window.inner_size());
println!("-> fullscreen : {:?}", window.fullscreen());
} }
L => window.set_min_inner_size(match state { L => window.set_min_inner_size(match state {
true => Some(WINDOW_SIZE.into()), true => Some(WINDOW_SIZE.into()),
@ -108,6 +149,7 @@ fn main() {
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: input:
KeyboardInput { KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::Escape), virtual_keycode: Some(VirtualKeyCode::Escape),
.. ..
}, },

View file

@ -7,6 +7,6 @@ fn main() {
println!("Listing available video modes:"); println!("Listing available video modes:");
for mode in monitor.video_modes() { for mode in monitor.video_modes() {
println!("{:?}", mode); println!("{}", mode);
} }
} }

View file

@ -121,7 +121,6 @@ extern crate log;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
#[cfg(target_os = "windows")]
extern crate derivative; extern crate derivative;
#[macro_use] #[macro_use]
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]

View file

@ -52,17 +52,41 @@ impl Iterator for AvailableMonitorsIter {
/// - [`MonitorHandle::video_modes`][monitor_get]. /// - [`MonitorHandle::video_modes`][monitor_get].
/// ///
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes /// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Derivative)]
#[derivative(Clone, Debug = "transparent", PartialEq, Eq, Hash)]
pub struct VideoMode { pub struct VideoMode {
pub(crate) size: (u32, u32), pub(crate) video_mode: platform_impl::VideoMode,
pub(crate) bit_depth: u16, }
pub(crate) refresh_rate: u16,
impl PartialOrd for VideoMode {
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VideoMode {
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
// to `u32` there
let size: (u32, u32) = self.size().into();
let other_size: (u32, u32) = other.size().into();
self.monitor().cmp(&other.monitor()).then(
size.cmp(&other_size)
.then(
self.refresh_rate()
.cmp(&other.refresh_rate())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
)
}
} }
impl VideoMode { impl VideoMode {
/// Returns the resolution of this video mode. /// Returns the resolution of this video mode.
#[inline]
pub fn size(&self) -> PhysicalSize { pub fn size(&self) -> PhysicalSize {
self.size.into() self.video_mode.size()
} }
/// Returns the bit depth of this video mode, as in how many bits you have /// Returns the bit depth of this video mode, as in how many bits you have
@ -73,15 +97,37 @@ impl VideoMode {
/// ///
/// - **Wayland:** Always returns 32. /// - **Wayland:** Always returns 32.
/// - **iOS:** Always returns 32. /// - **iOS:** Always returns 32.
#[inline]
pub fn bit_depth(&self) -> u16 { pub fn bit_depth(&self) -> u16 {
self.bit_depth self.video_mode.bit_depth()
} }
/// Returns the refresh rate of this video mode. **Note**: the returned /// Returns the refresh rate of this video mode. **Note**: the returned
/// refresh rate is an integer approximation, and you shouldn't rely on this /// refresh rate is an integer approximation, and you shouldn't rely on this
/// value to be exact. /// value to be exact.
#[inline]
pub fn refresh_rate(&self) -> u16 { pub fn refresh_rate(&self) -> u16 {
self.refresh_rate self.video_mode.refresh_rate()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
self.video_mode.monitor()
}
}
impl std::fmt::Display for VideoMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} @ {} Hz ({} bpp)",
self.size().width,
self.size().height,
self.refresh_rate(),
self.bit_depth()
)
} }
} }
@ -90,7 +136,7 @@ impl VideoMode {
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
/// ///
/// [`Window`]: ../window/struct.Window.html /// [`Window`]: ../window/struct.Window.html
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MonitorHandle { pub struct MonitorHandle {
pub(crate) inner: platform_impl::MonitorHandle, pub(crate) inner: platform_impl::MonitorHandle,
} }

View file

@ -79,7 +79,7 @@ use std::fmt;
pub use self::{ pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
}; };

View file

@ -1,18 +1,44 @@
use std::{ use std::{
collections::{HashSet, VecDeque}, collections::{BTreeSet, VecDeque},
fmt, fmt,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
}; };
use crate::platform_impl::platform::ffi::{ #[derive(Debug, Clone, PartialEq, Eq, Hash)]
id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger, pub struct VideoMode {
}; pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Inner { pub struct Inner {
uiscreen: id, uiscreen: id,
} }
@ -25,6 +51,7 @@ impl Drop for Inner {
} }
} }
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle { pub struct MonitorHandle {
inner: Inner, inner: Inner,
} }
@ -140,21 +167,24 @@ impl Inner {
} }
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] }; let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] }; let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] }; let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };
let mut modes = HashSet::with_capacity(available_mode_count); let mut modes = BTreeSet::new();
for i in 0..available_mode_count { for i in 0..available_mode_count {
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] }; let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] };
let size: CGSize = unsafe { msg_send![mode, size] }; let size: CGSize = unsafe { msg_send![mode, size] };
modes.insert(VideoMode { modes.insert(RootVideoMode {
size: (size.width as u32, size.height as u32), video_mode: VideoMode {
bit_depth: 32, size: (size.width as u32, size.height as u32),
refresh_rate: refresh_rate as u16, bit_depth: 32,
refresh_rate: refresh_rate as u16,
monitor: MonitorHandle::retained_new(self.uiscreen),
},
}); });
} }

View file

@ -8,15 +8,14 @@ use objc::{
use crate::{ use crate::{
event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent}, event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent},
platform::ios::MonitorHandleExtIOS, platform::ios::MonitorHandleExtIOS,
window::{WindowAttributes, WindowId as RootWindowId}, platform_impl::platform::{
}; app_state::AppState,
event_loop,
use crate::platform_impl::platform::{ ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase},
app_state::AppState, window::PlatformSpecificWindowBuilderAttributes,
event_loop, DeviceId,
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, },
window::PlatformSpecificWindowBuilderAttributes, window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
DeviceId,
}; };
// requires main thread // requires main thread
@ -366,8 +365,12 @@ pub unsafe fn create_window(
if let Some(hidpi_factor) = platform_attributes.hidpi_factor { if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat];
} }
if let &Some(ref monitor) = &window_attributes.fullscreen { match window_attributes.fullscreen {
let () = msg_send![window, setScreen:monitor.ui_screen()]; Some(Fullscreen::Exclusive(_)) => unimplemented!(),
Some(Fullscreen::Borderless(ref monitor)) => {
msg_send![window, setScreen:monitor.ui_screen()]
}
None => (),
} }
window window

View file

@ -17,7 +17,7 @@ use crate::{
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask}, ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask},
monitor, view, EventLoopWindowTarget, MonitorHandle, monitor, view, EventLoopWindowTarget, MonitorHandle,
}, },
window::{CursorIcon, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes},
}; };
pub struct Inner { pub struct Inner {
@ -157,10 +157,11 @@ impl Inner {
warn!("`Window::set_maximized` is ignored on iOS") warn!("`Window::set_maximized` is ignored on iOS")
} }
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe { unsafe {
match monitor { match monitor {
Some(monitor) => { Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO
Some(Fullscreen::Borderless(monitor)) => {
let uiscreen = monitor.ui_screen() as id; let uiscreen = monitor.ui_screen() as id;
let current: id = msg_send![self.window, screen]; let current: id = msg_send![self.window, screen];
let bounds: CGRect = msg_send![uiscreen, bounds]; let bounds: CGRect = msg_send![uiscreen, bounds];
@ -176,7 +177,7 @@ impl Inner {
} }
} }
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
unsafe { unsafe {
let monitor = self.current_monitor(); let monitor = self.current_monitor();
let uiscreen = monitor.inner.ui_screen(); let uiscreen = monitor.inner.ui_screen();
@ -189,7 +190,7 @@ impl Inner {
&& screen_space_bounds.size.width == screen_bounds.size.width && screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height && screen_space_bounds.size.height == screen_bounds.size.height
{ {
Some(monitor) Some(Fullscreen::Borderless(monitor))
} else { } else {
None None
} }
@ -293,11 +294,12 @@ impl Window {
// TODO: transparency, visible // TODO: transparency, visible
unsafe { unsafe {
let screen = window_attributes let screen = match window_attributes.fullscreen {
.fullscreen Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO: do we set the frame to video mode bounds instead of screen bounds?
.as_ref() Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
.map(|screen| screen.ui_screen() as _) None => monitor::main_uiscreen().ui_screen(),
.unwrap_or_else(|| monitor::main_uiscreen().ui_screen()); };
let screen_bounds: CGRect = msg_send![screen, bounds]; let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.inner_size { let frame = match window_attributes.inner_size {

View file

@ -13,8 +13,8 @@ use crate::{
event::Event, event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
icon::Icon, icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorIcon, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes},
}; };
mod dlopen; mod dlopen;
@ -92,7 +92,7 @@ impl DeviceId {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum MonitorHandle { pub enum MonitorHandle {
X(x11::MonitorHandle), X(x11::MonitorHandle),
Wayland(wayland::MonitorHandle), Wayland(wayland::MonitorHandle),
@ -140,7 +140,7 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> { pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
match self { match self {
MonitorHandle::X(m) => Box::new(m.video_modes()), MonitorHandle::X(m) => Box::new(m.video_modes()),
MonitorHandle::Wayland(m) => Box::new(m.video_modes()), MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
@ -148,6 +148,46 @@ impl MonitorHandle {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum VideoMode {
X(x11::VideoMode),
Wayland(wayland::VideoMode),
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
match self {
&VideoMode::X(ref m) => m.size(),
&VideoMode::Wayland(ref m) => m.size(),
}
}
#[inline]
pub fn bit_depth(&self) -> u16 {
match self {
&VideoMode::X(ref m) => m.bit_depth(),
&VideoMode::Wayland(ref m) => m.bit_depth(),
}
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
match self {
&VideoMode::X(ref m) => m.refresh_rate(),
&VideoMode::Wayland(ref m) => m.refresh_rate(),
}
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
match self {
&VideoMode::X(ref m) => m.monitor(),
&VideoMode::Wayland(ref m) => m.monitor(),
}
}
}
impl Window { impl Window {
#[inline] #[inline]
pub fn new<T>( pub fn new<T>(
@ -310,17 +350,15 @@ impl Window {
} }
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
match self { match self {
&Window::X(ref w) => w.fullscreen(), &Window::X(ref w) => w.fullscreen(),
&Window::Wayland(ref w) => w.fullscreen().map(|monitor_id| RootMonitorHandle { &Window::Wayland(ref w) => w.fullscreen(),
inner: MonitorHandle::Wayland(monitor_id),
}),
} }
} }
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
match self { match self {
&Window::X(ref w) => w.set_fullscreen(monitor), &Window::X(ref w) => w.set_fullscreen(monitor),
&Window::Wayland(ref w) => w.set_fullscreen(monitor), &Window::Wayland(ref w) => w.set_fullscreen(monitor),

View file

@ -16,8 +16,11 @@ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
event::ModifiersState, event::ModifiersState,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
monitor::VideoMode, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::sticky_exit_callback, platform_impl::platform::{
sticky_exit_callback, MonitorHandle as PlatformMonitorHandle,
VideoMode as PlatformVideoMode,
},
}; };
use super::{window::WindowStore, DeviceId, WindowId}; use super::{window::WindowStore, DeviceId, WindowId};
@ -603,17 +606,67 @@ impl<T> Drop for SeatData<T> {
* Monitor stuff * Monitor stuff
*/ */
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
}
}
}
#[derive(Clone)]
pub struct MonitorHandle { pub struct MonitorHandle {
pub(crate) proxy: wl_output::WlOutput, pub(crate) proxy: wl_output::WlOutput,
pub(crate) mgr: OutputMgr, pub(crate) mgr: OutputMgr,
} }
impl Clone for MonitorHandle { impl PartialEq for MonitorHandle {
fn clone(&self) -> MonitorHandle { fn eq(&self, other: &Self) -> bool {
MonitorHandle { self.native_identifier() == other.native_identifier()
proxy: self.proxy.clone(), }
mgr: self.mgr.clone(), }
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.native_identifier().cmp(&other.native_identifier())
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.native_identifier().hash(state);
} }
} }
@ -680,15 +733,20 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let monitor = self.clone();
self.mgr self.mgr
.with_info(&self.proxy, |_, info| info.modes.clone()) .with_info(&self.proxy, |_, info| info.modes.clone())
.unwrap_or(vec![]) .unwrap_or(vec![])
.into_iter() .into_iter()
.map(|x| VideoMode { .map(move |x| RootVideoMode {
size: (x.dimensions.0 as u32, x.dimensions.1 as u32), video_mode: PlatformVideoMode::Wayland(VideoMode {
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16, size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
bit_depth: 32, refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: 32,
monitor: monitor.clone(),
}),
}) })
} }
} }

View file

@ -3,7 +3,8 @@
pub use self::{ pub use self::{
event_loop::{ event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink, EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode,
WindowEventsSink,
}, },
window::Window, window::Window,
}; };

View file

@ -8,10 +8,11 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle, monitor::MonitorHandle as RootMonitorHandle,
platform_impl::{ platform_impl::{
platform::wayland::event_loop::{available_monitors, primary_monitor},
MonitorHandle as PlatformMonitorHandle, MonitorHandle as PlatformMonitorHandle,
PlatformSpecificWindowBuilderAttributes as PlAttributes, PlatformSpecificWindowBuilderAttributes as PlAttributes,
}, },
window::{CursorIcon, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes},
}; };
use smithay_client_toolkit::{ use smithay_client_toolkit::{
@ -25,7 +26,6 @@ use smithay_client_toolkit::{
}; };
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId}; use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor};
pub struct Window { pub struct Window {
surface: wl_surface::WlSurface, surface: wl_surface::WlSurface,
@ -108,13 +108,19 @@ impl Window {
} }
// Check for fullscreen requirements // Check for fullscreen requirements
if let Some(RootMonitorHandle { match attributes.fullscreen {
inner: PlatformMonitorHandle::Wayland(ref monitor_id), Some(Fullscreen::Exclusive(_)) => {
}) = attributes.fullscreen panic!("Wayland doesn't support exclusive fullscreen")
{ }
frame.set_fullscreen(Some(&monitor_id.proxy)); Some(Fullscreen::Borderless(RootMonitorHandle {
} else if attributes.maximized { inner: PlatformMonitorHandle::Wayland(ref monitor_id),
frame.set_maximized(); })) => frame.set_fullscreen(Some(&monitor_id.proxy)),
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => {
if attributes.maximized {
frame.set_maximized();
}
}
} }
frame.set_resizable(attributes.resizable); frame.set_resizable(attributes.resizable);
@ -252,25 +258,31 @@ impl Window {
} }
} }
pub fn fullscreen(&self) -> Option<MonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
if *(self.fullscreen.lock().unwrap()) { if *(self.fullscreen.lock().unwrap()) {
Some(self.current_monitor()) Some(Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
}))
} else { } else {
None None
} }
} }
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
if let Some(RootMonitorHandle { match fullscreen {
inner: PlatformMonitorHandle::Wayland(ref monitor_id), Some(Fullscreen::Exclusive(_)) => {
}) = monitor panic!("Wayland doesn't support exclusive fullscreen")
{ }
self.frame Some(Fullscreen::Borderless(RootMonitorHandle {
.lock() inner: PlatformMonitorHandle::Wayland(ref monitor_id),
.unwrap() })) => {
.set_fullscreen(Some(&monitor_id.proxy)); self.frame
} else { .lock()
self.frame.lock().unwrap().unset_fullscreen(); .unwrap()
.set_fullscreen(Some(&monitor_id.proxy));
}
Some(Fullscreen::Borderless(_)) => unreachable!(),
None => self.frame.lock().unwrap().unset_fullscreen(),
} }
} }

View file

@ -11,7 +11,7 @@ mod window;
mod xdisplay; mod xdisplay;
pub use self::{ pub use self::{
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::UnownedWindow, window::UnownedWindow,
xdisplay::{XConnection, XError, XNotSupported}, xdisplay::{XConnection, XError, XNotSupported},
}; };

View file

@ -4,47 +4,66 @@ use parking_lot::Mutex;
use super::{ use super::{
ffi::{ ffi::{
RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, Window, RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
XRRScreenResources, RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
}, },
util, XConnection, XError, util, XConnection, XError,
}; };
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode},
}; };
// Used to test XRandR < 1.5 code path. This should always be committed as false. // Used for testing. This should always be committed as false.
const FORCE_RANDR_COMPAT: bool = false;
// Also used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false; const DISABLE_MONITOR_LIST_CACHING: bool = false;
lazy_static! { lazy_static! {
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = Mutex::default();
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default(); static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
} }
fn version_is_at_least(major: c_int, minor: c_int) -> bool {
if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() {
if avail_major == major {
avail_minor >= minor
} else {
avail_major > major
}
} else {
unreachable!();
}
}
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> { pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
// We update this lazily. // We update this lazily.
(*MONITORS.lock()).take() (*MONITORS.lock()).take()
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) native_mode: RRMode,
pub(crate) monitor: Option<MonitorHandle>,
}
impl VideoMode {
#[inline]
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
#[inline]
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
#[inline]
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
#[inline]
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: PlatformMonitorHandle::X(self.monitor.clone().unwrap()),
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MonitorHandle { pub struct MonitorHandle {
/// The actual id /// The actual id
id: u32, pub(crate) id: RRCrtc,
/// The name of the monitor /// The name of the monitor
pub(crate) name: String, pub(crate) name: String,
/// The size of the monitor /// The size of the monitor
@ -61,16 +80,43 @@ pub struct MonitorHandle {
video_modes: Vec<VideoMode>, video_modes: Vec<VideoMode>,
} }
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.id.cmp(&other.id)
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl MonitorHandle { impl MonitorHandle {
fn from_repr( fn new(
xconn: &XConnection, xconn: &XConnection,
resources: *mut XRRScreenResources, resources: *mut XRRScreenResources,
id: u32, id: RRCrtc,
repr: util::MonitorRepr, crtc: *mut XRRCrtcInfo,
primary: bool, primary: bool,
) -> Option<Self> { ) -> Option<Self> {
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? }; let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
let (dimensions, position) = unsafe { (repr.size(), repr.position()) }; let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
let rect = util::AaRect::new(position, dimensions); let rect = util::AaRect::new(position, dimensions);
Some(MonitorHandle { Some(MonitorHandle {
id, id,
@ -107,8 +153,14 @@ impl MonitorHandle {
} }
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
self.video_modes.clone().into_iter() let monitor = self.clone();
self.video_modes.clone().into_iter().map(move |mut x| {
x.monitor = Some(monitor.clone());
RootVideoMode {
video_mode: PlatformVideoMode::X(x),
}
})
} }
} }
@ -139,8 +191,12 @@ impl XConnection {
fn query_monitor_list(&self) -> Vec<MonitorHandle> { fn query_monitor_list(&self) -> Vec<MonitorHandle> {
unsafe { unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display); let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if version_is_at_least(1, 3) { let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else { } else {
// WARNING: this function is supposedly very slow, on the order of hundreds of ms. // WARNING: this function is supposedly very slow, on the order of hundreds of ms.
@ -155,48 +211,19 @@ impl XConnection {
let mut available; let mut available;
let mut has_primary = false; let mut has_primary = false;
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT { let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and available = Vec::with_capacity((*resources).ncrtc as usize);
// videowalls. for crtc_index in 0..(*resources).ncrtc {
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap(); let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let mut monitor_count = 0; let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let monitors = let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
(xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count); if is_active {
assert!(monitor_count >= 0); let is_primary = *(*crtc).outputs.offset(0) == primary;
available = Vec::with_capacity(monitor_count as usize);
for monitor_index in 0..monitor_count {
let monitor = monitors.offset(monitor_index as isize);
let is_primary = (*monitor).primary != 0;
has_primary |= is_primary; has_primary |= is_primary;
MonitorHandle::from_repr( MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
self, .map(|monitor_id| available.push(monitor_id));
resources,
monitor_index as u32,
monitor.into(),
is_primary,
)
.map(|monitor_id| available.push(monitor_id));
}
(xrandr_1_5.XRRFreeMonitors)(monitors);
} else {
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
// videowall setups will also show monitors that aren't in the logical groups the user
// cares about.
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
if is_active {
let crtc = util::MonitorRepr::from(crtc);
let is_primary = crtc.get_output() == primary;
has_primary |= is_primary;
MonitorHandle::from_repr(self, resources, crtc_id as u32, crtc, is_primary)
.map(|monitor_id| available.push(monitor_id));
}
(self.xrandr.XRRFreeCrtcInfo)(crtc);
} }
(self.xrandr.XRRFreeCrtcInfo)(crtc);
} }
// If no monitors were detected as being primary, we just pick one ourselves! // If no monitors were detected as being primary, we just pick one ourselves!
@ -236,19 +263,15 @@ impl XConnection {
} }
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> { pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
{ let has_xrandr = unsafe {
let mut version_lock = XRANDR_VERSION.lock(); let mut major = 0;
if version_lock.is_none() { let mut minor = 0;
let mut major = 0; (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
let mut minor = 0; };
let has_extension = assert!(
unsafe { (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) }; has_xrandr == True,
if has_extension != True { "[winit] XRandR extension not available."
panic!("[winit] XRandR extension not available."); );
}
*version_lock = Some((major, minor));
}
}
let mut event_offset = 0; let mut event_offset = 0;
let mut error_offset = 0; let mut error_offset = 0;

View file

@ -1,7 +1,10 @@
use std::{env, slice, str::FromStr}; use std::{env, slice, str::FromStr};
use super::*; use super::{
use crate::{dpi::validate_hidpi_factor, monitor::VideoMode}; ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
*,
};
use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode};
pub fn calc_dpi_factor( pub fn calc_dpi_factor(
(width_px, height_px): (u32, u32), (width_px, height_px): (u32, u32),
@ -34,47 +37,6 @@ pub fn calc_dpi_factor(
dpi_factor dpi_factor
} }
pub enum MonitorRepr {
Monitor(*mut ffi::XRRMonitorInfo),
Crtc(*mut ffi::XRRCrtcInfo),
}
impl MonitorRepr {
pub unsafe fn get_output(&self) -> ffi::RROutput {
match *self {
// Same member names, but different locations within the struct...
MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)),
MonitorRepr::Crtc(crtc) => *((*crtc).outputs.offset(0)),
}
}
pub unsafe fn size(&self) -> (u32, u32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32),
MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32),
}
}
pub unsafe fn position(&self) -> (i32, i32) {
match *self {
MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32),
MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32),
}
}
}
impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr {
fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self {
MonitorRepr::Monitor(monitor)
}
}
impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self {
MonitorRepr::Crtc(crtc)
}
}
impl XConnection { impl XConnection {
// Retrieve DPI from Xft.dpi property // Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> { pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
@ -96,11 +58,11 @@ impl XConnection {
} }
pub unsafe fn get_output_info( pub unsafe fn get_output_info(
&self, &self,
resources: *mut ffi::XRRScreenResources, resources: *mut XRRScreenResources,
repr: &MonitorRepr, crtc: *mut XRRCrtcInfo,
) -> Option<(String, f64, Vec<VideoMode>)> { ) -> Option<(String, f64, Vec<VideoMode>)> {
let output_info = let output_info =
(self.xrandr.XRRGetOutputInfo)(self.display, resources, repr.get_output()); (self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0));
if output_info.is_null() { if output_info.is_null() {
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display) // When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
// it's possible for it to return null. // it's possible for it to return null.
@ -132,6 +94,10 @@ impl XConnection {
size: (x.width, x.height), size: (x.width, x.height),
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16, refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: bit_depth as u16, bit_depth: bit_depth as u16,
native_mode: x.id,
// This is populated in `MonitorHandle::video_modes` as the
// video mode is returned to the user
monitor: None,
} }
}); });
@ -144,7 +110,7 @@ impl XConnection {
dpi / 96. dpi / 96.
} else { } else {
calc_dpi_factor( calc_dpi_factor(
repr.size(), ((*crtc).width as u32, (*crtc).height as u32),
( (
(*output_info).mm_width as u64, (*output_info).mm_width as u64,
(*output_info).mm_height as u64, (*output_info).mm_height as u64,
@ -155,4 +121,61 @@ impl XConnection {
(self.xrandr.XRRFreeOutputInfo)(output_info); (self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, hidpi_factor, modes.collect())) Some((name, hidpi_factor, modes.collect()))
} }
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let status = (self.xrandr.XRRSetCrtcConfig)(
self.display,
resources,
crtc_id,
CurrentTime,
(*crtc).x,
(*crtc).y,
mode_id,
(*crtc).rotation,
(*crtc).outputs.offset(0),
1,
);
(self.xrandr.XRRFreeCrtcInfo)(crtc);
(self.xrandr.XRRFreeScreenResources)(resources);
if status == Success as i32 {
Ok(())
} else {
Err(())
}
}
}
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
let mode = (*crtc).mode;
(self.xrandr.XRRFreeCrtcInfo)(crtc);
(self.xrandr.XRRFreeScreenResources)(resources);
mode
}
}
} }

View file

@ -16,12 +16,13 @@ use parking_lot::Mutex;
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
monitor::MonitorHandle as RootMonitorHandle, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::{ platform_impl::{
x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle}, x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle},
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
VideoMode as PlatformVideoMode,
}, },
window::{CursorIcon, Icon, WindowAttributes}, window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
}; };
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
@ -46,9 +47,11 @@ pub struct SharedState {
pub guessed_dpi: Option<f64>, pub guessed_dpi: Option<f64>,
pub last_monitor: Option<X11MonitorHandle>, pub last_monitor: Option<X11MonitorHandle>,
pub dpi_adjusted: Option<(f64, f64)>, pub dpi_adjusted: Option<(f64, f64)>,
pub fullscreen: Option<RootMonitorHandle>, pub fullscreen: Option<Fullscreen>,
// Used to restore position after exiting fullscreen. // Used to restore position after exiting fullscreen
pub restore_position: Option<(i32, i32)>, pub restore_position: Option<(i32, i32)>,
// Used to restore video mode after exiting fullscreen
pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>,
pub frame_extents: Option<util::FrameExtentsHeuristic>, pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<LogicalSize>, pub min_inner_size: Option<LogicalSize>,
pub max_inner_size: Option<LogicalSize>, pub max_inner_size: Option<LogicalSize>,
@ -408,6 +411,7 @@ impl UnownedWindow {
if window_attrs.fullscreen.is_some() { if window_attrs.fullscreen.is_some() {
window window
.set_fullscreen_inner(window_attrs.fullscreen.clone()) .set_fullscreen_inner(window_attrs.fullscreen.clone())
.unwrap()
.queue(); .queue();
} }
if window_attrs.always_on_top { if window_attrs.always_on_top {
@ -564,41 +568,122 @@ impl UnownedWindow {
self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)) self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0))
} }
fn set_fullscreen_inner(&self, monitor: Option<RootMonitorHandle>) -> util::Flusher<'_> { fn set_fullscreen_inner(&self, fullscreen: Option<Fullscreen>) -> Option<util::Flusher<'_>> {
match monitor { let mut shared_state_lock = self.shared_state.lock();
let old_fullscreen = shared_state_lock.fullscreen.clone();
if old_fullscreen == fullscreen {
return None;
}
shared_state_lock.fullscreen = fullscreen.clone();
match (&old_fullscreen, &fullscreen) {
// Store the desktop video mode before entering exclusive
// fullscreen, so we can restore it upon exit, as XRandR does not
// provide a mechanism to set this per app-session or restore this
// to the desktop video mode as macOS and Windows do
(
&None,
&Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
})),
)
| (
&Some(Fullscreen::Borderless(_)),
&Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
})),
) => {
let monitor = video_mode.monitor.as_ref().unwrap();
shared_state_lock.desktop_video_mode =
Some((monitor.id, self.xconn.get_crtc_mode(monitor.id)));
}
// Restore desktop video mode upon exiting exclusive fullscreen
(&Some(Fullscreen::Exclusive(_)), &None)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
self.xconn
.set_crtc_config(monitor_id, mode_id)
.expect("failed to restore desktop video mode");
}
_ => (),
}
drop(shared_state_lock);
match fullscreen {
None => { None => {
let flusher = self.set_fullscreen_hint(false); let flusher = self.set_fullscreen_hint(false);
if let Some(position) = self.shared_state.lock().restore_position.take() { let mut shared_state_lock = self.shared_state.lock();
if let Some(position) = shared_state_lock.restore_position.take() {
self.set_position_inner(position.0, position.1).queue(); self.set_position_inner(position.0, position.1).queue();
} }
flusher Some(flusher)
} }
Some(RootMonitorHandle { Some(fullscreen) => {
inner: PlatformMonitorHandle::X(monitor), let (video_mode, monitor) = match fullscreen {
}) => { Fullscreen::Exclusive(RootVideoMode {
video_mode: PlatformVideoMode::X(ref video_mode),
}) => (Some(video_mode), video_mode.monitor.as_ref().unwrap()),
Fullscreen::Borderless(RootMonitorHandle {
inner: PlatformMonitorHandle::X(ref monitor),
}) => (None, monitor),
_ => unreachable!(),
};
if let Some(video_mode) = video_mode {
// FIXME: this is actually not correct if we're setting the
// video mode to a resolution higher than the current
// desktop resolution, because XRandR does not automatically
// reposition the monitors to the right and below this
// monitor.
//
// What ends up happening is we will get the fullscreen
// window showing up on those monitors as well, because
// their virtual position now overlaps with the monitor that
// we just made larger..
//
// It'd be quite a bit of work to handle this correctly (and
// nobody else seems to bother doing this correctly either),
// so we're just leaving this broken. Fixing this would
// involve storing all CRTCs upon entering fullscreen,
// restoring them upon exit, and after entering fullscreen,
// repositioning displays to the right and below this
// display. I think there would still be edge cases that are
// difficult or impossible to handle correctly, e.g. what if
// a new monitor was plugged in while in fullscreen?
//
// I think we might just want to disallow setting the video
// mode higher than the current desktop video mode (I'm sure
// this will make someone unhappy, but it's very unusual for
// games to want to do this anyway).
self.xconn
.set_crtc_config(monitor.id, video_mode.native_mode)
.expect("failed to set video mode");
}
let window_position = self.outer_position_physical(); let window_position = self.outer_position_physical();
self.shared_state.lock().restore_position = Some(window_position); self.shared_state.lock().restore_position = Some(window_position);
let monitor_origin: (i32, i32) = monitor.position().into(); let monitor_origin: (i32, i32) = monitor.position().into();
self.set_position_inner(monitor_origin.0, monitor_origin.1) self.set_position_inner(monitor_origin.0, monitor_origin.1)
.queue(); .queue();
self.set_fullscreen_hint(true) Some(self.set_fullscreen_hint(true))
} }
_ => unreachable!(),
} }
} }
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
self.shared_state.lock().fullscreen.clone() self.shared_state.lock().fullscreen.clone()
} }
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.shared_state.lock().fullscreen = monitor.clone(); if let Some(flusher) = self.set_fullscreen_inner(fullscreen) {
self.set_fullscreen_inner(monitor) flusher
.flush() .flush()
.expect("Failed to change window fullscreen state"); .expect("Failed to change window fullscreen state");
self.invalidate_cached_frame_extents(); self.invalidate_cached_frame_extents();
}
} }
fn get_rect(&self) -> util::AaRect { fn get_rect(&self) -> util::AaRect {

View file

@ -6,6 +6,13 @@ use cocoa::{
base::id, base::id,
foundation::{NSInteger, NSUInteger}, foundation::{NSInteger, NSUInteger},
}; };
use core_foundation::{
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
};
use core_graphics::{
base::CGError,
display::{CGDirectDisplayID, CGDisplayConfigRef},
};
use objc; use objc;
pub const NSNotFound: NSInteger = NSInteger::max_value(); pub const NSNotFound: NSInteger = NSInteger::max_value();
@ -108,3 +115,95 @@ pub enum NSWindowLevel {
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _, NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
} }
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
pub const kCGDisplayBlendNormal: f32 = 0.0;
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
pub type CGDisplayFadeReservationToken = u32;
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
pub type Boolean = u8;
pub const FALSE: Boolean = 0;
pub const TRUE: Boolean = 1;
pub const kCGErrorSuccess: i32 = 0;
pub const kCGErrorFailure: i32 = 1000;
pub const kCGErrorIllegalArgument: i32 = 1001;
pub const kCGErrorInvalidConnection: i32 = 1002;
pub const kCGErrorInvalidContext: i32 = 1003;
pub const kCGErrorCannotComplete: i32 = 1004;
pub const kCGErrorNotImplemented: i32 = 1006;
pub const kCGErrorRangeCheck: i32 = 1007;
pub const kCGErrorTypeCheck: i32 = 1008;
pub const kCGErrorInvalidOperation: i32 = 1010;
pub const kCGErrorNoneAvailable: i32 = 1011;
pub const IO1BitIndexedPixels: &str = "P";
pub const IO2BitIndexedPixels: &str = "PP";
pub const IO4BitIndexedPixels: &str = "PPPP";
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
pub const IOYUV422Pixels: &str = "Y4U2V2";
pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut libc::c_void;
#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
pub fn CGRestorePermanentDisplayConfiguration();
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
pub fn CGConfigureDisplayFadeEffect(
config: CGDisplayConfigRef,
fadeOutSeconds: CGDisplayFadeInterval,
fadeInSeconds: CGDisplayFadeInterval,
fadeRed: f32,
fadeGreen: f32,
fadeBlue: f32,
) -> CGError;
pub fn CGAcquireDisplayFadeReservation(
seconds: CGDisplayReservationInterval,
token: *mut CGDisplayFadeReservationToken,
) -> CGError;
pub fn CGDisplayFade(
token: CGDisplayFadeReservationToken,
duration: CGDisplayFadeInterval,
startBlend: CGDisplayBlendFraction,
endBlend: CGDisplayBlendFraction,
redBlend: f32,
greenBlend: f32,
blueBlend: f32,
synchronous: Boolean,
) -> CGError;
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
pub fn CGDisplaySetDisplayMode(
display: CGDirectDisplayID,
mode: CGDisplayModeRef,
options: CFDictionaryRef,
) -> CGError;
pub fn CGDisplayCopyAllDisplayModes(
display: CGDirectDisplayID,
options: CFDictionaryRef,
) -> CFArrayRef;
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
}

View file

@ -17,7 +17,7 @@ use std::{fmt, ops::Deref, sync::Arc};
pub use self::{ pub use self::{
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
}; };
use crate::{ use crate::{

View file

@ -1,25 +1,119 @@
use std::{collections::VecDeque, fmt}; use std::{collections::VecDeque, fmt};
use super::ffi;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::util::IdRef,
};
use cocoa::{ use cocoa::{
appkit::NSScreen, appkit::NSScreen,
base::{id, nil}, base::{id, nil},
foundation::{NSString, NSUInteger}, foundation::{NSString, NSUInteger},
}; };
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayMode}; use core_foundation::{
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
base::{CFRelease, TCFType},
string::CFString,
};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use core_video_sys::{ use core_video_sys::{
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay, kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease, CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
}; };
use crate::{ #[derive(Derivative)]
dpi::{PhysicalPosition, PhysicalSize}, #[derivative(Debug, Clone, PartialEq, Hash)]
monitor::VideoMode, pub struct VideoMode {
platform_impl::platform::util::IdRef, pub(crate) size: (u32, u32),
}; pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
pub(crate) monitor: MonitorHandle,
#[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
pub(crate) native_mode: NativeDisplayMode,
}
#[derive(Clone, PartialEq)] pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
ffi::CGDisplayModeRelease(self.0);
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
ffi::CGDisplayModeRetain(self.0);
}
NativeDisplayMode(self.0)
}
}
impl VideoMode {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(Clone)]
pub struct MonitorHandle(CGDirectDisplayID); pub struct MonitorHandle(CGDirectDisplayID);
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
// unique identifier that persists even across system reboots
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
}
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
}
}
pub fn available_monitors() -> VecDeque<MonitorHandle> { pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() { if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len()); let mut monitors = VecDeque::with_capacity(displays.len());
@ -101,7 +195,7 @@ impl MonitorHandle {
unsafe { NSScreen::backingScaleFactor(screen) as f64 } unsafe { NSScreen::backingScaleFactor(screen) as f64 }
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let cv_refresh_rate = unsafe { let cv_refresh_rate = unsafe {
let mut display_link = std::ptr::null_mut(); let mut display_link = std::ptr::null_mut();
assert_eq!( assert_eq!(
@ -117,11 +211,27 @@ impl MonitorHandle {
time.timeScale as i64 / time.timeValue time.timeScale as i64 / time.timeValue
}; };
CGDisplayMode::all_display_modes(self.0, std::ptr::null()) let monitor = self.clone();
.expect("failed to obtain list of display modes")
.into_iter() unsafe {
.map(move |mode| { let modes = {
let cg_refresh_rate = mode.refresh_rate().round() as i64; let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
assert!(!array.is_null(), "failed to get list of display modes");
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.into_iter()
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that // CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT // isn't a CRT
@ -131,34 +241,55 @@ impl MonitorHandle {
cv_refresh_rate cv_refresh_rate
}; };
VideoMode { let pixel_encoding =
size: (mode.width() as u32, mode.height() as u32), CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode))
.to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
32
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
16
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
30
} else {
unimplemented!()
};
let video_mode = VideoMode {
size: (
ffi::CGDisplayModeGetPixelWidth(mode) as u32,
ffi::CGDisplayModeGetPixelHeight(mode) as u32,
),
refresh_rate: refresh_rate as u16, refresh_rate: refresh_rate as u16,
bit_depth: mode.bit_depth() as u16, bit_depth,
} monitor: monitor.clone(),
native_mode: NativeDisplayMode(mode),
};
RootVideoMode { video_mode }
}) })
}
} }
pub(crate) fn ns_screen(&self) -> Option<id> { pub(crate) fn ns_screen(&self) -> Option<id> {
unsafe { unsafe {
let native_id = self.native_identifier(); let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
let screens = NSScreen::screens(nil); let screens = NSScreen::screens(nil);
let count: NSUInteger = msg_send![screens, count]; let count: NSUInteger = msg_send![screens, count];
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
let mut matching_screen: Option<id> = None;
for i in 0..count { for i in 0..count {
let screen = msg_send![screens, objectAtIndex: i as NSUInteger]; let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
let device_description = NSScreen::deviceDescription(screen); let device_description = NSScreen::deviceDescription(screen);
let value: id = msg_send![device_description, objectForKey:*key]; let value: id = msg_send![device_description, objectForKey:*key];
if value != nil { if value != nil {
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue]; let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue];
if screen_number as u32 == native_id { let other_uuid =
matching_screen = Some(screen); ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID);
break; if uuid == other_uuid {
return Some(screen);
} }
} }
} }
matching_screen None
} }
} }
} }

View file

@ -1,6 +1,6 @@
use std::{self, os::raw::*, ptr, time::Instant}; use std::{self, os::raw::*, ptr, time::Instant};
use crate::platform_impl::platform::app_state::AppState; use crate::platform_impl::platform::{app_state::AppState, ffi};
#[link(name = "CoreFoundation", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")]
extern "C" { extern "C" {
@ -13,7 +13,7 @@ extern "C" {
pub fn CFRunLoopObserverCreate( pub fn CFRunLoopObserverCreate(
allocator: CFAllocatorRef, allocator: CFAllocatorRef,
activities: CFOptionFlags, activities: CFOptionFlags,
repeats: Boolean, repeats: ffi::Boolean,
order: CFIndex, order: CFIndex,
callout: CFRunLoopObserverCallBack, callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext, context: *mut CFRunLoopObserverContext,
@ -51,11 +51,6 @@ extern "C" {
pub fn CFRelease(cftype: *const c_void); pub fn CFRelease(cftype: *const c_void);
} }
pub type Boolean = u8;
#[allow(dead_code)]
const FALSE: Boolean = 0;
const TRUE: Boolean = 1;
pub enum CFAllocator {} pub enum CFAllocator {}
pub type CFAllocatorRef = *mut CFAllocator; pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {} pub enum CFRunLoop {}
@ -102,7 +97,7 @@ pub struct CFRunLoopSourceContext {
pub retain: extern "C" fn(*const c_void) -> *const c_void, pub retain: extern "C" fn(*const c_void) -> *const c_void,
pub release: extern "C" fn(*const c_void), pub release: extern "C" fn(*const c_void),
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, pub equal: extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean,
pub hash: extern "C" fn(*const c_void) -> CFHashCode, pub hash: extern "C" fn(*const c_void) -> CFHashCode,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
@ -162,8 +157,8 @@ impl RunLoop {
let observer = CFRunLoopObserverCreate( let observer = CFRunLoopObserverCreate(
ptr::null_mut(), ptr::null_mut(),
flags, flags,
TRUE, // Indicates we want this to run repeatedly ffi::TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run priority, // The lower the value, the sooner this will run
handler, handler,
ptr::null_mut(), ptr::null_mut(),
); );

View file

@ -206,7 +206,10 @@ extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
} }
} }
} }
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
context.ns_window.setLevel_(0);
context.ns_window.toggleFullScreen_(nil); context.ns_window.toggleFullScreen_(nil);
} }
Box::from_raw(context_ptr); Box::from_raw(context_ptr);

View file

@ -8,6 +8,23 @@ use std::{
}, },
}; };
use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
platform_impl::platform::{
app_state::AppState,
ffi,
monitor::{self, MonitorHandle, VideoMode},
util::{self, IdRef},
view::{self, new_view},
window_delegate::new_delegate,
OsError,
},
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
};
use cocoa::{ use cocoa::{
appkit::{ appkit::{
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy, self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
@ -17,30 +34,12 @@ use cocoa::{
base::{id, nil}, base::{id, nil},
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
}; };
use core_graphics::display::CGDisplay; use core_graphics::display::{CGDisplay, CGDisplayMode};
use objc::{ use objc::{
declare::ClassDecl, declare::ClassDecl,
runtime::{Class, Object, Sel, BOOL, NO, YES}, runtime::{Class, Object, Sel, BOOL, NO, YES},
}; };
use crate::{
dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon,
monitor::MonitorHandle as RootMonitorHandle,
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
platform_impl::platform::{
app_state::AppState,
ffi,
monitor::{self, MonitorHandle},
util::{self, IdRef},
view::{self, new_view},
window_delegate::new_delegate,
OsError,
},
window::{CursorIcon, WindowAttributes, WindowId as RootWindowId},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub usize); pub struct Id(pub usize);
@ -119,11 +118,14 @@ fn create_window(
unsafe { unsafe {
let pool = NSAutoreleasePool::new(nil); let pool = NSAutoreleasePool::new(nil);
let screen = match attrs.fullscreen { let screen = match attrs.fullscreen {
Some(ref monitor_id) => { Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }))
let monitor_screen = monitor_id.inner.ns_screen(); | Some(Fullscreen::Exclusive(RootVideoMode {
video_mode: VideoMode { ref monitor, .. },
})) => {
let monitor_screen = monitor.ns_screen();
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
} }
_ => None, None => None,
}; };
let frame = match screen { let frame = match screen {
Some(screen) => appkit::NSScreen::frame(screen), Some(screen) => appkit::NSScreen::frame(screen),
@ -239,12 +241,15 @@ lazy_static! {
#[derive(Default)] #[derive(Default)]
pub struct SharedState { pub struct SharedState {
pub resizable: bool, pub resizable: bool,
pub fullscreen: Option<RootMonitorHandle>, pub fullscreen: Option<Fullscreen>,
pub maximized: bool, pub maximized: bool,
pub standard_frame: Option<NSRect>, pub standard_frame: Option<NSRect>,
is_simple_fullscreen: bool, is_simple_fullscreen: bool,
pub saved_style: Option<NSWindowStyleMask>, pub saved_style: Option<NSWindowStyleMask>,
/// Presentation options saved before entering `set_simple_fullscreen`, and
/// restored upon exiting it
save_presentation_opts: Option<NSApplicationPresentationOptions>, save_presentation_opts: Option<NSApplicationPresentationOptions>,
pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>,
} }
impl SharedState { impl SharedState {
@ -362,16 +367,7 @@ impl UnownedWindow {
let delegate = new_delegate(&window, fullscreen.is_some()); let delegate = new_delegate(&window, fullscreen.is_some());
// Set fullscreen mode after we setup everything // Set fullscreen mode after we setup everything
if let Some(monitor) = fullscreen { window.set_fullscreen(fullscreen);
if monitor.inner != window.current_monitor().inner {
// To do this with native fullscreen, we probably need to
// warp the window... while we could use
// `enterFullScreenMode`, they're idiomatically different
// fullscreen modes, so we'd have to support both anyway.
unimplemented!();
}
window.set_fullscreen(Some(monitor));
}
// Setting the window as key has to happen *after* we set the fullscreen // Setting the window as key has to happen *after* we set the fullscreen
// state, since otherwise we'll briefly see the window at normal size // state, since otherwise we'll briefly see the window at normal size
@ -601,22 +597,44 @@ impl UnownedWindow {
} }
} }
/// This is called when the window is exiting fullscreen, whether by the
/// user clicking on the green fullscreen button or programmatically by
/// `toggleFullScreen:`
pub(crate) fn restore_state_from_fullscreen(&self) { pub(crate) fn restore_state_from_fullscreen(&self) {
let maximized = { trace!("Locked shared state in `restore_state_from_fullscreen`");
trace!("Locked shared state in `restore_state_from_fullscreen`"); let mut shared_state_lock = self.shared_state.lock().unwrap();
let mut shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen = None; shared_state_lock.fullscreen = None;
let mask = self.saved_style(&mut *shared_state_lock); let maximized = shared_state_lock.maximized;
let mask = self.saved_style(&mut *shared_state_lock);
self.set_style_mask_async(mask); drop(shared_state_lock);
shared_state_lock.maximized
};
trace!("Unocked shared state in `restore_state_from_fullscreen`"); trace!("Unocked shared state in `restore_state_from_fullscreen`");
self.set_style_mask_async(mask);
self.set_maximized(maximized); self.set_maximized(maximized);
} }
fn restore_display_mode(&self) {
trace!("Locked shared state in `restore_display_mode`");
let shared_state_lock = self.shared_state.lock().unwrap();
if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) =
shared_state_lock.fullscreen
{
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().inner.native_identifier()),
ffi::kCGErrorSuccess
);
}
}
trace!("Unlocked shared state in `restore_display_mode`");
}
#[inline] #[inline]
pub fn set_maximized(&self, maximized: bool) { pub fn set_maximized(&self, maximized: bool) {
let is_zoomed = self.is_zoomed(); let is_zoomed = self.is_zoomed();
@ -634,44 +652,159 @@ impl UnownedWindow {
} }
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
let shared_state_lock = self.shared_state.lock().unwrap(); let shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen.clone() shared_state_lock.fullscreen.clone()
} }
#[inline] #[inline]
/// TODO: Right now set_fullscreen do not work on switching monitors pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
/// in fullscreen mode trace!("Locked shared state in `set_fullscreen`");
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
let shared_state_lock = self.shared_state.lock().unwrap(); let shared_state_lock = self.shared_state.lock().unwrap();
if shared_state_lock.is_simple_fullscreen { if shared_state_lock.is_simple_fullscreen {
trace!("Unlocked shared state in `set_fullscreen`");
return; return;
} }
let old_fullscreen = shared_state_lock.fullscreen.clone();
let not_fullscreen = { if fullscreen == old_fullscreen {
trace!("Locked shared state in `set_fullscreen`");
let current = &shared_state_lock.fullscreen;
match (current, monitor) {
(&Some(ref a), Some(ref b)) if a.inner != b.inner => {
// Our best bet is probably to move to the origin of the
// target monitor.
unimplemented!()
}
(&None, None) | (&Some(_), Some(_)) => return,
_ => (),
}
trace!("Unlocked shared state in `set_fullscreen`"); trace!("Unlocked shared state in `set_fullscreen`");
current.is_none() return;
}; }
trace!("Unlocked shared state in `set_fullscreen`");
drop(shared_state_lock);
unsafe { // If the fullscreen is on a different monitor, we must move the window
util::toggle_full_screen_async( // to that monitor before we toggle fullscreen (as `toggleFullScreen`
*self.ns_window, // does not take a screen parameter, but uses the current screen)
*self.ns_view, if let Some(ref fullscreen) = fullscreen {
not_fullscreen, let new_screen = match fullscreen {
Arc::downgrade(&self.shared_state), Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor,
) Fullscreen::Exclusive(RootVideoMode {
}; video_mode: VideoMode { ref monitor, .. },
}) => monitor,
}
.ns_screen()
.unwrap();
unsafe {
let old_screen = NSWindow::screen(*self.ns_window);
if old_screen != new_screen {
let mut screen_frame: NSRect = msg_send![new_screen, frame];
// The coordinate system here has its origin at bottom-left
// and Y goes up
screen_frame.origin.y += screen_frame.size.height;
util::set_frame_top_left_point_async(*self.ns_window, screen_frame.origin);
}
}
}
if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen {
// Note: `enterFullScreenMode:withOptions:` seems to do the exact
// same thing as we're doing here (captures the display, sets the
// video mode, and hides the menu bar and dock), with the exception
// of that I couldn't figure out how to set the display mode with
// it. I think `enterFullScreenMode:withOptions:` is still using the
// older display mode API where display modes were of the type
// `CFDictionary`, but this has changed, so we can't obtain the
// correct parameter for this any longer. Apple's code samples for
// this function seem to just pass in "YES" for the display mode
// parameter, which is not consistent with the docs saying that it
// takes a `NSDictionary`..
let display_id = video_mode.monitor().inner.native_identifier();
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
unsafe {
// Fade to black (and wait for the fade to complete) to hide the
// flicker from capturing the display and switching display mode
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
== ffi::kCGErrorSuccess
{
ffi::CGDisplayFade(
fade_token,
0.3,
ffi::kCGDisplayBlendNormal,
ffi::kCGDisplayBlendSolidColor,
0.0,
0.0,
0.0,
ffi::TRUE,
);
}
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
}
unsafe {
let result = ffi::CGDisplaySetDisplayMode(
display_id,
video_mode.video_mode.native_mode.0,
std::ptr::null(),
);
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
// After the display has been configured, fade back in
// asynchronously
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
ffi::CGDisplayFade(
fade_token,
0.6,
ffi::kCGDisplayBlendSolidColor,
ffi::kCGDisplayBlendNormal,
0.0,
0.0,
0.0,
ffi::FALSE,
);
ffi::CGReleaseDisplayFadeReservation(fade_token);
}
}
}
match (&old_fullscreen, &fullscreen) {
(&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe {
// If we're already in fullscreen mode, calling
// `CGDisplayCapture` will place the shielding window on top of
// our window, which results in a black display and is not what
// we want. So, we must place our window on top of the shielding
// window. Unfortunately, this also makes our window be on top
// of the menu bar, and this looks broken, so we must make sure
// that the menu bar is disabled. This is done in the window
// delegate in `window:willUseFullScreenPresentationOptions:`.
msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1];
},
(&Some(Fullscreen::Exclusive(_)), &None) => unsafe {
self.restore_display_mode();
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
old_fullscreen.is_none(),
Arc::downgrade(&self.shared_state),
);
},
(&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
self.restore_display_mode();
}
(&None, &Some(Fullscreen::Exclusive(_)))
| (&None, &Some(Fullscreen::Borderless(_)))
| (&Some(Fullscreen::Borderless(_)), &None) => unsafe {
// Wish it were this simple for all cases
util::toggle_full_screen_async(
*self.ns_window,
*self.ns_view,
old_fullscreen.is_none(),
Arc::downgrade(&self.shared_state),
);
},
_ => (),
}
trace!("Locked shared state in `set_fullscreen`");
let mut shared_state_lock = self.shared_state.lock().unwrap();
shared_state_lock.fullscreen = fullscreen.clone();
trace!("Unlocked shared state in `set_fullscreen`");
} }
#[inline] #[inline]

View file

@ -5,9 +5,9 @@ use std::{
}; };
use cocoa::{ use cocoa::{
appkit::{self, NSView, NSWindow}, appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow},
base::{id, nil}, base::{id, nil},
foundation::NSAutoreleasePool, foundation::{NSAutoreleasePool, NSUInteger},
}; };
use objc::{ use objc::{
declare::ClassDecl, declare::ClassDecl,
@ -22,7 +22,7 @@ use crate::{
util::{self, IdRef}, util::{self, IdRef},
window::{get_window_id, UnownedWindow}, window::{get_window_id, UnownedWindow},
}, },
window::WindowId, window::{Fullscreen, WindowId},
}; };
pub struct WindowDelegateState { pub struct WindowDelegateState {
@ -182,6 +182,11 @@ lazy_static! {
dragging_exited as extern "C" fn(&Object, Sel, id), dragging_exited as extern "C" fn(&Object, Sel, id),
); );
decl.add_method(
sel!(window:willUseFullScreenPresentationOptions:),
window_will_use_fullscreen_presentation_options
as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
);
decl.add_method( decl.add_method(
sel!(windowDidEnterFullScreen:), sel!(windowDidEnterFullScreen:),
window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id), window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
@ -408,6 +413,26 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Completed `windowWillEnterFullscreen:`"); trace!("Completed `windowWillEnterFullscreen:`");
} }
extern "C" fn window_will_use_fullscreen_presentation_options(
_this: &Object,
_: Sel,
_: id,
_proposed_options: NSUInteger,
) -> NSUInteger {
// Generally, games will want to disable the menu bar and the dock. Ideally,
// this would be configurable by the user. Unfortunately because of our
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
// placed on top of the menu bar in exclusive fullscreen mode. This looks
// broken so we always disable the menu bar in exclusive fullscreen. We may
// still want to make this configurable for borderless fullscreen. Right now
// we don't, for consistency. If we do, it should be documented that the
// user-provided options are ignored in exclusive fullscreen.
(NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
.bits()
}
/// Invoked when entered fullscreen /// Invoked when entered fullscreen
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
trace!("Triggered `windowDidEnterFullscreen:`"); trace!("Triggered `windowDidEnterFullscreen:`");
@ -415,8 +440,21 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
state.with_window(|window| { state.with_window(|window| {
let monitor = window.current_monitor(); let monitor = window.current_monitor();
trace!("Locked shared state in `window_did_enter_fullscreen`"); trace!("Locked shared state in `window_did_enter_fullscreen`");
window.shared_state.lock().unwrap().fullscreen = Some(monitor); let mut shared_state = window.shared_state.lock().unwrap();
trace!("Unlocked shared state in `window_will_enter_fullscreen`"); match shared_state.fullscreen {
// Exclusive mode sets the state in `set_fullscreen` as the user
// can't enter exclusive mode by other means (like the
// fullscreen button on the window decorations)
Some(Fullscreen::Exclusive(_)) => (),
// `window_did_enter_fullscreen` was triggered and we're already
// in fullscreen, so we must've reached here by `set_fullscreen`
// as it updates the state
Some(Fullscreen::Borderless(_)) => (),
// Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state!
None => shared_state.fullscreen = Some(Fullscreen::Borderless(monitor)),
}
trace!("Unlocked shared state in `window_did_enter_fullscreen`");
}); });
state.initial_fullscreen = false; state.initial_fullscreen = false;
}); });

View file

@ -4,7 +4,7 @@ use winapi::{self, shared::windef::HWND};
pub use self::{ pub use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::Window, window::Window,
}; };

View file

@ -3,54 +3,64 @@ use winapi::{
minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD}, minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD},
windef::{HDC, HMONITOR, HWND, LPRECT, POINT}, windef::{HDC, HMONITOR, HWND, LPRECT, POINT},
}, },
um::{wingdi, winnt::LONG, winuser}, um::{wingdi, winuser},
}; };
use std::{ use std::{
collections::{HashSet, VecDeque}, collections::{BTreeSet, VecDeque},
io, mem, ptr, io, mem, ptr,
}; };
use super::{util, EventLoop}; use super::{util, EventLoop};
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
platform_impl::platform::{ platform_impl::platform::{
dpi::{dpi_to_scale_factor, get_monitor_dpi}, dpi::{dpi_to_scale_factor, get_monitor_dpi},
window::Window, window::Window,
}, },
}; };
/// Win32 implementation of the main `MonitorHandle` object.
#[derive(Derivative)] #[derive(Derivative)]
#[derivative(Debug, Clone)] #[derivative(Debug, Clone, Eq, PartialEq, Hash)]
pub struct MonitorHandle { pub struct VideoMode {
/// Monitor handle. pub(crate) size: (u32, u32),
hmonitor: HMonitor, pub(crate) bit_depth: u16,
#[derivative(Debug = "ignore")] pub(crate) refresh_rate: u16,
monitor_info: winuser::MONITORINFOEXW, pub(crate) monitor: MonitorHandle,
/// The system name of the monitor. #[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
monitor_name: String, pub(crate) native_video_mode: wingdi::DEVMODEW,
/// True if this is the primary monitor.
primary: bool,
/// The position of the monitor in pixels on the desktop.
///
/// A window that is positioned at these coordinates will overlap the monitor.
position: (i32, i32),
/// The current resolution in pixels on the monitor.
dimensions: (u32, u32),
/// DPI scale factor.
hidpi_factor: f64,
} }
impl VideoMode {
pub fn size(&self) -> PhysicalSize {
self.size.into()
}
pub fn bit_depth(&self) -> u16 {
self.bit_depth
}
pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
pub fn monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: self.monitor.clone(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle(HMONITOR);
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually. // Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
// For more info see: // For more info see:
// https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/360
// https://github.com/retep998/winapi-rs/issues/396 // https://github.com/retep998/winapi-rs/issues/396
#[derive(Debug, Clone)]
struct HMonitor(HMONITOR);
unsafe impl Send for HMonitor {} unsafe impl Send for MonitorHandle {}
unsafe extern "system" fn monitor_enum_proc( unsafe extern "system" fn monitor_enum_proc(
hmonitor: HMONITOR, hmonitor: HMONITOR,
@ -59,7 +69,7 @@ unsafe extern "system" fn monitor_enum_proc(
data: LPARAM, data: LPARAM,
) -> BOOL { ) -> BOOL {
let monitors = data as *mut VecDeque<MonitorHandle>; let monitors = data as *mut VecDeque<MonitorHandle>;
(*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor)); (*monitors).push_back(MonitorHandle::new(hmonitor));
TRUE // continue enumeration TRUE // continue enumeration
} }
@ -79,12 +89,12 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
pub fn primary_monitor() -> MonitorHandle { pub fn primary_monitor() -> MonitorHandle {
const ORIGIN: POINT = POINT { x: 0, y: 0 }; const ORIGIN: POINT = POINT { x: 0, y: 0 };
let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) }; let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) };
MonitorHandle::from_hmonitor(hmonitor) MonitorHandle::new(hmonitor)
} }
pub fn current_monitor(hwnd: HWND) -> MonitorHandle { pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) }; let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) };
MonitorHandle::from_hmonitor(hmonitor) MonitorHandle::new(hmonitor)
} }
impl<T> EventLoop<T> { impl<T> EventLoop<T> {
@ -125,73 +135,69 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINF
} }
impl MonitorHandle { impl MonitorHandle {
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self { pub(crate) fn new(hmonitor: HMONITOR) -> Self {
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed"); MonitorHandle(hmonitor)
let place = monitor_info.rcMonitor;
let dimensions = (
(place.right - place.left) as u32,
(place.bottom - place.top) as u32,
);
MonitorHandle {
hmonitor: HMonitor(hmonitor),
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
position: (place.left as i32, place.top as i32),
dimensions,
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
monitor_info,
}
} }
pub(crate) fn contains_point(&self, point: &POINT) -> bool { pub(crate) fn contains_point(&self, point: &POINT) -> bool {
let left = self.position.0 as LONG; let monitor_info = get_monitor_info(self.0).unwrap();
let right = left + self.dimensions.0 as LONG; point.x >= monitor_info.rcMonitor.left
let top = self.position.1 as LONG; && point.x <= monitor_info.rcMonitor.right
let bottom = top + self.dimensions.1 as LONG; && point.y >= monitor_info.rcMonitor.top
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom && point.y <= monitor_info.rcMonitor.bottom
} }
#[inline] #[inline]
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
Some(self.monitor_name.clone()) let monitor_info = get_monitor_info(self.0).unwrap();
Some(util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()))
} }
#[inline] #[inline]
pub fn native_identifier(&self) -> String { pub fn native_identifier(&self) -> String {
self.monitor_name.clone() self.name().unwrap()
} }
#[inline] #[inline]
pub fn hmonitor(&self) -> HMONITOR { pub fn hmonitor(&self) -> HMONITOR {
self.hmonitor.0 self.0
} }
#[inline] #[inline]
pub fn size(&self) -> PhysicalSize { pub fn size(&self) -> PhysicalSize {
self.dimensions.into() let monitor_info = get_monitor_info(self.0).unwrap();
PhysicalSize {
width: (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) as f64,
height: (monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) as f64,
}
} }
#[inline] #[inline]
pub fn position(&self) -> PhysicalPosition { pub fn position(&self) -> PhysicalPosition {
self.position.into() let monitor_info = get_monitor_info(self.0).unwrap();
PhysicalPosition {
x: monitor_info.rcMonitor.left as f64,
y: monitor_info.rcMonitor.top as f64,
}
} }
#[inline] #[inline]
pub fn hidpi_factor(&self) -> f64 { pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
} }
#[inline] #[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
// EnumDisplaySettingsExW can return duplicate values (or some of the // EnumDisplaySettingsExW can return duplicate values (or some of the
// fields are probably changing, but we aren't looking at those fields // fields are probably changing, but we aren't looking at those fields
// anyway), so we're using a HashSet deduplicate // anyway), so we're using a BTreeSet deduplicate
let mut modes = HashSet::new(); let mut modes = BTreeSet::new();
let mut i = 0; let mut i = 0;
loop { loop {
unsafe { unsafe {
let device_name = self.monitor_info.szDevice.as_ptr(); let monitor_info = get_monitor_info(self.0).unwrap();
let device_name = monitor_info.szDevice.as_ptr();
let mut mode: wingdi::DEVMODEW = mem::zeroed(); let mut mode: wingdi::DEVMODEW = mem::zeroed();
mode.dmSize = mem::size_of_val(&mode) as WORD; mode.dmSize = mem::size_of_val(&mode) as WORD;
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 { if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
@ -205,10 +211,14 @@ impl MonitorHandle {
| wingdi::DM_DISPLAYFREQUENCY; | wingdi::DM_DISPLAYFREQUENCY;
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS); assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
modes.insert(VideoMode { modes.insert(RootVideoMode {
size: (mode.dmPelsWidth, mode.dmPelsHeight), video_mode: VideoMode {
bit_depth: mode.dmBitsPerPel as u16, size: (mode.dmPelsWidth, mode.dmPelsHeight),
refresh_rate: mode.dmDisplayFrequency as u16, bit_depth: mode.dmBitsPerPel as u16,
refresh_rate: mode.dmDisplayFrequency as u16,
monitor: self.clone(),
native_video_mode: mode,
},
}); });
} }
} }

View file

@ -46,7 +46,7 @@ use crate::{
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
PlatformSpecificWindowBuilderAttributes, WindowId, PlatformSpecificWindowBuilderAttributes, WindowId,
}, },
window::{CursorIcon, Icon, WindowAttributes}, window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
}; };
/// The Win32 implementation of the main `Window` object. /// The Win32 implementation of the main `Window` object.
@ -327,7 +327,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || { 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) f.set(WindowFlags::RESIZABLE, resizable)
}); });
}); });
@ -421,80 +421,177 @@ impl Window {
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || { 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) f.set(WindowFlags::MAXIMIZED, maximized)
}); });
}); });
} }
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<RootMonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
let window_state = self.window_state.lock(); let window_state = self.window_state.lock();
window_state.fullscreen.clone() window_state.fullscreen.clone()
} }
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
unsafe { let window = self.window.clone();
let window = self.window.clone(); let window_state = Arc::clone(&self.window_state);
let window_state = Arc::clone(&self.window_state);
match &monitor { let mut window_state_lock = window_state.lock();
&Some(RootMonitorHandle { ref inner }) => { let old_fullscreen = window_state_lock.fullscreen.clone();
let (x, y): (i32, i32) = inner.position().into(); if window_state_lock.fullscreen == fullscreen {
let (width, height): (u32, u32) = inner.size().into(); return;
}
window_state_lock.fullscreen = fullscreen.clone();
drop(window_state_lock);
let mut monitor = monitor.clone(); self.thread_executor.execute_in_thread(move || {
self.thread_executor.execute_in_thread(move || { let mut window_state_lock = window_state.lock();
let mut window_state_lock = window_state.lock();
let client_rect = // Save window bounds before entering fullscreen
util::get_client_rect(window.0).expect("get client rect failed!"); match (&old_fullscreen, &fullscreen) {
window_state_lock.saved_window = Some(SavedWindow { (&None, &Some(_)) => {
client_rect, let client_rect = util::get_client_rect(window.0).unwrap();
dpi_factor: window_state_lock.dpi_factor, 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);
}); });
} }
&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 { // Change video mode if we're transitioning to or from exclusive
client_rect, // fullscreen
dpi_factor, match (&old_fullscreen, &fullscreen) {
}) = window_state_lock.saved_window (&None, &Some(Fullscreen::Exclusive(ref video_mode)))
{ | (
window_state_lock.dpi_factor = dpi_factor; &Some(Fullscreen::Borderless(_)),
window_state_lock.saved_window = None; &Some(Fullscreen::Exclusive(ref video_mode)),
)
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) =>
{
let monitor = video_mode.monitor();
WindowState::refresh_window_state( let mut display_name = OsStr::new(&monitor.inner.native_identifier())
window_state_lock, .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, 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] #[inline]
@ -503,8 +600,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || { 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, |f| {
WindowState::set_window_flags(window_state.lock(), window.0, Some(client_rect), |f| {
f.set(WindowFlags::DECORATIONS, decorations) f.set(WindowFlags::DECORATIONS, decorations)
}); });
}); });
@ -516,7 +612,7 @@ impl Window {
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || { 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) f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top)
}); });
}); });
@ -769,9 +865,7 @@ unsafe fn init<T: 'static>(
let window_state = { let window_state = {
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor); let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
let window_state = Arc::new(Mutex::new(window_state)); let window_state = Arc::new(Mutex::new(window_state));
WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| { WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);
*f = window_flags
});
window_state window_state
}; };
@ -865,7 +959,7 @@ pub fn com_initialized() {
// is activated. If the window is not fullscreen, the Shell falls back to // is activated. If the window is not fullscreen, the Shell falls back to
// heuristics to determine how the window should be treated, which means // heuristics to determine how the window should be treated, which means
// that it could still consider the window as fullscreen. :( // 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(); com_initialized();
TASKBAR_LIST.with(|task_bar_list_ptr| { TASKBAR_LIST.with(|task_bar_list_ptr| {

View file

@ -1,8 +1,7 @@
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
monitor::MonitorHandle,
platform_impl::platform::{event_loop, icon::WinIcon, util}, platform_impl::platform::{event_loop, icon::WinIcon, util},
window::{CursorIcon, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes},
}; };
use parking_lot::MutexGuard; use parking_lot::MutexGuard;
use std::{io, ptr}; use std::{io, ptr};
@ -29,7 +28,7 @@ pub struct WindowState {
pub saved_window: Option<SavedWindow>, pub saved_window: Option<SavedWindow>,
pub dpi_factor: f64, pub dpi_factor: f64,
pub fullscreen: Option<MonitorHandle>, pub fullscreen: Option<Fullscreen>,
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
/// times in `EventsCleared`. /// times in `EventsCleared`.
pub queued_out_of_band_redraw: bool, pub queued_out_of_band_redraw: bool,
@ -84,6 +83,7 @@ bitflags! {
WindowFlags::RESIZABLE.bits | WindowFlags::RESIZABLE.bits |
WindowFlags::MAXIMIZED.bits WindowFlags::MAXIMIZED.bits
); );
const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
} }
@ -122,32 +122,16 @@ impl WindowState {
self.window_flags self.window_flags
} }
pub fn set_window_flags<F>( pub fn set_window_flags<F>(mut this: MutexGuard<'_, Self>, window: HWND, f: F)
mut this: MutexGuard<'_, Self>, where
window: HWND,
set_client_rect: Option<RECT>,
f: F,
) where
F: FnOnce(&mut WindowFlags), F: FnOnce(&mut WindowFlags),
{ {
let old_flags = this.window_flags; let old_flags = this.window_flags;
f(&mut this.window_flags); f(&mut this.window_flags);
let is_fullscreen = this.fullscreen.is_some();
this.window_flags
.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
let new_flags = this.window_flags; let new_flags = this.window_flags;
drop(this); drop(this);
old_flags.apply_diff(window, new_flags, set_client_rect); old_flags.apply_diff(window, new_flags);
}
pub fn refresh_window_state(
this: MutexGuard<'_, Self>,
window: HWND,
set_client_rect: Option<RECT>,
) {
Self::set_window_flags(this, window, set_client_rect, |_| ());
} }
pub fn set_window_flags_in_place<F>(&mut self, f: F) pub fn set_window_flags_in_place<F>(&mut self, f: F)
@ -185,6 +169,7 @@ impl WindowFlags {
fn mask(mut self) -> WindowFlags { fn mask(mut self) -> WindowFlags {
if self.contains(WindowFlags::MARKER_FULLSCREEN) { if self.contains(WindowFlags::MARKER_FULLSCREEN) {
self &= WindowFlags::FULLSCREEN_AND_MASK; self &= WindowFlags::FULLSCREEN_AND_MASK;
self |= WindowFlags::FULLSCREEN_OR_MASK;
} }
if !self.contains(WindowFlags::VISIBLE) { if !self.contains(WindowFlags::VISIBLE) {
self &= WindowFlags::INVISIBLE_AND_MASK; self &= WindowFlags::INVISIBLE_AND_MASK;
@ -236,7 +221,7 @@ impl WindowFlags {
} }
/// Adjust the window client rectangle to the return value, if present. /// Adjust the window client rectangle to the return value, if present.
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) { fn apply_diff(mut self, window: HWND, mut new: WindowFlags) {
self = self.mask(); self = self.mask();
new = new.mask(); new = new.mask();
@ -295,45 +280,20 @@ impl WindowFlags {
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
match set_client_rect let mut flags = winuser::SWP_NOZORDER
.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) | winuser::SWP_NOMOVE
{ | winuser::SWP_NOSIZE
Some(client_rect) => { | winuser::SWP_FRAMECHANGED;
let (x, y, w, h) = (
client_rect.left, // We generally don't want style changes here to affect window
client_rect.top, // focus, but for fullscreen windows they must be activated
client_rect.right - client_rect.left, // (i.e. focused) so that they appear on top of the taskbar
client_rect.bottom - client_rect.top, if !new.contains(WindowFlags::MARKER_FULLSCREEN) {
); flags |= winuser::SWP_NOACTIVATE;
winuser::SetWindowPos(
window,
ptr::null_mut(),
x,
y,
w,
h,
winuser::SWP_NOZORDER
| winuser::SWP_FRAMECHANGED
| winuser::SWP_NOACTIVATE,
);
}
None => {
// Refresh the window frame.
winuser::SetWindowPos(
window,
ptr::null_mut(),
0,
0,
0,
0,
winuser::SWP_NOZORDER
| winuser::SWP_NOMOVE
| winuser::SWP_NOSIZE
| winuser::SWP_FRAMECHANGED
| winuser::SWP_NOACTIVATE,
);
}
} }
// Refresh the window frame
winuser::SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags);
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0); winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
} }
} }

View file

@ -5,7 +5,7 @@ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
error::{ExternalError, NotSupportedError, OsError}, error::{ExternalError, NotSupportedError, OsError},
event_loop::EventLoopWindowTarget, event_loop::EventLoopWindowTarget,
monitor::{AvailableMonitorsIter, MonitorHandle}, monitor::{AvailableMonitorsIter, MonitorHandle, VideoMode},
platform_impl, platform_impl,
}; };
@ -45,6 +45,18 @@ impl fmt::Debug for Window {
} }
} }
impl Drop for Window {
fn drop(&mut self) {
// If the window is in exclusive fullscreen, we must restore the desktop
// video mode (generally this would be done on application exit, but
// closing the window doesn't necessarily always mean application exit,
// such as when there are multiple windows)
if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() {
self.set_fullscreen(None);
}
}
}
/// Identifier of a window. Unique for each window. /// Identifier of a window. Unique for each window.
/// ///
/// Can be obtained with `window.id()`. /// Can be obtained with `window.id()`.
@ -110,7 +122,7 @@ pub struct WindowAttributes {
/// Whether the window should be set as fullscreen upon creation. /// Whether the window should be set as fullscreen upon creation.
/// ///
/// The default is `None`. /// The default is `None`.
pub fullscreen: Option<MonitorHandle>, pub fullscreen: Option<Fullscreen>,
/// The title of the window in the title bar. /// The title of the window in the title bar.
/// ///
@ -222,14 +234,14 @@ impl WindowBuilder {
self self
} }
/// Sets the window fullscreen state. None means a normal window, Some(MonitorHandle) /// Sets the window fullscreen state. None means a normal window, Some(Fullscreen)
/// means a fullscreen window on that specific monitor /// means a fullscreen window on that specific monitor
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **Windows:** Screen saver is disabled in fullscreen mode. /// - **Windows:** Screen saver is disabled in fullscreen mode.
#[inline] #[inline]
pub fn with_fullscreen(mut self, monitor: Option<MonitorHandle>) -> WindowBuilder { pub fn with_fullscreen(mut self, monitor: Option<Fullscreen>) -> WindowBuilder {
self.window.fullscreen = monitor; self.window.fullscreen = monitor;
self self
} }
@ -295,7 +307,6 @@ impl WindowBuilder {
self, self,
window_target: &EventLoopWindowTarget<T>, window_target: &EventLoopWindowTarget<T>,
) -> Result<Window, OsError> { ) -> Result<Window, OsError> {
// building
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific) platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)
.map(|window| Window { window }) .map(|window| Window { window })
} }
@ -537,11 +548,27 @@ impl Window {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a
/// video mode change. *Caveat!* macOS doesn't provide task switching (or
/// spaces!) while in exclusive fullscreen mode. This mode should be used
/// when a video mode change is desired, but for a better user experience,
/// borderless fullscreen might be preferred.
///
/// `Fullscreen::Borderless` provides a borderless fullscreen window on a
/// separate space. This is the idiomatic way for fullscreen games to work
/// on macOS. See [`WindowExtMacOs::set_simple_fullscreen`][simple] if
/// separate spaces are not preferred.
///
/// The dock and the menu bar are always disabled in fullscreen mode.
/// - **iOS:** Can only be called on the main thread. /// - **iOS:** Can only be called on the main thread.
/// - **Wayland:** Does not support exclusive fullscreen mode.
/// - **Windows:** Screen saver is disabled in fullscreen mode. /// - **Windows:** Screen saver is disabled in fullscreen mode.
///
/// [simple]:
/// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.window.set_fullscreen(monitor) self.window.set_fullscreen(fullscreen)
} }
/// Gets the window's current fullscreen state. /// Gets the window's current fullscreen state.
@ -550,7 +577,7 @@ impl Window {
/// ///
/// - **iOS:** Can only be called on the main thread. /// - **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<MonitorHandle> { pub fn fullscreen(&self) -> Option<Fullscreen> {
self.window.fullscreen() self.window.fullscreen()
} }
@ -759,3 +786,9 @@ impl Default for CursorIcon {
CursorIcon::Default CursorIcon::Default
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Fullscreen {
Exclusive(VideoMode),
Borderless(MonitorHandle),
}