Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
#[cfg(target_os = "macos")]
extern crate objc as objc2;

pub mod dpi;
#[macro_use]
Expand Down
75 changes: 36 additions & 39 deletions src/platform_impl/macos/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,50 @@ use cocoa::{
appkit::{self, NSEvent},
base::id,
};
use objc::{
declare::ClassBuilder,
runtime::{Class, Object, Sel},
};
use once_cell::sync::Lazy;
use objc2::foundation::NSObject;
use objc2::{declare_class, ClassType};

use super::appkit::{NSApplication, NSResponder};
use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event};

pub struct AppClass(pub *const Class);
unsafe impl Send for AppClass {}
unsafe impl Sync for AppClass {}

pub static APP_CLASS: Lazy<AppClass> = Lazy::new(|| unsafe {
let superclass = class!(NSApplication);
let mut decl = ClassBuilder::new("WinitApp", superclass).unwrap();

decl.add_method(sel!(sendEvent:), send_event as extern "C" fn(_, _, _));
declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(super) struct WinitApplication {}

AppClass(decl.register())
});
unsafe impl ClassType for WinitApplication {
#[inherits(NSResponder, NSObject)]
type Super = NSApplication;
}

// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
extern "C" fn send_event(this: &Object, _sel: Sel, event: id) {
unsafe {
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = event.eventType();
let modifier_flags = event.modifierFlags();
if event_type == appkit::NSKeyUp
&& util::has_flag(
modifier_flags,
appkit::NSEventModifierFlags::NSCommandKeyMask,
)
{
let key_window: id = msg_send![this, keyWindow];
let _: () = msg_send![key_window, sendEvent: event];
} else {
maybe_dispatch_device_event(event);
let superclass = util::superclass(this);
let _: () = msg_send![super(this, superclass), sendEvent: event];
unsafe impl WinitApplication {
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
#[sel(sendEvent:)]
fn send_event(&self, event: id) {
unsafe {
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = event.eventType();
let modifier_flags = event.modifierFlags();
if event_type == appkit::NSKeyUp
&& util::has_flag(
modifier_flags,
appkit::NSEventModifierFlags::NSCommandKeyMask,
)
{
let key_window: id = msg_send![self, keyWindow];
let _: () = msg_send![key_window, sendEvent: event];
} else {
maybe_dispatch_device_event(event);
let _: () = msg_send![super(self), sendEvent: event];
}
}
}
}
}
);

unsafe fn maybe_dispatch_device_event(event: id) {
let event_type = event.eventType();
Expand Down
133 changes: 54 additions & 79 deletions src/platform_impl/macos/app_delegate.rs
Original file line number Diff line number Diff line change
@@ -1,89 +1,64 @@
use std::{
cell::{RefCell, RefMut},
os::raw::c_void,
};
use cocoa::appkit::NSApplicationActivationPolicy;
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{declare_class, ClassType};

use cocoa::base::id;
use objc::{
declare::ClassBuilder,
runtime::{Class, Object, Sel},
};
use once_cell::sync::Lazy;
use super::app_state::AppState;

use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};

static AUX_DELEGATE_STATE_NAME: &str = "auxState";

pub struct AuxDelegateState {
pub activation_policy: ActivationPolicy,
pub default_menu: bool,
}

pub struct AppDelegateClass(pub *const Class);
unsafe impl Send for AppDelegateClass {}
unsafe impl Sync for AppDelegateClass {}

pub static APP_DELEGATE_CLASS: Lazy<AppDelegateClass> = Lazy::new(|| unsafe {
let superclass = class!(NSResponder);
let mut decl = ClassBuilder::new("WinitAppDelegate", superclass).unwrap();

decl.add_class_method(sel!(new), new as extern "C" fn(_, _) -> _);
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(_, _));

decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(_, _, _),
);
decl.add_method(
sel!(applicationWillTerminate:),
will_terminate as extern "C" fn(_, _, _),
);
declare_class!(
#[derive(Debug)]
pub(super) struct ApplicationDelegate {
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
}

decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
unsafe impl ClassType for ApplicationDelegate {
type Super = NSObject;
const NAME: &'static str = "WinitApplicationDelegate";
}

AppDelegateClass(decl.register())
});
unsafe impl ApplicationDelegate {
#[sel(initWithActivationPolicy:defaultMenu:)]
fn init(
&mut self,
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
) -> Option<&mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| {
*this.activation_policy = activation_policy;
*this.default_menu = default_menu;
this
})
}

/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS
pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> {
let ptr: *mut c_void = *this.ivar(AUX_DELEGATE_STATE_NAME);
// Watch out that this needs to be the correct type
(*(ptr as *mut RefCell<AuxDelegateState>)).borrow_mut()
}
#[sel(applicationDidFinishLaunching:)]
fn did_finish_launching(&self, _sender: *const Object) {
trace_scope!("applicationDidFinishLaunching:");
AppState::launched(*self.activation_policy, *self.default_menu);
}

extern "C" fn new(class: &Class, _: Sel) -> id {
unsafe {
let this: id = msg_send![class, alloc];
let this: id = msg_send![this, init];
// TODO: Remove the need for this initialization here
(*this).set_ivar(
AUX_DELEGATE_STATE_NAME,
Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
activation_policy: ActivationPolicy::Regular,
default_menu: true,
}))) as *mut c_void,
);
this
#[sel(applicationWillTerminate:)]
fn will_terminate(&self, _sender: *const Object) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
AppState::exit();
}
}
}
);

extern "C" fn dealloc(this: &Object, _: Sel) {
unsafe {
let state_ptr: *mut c_void = *(this.ivar(AUX_DELEGATE_STATE_NAME));
// As soon as the box is constructed it is immediately dropped, releasing the underlying
// memory
drop(Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>));
impl ApplicationDelegate {
pub(super) fn new(
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
) -> Id<Self, Shared> {
unsafe {
msg_send_id![
msg_send_id![Self::class(), alloc],
initWithActivationPolicy: activation_policy,
defaultMenu: default_menu,
]
}
}
}

extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
trace_scope!("applicationDidFinishLaunching:");
AppState::launched(this);
}

extern "C" fn will_terminate(_this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationWillTerminate`");
// TODO: Notify every window that it will be destroyed, like done in iOS?
AppState::exit();
trace!("Completed `applicationWillTerminate`");
}
53 changes: 18 additions & 35 deletions src/platform_impl/macos/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,26 @@ use std::{
};

use cocoa::{
appkit::{NSApp, NSApplication, NSWindow},
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSWindow},
base::{id, nil},
foundation::NSSize,
};
use objc::{
foundation::is_main_thread,
rc::autoreleasepool,
runtime::{Bool, Object},
};
use objc::foundation::is_main_thread;
use objc::rc::autoreleasepool;
use objc::runtime::Bool;
use once_cell::sync::Lazy;

use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
platform::macos::ActivationPolicy,
platform_impl::{
get_aux_state_mut,
platform::{
event::{EventProxy, EventWrapper},
event_loop::{post_dummy_event, PanicInfo},
menu,
observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker},
util::{IdRef, Never},
window::get_window_id,
},
platform_impl::platform::{
event::{EventProxy, EventWrapper},
event_loop::{post_dummy_event, PanicInfo},
menu,
observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker},
util::{IdRef, Never},
window::get_window_id,
},
window::WindowId,
};
Expand Down Expand Up @@ -283,17 +277,21 @@ impl AppState {
}
}

pub fn launched(app_delegate: &Object) {
apply_activation_policy(app_delegate);
pub fn launched(activation_policy: NSApplicationActivationPolicy, create_default_menu: bool) {
unsafe {
let ns_app = NSApp();

// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
ns_app.setActivationPolicy_(activation_policy);

window_activation_hack(ns_app);
// TODO: Consider allowing the user to specify they don't want their application activated
ns_app.activateIgnoringOtherApps_(Bool::YES.as_raw());
};
HANDLER.set_ready();
HANDLER.waker().start();
let create_default_menu = unsafe { get_aux_state_mut(app_delegate).default_menu };
if create_default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
Expand Down Expand Up @@ -450,18 +448,3 @@ unsafe fn window_activation_hack(ns_app: id) {
}
}
}
fn apply_activation_policy(app_delegate: &Object) {
unsafe {
use cocoa::appkit::NSApplicationActivationPolicy::*;
let ns_app = NSApp();
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
let act_pol = get_aux_state_mut(app_delegate).activation_policy;
ns_app.setActivationPolicy_(match act_pol {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
});
}
}
14 changes: 14 additions & 0 deletions src/platform_impl/macos/appkit/application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};

use super::NSResponder;

extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSApplication;

unsafe impl ClassType for NSApplication {
#[inherits(NSObject)]
type Super = NSResponder;
}
);
11 changes: 11 additions & 0 deletions src/platform_impl/macos/appkit/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![deny(unsafe_op_in_unsafe_fn)]

mod application;
mod responder;
mod view;
mod window;

pub(crate) use self::application::NSApplication;
pub(crate) use self::responder::NSResponder;
pub(crate) use self::view::NSView;
pub(crate) use self::window::NSWindow;
11 changes: 11 additions & 0 deletions src/platform_impl/macos/appkit/responder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};

extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSResponder;

unsafe impl ClassType for NSResponder {
type Super = NSObject;
}
);
14 changes: 14 additions & 0 deletions src/platform_impl/macos/appkit/view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};

use super::NSResponder;

extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSView;

unsafe impl ClassType for NSView {
#[inherits(NSObject)]
type Super = NSResponder;
}
);
14 changes: 14 additions & 0 deletions src/platform_impl/macos/appkit/window.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};

use super::NSResponder;

extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSWindow;

unsafe impl ClassType for NSWindow {
#[inherits(NSObject)]
type Super = NSResponder;
}
);
Loading