Refactor macOS to use new objc2 features (#2465)

* Remove UnownedWindow::inner_rect

* Refactor custom view to use much less `unsafe`

The compiler fence is safe to get rid of now since `interpretKeyEvents` takes `&mut self`

* Refactor Window to use much less unsafe

* Refactor NSApplication usage to have much less unsafe

* Remove cocoa dependency

* Enable `deny(unsafe_op_in_unsafe_fn)` on macOS

Also re-enable clippy `let_unit_value` lint

* Remove #[macro_use] on macOS

* Refactor window delegate to use much less unsafe
This commit is contained in:
Mads Marquart 2022-09-08 16:45:29 +02:00 committed by GitHub
parent 05dd31b8ea
commit 340f951d10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2756 additions and 2335 deletions

View file

@ -65,11 +65,8 @@ ndk-glue = "0.7.0"
objc = { version = "=0.3.0-beta.3", package = "objc2" } objc = { version = "=0.3.0-beta.3", package = "objc2" }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
# Branch: objc2 core-foundation = "0.9.3"
# TODO: Use non-git versions before we release core-graphics = "0.22.3"
cocoa = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" }
core-foundation = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" }
core-graphics = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" }
dispatch = "0.2.0" dispatch = "0.2.0"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys] [target.'cfg(target_os = "windows")'.dependencies.windows-sys]

View file

@ -145,7 +145,7 @@ extern crate log;
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(target_os = "ios")]
#[macro_use] #[macro_use]
extern crate objc; extern crate objc;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View file

@ -1,3 +1,4 @@
use objc2::rc::Id;
use std::os::raw::c_void; use std::os::raw::c_void;
use crate::{ use crate::{
@ -240,7 +241,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
} }
fn ns_screen(&self) -> Option<*mut c_void> { fn ns_screen(&self) -> Option<*mut c_void> {
self.inner.ns_screen().map(|s| s as *mut c_void) self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _)
} }
} }

View file

@ -1,14 +1,10 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use cocoa::{
appkit::{self, NSEvent},
base::id,
};
use objc2::foundation::NSObject; use objc2::foundation::NSObject;
use objc2::{declare_class, ClassType}; use objc2::{declare_class, msg_send, ClassType};
use super::appkit::{NSApplication, NSResponder}; use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; use super::{app_state::AppState, event::EventWrapper, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event}; use crate::event::{DeviceEvent, ElementState, Event};
declare_class!( declare_class!(
@ -25,37 +21,33 @@ declare_class!(
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) // 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) // Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
#[sel(sendEvent:)] #[sel(sendEvent:)]
fn send_event(&self, event: id) { fn send_event(&self, event: &NSEvent) {
unsafe { // For posterity, there are some undocumented event types
// For posterity, there are some undocumented event types // (https://github.com/servo/cocoa-rs/issues/155)
// (https://github.com/servo/cocoa-rs/issues/155) // but that doesn't really matter here.
// but that doesn't really matter here. let event_type = event.type_();
let event_type = event.eventType(); let modifier_flags = event.modifierFlags();
let modifier_flags = event.modifierFlags(); if event_type == NSEventType::NSKeyUp
if event_type == appkit::NSKeyUp && modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask)
&& util::has_flag( {
modifier_flags, if let Some(key_window) = self.keyWindow() {
appkit::NSEventModifierFlags::NSCommandKeyMask, unsafe { key_window.sendEvent(event) };
)
{
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];
} }
} else {
maybe_dispatch_device_event(event);
unsafe { msg_send![super(self), sendEvent: event] }
} }
} }
} }
); );
unsafe fn maybe_dispatch_device_event(event: id) { fn maybe_dispatch_device_event(event: &NSEvent) {
let event_type = event.eventType(); let event_type = event.type_();
match event_type { match event_type {
appkit::NSMouseMoved NSEventType::NSMouseMoved
| appkit::NSLeftMouseDragged | NSEventType::NSLeftMouseDragged
| appkit::NSOtherMouseDragged | NSEventType::NSOtherMouseDragged
| appkit::NSRightMouseDragged => { | NSEventType::NSRightMouseDragged => {
let mut events = VecDeque::with_capacity(3); let mut events = VecDeque::with_capacity(3);
let delta_x = event.deltaX() as f64; let delta_x = event.deltaX() as f64;
@ -92,7 +84,9 @@ unsafe fn maybe_dispatch_device_event(event: id) {
AppState::queue_events(events); AppState::queue_events(events);
} }
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => { NSEventType::NSLeftMouseDown
| NSEventType::NSRightMouseDown
| NSEventType::NSOtherMouseDown => {
let mut events = VecDeque::with_capacity(1); let mut events = VecDeque::with_capacity(1);
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {
@ -105,7 +99,7 @@ unsafe fn maybe_dispatch_device_event(event: id) {
AppState::queue_events(events); AppState::queue_events(events);
} }
appkit::NSLeftMouseUp | appkit::NSRightMouseUp | appkit::NSOtherMouseUp => { NSEventType::NSLeftMouseUp | NSEventType::NSRightMouseUp | NSEventType::NSOtherMouseUp => {
let mut events = VecDeque::with_capacity(1); let mut events = VecDeque::with_capacity(1);
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {

View file

@ -1,10 +1,10 @@
use cocoa::appkit::NSApplicationActivationPolicy;
use objc2::foundation::NSObject; use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared}; use objc2::rc::{Id, Shared};
use objc2::runtime::Object; use objc2::runtime::Object;
use objc2::{declare_class, ClassType}; use objc2::{declare_class, msg_send, msg_send_id, ClassType};
use super::app_state::AppState; use super::app_state::AppState;
use super::appkit::NSApplicationActivationPolicy;
declare_class!( declare_class!(
#[derive(Debug)] #[derive(Debug)]

View file

@ -2,7 +2,6 @@ use std::{
cell::{RefCell, RefMut}, cell::{RefCell, RefMut},
collections::VecDeque, collections::VecDeque,
fmt::{self, Debug}, fmt::{self, Debug},
hint::unreachable_unchecked,
mem, mem,
rc::{Rc, Weak}, rc::{Rc, Weak},
sync::{ sync::{
@ -12,27 +11,22 @@ use std::{
time::Instant, time::Instant,
}; };
use cocoa::{ use objc2::foundation::{is_main_thread, NSSize};
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSWindow}, use objc2::rc::autoreleasepool;
base::{id, nil},
foundation::NSSize,
};
use objc::foundation::is_main_thread;
use objc::rc::autoreleasepool;
use objc::runtime::Bool;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent};
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
event::{Event, StartCause, WindowEvent}, event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
platform_impl::platform::{ platform_impl::platform::{
event::{EventProxy, EventWrapper}, event::{EventProxy, EventWrapper},
event_loop::{post_dummy_event, PanicInfo}, event_loop::PanicInfo,
menu, menu,
observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker}, observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker},
util::{IdRef, Never}, util::Never,
window::get_window_id, window::WinitWindow,
}, },
window::WindowId, window::WindowId,
}; };
@ -44,7 +38,7 @@ impl<'a, Never> Event<'a, Never> {
self.map_nonuser_event() self.map_nonuser_event()
// `Never` can't be constructed, so the `UserEvent` variant can't // `Never` can't be constructed, so the `UserEvent` variant can't
// be present here. // be present here.
.unwrap_or_else(|_| unsafe { unreachable_unchecked() }) .unwrap_or_else(|_| unreachable!())
} }
} }
@ -217,14 +211,14 @@ impl Handler {
fn handle_scale_factor_changed_event( fn handle_scale_factor_changed_event(
&self, &self,
callback: &mut Box<dyn EventHandler + 'static>, callback: &mut Box<dyn EventHandler + 'static>,
ns_window: IdRef, window: &WinitWindow,
suggested_size: LogicalSize<f64>, suggested_size: LogicalSize<f64>,
scale_factor: f64, scale_factor: f64,
) { ) {
let mut size = suggested_size.to_physical(scale_factor); let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size; let new_inner_size = &mut size;
let event = Event::WindowEvent { let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*ns_window)), window_id: WindowId(window.id()),
event: WindowEvent::ScaleFactorChanged { event: WindowEvent::ScaleFactorChanged {
scale_factor, scale_factor,
new_inner_size, new_inner_size,
@ -236,18 +230,18 @@ impl Handler {
let physical_size = *new_inner_size; let physical_size = *new_inner_size;
let logical_size = physical_size.to_logical(scale_factor); let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height); let size = NSSize::new(logical_size.width, logical_size.height);
unsafe { NSWindow::setContentSize_(*ns_window, size) }; window.setContentSize(size);
} }
fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box<dyn EventHandler + 'static>) { fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box<dyn EventHandler + 'static>) {
match proxy { match proxy {
EventProxy::DpiChangedProxy { EventProxy::DpiChangedProxy {
ns_window, window,
suggested_size, suggested_size,
scale_factor, scale_factor,
} => self.handle_scale_factor_changed_event( } => self.handle_scale_factor_changed_event(
callback, callback,
ns_window, &window,
suggested_size, suggested_size,
scale_factor, scale_factor,
), ),
@ -255,7 +249,7 @@ impl Handler {
} }
} }
pub enum AppState {} pub(crate) enum AppState {}
impl AppState { impl AppState {
pub fn set_callback<T>(callback: Weak<Callback<T>>, window_target: Rc<RootWindowTarget<T>>) { pub fn set_callback<T>(callback: Weak<Callback<T>>, window_target: Rc<RootWindowTarget<T>>) {
@ -278,18 +272,16 @@ impl AppState {
} }
pub fn launched(activation_policy: NSApplicationActivationPolicy, create_default_menu: bool) { pub fn launched(activation_policy: NSApplicationActivationPolicy, create_default_menu: bool) {
unsafe { let app = NSApp();
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.
app.setActivationPolicy(activation_policy);
// We need to delay setting the activation policy and activating the app window_activation_hack(&app);
// until `applicationDidFinishLaunching` has been called. Otherwise the // TODO: Consider allowing the user to specify they don't want their application activated
// menu bar is initially unresponsive on macOS 10.15. app.activateIgnoringOtherApps(true);
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.set_ready();
HANDLER.waker().start(); HANDLER.waker().start();
if create_default_menu { if create_default_menu {
@ -397,15 +389,12 @@ impl AppState {
HANDLER.set_in_callback(false); HANDLER.set_in_callback(false);
if HANDLER.should_exit() { if HANDLER.should_exit() {
unsafe { let app = NSApp();
let app: id = NSApp(); autoreleasepool(|_| {
app.stop(None);
autoreleasepool(|_| { // To stop event loop immediately, we need to post some event here.
let _: () = msg_send![app, stop: nil]; app.postEvent_atStart(&NSEvent::dummy(), true);
// To stop event loop immediately, we need to post some event here. });
post_dummy_event(app);
});
};
} }
HANDLER.update_start_time(); HANDLER.update_start_time();
match HANDLER.get_old_and_new_control_flow() { match HANDLER.get_old_and_new_control_flow() {
@ -426,25 +415,17 @@ impl AppState {
/// ///
/// If this becomes too bothersome to maintain, it can probably be removed /// If this becomes too bothersome to maintain, it can probably be removed
/// without too much damage. /// without too much damage.
unsafe fn window_activation_hack(ns_app: id) { fn window_activation_hack(app: &NSApplication) {
// Get the application's windows
// TODO: Proper ordering of the windows // TODO: Proper ordering of the windows
let ns_windows: id = msg_send![ns_app, windows]; app.windows().into_iter().for_each(|window| {
let ns_enumerator: id = msg_send![ns_windows, objectEnumerator]; // Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::new`
loop {
// Enumerate over the windows
let ns_window: id = msg_send![ns_enumerator, nextObject];
if ns_window == nil {
break;
}
// And call `makeKeyAndOrderFront` if it was called on the window in `UnownedWindow::new`
// This way we preserve the user's desired initial visiblity status // This way we preserve the user's desired initial visiblity status
// TODO: Also filter on the type/"level" of the window, and maybe other things? // TODO: Also filter on the type/"level" of the window, and maybe other things?
if Bool::from_raw(ns_window.isVisible()).as_bool() { if window.isVisible() {
trace!("Activating visible window"); trace!("Activating visible window");
ns_window.makeKeyAndOrderFront_(nil); window.makeKeyAndOrderFront(None);
} else { } else {
trace!("Skipping activating invisible window"); trace!("Skipping activating invisible window");
} }
} })
} }

View file

@ -1,7 +1,10 @@
use objc2::foundation::NSObject; use objc2::foundation::{MainThreadMarker, NSArray, NSInteger, NSObject, NSUInteger};
use objc2::{extern_class, ClassType}; use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use objc2::{Encode, Encoding};
use super::NSResponder; use super::{NSEvent, NSMenu, NSResponder, NSWindow};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@ -12,3 +15,121 @@ extern_class!(
type Super = NSResponder; type Super = NSResponder;
} }
); );
pub(crate) fn NSApp() -> Id<NSApplication, Shared> {
// TODO: Only allow access from main thread
NSApplication::shared(unsafe { MainThreadMarker::new_unchecked() })
}
extern_methods!(
unsafe impl NSApplication {
/// This can only be called on the main thread since it may initialize
/// the application and since it's parameters may be changed by the main
/// thread at any time (hence it is only safe to access on the main thread).
pub fn shared(_mtm: MainThreadMarker) -> Id<Self, Shared> {
let app: Option<_> = unsafe { msg_send_id![Self::class(), sharedApplication] };
// SAFETY: `sharedApplication` always initializes the app if it isn't already
unsafe { app.unwrap_unchecked() }
}
pub fn currentEvent(&self) -> Option<Id<NSEvent, Shared>> {
unsafe { msg_send_id![self, currentEvent] }
}
#[sel(postEvent:atStart:)]
pub fn postEvent_atStart(&self, event: &NSEvent, front_of_queue: bool);
#[sel(presentationOptions)]
pub fn presentationOptions(&self) -> NSApplicationPresentationOptions;
pub fn windows(&self) -> Id<NSArray<NSWindow, Shared>, Shared> {
unsafe { msg_send_id![self, windows] }
}
pub fn keyWindow(&self) -> Option<Id<NSWindow, Shared>> {
unsafe { msg_send_id![self, keyWindow] }
}
// TODO: NSApplicationDelegate
#[sel(setDelegate:)]
pub fn setDelegate(&self, delegate: &Object);
#[sel(setPresentationOptions:)]
pub fn setPresentationOptions(&self, options: NSApplicationPresentationOptions);
#[sel(hide:)]
pub fn hide(&self, sender: Option<&Object>);
#[sel(orderFrontCharacterPalette:)]
#[allow(dead_code)]
pub fn orderFrontCharacterPalette(&self, sender: Option<&Object>);
#[sel(hideOtherApplications:)]
pub fn hideOtherApplications(&self, sender: Option<&Object>);
#[sel(stop:)]
pub fn stop(&self, sender: Option<&Object>);
#[sel(activateIgnoringOtherApps:)]
pub fn activateIgnoringOtherApps(&self, ignore: bool);
#[sel(requestUserAttention:)]
pub fn requestUserAttention(&self, type_: NSRequestUserAttentionType) -> NSInteger;
#[sel(setActivationPolicy:)]
pub fn setActivationPolicy(&self, policy: NSApplicationActivationPolicy) -> bool;
#[sel(setMainMenu:)]
pub fn setMainMenu(&self, menu: &NSMenu);
#[sel(run)]
pub unsafe fn run(&self);
}
);
#[allow(dead_code)]
#[repr(isize)] // NSInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSApplicationActivationPolicy {
NSApplicationActivationPolicyRegular = 0,
NSApplicationActivationPolicyAccessory = 1,
NSApplicationActivationPolicyProhibited = 2,
NSApplicationActivationPolicyERROR = -1,
}
unsafe impl Encode for NSApplicationActivationPolicy {
const ENCODING: Encoding = NSInteger::ENCODING;
}
bitflags! {
pub struct NSApplicationPresentationOptions: NSUInteger {
const NSApplicationPresentationDefault = 0;
const NSApplicationPresentationAutoHideDock = 1 << 0;
const NSApplicationPresentationHideDock = 1 << 1;
const NSApplicationPresentationAutoHideMenuBar = 1 << 2;
const NSApplicationPresentationHideMenuBar = 1 << 3;
const NSApplicationPresentationDisableAppleMenu = 1 << 4;
const NSApplicationPresentationDisableProcessSwitching = 1 << 5;
const NSApplicationPresentationDisableForceQuit = 1 << 6;
const NSApplicationPresentationDisableSessionTermination = 1 << 7;
const NSApplicationPresentationDisableHideApplication = 1 << 8;
const NSApplicationPresentationDisableMenuBarTransparency = 1 << 9;
const NSApplicationPresentationFullScreen = 1 << 10;
const NSApplicationPresentationAutoHideToolbar = 1 << 11;
}
}
unsafe impl Encode for NSApplicationPresentationOptions {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[repr(usize)] // NSUInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSRequestUserAttentionType {
NSCriticalRequest = 0,
NSInformationalRequest = 10,
}
unsafe impl Encode for NSRequestUserAttentionType {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View file

@ -0,0 +1,14 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use super::{NSControl, NSResponder, NSView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSButton;
unsafe impl ClassType for NSButton {
#[inherits(NSView, NSResponder, NSObject)]
type Super = NSControl;
}
);

View file

@ -0,0 +1,28 @@
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// An object that stores color data and sometimes opacity (alpha value).
///
/// <https://developer.apple.com/documentation/appkit/nscolor?language=objc>
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSColor;
unsafe impl ClassType for NSColor {
type Super = NSObject;
}
);
// SAFETY: Documentation clearly states:
// > Color objects are immutable and thread-safe
unsafe impl Send for NSColor {}
unsafe impl Sync for NSColor {}
extern_methods!(
unsafe impl NSColor {
pub fn clear() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), clearColor] }
}
}
);

View file

@ -0,0 +1,14 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use super::{NSResponder, NSView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSControl;
unsafe impl ClassType for NSControl {
#[inherits(NSResponder, NSObject)]
type Super = NSView;
}
);

View file

@ -3,7 +3,7 @@ use once_cell::sync::Lazy;
use objc2::foundation::{NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSString}; use objc2::foundation::{NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSString};
use objc2::rc::{DefaultId, Id, Shared}; use objc2::rc::{DefaultId, Id, Shared};
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::{extern_class, extern_methods, msg_send_id, ns_string, ClassType}; use objc2::{extern_class, extern_methods, msg_send_id, ns_string, sel, ClassType};
use super::NSImage; use super::NSImage;
use crate::window::CursorIcon; use crate::window::CursorIcon;

View file

@ -0,0 +1,234 @@
use std::os::raw::c_ushort;
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{
CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSTimeInterval, NSUInteger,
};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSEvent;
unsafe impl ClassType for NSEvent {
type Super = NSObject;
}
);
// > Safely handled only on the same thread, whether that be the main thread
// > or a secondary thread; otherwise you run the risk of having events get
// > out of sequence.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123383>
extern_methods!(
unsafe impl NSEvent {
unsafe fn otherEventWithType(
type_: NSEventType,
location: NSPoint,
flags: NSEventModifierFlags,
time: NSTimeInterval,
window_num: NSInteger,
context: Option<&NSObject>, // NSGraphicsContext
subtype: NSEventSubtype,
data1: NSInteger,
data2: NSInteger,
) -> Id<Self, Shared> {
unsafe {
msg_send_id![
Self::class(),
otherEventWithType: type_,
location: location,
modifierFlags: flags,
timestamp: time,
windowNumber: window_num,
context: context,
subtype: subtype,
data1: data1,
data2: data2,
]
}
}
pub fn dummy() -> Id<Self, Shared> {
unsafe {
Self::otherEventWithType(
NSEventType::NSApplicationDefined,
NSPoint::new(0.0, 0.0),
NSEventModifierFlags::empty(),
0.0,
0,
None,
NSEventSubtype::NSWindowExposedEventType,
0,
0,
)
}
}
#[sel(locationInWindow)]
pub fn locationInWindow(&self) -> NSPoint;
// TODO: MainThreadMarker
#[sel(pressedMouseButtons)]
pub fn pressedMouseButtons() -> NSUInteger;
#[sel(modifierFlags)]
pub fn modifierFlags(&self) -> NSEventModifierFlags;
#[sel(type)]
pub fn type_(&self) -> NSEventType;
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
// and there is no easy way to navtively retrieve the layout-dependent character.
// In winit, we use keycode to refer to the key's character, and so this function aligns
// AppKit's terminology with ours.
#[sel(keyCode)]
pub fn scancode(&self) -> c_ushort;
#[sel(magnification)]
pub fn magnification(&self) -> CGFloat;
#[sel(phase)]
pub fn phase(&self) -> NSEventPhase;
#[sel(momentumPhase)]
pub fn momentumPhase(&self) -> NSEventPhase;
#[sel(deltaX)]
pub fn deltaX(&self) -> CGFloat;
#[sel(deltaY)]
pub fn deltaY(&self) -> CGFloat;
#[sel(buttonNumber)]
pub fn buttonNumber(&self) -> NSInteger;
#[sel(scrollingDeltaX)]
pub fn scrollingDeltaX(&self) -> CGFloat;
#[sel(scrollingDeltaY)]
pub fn scrollingDeltaY(&self) -> CGFloat;
#[sel(hasPreciseScrollingDeltas)]
pub fn hasPreciseScrollingDeltas(&self) -> bool;
#[sel(rotation)]
pub fn rotation(&self) -> f32;
#[sel(pressure)]
pub fn pressure(&self) -> f32;
#[sel(stage)]
pub fn stage(&self) -> NSInteger;
pub fn characters(&self) -> Option<Id<NSString, Shared>> {
unsafe { msg_send_id![self, characters] }
}
pub fn charactersIgnoringModifiers(&self) -> Option<Id<NSString, Shared>> {
unsafe { msg_send_id![self, charactersIgnoringModifiers] }
}
}
);
unsafe impl NSCopying for NSEvent {
type Ownership = Shared;
type Output = NSEvent;
}
bitflags! {
pub struct NSEventModifierFlags: NSUInteger {
const NSAlphaShiftKeyMask = 1 << 16;
const NSShiftKeyMask = 1 << 17;
const NSControlKeyMask = 1 << 18;
const NSAlternateKeyMask = 1 << 19;
const NSCommandKeyMask = 1 << 20;
const NSNumericPadKeyMask = 1 << 21;
const NSHelpKeyMask = 1 << 22;
const NSFunctionKeyMask = 1 << 23;
const NSDeviceIndependentModifierFlagsMask = 0xffff0000;
}
}
unsafe impl Encode for NSEventModifierFlags {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
bitflags! {
pub struct NSEventPhase: NSUInteger {
const NSEventPhaseNone = 0;
const NSEventPhaseBegan = 0x1 << 0;
const NSEventPhaseStationary = 0x1 << 1;
const NSEventPhaseChanged = 0x1 << 2;
const NSEventPhaseEnded = 0x1 << 3;
const NSEventPhaseCancelled = 0x1 << 4;
const NSEventPhaseMayBegin = 0x1 << 5;
}
}
unsafe impl Encode for NSEventPhase {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(i16)] // short
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSEventSubtype {
// TODO: Not sure what these values are
// NSMouseEventSubtype = NX_SUBTYPE_DEFAULT,
// NSTabletPointEventSubtype = NX_SUBTYPE_TABLET_POINT,
// NSTabletProximityEventSubtype = NX_SUBTYPE_TABLET_PROXIMITY
// NSTouchEventSubtype = NX_SUBTYPE_MOUSE_TOUCH,
NSWindowExposedEventType = 0,
NSApplicationActivatedEventType = 1,
NSApplicationDeactivatedEventType = 2,
NSWindowMovedEventType = 4,
NSScreenChangedEventType = 8,
NSAWTEventType = 16,
}
unsafe impl Encode for NSEventSubtype {
const ENCODING: Encoding = i16::ENCODING;
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(usize)] // NSUInteger
pub enum NSEventType {
NSLeftMouseDown = 1,
NSLeftMouseUp = 2,
NSRightMouseDown = 3,
NSRightMouseUp = 4,
NSMouseMoved = 5,
NSLeftMouseDragged = 6,
NSRightMouseDragged = 7,
NSMouseEntered = 8,
NSMouseExited = 9,
NSKeyDown = 10,
NSKeyUp = 11,
NSFlagsChanged = 12,
NSAppKitDefined = 13,
NSSystemDefined = 14,
NSApplicationDefined = 15,
NSPeriodic = 16,
NSCursorUpdate = 17,
NSScrollWheel = 22,
NSTabletPoint = 23,
NSTabletProximity = 24,
NSOtherMouseDown = 25,
NSOtherMouseUp = 26,
NSOtherMouseDragged = 27,
NSEventTypeGesture = 29,
NSEventTypeMagnify = 30,
NSEventTypeSwipe = 31,
NSEventTypeRotate = 18,
NSEventTypeBeginGesture = 19,
NSEventTypeEndGesture = 20,
NSEventTypePressure = 34,
}
unsafe impl Encode for NSEventType {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View file

@ -0,0 +1,25 @@
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::NSMenuItem;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSMenu;
unsafe impl ClassType for NSMenu {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl NSMenu {
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
#[sel(addItem:)]
pub fn addItem(&self, item: &NSMenuItem);
}
);

View file

@ -0,0 +1,48 @@
use objc2::foundation::{NSObject, NSString};
use objc2::rc::{Id, Shared};
use objc2::runtime::Sel;
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{NSEventModifierFlags, NSMenu};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSMenuItem;
unsafe impl ClassType for NSMenuItem {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl NSMenuItem {
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
pub fn newWithTitle(
title: &NSString,
action: Sel,
key_equivalent: &NSString,
) -> Id<Self, Shared> {
unsafe {
msg_send_id![
msg_send_id![Self::class(), alloc],
initWithTitle: title,
action: action,
keyEquivalent: key_equivalent,
]
}
}
pub fn separatorItem() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), separatorItem] }
}
#[sel(setKeyEquivalentModifierMask:)]
pub fn setKeyEquivalentModifierMask(&self, mask: NSEventModifierFlags);
#[sel(setSubmenu:)]
pub fn setSubmenu(&self, submenu: &NSMenu);
}
);

View file

@ -1,18 +1,59 @@
#![deny(unsafe_op_in_unsafe_fn)] //! Safe bindings for the AppKit framework.
//!
//! These are split out from the rest of `winit` to make safety easier to review.
//! In the future, these should probably live in another crate like `cacao`.
//!
//! TODO: Main thread safety.
// Objective-C methods have different conventions, and it's much easier to // Objective-C methods have different conventions, and it's much easier to
// understand if we just use the same names // understand if we just use the same names
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::enum_variant_names)]
#![allow(non_upper_case_globals)]
mod application; mod application;
mod button;
mod color;
mod control;
mod cursor; mod cursor;
mod event;
mod image; mod image;
mod menu;
mod menu_item;
mod pasteboard;
mod responder; mod responder;
mod screen;
mod text_input_context;
mod version;
mod view; mod view;
mod window; mod window;
pub(crate) use self::application::NSApplication; pub(crate) use self::application::{
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
NSRequestUserAttentionType,
};
pub(crate) use self::button::NSButton;
pub(crate) use self::color::NSColor;
pub(crate) use self::control::NSControl;
pub(crate) use self::cursor::NSCursor; pub(crate) use self::cursor::NSCursor;
#[allow(unused_imports)]
pub(crate) use self::event::{
NSEvent, NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSEventType,
};
pub(crate) use self::image::NSImage; pub(crate) use self::image::NSImage;
pub(crate) use self::menu::NSMenu;
pub(crate) use self::menu_item::NSMenuItem;
pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboard, NSPasteboardType};
pub(crate) use self::responder::NSResponder; pub(crate) use self::responder::NSResponder;
pub(crate) use self::view::NSView; #[allow(unused_imports)]
pub(crate) use self::window::NSWindow; pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen};
pub(crate) use self::text_input_context::NSTextInputContext;
pub(crate) use self::version::NSAppKitVersion;
pub(crate) use self::view::{NSTrackingRectTag, NSView};
pub(crate) use self::window::{
NSBackingStoreType, NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState,
NSWindowStyleMask, NSWindowTitleVisibility,
};
#[link(name = "AppKit", kind = "framework")]
extern "C" {}

View file

@ -0,0 +1,26 @@
use objc2::foundation::{NSObject, NSString};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSPasteboard;
unsafe impl ClassType for NSPasteboard {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl NSPasteboard {
pub fn propertyListForType(&self, type_: &NSPasteboardType) -> Id<NSObject, Shared> {
unsafe { msg_send_id![self, propertyListForType: type_] }
}
}
);
pub type NSPasteboardType = NSString;
extern "C" {
pub static NSFilenamesPboardType: &'static NSPasteboardType;
}

View file

@ -1,5 +1,8 @@
use objc2::foundation::NSObject; use objc2::foundation::{NSArray, NSObject};
use objc2::{extern_class, ClassType}; use objc2::rc::Shared;
use objc2::{extern_class, extern_methods, ClassType};
use super::NSEvent;
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@ -9,3 +12,13 @@ extern_class!(
type Super = NSObject; type Super = NSObject;
} }
); );
// Documented as "Thread-Unsafe".
extern_methods!(
unsafe impl NSResponder {
// TODO: Allow "immutably" on main thread
#[sel(interpretKeyEvents:)]
pub unsafe fn interpretKeyEvents(&mut self, events: &NSArray<NSEvent, Shared>);
}
);

View file

@ -0,0 +1,64 @@
use objc2::foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{extern_class, extern_methods, msg_send_id, ns_string, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSScreen;
unsafe impl ClassType for NSScreen {
type Super = NSObject;
}
);
// TODO: Main thread marker!
extern_methods!(
unsafe impl NSScreen {
/// The application object must have been created.
pub fn main() -> Option<Id<Self, Shared>> {
unsafe { msg_send_id![Self::class(), mainScreen] }
}
/// The application object must have been created.
pub fn screens() -> Id<NSArray<Self, Shared>, Shared> {
unsafe { msg_send_id![Self::class(), screens] }
}
#[sel(frame)]
pub fn frame(&self) -> NSRect;
#[sel(visibleFrame)]
pub fn visibleFrame(&self) -> NSRect;
pub fn deviceDescription(
&self,
) -> Id<NSDictionary<NSDeviceDescriptionKey, Object>, Shared> {
unsafe { msg_send_id![self, deviceDescription] }
}
pub fn display_id(&self) -> u32 {
let device_description = self.deviceDescription();
// Retrieve the CGDirectDisplayID associated with this screen
//
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for `deviceDescription` for details:
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
let obj = device_description
.get(ns_string!("NSScreenNumber"))
.expect("failed getting screen display id from device description");
let obj: *const Object = obj;
let obj: *const NSNumber = obj.cast();
let obj: &NSNumber = unsafe { &*obj };
obj.as_u32()
}
#[sel(backingScaleFactor)]
pub fn backingScaleFactor(&self) -> CGFloat;
}
);
pub type NSDeviceDescriptionKey = NSString;

View file

@ -0,0 +1,31 @@
use objc2::foundation::{NSObject, NSString};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
type NSTextInputSourceIdentifier = NSString;
extern_class!(
/// Main-Thread-Only!
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSTextInputContext;
unsafe impl ClassType for NSTextInputContext {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl NSTextInputContext {
#[sel(invalidateCharacterCoordinates)]
pub fn invalidateCharacterCoordinates(&self);
#[sel(discardMarkedText)]
pub fn discardMarkedText(&self);
pub fn selectedKeyboardInputSource(
&self,
) -> Option<Id<NSTextInputSourceIdentifier, Shared>> {
unsafe { msg_send_id![self, selectedKeyboardInputSource] }
}
}
);

View file

@ -0,0 +1,62 @@
#[repr(transparent)]
#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
pub struct NSAppKitVersion(f64);
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
impl NSAppKitVersion {
pub fn current() -> Self {
extern "C" {
static NSAppKitVersionNumber: NSAppKitVersion;
}
unsafe { NSAppKitVersionNumber }
}
pub fn floor(self) -> Self {
Self(self.0.floor())
}
pub const NSAppKitVersionNumber10_0: Self = Self(577.0);
pub const NSAppKitVersionNumber10_1: Self = Self(620.0);
pub const NSAppKitVersionNumber10_2: Self = Self(663.0);
pub const NSAppKitVersionNumber10_2_3: Self = Self(663.6);
pub const NSAppKitVersionNumber10_3: Self = Self(743.0);
pub const NSAppKitVersionNumber10_3_2: Self = Self(743.14);
pub const NSAppKitVersionNumber10_3_3: Self = Self(743.2);
pub const NSAppKitVersionNumber10_3_5: Self = Self(743.24);
pub const NSAppKitVersionNumber10_3_7: Self = Self(743.33);
pub const NSAppKitVersionNumber10_3_9: Self = Self(743.36);
pub const NSAppKitVersionNumber10_4: Self = Self(824.0);
pub const NSAppKitVersionNumber10_4_1: Self = Self(824.1);
pub const NSAppKitVersionNumber10_4_3: Self = Self(824.23);
pub const NSAppKitVersionNumber10_4_4: Self = Self(824.33);
pub const NSAppKitVersionNumber10_4_7: Self = Self(824.41);
pub const NSAppKitVersionNumber10_5: Self = Self(949.0);
pub const NSAppKitVersionNumber10_5_2: Self = Self(949.27);
pub const NSAppKitVersionNumber10_5_3: Self = Self(949.33);
pub const NSAppKitVersionNumber10_6: Self = Self(1038.0);
pub const NSAppKitVersionNumber10_7: Self = Self(1138.0);
pub const NSAppKitVersionNumber10_7_2: Self = Self(1138.23);
pub const NSAppKitVersionNumber10_7_3: Self = Self(1138.32);
pub const NSAppKitVersionNumber10_7_4: Self = Self(1138.47);
pub const NSAppKitVersionNumber10_8: Self = Self(1187.0);
pub const NSAppKitVersionNumber10_9: Self = Self(1265.0);
pub const NSAppKitVersionNumber10_10: Self = Self(1343.0);
pub const NSAppKitVersionNumber10_10_2: Self = Self(1344.0);
pub const NSAppKitVersionNumber10_10_3: Self = Self(1347.0);
pub const NSAppKitVersionNumber10_10_4: Self = Self(1348.0);
pub const NSAppKitVersionNumber10_10_5: Self = Self(1348.0);
pub const NSAppKitVersionNumber10_10_Max: Self = Self(1349.0);
pub const NSAppKitVersionNumber10_11: Self = Self(1404.0);
pub const NSAppKitVersionNumber10_11_1: Self = Self(1404.13);
pub const NSAppKitVersionNumber10_11_2: Self = Self(1404.34);
pub const NSAppKitVersionNumber10_11_3: Self = Self(1404.34);
pub const NSAppKitVersionNumber10_12: Self = Self(1504.0);
pub const NSAppKitVersionNumber10_12_1: Self = Self(1504.60);
pub const NSAppKitVersionNumber10_12_2: Self = Self(1504.76);
pub const NSAppKitVersionNumber10_13: Self = Self(1561.0);
pub const NSAppKitVersionNumber10_13_1: Self = Self(1561.1);
pub const NSAppKitVersionNumber10_13_2: Self = Self(1561.2);
pub const NSAppKitVersionNumber10_13_4: Self = Self(1561.4);
}

View file

@ -1,7 +1,13 @@
use objc2::foundation::{NSObject, NSRect}; use std::ffi::c_void;
use objc2::{extern_class, extern_methods, ClassType}; use std::num::NonZeroIsize;
use std::ptr;
use super::{NSCursor, NSResponder}; use objc2::foundation::{NSObject, NSPoint, NSRect};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{NSCursor, NSResponder, NSTextInputContext};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@ -13,16 +19,77 @@ extern_class!(
} }
); );
// Documented as "Main Thread Only".
// > generally thread safe, although operations on views such as creating,
// > resizing, and moving should happen on the main thread.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
//
// > If you want to use a thread to draw to a view, bracket all drawing code
// > between the lockFocusIfCanDraw and unlockFocus methods of NSView.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB>
extern_methods!( extern_methods!(
/// Getter methods /// Getter methods
unsafe impl NSView { unsafe impl NSView {
#[sel(frame)]
pub fn frame(&self) -> NSRect;
#[sel(bounds)] #[sel(bounds)]
pub fn bounds(&self) -> NSRect; pub fn bounds(&self) -> NSRect;
pub fn inputContext(
&self,
// _mtm: MainThreadMarker,
) -> Option<Id<NSTextInputContext, Shared>> {
unsafe { msg_send_id![self, inputContext] }
}
#[sel(visibleRect)]
pub fn visibleRect(&self) -> NSRect;
#[sel(hasMarkedText)]
pub fn hasMarkedText(&self) -> bool;
#[sel(convertPoint:fromView:)]
pub fn convertPoint_fromView(&self, point: NSPoint, view: Option<&NSView>) -> NSPoint;
} }
unsafe impl NSView { unsafe impl NSView {
#[sel(setWantsBestResolutionOpenGLSurface:)]
pub fn setWantsBestResolutionOpenGLSurface(&self, value: bool);
#[sel(setWantsLayer:)]
pub fn setWantsLayer(&self, wants_layer: bool);
#[sel(setPostsFrameChangedNotifications:)]
pub fn setPostsFrameChangedNotifications(&mut self, value: bool);
#[sel(removeTrackingRect:)]
pub fn removeTrackingRect(&self, tag: NSTrackingRectTag);
#[sel(addTrackingRect:owner:userData:assumeInside:)]
unsafe fn inner_addTrackingRect(
&self,
rect: NSRect,
owner: &Object,
user_data: *mut c_void,
assume_inside: bool,
) -> Option<NSTrackingRectTag>;
pub fn add_tracking_rect(&self, rect: NSRect, assume_inside: bool) -> NSTrackingRectTag {
// SAFETY: The user data is NULL, so it is valid
unsafe { self.inner_addTrackingRect(rect, self, ptr::null_mut(), assume_inside) }
.expect("failed creating tracking rect")
}
#[sel(addCursorRect:cursor:)] #[sel(addCursorRect:cursor:)]
// NSCursor safe to take by shared reference since it is already immutable // NSCursor safe to take by shared reference since it is already immutable
pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor); pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor);
#[sel(setHidden:)]
pub fn setHidden(&self, hidden: bool);
} }
); );
/// <https://developer.apple.com/documentation/appkit/nstrackingrecttag?language=objc>
pub type NSTrackingRectTag = NonZeroIsize; // NSInteger, but non-zero!

View file

@ -1,9 +1,15 @@
use objc2::foundation::NSObject; use objc2::encode::{Encode, Encoding};
use objc2::{extern_class, ClassType}; use objc2::foundation::{
CGFloat, NSArray, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, NSUInteger,
};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::NSResponder; use super::{NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView};
extern_class!( extern_class!(
/// Main-Thread-Only!
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct NSWindow; pub(crate) struct NSWindow;
@ -12,3 +18,314 @@ extern_class!(
type Super = NSResponder; type Super = NSResponder;
} }
); );
// Documented as "Main Thread Only", but:
// > Thread safe in that you can create and manage them on a secondary thread.
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123364>
//
// So could in theory be `Send`, and perhaps also `Sync` - but we would like
// interior mutability on windows, since that's just much easier, and in that
// case, they can't be!
extern_methods!(
unsafe impl NSWindow {
#[sel(frame)]
pub fn frame(&self) -> NSRect;
#[sel(backingScaleFactor)]
pub fn backingScaleFactor(&self) -> CGFloat;
pub fn contentView(&self) -> Id<NSView, Shared> {
unsafe { msg_send_id![self, contentView] }
}
#[sel(setContentView:)]
pub fn setContentView(&self, view: &NSView);
#[sel(setInitialFirstResponder:)]
pub fn setInitialFirstResponder(&self, view: &NSView);
#[sel(makeFirstResponder:)]
#[must_use]
pub fn makeFirstResponder(&self, responder: Option<&NSResponder>) -> bool;
#[sel(contentRectForFrameRect:)]
pub fn contentRectForFrameRect(&self, windowFrame: NSRect) -> NSRect;
pub fn screen(&self) -> Option<Id<NSScreen, Shared>> {
unsafe { msg_send_id![self, screen] }
}
#[sel(setContentSize:)]
pub fn setContentSize(&self, contentSize: NSSize);
#[sel(setFrameTopLeftPoint:)]
pub fn setFrameTopLeftPoint(&self, point: NSPoint);
#[sel(setMinSize:)]
pub fn setMinSize(&self, minSize: NSSize);
#[sel(setMaxSize:)]
pub fn setMaxSize(&self, maxSize: NSSize);
#[sel(setResizeIncrements:)]
pub fn setResizeIncrements(&self, increments: NSSize);
#[sel(contentResizeIncrements)]
pub fn contentResizeIncrements(&self) -> NSSize;
#[sel(setContentResizeIncrements:)]
pub fn setContentResizeIncrements(&self, increments: NSSize);
#[sel(setFrame:display:)]
pub fn setFrame_display(&self, frameRect: NSRect, flag: bool);
#[sel(setMovable:)]
pub fn setMovable(&self, movable: bool);
#[sel(setOpaque:)]
pub fn setOpaque(&self, opaque: bool);
#[sel(hasShadow)]
pub fn hasShadow(&self) -> bool;
#[sel(setHasShadow:)]
pub fn setHasShadow(&self, has_shadow: bool);
#[sel(setIgnoresMouseEvents:)]
pub fn setIgnoresMouseEvents(&self, ignores: bool);
#[sel(setBackgroundColor:)]
pub fn setBackgroundColor(&self, color: &NSColor);
#[sel(styleMask)]
pub fn styleMask(&self) -> NSWindowStyleMask;
#[sel(setStyleMask:)]
pub fn setStyleMask(&self, mask: NSWindowStyleMask);
#[sel(registerForDraggedTypes:)]
pub fn registerForDraggedTypes(&self, types: &NSArray<NSPasteboardType>);
#[sel(makeKeyAndOrderFront:)]
pub fn makeKeyAndOrderFront(&self, sender: Option<&Object>);
#[sel(miniaturize:)]
pub fn miniaturize(&self, sender: Option<&Object>);
#[sel(sender:)]
pub fn deminiaturize(&self, sender: Option<&Object>);
#[sel(toggleFullScreen:)]
pub fn toggleFullScreen(&self, sender: Option<&Object>);
#[sel(orderOut:)]
pub fn orderOut(&self, sender: Option<&Object>);
#[sel(zoom:)]
pub fn zoom(&self, sender: Option<&Object>);
#[sel(selectNextKeyView:)]
pub fn selectNextKeyView(&self, sender: Option<&Object>);
#[sel(selectPreviousKeyView:)]
pub fn selectPreviousKeyView(&self, sender: Option<&Object>);
pub fn firstResponder(&self) -> Option<Id<NSResponder, Shared>> {
unsafe { msg_send_id![self, firstResponder] }
}
pub fn standardWindowButton(&self, kind: NSWindowButton) -> Option<Id<NSButton, Shared>> {
unsafe { msg_send_id![self, standardWindowButton: kind] }
}
#[sel(setTitle:)]
pub fn setTitle(&self, title: &NSString);
#[sel(setReleasedWhenClosed:)]
pub fn setReleasedWhenClosed(&self, val: bool);
#[sel(setAcceptsMouseMovedEvents:)]
pub fn setAcceptsMouseMovedEvents(&self, val: bool);
#[sel(setTitlebarAppearsTransparent:)]
pub fn setTitlebarAppearsTransparent(&self, val: bool);
#[sel(setTitleVisibility:)]
pub fn setTitleVisibility(&self, visibility: NSWindowTitleVisibility);
#[sel(setMovableByWindowBackground:)]
pub fn setMovableByWindowBackground(&self, val: bool);
#[sel(setLevel:)]
pub fn setLevel(&self, level: NSWindowLevel);
#[sel(occlusionState)]
pub fn occlusionState(&self) -> NSWindowOcclusionState;
#[sel(center)]
pub fn center(&self);
#[sel(isResizable)]
pub fn isResizable(&self) -> bool;
#[sel(isMiniaturized)]
pub fn isMiniaturized(&self) -> bool;
#[sel(isVisible)]
pub fn isVisible(&self) -> bool;
#[sel(isZoomed)]
pub fn isZoomed(&self) -> bool;
#[sel(close)]
pub fn close(&self);
#[sel(performWindowDragWithEvent:)]
// TODO: Can this actually accept NULL?
pub fn performWindowDragWithEvent(&self, event: Option<&NSEvent>);
#[sel(invalidateCursorRectsForView:)]
pub fn invalidateCursorRectsForView(&self, view: &NSView);
#[sel(setDelegate:)]
pub fn setDelegate(&self, delegate: Option<&NSObject>);
#[sel(sendEvent:)]
pub unsafe fn sendEvent(&self, event: &NSEvent);
}
);
#[allow(dead_code)]
#[repr(isize)] // NSInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSWindowTitleVisibility {
#[doc(alias = "NSWindowTitleVisible")]
Visible = 0,
#[doc(alias = "NSWindowTitleHidden")]
Hidden = 1,
}
unsafe impl Encode for NSWindowTitleVisibility {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(usize)] // NSUInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSWindowButton {
#[doc(alias = "NSWindowCloseButton")]
Close = 0,
#[doc(alias = "NSWindowMiniaturizeButton")]
Miniaturize = 1,
#[doc(alias = "NSWindowZoomButton")]
Zoom = 2,
#[doc(alias = "NSWindowToolbarButton")]
Toolbar = 3,
#[doc(alias = "NSWindowDocumentIconButton")]
DocumentIcon = 4,
#[doc(alias = "NSWindowDocumentVersionsButton")]
DocumentVersions = 6,
#[doc(alias = "NSWindowFullScreenButton")]
#[deprecated = "Deprecated since macOS 10.12"]
FullScreen = 7,
}
unsafe impl Encode for NSWindowButton {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[allow(dead_code)]
mod window_level_key {
use objc2::foundation::NSInteger;
pub const kCGBaseWindowLevelKey: NSInteger = 0;
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
pub const kCGNormalWindowLevelKey: NSInteger = 4;
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
pub const kCGDockWindowLevelKey: NSInteger = 7;
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
pub const kCGStatusWindowLevelKey: NSInteger = 9;
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
pub const kCGHelpWindowLevelKey: NSInteger = 16;
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
pub const kCGCursorWindowLevelKey: NSInteger = 19;
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
}
use window_level_key::*;
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(isize)]
pub enum NSWindowLevel {
#[doc(alias = "NSNormalWindowLevel")]
Normal = kCGBaseWindowLevelKey,
#[doc(alias = "NSFloatingWindowLevel")]
Floating = kCGFloatingWindowLevelKey,
#[doc(alias = "NSTornOffMenuWindowLevel")]
TornOffMenu = kCGTornOffMenuWindowLevelKey,
#[doc(alias = "NSModalPanelWindowLevel")]
ModalPanel = kCGModalPanelWindowLevelKey,
#[doc(alias = "NSMainMenuWindowLevel")]
MainMenu = kCGMainMenuWindowLevelKey,
#[doc(alias = "NSStatusWindowLevel")]
Status = kCGStatusWindowLevelKey,
#[doc(alias = "NSPopUpMenuWindowLevel")]
PopUpMenu = kCGPopUpMenuWindowLevelKey,
#[doc(alias = "NSScreenSaverWindowLevel")]
ScreenSaver = kCGScreenSaverWindowLevelKey,
}
unsafe impl Encode for NSWindowLevel {
const ENCODING: Encoding = NSInteger::ENCODING;
}
bitflags! {
pub struct NSWindowOcclusionState: NSUInteger {
const NSWindowOcclusionStateVisible = 1 << 1;
}
}
unsafe impl Encode for NSWindowOcclusionState {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
bitflags! {
pub struct NSWindowStyleMask: NSUInteger {
const NSBorderlessWindowMask = 0;
const NSTitledWindowMask = 1 << 0;
const NSClosableWindowMask = 1 << 1;
const NSMiniaturizableWindowMask = 1 << 2;
const NSResizableWindowMask = 1 << 3;
const NSTexturedBackgroundWindowMask = 1 << 8;
const NSUnifiedTitleAndToolbarWindowMask = 1 << 12;
const NSFullScreenWindowMask = 1 << 14;
const NSFullSizeContentViewWindowMask = 1 << 15;
}
}
unsafe impl Encode for NSWindowStyleMask {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
#[allow(dead_code)]
#[repr(usize)] // NSUInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NSBackingStoreType {
NSBackingStoreRetained = 0,
NSBackingStoreNonretained = 1,
NSBackingStoreBuffered = 2,
}
unsafe impl Encode for NSBackingStoreType {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View file

@ -1,29 +1,25 @@
use std::os::raw::c_ushort; use std::os::raw::c_ushort;
use cocoa::{ use objc2::rc::{Id, Shared};
appkit::{NSEvent, NSEventModifierFlags},
base::id,
};
use super::appkit::{NSEvent, NSEventModifierFlags};
use super::window::WinitWindow;
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}, event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent},
platform_impl::platform::{ platform_impl::platform::{util::Never, DEVICE_ID},
util::{IdRef, Never},
DEVICE_ID,
},
}; };
#[derive(Debug)] #[derive(Debug)]
pub enum EventWrapper { pub(crate) enum EventWrapper {
StaticEvent(Event<'static, Never>), StaticEvent(Event<'static, Never>),
EventProxy(EventProxy), EventProxy(EventProxy),
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum EventProxy { pub(crate) enum EventProxy {
DpiChangedProxy { DpiChangedProxy {
ns_window: IdRef, window: Id<WinitWindow, Shared>,
suggested_size: LogicalSize<f64>, suggested_size: LogicalSize<f64>,
scale_factor: f64, scale_factor: f64,
}, },
@ -241,8 +237,8 @@ pub fn check_function_keys(string: &str) -> Option<VirtualKeyCode> {
None None
} }
pub fn event_mods(event: id) -> ModifiersState { pub(super) fn event_mods(event: &NSEvent) -> ModifiersState {
let flags = unsafe { NSEvent::modifierFlags(event) }; let flags = event.modifierFlags();
let mut m = ModifiersState::empty(); let mut m = ModifiersState::empty();
m.set( m.set(
ModifiersState::SHIFT, ModifiersState::SHIFT,
@ -263,21 +259,13 @@ pub fn event_mods(event: id) -> ModifiersState {
m m
} }
pub fn get_scancode(event: cocoa::base::id) -> c_ushort { pub(super) fn modifier_event(
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character, event: &NSEvent,
// and there is no easy way to navtively retrieve the layout-dependent character.
// In winit, we use keycode to refer to the key's character, and so this function aligns
// AppKit's terminology with ours.
unsafe { msg_send![event, keyCode] }
}
pub unsafe fn modifier_event(
ns_event: id,
keymask: NSEventModifierFlags, keymask: NSEventModifierFlags,
was_key_pressed: bool, was_key_pressed: bool,
) -> Option<WindowEvent<'static>> { ) -> Option<WindowEvent<'static>> {
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask) if !was_key_pressed && event.modifierFlags().contains(keymask)
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) || was_key_pressed && !event.modifierFlags().contains(keymask)
{ {
let state = if was_key_pressed { let state = if was_key_pressed {
ElementState::Released ElementState::Released
@ -285,7 +273,7 @@ pub unsafe fn modifier_event(
ElementState::Pressed ElementState::Pressed
}; };
let scancode = get_scancode(ns_event); let scancode = event.scancode();
let virtual_keycode = scancode_to_keycode(scancode); let virtual_keycode = scancode_to_keycode(scancode);
#[allow(deprecated)] #[allow(deprecated)]
Some(WindowEvent::KeyboardInput { Some(WindowEvent::KeyboardInput {
@ -294,7 +282,7 @@ pub unsafe fn modifier_event(
state, state,
scancode: scancode as _, scancode: scancode as _,
virtual_keycode, virtual_keycode,
modifiers: event_mods(ns_event), modifiers: event_mods(event),
}, },
is_synthetic: false, is_synthetic: false,
}) })

View file

@ -11,16 +11,12 @@ use std::{
sync::mpsc, sync::mpsc,
}; };
use cocoa::{
appkit::{NSApp, NSEventModifierFlags, NSEventSubtype, NSEventType::NSApplicationDefined},
base::{id, nil},
foundation::{NSPoint, NSTimeInterval},
};
use objc2::foundation::is_main_thread; use objc2::foundation::is_main_thread;
use objc2::rc::{autoreleasepool, Id, Shared}; use objc2::rc::{autoreleasepool, Id, Shared};
use objc2::ClassType; use objc2::{msg_send_id, ClassType};
use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle};
use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent};
use crate::{ use crate::{
event::Event, event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
@ -95,15 +91,11 @@ impl<T: 'static> EventLoopWindowTarget<T> {
impl<T> EventLoopWindowTarget<T> { impl<T> EventLoopWindowTarget<T> {
pub(crate) fn hide_application(&self) { pub(crate) fn hide_application(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap(); NSApp().hide(None)
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hide: 0] }
} }
pub(crate) fn hide_other_applications(&self) { pub(crate) fn hide_other_applications(&self) {
let cls = objc::runtime::Class::get("NSApplication").unwrap(); NSApp().hideOtherApplications(None)
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
unsafe { msg_send![app, hideOtherApplications: 0] }
} }
} }
@ -141,31 +133,29 @@ impl Default for PlatformSpecificEventLoopAttributes {
impl<T> EventLoop<T> { impl<T> EventLoop<T> {
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self { pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
let delegate = unsafe { if !is_main_thread() {
if !is_main_thread() { panic!("On macOS, `EventLoop` must be created on the main thread!");
panic!("On macOS, `EventLoop` must be created on the main thread!"); }
}
// This must be done before `NSApp()` (equivalent to sending // This must be done before `NSApp()` (equivalent to sending
// `sharedApplication`) is called anywhere else, or we'll end up // `sharedApplication`) is called anywhere else, or we'll end up
// with the wrong `NSApplication` class and the wrong thread could // with the wrong `NSApplication` class and the wrong thread could
// be marked as main. // be marked as main.
let app: id = msg_send![WinitApplication::class(), sharedApplication]; let app: Id<WinitApplication, Shared> =
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
use cocoa::appkit::NSApplicationActivationPolicy::*; use NSApplicationActivationPolicy::*;
let activation_policy = match attributes.activation_policy { let activation_policy = match attributes.activation_policy {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory, ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited, ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
};
let delegate = ApplicationDelegate::new(activation_policy, attributes.default_menu);
autoreleasepool(|_| {
let _: () = msg_send![app, setDelegate: &*delegate];
});
delegate
}; };
let delegate = ApplicationDelegate::new(activation_policy, attributes.default_menu);
autoreleasepool(|_| {
app.setDelegate(&delegate);
});
let panic_info: Rc<PanicInfo> = Default::default(); let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(Rc::downgrade(&panic_info)); setup_control_flow_observers(Rc::downgrade(&panic_info));
EventLoop { EventLoop {
@ -208,9 +198,8 @@ impl<T> EventLoop<T> {
self._callback = Some(Rc::clone(&callback)); self._callback = Some(Rc::clone(&callback));
let exit_code = autoreleasepool(|_| unsafe { let exit_code = autoreleasepool(|_| {
let app = NSApp(); let app = NSApp();
assert_ne!(app, nil);
// A bit of juggling with the callback references to make sure // A bit of juggling with the callback references to make sure
// that `self.callback` is the only owner of the callback. // that `self.callback` is the only owner of the callback.
@ -218,7 +207,7 @@ impl<T> EventLoop<T> {
drop(callback); drop(callback);
AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
let _: () = msg_send![app, run]; unsafe { app.run() };
if let Some(panic) = self.panic_info.take() { if let Some(panic) = self.panic_info.take() {
drop(self._callback.take()); drop(self._callback.take());
@ -236,24 +225,6 @@ impl<T> EventLoop<T> {
} }
} }
#[inline]
pub unsafe fn post_dummy_event(target: id) {
let event_class = class!(NSEvent);
let dummy_event: id = msg_send![
event_class,
otherEventWithType: NSApplicationDefined
location: NSPoint::new(0.0, 0.0)
modifierFlags: NSEventModifierFlags::empty()
timestamp: 0 as NSTimeInterval
windowNumber: 0isize
context: nil
subtype: NSEventSubtype::NSWindowExposedEventType
data1: 0isize
data2: 0isize
];
let _: () = msg_send![target, postEvent: dummy_event, atStart: true];
}
/// Catches panics that happen inside `f` and when a panic /// Catches panics that happen inside `f` and when a panic
/// happens, stops the `sharedApplication` /// happens, stops the `sharedApplication`
#[inline] #[inline]
@ -272,15 +243,11 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
let panic_info = panic_info.upgrade().unwrap(); let panic_info = panic_info.upgrade().unwrap();
panic_info.set_panic(e); panic_info.set_panic(e);
} }
unsafe { let app = NSApp();
let app_class = class!(NSApplication); app.stop(None);
let app: id = msg_send![app_class, sharedApplication]; // Posting a dummy event to get `stop` to take effect immediately.
let _: () = msg_send![app, stop: nil]; // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
app.postEvent_atStart(&NSEvent::dummy(), true);
// Posting a dummy event to get `stop` to take effect immediately.
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
post_dummy_event(app);
}
None None
} }
} }

View file

@ -4,7 +4,6 @@
use std::ffi::c_void; use std::ffi::c_void;
use cocoa::base::id;
use core_foundation::{ use core_foundation::{
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef, array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
}; };
@ -12,85 +11,6 @@ use core_graphics::{
base::CGError, base::CGError,
display::{CGDirectDisplayID, CGDisplayConfigRef}, display::{CGDirectDisplayID, CGDisplayConfigRef},
}; };
use objc::foundation::{NSInteger, NSUInteger};
pub const NSNotFound: NSInteger = NSInteger::max_value();
pub trait NSMutableAttributedString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSMutableAttributedString), alloc]
}
unsafe fn init(self) -> id; // *mut NSMutableAttributedString
unsafe fn initWithString(self, string: id) -> id;
unsafe fn initWithAttributedString(self, string: id) -> id;
unsafe fn string(self) -> id; // *mut NSString
unsafe fn mutableString(self) -> id; // *mut NSMutableString
unsafe fn length(self) -> NSUInteger;
}
impl NSMutableAttributedString for id {
unsafe fn init(self) -> id {
msg_send![self, init]
}
unsafe fn initWithString(self, string: id) -> id {
msg_send![self, initWithString: string]
}
unsafe fn initWithAttributedString(self, string: id) -> id {
msg_send![self, initWithAttributedString: string]
}
unsafe fn string(self) -> id {
msg_send![self, string]
}
unsafe fn mutableString(self) -> id {
msg_send![self, mutableString]
}
unsafe fn length(self) -> NSUInteger {
msg_send![self, length]
}
}
pub const kCGBaseWindowLevelKey: NSInteger = 0;
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
pub const kCGNormalWindowLevelKey: NSInteger = 4;
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
pub const kCGDockWindowLevelKey: NSInteger = 7;
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
pub const kCGStatusWindowLevelKey: NSInteger = 9;
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
pub const kCGHelpWindowLevelKey: NSInteger = 16;
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
pub const kCGCursorWindowLevelKey: NSInteger = 19;
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
#[derive(Debug, Clone, Copy)]
#[repr(isize)]
#[allow(clippy::enum_variant_names)]
pub enum NSWindowLevel {
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _,
NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _,
NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _,
NSStatusWindowLevel = kCGStatusWindowLevelKey as _,
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
}
pub type CGDisplayFadeInterval = f32; pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32; pub type CGDisplayReservationInterval = f32;

View file

@ -1,115 +1,98 @@
use super::util::IdRef; use objc2::foundation::{NSProcessInfo, NSString};
use cocoa::appkit::{NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}; use objc2::rc::{Id, Shared};
use cocoa::base::{nil, selector}; use objc2::runtime::Sel;
use cocoa::foundation::{NSProcessInfo, NSString}; use objc2::{ns_string, sel};
use objc::{
rc::autoreleasepool, use super::appkit::{NSApp, NSEventModifierFlags, NSMenu, NSMenuItem};
runtime::{Object, Sel},
};
struct KeyEquivalent<'a> { struct KeyEquivalent<'a> {
key: &'a str, key: &'a NSString,
masks: Option<NSEventModifierFlags>, masks: Option<NSEventModifierFlags>,
} }
pub fn initialize() { pub fn initialize() {
autoreleasepool(|_| unsafe { let menubar = NSMenu::new();
let menubar = IdRef::new(NSMenu::new(nil)); let app_menu_item = NSMenuItem::new();
let app_menu_item = IdRef::new(NSMenuItem::new(nil)); menubar.addItem(&app_menu_item);
menubar.addItem_(*app_menu_item);
let app = NSApp();
app.setMainMenu_(*menubar);
let app_menu = NSMenu::new(nil); let app_menu = NSMenu::new();
let process_name = NSProcessInfo::processInfo(nil).processName(); let process_name = NSProcessInfo::process_info().process_name();
// About menu item // About menu item
let about_item_prefix = NSString::alloc(nil).init_str("About "); let about_item_title = ns_string!("About ").concat(&*process_name);
let about_item_title = about_item_prefix.stringByAppendingString_(process_name); let about_item = menu_item(&about_item_title, sel!(orderFrontStandardAboutPanel:), None);
let about_item = menu_item(
about_item_title,
selector("orderFrontStandardAboutPanel:"),
None,
);
// Seperator menu item // Seperator menu item
let sep_first = NSMenuItem::separatorItem(nil); let sep_first = NSMenuItem::separatorItem();
// Hide application menu item // Hide application menu item
let hide_item_prefix = NSString::alloc(nil).init_str("Hide "); let hide_item_title = ns_string!("Hide ").concat(&*process_name);
let hide_item_title = hide_item_prefix.stringByAppendingString_(process_name); let hide_item = menu_item(
let hide_item = menu_item( &hide_item_title,
hide_item_title, sel!(hide:),
selector("hide:"), Some(KeyEquivalent {
Some(KeyEquivalent { key: ns_string!("h"),
key: "h", masks: None,
masks: None, }),
}), );
);
// Hide other applications menu item // Hide other applications menu item
let hide_others_item_title = NSString::alloc(nil).init_str("Hide Others"); let hide_others_item_title = ns_string!("Hide Others");
let hide_others_item = menu_item( let hide_others_item = menu_item(
hide_others_item_title, hide_others_item_title,
selector("hideOtherApplications:"), sel!(hideOtherApplications:),
Some(KeyEquivalent { Some(KeyEquivalent {
key: "h", key: ns_string!("h"),
masks: Some( masks: Some(
NSEventModifierFlags::NSAlternateKeyMask NSEventModifierFlags::NSAlternateKeyMask | NSEventModifierFlags::NSCommandKeyMask,
| NSEventModifierFlags::NSCommandKeyMask, ),
), }),
}), );
);
// Show applications menu item // Show applications menu item
let show_all_item_title = NSString::alloc(nil).init_str("Show All"); let show_all_item_title = ns_string!("Show All");
let show_all_item = menu_item( let show_all_item = menu_item(show_all_item_title, sel!(unhideAllApplications:), None);
show_all_item_title,
selector("unhideAllApplications:"),
None,
);
// Seperator menu item // Seperator menu item
let sep = NSMenuItem::separatorItem(nil); let sep = NSMenuItem::separatorItem();
// Quit application menu item // Quit application menu item
let quit_item_prefix = NSString::alloc(nil).init_str("Quit "); let quit_item_title = ns_string!("Quit ").concat(&*process_name);
let quit_item_title = quit_item_prefix.stringByAppendingString_(process_name); let quit_item = menu_item(
let quit_item = menu_item( &quit_item_title,
quit_item_title, sel!(terminate:),
selector("terminate:"), Some(KeyEquivalent {
Some(KeyEquivalent { key: ns_string!("q"),
key: "q", masks: None,
masks: None, }),
}), );
);
app_menu.addItem_(about_item); app_menu.addItem(&about_item);
app_menu.addItem_(sep_first); app_menu.addItem(&sep_first);
app_menu.addItem_(hide_item); app_menu.addItem(&hide_item);
app_menu.addItem_(hide_others_item); app_menu.addItem(&hide_others_item);
app_menu.addItem_(show_all_item); app_menu.addItem(&show_all_item);
app_menu.addItem_(sep); app_menu.addItem(&sep);
app_menu.addItem_(quit_item); app_menu.addItem(&quit_item);
app_menu_item.setSubmenu_(app_menu); app_menu_item.setSubmenu(&app_menu);
});
let app = NSApp();
app.setMainMenu(&menubar);
} }
fn menu_item( fn menu_item(
title: *mut Object, title: &NSString,
selector: Sel, selector: Sel,
key_equivalent: Option<KeyEquivalent<'_>>, key_equivalent: Option<KeyEquivalent<'_>>,
) -> *mut Object { ) -> Id<NSMenuItem, Shared> {
unsafe { let (key, masks) = match key_equivalent {
let (key, masks) = match key_equivalent { Some(ke) => (ke.key, ke.masks),
Some(ke) => (NSString::alloc(nil).init_str(ke.key), ke.masks), None => (ns_string!(""), None),
None => (NSString::alloc(nil).init_str(""), None), };
}; let item = NSMenuItem::newWithTitle(title, selector, key);
let item = NSMenuItem::alloc(nil).initWithTitle_action_keyEquivalent_(title, selector, key); if let Some(masks) = masks {
if let Some(masks) = masks { item.setKeyEquivalentModifierMask(masks)
item.setKeyEquivalentModifierMask_(masks)
}
item
} }
item
} }

View file

@ -1,5 +1,4 @@
#![cfg(target_os = "macos")] #![deny(unsafe_op_in_unsafe_fn)]
#![allow(clippy::let_unit_value)]
#[macro_use] #[macro_use]
mod util; mod util;
@ -18,19 +17,21 @@ mod view;
mod window; mod window;
mod window_delegate; mod window_delegate;
use std::{fmt, ops::Deref, sync::Arc}; use std::{fmt, ops::Deref};
use self::window::WinitWindow;
use self::window_delegate::WinitWindowDelegate;
pub(crate) use self::{ pub(crate) use self::{
event_loop::{ event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
}, },
monitor::{MonitorHandle, VideoMode}, monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, UnownedWindow, WindowId}, window::{PlatformSpecificWindowBuilderAttributes, WindowId},
}; };
use crate::{ use crate::{
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes, error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
}; };
use objc::rc::autoreleasepool; use objc2::rc::{autoreleasepool, Id, Shared};
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
@ -46,10 +47,17 @@ impl DeviceId {
// Constant device ID; to be removed when if backend is updated to report real device IDs. // Constant device ID; to be removed when if backend is updated to report real device IDs.
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
pub struct Window { pub(crate) struct Window {
window: Arc<UnownedWindow>, pub(crate) window: Id<WinitWindow, Shared>,
// We keep this around so that it doesn't get dropped until the window does. // We keep this around so that it doesn't get dropped until the window does.
_delegate: util::IdRef, _delegate: Id<WinitWindowDelegate, Shared>,
}
impl Drop for Window {
fn drop(&mut self) {
// Ensure the window is closed
util::close_async(Id::into_super(self.window.clone()));
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -62,7 +70,7 @@ unsafe impl Send for Window {}
unsafe impl Sync for Window {} unsafe impl Sync for Window {}
impl Deref for Window { impl Deref for Window {
type Target = UnownedWindow; type Target = WinitWindow;
#[inline] #[inline]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&*self.window &*self.window
@ -75,7 +83,7 @@ impl Window {
attributes: WindowAttributes, attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> { ) -> Result<Self, RootOsError> {
let (window, _delegate) = autoreleasepool(|_| UnownedWindow::new(attributes, pl_attribs))?; let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?;
Ok(Window { window, _delegate }) Ok(Window { window, _delegate })
} }
} }

View file

@ -1,21 +1,19 @@
use std::{collections::VecDeque, fmt}; use std::{collections::VecDeque, fmt};
use super::{ffi, util};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
};
use cocoa::{
appkit::NSScreen,
base::{id, nil},
};
use core_foundation::{ use core_foundation::{
array::{CFArrayGetCount, CFArrayGetValueAtIndex}, array::{CFArrayGetCount, CFArrayGetValueAtIndex},
base::{CFRelease, TCFType}, base::{CFRelease, TCFType},
string::CFString, string::CFString,
}; };
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}; use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use objc::foundation::NSUInteger; use objc2::rc::{Id, Shared};
use super::appkit::NSScreen;
use super::ffi;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
};
#[derive(Clone)] #[derive(Clone)]
pub struct VideoMode { pub struct VideoMode {
@ -213,11 +211,10 @@ impl MonitorHandle {
} }
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
let screen = match self.ns_screen() { match self.ns_screen() {
Some(screen) => screen, Some(screen) => screen.backingScaleFactor() as f64,
None => return 1.0, // default to 1.0 when we can't find the screen None => 1.0, // default to 1.0 when we can't find the screen
}; }
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
} }
pub fn refresh_rate_millihertz(&self) -> Option<u32> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {
@ -298,26 +295,19 @@ impl MonitorHandle {
} }
} }
pub(crate) fn ns_screen(&self) -> Option<id> { pub(crate) fn ns_screen(&self) -> Option<Id<NSScreen, Shared>> {
unsafe { let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0); NSScreen::screens()
let screens = NSScreen::screens(nil); .into_iter()
let count: NSUInteger = msg_send![screens, count]; .find(|screen| {
let key = util::ns_string_id_ref("NSScreenNumber"); let other_native_id = screen.display_id();
for i in 0..count { let other_uuid = unsafe {
let screen = msg_send![screens, objectAtIndex: i as NSUInteger]; ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
let device_description = NSScreen::deviceDescription(screen); };
let value: id = msg_send![device_description, objectForKey:*key]; uuid == other_uuid
if value != nil { })
let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue]; .map(|screen| unsafe {
let other_uuid = Id::retain(screen as *const NSScreen as *mut NSScreen).unwrap()
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID); })
if uuid == other_uuid {
return Some(screen);
}
}
}
None
}
} }
} }

View file

@ -130,7 +130,7 @@ unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
where where
F: FnOnce(Weak<PanicInfo>) + UnwindSafe, F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
{ {
let info_from_raw = Weak::from_raw(panic_info as *mut PanicInfo); let info_from_raw = unsafe { Weak::from_raw(panic_info as *mut PanicInfo) };
// Asserting unwind safety on this type should be fine because `PanicInfo` is // Asserting unwind safety on this type should be fine because `PanicInfo` is
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`. // `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw)); let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
@ -195,7 +195,7 @@ struct RunLoop(CFRunLoopRef);
impl RunLoop { impl RunLoop {
unsafe fn get() -> Self { unsafe fn get() -> Self {
RunLoop(CFRunLoopGetMain()) RunLoop(unsafe { CFRunLoopGetMain() })
} }
unsafe fn add_observer( unsafe fn add_observer(
@ -205,15 +205,17 @@ impl RunLoop {
handler: CFRunLoopObserverCallBack, handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext, context: *mut CFRunLoopObserverContext,
) { ) {
let observer = CFRunLoopObserverCreate( let observer = unsafe {
ptr::null_mut(), CFRunLoopObserverCreate(
flags, ptr::null_mut(),
ffi::TRUE, // Indicates we want this to run repeatedly flags,
priority, // The lower the value, the sooner this will run ffi::TRUE, // Indicates we want this to run repeatedly
handler, priority, // The lower the value, the sooner this will run
context, handler,
); context,
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes); )
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
} }
} }

View file

@ -1,23 +1,16 @@
use std::{ use std::mem;
ops::Deref, use std::ops::Deref;
sync::{Mutex, Weak}, use std::sync::{Mutex, Weak};
};
use cocoa::{
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
base::{id, nil},
foundation::{NSPoint, NSSize, NSString},
};
use dispatch::Queue; use dispatch::Queue;
use objc::foundation::is_main_thread; use objc2::foundation::{is_main_thread, CGFloat, NSPoint, NSSize, NSString};
use objc::rc::autoreleasepool; use objc2::rc::{autoreleasepool, Id, Shared};
use objc::runtime::Bool;
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
platform_impl::platform::{ platform_impl::platform::{
appkit::{NSScreen, NSWindow, NSWindowLevel, NSWindowStyleMask},
ffi, ffi,
util::IdRef,
window::{SharedState, SharedStateMutexGuard}, window::{SharedState, SharedStateMutexGuard},
}, },
}; };
@ -37,91 +30,88 @@ impl<T> Deref for MainThreadSafe<T> {
} }
} }
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { fn set_style_mask(window: &NSWindow, mask: NSWindowStyleMask) {
ns_window.setStyleMask_(mask); window.setStyleMask(mask);
// If we don't do this, key handling will break // If we don't do this, key handling will break
// (at least until the window is clicked again/etc.) // (at least until the window is clicked again/etc.)
ns_window.makeFirstResponder_(ns_view); let _ = window.makeFirstResponder(Some(&window.contentView()));
} }
// Always use this function instead of trying to modify `styleMask` directly! // Always use this function instead of trying to modify `styleMask` directly!
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch. // `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
// Otherwise, this would vomit out errors about not being on the main thread // Otherwise, this would vomit out errors about not being on the main thread
// and fail to do anything. // and fail to do anything.
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { pub(crate) fn set_style_mask_async(window: &NSWindow, mask: NSWindowStyleMask) {
let ns_window = MainThreadSafe(ns_window); // TODO(madsmtm): Remove this 'static hack!
let ns_view = MainThreadSafe(ns_view); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
set_style_mask(*ns_window, *ns_view, mask); set_style_mask(*window, mask);
}); });
} }
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { pub(crate) fn set_style_mask_sync(window: &NSWindow, mask: NSWindowStyleMask) {
if is_main_thread() { if is_main_thread() {
set_style_mask(ns_window, ns_view, mask); set_style_mask(window, mask);
} else { } else {
let ns_window = MainThreadSafe(ns_window); let window = MainThreadSafe(window);
let ns_view = MainThreadSafe(ns_view);
Queue::main().exec_sync(move || { Queue::main().exec_sync(move || {
set_style_mask(*ns_window, *ns_view, mask); set_style_mask(*window, mask);
}) })
} }
} }
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors // `setContentSize:` isn't thread-safe either, though it doesn't log any errors
// and just fails silently. Anyway, GCD to the rescue! // and just fails silently. Anyway, GCD to the rescue!
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) { pub(crate) fn set_content_size_async(window: &NSWindow, size: LogicalSize<f64>) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat)); window.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat));
}); });
} }
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy // `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
// to log errors. // to log errors.
pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) { pub(crate) fn set_frame_top_left_point_async(window: &NSWindow, point: NSPoint) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
ns_window.setFrameTopLeftPoint_(point); window.setFrameTopLeftPoint(point);
}); });
} }
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. // `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) { pub(crate) fn set_level_async(window: &NSWindow, level: NSWindowLevel) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
ns_window.setLevel_(level as _); window.setLevel(level);
}); });
} }
// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently. // `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently.
pub unsafe fn set_ignore_mouse_events(ns_window: id, ignore: bool) { pub(crate) fn set_ignore_mouse_events(window: &NSWindow, ignore: bool) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
ns_window.setIgnoresMouseEvents_(Bool::from(ignore).as_raw()); window.setIgnoresMouseEvents(ignore);
}); });
} }
// `toggleFullScreen` is thread-safe, but our additional logic to account for // `toggleFullScreen` is thread-safe, but our additional logic to account for
// window styles isn't. // window styles isn't.
pub unsafe fn toggle_full_screen_async( pub(crate) fn toggle_full_screen_async(
ns_window: id, window: &NSWindow,
ns_view: id,
not_fullscreen: bool, not_fullscreen: bool,
shared_state: Weak<Mutex<SharedState>>, shared_state: Weak<Mutex<SharedState>>,
) { ) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
let ns_view = MainThreadSafe(ns_view);
let shared_state = MainThreadSafe(shared_state); let shared_state = MainThreadSafe(shared_state);
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be // set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`. // restored in `WindowDelegate::window_did_exit_fullscreen`.
if not_fullscreen { if not_fullscreen {
let curr_mask = ns_window.styleMask(); let curr_mask = window.styleMask();
let required = let required =
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) { if !curr_mask.contains(required) {
set_style_mask(*ns_window, *ns_view, required); set_style_mask(*window, required);
if let Some(shared_state) = shared_state.upgrade() { if let Some(shared_state) = shared_state.upgrade() {
let mut shared_state_lock = SharedStateMutexGuard::new( let mut shared_state_lock = SharedStateMutexGuard::new(
shared_state.lock().unwrap(), shared_state.lock().unwrap(),
@ -134,26 +124,29 @@ pub unsafe fn toggle_full_screen_async(
// Window level must be restored from `CGShieldingWindowLevel() // Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do // + 1` back to normal in order for `toggleFullScreen` to do
// anything // anything
ns_window.setLevel_(0); window.setLevel(NSWindowLevel::Normal);
ns_window.toggleFullScreen_(nil); window.toggleFullScreen(None);
}); });
} }
pub unsafe fn restore_display_mode_async(ns_screen: u32) { pub(crate) unsafe fn restore_display_mode_async(ns_screen: u32) {
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
ffi::CGRestorePermanentDisplayConfiguration(); unsafe { ffi::CGRestorePermanentDisplayConfiguration() };
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess); assert_eq!(
unsafe { ffi::CGDisplayRelease(ns_screen) },
ffi::kCGErrorSuccess
);
}); });
} }
// `setMaximized` is not thread-safe // `setMaximized` is not thread-safe
pub unsafe fn set_maximized_async( pub(crate) fn set_maximized_async(
ns_window: id, window: &NSWindow,
is_zoomed: bool, is_zoomed: bool,
maximized: bool, maximized: bool,
shared_state: Weak<Mutex<SharedState>>, shared_state: Weak<Mutex<SharedState>>,
) { ) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
let shared_state = MainThreadSafe(shared_state); let shared_state = MainThreadSafe(shared_state);
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
if let Some(shared_state) = shared_state.upgrade() { if let Some(shared_state) = shared_state.upgrade() {
@ -162,7 +155,7 @@ pub unsafe fn set_maximized_async(
// Save the standard frame sized if it is not zoomed // Save the standard frame sized if it is not zoomed
if !is_zoomed { if !is_zoomed {
shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window)); shared_state_lock.standard_frame = Some(window.frame());
} }
shared_state_lock.maximized = maximized; shared_state_lock.maximized = maximized;
@ -172,21 +165,21 @@ pub unsafe fn set_maximized_async(
return; return;
} }
if ns_window if window
.styleMask() .styleMask()
.contains(NSWindowStyleMask::NSResizableWindowMask) .contains(NSWindowStyleMask::NSResizableWindowMask)
{ {
// Just use the native zoom if resizable // Just use the native zoom if resizable
ns_window.zoom_(nil); window.zoom(None);
} else { } else {
// if it's not resizable, we set the frame directly // if it's not resizable, we set the frame directly
let new_rect = if maximized { let new_rect = if maximized {
let screen = NSScreen::mainScreen(nil); let screen = NSScreen::main().expect("no screen found");
NSScreen::visibleFrame(screen) screen.visibleFrame()
} else { } else {
shared_state_lock.saved_standard_frame() shared_state_lock.saved_standard_frame()
}; };
ns_window.setFrame_display_(new_rect, Bool::NO.as_raw()); window.setFrame_display(new_rect, false);
} }
} }
}); });
@ -194,30 +187,29 @@ pub unsafe fn set_maximized_async(
// `orderOut:` isn't thread-safe. Calling it from another thread actually works, // `orderOut:` isn't thread-safe. Calling it from another thread actually works,
// but with an odd delay. // but with an odd delay.
pub unsafe fn order_out_async(ns_window: id) { pub(crate) fn order_out_async(window: &NSWindow) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
ns_window.orderOut_(nil); window.orderOut(None);
}); });
} }
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread // `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
// actually works, but with an odd delay. // actually works, but with an odd delay.
pub unsafe fn make_key_and_order_front_async(ns_window: id) { pub(crate) fn make_key_and_order_front_async(window: &NSWindow) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
ns_window.makeKeyAndOrderFront_(nil); window.makeKeyAndOrderFront(None);
}); });
} }
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the // `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
// window drag regions, which throws an exception when not done in the main // window drag regions, which throws an exception when not done in the main
// thread // thread
pub unsafe fn set_title_async(ns_window: id, title: String) { pub(crate) fn set_title_async(window: &NSWindow, title: String) {
let ns_window = MainThreadSafe(ns_window); let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
let title = IdRef::new(NSString::alloc(nil).init_str(&title)); window.setTitle(&NSString::from_str(&title));
ns_window.setTitle_(*title);
}); });
} }
@ -225,12 +217,12 @@ pub unsafe fn set_title_async(ns_window: id, title: String) {
// thread. Though, it's a good idea to look into that more... // thread. Though, it's a good idea to look into that more...
// //
// ArturKovacs: It's important that this operation keeps the underlying window alive // ArturKovacs: It's important that this operation keeps the underlying window alive
// through the `IdRef` because otherwise it would dereference free'd memory // through the `Id` because otherwise it would dereference free'd memory
pub unsafe fn close_async(ns_window: IdRef) { pub(crate) fn close_async(window: Id<NSWindow, Shared>) {
let ns_window = MainThreadSafe(ns_window); let window = MainThreadSafe(window);
Queue::main().exec_async(move || { Queue::main().exec_async(move || {
autoreleasepool(move |_| { autoreleasepool(move |_| {
ns_window.close(); window.close();
}); });
}); });
} }

View file

@ -1,84 +1,21 @@
mod r#async; mod r#async;
pub use self::r#async::*; pub(crate) use self::r#async::*;
use std::ops::{BitAnd, Deref};
use std::os::raw::c_uchar;
use cocoa::{
appkit::{CGFloat, NSApp, NSWindowStyleMask},
base::{id, nil},
foundation::{NSPoint, NSRect, NSString},
};
use core_graphics::display::CGDisplay; use core_graphics::display::CGDisplay;
use objc2::foundation::{NSRange, NSUInteger}; use objc2::foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger};
use crate::dpi::LogicalPosition; use crate::dpi::LogicalPosition;
use crate::platform_impl::platform::ffi;
// Replace with `!` once stable // Replace with `!` once stable
#[derive(Debug)] #[derive(Debug)]
pub enum Never {} pub enum Never {}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
{
bitset & flag == flag
}
pub const EMPTY_RANGE: NSRange = NSRange { pub const EMPTY_RANGE: NSRange = NSRange {
location: ffi::NSNotFound as NSUInteger, location: NSNotFound as NSUInteger,
length: 0, length: 0,
}; };
#[derive(Debug, PartialEq, Eq)]
pub struct IdRef(id);
impl IdRef {
pub fn new(inner: id) -> IdRef {
IdRef(inner)
}
pub fn retain(inner: id) -> IdRef {
if inner != nil {
let _: id = unsafe { msg_send![inner, retain] };
}
IdRef(inner)
}
pub fn non_nil(self) -> Option<IdRef> {
if self.0 == nil {
None
} else {
Some(self)
}
}
}
impl Drop for IdRef {
fn drop(&mut self) {
if self.0 != nil {
unsafe {
let _: () = msg_send![self.0, release];
};
}
}
}
impl Deref for IdRef {
type Target = id;
fn deref(&self) -> &id {
&self.0
}
}
impl Clone for IdRef {
fn clone(&self) -> IdRef {
IdRef::retain(self.0)
}
}
macro_rules! trace_scope { macro_rules! trace_scope {
($s:literal) => { ($s:literal) => {
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s); let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
@ -124,57 +61,3 @@ pub fn window_position(position: LogicalPosition<f64>) -> NSPoint {
CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat, CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat,
) )
} }
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
IdRef::new(NSString::alloc(nil).init_str(s))
}
#[allow(dead_code)] // In case we want to use this function in the future
pub unsafe fn app_name() -> Option<id> {
let bundle: id = msg_send![class!(NSBundle), mainBundle];
let dict: id = msg_send![bundle, infoDictionary];
let key = ns_string_id_ref("CFBundleName");
let app_name: id = msg_send![dict, objectForKey:*key];
if app_name != nil {
Some(app_name)
} else {
None
}
}
#[allow(dead_code)]
pub unsafe fn open_emoji_picker() {
let _: () = msg_send![NSApp(), orderFrontCharacterPalette: nil];
}
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
use cocoa::appkit::NSWindow;
let current_style_mask = window.styleMask();
if on {
window.setStyleMask_(current_style_mask | mask);
} else {
window.setStyleMask_(current_style_mask & (!mask));
}
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
/// For invalid utf8 sequences potentially returned by `UTF8String`,
/// it behaves identically to `String::from_utf8_lossy`
///
/// Safety: Assumes that `string` is an instance of `NSAttributedString` or `NSString`
pub unsafe fn id_to_string_lossy(string: id) -> String {
let has_attr = msg_send![string, isKindOfClass: class!(NSAttributedString)];
let characters = if has_attr {
// This is a *mut NSAttributedString
msg_send![string, string]
} else {
// This is already a *mut NSString
string
};
let utf8_sequence =
std::slice::from_raw_parts(characters.UTF8String() as *const c_uchar, characters.len());
String::from_utf8_lossy(utf8_sequence).into_owned()
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,137 +1,44 @@
use std::{ use std::ptr;
f64,
os::raw::c_void,
sync::{Arc, Weak},
};
use cocoa::{ use objc2::declare::{Ivar, IvarDrop};
appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow, NSWindowOcclusionState}, use objc2::foundation::{NSArray, NSObject, NSString};
base::{id, nil}, use objc2::rc::{autoreleasepool, Id, Shared};
};
use objc2::foundation::{NSObject, NSUInteger};
use objc2::rc::autoreleasepool;
use objc2::runtime::Object; use objc2::runtime::Object;
use objc2::{declare_class, ClassType}; use objc2::{declare_class, msg_send, msg_send_id, sel, ClassType};
use super::appkit::{
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
};
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
event::{Event, ModifiersState, WindowEvent}, event::{Event, ModifiersState, WindowEvent},
platform_impl::platform::{ platform_impl::platform::{
app_state::AppState, app_state::AppState,
event::{EventProxy, EventWrapper}, event::{EventProxy, EventWrapper},
util::{self, IdRef}, util,
view::ViewState, window::WinitWindow,
window::{get_window_id, UnownedWindow},
}, },
window::{Fullscreen, WindowId}, window::{Fullscreen, WindowId},
}; };
struct WindowDelegateState {
ns_window: IdRef, // never changes
ns_view: IdRef, // never changes
window: Weak<UnownedWindow>,
// TODO: It's possible for delegate methods to be called asynchronously,
// causing data races / `RefCell` panics.
// This is set when WindowBuilder::with_fullscreen was set,
// see comments of `window_did_fail_to_enter_fullscreen`
initial_fullscreen: bool,
// During `windowDidResize`, we use this to only send Moved if the position changed.
previous_position: Option<(f64, f64)>,
// Used to prevent redundant events.
previous_scale_factor: f64,
}
impl WindowDelegateState {
fn new(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> Self {
let scale_factor = window.scale_factor();
let mut delegate_state = WindowDelegateState {
ns_window: window.ns_window.clone(),
ns_view: window.ns_view.clone(),
window: Arc::downgrade(window),
initial_fullscreen,
previous_position: None,
previous_scale_factor: scale_factor,
};
if scale_factor != 1.0 {
delegate_state.emit_static_scale_factor_changed_event();
}
delegate_state
}
fn with_window<F, T>(&mut self, callback: F) -> Option<T>
where
F: FnOnce(&UnownedWindow) -> T,
{
self.window.upgrade().map(|ref window| callback(window))
}
fn emit_event(&mut self, event: WindowEvent<'static>) {
let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*self.ns_window)),
event,
};
AppState::queue_event(EventWrapper::StaticEvent(event));
}
fn emit_static_scale_factor_changed_event(&mut self) {
let scale_factor = self.get_scale_factor();
if scale_factor == self.previous_scale_factor {
return;
};
self.previous_scale_factor = scale_factor;
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
ns_window: IdRef::retain(*self.ns_window),
suggested_size: self.view_size(),
scale_factor,
});
AppState::queue_event(wrapper);
}
fn emit_move_event(&mut self) {
let rect = unsafe { NSWindow::frame(*self.ns_window) };
let x = rect.origin.x as f64;
let y = util::bottom_left_to_top_left(rect);
let moved = self.previous_position != Some((x, y));
if moved {
self.previous_position = Some((x, y));
let scale_factor = self.get_scale_factor();
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
self.emit_event(WindowEvent::Moved(physical_pos));
}
}
fn get_scale_factor(&self) -> f64 {
(unsafe { NSWindow::backingScaleFactor(*self.ns_window) }) as f64
}
fn view_size(&self) -> LogicalSize<f64> {
let ns_size = unsafe { NSView::frame(*self.ns_view).size };
LogicalSize::new(ns_size.width as f64, ns_size.height as f64)
}
}
pub fn new_delegate(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> IdRef {
let state = WindowDelegateState::new(window, initial_fullscreen);
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let delegate: id = msg_send![WinitWindowDelegate::class(), alloc];
IdRef::new(msg_send![delegate, initWithWinit: state_ptr])
}
}
declare_class!( declare_class!(
#[derive(Debug)] #[derive(Debug)]
struct WinitWindowDelegate { pub(crate) struct WinitWindowDelegate {
state: *mut c_void, window: IvarDrop<Id<WinitWindow, Shared>>,
// TODO: It's possible for delegate methods to be called asynchronously,
// causing data races / `RefCell` panics.
// This is set when WindowBuilder::with_fullscreen was set,
// see comments of `window_did_fail_to_enter_fullscreen`
initial_fullscreen: bool,
// During `windowDidResize`, we use this to only send Moved if the position changed.
// TODO: Remove unnecessary boxing here
previous_position: IvarDrop<Option<Box<(f64, f64)>>>,
// Used to prevent redundant events.
previous_scale_factor: f64,
} }
unsafe impl ClassType for WinitWindowDelegate { unsafe impl ClassType for WinitWindowDelegate {
@ -139,21 +46,28 @@ declare_class!(
} }
unsafe impl WinitWindowDelegate { unsafe impl WinitWindowDelegate {
#[sel(dealloc)] #[sel(initWithWindow:initialFullscreen:)]
fn dealloc(&mut self) { fn init_with_winit(
self.with_state(|state| unsafe { &mut self,
drop(Box::from_raw(state as *mut WindowDelegateState)); window: &WinitWindow,
}); initial_fullscreen: bool,
} ) -> Option<&mut Self> {
#[sel(initWithWinit:)]
fn init_with_winit(&mut self, state: *mut c_void) -> Option<&mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![self, init] }; let this: Option<&mut Self> = unsafe { msg_send![self, init] };
this.map(|this| { this.map(|this| {
*this.state = state; let scale_factor = window.scale_factor();
this.with_state(|state| {
let _: () = unsafe { msg_send![*state.ns_window, setDelegate: &*this] }; Ivar::write(&mut this.window, unsafe {
let window: *const WinitWindow = window;
Id::retain(window as *mut WinitWindow).unwrap()
}); });
Ivar::write(&mut this.initial_fullscreen, initial_fullscreen);
Ivar::write(&mut this.previous_position, None);
Ivar::write(&mut this.previous_scale_factor, scale_factor);
if scale_factor != 1.0 {
this.emit_static_scale_factor_changed_event();
}
this.window.setDelegate(Some(this));
this this
}) })
} }
@ -162,221 +76,177 @@ declare_class!(
// NSWindowDelegate + NSDraggingDestination protocols // NSWindowDelegate + NSDraggingDestination protocols
unsafe impl WinitWindowDelegate { unsafe impl WinitWindowDelegate {
#[sel(windowShouldClose:)] #[sel(windowShouldClose:)]
fn window_should_close(&self, _: id) -> bool { fn window_should_close(&self, _: Option<&Object>) -> bool {
trace_scope!("windowShouldClose:"); trace_scope!("windowShouldClose:");
self.with_state(|state| state.emit_event(WindowEvent::CloseRequested)); self.emit_event(WindowEvent::CloseRequested);
false false
} }
#[sel(windowWillClose:)] #[sel(windowWillClose:)]
fn window_will_close(&self, _: id) { fn window_will_close(&self, _: Option<&Object>) {
trace_scope!("windowWillClose:"); trace_scope!("windowWillClose:");
self.with_state(|state| unsafe { // `setDelegate:` retains the previous value and then autoreleases it
// `setDelegate:` retains the previous value and then autoreleases it autoreleasepool(|_| {
autoreleasepool(|_| { // Since El Capitan, we need to be careful that delegate methods can't
// Since El Capitan, we need to be careful that delegate methods can't // be called after the window closes.
// be called after the window closes. self.window.setDelegate(None);
let _: () = msg_send![*state.ns_window, setDelegate: nil];
});
state.emit_event(WindowEvent::Destroyed);
}); });
self.emit_event(WindowEvent::Destroyed);
} }
#[sel(windowDidResize:)] #[sel(windowDidResize:)]
fn window_did_resize(&self, _: id) { fn window_did_resize(&mut self, _: Option<&Object>) {
trace_scope!("windowDidResize:"); trace_scope!("windowDidResize:");
self.with_state(|state| { // NOTE: WindowEvent::Resized is reported in frameDidChange.
// NOTE: WindowEvent::Resized is reported in frameDidChange. self.emit_move_event();
state.emit_move_event();
});
} }
// This won't be triggered if the move was part of a resize. // This won't be triggered if the move was part of a resize.
#[sel(windowDidMove:)] #[sel(windowDidMove:)]
fn window_did_move(&self, _: id) { fn window_did_move(&mut self, _: Option<&Object>) {
trace_scope!("windowDidMove:"); trace_scope!("windowDidMove:");
self.with_state(|state| { self.emit_move_event();
state.emit_move_event();
});
} }
#[sel(windowDidChangeBackingProperties:)] #[sel(windowDidChangeBackingProperties:)]
fn window_did_change_backing_properties(&self, _: id) { fn window_did_change_backing_properties(&mut self, _: Option<&Object>) {
trace_scope!("windowDidChangeBackingProperties:"); trace_scope!("windowDidChangeBackingProperties:");
self.with_state(|state| { self.emit_static_scale_factor_changed_event();
state.emit_static_scale_factor_changed_event();
});
} }
#[sel(windowDidBecomeKey:)] #[sel(windowDidBecomeKey:)]
fn window_did_become_key(&self, _: id) { fn window_did_become_key(&self, _: Option<&Object>) {
trace_scope!("windowDidBecomeKey:"); trace_scope!("windowDidBecomeKey:");
self.with_state(|state| { // TODO: center the cursor if the window had mouse grab when it
// TODO: center the cursor if the window had mouse grab when it // lost focus
// lost focus self.emit_event(WindowEvent::Focused(true));
state.emit_event(WindowEvent::Focused(true));
});
} }
#[sel(windowDidResignKey:)] #[sel(windowDidResignKey:)]
fn window_did_resign_key(&self, _: id) { fn window_did_resign_key(&self, _: Option<&Object>) {
trace_scope!("windowDidResignKey:"); trace_scope!("windowDidResignKey:");
self.with_state(|state| { // It happens rather often, e.g. when the user is Cmd+Tabbing, that the
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the // NSWindowDelegate will receive a didResignKey event despite no event
// NSWindowDelegate will receive a didResignKey event despite no event // being received when the modifiers are released. This is because
// being received when the modifiers are released. This is because // flagsChanged events are received by the NSView instead of the
// flagsChanged events are received by the NSView instead of the // NSWindowDelegate, and as a result a tracked modifiers state can quite
// NSWindowDelegate, and as a result a tracked modifiers state can quite // easily fall out of synchrony with reality. This requires us to emit
// easily fall out of synchrony with reality. This requires us to emit // a synthetic ModifiersChanged event when we lose focus.
// a synthetic ModifiersChanged event when we lose focus.
//
// Here we (very unsafely) acquire the winitState (a ViewState) from the
// Object referenced by state.ns_view (an IdRef, which is dereferenced
// to an id)
let view_state: &mut ViewState = unsafe {
let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref");
let state_ptr: *mut c_void = *ns_view.ivar("winitState");
&mut *(state_ptr as *mut ViewState)
};
// Both update the state and emit a ModifiersChanged event. // TODO(madsmtm): Remove the need for this unsafety
if !view_state.modifiers.is_empty() { let mut view = unsafe { Id::from_shared(self.window.view()) };
view_state.modifiers = ModifiersState::empty();
state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers));
}
state.emit_event(WindowEvent::Focused(false)); // Both update the state and emit a ModifiersChanged event.
}); if !view.state.modifiers.is_empty() {
view.state.modifiers = ModifiersState::empty();
self.emit_event(WindowEvent::ModifiersChanged(view.state.modifiers));
}
self.emit_event(WindowEvent::Focused(false));
} }
/// Invoked when the dragged image enters destination bounds or frame /// Invoked when the dragged image enters destination bounds or frame
#[sel(draggingEntered:)] #[sel(draggingEntered:)]
fn dragging_entered(&self, sender: id) -> bool { fn dragging_entered(&self, sender: *mut Object) -> bool {
trace_scope!("draggingEntered:"); trace_scope!("draggingEntered:");
use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration};
use std::path::PathBuf; use std::path::PathBuf;
let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; let pb: Id<NSPasteboard, Shared> = unsafe { msg_send_id![sender, draggingPasteboard] };
let filenames = let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; let filenames: Id<NSArray<NSString>, Shared> = unsafe { Id::cast(filenames) };
for file in unsafe { filenames.iter() } { filenames.into_iter().for_each(|file| {
use cocoa::foundation::NSString; let path = PathBuf::from(file.to_string());
use std::ffi::CStr; self.emit_event(WindowEvent::HoveredFile(path));
});
unsafe {
let f = NSString::UTF8String(file);
let path = CStr::from_ptr(f).to_string_lossy().into_owned();
self.with_state(|state| {
state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path)));
});
}
}
true true
} }
/// Invoked when the image is released /// Invoked when the image is released
#[sel(prepareForDragOperation:)] #[sel(prepareForDragOperation:)]
fn prepare_for_drag_operation(&self, _: id) -> bool { fn prepare_for_drag_operation(&self, _: Option<&Object>) -> bool {
trace_scope!("prepareForDragOperation:"); trace_scope!("prepareForDragOperation:");
true true
} }
/// Invoked after the released image has been removed from the screen /// Invoked after the released image has been removed from the screen
#[sel(performDragOperation:)] #[sel(performDragOperation:)]
fn perform_drag_operation(&self, sender: id) -> bool { fn perform_drag_operation(&self, sender: *mut Object) -> bool {
trace_scope!("performDragOperation:"); trace_scope!("performDragOperation:");
use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration};
use std::path::PathBuf; use std::path::PathBuf;
let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; let pb: Id<NSPasteboard, Shared> = unsafe { msg_send_id![sender, draggingPasteboard] };
let filenames = let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; let filenames: Id<NSArray<NSString>, Shared> = unsafe { Id::cast(filenames) };
for file in unsafe { filenames.iter() } { filenames.into_iter().for_each(|file| {
use cocoa::foundation::NSString; let path = PathBuf::from(file.to_string());
use std::ffi::CStr; self.emit_event(WindowEvent::DroppedFile(path));
});
unsafe {
let f = NSString::UTF8String(file);
let path = CStr::from_ptr(f).to_string_lossy().into_owned();
self.with_state(|state| {
state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path)));
});
}
}
true true
} }
/// Invoked when the dragging operation is complete /// Invoked when the dragging operation is complete
#[sel(concludeDragOperation:)] #[sel(concludeDragOperation:)]
fn conclude_drag_operation(&self, _: id) { fn conclude_drag_operation(&self, _: Option<&Object>) {
trace_scope!("concludeDragOperation:"); trace_scope!("concludeDragOperation:");
} }
/// Invoked when the dragging operation is cancelled /// Invoked when the dragging operation is cancelled
#[sel(draggingExited:)] #[sel(draggingExited:)]
fn dragging_exited(&self, _: id) { fn dragging_exited(&self, _: Option<&Object>) {
trace_scope!("draggingExited:"); trace_scope!("draggingExited:");
self.with_state(|state| state.emit_event(WindowEvent::HoveredFileCancelled)); self.emit_event(WindowEvent::HoveredFileCancelled);
} }
/// Invoked when before enter fullscreen /// Invoked when before enter fullscreen
#[sel(windowWillEnterFullscreen:)] #[sel(windowWillEnterFullscreen:)]
fn window_will_enter_fullscreen(&self, _: id) { fn window_will_enter_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowWillEnterFullscreen:"); trace_scope!("windowWillEnterFullscreen:");
self.with_state(|state| { let mut shared_state = self
state.with_window(|window| { .window
let mut shared_state = window.lock_shared_state("window_will_enter_fullscreen"); .lock_shared_state("window_will_enter_fullscreen");
shared_state.maximized = window.is_zoomed(); shared_state.maximized = self.window.is_zoomed();
let fullscreen = shared_state.fullscreen.as_ref(); let fullscreen = shared_state.fullscreen.as_ref();
match fullscreen { match fullscreen {
// Exclusive mode sets the state in `set_fullscreen` as the user // Exclusive mode sets the state in `set_fullscreen` as the user
// can't enter exclusive mode by other means (like the // can't enter exclusive mode by other means (like the
// fullscreen button on the window decorations) // fullscreen button on the window decorations)
Some(Fullscreen::Exclusive(_)) => (), Some(Fullscreen::Exclusive(_)) => (),
// `window_will_enter_fullscreen` was triggered and we're already // `window_will_enter_fullscreen` was triggered and we're already
// in fullscreen, so we must've reached here by `set_fullscreen` // in fullscreen, so we must've reached here by `set_fullscreen`
// as it updates the state // as it updates the state
Some(Fullscreen::Borderless(_)) => (), Some(Fullscreen::Borderless(_)) => (),
// Otherwise, we must've reached fullscreen by the user clicking // Otherwise, we must've reached fullscreen by the user clicking
// on the green fullscreen button. Update state! // on the green fullscreen button. Update state!
None => { None => {
let current_monitor = Some(window.current_monitor_inner()); let current_monitor = Some(self.window.current_monitor_inner());
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
} }
} }
shared_state.in_fullscreen_transition = true; shared_state.in_fullscreen_transition = true;
})
});
} }
/// Invoked when before exit fullscreen /// Invoked when before exit fullscreen
#[sel(windowWillExitFullScreen:)] #[sel(windowWillExitFullScreen:)]
fn window_will_exit_fullscreen(&self, _: id) { fn window_will_exit_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowWillExitFullScreen:"); trace_scope!("windowWillExitFullScreen:");
self.with_state(|state| { let mut shared_state = self.window.lock_shared_state("window_will_exit_fullscreen");
state.with_window(|window| { shared_state.in_fullscreen_transition = true;
let mut shared_state = window.lock_shared_state("window_will_exit_fullscreen");
shared_state.in_fullscreen_transition = true;
});
});
} }
#[sel(window:willUseFullScreenPresentationOptions:)] #[sel(window:willUseFullScreenPresentationOptions:)]
fn window_will_use_fullscreen_presentation_options( fn window_will_use_fullscreen_presentation_options(
&self, &self,
_: id, _: Option<&Object>,
proposed_options: NSUInteger, proposed_options: NSApplicationPresentationOptions,
) -> NSUInteger { ) -> NSApplicationPresentationOptions {
trace_scope!("window:willUseFullScreenPresentationOptions:"); trace_scope!("window:willUseFullScreenPresentationOptions:");
// Generally, games will want to disable the menu bar and the dock. Ideally, // Generally, games will want to disable the menu bar and the dock. Ideally,
// this would be configurable by the user. Unfortunately because of our // this would be configurable by the user. Unfortunately because of our
@ -386,58 +256,46 @@ declare_class!(
// still want to make this configurable for borderless fullscreen. Right now // 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 // we don't, for consistency. If we do, it should be documented that the
// user-provided options are ignored in exclusive fullscreen. // user-provided options are ignored in exclusive fullscreen.
let mut options: NSUInteger = proposed_options; let mut options = proposed_options;
self.with_state(|state| { let shared_state = self
state.with_window(|window| { .window
let shared_state = .lock_shared_state("window_will_use_fullscreen_presentation_options");
window.lock_shared_state("window_will_use_fullscreen_presentation_options"); if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
options = (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar) }
.bits() as NSUInteger;
}
})
});
options options
} }
/// Invoked when entered fullscreen /// Invoked when entered fullscreen
#[sel(windowDidEnterFullscreen:)] #[sel(windowDidEnterFullscreen:)]
fn window_did_enter_fullscreen(&self, _: id) { fn window_did_enter_fullscreen(&mut self, _: Option<&Object>) {
trace_scope!("windowDidEnterFullscreen:"); trace_scope!("windowDidEnterFullscreen:");
self.with_state(|state| { *self.initial_fullscreen = false;
state.initial_fullscreen = false; let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen");
state.with_window(|window| { shared_state.in_fullscreen_transition = false;
let mut shared_state = window.lock_shared_state("window_did_enter_fullscreen"); let target_fullscreen = shared_state.target_fullscreen.take();
shared_state.in_fullscreen_transition = false; drop(shared_state);
let target_fullscreen = shared_state.target_fullscreen.take(); if let Some(target_fullscreen) = target_fullscreen {
drop(shared_state); self.window.set_fullscreen(target_fullscreen);
if let Some(target_fullscreen) = target_fullscreen { }
window.set_fullscreen(target_fullscreen);
}
});
});
} }
/// Invoked when exited fullscreen /// Invoked when exited fullscreen
#[sel(windowDidExitFullscreen:)] #[sel(windowDidExitFullscreen:)]
fn window_did_exit_fullscreen(&self, _: id) { fn window_did_exit_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowDidExitFullscreen:"); trace_scope!("windowDidExitFullscreen:");
self.with_state(|state| { self.window.restore_state_from_fullscreen();
state.with_window(|window| { let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen");
window.restore_state_from_fullscreen(); shared_state.in_fullscreen_transition = false;
let mut shared_state = window.lock_shared_state("window_did_exit_fullscreen"); let target_fullscreen = shared_state.target_fullscreen.take();
shared_state.in_fullscreen_transition = false; drop(shared_state);
let target_fullscreen = shared_state.target_fullscreen.take(); if let Some(target_fullscreen) = target_fullscreen {
drop(shared_state); self.window.set_fullscreen(target_fullscreen);
if let Some(target_fullscreen) = target_fullscreen { }
window.set_fullscreen(target_fullscreen);
}
})
});
} }
/// Invoked when fail to enter fullscreen /// Invoked when fail to enter fullscreen
@ -457,55 +315,90 @@ declare_class!(
/// This method indicates that there was an error, and you should clean up any /// This method indicates that there was an error, and you should clean up any
/// work you may have done to prepare to enter full-screen mode. /// work you may have done to prepare to enter full-screen mode.
#[sel(windowDidFailToEnterFullscreen:)] #[sel(windowDidFailToEnterFullscreen:)]
fn window_did_fail_to_enter_fullscreen(&self, _: id) { fn window_did_fail_to_enter_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowDidFailToEnterFullscreen:"); trace_scope!("windowDidFailToEnterFullscreen:");
self.with_state(|state| { let mut shared_state = self
state.with_window(|window| { .window
let mut shared_state = .lock_shared_state("window_did_fail_to_enter_fullscreen");
window.lock_shared_state("window_did_fail_to_enter_fullscreen"); shared_state.in_fullscreen_transition = false;
shared_state.in_fullscreen_transition = false; shared_state.target_fullscreen = None;
shared_state.target_fullscreen = None; if *self.initial_fullscreen {
}); #[allow(clippy::let_unit_value)]
if state.initial_fullscreen { unsafe {
unsafe { let _: () = msg_send![
let _: () = msg_send![*state.ns_window, &*self.window,
performSelector:sel!(toggleFullScreen:) performSelector: sel!(toggleFullScreen:),
withObject:nil withObject: ptr::null::<Object>(),
afterDelay: 0.5 afterDelay: 0.5,
]; ];
}; };
} else { } else {
state.with_window(|window| window.restore_state_from_fullscreen()); self.window.restore_state_from_fullscreen();
} }
});
} }
// Invoked when the occlusion state of the window changes // Invoked when the occlusion state of the window changes
#[sel(windowDidChangeOcclusionState:)] #[sel(windowDidChangeOcclusionState:)]
fn window_did_change_occlusion_state(&self, _: id) { fn window_did_change_occlusion_state(&self, _: Option<&Object>) {
trace_scope!("windowDidChangeOcclusionState:"); trace_scope!("windowDidChangeOcclusionState:");
unsafe { self.emit_event(WindowEvent::Occluded(
self.with_state(|state| { !self
state.emit_event(WindowEvent::Occluded( .window
!state .occlusionState()
.ns_window .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
.occlusionState() ))
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
))
});
}
} }
} }
); );
impl WinitWindowDelegate { impl WinitWindowDelegate {
// This function is definitely unsafe (&self -> &mut state), but labeling that pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id<Self, Shared> {
// would increase boilerplate and wouldn't really clarify anything... unsafe {
fn with_state<F: FnOnce(&mut WindowDelegateState) -> T, T>(&self, callback: F) { msg_send_id![
let state_ptr = unsafe { msg_send_id![Self::class(), alloc],
let state_ptr: *mut c_void = *self.state; initWithWindow: window,
&mut *(state_ptr as *mut WindowDelegateState) initialFullscreen: initial_fullscreen,
]
}
}
fn emit_event(&self, event: WindowEvent<'static>) {
let event = Event::WindowEvent {
window_id: WindowId(self.window.id()),
event,
}; };
callback(state_ptr); AppState::queue_event(EventWrapper::StaticEvent(event));
}
fn emit_static_scale_factor_changed_event(&mut self) {
let scale_factor = self.window.scale_factor();
if scale_factor == *self.previous_scale_factor {
return;
};
*self.previous_scale_factor = scale_factor;
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window: self.window.clone(),
suggested_size: self.view_size(),
scale_factor,
});
AppState::queue_event(wrapper);
}
fn emit_move_event(&mut self) {
let rect = self.window.frame();
let x = rect.origin.x as f64;
let y = util::bottom_left_to_top_left(rect);
if self.previous_position.as_deref() != Some(&(x, y)) {
*self.previous_position = Some(Box::new((x, y)));
let scale_factor = self.window.scale_factor();
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
self.emit_event(WindowEvent::Moved(physical_pos));
}
}
fn view_size(&self) -> LogicalSize<f64> {
let size = self.window.contentView().frame().size;
LogicalSize::new(size.width as f64, size.height as f64)
} }
} }