Skip to content

Commit

Permalink
Implement ability to make non-activating window on macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
Exidex committed Dec 8, 2024
1 parent 5835c91 commit 35a47fa
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 45 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSMenu",
"NSMenuItem",
"NSOpenGLView",
"NSPanel",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
Expand Down
27 changes: 27 additions & 0 deletions src/platform/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ pub trait WindowAttributesExtMacOS {
fn with_borderless_game(self, borderless_game: bool) -> Self;
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
/// Defines kind of the window
fn with_window_kind(self, kind: WindowKind) -> Self;
}

impl WindowAttributesExtMacOS for WindowAttributes {
Expand Down Expand Up @@ -412,6 +414,12 @@ impl WindowAttributesExtMacOS for WindowAttributes {
self.platform_specific.unified_titlebar = unified_titlebar;
self
}

#[inline]
fn with_window_kind(mut self, kind: WindowKind) -> Self {
self.platform_specific.window_kind = kind;
self
}
}

pub trait EventLoopBuilderExtMacOS {
Expand Down Expand Up @@ -580,6 +588,25 @@ pub enum OptionAsAlt {
None,
}

/// Window Kind
///
/// The default is `Normal`.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WindowKind {
/// Normal MacOS using `NSWindow`
///
/// [`NSWindow`]: objc2_app_kit::NSWindow
#[default]
Normal,

/// [`NSPanel`] window with [`NonactivatingPanel`] window style mask
///
/// [`NSPanel`]: objc2_app_kit::NSPanel
/// [`NonactivatingPanel`]: objc2_app_kit::NSWindowStyleMask::NonactivatingPanel
Popup,
}

/// Additional events on [`ApplicationHandler`] that are specific to macOS.
///
/// This can be registered with [`ApplicationHandler::macos_handler`].
Expand Down
21 changes: 9 additions & 12 deletions src/platform_impl/apple/appkit/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use std::rc::Rc;
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView,
};
use objc2_app_kit::{NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag, NSView, NSWindow};
use objc2_foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
Expand All @@ -23,7 +20,7 @@ use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey, KeyEventExtra,
};
use super::window::WinitWindow;
use super::window::ns_window_id;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
Expand Down Expand Up @@ -201,7 +198,7 @@ declare_class!(
fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:");

self.ivars().app_state.handle_redraw(self.window().id());
self.ivars().app_state.handle_redraw(ns_window_id(&self.window()));

// This is a direct subclass of NSView, no need to call superclass' drawRect:
}
Expand Down Expand Up @@ -426,7 +423,7 @@ declare_class!(
}

// Send command action to user if they requested it.
let window_id = self.window().id();
let window_id = ns_window_id(&self.window());
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
if let Some(handler) = app.macos_handler() {
handler.standard_key_binding(event_loop, window_id, command.name());
Expand Down Expand Up @@ -828,19 +825,19 @@ impl WinitView {
this
}

fn window(&self) -> Retained<WinitWindow> {
fn window(&self) -> Retained<NSWindow> {
let window = (**self).window().expect("view must be installed in a window");

if !window.isKindOfClass(WinitWindow::class()) {
unreachable!("view installed in non-WinitWindow");
if !window.isKindOfClass(NSWindow::class()) {
unreachable!("view installed in non-NSWindow");
}

// SAFETY: Just checked that the window is `WinitWindow`
// SAFETY: Just checked that the window is `NSWindow`
unsafe { Retained::cast(window) }
}

fn queue_event(&self, event: WindowEvent) {
let window_id = self.window().id();
let window_id = ns_window_id(&self.window());
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
Expand Down
38 changes: 32 additions & 6 deletions src/platform_impl/apple/appkit/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSResponder, NSWindow};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};

use super::event_loop::ActiveEventLoop;
Expand All @@ -16,7 +16,7 @@ use crate::window::{
};

pub(crate) struct Window {
window: MainThreadBound<Retained<WinitWindow>>,
window: MainThreadBound<Retained<NSWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
Expand Down Expand Up @@ -360,8 +360,34 @@ declare_class!(
}
);

impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId::from_raw(self as *const Self as usize)
declare_class!(
#[derive(Debug)]
pub struct WinitPanel;

unsafe impl ClassType for WinitPanel {
#[inherits(NSWindow, NSResponder, NSObject)]
type Super = NSPanel;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitPanel";
}
}

impl DeclaredClass for WinitPanel {}

unsafe impl WinitPanel {
#[method(canBecomeMainWindow)]
fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow");
true
}
#[method(canBecomeKeyWindow)]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
}
}
);

pub(super) fn ns_window_id(window: &NSWindow) -> WindowId {
WindowId::from_raw(window as *const _ as usize)
}
67 changes: 40 additions & 27 deletions src/platform_impl/apple/appkit/window_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,7 @@ use monitor::VideoModeHandle;
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
};
use objc2_app_kit::{NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification, NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle};
use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
Expand All @@ -33,22 +25,23 @@ use super::cursor::cursor_from_icon;
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop;
use super::view::WinitView;
use super::window::WinitWindow;
use super::window::{ns_window_id, WinitPanel, WinitWindow};
use super::{ffi, Fullscreen, MonitorHandle};
use crate::dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
Position, Size,
};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{SurfaceSizeWriter, WindowEvent};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS, WindowKind};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};

#[derive(Clone, Debug, PartialEq)]
pub struct PlatformSpecificWindowAttributes {
pub window_kind: WindowKind,
pub movable_by_window_background: bool,
pub titlebar_transparent: bool,
pub title_hidden: bool,
Expand All @@ -68,6 +61,7 @@ impl Default for PlatformSpecificWindowAttributes {
#[inline]
fn default() -> Self {
Self {
window_kind: WindowKind::default(),
movable_by_window_background: false,
titlebar_transparent: false,
title_hidden: false,
Expand All @@ -90,7 +84,7 @@ pub(crate) struct State {
/// Strong reference to the global application state.
app_state: Rc<AppState>,

window: Retained<WinitWindow>,
window: Retained<NSWindow>,

// During `windowDidResize`, we use this to only send Moved if the position changed.
//
Expand Down Expand Up @@ -501,7 +495,7 @@ fn new_window(
app_state: &Rc<AppState>,
attrs: &WindowAttributes,
mtm: MainThreadMarker,
) -> Option<Retained<WinitWindow>> {
) -> Option<Retained<NSWindow>> {
autoreleasepool(|_| {
let screen = match attrs.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Borderless(Some(monitor)))
Expand Down Expand Up @@ -584,16 +578,35 @@ fn new_window(
// confusing issues with the window not being properly activated.
//
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
let window: Option<Retained<WinitWindow>> = unsafe {
msg_send_id![
super(mtm.alloc().set_ivars(())),
initWithContentRect: frame,
styleMask: masks,
backing: NSBackingStoreType::NSBackingStoreBuffered,
defer: false,
]
let window: Retained<NSWindow> = match attrs.platform_specific.window_kind {
WindowKind::Normal => {
let window: Option<Retained<WinitWindow>> = unsafe {
msg_send_id![
super(mtm.alloc().set_ivars(())),
initWithContentRect: frame,
styleMask: masks,
backing: NSBackingStoreType::NSBackingStoreBuffered,
defer: false,
]
};

window?.as_super().retain()
}
WindowKind::Popup => {
masks |= NSWindowStyleMask::NonactivatingPanel;
let window: Option<Retained<WinitPanel>> = unsafe {
msg_send_id![
super(mtm.alloc().set_ivars(())),
initWithContentRect: frame,
styleMask: masks,
backing: NSBackingStoreType::NSBackingStoreBuffered,
defer: false,
]
};

window?.as_super().as_super().retain()
}
};
let window = window?;

// It is very important for correct memory management that we
// disable the extra release that would otherwise happen when
Expand Down Expand Up @@ -841,17 +854,17 @@ impl WindowDelegate {
}

#[track_caller]
pub(super) fn window(&self) -> &WinitWindow {
pub(super) fn window(&self) -> &NSWindow {
&self.ivars().window
}

#[track_caller]
pub(crate) fn id(&self) -> WindowId {
self.window().id()
ns_window_id(&self.window())
}

pub(crate) fn queue_event(&self, event: WindowEvent) {
let window_id = self.window().id();
let window_id = ns_window_id(&self.window());
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
Expand Down Expand Up @@ -950,7 +963,7 @@ impl WindowDelegate {
}

pub fn request_redraw(&self) {
self.ivars().app_state.queue_redraw(self.window().id());
self.ivars().app_state.queue_redraw(ns_window_id(&self.window()));
}

#[inline]
Expand Down Expand Up @@ -1488,7 +1501,7 @@ impl WindowDelegate {

self.ivars().fullscreen.replace(fullscreen.clone());

fn toggle_fullscreen(window: &WinitWindow) {
fn toggle_fullscreen(window: &NSWindow) {
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
Expand Down

0 comments on commit 35a47fa

Please sign in to comment.