Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exclusive fullscreen mode #925

Merged
merged 36 commits into from Jul 29, 2019
Merged
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
985fe0d
Add exclusive fullscreen mode
aleksijuvani Jun 15, 2019
3dd7184
Add `WindowExtMacOS::set_fullscreen_presentation_options`
aleksijuvani Jun 17, 2019
bbd88af
Capture display for exclusive fullscreen on macOS
aleksijuvani Jun 17, 2019
cfed2f3
Fix applying video mode on macOS after a fullscreen cycle
aleksijuvani Jun 17, 2019
047c017
Fix compilation on iOS
aleksijuvani Jun 18, 2019
e293bd7
Set monitor appropriately for fullscreen on macOS
aleksijuvani Jun 18, 2019
372056e
Fix exclusive to borderless fullscreen transitions on macOS
aleksijuvani Jun 18, 2019
0f136ef
Fix borderless to exclusive fullscreen transition on macOS
aleksijuvani Jun 19, 2019
ba7a653
Sort video modes on Windows
aleksijuvani Jun 21, 2019
f00b10f
Fix fullscreen issues on Windows
aleksijuvani Jun 21, 2019
471e14b
Fix video mode changes during exclusive fullscreen on Windows
aleksijuvani Jun 22, 2019
6007e14
Add video mode sorting for macOS and iOS
aleksijuvani Jun 22, 2019
809e4ea
Fix monitor `ns_screen` returning `None` after video mode change
aleksijuvani Jun 22, 2019
f419c6d
Fix "multithreaded" example on macOS
aleksijuvani Jun 22, 2019
e4ee11c
Restore video mode upon closing an exclusive fullscreen window
aleksijuvani Jun 22, 2019
68b7258
Fix "multithreaded" example closing multiple windows at once
aleksijuvani Jun 22, 2019
dcfc9f0
Fix compilation on Linux
aleksijuvani Jun 23, 2019
5be964e
Update FEATURES.md
aleksijuvani Jun 23, 2019
f3e4624
Don't care about logical monitor groups on X11
aleksijuvani Jun 25, 2019
d1e13ee
Add exclusive fullscreen for X11
aleksijuvani Jun 25, 2019
890643c
Update FEATURES.md
aleksijuvani Jun 25, 2019
7930620
Fix transitions between exclusive and borderless fullscreen on X11
aleksijuvani Jun 25, 2019
f956199
Update CHANGELOG.md
aleksijuvani Jun 25, 2019
8b8a045
Document that Wayland doesn't support exclusive fullscreen
aleksijuvani Jun 25, 2019
b6eb841
Replace core-graphics display mode bindings on macOS
aleksijuvani Jun 26, 2019
9492f65
Use `panic!()` instead of `unreachable!()` in "fullscreen" example
aleksijuvani Jun 28, 2019
796cd07
Fix fullscreen "always on top" flag on Windows
aleksijuvani Jun 28, 2019
a7db552
Track current monitor for fullscreen in "multithreaded" example
aleksijuvani Jun 28, 2019
81bbd31
Fix exclusive fullscreen sometimes not positioning window properly
Osspial Jul 22, 2019
de8bfdc
Merge branch 'master' into exclusive-fullscreen
Osspial Jul 22, 2019
b597634
Format
Osspial Jul 22, 2019
cd61cc0
More formatting and fix CI issues
Osspial Jul 23, 2019
28f7315
Fix formatting
aleksijuvani Jul 26, 2019
d739fb9
Merge branch 'master' into exclusive-fullscreen
Osspial Jul 29, 2019
3737228
Merge branch 'exclusive-fullscreen' of https://github.com/aleksijuvan…
Osspial Jul 29, 2019
f17b74c
Fix changelog formatting
Osspial Jul 29, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix borderless to exclusive fullscreen transition on macOS
aleksijuvani committed Jun 26, 2019
commit 0f136ef16f09ecc5dee654511b07960f6e81f07a
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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<Fullscreen>` where `Fullscreen`
consists of `Fullscreen::Exclusive(VideoMode)` and
`Fullscreen::Borderless(MonitorHandle)` variants.
11 changes: 0 additions & 11 deletions examples/fullscreen.rs
Original file line number Diff line number Diff line change
@@ -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;

23 changes: 0 additions & 23 deletions src/platform/macos.rs
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions src/platform_impl/macos/ffi.rs
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 4 additions & 1 deletion src/platform_impl/macos/util/async.rs
Original file line number Diff line number Diff line change
@@ -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);
101 changes: 45 additions & 56 deletions src/platform_impl/macos/window.rs
Original file line number Diff line number Diff line change
@@ -244,9 +244,6 @@ pub struct SharedState {
/// restored upon exiting it
save_presentation_opts: Option<NSApplicationPresentationOptions>,
pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>,
/// Requested fullscreen presentation options via
/// `WindowExtMacOS::set_fullscreen_presentation_options`
pub fullscreen_presentation_options: Option<NSApplicationPresentationOptions>,
}

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();
43 changes: 20 additions & 23 deletions src/platform_impl/macos/window_delegate.rs
Original file line number Diff line number Diff line change
@@ -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`");
7 changes: 1 addition & 6 deletions src/window.rs
Original file line number Diff line number Diff line change
@@ -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<Fullscreen>) {
self.window.set_fullscreen(fullscreen)