diff --git a/CHANGELOG.md b/CHANGELOG.md index 253f0d6c..4be82eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`. - On macOS, fix native file dialogs hanging the event loop. - On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland` +- On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode. # 0.25.0 (2021-05-15) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 7b46ba9a..afea222e 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -281,7 +281,10 @@ pub struct SharedState { is_simple_fullscreen: bool, pub saved_style: Option, /// Presentation options saved before entering `set_simple_fullscreen`, and - /// restored upon exiting it + /// restored upon exiting it. Also used when transitioning from Borderless to + /// Exclusive fullscreen in `set_fullscreen` because we need to disable the menu + /// bar in exclusive fullscreen but want to restore the original options when + /// transitioning back to borderless fullscreen. save_presentation_opts: Option, pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, } @@ -782,6 +785,15 @@ impl UnownedWindow { let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken; + if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) { + unsafe { + let app = NSApp(); + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions_()); + } + } + unsafe { // Fade to black (and wait for the fade to complete) to hide the // flicker from capturing the display and switching display mode @@ -832,7 +844,6 @@ impl UnownedWindow { 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`"); INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); @@ -873,16 +884,42 @@ impl UnownedWindow { // 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:`. + let app = NSApp(); + trace!("Locked shared state in `set_fullscreen`"); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions_()); + + let presentation_options = + NSApplicationPresentationOptions::NSApplicationPresentationFullScreen + | NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; + app.setPresentationOptions_(presentation_options); + let () = msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1]; }, ( &Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &Some(Fullscreen::Borderless(_)), ) => unsafe { + let presentation_options = + shared_state_lock.save_presentation_opts.unwrap_or_else(|| { + NSApplicationPresentationOptions::NSApplicationPresentationFullScreen + | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar + }); + NSApp().setPresentationOptions_(presentation_options); + util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); + + // Restore the normal window level following the Borderless fullscreen + // `CGShieldingWindowLevel() + 1` hack. + let () = msg_send![ + *self.ns_window, + setLevel: ffi::NSWindowLevel::NSNormalWindowLevel + ]; }, _ => INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst), - } + }; + 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 d77c8957..2c0d962b 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -482,10 +482,10 @@ extern "C" fn window_will_exit_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 { // Generally, games will want to disable the menu bar and the dock. Ideally, // this would be configurable by the user. Unfortunately because of our @@ -495,10 +495,22 @@ extern "C" fn window_will_use_fullscreen_presentation_options( // 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() + let mut options: NSUInteger = proposed_options; + with_state(this, |state| { + state.with_window(|window| { + trace!("Locked shared state in `window_will_use_fullscreen_presentation_options`"); + let shared_state = window.shared_state.lock().unwrap(); + if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { + options = (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen + | NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar) + .bits(); + } + trace!("Unlocked shared state in `window_will_use_fullscreen_presentation_options`"); + }) + }); + + options } /// Invoked when entered fullscreen