From 985fe0d03f3b544ff47ae37023e28fef2dc4d9ec Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sat, 15 Jun 2019 09:08:45 +0300 Subject: [PATCH 01/33] Add exclusive fullscreen mode --- Cargo.toml | 15 +- examples/fullscreen.rs | 104 ++++++------- examples/multithreaded.rs | 13 +- src/monitor.rs | 32 +++- src/platform_impl/macos/mod.rs | 2 +- src/platform_impl/macos/monitor.rs | 45 +++++- src/platform_impl/macos/window.rs | 161 +++++++++++++-------- src/platform_impl/macos/window_delegate.rs | 4 +- src/platform_impl/windows/mod.rs | 2 +- src/platform_impl/windows/monitor.rs | 52 ++++++- src/platform_impl/windows/window.rs | 77 +++++----- src/platform_impl/windows/window_state.rs | 54 ++++++- src/window.rs | 33 ++--- 13 files changed, 389 insertions(+), 205 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 18b7e954b8..cf384919ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,13 +32,22 @@ version = "0.2" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.18.4" -core-foundation = "0.6" -core-graphics = "0.17.3" core-video-sys = "0.1.2" dispatch = "0.1.4" objc = "0.2.3" +[target.'cfg(target_os = "macos")'.dependencies.cocoa] +git = "https://github.com/aleksijuvani/core-foundation-rs/" +rev = "9293a70904892ab279b264be5f1b6efe83e82abd" + +[target.'cfg(target_os = "macos")'.dependencies.core-foundation] +git = "https://github.com/aleksijuvani/core-foundation-rs/" +rev = "9293a70904892ab279b264be5f1b6efe83e82abd" + +[target.'cfg(target_os = "macos")'.dependencies.core-graphics] +git = "https://github.com/aleksijuvani/core-foundation-rs/" +rev = "9293a70904892ab279b264be5f1b6efe83e82abd" + [target.'cfg(target_os = "windows")'.dependencies] bitflags = "1" diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 27df22767b..edc20b4ae1 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,56 +1,36 @@ -use std::io::{self, Write}; -use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - monitor::MonitorHandle, - window::WindowBuilder, -}; +use std::io::{stdin, stdout, Write}; +use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::monitor::{MonitorHandle, VideoMode}; +use winit::window::{Fullscreen, WindowBuilder}; fn main() { let event_loop = EventLoop::new(); - #[cfg(target_os = "macos")] - let mut macos_use_simple_fullscreen = false; - - let monitor = { - // On macOS there are two fullscreen modes "native" and "simple" - #[cfg(target_os = "macos")] - { - print!("Please choose the fullscreen mode: (1) native, (2) simple: "); - io::stdout().flush().unwrap(); - - let mut num = String::new(); - io::stdin().read_line(&mut num).unwrap(); - let num = num.trim().parse().ok().expect("Please enter a number"); - match num { - 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 - } - } + print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: "); + stdout().flush().unwrap(); - #[cfg(not(target_os = "macos"))] - Some(prompt_for_monitor(&event_loop)) - }; + let mut num = String::new(); + stdin().read_line(&mut num).unwrap(); + let num = num.trim().parse().ok().expect("Please enter a number"); + + let fullscreen = Some(match num { + 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))), + 2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)), + _ => unreachable!("Please enter a valid number"), + }); - let mut is_fullscreen = monitor.is_some(); + let mut is_fullscreen = true; let mut is_maximized = false; let mut decorations = true; let window = WindowBuilder::new() .with_title("Hello world!") - .with_fullscreen(monitor) + .with_fullscreen(fullscreen.clone()) .build(&event_loop) .unwrap(); event_loop.run(move |event, _, control_flow| { - println!("{:?}", event); *control_flow = ControlFlow::Wait; match event { @@ -67,35 +47,15 @@ fn main() { } => match (virtual_code, state) { (VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit, (VirtualKeyCode::F, ElementState::Pressed) => { - #[cfg(target_os = "macos")] - { - 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); } else { - window.set_fullscreen(Some(window.current_monitor())); + window.set_fullscreen(fullscreen.clone()); } } (VirtualKeyCode::S, ElementState::Pressed) => { 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) => { is_maximized = !is_maximized; @@ -121,10 +81,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { } print!("Please write the number of the monitor to use: "); - io::stdout().flush().unwrap(); + stdout().flush().unwrap(); 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 monitor = event_loop .available_monitors() @@ -135,3 +95,25 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { monitor } + +fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode { + // Video modes are returned in a random order, so we must store them in + // order to be able to later pick the nth video mode reliably + let mut video_modes: Vec<_> = monitor.video_modes().collect(); + + for (i, video_mode) in video_modes.iter().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 = video_modes.remove(num); + + println!("Using {}", video_mode); + + video_mode +} diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 39f7af3df8..a805f5d8e8 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::{CursorIcon, WindowBuilder}, + window::{CursorIcon, Fullscreen, WindowBuilder}, }; const WINDOW_COUNT: usize = 3; @@ -44,9 +44,14 @@ fn main() { false => CursorIcon::Default, }), D => window.set_decorations(!state), - F => window.set_fullscreen(match state { - true => Some(window.current_monitor()), - false => None, + F => window.set_fullscreen(match (state, modifiers.alt) { + (true, false) => { + Some(Fullscreen::Borderless(window.current_monitor())) + } + (true, true) => Some(Fullscreen::Exclusive( + window.current_monitor().video_modes().next().unwrap(), + )), + (false, _) => None, }), G => window.set_cursor_grab(state).unwrap(), H => window.set_cursor_visible(!state), diff --git a/src/monitor.rs b/src/monitor.rs index fe8c9c57d4..2c3669bc73 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -52,17 +52,16 @@ impl Iterator for AvailableMonitorsIter { /// - [`MonitorHandle::video_modes`][monitor_get]. /// /// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Derivative)] +#[derivative(Clone, Debug = "transparent")] pub struct VideoMode { - pub(crate) size: (u32, u32), - pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, + pub(crate) video_mode: platform_impl::VideoMode, } impl VideoMode { /// Returns the resolution of this video mode. 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 @@ -74,14 +73,33 @@ impl VideoMode { /// - **Wayland:** Always returns 32. /// - **iOS:** Always returns 32. 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 /// refresh rate is an integer approximation, and you shouldn't rely on this /// value to be exact. 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. + 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() + ) } } diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 02b63515b1..c26385da89 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -17,7 +17,7 @@ use std::{fmt, ops::Deref, sync::Arc}; pub use self::{ event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, }; use crate::{ diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index cf74c64437..d03a9dd997 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -13,10 +13,41 @@ use core_video_sys::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - monitor::VideoMode, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::platform::util::IdRef, }; +#[derive(Derivative)] +#[derivative(Debug, Clone)] +pub struct VideoMode { + pub(crate) size: (u32, u32), + pub(crate) bit_depth: u16, + pub(crate) refresh_rate: u16, + pub(crate) monitor: MonitorHandle, + #[derivative(Debug = "ignore")] + pub(crate) native_mode: CGDisplayMode, +} + +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, PartialEq)] pub struct MonitorHandle(CGDirectDisplayID); @@ -101,7 +132,7 @@ impl MonitorHandle { unsafe { NSScreen::backingScaleFactor(screen) as f64 } } - pub fn video_modes(&self) -> impl Iterator { + pub fn video_modes(&self) -> impl Iterator { let cv_refresh_rate = unsafe { let mut display_link = std::ptr::null_mut(); assert_eq!( @@ -117,6 +148,8 @@ impl MonitorHandle { time.timeScale as i64 / time.timeValue }; + let monitor = self.clone(); + CGDisplayMode::all_display_modes(self.0, std::ptr::null()) .expect("failed to obtain list of display modes") .into_iter() @@ -131,11 +164,15 @@ impl MonitorHandle { cv_refresh_rate }; - VideoMode { + let video_mode = VideoMode { size: (mode.width() as u32, mode.height() as u32), refresh_rate: refresh_rate as u16, bit_depth: mode.bit_depth() as u16, - } + monitor: monitor.clone(), + native_mode: mode, + }; + + RootVideoMode { video_mode } }) } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 67469b126e..190a3cd30a 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -8,37 +8,36 @@ use std::{ }, }; -use cocoa::{ - appkit::{ - self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy, - NSApplicationPresentationOptions, NSColor, NSRequestUserAttentionType, NSScreen, NSView, - NSWindow, NSWindowButton, NSWindowStyleMask, - }, - base::{id, nil}, - foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, -}; -use core_graphics::display::CGDisplay; -use objc::{ - declare::ClassDecl, - runtime::{Class, Object, Sel, BOOL, NO, YES}, -}; - use crate::{ dpi::{LogicalPosition, LogicalSize}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, - monitor::MonitorHandle as RootMonitorHandle, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform::macos::{ActivationPolicy, WindowExtMacOS}, platform_impl::platform::{ app_state::AppState, ffi, - monitor::{self, MonitorHandle}, + monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, view::{self, new_view}, window_delegate::new_delegate, OsError, }, - window::{CursorIcon, WindowAttributes, WindowId as RootWindowId}, + window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, +}; +use cocoa::{ + appkit::{ + self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy, + NSApplicationPresentationOptions, NSColor, NSRequestUserAttentionType, NSScreen, NSView, + NSWindow, NSWindowButton, NSWindowStyleMask, + }, + base::{id, nil}, + foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, +}; +use core_graphics::display::{CGConfigureOption, CGDisplay, CGDisplayMode}; +use objc::{ + declare::ClassDecl, + runtime::{Class, Object, Sel, BOOL, NO, YES}, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -113,11 +112,14 @@ fn create_window( unsafe { let pool = NSAutoreleasePool::new(nil); let screen = match attrs.fullscreen { - Some(ref monitor_id) => { - let monitor_screen = monitor_id.inner.ns_screen(); + Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor })) + | Some(Fullscreen::Exclusive(RootVideoMode { + video_mode: VideoMode { ref monitor, .. }, + })) => { + let monitor_screen = monitor.ns_screen(); Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) } - _ => None, + None => None, }; let frame = match screen { Some(screen) => appkit::NSScreen::frame(screen), @@ -233,12 +235,13 @@ lazy_static! { #[derive(Default)] pub struct SharedState { pub resizable: bool, - pub fullscreen: Option, + pub fullscreen: Option, pub maximized: bool, pub standard_frame: Option, is_simple_fullscreen: bool, pub saved_style: Option, save_presentation_opts: Option, + pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, } impl SharedState { @@ -355,16 +358,7 @@ impl UnownedWindow { let delegate = new_delegate(&window, fullscreen.is_some()); // Set fullscreen mode after we setup everything - if let Some(monitor) = 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)); - } + window.set_fullscreen(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 @@ -627,44 +621,95 @@ impl UnownedWindow { } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { let shared_state_lock = self.shared_state.lock().unwrap(); shared_state_lock.fullscreen.clone() } #[inline] - /// TODO: Right now set_fullscreen do not work on switching monitors - /// in fullscreen mode - pub fn set_fullscreen(&self, monitor: Option) { - let shared_state_lock = self.shared_state.lock().unwrap(); + pub fn set_fullscreen(&self, fullscreen: Option) { + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); if shared_state_lock.is_simple_fullscreen { return; } + let old_fullscreen = shared_state_lock.fullscreen.clone(); + + // TODO: Right now set_fullscreen does not work on switching monitors in + // fullscreen mode! Our best bet is probably to move to the origin of + // the target monitor. + if fullscreen.is_some() { + let current_monitor = &self.current_monitor().inner; + let new_monitor = match fullscreen { + Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor })) => monitor, + Some(Fullscreen::Exclusive(RootVideoMode { + video_mode: VideoMode { ref monitor, .. }, + })) => monitor, + None => current_monitor, + }; + if new_monitor != current_monitor { + unimplemented!("fullscreen on non-current monitor") + } + } + + fn change_display_mode(display: &CGDisplay, video_mode: &CGDisplayMode) { + let config = display + .begin_configuration() + .expect("failed to begin display configuration"); + display + .configure_display_with_display_mode(&config, video_mode) + .expect("failed to set display mode"); + display + .complete_configuration(&config, CGConfigureOption::ConfigureForAppOnly) + .expect("failed to apply new display configuration"); + } - let not_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!() + if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = fullscreen { + let display = CGDisplay::new(video_mode.monitor().inner.native_identifier()); + + // If we're entering exclusive fullscreen and weren't already, store + // the desktop display mode so we can restore it upon exiting + // fullscreen + match old_fullscreen { + Some(Fullscreen::Exclusive(_)) => (), + Some(Fullscreen::Borderless(_)) | None => { + shared_state_lock.saved_desktop_display_mode = + Some((display, display.display_mode().unwrap())) } - (&None, None) | (&Some(_), Some(_)) => return, - _ => (), + }; + + // TODO: capture display so other applications can't mess with our + // fullscreen mode + change_display_mode(&display, &video_mode.native_mode); + } else { + // Restore desktop display mode + if let Some((display, display_mode)) = + shared_state_lock.saved_desktop_display_mode.take() + { + change_display_mode(&display, &display_mode); + // TODO: release display } - trace!("Unlocked shared state in `set_fullscreen`"); - current.is_none() - }; + } - unsafe { - util::toggle_full_screen_async( - *self.ns_window, - *self.ns_view, - not_fullscreen, - Arc::downgrade(&self.shared_state), - ) - }; + drop(shared_state_lock); + trace!("Unlocked shared state in `set_fullscreen`"); + + // Toggle window "fullscreen" (this is the thing that happens when you + // press the maximize button and the window goes fullscreen on a new + // space) if the fullscreen state has changed (note that we only want to + // toggle on transitions between windowed and fullscreen mode, not + // between different fullscreen modes, hence why we're comparing + // `is_some()`) + if old_fullscreen.is_some() != fullscreen.is_some() { + unsafe { + util::toggle_full_screen_async( + *self.ns_window, + *self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ) + }; + } } #[inline] diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index c9680d892a..50d3c914bf 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -22,7 +22,7 @@ use crate::{ util::{self, IdRef}, window::{get_window_id, UnownedWindow}, }, - window::WindowId, + window::{Fullscreen, WindowId}, }; pub struct WindowDelegateState { @@ -415,7 +415,7 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { state.with_window(|window| { let monitor = window.current_monitor(); trace!("Locked shared state in `window_did_enter_fullscreen`"); - window.shared_state.lock().unwrap().fullscreen = Some(monitor); + window.shared_state.lock().unwrap().fullscreen = Some(Fullscreen::Borderless(monitor)); trace!("Unlocked shared state in `window_will_enter_fullscreen`"); }); state.initial_fullscreen = false; diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 58d86faf00..bd28640af4 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -4,7 +4,7 @@ use winapi::{self, shared::windef::HWND}; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::Window, }; diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index e4ee3f92f3..34f6c1d5e9 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -14,32 +14,68 @@ use std::{ use super::{util, EventLoop}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - monitor::VideoMode, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::platform::{ dpi::{dpi_to_scale_factor, get_monitor_dpi}, window::Window, }, }; +#[derive(Derivative)] +#[derivative(Debug, Clone, Eq, PartialEq, Hash)] +pub struct VideoMode { + 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_video_mode: wingdi::DEVMODEW, +} + +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(), + } + } +} + /// Win32 implementation of the main `MonitorHandle` object. #[derive(Derivative)] -#[derivative(Debug, Clone)] +#[derivative(Debug, Clone, Eq, PartialEq, Hash)] pub struct MonitorHandle { /// Monitor handle. hmonitor: HMonitor, - #[derivative(Debug = "ignore")] + #[derivative(Hash = "ignore", PartialEq = "ignore", Debug = "ignore")] monitor_info: winuser::MONITORINFOEXW, /// The system name of the monitor. + #[derivative(Hash = "ignore", PartialEq = "ignore")] monitor_name: String, /// True if this is the primary monitor. + #[derivative(Hash = "ignore", PartialEq = "ignore")] primary: bool, /// The position of the monitor in pixels on the desktop. /// /// A window that is positioned at these coordinates will overlap the monitor. + #[derivative(Hash = "ignore", PartialEq = "ignore")] position: (i32, i32), /// The current resolution in pixels on the monitor. + #[derivative(Hash = "ignore", PartialEq = "ignore")] dimensions: (u32, u32), /// DPI scale factor. + #[derivative(Hash = "ignore", PartialEq = "ignore")] hidpi_factor: f64, } @@ -47,7 +83,7 @@ pub struct MonitorHandle { // For more info see: // https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/396 -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] struct HMonitor(HMONITOR); unsafe impl Send for HMonitor {} @@ -182,7 +218,7 @@ impl MonitorHandle { } #[inline] - pub fn video_modes(&self) -> impl Iterator { + pub fn video_modes(&self) -> impl Iterator { // EnumDisplaySettingsExW can return duplicate values (or some of the // fields are probably changing, but we aren't looking at those fields // anyway), so we're using a HashSet deduplicate @@ -209,10 +245,14 @@ impl MonitorHandle { size: (mode.dmPelsWidth, mode.dmPelsHeight), bit_depth: mode.dmBitsPerPel as u16, refresh_rate: mode.dmDisplayFrequency as u16, + monitor: self.clone(), + native_video_mode: mode, }); } } - modes.into_iter() + modes + .into_iter() + .map(|video_mode| RootVideoMode { video_mode }) } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 54089219f8..11959317f3 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -46,7 +46,7 @@ use crate::{ window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Icon, WindowAttributes}, + window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -424,49 +424,19 @@ impl Window { } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { let window_state = self.window_state.lock(); window_state.fullscreen.clone() } #[inline] - pub fn set_fullscreen(&self, monitor: Option) { + pub fn set_fullscreen(&self, fullscreen: Option) { unsafe { let window = self.window.clone(); let window_state = Arc::clone(&self.window_state); - match &monitor { - &Some(RootMonitorHandle { ref inner }) => { - let (x, y): (i32, i32) = inner.position().into(); - let (width, height): (u32, u32) = inner.size().into(); - - let mut monitor = monitor.clone(); - self.thread_executor.execute_in_thread(move || { - let mut window_state_lock = window_state.lock(); - - let client_rect = - util::get_client_rect(window.0).expect("get client rect failed!"); - window_state_lock.saved_window = Some(SavedWindow { - client_rect, - dpi_factor: window_state_lock.dpi_factor, - }); - - window_state_lock.fullscreen = monitor.take(); - WindowState::refresh_window_state( - window_state_lock, - window.0, - Some(RECT { - left: x, - top: y, - right: x + width as c_int, - bottom: y + height as c_int, - }), - ); - - mark_fullscreen(window.0, true); - }); - } - &None => { + match fullscreen { + None => { self.thread_executor.execute_in_thread(move || { let mut window_state_lock = window_state.lock(); window_state_lock.fullscreen = None; @@ -487,6 +457,43 @@ impl Window { } mark_fullscreen(window.0, false); + return; + }); + } + Some(fullscreen) => { + let (position, size): ((i32, i32), (u32, u32)) = match fullscreen { + Fullscreen::Exclusive(ref video_mode) => ( + video_mode.monitor().position().into(), + video_mode.size().into(), + ), + Fullscreen::Borderless(ref monitor) => { + (monitor.position().into(), monitor.size().into()) + } + }; + + self.thread_executor.execute_in_thread(move || { + let mut window_state_lock = window_state.lock(); + + let client_rect = + util::get_client_rect(window.0).expect("get client rect failed!"); + window_state_lock.saved_window = Some(SavedWindow { + client_rect, + dpi_factor: window_state_lock.dpi_factor, + }); + + window_state_lock.fullscreen = Some(fullscreen.clone()); + WindowState::refresh_window_state( + window_state_lock, + window.0, + Some(RECT { + left: position.0, + top: position.1, + right: position.0 + size.0 as c_int, + bottom: position.1 + size.1 as c_int, + }), + ); + + mark_fullscreen(window.0, true); }); } } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 14cc71445d..2d38b41623 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,11 +1,10 @@ use crate::{ dpi::LogicalSize, - monitor::MonitorHandle, platform_impl::platform::{event_loop, icon::WinIcon, util}, - window::{CursorIcon, WindowAttributes}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; use parking_lot::MutexGuard; -use std::{io, ptr}; +use std::{ffi::OsStr, io, os::windows::ffi::OsStrExt, ptr}; use winapi::{ shared::{ minwindef::DWORD, @@ -29,7 +28,7 @@ pub struct WindowState { pub saved_window: Option, pub dpi_factor: f64, - pub fullscreen: Option, + pub fullscreen: Option, window_flags: WindowFlags, } @@ -134,6 +133,53 @@ impl WindowState { .set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen); let new_flags = this.window_flags; + // Change video mode if necessary + if old_flags & WindowFlags::MARKER_FULLSCREEN != new_flags & WindowFlags::MARKER_FULLSCREEN + { + let res = if let Some(Fullscreen::Exclusive(ref video_mode)) = this.fullscreen { + let monitor = video_mode.monitor(); + + // The display name on Windows is the same we get from + // EnumDisplayDevices, so it's good for ChangeDisplaySettingsExW + let mut display_name = OsStr::new(monitor.name().as_ref().unwrap()) + .encode_wide() + .collect::>(); + // encode_wide does not add a null-terminator but + // ChangeDisplaySettingsExW requires a null-terminated string + display_name.push(0); + + let mut native_video_mode = video_mode.video_mode.native_video_mode.clone(); + + unsafe { + winuser::ChangeDisplaySettingsExW( + display_name.as_ptr(), + &mut native_video_mode, + std::ptr::null_mut(), + winuser::CDS_FULLSCREEN, + std::ptr::null_mut(), + ) + } + } else { + unsafe { + winuser::ChangeDisplaySettingsExW( + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + winuser::CDS_FULLSCREEN, + std::ptr::null_mut(), + ) + } + }; + + // These are separate asserts so we can easily see which error we + // hit here + 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); + } + drop(this); old_flags.apply_diff(window, new_flags, set_client_rect); } diff --git a/src/window.rs b/src/window.rs index ccaf00a38d..9c0384eb4f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -5,7 +5,7 @@ use crate::{ dpi::{LogicalPosition, LogicalSize}, error::{ExternalError, NotSupportedError, OsError}, event_loop::EventLoopWindowTarget, - monitor::{AvailableMonitorsIter, MonitorHandle}, + monitor::{AvailableMonitorsIter, MonitorHandle, VideoMode}, platform_impl, }; @@ -110,7 +110,7 @@ pub struct WindowAttributes { /// Whether the window should be set as fullscreen upon creation. /// /// The default is `None`. - pub fullscreen: Option, + pub fullscreen: Option, /// The title of the window in the title bar. /// @@ -222,10 +222,10 @@ impl WindowBuilder { 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 #[inline] - pub fn with_fullscreen(mut self, monitor: Option) -> WindowBuilder { + pub fn with_fullscreen(mut self, monitor: Option) -> WindowBuilder { self.window.fullscreen = monitor; self } @@ -288,20 +288,9 @@ impl WindowBuilder { /// Possible causes of error include denied permission, incompatible system, and lack of memory. #[inline] pub fn build( - mut self, + self, window_target: &EventLoopWindowTarget, ) -> Result { - self.window.inner_size = Some(self.window.inner_size.unwrap_or_else(|| { - if let Some(ref monitor) = self.window.fullscreen { - // resizing the window to the dimensions of the monitor when fullscreen - LogicalSize::from_physical(monitor.size(), monitor.hidpi_factor()) // DPI factor applies here since this is a borderless window and not real fullscreen - } else { - // default dimensions - (1024, 768).into() - } - })); - - // building platform_impl::Window::new(&window_target.p, self.window, self.platform_specific) .map(|window| Window { window }) } @@ -545,8 +534,8 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. #[inline] - pub fn set_fullscreen(&self, monitor: Option) { - self.window.set_fullscreen(monitor) + pub fn set_fullscreen(&self, fullscreen: Option) { + self.window.set_fullscreen(fullscreen) } /// Gets the window's current fullscreen state. @@ -555,7 +544,7 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { self.window.fullscreen() } @@ -764,3 +753,9 @@ impl Default for CursorIcon { CursorIcon::Default } } + +#[derive(Clone, Debug)] +pub enum Fullscreen { + Exclusive(VideoMode), + Borderless(MonitorHandle), +} From 3dd7184c3487b9b497bd8d991b36970c2fbff550 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Mon, 17 Jun 2019 14:22:23 +0300 Subject: [PATCH 02/33] Add `WindowExtMacOS::set_fullscreen_presentation_options` --- CHANGELOG.md | 1 + examples/fullscreen.rs | 11 ++++++++ src/platform/macos.rs | 23 ++++++++++++++++ src/platform_impl/macos/window.rs | 14 ++++++++++ src/platform_impl/macos/window_delegate.rs | 32 ++++++++++++++++++++-- 5 files changed, 79 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d44694981a..a9b39adc77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - On Wayland, the window now exists even if nothing has been drawn. - On Windows, fix initial dimensions of a fullscreen window. - On Windows, Fix transparent borderless windows rendering wrong. +- On macOS, add `WindowExtMacOS::set_fullscreen_presentation_options` for hiding the dock and the menu bar in fullscreen mode. # Version 0.19.1 (2019-04-08) diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index edc20b4ae1..5661342bfd 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,7 +1,11 @@ +#[cfg(target_os = "macos")] +use cocoa::appkit::NSApplicationPresentationOptions; use std::io::{stdin, stdout, Write}; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::monitor::{MonitorHandle, VideoMode}; +#[cfg(target_os = "macos")] +use winit::platform::macos::WindowExtMacOS; use winit::window::{Fullscreen, WindowBuilder}; fn main() { @@ -30,6 +34,13 @@ fn main() { .build(&event_loop) .unwrap(); + #[cfg(target_os = "macos")] + window.set_fullscreen_presentation_options( + NSApplicationPresentationOptions::NSApplicationPresentationFullScreen + | NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar, + ); + event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/src/platform/macos.rs b/src/platform/macos.rs index da02a1dd2f..c43af08db8 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -1,5 +1,7 @@ #![cfg(target_os = "macos")] +use cocoa::appkit::NSApplicationPresentationOptions; + use std::os::raw::c_void; use crate::{ @@ -31,6 +33,18 @@ pub trait WindowExtMacOS { /// Returns whether or not the window is in simple fullscreen mode. fn simple_fullscreen(&self) -> bool; + /// The presentation options the window should use when transitioning to + /// fullscreen mode. This allows the application to, for example, hide the + /// menu bar and the dock while in fullscreen mode. See + /// [`NSApplicationPresentationOptions`][options] for the possible values. + /// `NSApplicationPresentationFullScreen` must be included in the options. + /// + /// [options]: https://developer.apple.com/documentation/appkit/nsapplicationpresentationoptions?language=objc + fn set_fullscreen_presentation_options( + &self, + proposed_options: NSApplicationPresentationOptions, + ); + /// Toggles a fullscreen mode that doesn't require a new macOS space. /// Returns a boolean indicating whether the transition was successful (this /// won't work if the window was already in the native fullscreen). @@ -62,6 +76,15 @@ impl WindowExtMacOS for Window { self.window.simple_fullscreen() } + #[inline] + fn set_fullscreen_presentation_options( + &self, + proposed_options: NSApplicationPresentationOptions, + ) { + self.window + .set_fullscreen_presentation_options(proposed_options); + } + #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { self.window.set_simple_fullscreen(fullscreen) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 190a3cd30a..095a13c00c 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -240,8 +240,13 @@ pub struct SharedState { pub standard_frame: Option, is_simple_fullscreen: bool, pub saved_style: Option, + /// Presentation options saved before entering `set_simple_fullscreen`, and + /// restored upon exiting it save_presentation_opts: Option, pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, + /// Requested fullscreen presentation options via + /// `WindowExtMacOS::set_fullscreen_presentation_options` + pub fullscreen_presentation_options: Option, } impl SharedState { @@ -838,6 +843,15 @@ impl WindowExtMacOS for UnownedWindow { shared_state_lock.is_simple_fullscreen } + #[inline] + fn set_fullscreen_presentation_options( + &self, + proposed_options: NSApplicationPresentationOptions, + ) { + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.fullscreen_presentation_options = Some(proposed_options); + } + #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { let mut shared_state_lock = self.shared_state.lock().unwrap(); diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 50d3c914bf..2afab9727e 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -5,9 +5,9 @@ use std::{ }; use cocoa::{ - appkit::{self, NSView, NSWindow}, + appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow}, base::{id, nil}, - foundation::NSAutoreleasePool, + foundation::{NSAutoreleasePool, NSUInteger}, }; use objc::{ declare::ClassDecl, @@ -182,6 +182,11 @@ lazy_static! { 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( sel!(windowDidEnterFullScreen:), window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id), @@ -408,6 +413,29 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { trace!("Completed `windowWillEnterFullscreen:`"); } +extern "C" fn window_will_use_fullscreen_presentation_options( + this: &Object, + _: Sel, + _: id, + proposed_options: NSUInteger, +) -> NSUInteger { + let state = unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + &mut *(state_ptr as *mut WindowDelegateState) + }; + let window = state.window.upgrade().unwrap(); + trace!("Locked shared state in `window_will_use_fullscreen_presentation_options`"); + let opts = window + .shared_state + .lock() + .unwrap() + .fullscreen_presentation_options + .map(|x| x.bits()) + .unwrap_or(proposed_options); + trace!("Unlocked shared state in `window_will_use_fullscreen_presentation_options`"); + opts +} + /// Invoked when entered fullscreen extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { trace!("Triggered `windowDidEnterFullscreen:`"); From bbd88af205eecc51a652858e63a2d82f0aaf2b95 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Mon, 17 Jun 2019 14:35:49 +0300 Subject: [PATCH 03/33] Capture display for exclusive fullscreen on macOS --- CHANGELOG.md | 4 + src/monitor.rs | 4 +- src/platform_impl/macos/ffi.rs | 61 ++++++++++ src/platform_impl/macos/monitor.rs | 5 +- src/platform_impl/macos/observer.rs | 15 +-- src/platform_impl/macos/window.rs | 129 ++++++++++++++------- src/platform_impl/macos/window_delegate.rs | 17 ++- src/window.rs | 22 +++- 8 files changed, 198 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b39adc77..170fe93313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,10 @@ - On Windows, fix initial dimensions of a fullscreen window. - On Windows, Fix transparent borderless windows rendering wrong. - On macOS, add `WindowExtMacOS::set_fullscreen_presentation_options` for hiding the dock and the menu bar in fullscreen mode. +- `Window::set_fullscreen` now takes `Option` where `Fullscreen` + consists of `Fullscreen::Exclusive(VideoMode)` and + `Fullscreen::Borderless(MonitorHandle)` variants. + - Adds support for exclusive fullscreen mode. # Version 0.19.1 (2019-04-08) diff --git a/src/monitor.rs b/src/monitor.rs index 2c3669bc73..10714fe3b4 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -53,7 +53,7 @@ impl Iterator for AvailableMonitorsIter { /// /// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes #[derive(Derivative)] -#[derivative(Clone, Debug = "transparent")] +#[derivative(Clone, Debug = "transparent", PartialEq)] pub struct VideoMode { pub(crate) video_mode: platform_impl::VideoMode, } @@ -108,7 +108,7 @@ impl std::fmt::Display for VideoMode { /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. /// /// [`Window`]: ../window/struct.Window.html -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct MonitorHandle { pub(crate) inner: platform_impl::MonitorHandle, } diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 7ddcdf6fd1..7ce99610ff 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -108,3 +108,64 @@ pub enum NSWindowLevel { NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _, NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, } + +use core_graphics::{ + base::CGError, + display::{CGDirectDisplayID, CGDisplayConfigRef}, +}; + +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; + +#[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; +} diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index d03a9dd997..b2dc91e8dc 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -18,10 +18,13 @@ use crate::{ }; #[derive(Derivative)] -#[derivative(Debug, Clone)] +#[derivative(Debug, Clone, PartialEq)] pub struct VideoMode { + #[derivative(PartialEq = "ignore")] pub(crate) size: (u32, u32), + #[derivative(PartialEq = "ignore")] pub(crate) bit_depth: u16, + #[derivative(PartialEq = "ignore")] pub(crate) refresh_rate: u16, pub(crate) monitor: MonitorHandle, #[derivative(Debug = "ignore")] diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index ddd20a8f60..fa69b3ca28 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -1,6 +1,6 @@ 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")] extern "C" { @@ -13,7 +13,7 @@ extern "C" { pub fn CFRunLoopObserverCreate( allocator: CFAllocatorRef, activities: CFOptionFlags, - repeats: Boolean, + repeats: ffi::Boolean, order: CFIndex, callout: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, @@ -51,11 +51,6 @@ extern "C" { 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 type CFAllocatorRef = *mut CFAllocator; pub enum CFRunLoop {} @@ -102,7 +97,7 @@ pub struct CFRunLoopSourceContext { pub retain: extern "C" fn(*const c_void) -> *const c_void, pub release: extern "C" fn(*const c_void), 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 schedule: 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( ptr::null_mut(), flags, - TRUE, // Indicates we want this to run repeatedly - priority, // The lower the value, the sooner this will run + ffi::TRUE, // Indicates we want this to run repeatedly + priority, // The lower the value, the sooner this will run handler, ptr::null_mut(), ); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 095a13c00c..f4beab189c 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -594,18 +594,28 @@ impl UnownedWindow { } pub(crate) fn restore_state_from_fullscreen(&self) { - let maximized = { - trace!("Locked shared state in `restore_state_from_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); + trace!("Locked shared state in `restore_state_from_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.fullscreen = None; + if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = + shared_state_lock.fullscreen.take() + { + unsafe { + ffi::CGRestorePermanentDisplayConfiguration(); + assert_eq!( + ffi::CGDisplayRelease(video_mode.monitor().inner.native_identifier()), + ffi::kCGErrorSuccess + ); + } + } - 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); - shared_state_lock.maximized - }; + drop(shared_state_lock); trace!("Unocked shared state in `restore_state_from_fullscreen`"); + + self.set_style_mask_async(mask); self.set_maximized(maximized); } @@ -636,9 +646,14 @@ impl UnownedWindow { trace!("Locked shared state in `set_fullscreen`"); let mut shared_state_lock = self.shared_state.lock().unwrap(); if shared_state_lock.is_simple_fullscreen { + trace!("Unlocked shared state in `set_fullscreen`"); return; } let old_fullscreen = shared_state_lock.fullscreen.clone(); + if fullscreen == old_fullscreen { + trace!("Unlocked shared state in `set_fullscreen`"); + return; + } // TODO: Right now set_fullscreen does not work on switching monitors in // fullscreen mode! Our best bet is probably to move to the origin of @@ -657,54 +672,80 @@ impl UnownedWindow { } } - fn change_display_mode(display: &CGDisplay, video_mode: &CGDisplayMode) { + if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = fullscreen { + let display_id = video_mode.monitor().inner.native_identifier(); + let display = CGDisplay::new(display_id); + + // 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 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); + } + let config = display .begin_configuration() .expect("failed to begin display configuration"); display - .configure_display_with_display_mode(&config, video_mode) + .configure_display_with_display_mode(&config, &video_mode.native_mode) .expect("failed to set display mode"); display .complete_configuration(&config, CGConfigureOption::ConfigureForAppOnly) .expect("failed to apply new display configuration"); - } - if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = fullscreen { - let display = CGDisplay::new(video_mode.monitor().inner.native_identifier()); - - // If we're entering exclusive fullscreen and weren't already, store - // the desktop display mode so we can restore it upon exiting - // fullscreen - match old_fullscreen { - Some(Fullscreen::Exclusive(_)) => (), - Some(Fullscreen::Borderless(_)) | None => { - shared_state_lock.saved_desktop_display_mode = - Some((display, display.display_mode().unwrap())) + // After the display has been configured, fade back in + // asynchronously + unsafe { + 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); } - }; - - // TODO: capture display so other applications can't mess with our - // fullscreen mode - change_display_mode(&display, &video_mode.native_mode); - } else { - // Restore desktop display mode - if let Some((display, display_mode)) = - shared_state_lock.saved_desktop_display_mode.take() - { - change_display_mode(&display, &display_mode); - // TODO: release display } - } - drop(shared_state_lock); - trace!("Unlocked shared state in `set_fullscreen`"); + // This is set in `window_did_enter_fullscreen` for borderless + // fullscreen, but for exclusive mode we set it here, as we can only + // enter exclusive mode from this function (and not also by user + // action like borderless) + shared_state_lock.fullscreen = fullscreen.clone(); + } - // Toggle window "fullscreen" (this is the thing that happens when you - // press the maximize button and the window goes fullscreen on a new - // space) if the fullscreen state has changed (note that we only want to - // toggle on transitions between windowed and fullscreen mode, not - // between different fullscreen modes, hence why we're comparing - // `is_some()`) if old_fullscreen.is_some() != fullscreen.is_some() { unsafe { util::toggle_full_screen_async( @@ -715,6 +756,8 @@ impl UnownedWindow { ) }; } + + trace!("Unlocked shared state in `set_fullscreen`"); } #[inline] diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 2afab9727e..92a3eab2de 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -443,8 +443,21 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { state.with_window(|window| { let monitor = window.current_monitor(); trace!("Locked shared state in `window_did_enter_fullscreen`"); - window.shared_state.lock().unwrap().fullscreen = Some(Fullscreen::Borderless(monitor)); - trace!("Unlocked shared state in `window_will_enter_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + 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` shouldn't be triggered if we're + // already in fullscreen + Some(Fullscreen::Borderless(_)) => unreachable!(), + // Otherwise, update the fullscreen state (we reached fullscreen + // either via `set_fullscreen` or by the user fullscreening the + // window from the fullscreen button) + None => shared_state.fullscreen = Some(Fullscreen::Borderless(monitor)), + } + trace!("Unlocked shared state in `window_did_enter_fullscreen`"); }); state.initial_fullscreen = false; }); diff --git a/src/window.rs b/src/window.rs index 9c0384eb4f..66a4df2efc 100644 --- a/src/window.rs +++ b/src/window.rs @@ -532,7 +532,27 @@ impl Window { /// /// ## 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. + /// + /// *Note!* For games and kiosk applications, you will generally want to + /// hide and disable the dock and the menu bar while in fullscreen mode. + /// This applies to both exclusive and borderless mode. See + /// [`WindowExtMacOs::set_fullscreen_presentation_options`][presentation]. /// - **iOS:** Can only be called on the main thread. + /// + /// [simple]: + /// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen + /// [presentation]: + /// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_fullscreen_presentation_options #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { self.window.set_fullscreen(fullscreen) @@ -754,7 +774,7 @@ impl Default for CursorIcon { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum Fullscreen { Exclusive(VideoMode), Borderless(MonitorHandle), From cfed2f3714b5e12cc19498d04f1a650cdac5c67c Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Mon, 17 Jun 2019 18:45:50 +0300 Subject: [PATCH 04/33] Fix applying video mode on macOS after a fullscreen cycle --- src/platform_impl/macos/monitor.rs | 5 +---- src/platform_impl/macos/window.rs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index b2dc91e8dc..6fec988e1d 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -20,14 +20,11 @@ use crate::{ #[derive(Derivative)] #[derivative(Debug, Clone, PartialEq)] pub struct VideoMode { - #[derivative(PartialEq = "ignore")] pub(crate) size: (u32, u32), - #[derivative(PartialEq = "ignore")] pub(crate) bit_depth: u16, - #[derivative(PartialEq = "ignore")] pub(crate) refresh_rate: u16, pub(crate) monitor: MonitorHandle, - #[derivative(Debug = "ignore")] + #[derivative(Debug = "ignore", PartialEq = "ignore")] pub(crate) native_mode: CGDisplayMode, } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index f4beab189c..1491c67665 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -672,7 +672,7 @@ impl UnownedWindow { } } - if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = fullscreen { + if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen { let display_id = video_mode.monitor().inner.native_identifier(); let display = CGDisplay::new(display_id); @@ -711,11 +711,23 @@ impl UnownedWindow { assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess); } + // Capturing (and subsequently releasing) the display invalidates + // all display modes, so the stored display mode may no longer be + // valid. Calling functions on it still yields correct results, but + // configuring the display to use that display mode will no longer + // succeed, so query display modes anew and look for a mode that + // matches the description of our stored mode. + let video_mode = video_mode + .monitor() + .video_modes() + .find(|x| x == video_mode) + .expect("failed to find a video mode matching the stored video mode"); + let config = display .begin_configuration() .expect("failed to begin display configuration"); display - .configure_display_with_display_mode(&config, &video_mode.native_mode) + .configure_display_with_display_mode(&config, &video_mode.video_mode.native_mode) .expect("failed to set display mode"); display .complete_configuration(&config, CGConfigureOption::ConfigureForAppOnly) From 047c017ce0f787aacb82c6ec6ecb087cfe1b9bda Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 18 Jun 2019 10:39:35 +0300 Subject: [PATCH 05/33] Fix compilation on iOS --- src/platform_impl/ios/mod.rs | 2 +- src/platform_impl/ios/monitor.rs | 42 +++++++++++++++++++++++++++----- src/platform_impl/ios/view.rs | 25 ++++++++++--------- src/platform_impl/ios/window.rs | 22 +++++++++-------- 4 files changed, 63 insertions(+), 28 deletions(-) diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index dfb659f949..3141dea4cc 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -79,7 +79,7 @@ use std::fmt; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, }; diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index c75b7ef6e3..d1200d1693 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -6,13 +6,39 @@ use std::{ use crate::{ 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::{ - id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger, -}; +#[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 { + 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)] pub struct Inner { uiscreen: id, } @@ -25,6 +51,7 @@ impl Drop for Inner { } } +#[derive(PartialEq, Eq, Hash)] pub struct MonitorHandle { inner: Inner, } @@ -140,7 +167,7 @@ impl Inner { } } - pub fn video_modes(&self) -> impl Iterator { + pub fn video_modes(&self) -> impl Iterator { let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] }; let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] }; @@ -155,10 +182,13 @@ impl Inner { size: (size.width as u32, size.height as u32), bit_depth: 32, refresh_rate: refresh_rate as u16, + monitor: MonitorHandle::retained_new(self.uiscreen), }); } - modes.into_iter() + modes + .into_iter() + .map(|video_mode| RootVideoMode { video_mode }) } } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index b174b75a78..9504781467 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -8,15 +8,14 @@ use objc::{ use crate::{ event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, - window::{WindowAttributes, WindowId as RootWindowId}, -}; - -use crate::platform_impl::platform::{ - app_state::AppState, - event_loop, - ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, - window::PlatformSpecificWindowBuilderAttributes, - DeviceId, + platform_impl::platform::{ + app_state::AppState, + event_loop, + ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, + window::PlatformSpecificWindowBuilderAttributes, + DeviceId, + }, + window::{Fullscreen, WindowAttributes, WindowId as RootWindowId}, }; // requires main thread @@ -366,8 +365,12 @@ pub unsafe fn create_window( if let Some(hidpi_factor) = platform_attributes.hidpi_factor { let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; } - if let &Some(ref monitor) = &window_attributes.fullscreen { - let () = msg_send![window, setScreen:monitor.ui_screen()]; + match window_attributes.fullscreen { + Some(Fullscreen::Exclusive(_)) => unimplemented!(), + Some(Fullscreen::Borderless(ref monitor)) => { + msg_send![window, setScreen:monitor.ui_screen()] + } + None => (), } window diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index ea4f022f43..05b68ea4c0 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -17,7 +17,7 @@ use crate::{ ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask}, monitor, view, EventLoopWindowTarget, MonitorHandle, }, - window::{CursorIcon, WindowAttributes}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; pub struct Inner { @@ -157,10 +157,11 @@ impl Inner { warn!("`Window::set_maximized` is ignored on iOS") } - pub fn set_fullscreen(&self, monitor: Option) { + pub fn set_fullscreen(&self, monitor: Option) { unsafe { 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 current: id = msg_send![self.window, screen]; let bounds: CGRect = msg_send![uiscreen, bounds]; @@ -176,7 +177,7 @@ impl Inner { } } - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { unsafe { let monitor = self.current_monitor(); 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.height == screen_bounds.size.height { - Some(monitor) + Some(Fullscreen::Borderless(monitor)) } else { None } @@ -293,11 +294,12 @@ impl Window { // TODO: transparency, visible unsafe { - let screen = window_attributes - .fullscreen - .as_ref() - .map(|screen| screen.ui_screen() as _) - .unwrap_or_else(|| monitor::main_uiscreen().ui_screen()); + let screen = match window_attributes.fullscreen { + Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO: do we set the frame to video mode bounds instead of screen bounds? + Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id, + None => monitor::main_uiscreen().ui_screen(), + }; + let screen_bounds: CGRect = msg_send![screen, bounds]; let frame = match window_attributes.inner_size { From e293bd7bae187eb26e1c1545c725e9b0ca7770b5 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 18 Jun 2019 15:08:43 +0300 Subject: [PATCH 06/33] Set monitor appropriately for fullscreen on macOS --- examples/fullscreen.rs | 4 +--- src/platform_impl/macos/window.rs | 33 +++++++++++++++++++------------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 5661342bfd..f7613b1575 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -24,7 +24,6 @@ fn main() { _ => unreachable!("Please enter a valid number"), }); - let mut is_fullscreen = true; let mut is_maximized = false; let mut decorations = true; @@ -58,8 +57,7 @@ fn main() { } => match (virtual_code, state) { (VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit, (VirtualKeyCode::F, ElementState::Pressed) => { - is_fullscreen = !is_fullscreen; - if !is_fullscreen { + if window.fullscreen().is_some() { window.set_fullscreen(None); } else { window.set_fullscreen(fullscreen.clone()); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 1491c67665..e08a383b5a 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -655,20 +655,27 @@ impl UnownedWindow { return; } - // TODO: Right now set_fullscreen does not work on switching monitors in - // fullscreen mode! Our best bet is probably to move to the origin of - // the target monitor. - if fullscreen.is_some() { - let current_monitor = &self.current_monitor().inner; - let new_monitor = match fullscreen { - Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor })) => monitor, - Some(Fullscreen::Exclusive(RootVideoMode { + // If the fullscreen is on a different monitor, we must move the window + // to that monitor before we toggle fullscreen + if let Some(ref fullscreen) = fullscreen { + let new_screen = match fullscreen { + Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor, + Fullscreen::Exclusive(RootVideoMode { video_mode: VideoMode { ref monitor, .. }, - })) => monitor, - None => current_monitor, - }; - if new_monitor != current_monitor { - unimplemented!("fullscreen on non-current 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); + } } } From 372056e61528361bc7c0051150879adf30ec27d9 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 18 Jun 2019 16:18:02 +0300 Subject: [PATCH 07/33] Fix exclusive to borderless fullscreen transitions on macOS --- src/platform_impl/macos/ffi.rs | 11 +++--- src/platform_impl/macos/monitor.rs | 15 +++++++- src/platform_impl/macos/window.rs | 61 ++++++++++++++++++++++++------ 3 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 7ce99610ff..dd64bcd54f 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -6,6 +6,11 @@ use cocoa::{ base::id, foundation::{NSInteger, NSUInteger}, }; +use core_foundation::uuid::CFUUIDRef; +use core_graphics::{ + base::CGError, + display::{CGDirectDisplayID, CGDisplayConfigRef}, +}; use objc; pub const NSNotFound: NSInteger = NSInteger::max_value(); @@ -109,11 +114,6 @@ pub enum NSWindowLevel { NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, } -use core_graphics::{ - base::CGError, - display::{CGDirectDisplayID, CGDisplayConfigRef}, -}; - pub type CGDisplayFadeInterval = f32; pub type CGDisplayReservationInterval = f32; pub type CGDisplayBlendFraction = f32; @@ -168,4 +168,5 @@ extern "C" { synchronous: Boolean, ) -> CGError; pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError; + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; } diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 6fec988e1d..d02a0c5da6 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -11,6 +11,7 @@ use core_video_sys::{ CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease, }; +use super::ffi; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, @@ -48,9 +49,21 @@ impl VideoMode { } } -#[derive(Clone, PartialEq)] +#[derive(Clone)] 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) + } + } +} + pub fn available_monitors() -> VecDeque { if let Ok(displays) = CGDisplay::active_displays() { let mut monitors = VecDeque::with_capacity(displays.len()); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index e08a383b5a..6aea389104 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -593,10 +593,29 @@ 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) { + self.restore_display_mode(); + trace!("Locked shared state in `restore_state_from_fullscreen`"); let mut shared_state_lock = self.shared_state.lock().unwrap(); + let maximized = shared_state_lock.maximized; + let mask = self.saved_style(&mut *shared_state_lock); + + drop(shared_state_lock); + trace!("Unocked shared state in `restore_state_from_fullscreen`"); + + self.set_style_mask_async(mask); + self.set_maximized(maximized); + } + + fn restore_display_mode(&self) { + trace!("Locked shared state in `restore_display_mode`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = shared_state_lock.fullscreen.take() { @@ -609,14 +628,7 @@ impl UnownedWindow { } } - let maximized = shared_state_lock.maximized; - let mask = self.saved_style(&mut *shared_state_lock); - - drop(shared_state_lock); - trace!("Unocked shared state in `restore_state_from_fullscreen`"); - - self.set_style_mask_async(mask); - self.set_maximized(maximized); + trace!("Unlocked shared state in `restore_display_mode`"); } #[inline] @@ -644,7 +656,7 @@ impl UnownedWindow { #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { trace!("Locked shared state in `set_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); + let shared_state_lock = self.shared_state.lock().unwrap(); if shared_state_lock.is_simple_fullscreen { trace!("Unlocked shared state in `set_fullscreen`"); return; @@ -654,6 +666,29 @@ impl UnownedWindow { trace!("Unlocked shared state in `set_fullscreen`"); return; } + trace!("Unlocked shared state in `set_fullscreen`"); + drop(shared_state_lock); + + // If we're switching from exclusive to borderless mode, we need to + // restore the previous display mode (for switching to windowed mode + // this is handled in the `restore_state_from_fullscreen` callback) + match (&old_fullscreen, &fullscreen) { + (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { + self.restore_display_mode(); + + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + + // This is usually set in `window_did_enter_fullscreen` for + // borderless fullscreen, but since we're already in fullscreen, + // that isn't triggered and we must set it here + shared_state_lock.fullscreen = fullscreen.clone(); + + drop(shared_state_lock); + trace!("Unlocked shared state in `set_fullscreen`"); + } + _ => (), + } // If the fullscreen is on a different monitor, we must move the window // to that monitor before we toggle fullscreen @@ -758,11 +793,17 @@ impl UnownedWindow { } } + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + // This is set in `window_did_enter_fullscreen` for borderless // fullscreen, but for exclusive mode we set it here, as we can only // enter exclusive mode from this function (and not also by user // action like borderless) shared_state_lock.fullscreen = fullscreen.clone(); + + drop(shared_state_lock); + trace!("Unlocked shared state in `set_fullscreen`"); } if old_fullscreen.is_some() != fullscreen.is_some() { @@ -775,8 +816,6 @@ impl UnownedWindow { ) }; } - - trace!("Unlocked shared state in `set_fullscreen`"); } #[inline] From 0f136ef16f09ecc5dee654511b07960f6e81f07a Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Wed, 19 Jun 2019 11:40:29 +0300 Subject: [PATCH 08/33] Fix borderless to exclusive fullscreen transition on macOS --- CHANGELOG.md | 1 + examples/fullscreen.rs | 11 --- src/platform/macos.rs | 23 ----- src/platform_impl/macos/ffi.rs | 3 + src/platform_impl/macos/util/async.rs | 5 +- src/platform_impl/macos/window.rs | 101 +++++++++------------ src/platform_impl/macos/window_delegate.rs | 43 ++++----- src/window.rs | 7 +- 8 files changed, 74 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 170fe93313..5d0995c752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ - On Windows, fix initial dimensions of a fullscreen window. - On Windows, Fix transparent borderless windows rendering wrong. - On macOS, add `WindowExtMacOS::set_fullscreen_presentation_options` for hiding the dock and the menu bar in fullscreen mode. +- On macOS, the dock and the menu bar are now hidden in fullscreen mode. - `Window::set_fullscreen` now takes `Option` where `Fullscreen` consists of `Fullscreen::Exclusive(VideoMode)` and `Fullscreen::Borderless(MonitorHandle)` variants. diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index f7613b1575..e0b47159b4 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,11 +1,7 @@ -#[cfg(target_os = "macos")] -use cocoa::appkit::NSApplicationPresentationOptions; use std::io::{stdin, stdout, Write}; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::monitor::{MonitorHandle, VideoMode}; -#[cfg(target_os = "macos")] -use winit::platform::macos::WindowExtMacOS; use winit::window::{Fullscreen, WindowBuilder}; fn main() { @@ -33,13 +29,6 @@ fn main() { .build(&event_loop) .unwrap(); - #[cfg(target_os = "macos")] - window.set_fullscreen_presentation_options( - NSApplicationPresentationOptions::NSApplicationPresentationFullScreen - | NSApplicationPresentationOptions::NSApplicationPresentationHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar, - ); - event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/src/platform/macos.rs b/src/platform/macos.rs index c43af08db8..da02a1dd2f 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -1,7 +1,5 @@ #![cfg(target_os = "macos")] -use cocoa::appkit::NSApplicationPresentationOptions; - use std::os::raw::c_void; use crate::{ @@ -33,18 +31,6 @@ pub trait WindowExtMacOS { /// Returns whether or not the window is in simple fullscreen mode. fn simple_fullscreen(&self) -> bool; - /// The presentation options the window should use when transitioning to - /// fullscreen mode. This allows the application to, for example, hide the - /// menu bar and the dock while in fullscreen mode. See - /// [`NSApplicationPresentationOptions`][options] for the possible values. - /// `NSApplicationPresentationFullScreen` must be included in the options. - /// - /// [options]: https://developer.apple.com/documentation/appkit/nsapplicationpresentationoptions?language=objc - fn set_fullscreen_presentation_options( - &self, - proposed_options: NSApplicationPresentationOptions, - ); - /// Toggles a fullscreen mode that doesn't require a new macOS space. /// Returns a boolean indicating whether the transition was successful (this /// won't work if the window was already in the native fullscreen). @@ -76,15 +62,6 @@ impl WindowExtMacOS for Window { self.window.simple_fullscreen() } - #[inline] - fn set_fullscreen_presentation_options( - &self, - proposed_options: NSApplicationPresentationOptions, - ) { - self.window - .set_fullscreen_presentation_options(proposed_options); - } - #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { self.window.set_simple_fullscreen(fullscreen) diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index dd64bcd54f..03ae551065 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -140,6 +140,8 @@ pub const kCGErrorTypeCheck: i32 = 1008; pub const kCGErrorInvalidOperation: i32 = 1010; pub const kCGErrorNoneAvailable: i32 = 1011; +pub type CGWindowLevel = i32; + #[link(name = "CoreGraphics", kind = "framework")] extern "C" { pub fn CGRestorePermanentDisplayConfiguration(); @@ -169,4 +171,5 @@ extern "C" { ) -> CGError; pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError; pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; + pub fn CGShieldingWindowLevel() -> CGWindowLevel; } diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index bb8655fc66..d7fe95929b 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -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); } Box::from_raw(context_ptr); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 6aea389104..570301f61a 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -244,9 +244,6 @@ pub struct SharedState { /// restored upon exiting it save_presentation_opts: Option, pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, - /// Requested fullscreen presentation options via - /// `WindowExtMacOS::set_fullscreen_presentation_options` - pub fullscreen_presentation_options: Option, } impl SharedState { @@ -597,11 +594,11 @@ impl UnownedWindow { /// user clicking on the green fullscreen button or programmatically by /// `toggleFullScreen:` pub(crate) fn restore_state_from_fullscreen(&self) { - self.restore_display_mode(); - trace!("Locked shared state in `restore_state_from_fullscreen`"); let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.fullscreen = None; + let maximized = shared_state_lock.maximized; let mask = self.saved_style(&mut *shared_state_lock); @@ -614,10 +611,10 @@ impl UnownedWindow { fn restore_display_mode(&self) { trace!("Locked shared state in `restore_display_mode`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); + let shared_state_lock = self.shared_state.lock().unwrap(); if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = - shared_state_lock.fullscreen.take() + shared_state_lock.fullscreen { unsafe { ffi::CGRestorePermanentDisplayConfiguration(); @@ -669,29 +666,9 @@ impl UnownedWindow { trace!("Unlocked shared state in `set_fullscreen`"); drop(shared_state_lock); - // If we're switching from exclusive to borderless mode, we need to - // restore the previous display mode (for switching to windowed mode - // this is handled in the `restore_state_from_fullscreen` callback) - match (&old_fullscreen, &fullscreen) { - (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { - self.restore_display_mode(); - - trace!("Locked shared state in `set_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - - // This is usually set in `window_did_enter_fullscreen` for - // borderless fullscreen, but since we're already in fullscreen, - // that isn't triggered and we must set it here - shared_state_lock.fullscreen = fullscreen.clone(); - - drop(shared_state_lock); - trace!("Unlocked shared state in `set_fullscreen`"); - } - _ => (), - } - // If the fullscreen is on a different monitor, we must move the window - // to that monitor before we toggle fullscreen + // to that monitor before we toggle fullscreen (as `toggleFullScreen` + // does not take a screen parameter, but uses the current screen) if let Some(ref fullscreen) = fullscreen { let new_screen = match fullscreen { Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor, @@ -706,8 +683,8 @@ impl UnownedWindow { 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 + // 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); } @@ -792,30 +769,51 @@ impl UnownedWindow { ffi::CGReleaseDisplayFadeReservation(fade_token); } } - - trace!("Locked shared state in `set_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - - // This is set in `window_did_enter_fullscreen` for borderless - // fullscreen, but for exclusive mode we set it here, as we can only - // enter exclusive mode from this function (and not also by user - // action like borderless) - shared_state_lock.fullscreen = fullscreen.clone(); - - drop(shared_state_lock); - trace!("Unlocked shared state in `set_fullscreen`"); } - if old_fullscreen.is_some() != fullscreen.is_some() { - unsafe { + 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] @@ -944,15 +942,6 @@ impl WindowExtMacOS for UnownedWindow { shared_state_lock.is_simple_fullscreen } - #[inline] - fn set_fullscreen_presentation_options( - &self, - proposed_options: NSApplicationPresentationOptions, - ) { - let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.fullscreen_presentation_options = Some(proposed_options); - } - #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { let mut shared_state_lock = self.shared_state.lock().unwrap(); diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 92a3eab2de..9bce9ce725 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -414,26 +414,23 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { } extern "C" fn window_will_use_fullscreen_presentation_options( - this: &Object, + _this: &Object, _: Sel, _: id, - proposed_options: NSUInteger, + _proposed_options: NSUInteger, ) -> NSUInteger { - let state = unsafe { - let state_ptr: *mut c_void = *this.get_ivar("winitState"); - &mut *(state_ptr as *mut WindowDelegateState) - }; - let window = state.window.upgrade().unwrap(); - trace!("Locked shared state in `window_will_use_fullscreen_presentation_options`"); - let opts = window - .shared_state - .lock() - .unwrap() - .fullscreen_presentation_options - .map(|x| x.bits()) - .unwrap_or(proposed_options); - trace!("Unlocked shared state in `window_will_use_fullscreen_presentation_options`"); - opts + // 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 @@ -449,12 +446,12 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { // can't enter exclusive mode by other means (like the // fullscreen button on the window decorations) Some(Fullscreen::Exclusive(_)) => (), - // `window_did_enter_fullscreen` shouldn't be triggered if we're - // already in fullscreen - Some(Fullscreen::Borderless(_)) => unreachable!(), - // Otherwise, update the fullscreen state (we reached fullscreen - // either via `set_fullscreen` or by the user fullscreening the - // window from the fullscreen button) + // `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`"); diff --git a/src/window.rs b/src/window.rs index 66a4df2efc..8a8c6e6cf5 100644 --- a/src/window.rs +++ b/src/window.rs @@ -543,16 +543,11 @@ impl Window { /// on macOS. See [`WindowExtMacOs::set_simple_fullscreen`][simple] if /// separate spaces are not preferred. /// - /// *Note!* For games and kiosk applications, you will generally want to - /// hide and disable the dock and the menu bar while in fullscreen mode. - /// This applies to both exclusive and borderless mode. See - /// [`WindowExtMacOs::set_fullscreen_presentation_options`][presentation]. + /// The dock and the menu bar are always disabled in fullscreen mode. /// - **iOS:** Can only be called on the main thread. /// /// [simple]: /// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen - /// [presentation]: - /// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_fullscreen_presentation_options #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { self.window.set_fullscreen(fullscreen) From ba7a6530b9b0d1699c70f60b95ccee85bce1de34 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Fri, 21 Jun 2019 17:29:54 +0300 Subject: [PATCH 09/33] Sort video modes on Windows --- src/platform_impl/windows/monitor.rs | 41 +++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 34f6c1d5e9..49dafc2fdf 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -7,7 +7,7 @@ use winapi::{ }; use std::{ - collections::{HashSet, VecDeque}, + collections::{BTreeSet, VecDeque}, io, mem, ptr, }; @@ -32,6 +32,27 @@ pub struct VideoMode { pub(crate) native_video_mode: wingdi::DEVMODEW, } +impl PartialOrd for VideoMode { + fn partial_cmp(&self, other: &VideoMode) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VideoMode { + fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering { + self.monitor.cmp(&other.monitor).then( + self.size + .cmp(&other.size) + .then( + self.refresh_rate + .cmp(&other.refresh_rate) + .then(self.bit_depth.cmp(&other.bit_depth)), + ) + .reverse(), + ) + } +} + impl VideoMode { pub fn size(&self) -> PhysicalSize { self.size.into() @@ -79,11 +100,23 @@ pub struct MonitorHandle { hidpi_factor: f64, } +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, other: &MonitorHandle) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MonitorHandle { + fn cmp(&self, other: &MonitorHandle) -> std::cmp::Ordering { + self.hmonitor.cmp(&other.hmonitor) + } +} + // Send is not implemented for HMONITOR, we have to wrap it and implement it manually. // For more info see: // https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/396 -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] struct HMonitor(HMONITOR); unsafe impl Send for HMonitor {} @@ -221,8 +254,8 @@ impl MonitorHandle { pub fn video_modes(&self) -> impl Iterator { // EnumDisplaySettingsExW can return duplicate values (or some of the // fields are probably changing, but we aren't looking at those fields - // anyway), so we're using a HashSet deduplicate - let mut modes = HashSet::new(); + // anyway), so we're using a BTreeSet deduplicate + let mut modes = BTreeSet::new(); let mut i = 0; loop { From f00b10f2fc1ba7628decf06e1d30fcb7d640cefc Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Fri, 21 Jun 2019 17:24:05 +0300 Subject: [PATCH 10/33] Fix fullscreen issues on Windows --- examples/video_modes.rs | 2 +- src/platform_impl/windows/monitor.rs | 104 ++++------- src/platform_impl/windows/window.rs | 200 +++++++++++++++------- src/platform_impl/windows/window_state.rs | 128 +++----------- 4 files changed, 189 insertions(+), 245 deletions(-) diff --git a/examples/video_modes.rs b/examples/video_modes.rs index f8c6aa087a..f923fa9202 100644 --- a/examples/video_modes.rs +++ b/examples/video_modes.rs @@ -7,6 +7,6 @@ fn main() { println!("Listing available video modes:"); for mode in monitor.video_modes() { - println!("{:?}", mode); + println!("{}", mode); } } diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 49dafc2fdf..0abdacba58 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -3,7 +3,7 @@ use winapi::{ minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD}, windef::{HDC, HMONITOR, HWND, LPRECT, POINT}, }, - um::{wingdi, winnt::LONG, winuser}, + um::{wingdi, winuser}, }; use std::{ @@ -73,53 +73,15 @@ impl VideoMode { } } -/// Win32 implementation of the main `MonitorHandle` object. -#[derive(Derivative)] -#[derivative(Debug, Clone, Eq, PartialEq, Hash)] -pub struct MonitorHandle { - /// Monitor handle. - hmonitor: HMonitor, - #[derivative(Hash = "ignore", PartialEq = "ignore", Debug = "ignore")] - monitor_info: winuser::MONITORINFOEXW, - /// The system name of the monitor. - #[derivative(Hash = "ignore", PartialEq = "ignore")] - monitor_name: String, - /// True if this is the primary monitor. - #[derivative(Hash = "ignore", PartialEq = "ignore")] - primary: bool, - /// The position of the monitor in pixels on the desktop. - /// - /// A window that is positioned at these coordinates will overlap the monitor. - #[derivative(Hash = "ignore", PartialEq = "ignore")] - position: (i32, i32), - /// The current resolution in pixels on the monitor. - #[derivative(Hash = "ignore", PartialEq = "ignore")] - dimensions: (u32, u32), - /// DPI scale factor. - #[derivative(Hash = "ignore", PartialEq = "ignore")] - hidpi_factor: f64, -} - -impl PartialOrd for MonitorHandle { - fn partial_cmp(&self, other: &MonitorHandle) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for MonitorHandle { - fn cmp(&self, other: &MonitorHandle) -> std::cmp::Ordering { - self.hmonitor.cmp(&other.hmonitor) - } -} +#[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. // For more info see: // https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/396 -#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] -struct HMonitor(HMONITOR); -unsafe impl Send for HMonitor {} +unsafe impl Send for MonitorHandle {} unsafe extern "system" fn monitor_enum_proc( hmonitor: HMONITOR, @@ -128,7 +90,7 @@ unsafe extern "system" fn monitor_enum_proc( data: LPARAM, ) -> BOOL { let monitors = data as *mut VecDeque; - (*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor)); + (*monitors).push_back(MonitorHandle::new(hmonitor)); TRUE // continue enumeration } @@ -148,12 +110,12 @@ pub fn available_monitors() -> VecDeque { pub fn primary_monitor() -> MonitorHandle { const ORIGIN: POINT = POINT { x: 0, y: 0 }; let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) }; - MonitorHandle::from_hmonitor(hmonitor) + MonitorHandle::new(hmonitor) } pub fn current_monitor(hwnd: HWND) -> MonitorHandle { let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) }; - MonitorHandle::from_hmonitor(hmonitor) + MonitorHandle::new(hmonitor) } impl EventLoop { @@ -194,60 +156,55 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result Self { - let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed"); - 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 new(hmonitor: HMONITOR) -> Self { + MonitorHandle(hmonitor) } pub(crate) fn contains_point(&self, point: &POINT) -> bool { - let left = self.position.0 as LONG; - let right = left + self.dimensions.0 as LONG; - let top = self.position.1 as LONG; - let bottom = top + self.dimensions.1 as LONG; - point.x >= left && point.x <= right && point.y >= top && point.y <= bottom + let monitor_info = get_monitor_info(self.0).unwrap(); + point.x >= monitor_info.rcMonitor.left + && point.x <= monitor_info.rcMonitor.right + && point.y >= monitor_info.rcMonitor.top + && point.y <= monitor_info.rcMonitor.bottom } #[inline] pub fn name(&self) -> Option { - 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] pub fn native_identifier(&self) -> String { - self.monitor_name.clone() + self.name().unwrap() } #[inline] pub fn hmonitor(&self) -> HMONITOR { - self.hmonitor.0 + self.0 } #[inline] 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] 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] pub fn hidpi_factor(&self) -> f64 { - self.hidpi_factor + dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96)) } #[inline] @@ -260,7 +217,8 @@ impl MonitorHandle { loop { 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(); mode.dmSize = mem::size_of_val(&mode) as WORD; if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 11959317f3..4dff694ab7 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -323,7 +323,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::RESIZABLE, resizable) }); }); @@ -417,7 +417,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::MAXIMIZED, maximized) }); }); @@ -431,73 +431,148 @@ impl Window { #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { - unsafe { - let window = self.window.clone(); - let window_state = Arc::clone(&self.window_state); + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); - match fullscreen { - None => { - self.thread_executor.execute_in_thread(move || { - let mut window_state_lock = window_state.lock(); - window_state_lock.fullscreen = None; - - if let Some(SavedWindow { - client_rect, - dpi_factor, - }) = window_state_lock.saved_window - { - window_state_lock.dpi_factor = dpi_factor; - window_state_lock.saved_window = None; - - WindowState::refresh_window_state( - window_state_lock, - window.0, - Some(client_rect), - ); - } + let mut window_state_lock = window_state.lock(); + let old_fullscreen = window_state_lock.fullscreen.clone(); + if window_state_lock.fullscreen == fullscreen { + return; + } + window_state_lock.fullscreen = fullscreen.clone(); + drop(window_state_lock); - mark_fullscreen(window.0, false); - return; + self.thread_executor.execute_in_thread(move || { + let mut window_state_lock = window_state.lock(); + + // Save window bounds before entering fullscreen + match (&old_fullscreen, &fullscreen) { + (&None, &Some(_)) => { + let client_rect = util::get_client_rect(window.0).unwrap(); + window_state_lock.saved_window = Some(SavedWindow { + client_rect, + dpi_factor: window_state_lock.dpi_factor, }); } - Some(fullscreen) => { - let (position, size): ((i32, i32), (u32, u32)) = match fullscreen { - Fullscreen::Exclusive(ref video_mode) => ( - video_mode.monitor().position().into(), - video_mode.size().into(), - ), - Fullscreen::Borderless(ref monitor) => { - (monitor.position().into(), monitor.size().into()) - } + _ => (), + } + + // Change video mode if we're transitioning to or from exclusive + // fullscreen + match (&old_fullscreen, &fullscreen) { + (&None, &Some(Fullscreen::Exclusive(ref video_mode))) + | ( + &Some(Fullscreen::Borderless(_)), + &Some(Fullscreen::Exclusive(ref video_mode)), + ) => { + let monitor = video_mode.monitor(); + + let mut display_name = OsStr::new(&monitor.inner.native_identifier()) + .encode_wide() + .collect::>(); + // `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(), + ) }; - self.thread_executor.execute_in_thread(move || { - let mut window_state_lock = window_state.lock(); + 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(), + ) + }; - let client_rect = - util::get_client_rect(window.0).expect("get client rect failed!"); - window_state_lock.saved_window = Some(SavedWindow { - client_rect, - dpi_factor: window_state_lock.dpi_factor, - }); + 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); + } + _ => (), + } + + // Update window style + WindowState::set_window_flags(window_state_lock, window.0, |f| { + f.set(WindowFlags::MARKER_FULLSCREEN, fullscreen.is_some()) + }); - window_state_lock.fullscreen = Some(fullscreen.clone()); - WindowState::refresh_window_state( - window_state_lock, + // 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, - Some(RECT { - left: position.0, - top: position.1, - right: position.0 + size.0 as c_int, - bottom: position.1 + size.1 as c_int, - }), + ptr::null_mut(), + position.0, + position.1, + size.0 as i32, + size.1 as i32, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER, ); - - mark_fullscreen(window.0, true); - }); + 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, + 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); + } + } } } - } + + unsafe { + taskbar_mark_fullscreen(window.0, fullscreen.is_some()); + } + }); } #[inline] @@ -506,8 +581,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - let client_rect = util::get_client_rect(window.0).expect("get client rect failed!"); - WindowState::set_window_flags(window_state.lock(), window.0, Some(client_rect), |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::DECORATIONS, decorations) }); }); @@ -519,7 +593,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top) }); }); @@ -772,9 +846,7 @@ unsafe fn init( let window_state = { let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor); let window_state = Arc::new(Mutex::new(window_state)); - WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| { - *f = window_flags - }); + WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); window_state }; @@ -868,7 +940,7 @@ pub fn com_initialized() { // is activated. If the window is not fullscreen, the Shell falls back to // heuristics to determine how the window should be treated, which means // that it could still consider the window as fullscreen. :( -unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) { +unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) { com_initialized(); TASKBAR_LIST.with(|task_bar_list_ptr| { diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 2d38b41623..8c9d030b52 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -4,7 +4,7 @@ use crate::{ window::{CursorIcon, Fullscreen, WindowAttributes}, }; use parking_lot::MutexGuard; -use std::{ffi::OsStr, io, os::windows::ffi::OsStrExt, ptr}; +use std::{io, ptr}; use winapi::{ shared::{ minwindef::DWORD, @@ -80,6 +80,7 @@ bitflags! { WindowFlags::RESIZABLE.bits | WindowFlags::MAXIMIZED.bits ); + const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; } @@ -117,79 +118,16 @@ impl WindowState { self.window_flags } - pub fn set_window_flags( - mut this: MutexGuard<'_, Self>, - window: HWND, - set_client_rect: Option, - f: F, - ) where + pub fn set_window_flags(mut this: MutexGuard<'_, Self>, window: HWND, f: F) + where F: FnOnce(&mut WindowFlags), { let old_flags = 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; - // Change video mode if necessary - if old_flags & WindowFlags::MARKER_FULLSCREEN != new_flags & WindowFlags::MARKER_FULLSCREEN - { - let res = if let Some(Fullscreen::Exclusive(ref video_mode)) = this.fullscreen { - let monitor = video_mode.monitor(); - - // The display name on Windows is the same we get from - // EnumDisplayDevices, so it's good for ChangeDisplaySettingsExW - let mut display_name = OsStr::new(monitor.name().as_ref().unwrap()) - .encode_wide() - .collect::>(); - // encode_wide does not add a null-terminator but - // ChangeDisplaySettingsExW requires a null-terminated string - display_name.push(0); - - let mut native_video_mode = video_mode.video_mode.native_video_mode.clone(); - - unsafe { - winuser::ChangeDisplaySettingsExW( - display_name.as_ptr(), - &mut native_video_mode, - std::ptr::null_mut(), - winuser::CDS_FULLSCREEN, - std::ptr::null_mut(), - ) - } - } else { - unsafe { - winuser::ChangeDisplaySettingsExW( - std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - winuser::CDS_FULLSCREEN, - std::ptr::null_mut(), - ) - } - }; - - // These are separate asserts so we can easily see which error we - // hit here - 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); - } - drop(this); - old_flags.apply_diff(window, new_flags, set_client_rect); - } - - pub fn refresh_window_state( - this: MutexGuard<'_, Self>, - window: HWND, - set_client_rect: Option, - ) { - Self::set_window_flags(this, window, set_client_rect, |_| ()); + old_flags.apply_diff(window, new_flags); } pub fn set_window_flags_in_place(&mut self, f: F) @@ -227,6 +165,7 @@ impl WindowFlags { fn mask(mut self) -> WindowFlags { if self.contains(WindowFlags::MARKER_FULLSCREEN) { self &= WindowFlags::FULLSCREEN_AND_MASK; + self |= WindowFlags::FULLSCREEN_OR_MASK; } if !self.contains(WindowFlags::VISIBLE) { self &= WindowFlags::INVISIBLE_AND_MASK; @@ -277,7 +216,7 @@ impl WindowFlags { } /// 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) { + fn apply_diff(mut self, window: HWND, mut new: WindowFlags) { self = self.mask(); new = new.mask(); @@ -336,45 +275,20 @@ impl WindowFlags { winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); - match set_client_rect - .and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) - { - Some(client_rect) => { - let (x, y, w, h) = ( - client_rect.left, - client_rect.top, - client_rect.right - client_rect.left, - client_rect.bottom - client_rect.top, - ); - 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, + winuser::SWP_NOZORDER + | winuser::SWP_NOMOVE + | winuser::SWP_NOSIZE + | winuser::SWP_FRAMECHANGED + | winuser::SWP_NOACTIVATE, + ); winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0); } } From 471e14b42f36a75bdcfc1c7ebcabacdae4e48324 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sat, 22 Jun 2019 10:01:36 +0300 Subject: [PATCH 11/33] Fix video mode changes during exclusive fullscreen on Windows --- examples/multithreaded.rs | 17 ++++++++++++++++- src/platform_impl/windows/window.rs | 4 +++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index a805f5d8e8..9b9ee3c4e6 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -19,6 +19,8 @@ fn main() { .with_inner_size(WINDOW_SIZE.into()) .build(&event_loop) .unwrap(); + let video_modes: Vec<_> = window.current_monitor().video_modes().collect(); + let mut video_mode = 0usize; let (tx, rx) = mpsc::channel(); window_senders.insert(window.id(), tx); thread::spawn(move || { @@ -44,12 +46,24 @@ fn main() { false => CursorIcon::Default, }), D => window.set_decorations(!state), + // Cycle through video modes + Right | Left => { + video_mode = match key { + Left => video_mode.saturating_sub(1), + Right => (video_modes.len() - 1).min(video_mode + 1), + _ => unreachable!(), + }; + println!( + "Picking video mode: {}", + video_modes.iter().nth(video_mode).unwrap() + ); + } F => window.set_fullscreen(match (state, modifiers.alt) { (true, false) => { Some(Fullscreen::Borderless(window.current_monitor())) } (true, true) => Some(Fullscreen::Exclusive( - window.current_monitor().video_modes().next().unwrap(), + video_modes.iter().nth(video_mode).unwrap().clone(), )), (false, _) => None, }), @@ -61,6 +75,7 @@ fn main() { println!("-> inner_position : {:?}", window.inner_position()); println!("-> outer_size : {:?}", window.outer_size()); println!("-> inner_size : {:?}", window.inner_size()); + println!("-> fullscreen : {:?}", window.fullscreen()); } L => window.set_min_inner_size(match state { true => Some(WINDOW_SIZE.into()), diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 4dff694ab7..756fc5ad6c 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -464,7 +464,9 @@ impl Window { | ( &Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(ref video_mode)), - ) => { + ) + | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) => + { let monitor = video_mode.monitor(); let mut display_name = OsStr::new(&monitor.inner.native_identifier()) From 6007e145412fc3e9468341f2cd4cb4cbcf4c0796 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sat, 22 Jun 2019 10:17:53 +0300 Subject: [PATCH 12/33] Add video mode sorting for macOS and iOS --- examples/fullscreen.rs | 11 ++++---- src/monitor.rs | 28 ++++++++++++++++++-- src/platform_impl/ios/monitor.rs | 24 ++++++++--------- src/platform_impl/macos/monitor.rs | 29 +++++++++++++++++++-- src/platform_impl/windows/monitor.rs | 39 +++++++--------------------- 5 files changed, 79 insertions(+), 52 deletions(-) diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index e0b47159b4..f3aa57f093 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -95,11 +95,7 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { } fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode { - // Video modes are returned in a random order, so we must store them in - // order to be able to later pick the nth video mode reliably - let mut video_modes: Vec<_> = monitor.video_modes().collect(); - - for (i, video_mode) in video_modes.iter().enumerate() { + for (i, video_mode) in monitor.video_modes().enumerate() { println!("Video mode #{}: {}", i, video_mode); } @@ -109,7 +105,10 @@ fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode { 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 = video_modes.remove(num); + let video_mode = monitor + .video_modes() + .nth(num) + .expect("Please enter a valid ID"); println!("Using {}", video_mode); diff --git a/src/monitor.rs b/src/monitor.rs index 10714fe3b4..021ef4f1bc 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -53,11 +53,35 @@ impl Iterator for AvailableMonitorsIter { /// /// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes #[derive(Derivative)] -#[derivative(Clone, Debug = "transparent", PartialEq)] +#[derivative(Clone, Debug = "transparent", PartialEq, Eq, Hash)] pub struct VideoMode { pub(crate) video_mode: platform_impl::VideoMode, } +impl PartialOrd for VideoMode { + fn partial_cmp(&self, other: &VideoMode) -> Option { + 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 { /// Returns the resolution of this video mode. pub fn size(&self) -> PhysicalSize { @@ -108,7 +132,7 @@ impl std::fmt::Display for VideoMode { /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. /// /// [`Window`]: ../window/struct.Window.html -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct MonitorHandle { pub(crate) inner: platform_impl::MonitorHandle, } diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index d1200d1693..90257077e5 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashSet, VecDeque}, + collections::{BTreeSet, VecDeque}, fmt, ops::{Deref, DerefMut}, }; @@ -38,7 +38,7 @@ impl VideoMode { } } -#[derive(PartialEq, Eq, Hash)] +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Inner { uiscreen: id, } @@ -51,7 +51,7 @@ impl Drop for Inner { } } -#[derive(PartialEq, Eq, Hash)] +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct MonitorHandle { inner: Inner, } @@ -173,22 +173,22 @@ impl Inner { let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] }; 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 { let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] }; let size: CGSize = unsafe { msg_send![mode, size] }; - modes.insert(VideoMode { - size: (size.width as u32, size.height as u32), - bit_depth: 32, - refresh_rate: refresh_rate as u16, - monitor: MonitorHandle::retained_new(self.uiscreen), + modes.insert(RootVideoMode { + video_mode: VideoMode { + size: (size.width as u32, size.height as u32), + bit_depth: 32, + refresh_rate: refresh_rate as u16, + monitor: MonitorHandle::retained_new(self.uiscreen), + }, }); } - modes - .into_iter() - .map(|video_mode| RootVideoMode { video_mode }) + modes.into_iter() } } diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index d02a0c5da6..485d6081d7 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -19,13 +19,13 @@ use crate::{ }; #[derive(Derivative)] -#[derivative(Debug, Clone, PartialEq)] +#[derivative(Debug, Clone, PartialEq, Hash)] pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate: u16, pub(crate) monitor: MonitorHandle, - #[derivative(Debug = "ignore", PartialEq = "ignore")] + #[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")] pub(crate) native_mode: CGDisplayMode, } @@ -64,6 +64,31 @@ impl PartialEq for MonitorHandle { } } +impl Eq for MonitorHandle {} + +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, other: &Self) -> Option { + 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(&self, state: &mut H) { + unsafe { + ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state); + } + } +} + pub fn available_monitors() -> VecDeque { if let Ok(displays) = CGDisplay::active_displays() { let mut monitors = VecDeque::with_capacity(displays.len()); diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 0abdacba58..a8c02b4f33 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -32,27 +32,6 @@ pub struct VideoMode { pub(crate) native_video_mode: wingdi::DEVMODEW, } -impl PartialOrd for VideoMode { - fn partial_cmp(&self, other: &VideoMode) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VideoMode { - fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering { - self.monitor.cmp(&other.monitor).then( - self.size - .cmp(&other.size) - .then( - self.refresh_rate - .cmp(&other.refresh_rate) - .then(self.bit_depth.cmp(&other.bit_depth)), - ) - .reverse(), - ) - } -} - impl VideoMode { pub fn size(&self) -> PhysicalSize { self.size.into() @@ -232,18 +211,18 @@ impl MonitorHandle { | wingdi::DM_DISPLAYFREQUENCY; assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS); - modes.insert(VideoMode { - size: (mode.dmPelsWidth, mode.dmPelsHeight), - bit_depth: mode.dmBitsPerPel as u16, - refresh_rate: mode.dmDisplayFrequency as u16, - monitor: self.clone(), - native_video_mode: mode, + modes.insert(RootVideoMode { + video_mode: VideoMode { + size: (mode.dmPelsWidth, mode.dmPelsHeight), + bit_depth: mode.dmBitsPerPel as u16, + refresh_rate: mode.dmDisplayFrequency as u16, + monitor: self.clone(), + native_video_mode: mode, + }, }); } } - modes - .into_iter() - .map(|video_mode| RootVideoMode { video_mode }) + modes.into_iter() } } From 809e4ea9140fd57531603182d8197c2f0d2a4ffa Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sat, 22 Jun 2019 11:25:58 +0300 Subject: [PATCH 13/33] Fix monitor `ns_screen` returning `None` after video mode change --- src/platform_impl/macos/monitor.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 485d6081d7..19f145a586 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -216,24 +216,24 @@ impl MonitorHandle { pub(crate) fn ns_screen(&self) -> Option { unsafe { - let native_id = self.native_identifier(); + let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0); let screens = NSScreen::screens(nil); let count: NSUInteger = msg_send![screens, count]; let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); - let mut matching_screen: Option = None; for i in 0..count { let screen = msg_send![screens, objectAtIndex: i as NSUInteger]; let device_description = NSScreen::deviceDescription(screen); let value: id = msg_send![device_description, objectForKey:*key]; if value != nil { - let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue]; - if screen_number as u32 == native_id { - matching_screen = Some(screen); - break; + let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue]; + let other_uuid = + ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID); + if uuid == other_uuid { + return Some(screen); } } } - matching_screen + None } } } From f419c6d7694c78287a9e687cb9b13a251f3d2e4e Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sat, 22 Jun 2019 11:26:52 +0300 Subject: [PATCH 14/33] Fix "multithreaded" example on macOS --- src/platform_impl/macos/monitor.rs | 8 ++++++-- src/platform_impl/macos/window.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 19f145a586..50d0210f60 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -26,9 +26,13 @@ pub struct VideoMode { pub(crate) refresh_rate: u16, pub(crate) monitor: MonitorHandle, #[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")] - pub(crate) native_mode: CGDisplayMode, + pub(crate) native_mode: NativeDisplayMode, } +#[derive(Clone)] +pub struct NativeDisplayMode(pub CGDisplayMode); +unsafe impl Send for NativeDisplayMode {} + impl VideoMode { pub fn size(&self) -> PhysicalSize { self.size.into() @@ -207,7 +211,7 @@ impl MonitorHandle { refresh_rate: refresh_rate as u16, bit_depth: mode.bit_depth() as u16, monitor: monitor.clone(), - native_mode: mode, + native_mode: NativeDisplayMode(mode), }; RootVideoMode { video_mode } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 570301f61a..bd05894fc0 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -746,7 +746,7 @@ impl UnownedWindow { .begin_configuration() .expect("failed to begin display configuration"); display - .configure_display_with_display_mode(&config, &video_mode.video_mode.native_mode) + .configure_display_with_display_mode(&config, &video_mode.video_mode.native_mode.0) .expect("failed to set display mode"); display .complete_configuration(&config, CGConfigureOption::ConfigureForAppOnly) From e4ee11c656d84fc09bb7f5ed57f6e324eeb72658 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sat, 22 Jun 2019 11:33:59 +0300 Subject: [PATCH 15/33] Restore video mode upon closing an exclusive fullscreen window --- src/window.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/window.rs b/src/window.rs index 8a8c6e6cf5..6ba82e2e83 100644 --- a/src/window.rs +++ b/src/window.rs @@ -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. /// /// Can be obtained with `window.id()`. From 68b7258320f6e02882f7f864034116082193448a Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sat, 22 Jun 2019 11:48:30 +0300 Subject: [PATCH 16/33] Fix "multithreaded" example closing multiple windows at once --- examples/multithreaded.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 9b9ee3c4e6..97a5c53ceb 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -128,6 +128,7 @@ fn main() { | WindowEvent::KeyboardInput { input: KeyboardInput { + state: ElementState::Released, virtual_keycode: Some(VirtualKeyCode::Escape), .. }, From dcfc9f0db1e176c2ad30c5c1983365f2aed01ac3 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sun, 23 Jun 2019 10:51:02 +0300 Subject: [PATCH 17/33] Fix compilation on Linux --- src/lib.rs | 1 - src/monitor.rs | 4 + src/platform_impl/linux/mod.rs | 56 +++++++++++-- src/platform_impl/linux/wayland/event_loop.rs | 84 ++++++++++++++++--- src/platform_impl/linux/wayland/mod.rs | 3 +- src/platform_impl/linux/wayland/window.rs | 56 ++++++++----- src/platform_impl/linux/x11/mod.rs | 2 +- src/platform_impl/linux/x11/monitor.rs | 71 +++++++++++++++- src/platform_impl/linux/x11/util/randr.rs | 5 +- src/platform_impl/linux/x11/window.rs | 19 +++-- 10 files changed, 241 insertions(+), 60 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1290bcd1bf..a4a65f54f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,6 @@ extern crate log; #[macro_use] extern crate serde; #[macro_use] -#[cfg(target_os = "windows")] extern crate derivative; #[macro_use] #[cfg(target_os = "windows")] diff --git a/src/monitor.rs b/src/monitor.rs index 021ef4f1bc..13a236b42a 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -84,6 +84,7 @@ impl Ord for VideoMode { impl VideoMode { /// Returns the resolution of this video mode. + #[inline] pub fn size(&self) -> PhysicalSize { self.video_mode.size() } @@ -96,6 +97,7 @@ impl VideoMode { /// /// - **Wayland:** Always returns 32. /// - **iOS:** Always returns 32. + #[inline] pub fn bit_depth(&self) -> u16 { self.video_mode.bit_depth() } @@ -103,12 +105,14 @@ impl VideoMode { /// Returns the refresh rate of this video mode. **Note**: the returned /// refresh rate is an integer approximation, and you shouldn't rely on this /// value to be exact. + #[inline] pub fn refresh_rate(&self) -> u16 { 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() } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 62366a133a..33ce2ada91 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -13,8 +13,8 @@ use crate::{ event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, icon::Icon, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode}, - window::{CursorIcon, WindowAttributes}, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; mod dlopen; @@ -92,7 +92,7 @@ impl DeviceId { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum MonitorHandle { X(x11::MonitorHandle), Wayland(wayland::MonitorHandle), @@ -140,7 +140,7 @@ impl MonitorHandle { } #[inline] - pub fn video_modes(&self) -> Box> { + pub fn video_modes(&self) -> Box> { match self { MonitorHandle::X(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 { #[inline] pub fn new( @@ -310,17 +350,15 @@ impl Window { } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { match self { &Window::X(ref w) => w.fullscreen(), - &Window::Wayland(ref w) => w.fullscreen().map(|monitor_id| RootMonitorHandle { - inner: MonitorHandle::Wayland(monitor_id), - }), + &Window::Wayland(ref w) => w.fullscreen(), } } #[inline] - pub fn set_fullscreen(&self, monitor: Option) { + pub fn set_fullscreen(&self, monitor: Option) { match self { &Window::X(ref w) => w.set_fullscreen(monitor), &Window::Wayland(ref w) => w.set_fullscreen(monitor), diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 7c3b21a08c..2cdfb1689d 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -16,8 +16,11 @@ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event::ModifiersState, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, - monitor::VideoMode, - platform_impl::platform::sticky_exit_callback, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + platform_impl::platform::{ + sticky_exit_callback, MonitorHandle as PlatformMonitorHandle, + VideoMode as PlatformVideoMode, + }, }; use super::{window::WindowStore, DeviceId, WindowId}; @@ -603,17 +606,67 @@ impl Drop for SeatData { * 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(crate) proxy: wl_output::WlOutput, pub(crate) mgr: OutputMgr, } -impl Clone for MonitorHandle { - fn clone(&self) -> MonitorHandle { - MonitorHandle { - proxy: self.proxy.clone(), - mgr: self.mgr.clone(), - } +impl PartialEq for MonitorHandle { + fn eq(&self, other: &Self) -> bool { + self.native_identifier() == other.native_identifier() + } +} + +impl Eq for MonitorHandle {} + +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, other: &Self) -> Option { + 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(&self, state: &mut H) { + self.native_identifier().hash(state); } } @@ -680,15 +733,20 @@ impl MonitorHandle { } #[inline] - pub fn video_modes(&self) -> impl Iterator { + pub fn video_modes(&self) -> impl Iterator { + let monitor = self.clone(); + self.mgr .with_info(&self.proxy, |_, info| info.modes.clone()) .unwrap_or(vec![]) .into_iter() - .map(|x| VideoMode { - size: (x.dimensions.0 as u32, x.dimensions.1 as u32), - refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16, - bit_depth: 32, + .map(move |x| RootVideoMode { + video_mode: PlatformVideoMode::Wayland(VideoMode { + size: (x.dimensions.0 as u32, x.dimensions.1 as u32), + refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16, + bit_depth: 32, + monitor: monitor.clone(), + }), }) } } diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index aa96e27670..09cd66d15a 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -3,7 +3,8 @@ pub use self::{ event_loop::{ - EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink, + EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode, + WindowEventsSink, }, window::Window, }; diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index bad69b4dc1..4be96d9d12 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -9,10 +9,11 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::{ + platform::wayland::event_loop::{available_monitors, primary_monitor}, MonitorHandle as PlatformMonitorHandle, PlatformSpecificWindowBuilderAttributes as PlAttributes, }, - window::{CursorIcon, WindowAttributes}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; use smithay_client_toolkit::{ @@ -26,7 +27,6 @@ use smithay_client_toolkit::{ }; use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId}; -use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor}; pub struct Window { _bg_surface: wl_surface::WlSurface, @@ -140,13 +140,19 @@ impl Window { } // Check for fullscreen requirements - if let Some(RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(ref monitor_id), - }) = attributes.fullscreen - { - frame.set_fullscreen(Some(&monitor_id.proxy)); - } else if attributes.maximized { - frame.set_maximized(); + match attributes.fullscreen { + Some(Fullscreen::Exclusive(_)) => { + panic!("Wayland doesn't support exclusive fullscreen") + } + Some(Fullscreen::Borderless(RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(ref monitor_id), + })) => frame.set_fullscreen(Some(&monitor_id.proxy)), + Some(Fullscreen::Borderless(_)) => unreachable!(), + None => { + if attributes.maximized { + frame.set_maximized(); + } + } } frame.set_resizable(attributes.resizable); @@ -286,25 +292,31 @@ impl Window { } } - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { if *(self.fullscreen.lock().unwrap()) { - Some(self.current_monitor()) + Some(Fullscreen::Borderless(RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(self.current_monitor()), + })) } else { None } } - pub fn set_fullscreen(&self, monitor: Option) { - if let Some(RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(ref monitor_id), - }) = monitor - { - self.frame - .lock() - .unwrap() - .set_fullscreen(Some(&monitor_id.proxy)); - } else { - self.frame.lock().unwrap().unset_fullscreen(); + pub fn set_fullscreen(&self, fullscreen: Option) { + match fullscreen { + Some(Fullscreen::Exclusive(_)) => { + panic!("Wayland doesn't support exclusive fullscreen") + } + Some(Fullscreen::Borderless(RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(ref monitor_id), + })) => { + self.frame + .lock() + .unwrap() + .set_fullscreen(Some(&monitor_id.proxy)); + } + Some(Fullscreen::Borderless(_)) => unreachable!(), + None => self.frame.lock().unwrap().unset_fullscreen(), } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 65f0737d5a..ac8149c0ad 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -11,7 +11,7 @@ mod window; mod xdisplay; pub use self::{ - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::UnownedWindow, xdisplay::{XConnection, XError, XNotSupported}, }; diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 7202625788..812a049d84 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -11,7 +11,8 @@ use super::{ }; use crate::{ 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. @@ -41,6 +42,38 @@ pub fn invalidate_cached_monitor_list() -> Option> { (*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) monitor: Option, +} + +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)] pub struct MonitorHandle { /// The actual id @@ -61,6 +94,32 @@ pub struct MonitorHandle { video_modes: Vec, } +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 { + 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(&self, state: &mut H) { + self.id.hash(state); + } +} + impl MonitorHandle { fn from_repr( xconn: &XConnection, @@ -107,8 +166,14 @@ impl MonitorHandle { } #[inline] - pub fn video_modes(&self) -> impl Iterator { - self.video_modes.clone().into_iter() + pub fn video_modes(&self) -> impl Iterator { + 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), + } + }) } } diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 1e9a41c6b9..5b6c429506 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,7 +1,7 @@ use std::{env, slice, str::FromStr}; use super::*; -use crate::{dpi::validate_hidpi_factor, monitor::VideoMode}; +use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode}; pub fn calc_dpi_factor( (width_px, height_px): (u32, u32), @@ -132,6 +132,9 @@ impl XConnection { size: (x.width, x.height), refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16, bit_depth: bit_depth as u16, + // This is populated in `MonitorHandle::video_modes` as the + // video mode is returned to the user + monitor: None, } }); diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 5e973e93ea..d1cbab12c4 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -11,7 +11,7 @@ use crate::{ x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle}, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, }, - window::{CursorIcon, Icon, WindowAttributes}, + window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, }; use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; @@ -36,7 +36,7 @@ pub struct SharedState { pub guessed_dpi: Option, pub last_monitor: Option, pub dpi_adjusted: Option<(f64, f64)>, - pub fullscreen: Option, + pub fullscreen: Option, // Used to restore position after exiting fullscreen. pub restore_position: Option<(i32, i32)>, pub frame_extents: Option, @@ -550,8 +550,8 @@ impl UnownedWindow { self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)) } - fn set_fullscreen_inner(&self, monitor: Option) -> util::Flusher<'_> { - match monitor { + fn set_fullscreen_inner(&self, fullscreen: Option) -> util::Flusher<'_> { + match fullscreen { None => { let flusher = self.set_fullscreen_hint(false); if let Some(position) = self.shared_state.lock().restore_position.take() { @@ -559,9 +559,10 @@ impl UnownedWindow { } flusher } - Some(RootMonitorHandle { + Some(Fullscreen::Exclusive(_)) => unimplemented!("fullscreen on X11"), // TODO + Some(Fullscreen::Borderless(RootMonitorHandle { inner: PlatformMonitorHandle::X(monitor), - }) => { + })) => { let window_position = self.outer_position_physical(); self.shared_state.lock().restore_position = Some(window_position); let monitor_origin: (i32, i32) = monitor.position().into(); @@ -569,17 +570,17 @@ impl UnownedWindow { .queue(); self.set_fullscreen_hint(true) } - _ => unreachable!(), + Some(Fullscreen::Borderless(_)) => unreachable!(), } } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { self.shared_state.lock().fullscreen.clone() } #[inline] - pub fn set_fullscreen(&self, monitor: Option) { + pub fn set_fullscreen(&self, monitor: Option) { self.shared_state.lock().fullscreen = monitor.clone(); self.set_fullscreen_inner(monitor) .flush() From 5be964ed2b6fb937310403a1089800aa119ff447 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Sun, 23 Jun 2019 11:22:52 +0300 Subject: [PATCH 18/33] Update FEATURES.md --- FEATURES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FEATURES.md b/FEATURES.md index d71b00e451..8628d95495 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -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 toggle**: The windows created by winit can be switched to and from fullscreen after 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. - **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 @@ -157,6 +160,7 @@ Legend: |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | +|Exclusive fullscreen |✔️ |✔️ |❌ |**N/A** |❌ |❌ |❌ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | From f3e4624d333ccdc4f4abd140eec56cb94661bd48 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 25 Jun 2019 09:39:34 +0300 Subject: [PATCH 19/33] Don't care about logical monitor groups on X11 --- src/platform_impl/linux/x11/monitor.rs | 72 +++++++---------------- src/platform_impl/linux/x11/util/randr.rs | 54 +++-------------- 2 files changed, 29 insertions(+), 97 deletions(-) diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 812a049d84..d9bd786ff9 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -4,8 +4,8 @@ use parking_lot::Mutex; use super::{ ffi::{ - RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, Window, - XRRScreenResources, + RRCrtc, RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, + Window, XRRCrtcInfo, XRRScreenResources, }, util, XConnection, XError, }; @@ -15,9 +15,7 @@ use crate::{ platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode}, }; -// Used to test XRandR < 1.5 code path. This should always be committed as false. -const FORCE_RANDR_COMPAT: bool = false; -// Also used for testing. This should always be committed as false. +// Used for testing. This should always be committed as false. const DISABLE_MONITOR_LIST_CACHING: bool = false; lazy_static! { @@ -77,7 +75,7 @@ impl VideoMode { #[derive(Debug, Clone)] pub struct MonitorHandle { /// The actual id - id: u32, + id: RRCrtc, /// The name of the monitor pub(crate) name: String, /// The size of the monitor @@ -121,15 +119,16 @@ impl std::hash::Hash for MonitorHandle { } impl MonitorHandle { - fn from_repr( + fn new( xconn: &XConnection, resources: *mut XRRScreenResources, - id: u32, - repr: util::MonitorRepr, + id: RRCrtc, + crtc: *mut XRRCrtcInfo, primary: bool, ) -> Option { - let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? }; - let (dimensions, position) = unsafe { (repr.size(), repr.position()) }; + let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? }; + 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); Some(MonitorHandle { id, @@ -220,48 +219,19 @@ impl XConnection { let mut available; let mut has_primary = false; - if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT { - // We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and - // videowalls. - let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap(); - let mut monitor_count = 0; - let monitors = - (xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count); - assert!(monitor_count >= 0); - 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; + 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 is_primary = *(*crtc).outputs.offset(0) == primary; has_primary |= is_primary; - MonitorHandle::from_repr( - self, - 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); + MonitorHandle::new(self, resources, crtc_id, crtc, is_primary) + .map(|monitor_id| available.push(monitor_id)); } + (self.xrandr.XRRFreeCrtcInfo)(crtc); } // If no monitors were detected as being primary, we just pick one ourselves! diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 5b6c429506..964e0af390 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,6 +1,9 @@ use std::{env, slice, str::FromStr}; -use super::*; +use super::{ + ffi::{XRRCrtcInfo, XRRScreenResources}, + *, +}; use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode}; pub fn calc_dpi_factor( @@ -34,47 +37,6 @@ pub fn calc_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 { // Retrieve DPI from Xft.dpi property pub unsafe fn get_xft_dpi(&self) -> Option { @@ -96,11 +58,11 @@ impl XConnection { } pub unsafe fn get_output_info( &self, - resources: *mut ffi::XRRScreenResources, - repr: &MonitorRepr, + resources: *mut XRRScreenResources, + crtc: *mut XRRCrtcInfo, ) -> Option<(String, f64, Vec)> { 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() { // When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display) // it's possible for it to return null. @@ -147,7 +109,7 @@ impl XConnection { dpi / 96. } else { calc_dpi_factor( - repr.size(), + ((*crtc).width as u32, (*crtc).height as u32), ( (*output_info).mm_width as u64, (*output_info).mm_height as u64, From d1e13ee3970766193a72a821b6965c414d4c530f Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 25 Jun 2019 12:43:25 +0300 Subject: [PATCH 20/33] Add exclusive fullscreen for X11 --- src/platform_impl/linux/x11/monitor.rs | 48 ++++------ src/platform_impl/linux/x11/util/randr.rs | 60 +++++++++++- src/platform_impl/linux/x11/window.rs | 110 ++++++++++++++++++---- 3 files changed, 170 insertions(+), 48 deletions(-) diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index d9bd786ff9..33a694e0ad 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -4,8 +4,8 @@ use parking_lot::Mutex; use super::{ ffi::{ - RRCrtc, RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, - Window, XRRCrtcInfo, XRRScreenResources, + RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask, + RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources, }, util, XConnection, XError, }; @@ -19,22 +19,9 @@ use crate::{ const DISABLE_MONITOR_LIST_CACHING: bool = false; lazy_static! { - static ref XRANDR_VERSION: Mutex> = Mutex::default(); static ref MONITORS: Mutex>> = 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> { // We update this lazily. (*MONITORS.lock()).take() @@ -45,6 +32,7 @@ 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, } @@ -75,7 +63,7 @@ impl VideoMode { #[derive(Debug, Clone)] pub struct MonitorHandle { /// The actual id - id: RRCrtc, + pub(crate) id: RRCrtc, /// The name of the monitor pub(crate) name: String, /// The size of the monitor @@ -203,8 +191,12 @@ impl XConnection { fn query_monitor_list(&self) -> Vec { 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 version_is_at_least(1, 3) { + let resources = if (major == 1 && minor >= 3) || major > 1 { (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) } else { // WARNING: this function is supposedly very slow, on the order of hundreds of ms. @@ -271,19 +263,15 @@ impl XConnection { } pub fn select_xrandr_input(&self, root: Window) -> Result { - { - let mut version_lock = XRANDR_VERSION.lock(); - if version_lock.is_none() { - let mut major = 0; - let mut minor = 0; - let has_extension = - unsafe { (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) }; - if has_extension != True { - panic!("[winit] XRandR extension not available."); - } - *version_lock = Some((major, minor)); - } - } + let has_xrandr = unsafe { + let mut major = 0; + let mut minor = 0; + (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) + }; + assert!( + has_xrandr == True, + "[winit] XRandR extension not available." + ); let mut event_offset = 0; let mut error_offset = 0; diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 964e0af390..1fbef41db9 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,7 +1,7 @@ use std::{env, slice, str::FromStr}; use super::{ - ffi::{XRRCrtcInfo, XRRScreenResources}, + ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources}, *, }; use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode}; @@ -94,6 +94,7 @@ impl XConnection { size: (x.width, x.height), refresh_rate: (refresh_rate as f32 / 1000.0).round() 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, @@ -120,4 +121,61 @@ impl XConnection { (self.xrandr.XRRFreeOutputInfo)(output_info); 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 + } + } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index d1cbab12c4..c2941dfd95 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -6,10 +6,11 @@ use parking_lot::Mutex; use crate::{ dpi::{LogicalPosition, LogicalSize}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, - monitor::MonitorHandle as RootMonitorHandle, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::{ x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle}, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, + VideoMode as PlatformVideoMode, }, window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, }; @@ -37,8 +38,10 @@ pub struct SharedState { pub last_monitor: Option, pub dpi_adjusted: Option<(f64, f64)>, pub fullscreen: Option, - // Used to restore position after exiting fullscreen. + // Used to restore position after exiting fullscreen 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, pub min_inner_size: Option, pub max_inner_size: Option, @@ -397,6 +400,7 @@ impl UnownedWindow { if window_attrs.fullscreen.is_some() { window .set_fullscreen_inner(window_attrs.fullscreen.clone()) + .unwrap() .queue(); } if window_attrs.always_on_top { @@ -550,27 +554,98 @@ impl UnownedWindow { self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)) } - fn set_fullscreen_inner(&self, fullscreen: Option) -> util::Flusher<'_> { + fn set_fullscreen_inner(&self, fullscreen: Option) -> Option> { + 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(); + + // 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 + match (&old_fullscreen, &fullscreen) { + ( + &None, + &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))); + } + _ => (), + } + + drop(shared_state_lock); + match fullscreen { None => { let flusher = self.set_fullscreen_hint(false); - if let Some(position) = self.shared_state.lock().restore_position.take() { + // Restore desktop video mode and previous window position + let mut shared_state_lock = self.shared_state.lock(); + if let Some((monitor_id, mode_id)) = shared_state_lock.desktop_video_mode.take() { + self.xconn + .set_crtc_config(monitor_id, mode_id) + .expect("failed to restore desktop video mode"); + } + if let Some(position) = shared_state_lock.restore_position.take() { self.set_position_inner(position.0, position.1).queue(); } - flusher + Some(flusher) } - Some(Fullscreen::Exclusive(_)) => unimplemented!("fullscreen on X11"), // TODO - Some(Fullscreen::Borderless(RootMonitorHandle { - inner: PlatformMonitorHandle::X(monitor), - })) => { + Some(fullscreen) => { + 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(); self.shared_state.lock().restore_position = Some(window_position); let monitor_origin: (i32, i32) = monitor.position().into(); self.set_position_inner(monitor_origin.0, monitor_origin.1) .queue(); - self.set_fullscreen_hint(true) + Some(self.set_fullscreen_hint(true)) } - Some(Fullscreen::Borderless(_)) => unreachable!(), } } @@ -580,12 +655,13 @@ impl UnownedWindow { } #[inline] - pub fn set_fullscreen(&self, monitor: Option) { - self.shared_state.lock().fullscreen = monitor.clone(); - self.set_fullscreen_inner(monitor) - .flush() - .expect("Failed to change window fullscreen state"); - self.invalidate_cached_frame_extents(); + pub fn set_fullscreen(&self, fullscreen: Option) { + if let Some(flusher) = self.set_fullscreen_inner(fullscreen) { + flusher + .flush() + .expect("Failed to change window fullscreen state"); + self.invalidate_cached_frame_extents(); + } } fn get_rect(&self) -> util::AaRect { From 890643cbbcfcc505b3bfbc63cf757341f5bdfd70 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 25 Jun 2019 12:46:17 +0300 Subject: [PATCH 21/33] Update FEATURES.md --- FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FEATURES.md b/FEATURES.md index 8628d95495..05b3d1ac57 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -160,7 +160,7 @@ Legend: |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | -|Exclusive fullscreen |✔️ |✔️ |❌ |**N/A** |❌ |❌ |❌ | +|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |❌ |❌ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | From 793062086f8af23b496f9cb80f903666c3f4325b Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 25 Jun 2019 14:26:29 +0300 Subject: [PATCH 22/33] Fix transitions between exclusive and borderless fullscreen on X11 --- src/platform_impl/linux/x11/window.rs | 28 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index c2941dfd95..e69a8fe4d0 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -562,21 +562,35 @@ impl UnownedWindow { } shared_state_lock.fullscreen = fullscreen.clone(); - // 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 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"); + } _ => (), } @@ -585,13 +599,7 @@ impl UnownedWindow { match fullscreen { None => { let flusher = self.set_fullscreen_hint(false); - // Restore desktop video mode and previous window position let mut shared_state_lock = self.shared_state.lock(); - if let Some((monitor_id, mode_id)) = shared_state_lock.desktop_video_mode.take() { - self.xconn - .set_crtc_config(monitor_id, mode_id) - .expect("failed to restore desktop video mode"); - } if let Some(position) = shared_state_lock.restore_position.take() { self.set_position_inner(position.0, position.1).queue(); } From f9561992445274e63e456f6d334edc136c916ecf Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 25 Jun 2019 14:28:18 +0300 Subject: [PATCH 23/33] Update CHANGELOG.md --- CHANGELOG.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d0995c752..0cb4fd554a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ - Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`. - On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor. - On Windows, when a window is initially invisible, it won't take focus from the existing visible windows. +- On macOS, the dock and the menu bar are now hidden in fullscreen mode. +- `Window::set_fullscreen` now takes `Option` where `Fullscreen` + consists of `Fullscreen::Exclusive(VideoMode)` and + `Fullscreen::Borderless(MonitorHandle)` variants. + - Adds support for exclusive fullscreen mode. # 0.20.0 Alpha 1 @@ -50,12 +55,6 @@ - On Wayland, the window now exists even if nothing has been drawn. - On Windows, fix initial dimensions of a fullscreen window. - On Windows, Fix transparent borderless windows rendering wrong. -- On macOS, add `WindowExtMacOS::set_fullscreen_presentation_options` for hiding the dock and the menu bar in fullscreen mode. -- On macOS, the dock and the menu bar are now hidden in fullscreen mode. -- `Window::set_fullscreen` now takes `Option` where `Fullscreen` - consists of `Fullscreen::Exclusive(VideoMode)` and - `Fullscreen::Borderless(MonitorHandle)` variants. - - Adds support for exclusive fullscreen mode. # Version 0.19.1 (2019-04-08) From 8b8a0456e44697e72a221cfc7ae4ef0088767e16 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Tue, 25 Jun 2019 14:34:44 +0300 Subject: [PATCH 24/33] Document that Wayland doesn't support exclusive fullscreen --- src/window.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window.rs b/src/window.rs index 6ba82e2e83..645c35c996 100644 --- a/src/window.rs +++ b/src/window.rs @@ -557,6 +557,7 @@ impl Window { /// /// The dock and the menu bar are always disabled in fullscreen mode. /// - **iOS:** Can only be called on the main thread. + /// - **Wayland:** Does not support exclusive fullscreen mode. /// /// [simple]: /// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen From b6eb841ff094efa3a85a9dfea4d4b114b9b05bcd Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Wed, 26 Jun 2019 15:39:20 +0300 Subject: [PATCH 25/33] Replace core-graphics display mode bindings on macOS --- Cargo.toml | 15 ++---- src/platform_impl/macos/ffi.rs | 36 ++++++++++++- src/platform_impl/macos/monitor.rs | 86 ++++++++++++++++++++++++------ src/platform_impl/macos/window.rs | 40 +++++--------- 4 files changed, 119 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cf384919ca..18b7e954b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,22 +32,13 @@ version = "0.2" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] +cocoa = "0.18.4" +core-foundation = "0.6" +core-graphics = "0.17.3" core-video-sys = "0.1.2" dispatch = "0.1.4" objc = "0.2.3" -[target.'cfg(target_os = "macos")'.dependencies.cocoa] -git = "https://github.com/aleksijuvani/core-foundation-rs/" -rev = "9293a70904892ab279b264be5f1b6efe83e82abd" - -[target.'cfg(target_os = "macos")'.dependencies.core-foundation] -git = "https://github.com/aleksijuvani/core-foundation-rs/" -rev = "9293a70904892ab279b264be5f1b6efe83e82abd" - -[target.'cfg(target_os = "macos")'.dependencies.core-graphics] -git = "https://github.com/aleksijuvani/core-foundation-rs/" -rev = "9293a70904892ab279b264be5f1b6efe83e82abd" - [target.'cfg(target_os = "windows")'.dependencies] bitflags = "1" diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 03ae551065..aec0fc974e 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -6,7 +6,9 @@ use cocoa::{ base::id, foundation::{NSInteger, NSUInteger}, }; -use core_foundation::uuid::CFUUIDRef; +use core_foundation::{ + array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef, +}; use core_graphics::{ base::CGError, display::{CGDirectDisplayID, CGDisplayConfigRef}, @@ -140,7 +142,24 @@ 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" { @@ -172,4 +191,19 @@ extern "C" { 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); } diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 50d0210f60..c940889036 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -1,23 +1,27 @@ 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::{ appkit::NSScreen, base::{id, nil}, 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::{ kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay, CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease, }; -use super::ffi; -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform_impl::platform::util::IdRef, -}; - #[derive(Derivative)] #[derivative(Debug, Clone, PartialEq, Hash)] pub struct VideoMode { @@ -29,10 +33,27 @@ pub struct VideoMode { pub(crate) native_mode: NativeDisplayMode, } -#[derive(Clone)] -pub struct NativeDisplayMode(pub CGDisplayMode); +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() @@ -192,11 +213,25 @@ impl MonitorHandle { let monitor = self.clone(); - CGDisplayMode::all_display_modes(self.0, std::ptr::null()) - .expect("failed to obtain list of display modes") - .into_iter() - .map(move |mode| { - let cg_refresh_rate = mode.refresh_rate().round() as i64; + unsafe { + let modes = { + 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 // isn't a CRT @@ -206,16 +241,33 @@ impl MonitorHandle { cv_refresh_rate }; + let pixel_encoding = + 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: (mode.width() as u32, mode.height() as u32), + size: ( + ffi::CGDisplayModeGetPixelWidth(mode) as u32, + ffi::CGDisplayModeGetPixelHeight(mode) as u32, + ), 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 { diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index bd05894fc0..760f301d0f 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -34,7 +34,7 @@ use cocoa::{ base::{id, nil}, foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, }; -use core_graphics::display::{CGConfigureOption, CGDisplay, CGDisplayMode}; +use core_graphics::display::{CGDisplay, CGDisplayMode}; use objc::{ declare::ClassDecl, runtime::{Class, Object, Sel, BOOL, NO, YES}, @@ -692,9 +692,6 @@ impl UnownedWindow { } if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen { - let display_id = video_mode.monitor().inner.native_identifier(); - let display = CGDisplay::new(display_id); - // 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 @@ -707,6 +704,8 @@ impl UnownedWindow { // 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 { @@ -730,31 +729,16 @@ impl UnownedWindow { assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess); } - // Capturing (and subsequently releasing) the display invalidates - // all display modes, so the stored display mode may no longer be - // valid. Calling functions on it still yields correct results, but - // configuring the display to use that display mode will no longer - // succeed, so query display modes anew and look for a mode that - // matches the description of our stored mode. - let video_mode = video_mode - .monitor() - .video_modes() - .find(|x| x == video_mode) - .expect("failed to find a video mode matching the stored video mode"); - - let config = display - .begin_configuration() - .expect("failed to begin display configuration"); - display - .configure_display_with_display_mode(&config, &video_mode.video_mode.native_mode.0) - .expect("failed to set display mode"); - display - .complete_configuration(&config, CGConfigureOption::ConfigureForAppOnly) - .expect("failed to apply new display configuration"); - - // After the display has been configured, fade back in - // asynchronously 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, From 9492f65c83dce5d2a92262d54f54edbc66101244 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Fri, 28 Jun 2019 10:56:03 +0300 Subject: [PATCH 26/33] Use `panic!()` instead of `unreachable!()` in "fullscreen" example --- examples/fullscreen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index f3aa57f093..d4b83bb0f7 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -17,7 +17,7 @@ fn main() { let fullscreen = Some(match num { 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))), 2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)), - _ => unreachable!("Please enter a valid number"), + _ => panic!("Please enter a valid number"), }); let mut is_maximized = false; From 796cd07e3b2b4d8d173090c9e3296c9850d88f9f Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Fri, 28 Jun 2019 14:27:22 +0300 Subject: [PATCH 27/33] Fix fullscreen "always on top" flag on Windows --- src/platform_impl/windows/window_state.rs | 28 +++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 8c9d030b52..f891a6b5be 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -275,20 +275,20 @@ impl WindowFlags { winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); - // 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, - ); + let mut flags = winuser::SWP_NOZORDER + | winuser::SWP_NOMOVE + | winuser::SWP_NOSIZE + | winuser::SWP_FRAMECHANGED; + + // We generally don't want style changes here to affect window + // focus, but for fullscreen windows they must be activated + // (i.e. focused) so that they appear on top of the taskbar + if !new.contains(WindowFlags::MARKER_FULLSCREEN) { + flags |= 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); } } From a7db5524972e1d6ba5f32a71bd083167823101c4 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Fri, 28 Jun 2019 14:29:24 +0300 Subject: [PATCH 28/33] Track current monitor for fullscreen in "multithreaded" example --- examples/multithreaded.rs | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 97a5c53ceb..4f5dbc4963 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -19,13 +19,34 @@ fn main() { .with_inner_size(WINDOW_SIZE.into()) .build(&event_loop) .unwrap(); - let video_modes: Vec<_> = window.current_monitor().video_modes().collect(); - let mut video_mode = 0usize; + + let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect(); + let mut video_mode_id = 0usize; + let (tx, rx) = mpsc::channel(); window_senders.insert(window.id(), tx); thread::spawn(move || { while let Ok(event) = rx.recv() { 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 { input: KeyboardInput { @@ -48,14 +69,14 @@ fn main() { D => window.set_decorations(!state), // Cycle through video modes Right | Left => { - video_mode = match key { - Left => video_mode.saturating_sub(1), - Right => (video_modes.len() - 1).min(video_mode + 1), + 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).unwrap() + video_modes.iter().nth(video_mode_id).unwrap() ); } F => window.set_fullscreen(match (state, modifiers.alt) { @@ -63,7 +84,7 @@ fn main() { Some(Fullscreen::Borderless(window.current_monitor())) } (true, true) => Some(Fullscreen::Exclusive( - video_modes.iter().nth(video_mode).unwrap().clone(), + video_modes.iter().nth(video_mode_id).unwrap().clone(), )), (false, _) => None, }), From 81bbd31a8b9de6bbafe52a01919702a11dd24126 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 22 Jul 2019 17:46:41 -0400 Subject: [PATCH 29/33] Fix exclusive fullscreen sometimes not positioning window properly --- src/platform_impl/windows/window.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 756fc5ad6c..5a3eada688 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -516,6 +516,19 @@ impl Window { _ => (), } + 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()) From b5976342b837aaf320d60b68781704f09d33ef26 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 22 Jul 2019 18:44:45 -0400 Subject: [PATCH 30/33] Format --- src/platform_impl/macos/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 5835d60c0c..a475701e58 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -13,9 +13,9 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform::macos::{ActivationPolicy, WindowExtMacOS}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, + platform::macos::{ActivationPolicy, WindowExtMacOS}, platform_impl::platform::{ app_state::AppState, ffi, From cd61cc099dd034d9f6a1b4434202d0a7593a3886 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 22 Jul 2019 20:12:08 -0400 Subject: [PATCH 31/33] More formatting and fix CI issues --- src/platform_impl/ios/view.rs | 4 +--- src/platform_impl/macos/window.rs | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 9504781467..21e58524a5 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -367,9 +367,7 @@ pub unsafe fn create_window( } match window_attributes.fullscreen { Some(Fullscreen::Exclusive(_)) => unimplemented!(), - Some(Fullscreen::Borderless(ref monitor)) => { - msg_send![window, setScreen:monitor.ui_screen()] - } + Some(Fullscreen::Borderless(ref monitor)) => msg_send![window, setScreen:monitor.ui_screen()], None => (), } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index a475701e58..55c6257379 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -13,9 +13,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, - platform::macos::{ActivationPolicy, WindowExtMacOS}, platform_impl::platform::{ app_state::AppState, ffi, From 28f73153c335fc30e46b18a4cd987f326614e937 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani Date: Fri, 26 Jul 2019 08:35:38 +0300 Subject: [PATCH 32/33] Fix formatting --- src/platform_impl/ios/view.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 21e58524a5..9504781467 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -367,7 +367,9 @@ pub unsafe fn create_window( } match window_attributes.fullscreen { Some(Fullscreen::Exclusive(_)) => unimplemented!(), - Some(Fullscreen::Borderless(ref monitor)) => msg_send![window, setScreen:monitor.ui_screen()], + Some(Fullscreen::Borderless(ref monitor)) => { + msg_send![window, setScreen:monitor.ui_screen()] + } None => (), } From f17b74c89e003bc11e294aedc2c6aff6928c1915 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 29 Jul 2019 13:30:24 -0400 Subject: [PATCH 33/33] Fix changelog formatting --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff1389b81..b8ec93461d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Unreleased + - On macOS, drop the run closure on exit. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - 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 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`. - - On macOS, the dock and the menu bar are now hidden in fullscreen mode. - `Window::set_fullscreen` now takes `Option` where `Fullscreen` consists of `Fullscreen::Exclusive(VideoMode)` and