Update to objc2 v0.6 (#4092)

* Use available! macro
* Use objc2-core-foundation and objc2-core-graphics
* Use MainThreadBound instead of StaticMainThreadBound hack
This commit is contained in:
Mads Marquart
2025-01-28 21:31:14 +01:00
committed by GitHub
parent f5dcd2aabe
commit 953d9b4268
25 changed files with 787 additions and 1103 deletions

View File

@@ -104,14 +104,15 @@ ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# AppKit or UIKit # AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies] [target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.5.1" block2 = "0.6.0"
core-foundation = "0.9.3" dispatch2 = { version = "0.2.0", default-features = false, features = ["std", "objc2"] }
objc2 = "0.5.2" objc2 = "0.6.0"
# AppKit # AppKit
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1" objc2-app-kit = { version = "0.3.0", default-features = false, features = [
objc2-app-kit = { version = "0.2.2", features = [ "std",
"objc2-core-foundation",
"NSAppearance", "NSAppearance",
"NSApplication", "NSApplication",
"NSBitmapImageRep", "NSBitmapImageRep",
@@ -141,9 +142,37 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSWindowScripting", "NSWindowScripting",
"NSWindowTabGroup", "NSWindowTabGroup",
] } ] }
objc2-foundation = { version = "0.2.2", features = [ objc2-core-foundation = { version = "0.3.0", default-features = false, features = [
"std",
"block2", "block2",
"dispatch", "CFBase",
"CFCGTypes",
"CFData",
"CFRunLoop",
"CFString",
"CFUUID",
] }
objc2-core-graphics = { version = "0.3.0", default-features = false, features = [
"std",
"libc",
"CGDirectDisplay",
"CGDisplayConfiguration",
"CGDisplayFade",
"CGError",
"CGRemoteOperation",
"CGWindowLevel",
] }
objc2-core-video = { version = "0.3.0", default-features = false, features = [
"std",
"objc2-core-graphics",
"CVBase",
"CVReturn",
"CVDisplayLink",
] }
objc2-foundation = { version = "0.3.0", default-features = false, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray", "NSArray",
"NSAttributedString", "NSAttributedString",
"NSData", "NSData",
@@ -165,20 +194,29 @@ objc2-foundation = { version = "0.2.2", features = [
# UIKit # UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies] [target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-foundation = { version = "0.2.2", features = [ objc2-core-foundation = { version = "0.3.0", default-features = false, features = [
"std",
"CFCGTypes",
"CFBase",
"CFRunLoop",
"CFString",
] }
objc2-foundation = { version = "0.3.0", default-features = false, features = [
"std",
"block2", "block2",
"dispatch", "objc2-core-foundation",
"NSArray", "NSArray",
"NSEnumerator", "NSEnumerator",
"NSGeometry", "NSGeometry",
"NSObjCRuntime", "NSObjCRuntime",
"NSOperation", "NSOperation",
"NSString", "NSString",
"NSProcessInfo",
"NSThread", "NSThread",
"NSSet", "NSSet",
] } ] }
objc2-ui-kit = { version = "0.2.2", features = [ objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
"std",
"objc2-core-foundation",
"UIApplication", "UIApplication",
"UIDevice", "UIDevice",
"UIEvent", "UIEvent",

View File

@@ -184,6 +184,7 @@ changelog entry.
The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves
whilst files are being dragged over the window. It doesn't contain any file paths, just the whilst files are being dragged over the window. It doesn't contain any file paths, just the
pointer position. pointer position.
- Updated `objc2` to `v0.6`.
### Removed ### Removed

View File

@@ -394,7 +394,7 @@ impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen // SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
} }

View File

@@ -21,26 +21,21 @@
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")] #![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! use objc2::rc::Retained; //! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject; //! use objc2::runtime::ProtocolObject;
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; //! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate}; //! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol}; //! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol};
//! use winit::event_loop::EventLoop; //! use winit::event_loop::EventLoop;
//! //!
//! declare_class!( //! define_class!(
//! #[unsafe(super(NSObject))]
//! #[thread_kind = MainThreadOnly]
//! #[name = "AppDelegate"]
//! struct AppDelegate; //! struct AppDelegate;
//! //!
//! unsafe impl ClassType for AppDelegate {
//! type Super = NSObject;
//! type Mutability = mutability::MainThreadOnly;
//! const NAME: &'static str = "MyAppDelegate";
//! }
//!
//! impl DeclaredClass for AppDelegate {}
//!
//! unsafe impl NSObjectProtocol for AppDelegate {} //! unsafe impl NSObjectProtocol for AppDelegate {}
//! //!
//! unsafe impl NSApplicationDelegate for AppDelegate { //! unsafe impl NSApplicationDelegate for AppDelegate {
//! #[method(application:openURLs:)] //! #[unsafe(method(application:openURLs:))]
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) { //! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
//! // Note: To specifically get `application:openURLs:` to work, you _might_ //! // Note: To specifically get `application:openURLs:` to work, you _might_
//! // have to bundle your application. This is not done in this example. //! // have to bundle your application. This is not done in this example.
@@ -51,7 +46,7 @@
//! //!
//! impl AppDelegate { //! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> { //! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] } //! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] }
//! } //! }
//! } //! }
//! //!
@@ -518,7 +513,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
fn ns_screen(&self) -> Option<*mut c_void> { fn ns_screen(&self) -> Option<*mut c_void> {
// SAFETY: We only use the marker to get a pointer // SAFETY: We only use the marker to get a pointer
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _) self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
} }
} }

View File

@@ -2,30 +2,23 @@
use std::rc::Rc; use std::rc::Rc;
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass}; use objc2::{define_class, msg_send, MainThreadMarker};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject}; use objc2_foundation::NSObject;
use super::app_state::AppState; use super::app_state::AppState;
use crate::event::{DeviceEvent, ElementState}; use crate::event::{DeviceEvent, ElementState};
declare_class!( define_class!(
#[unsafe(super(NSApplication, NSResponder, NSObject))]
#[name = "WinitApplication"]
pub(super) struct WinitApplication; pub(super) struct WinitApplication;
unsafe impl ClassType for WinitApplication { impl WinitApplication {
#[inherits(NSResponder, NSObject)]
type Super = NSApplication;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplication";
}
impl DeclaredClass for WinitApplication {}
unsafe impl WinitApplication {
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. // Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) // 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)
#[method(sendEvent:)] #[unsafe(method(sendEvent:))]
fn send_event(&self, event: &NSEvent) { fn send_event(&self, event: &NSEvent) {
// 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)
@@ -33,7 +26,7 @@ declare_class!(
let event_type = unsafe { event.r#type() }; let event_type = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() }; let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventType::KeyUp if event_type == NSEventType::KeyUp
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand) && modifier_flags.contains(NSEventModifierFlags::Command)
{ {
if let Some(key_window) = self.keyWindow() { if let Some(key_window) = self.keyWindow() {
key_window.sendEvent(event); key_window.sendEvent(event);

View File

@@ -5,8 +5,10 @@ use std::sync::atomic::Ordering as AtomicOrdering;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::{MainThreadMarker, NSNotification}; use objc2_foundation::NSNotification;
use super::super::event_handler::EventHandler; use super::super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo}; use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo};
@@ -45,22 +47,10 @@ pub(super) struct AppState {
// as such should be careful to not add fields that, in turn, strongly reference those. // as such should be careful to not add fields that, in turn, strongly reference those.
} }
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s. // SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the
struct StaticMainThreadBound<T>(T);
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the
// main thread. // main thread.
static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> = static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> =
StaticMainThreadBound(OnceCell::new()); MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
impl AppState { impl AppState {
pub(super) fn setup_global( pub(super) fn setup_global(

View File

@@ -4,11 +4,10 @@ use std::sync::OnceLock;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::{msg_send, msg_send_id, sel, ClassType}; use objc2::{available, msg_send, sel, AllocAnyThread, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage}; use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{ use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
NSString,
}; };
use crate::cursor::{CursorImage, OnlyCursorImageSource}; use crate::cursor::{CursorImage, OnlyCursorImageSource};
@@ -67,8 +66,8 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> { unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class(); let cls = NSCursor::class();
if msg_send![cls, respondsToSelector: sel] { if unsafe { msg_send![cls, respondsToSelector: sel] } {
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] }; let cursor: Retained<NSCursor> = unsafe { msg_send![cls, performSelector: sel] };
Some(cursor) Some(cursor)
} else { } else {
tracing::warn!("cursor `{sel}` appears to be invalid"); tracing::warn!("cursor `{sel}` appears to be invalid");
@@ -130,25 +129,21 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
// TODO: Handle PLists better // TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist")); let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe { let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
msg_send_id![ msg_send![
<NSDictionary<NSObject, NSObject>>::class(), <NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path, dictionaryWithContentsOfFile: &*info_path,
] ]
}; };
let mut x = 0.0; let mut x = 0.0;
if let Some(n) = info.get(&*ns_string!("hotx")) { if let Some(n) = info.objectForKey(ns_string!("hotx")) {
if n.is_kind_of::<NSNumber>() { if let Ok(n) = n.downcast::<NSNumber>() {
let ptr: *const NSObject = n; x = n.as_cgfloat();
let ptr: *const NSNumber = ptr.cast();
x = unsafe { &*ptr }.as_cgfloat()
} }
} }
let mut y = 0.0; let mut y = 0.0;
if let Some(n) = info.get(&*ns_string!("hotx")) { if let Some(n) = info.objectForKey(ns_string!("hoty")) {
if n.is_kind_of::<NSNumber>() { if let Ok(n) = n.downcast::<NSNumber>() {
let ptr: *const NSObject = n; y = n.as_cgfloat();
let ptr: *const NSNumber = ptr.cast();
y = unsafe { &*ptr }.as_cgfloat()
} }
} }
@@ -188,6 +183,7 @@ pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone() CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
} }
#[allow(deprecated)]
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> { pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
match icon { match icon {
CursorIcon::Default => default_cursor(), CursorIcon::Default => default_cursor(),
@@ -208,7 +204,9 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(), CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(), CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
CursorIcon::Help => _helpCursor(), CursorIcon::Help => _helpCursor(),
CursorIcon::ZoomIn if available!(macos = 15.0) => unsafe { NSCursor::zoomInCursor() },
CursorIcon::ZoomIn => _zoomInCursor(), CursorIcon::ZoomIn => _zoomInCursor(),
CursorIcon::ZoomOut if available!(macos = 15.0) => unsafe { NSCursor::zoomOutCursor() },
CursorIcon::ZoomOut => _zoomOutCursor(), CursorIcon::ZoomOut => _zoomOutCursor(),
CursorIcon::NeResize => _windowResizeNorthEastCursor(), CursorIcon::NeResize => _windowResizeNorthEastCursor(),
CursorIcon::NwResize => _windowResizeNorthWestCursor(), CursorIcon::NwResize => _windowResizeNorthWestCursor(),

View File

@@ -1,10 +1,10 @@
use std::ffi::c_void; use std::ptr::NonNull;
use core_foundation::base::CFRelease; use dispatch2::run_on_main;
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType}; use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_foundation::{run_on_main, NSPoint}; use objc2_core_foundation::{CFData, CFDataGetBytePtr, CFRetained};
use objc2_foundation::NSPoint;
use smol_str::SmolStr; use smol_str::SmolStr;
use super::ffi; use super::ffi;
@@ -22,29 +22,27 @@ pub struct KeyEventExtra {
/// Ignores ALL modifiers. /// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key { pub fn get_modifierless_char(scancode: u16) -> Key {
let mut string = [0; 16]; let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else {
let input_source; tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
let layout; return Key::Unidentified(NativeKey::MacOS(scancode));
unsafe { };
input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource(); let input_source = unsafe { CFRetained::from_raw(ptr) };
if input_source.is_null() {
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr"); let layout_data = unsafe {
return Key::Unidentified(NativeKey::MacOS(scancode)); ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData)
} };
let layout_data = let Some(layout_data) = (unsafe { layout_data.cast::<CFData>().as_ref() }) else {
ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData); tracing::error!("`TISGetInputSourceProperty` returned null ptr");
if layout_data.is_null() { return Key::Unidentified(NativeKey::MacOS(scancode));
CFRelease(input_source as *mut c_void); };
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode)); let layout = unsafe { CFDataGetBytePtr(layout_data).cast() };
}
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
}
let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() }); let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
let mut result_len = 0; let mut result_len = 0;
let mut dead_keys = 0; let mut dead_keys = 0;
let modifiers = 0; let modifiers = 0;
let mut string = [0; 16];
let translate_result = unsafe { let translate_result = unsafe {
ffi::UCKeyTranslate( ffi::UCKeyTranslate(
layout, layout,
@@ -59,9 +57,6 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
string.as_mut_ptr(), string.as_mut_ptr(),
) )
}; };
unsafe {
CFRelease(input_source as *mut c_void);
}
if translate_result != 0 { if translate_result != 0 {
tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result); tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
return Key::Unidentified(NativeKey::MacOS(scancode)); return Key::Unidentified(NativeKey::MacOS(scancode));
@@ -123,8 +118,8 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
let key_without_modifiers = get_modifierless_char(scancode); let key_without_modifiers = get_modifierless_char(scancode);
let modifiers = unsafe { ns_event.modifierFlags() }; let modifiers = unsafe { ns_event.modifierFlags() };
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl); let has_ctrl = modifiers.contains(NSEventModifierFlags::Control);
let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand); let has_cmd = modifiers.contains(NSEventModifierFlags::Command);
let logical_key = match text_with_all_modifiers.as_ref() { let logical_key = match text_with_all_modifiers.as_ref() {
// Only checking for ctrl and cmd here, not checking for alt because we DO want to // Only checking for ctrl and cmd here, not checking for alt because we DO want to
@@ -308,26 +303,19 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
let mut state = ModifiersState::empty(); let mut state = ModifiersState::empty();
let mut pressed_mods = ModifiersKeys::empty(); let mut pressed_mods = ModifiersKeys::empty();
state state.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::Shift));
.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK)); pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK)); pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK));
state.set( state.set(ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::Control));
ModifiersState::CONTROL,
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
);
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK)); pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK)); pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK));
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption)); state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::Option));
pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK)); pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK));
pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK)); pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK));
state.set( state.set(ModifiersState::SUPER, flags.contains(NSEventModifierFlags::Command));
ModifiersState::SUPER,
flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
);
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK)); pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK)); pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));

View File

@@ -8,18 +8,19 @@ use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained}; use objc2::rc::{autoreleasepool, Retained};
use objc2::{msg_send_id, sel, ClassType}; use objc2::runtime::ProtocolObject;
use objc2::{available, msg_send, ClassType, MainThreadMarker};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification, NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow, NSApplicationWillTerminateNotification, NSWindow,
}; };
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol}; use objc2_core_foundation::{
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceSignal,
CFRunLoopWakeUp,
};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use rwh_06::HasDisplayHandle; use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer; use super::super::notification_center::create_observer;
@@ -129,7 +130,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn system_theme(&self) -> Option<Theme> { fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) { // Dark appearance was introduced in macOS 10.14
if available!(macos = 10.14) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else { } else {
Some(Theme::Light) Some(Theme::Light)
@@ -183,8 +185,8 @@ pub struct EventLoop {
// the system instead cleans it up next time it would have posted a notification to it. // the system instead cleans it up next time it would have posted a notification to it.
// //
// Though we do still need to keep the observers around to prevent them from being deallocated. // Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>, _did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<NSObject>, _will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -208,9 +210,9 @@ impl EventLoop {
.expect("on macOS, `EventLoop` must be created on the main thread!"); .expect("on macOS, `EventLoop` must be created on the main thread!");
let app: Retained<NSApplication> = let app: Retained<NSApplication> =
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] }; unsafe { msg_send![WinitApplication::class(), sharedApplication] };
if !app.is_kind_of::<WinitApplication>() { if !app.isKindOfClass(WinitApplication::class()) {
panic!( panic!(
"`winit` requires control over the principal class. You must create the event \ "`winit` requires control over the principal class. You must create the event \
loop before other parts of your application initialize NSApplication" loop before other parts of your application initialize NSApplication"
@@ -301,8 +303,8 @@ impl EventLoop {
self.app_state.dispatch_init_events(); self.app_state.dispatch_init_events();
} }
// SAFETY: We do not run the application re-entrantly // NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
unsafe { self.app.run() }; self.app.run();
// While the app is running it's possible that we catch a panic // While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which // to avoid unwinding across an objective-c ffi boundary, which
@@ -333,8 +335,7 @@ impl EventLoop {
debug_assert!(!self.app_state.is_running()); debug_assert!(!self.app_state.is_running());
self.app_state.set_stop_on_launch(); self.app_state.set_stop_on_launch();
// SAFETY: We do not run the application re-entrantly self.app.run();
unsafe { self.app.run() };
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
// has launched // has launched
@@ -366,8 +367,7 @@ impl EventLoop {
}, },
} }
self.app_state.set_stop_on_redraw(true); self.app_state.set_stop_on_redraw(true);
// SAFETY: We do not run the application re-entrantly self.app.run();
unsafe { self.app.run() };
} }
// While the app is running it's possible that we catch a panic // While the app is running it's possible that we catch a panic
@@ -437,29 +437,21 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoopProxy { pub struct EventLoopProxy {
pub(crate) wake_up: AtomicBool, pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef, source: CFRetained<CFRunLoopSource>,
} }
unsafe impl Send for EventLoopProxy {} unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {} unsafe impl Sync for EventLoopProxy {}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
}
}
}
impl EventLoopProxy { impl EventLoopProxy {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
unsafe { unsafe {
// just wake up the eventloop // just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {} extern "C-unwind" fn event_loop_proxy_handler(_context: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and // adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms. // process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain(); let rl = CFRunLoopGetMain().unwrap();
let mut context = CFRunLoopSourceContext { let mut context = CFRunLoopSourceContext {
version: 0, version: 0,
info: ptr::null_mut(), info: ptr::null_mut(),
@@ -470,11 +462,11 @@ impl EventLoopProxy {
hash: None, hash: None,
schedule: None, schedule: None,
cancel: None, cancel: None,
perform: event_loop_proxy_handler, perform: Some(event_loop_proxy_handler),
}; };
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); let source = CFRunLoopSourceCreate(None, CFIndex::MAX - 1, &mut context).unwrap();
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); CFRunLoopAddSource(&rl, Some(&source), kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl); CFRunLoopWakeUp(&rl);
EventLoopProxy { wake_up: AtomicBool::new(false), source } EventLoopProxy { wake_up: AtomicBool::new(false), source }
} }
@@ -486,9 +478,9 @@ impl EventLoopProxyProvider for EventLoopProxy {
self.wake_up.store(true, AtomicOrdering::Relaxed); self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe { unsafe {
// Let the main thread know there's a new event. // Let the main thread know there's a new event.
CFRunLoopSourceSignal(self.source); CFRunLoopSourceSignal(&self.source);
let rl = CFRunLoopGetMain(); let rl = CFRunLoopGetMain().unwrap();
CFRunLoopWakeUp(rl); CFRunLoopWakeUp(&rl);
} }
} }
} }

View File

@@ -4,18 +4,10 @@
use std::ffi::c_void; use std::ffi::c_void;
use core_foundation::array::CFArrayRef;
use core_foundation::dictionary::CFDictionaryRef;
use core_foundation::string::CFStringRef;
use core_foundation::uuid::CFUUIDRef;
use core_graphics::base::CGError;
use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef};
use objc2::ffi::NSInteger; use objc2::ffi::NSInteger;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2_core_foundation::{cf_type, CFString, CFUUID};
pub type CGDisplayFadeInterval = f32; use objc2_core_graphics::CGDirectDisplayID;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
pub const kCGDisplayBlendNormal: f32 = 0.0; pub const kCGDisplayBlendNormal: f32 = 0.0;
pub const kCGDisplayBlendSolidColor: f32 = 1.0; pub const kCGDisplayBlendSolidColor: f32 = 1.0;
@@ -23,22 +15,6 @@ pub const kCGDisplayBlendSolidColor: f32 = 1.0;
pub type CGDisplayFadeReservationToken = u32; pub type CGDisplayFadeReservationToken = u32;
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0; pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
pub type Boolean = u8;
pub const FALSE: Boolean = 0;
pub const TRUE: Boolean = 1;
pub const kCGErrorSuccess: i32 = 0;
pub const kCGErrorFailure: i32 = 1000;
pub const kCGErrorIllegalArgument: i32 = 1001;
pub const kCGErrorInvalidConnection: i32 = 1002;
pub const kCGErrorInvalidContext: i32 = 1003;
pub const kCGErrorCannotComplete: i32 = 1004;
pub const kCGErrorNotImplemented: i32 = 1006;
pub const kCGErrorRangeCheck: i32 = 1007;
pub const kCGErrorTypeCheck: i32 = 1008;
pub const kCGErrorInvalidOperation: i32 = 1010;
pub const kCGErrorNoneAvailable: i32 = 1011;
pub const IO1BitIndexedPixels: &str = "P"; pub const IO1BitIndexedPixels: &str = "P";
pub const IO2BitIndexedPixels: &str = "PP"; pub const IO2BitIndexedPixels: &str = "PP";
pub const IO4BitIndexedPixels: &str = "PPPP"; pub const IO4BitIndexedPixels: &str = "PPPP";
@@ -55,9 +31,6 @@ pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
pub const IOYUV422Pixels: &str = "Y4U2V2"; pub const IOYUV422Pixels: &str = "Y4U2V2";
pub const IO8BitOverlayPixels: &str = "O8"; pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut c_void;
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework. // `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
// However, that framework was only introduced "publicly" in macOS 10.13. // However, that framework was only introduced "publicly" in macOS 10.13.
// //
@@ -67,54 +40,11 @@ pub type CGDisplayModeRef = *mut c_void;
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG // https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
#[link(name = "ApplicationServices", kind = "framework")] #[link(name = "ApplicationServices", kind = "framework")]
extern "C" { extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID;
} }
#[link(name = "CoreGraphics", kind = "framework")] #[link(name = "CoreGraphics", kind = "framework")]
extern "C" { extern "C" {
pub fn CGRestorePermanentDisplayConfiguration();
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
pub fn CGConfigureDisplayFadeEffect(
config: CGDisplayConfigRef,
fadeOutSeconds: CGDisplayFadeInterval,
fadeInSeconds: CGDisplayFadeInterval,
fadeRed: f32,
fadeGreen: f32,
fadeBlue: f32,
) -> CGError;
pub fn CGAcquireDisplayFadeReservation(
seconds: CGDisplayReservationInterval,
token: *mut CGDisplayFadeReservationToken,
) -> CGError;
pub fn CGDisplayFade(
token: CGDisplayFadeReservationToken,
duration: CGDisplayFadeInterval,
startBlend: CGDisplayBlendFraction,
endBlend: CGDisplayBlendFraction,
redBlend: f32,
greenBlend: f32,
blueBlend: f32,
synchronous: Boolean,
) -> CGError;
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
pub fn CGDisplaySetDisplayMode(
display: CGDirectDisplayID,
mode: CGDisplayModeRef,
options: CFDictionaryRef,
) -> CGError;
pub fn CGDisplayCopyAllDisplayModes(
display: CGDirectDisplayID,
options: CFDictionaryRef,
) -> CFArrayRef;
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
// Wildly used private APIs; Apple uses them for their Terminal.app. // Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject; pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius( pub fn CGSSetWindowBackgroundBlurRadius(
@@ -124,50 +54,13 @@ extern "C" {
) -> i32; ) -> i32;
} }
mod core_video {
use super::*;
#[link(name = "CoreVideo", kind = "framework")]
extern "C" {}
// CVBase.h
pub type CVTimeFlags = i32; // int32_t
pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CVTime {
pub time_value: i64, // int64_t
pub time_scale: i32, // int32_t
pub flags: i32, // int32_t
}
// CVReturn.h
pub type CVReturn = i32; // int32_t
pub const kCVReturnSuccess: CVReturn = 0;
// CVDisplayLink.h
pub type CVDisplayLinkRef = *mut c_void;
extern "C" {
pub fn CVDisplayLinkCreateWithCGDisplay(
displayID: CGDirectDisplayID,
displayLinkOut: *mut CVDisplayLinkRef,
) -> CVReturn;
pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(
displayLink: CVDisplayLinkRef,
) -> CVTime;
pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef);
}
}
pub use core_video::*;
#[repr(transparent)] #[repr(transparent)]
pub struct TISInputSource(std::ffi::c_void); pub struct TISInputSource(std::ffi::c_void);
pub type TISInputSourceRef = *mut TISInputSource;
cf_type!(
#[encoding_name = "__TISInputSource"]
unsafe impl TISInputSource {}
);
#[repr(transparent)] #[repr(transparent)]
pub struct UCKeyboardLayout(std::ffi::c_void); pub struct UCKeyboardLayout(std::ffi::c_void);
@@ -184,15 +77,15 @@ pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
#[link(name = "Carbon", kind = "framework")] #[link(name = "Carbon", kind = "framework")]
extern "C" { extern "C" {
pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef; pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString;
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn TISGetInputSourceProperty( pub fn TISGetInputSourceProperty(
inputSource: TISInputSourceRef, inputSource: &TISInputSource,
propertyKey: CFStringRef, propertyKey: &CFString,
) -> *mut c_void; ) -> *mut c_void;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef; pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource;
pub fn LMGetKbdType() -> u8; pub fn LMGetKbdType() -> u8;

View File

@@ -1,8 +1,8 @@
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::sel; use objc2::{sel, MainThreadMarker};
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}; use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString}; use objc2_foundation::{ns_string, NSProcessInfo, NSString};
struct KeyEquivalent<'a> { struct KeyEquivalent<'a> {
key: &'a NSString, key: &'a NSString,
@@ -48,10 +48,7 @@ pub fn initialize(app: &NSApplication) {
Some(sel!(hideOtherApplications:)), Some(sel!(hideOtherApplications:)),
Some(KeyEquivalent { Some(KeyEquivalent {
key: ns_string!("h"), key: ns_string!("h"),
masks: Some( masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command),
NSEventModifierFlags::NSEventModifierFlagOption
| NSEventModifierFlags::NSEventModifierFlagCommand,
),
}), }),
); );

View File

@@ -1,22 +1,33 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt;
use std::num::{NonZeroU16, NonZeroU32}; use std::num::{NonZeroU16, NonZeroU32};
use std::ptr::NonNull;
use std::{fmt, ptr};
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; use dispatch2::run_on_main;
use core_foundation::base::{CFRelease, TCFType};
use core_foundation::string::CFString;
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUID};
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::AnyObject; use objc2::MainThreadMarker;
use objc2_app_kit::NSScreen; use objc2_app_kit::NSScreen;
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect}; use objc2_core_foundation::{
CFArrayGetCount, CFArrayGetValueAtIndex, CFRetained, CFUUIDGetUUIDBytes,
};
#[allow(deprecated)]
use objc2_core_graphics::{
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
CGDisplayMode, CGDisplayModeCopyPixelEncoding, CGDisplayModeGetPixelHeight,
CGDisplayModeGetPixelWidth, CGDisplayModeGetRefreshRate, CGDisplayModelNumber,
CGGetActiveDisplayList, CGMainDisplayID,
};
#[allow(deprecated)]
use objc2_core_video::{
kCVReturnSuccess, CVDisplayLinkCreateWithCGDisplay,
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVTimeFlags,
};
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect};
use super::ffi; use super::ffi;
use super::util::cgerr;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::monitor::VideoMode; use crate::monitor::VideoMode;
@@ -50,28 +61,12 @@ impl std::fmt::Debug for VideoModeHandle {
} }
} }
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef); #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub CFRetained<CGDisplayMode>);
unsafe impl Send for NativeDisplayMode {} unsafe impl Send for NativeDisplayMode {}
unsafe impl Sync for NativeDisplayMode {} unsafe impl Sync for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
ffi::CGDisplayModeRelease(self.0);
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
ffi::CGDisplayModeRetain(self.0);
}
NativeDisplayMode(self.0)
}
}
impl VideoModeHandle { impl VideoModeHandle {
fn new( fn new(
monitor: MonitorHandle, monitor: MonitorHandle,
@@ -79,10 +74,9 @@ impl VideoModeHandle {
refresh_rate_millihertz: Option<NonZeroU32>, refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self { ) -> Self {
unsafe { unsafe {
let pixel_encoding = CFString::wrap_under_create_rule( #[allow(deprecated)]
ffi::CGDisplayModeCopyPixelEncoding(native_mode.0), let pixel_encoding =
) CGDisplayModeCopyPixelEncoding(Some(&native_mode.0)).unwrap().to_string();
.to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
32 32
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
@@ -95,8 +89,8 @@ impl VideoModeHandle {
let mode = VideoMode { let mode = VideoMode {
size: PhysicalSize::new( size: PhysicalSize::new(
ffi::CGDisplayModeGetPixelWidth(native_mode.0) as u32, CGDisplayModeGetPixelWidth(Some(&native_mode.0)) as u32,
ffi::CGDisplayModeGetPixelHeight(native_mode.0) as u32, CGDisplayModeGetPixelHeight(Some(&native_mode.0)) as u32,
), ),
refresh_rate_millihertz, refresh_rate_millihertz,
bit_depth: NonZeroU16::new(bit_depth), bit_depth: NonZeroU16::new(bit_depth),
@@ -110,33 +104,12 @@ impl VideoModeHandle {
#[derive(Clone)] #[derive(Clone)]
pub struct MonitorHandle(CGDirectDisplayID); pub struct MonitorHandle(CGDirectDisplayID);
type MonitorUuid = [u8; 16];
impl MonitorHandle { impl MonitorHandle {
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle. /// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
fn uuid(&self) -> MonitorUuid { fn uuid(&self) -> [u8; 16] {
let cf_uuid = unsafe { let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
CFUUID::wrap_under_create_rule(ffi::CGDisplayCreateUUIDFromDisplayID(self.0)) let cf_uuid = unsafe { CFRetained::from_raw(NonNull::new(ptr).unwrap()) };
}; unsafe { CFUUIDGetUUIDBytes(&cf_uuid) }.into()
let uuid = unsafe { CFUUIDGetUUIDBytes(cf_uuid.as_concrete_TypeRef()) };
MonitorUuid::from([
uuid.byte0,
uuid.byte1,
uuid.byte2,
uuid.byte3,
uuid.byte4,
uuid.byte5,
uuid.byte6,
uuid.byte7,
uuid.byte8,
uuid.byte9,
uuid.byte10,
uuid.byte11,
uuid.byte12,
uuid.byte13,
uuid.byte14,
uuid.byte15,
])
} }
} }
@@ -170,19 +143,32 @@ impl std::hash::Hash for MonitorHandle {
} }
pub fn available_monitors() -> VecDeque<MonitorHandle> { pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() { let mut expected_count = 0;
let mut monitors = VecDeque::with_capacity(displays.len()); let res = cgerr(unsafe { CGGetActiveDisplayList(0, ptr::null_mut(), &mut expected_count) });
for display in displays { if res.is_err() {
monitors.push_back(MonitorHandle(display)); return VecDeque::with_capacity(0);
}
monitors
} else {
VecDeque::with_capacity(0)
} }
let mut displays: Vec<CGDirectDisplayID> = vec![0; expected_count as usize];
let mut actual_count = 0;
let res = cgerr(unsafe {
CGGetActiveDisplayList(expected_count, displays.as_mut_ptr(), &mut actual_count)
});
displays.truncate(actual_count as usize);
if res.is_err() {
return VecDeque::with_capacity(0);
}
let mut monitors = VecDeque::with_capacity(displays.len());
for display in displays {
monitors.push_back(MonitorHandle(display));
}
monitors
} }
pub fn primary_monitor() -> MonitorHandle { pub fn primary_monitor() -> MonitorHandle {
MonitorHandle(CGDisplay::main().id) MonitorHandle(unsafe { CGMainDisplayID() })
} }
impl fmt::Debug for MonitorHandle { impl fmt::Debug for MonitorHandle {
@@ -204,8 +190,7 @@ impl MonitorHandle {
// TODO: Be smarter about this: // TODO: Be smarter about this:
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126> // <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
let MonitorHandle(display_id) = *self; let screen_num = unsafe { CGDisplayModelNumber(self.0) };
let screen_num = CGDisplay::new(display_id).model_number();
Some(format!("Monitor #{screen_num}")) Some(format!("Monitor #{screen_num}"))
} }
@@ -219,7 +204,7 @@ impl MonitorHandle {
// This is already in screen coordinates. If we were using `NSScreen`, // This is already in screen coordinates. If we were using `NSScreen`,
// then a conversion would've been needed: // then a conversion would've been needed:
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame()) // flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; let bounds = unsafe { CGDisplayBounds(self.0) };
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y); let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor())) Some(position.to_physical(self.scale_factor()))
} }
@@ -235,12 +220,12 @@ impl MonitorHandle {
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> { fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode = let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _); NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
refresh_rate_millihertz(self.0, &current_display_mode) refresh_rate_millihertz(self.0, &current_display_mode)
} }
pub fn current_video_mode(&self) -> Option<VideoMode> { pub fn current_video_mode(&self) -> Option<VideoMode> {
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _); let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode); let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode) Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
} }
@@ -255,22 +240,20 @@ impl MonitorHandle {
unsafe { unsafe {
let modes = { let modes = {
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null()); let array = CGDisplayCopyAllDisplayModes(self.0, None)
assert!(!array.is_null(), "failed to get list of display modes"); .expect("failed to get list of display modes");
let array_count = CFArrayGetCount(array); let array_count = CFArrayGetCount(&array);
let modes: Vec<_> = (0..array_count) let modes: Vec<_> = (0..array_count)
.map(move |i| { .map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _; let mode = CFArrayGetValueAtIndex(&array, i) as *mut CGDisplayMode;
ffi::CGDisplayModeRetain(mode); CFRetained::from_raw(NonNull::new(mode).unwrap())
mode
}) })
.collect(); .collect();
CFRelease(array as *const _);
modes modes
}; };
modes.into_iter().map(move |mode| { modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; let cg_refresh_rate_hertz = CGDisplayModeGetRefreshRate(Some(&mode)).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that // CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT // isn't a CRT
@@ -307,15 +290,14 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
// Retrieve the CGDirectDisplayID associated with this screen // Retrieve the CGDirectDisplayID associated with this screen
// //
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed // The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for `deviceDescription` for details: // to be an NSNumber. See documentation for details:
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc> // <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
let obj = device_description let obj = device_description
.get(key) .objectForKey(key)
.expect("failed getting screen display id from device description"); .expect("failed getting screen display id from device description")
let obj: *const AnyObject = obj; .downcast::<NSNumber>()
let obj: *const NSNumber = obj.cast(); .expect("NSScreenNumber must be NSNumber");
let obj: &NSNumber = unsafe { &*obj };
obj.as_u32() obj.as_u32()
}) })
@@ -336,7 +318,7 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
// It is intentional that we use `CGMainDisplayID` (as opposed to // It is intentional that we use `CGMainDisplayID` (as opposed to
// `NSScreen::mainScreen`), because that's what the screen coordinates // `NSScreen::mainScreen`), because that's what the screen coordinates
// are relative to, no matter which display the window is currently on. // are relative to, no matter which display the window is currently on.
let main_screen_height = CGDisplay::main().bounds().size.height; let main_screen_height = unsafe { CGDisplayBounds(CGMainDisplayID()) }.size.height;
let y = main_screen_height - frame.size.height - frame.origin.y; let y = main_screen_height - frame.size.height - frame.origin.y;
NSPoint::new(frame.origin.x, y) NSPoint::new(frame.origin.x, y)
@@ -344,25 +326,29 @@ pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> { fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> {
unsafe { unsafe {
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0); let refresh_rate = CGDisplayModeGetRefreshRate(Some(&mode.0));
if refresh_rate > 0.0 { if refresh_rate > 0.0 {
return NonZeroU32::new((refresh_rate * 1000.0).round() as u32); return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
} }
let mut display_link = std::ptr::null_mut(); let mut display_link = std::ptr::null_mut();
if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess { #[allow(deprecated)]
if CVDisplayLinkCreateWithCGDisplay(id, NonNull::from(&mut display_link))
!= kCVReturnSuccess
{
return None; return None;
} }
let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap());
ffi::CVDisplayLinkRelease(display_link); #[allow(deprecated)]
let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(&display_link);
// This value is indefinite if an invalid display link was specified // This value is indefinite if an invalid display link was specified
if time.flags & ffi::kCVTimeIsIndefinite != 0 { if time.flags & CVTimeFlags::IsIndefinite.0 != 0 {
return None; return None;
} }
(time.time_scale as i64) (time.timeScale as i64)
.checked_div(time.time_value) .checked_div(time.timeValue)
.map(|v| (v * 1000) as u32) .map(|v| (v * 1000) as u32)
.and_then(NonZeroU32::new) .and_then(NonZeroU32::new)
} }

View File

@@ -9,22 +9,18 @@ use std::ptr;
use std::rc::Weak; use std::rc::Weak;
use std::time::Instant; use std::time::Instant;
use block2::Block; use objc2::MainThreadMarker;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef}; use objc2_core_foundation::{
use core_foundation::date::CFAbsoluteTimeGetCurrent; kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained,
use core_foundation::runloop::{ CFRunLoop, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFRunLoopObserver, CFRunLoopObserverCallBack, CFRunLoopObserverContext,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopObserverCreate, CFRunLoopPerformBlock, CFRunLoopTimer, CFRunLoopTimerCreate,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate, CFRunLoopTimerInvalidate, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
}; };
use objc2_foundation::MainThreadMarker;
use tracing::error; use tracing::error;
use super::app_state::AppState; use super::app_state::AppState;
use super::event_loop::{stop_app_on_panic, PanicInfo}; use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F) unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
where where
@@ -48,8 +44,8 @@ where
} }
// begin is queued with the highest priority to ensure it is processed before other observers // begin is queued with the highest priority to ensure it is processed before other observers
extern "C" fn control_flow_begin_handler( extern "C-unwind" fn control_flow_begin_handler(
_: CFRunLoopObserverRef, _: *mut CFRunLoopObserver,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
panic_info: *mut c_void, panic_info: *mut c_void,
) { ) {
@@ -57,7 +53,7 @@ extern "C" fn control_flow_begin_handler(
control_flow_handler(panic_info, |panic_info| { control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopAfterWaiting => { CFRunLoopActivity::AfterWaiting => {
// trace!("Triggered `CFRunLoopAfterWaiting`"); // trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info); AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
// trace!("Completed `CFRunLoopAfterWaiting`"); // trace!("Completed `CFRunLoopAfterWaiting`");
@@ -70,8 +66,8 @@ extern "C" fn control_flow_begin_handler(
// end is queued with the lowest priority to ensure it is processed after other observers // end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopExiting would get sent after AboutToWait // without that, LoopExiting would get sent after AboutToWait
extern "C" fn control_flow_end_handler( extern "C-unwind" fn control_flow_end_handler(
_: CFRunLoopObserverRef, _: *mut CFRunLoopObserver,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
panic_info: *mut c_void, panic_info: *mut c_void,
) { ) {
@@ -79,12 +75,12 @@ extern "C" fn control_flow_end_handler(
control_flow_handler(panic_info, |panic_info| { control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => { CFRunLoopActivity::BeforeWaiting => {
// trace!("Triggered `CFRunLoopBeforeWaiting`"); // trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info); AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
// trace!("Completed `CFRunLoopBeforeWaiting`"); // trace!("Completed `CFRunLoopBeforeWaiting`");
}, },
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen CFRunLoopActivity::Exit => (), /* unimplemented!(), // not expected to ever happen */
_ => unreachable!(), _ => unreachable!(),
} }
}); });
@@ -92,44 +88,32 @@ extern "C" fn control_flow_end_handler(
} }
#[derive(Debug)] #[derive(Debug)]
pub struct RunLoop(CFRunLoopRef); pub struct RunLoop(CFRetained<CFRunLoop>);
impl Default for RunLoop {
fn default() -> Self {
Self(ptr::null_mut())
}
}
impl RunLoop { impl RunLoop {
pub fn main(mtm: MainThreadMarker) -> Self { pub fn main(mtm: MainThreadMarker) -> Self {
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so // SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
// scheduling (and scheduling a non-`Send` block) to that thread is allowed. // scheduling (and scheduling a non-`Send` block) to that thread is allowed.
let _ = mtm; let _ = mtm;
RunLoop(unsafe { CFRunLoopGetMain() }) RunLoop(unsafe { CFRunLoopGetMain() }.unwrap())
} }
pub fn wakeup(&self) { pub fn wakeup(&self) {
unsafe { CFRunLoopWakeUp(self.0) } unsafe { CFRunLoopWakeUp(&self.0) }
} }
unsafe fn add_observer( unsafe fn add_observer(
&self, &self,
flags: CFOptionFlags, flags: CFRunLoopActivity,
// The lower the value, the sooner this will run
priority: CFIndex, priority: CFIndex,
handler: CFRunLoopObserverCallBack, handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext, context: *mut CFRunLoopObserverContext,
) { ) {
let observer = unsafe { let observer =
CFRunLoopObserverCreate( unsafe { CFRunLoopObserverCreate(None, flags.0, true, priority, handler, context) }
ptr::null_mut(), .unwrap();
flags, unsafe { CFRunLoopAddObserver(&self.0, Some(&observer), kCFRunLoopCommonModes) };
ffi::TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run
handler,
context,
)
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
} }
/// Submit a closure to run on the main thread as the next step in the run loop, before other /// Submit a closure to run on the main thread as the next step in the run loop, before other
@@ -166,10 +150,6 @@ impl RunLoop {
/// put the event at the very front of the queue, to be handled as soon as possible after /// put the event at the very front of the queue, to be handled as soon as possible after
/// handling whatever event it's currently handling. /// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) { pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
extern "C" {
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
}
// Convert `FnOnce()` to `Block<dyn Fn()>`. // Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure)); let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || { let block = block2::RcBlock::new(move || {
@@ -195,10 +175,10 @@ impl RunLoop {
// and be delivered to the application afterwards. // and be delivered to the application afterwards.
// //
// [#1779]: https://github.com/rust-windowing/winit/issues/1779 // [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef }; let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`. // SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) } unsafe { CFRunLoopPerformBlock(&self.0, Some(mode), Some(&block)) }
} }
} }
@@ -213,15 +193,15 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
copyDescription: None, copyDescription: None,
}; };
run_loop.add_observer( run_loop.add_observer(
kCFRunLoopAfterWaiting, CFRunLoopActivity::AfterWaiting,
CFIndex::MIN, CFIndex::MIN,
control_flow_begin_handler, Some(control_flow_begin_handler),
&mut context as *mut _, &mut context as *mut _,
); );
run_loop.add_observer( run_loop.add_observer(
kCFRunLoopExit | kCFRunLoopBeforeWaiting, CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
CFIndex::MAX, CFIndex::MAX,
control_flow_end_handler, Some(control_flow_end_handler),
&mut context as *mut _, &mut context as *mut _,
); );
} }
@@ -229,7 +209,7 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoopWaker { pub struct EventLoopWaker {
timer: CFRunLoopTimerRef, timer: CFRetained<CFRunLoopTimer>,
/// An arbitrary instant in the past, that will trigger an immediate wake /// An arbitrary instant in the past, that will trigger an immediate wake
/// We save this as the `next_fire_date` for consistency so we can /// We save this as the `next_fire_date` for consistency so we can
@@ -244,30 +224,28 @@ pub struct EventLoopWaker {
impl Drop for EventLoopWaker { impl Drop for EventLoopWaker {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe { CFRunLoopTimerInvalidate(&self.timer) };
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
} }
} }
impl EventLoopWaker { impl EventLoopWaker {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
unsafe { unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the // It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching // future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate( let timer = CFRunLoopTimerCreate(
ptr::null_mut(), None,
f64::MAX, f64::MAX,
0.000_000_1, 0.000_000_1,
0, 0,
0, 0,
wakeup_main_loop, Some(wakeup_main_loop),
ptr::null_mut(), ptr::null_mut(),
); )
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); .unwrap();
CFRunLoopAddTimer(&CFRunLoopGetMain().unwrap(), Some(&timer), kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None } Self { timer, start_instant: Instant::now(), next_fire_date: None }
} }
} }
@@ -275,14 +253,14 @@ impl EventLoopWaker {
pub fn stop(&mut self) { pub fn stop(&mut self) {
if self.next_fire_date.is_some() { if self.next_fire_date.is_some() {
self.next_fire_date = None; self.next_fire_date = None;
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) };
} }
} }
pub fn start(&mut self) { pub fn start(&mut self) {
if self.next_fire_date != Some(self.start_instant) { if self.next_fire_date != Some(self.start_instant) {
self.next_fire_date = Some(self.start_instant); self.next_fire_date = Some(self.start_instant);
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) };
} }
} }
@@ -300,7 +278,7 @@ impl EventLoopWaker {
let duration = instant - now; let duration = instant - now;
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
+ duration.as_secs() as f64; + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs);
} }
} }
}, },

View File

@@ -1,5 +1,8 @@
use objc2_core_graphics::CGError;
use tracing::trace; use tracing::trace;
use crate::error::OsError;
macro_rules! trace_scope { macro_rules! trace_scope {
($s:literal) => { ($s:literal) => {
let _crate = let _crate =
@@ -26,3 +29,12 @@ impl Drop for TraceGuard {
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn); trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
} }
} }
#[track_caller]
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
if err == CGError::Success {
Ok(())
} else {
Err(os_error!(format!("CGError {err:?}")))
}
}

View File

@@ -6,15 +6,14 @@ use std::rc::Rc;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSWindow, NSTrackingRectTag, NSView, NSWindow,
}; };
use objc2_foundation::{ use objc2_foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
NSSize, NSString, NSUInteger,
}; };
use super::app_state::AppState; use super::app_state::AppState;
@@ -138,28 +137,21 @@ pub struct ViewState {
option_as_alt: Cell<OptionAsAlt>, option_as_alt: Cell<OptionAsAlt>,
} }
declare_class!( define_class!(
#[unsafe(super(NSView, NSResponder, NSObject))]
#[ivars = ViewState]
#[name = "WinitView"]
pub(super) struct WinitView; pub(super) struct WinitView;
unsafe impl ClassType for WinitView { /// This documentation attribute makes rustfmt work for some reason?
#[inherits(NSResponder, NSObject)] impl WinitView {
type Super = NSView; #[unsafe(method(isFlipped))]
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitView";
}
impl DeclaredClass for WinitView {
type Ivars = ViewState;
}
unsafe impl WinitView {
#[method(isFlipped)]
fn is_flipped(&self) -> bool { fn is_flipped(&self) -> bool {
// `winit` uses the upper-left corner as the origin. // `winit` uses the upper-left corner as the origin.
true true
} }
#[method(viewDidMoveToWindow)] #[unsafe(method(viewDidMoveToWindow))]
fn view_did_move_to_window(&self) { fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow"); trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() { if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -175,7 +167,7 @@ declare_class!(
} }
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`. // Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[method(viewFrameDidChangeNotification:)] #[unsafe(method(viewFrameDidChangeNotification:))]
fn frame_did_change(&self, _notification: Option<&AnyObject>) { fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification"); trace_scope!("NSViewFrameDidChangeNotification");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() { if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -190,14 +182,16 @@ declare_class!(
self.ivars().tracking_rect.set(Some(tracking_rect)); self.ivars().tracking_rect.set(Some(tracking_rect));
// Emit resize event here rather than from windowDidResize because: // Emit resize event here rather than from windowDidResize because:
// 1. When a new window is created as a tab, the frame size may change without a window resize occurring. // 1. When a new window is created as a tab, the frame size may change without a window
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height). // resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height).
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical::<u32>(self.scale_factor()); let size = logical_size.to_physical::<u32>(self.scale_factor());
self.queue_event(WindowEvent::SurfaceResized(size)); self.queue_event(WindowEvent::SurfaceResized(size));
} }
#[method(drawRect:)] #[unsafe(method(drawRect:))]
fn draw_rect(&self, _rect: NSRect) { fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:"); trace_scope!("drawRect:");
@@ -206,22 +200,27 @@ declare_class!(
// This is a direct subclass of NSView, no need to call superclass' drawRect: // This is a direct subclass of NSView, no need to call superclass' drawRect:
} }
#[method(acceptsFirstResponder)] #[unsafe(method(acceptsFirstResponder))]
fn accepts_first_responder(&self) -> bool { fn accepts_first_responder(&self) -> bool {
trace_scope!("acceptsFirstResponder"); trace_scope!("acceptsFirstResponder");
true true
} }
// This is necessary to prevent a beefy terminal error on MacBook Pros: // This is necessary to prevent a beefy terminal error on MacBook Pros:
// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem // IMKInputSession [0x7fc573576ff0
// TODO: Add an API extension for using `NSTouchBar` // presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self
#[method_id(touchBar)] // textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error
// Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this
// process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from
// this process.}, com.apple.inputmethod.EmojiFunctionRowItem TODO: Add an API
// extension for using `NSTouchBar`
#[unsafe(method_id(touchBar))]
fn touch_bar(&self) -> Option<Retained<NSObject>> { fn touch_bar(&self) -> Option<Retained<NSObject>> {
trace_scope!("touchBar"); trace_scope!("touchBar");
None None
} }
#[method(resetCursorRects)] #[unsafe(method(resetCursorRects))]
fn reset_cursor_rects(&self) { fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects"); trace_scope!("resetCursorRects");
let bounds = self.bounds(); let bounds = self.bounds();
@@ -236,13 +235,13 @@ declare_class!(
} }
unsafe impl NSTextInputClient for WinitView { unsafe impl NSTextInputClient for WinitView {
#[method(hasMarkedText)] #[unsafe(method(hasMarkedText))]
fn has_marked_text(&self) -> bool { fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText"); trace_scope!("hasMarkedText");
self.ivars().marked_text.borrow().length() > 0 self.ivars().marked_text.borrow().length() > 0
} }
#[method(markedRange)] #[unsafe(method(markedRange))]
fn marked_range(&self) -> NSRange { fn marked_range(&self) -> NSRange {
trace_scope!("markedRange"); trace_scope!("markedRange");
let length = self.ivars().marked_text.borrow().length(); let length = self.ivars().marked_text.borrow().length();
@@ -254,14 +253,14 @@ declare_class!(
} }
} }
#[method(selectedRange)] #[unsafe(method(selectedRange))]
fn selected_range(&self) -> NSRange { fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange"); trace_scope!("selectedRange");
// Documented to return `{NSNotFound, 0}` if there is no selection. // Documented to return `{NSNotFound, 0}` if there is no selection.
NSRange::new(NSNotFound as NSUInteger, 0) NSRange::new(NSNotFound as NSUInteger, 0)
} }
#[method(setMarkedText:selectedRange:replacementRange:)] #[unsafe(method(setMarkedText:selectedRange:replacementRange:))]
fn set_marked_text( fn set_marked_text(
&self, &self,
string: &NSObject, string: &NSObject,
@@ -271,23 +270,15 @@ declare_class!(
// TODO: Use _replacement_range, requires changing the event to report surrounding text. // TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:"); trace_scope!("setMarkedText:selectedRange:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. let (marked_text, string) = if let Some(string) =
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() { string.downcast_ref::<NSAttributedString>()
let string: *const NSObject = string; {
let string: *const NSAttributedString = string.cast(); (NSMutableAttributedString::from_attributed_nsstring(string), string.string())
let string = unsafe { &*string }; } else if let Some(string) = string.downcast_ref::<NSString>() {
( (NSMutableAttributedString::from_nsstring(string), string.copy())
NSMutableAttributedString::from_attributed_nsstring(string),
string.string(),
)
} else { } else {
let string: *const NSObject = string; // This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let string: *const NSString = string.cast(); panic!("unexpected text {string:?}")
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.copy(),
)
}; };
// Update marked text. // Update marked text.
@@ -323,7 +314,7 @@ declare_class!(
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range))); self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
} }
#[method(unmarkText)] #[unsafe(method(unmarkText))]
fn unmark_text(&self) { fn unmark_text(&self) {
trace_scope!("unmarkText"); trace_scope!("unmarkText");
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
@@ -340,13 +331,13 @@ declare_class!(
} }
} }
#[method_id(validAttributesForMarkedText)] #[unsafe(method_id(validAttributesForMarkedText))]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> { fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText"); trace_scope!("validAttributesForMarkedText");
NSArray::new() NSArray::new()
} }
#[method_id(attributedSubstringForProposedRange:actualRange:)] #[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))]
fn attributed_substring_for_proposed_range( fn attributed_substring_for_proposed_range(
&self, &self,
_range: NSRange, _range: NSRange,
@@ -356,42 +347,36 @@ declare_class!(
None None
} }
#[method(characterIndexForPoint:)] #[unsafe(method(characterIndexForPoint:))]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
trace_scope!("characterIndexForPoint:"); trace_scope!("characterIndexForPoint:");
0 0
} }
#[method(firstRectForCharacterRange:actualRange:)] #[unsafe(method(firstRectForCharacterRange:actualRange:))]
fn first_rect_for_character_range( fn first_rect_for_character_range(
&self, &self,
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> NSRect { ) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:"); trace_scope!("firstRectForCharacterRange:actualRange:");
let rect = NSRect::new( let rect = NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get());
self.ivars().ime_position.get(),
self.ivars().ime_size.get()
);
// Return value is expected to be in screen coordinates, so we need a conversion here // Return value is expected to be in screen coordinates, so we need a conversion here
self.window() self.window().convertRectToScreen(self.convertRect_toView(rect, None))
.convertRectToScreen(self.convertRect_toView(rect, None))
} }
#[method(insertText:replacementRange:)] #[unsafe(method(insertText:replacementRange:))]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) { fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text. // TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:"); trace_scope!("insertText:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
let string = if string.is_kind_of::<NSAttributedString>() { string.string().to_string()
let string: *const NSObject = string; } else if let Some(string) = string.downcast_ref::<NSString>() {
let string: *const NSAttributedString = string.cast(); string.to_string()
unsafe { &*string }.string().to_string()
} else { } else {
let string: *const NSObject = string; // This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let string: *const NSString = string.cast(); panic!("unexpected text {string:?}")
unsafe { &*string }.to_string()
}; };
let is_control = string.chars().next().is_some_and(|c| c.is_control()); let is_control = string.chars().next().is_some_and(|c| c.is_control());
@@ -404,15 +389,16 @@ declare_class!(
} }
} }
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human // Basically, we're sent this message whenever a keyboard event that doesn't generate a
// readable" character happens, i.e. newlines, tabs, and Ctrl+C. // "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[method(doCommandBySelector:)] #[unsafe(method(doCommandBySelector:))]
fn do_command_by_selector(&self, command: Sel) { fn do_command_by_selector(&self, command: Sel) {
trace_scope!("doCommandBySelector:"); trace_scope!("doCommandBySelector:");
// We shouldn't forward any character from just committed text, since we'll end up sending // We shouldn't forward any character from just committed text, since we'll end up
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case, // sending it twice with some IMEs like Korean one. We'll also always send
// which is not desired given it was used to confirm IME input. // `Enter` in that case, which is not desired given it was used to confirm
// IME input.
if self.ivars().ime_state.get() == ImeState::Committed { if self.ivars().ime_state.get() == ImeState::Committed {
return; return;
} }
@@ -429,7 +415,11 @@ declare_class!(
let window_id = window_id(&self.window()); let window_id = window_id(&self.window());
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
if let Some(handler) = app.macos_handler() { if let Some(handler) = app.macos_handler() {
handler.standard_key_binding(event_loop, window_id, command.name()); handler.standard_key_binding(
event_loop,
window_id,
command.name().to_str().unwrap(),
);
} }
}); });
@@ -439,8 +429,9 @@ declare_class!(
} }
} }
unsafe impl WinitView { /// This documentation attribute makes rustfmt work for some reason?
#[method(keyDown:)] impl WinitView {
#[unsafe(method(keyDown:))]
fn key_down(&self, event: &NSEvent) { fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:"); trace_scope!("keyDown:");
{ {
@@ -483,7 +474,7 @@ declare_class!(
// Allow normal input after the commit. // Allow normal input after the commit.
self.ivars().ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
true true
} },
ImeState::Preedit => true, ImeState::Preedit => true,
// `key_down` could result in preedit clear, so compare old and current state. // `key_down` could result in preedit clear, so compare old and current state.
_ => old_ime_state != self.ivars().ime_state.get(), _ => old_ime_state != self.ivars().ime_state.get(),
@@ -499,7 +490,7 @@ declare_class!(
} }
} }
#[method(keyUp:)] #[unsafe(method(keyUp:))]
fn key_up(&self, event: &NSEvent) { fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:"); trace_scope!("keyUp:");
@@ -507,10 +498,7 @@ declare_class!(
self.update_modifiers(&event, false); self.update_modifiers(&event, false);
// We want to send keyboard input when we are currently in the ground state. // We want to send keyboard input when we are currently in the ground state.
if matches!( if matches!(self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled) {
self.ivars().ime_state.get(),
ImeState::Ground | ImeState::Disabled
) {
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: None, device_id: None,
event: create_key_event(&event, false, false), event: create_key_event(&event, false, false),
@@ -519,14 +507,14 @@ declare_class!(
} }
} }
#[method(flagsChanged:)] #[unsafe(method(flagsChanged:))]
fn flags_changed(&self, event: &NSEvent) { fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:"); trace_scope!("flagsChanged:");
self.update_modifiers(event, true); self.update_modifiers(event, true);
} }
#[method(insertTab:)] #[unsafe(method(insertTab:))]
fn insert_tab(&self, _sender: Option<&AnyObject>) { fn insert_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertTab:"); trace_scope!("insertTab:");
let window = self.window(); let window = self.window();
@@ -537,7 +525,7 @@ declare_class!(
} }
} }
#[method(insertBackTab:)] #[unsafe(method(insertBackTab:))]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) { fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertBackTab:"); trace_scope!("insertBackTab:");
let window = self.window(); let window = self.window();
@@ -550,7 +538,7 @@ declare_class!(
// Allows us to receive Cmd-. (the shortcut for closing a dialog) // Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
#[method(cancelOperation:)] #[unsafe(method(cancelOperation:))]
fn cancel_operation(&self, _sender: Option<&AnyObject>) { fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self); let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:"); trace_scope!("cancelOperation:");
@@ -580,42 +568,42 @@ declare_class!(
// //
// See https://github.com/rust-windowing/winit/pull/1490 for history. // See https://github.com/rust-windowing/winit/pull/1490 for history.
#[method(mouseDown:)] #[unsafe(method(mouseDown:))]
fn mouse_down(&self, event: &NSEvent) { fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:"); trace_scope!("mouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[method(mouseUp:)] #[unsafe(method(mouseUp:))]
fn mouse_up(&self, event: &NSEvent) { fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:"); trace_scope!("mouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[method(rightMouseDown:)] #[unsafe(method(rightMouseDown:))]
fn right_mouse_down(&self, event: &NSEvent) { fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:"); trace_scope!("rightMouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[method(rightMouseUp:)] #[unsafe(method(rightMouseUp:))]
fn right_mouse_up(&self, event: &NSEvent) { fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:"); trace_scope!("rightMouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[method(otherMouseDown:)] #[unsafe(method(otherMouseDown:))]
fn other_mouse_down(&self, event: &NSEvent) { fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:"); trace_scope!("otherMouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[method(otherMouseUp:)] #[unsafe(method(otherMouseUp:))]
fn other_mouse_up(&self, event: &NSEvent) { fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:"); trace_scope!("otherMouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
@@ -624,27 +612,27 @@ declare_class!(
// No tracing on these because that would be overly verbose // No tracing on these because that would be overly verbose
#[method(mouseMoved:)] #[unsafe(method(mouseMoved:))]
fn mouse_moved(&self, event: &NSEvent) { fn mouse_moved(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[method(mouseDragged:)] #[unsafe(method(mouseDragged:))]
fn mouse_dragged(&self, event: &NSEvent) { fn mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[method(rightMouseDragged:)] #[unsafe(method(rightMouseDragged:))]
fn right_mouse_dragged(&self, event: &NSEvent) { fn right_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[method(otherMouseDragged:)] #[unsafe(method(otherMouseDragged:))]
fn other_mouse_dragged(&self, event: &NSEvent) { fn other_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[method(mouseEntered:)] #[unsafe(method(mouseEntered:))]
fn mouse_entered(&self, event: &NSEvent) { fn mouse_entered(&self, event: &NSEvent) {
trace_scope!("mouseEntered:"); trace_scope!("mouseEntered:");
@@ -658,7 +646,7 @@ declare_class!(
}); });
} }
#[method(mouseExited:)] #[unsafe(method(mouseExited:))]
fn mouse_exited(&self, event: &NSEvent) { fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:"); trace_scope!("mouseExited:");
@@ -672,7 +660,7 @@ declare_class!(
}); });
} }
#[method(scrollWheel:)] #[unsafe(method(scrollWheel:))]
fn scroll_wheel(&self, event: &NSEvent) { fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:"); trace_scope!("scrollWheel:");
@@ -689,9 +677,9 @@ declare_class!(
}; };
// The "momentum phase," if any, has higher priority than touch phase (the two should // The "momentum phase," if any, has higher priority than touch phase (the two should
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum // be mutually exclusive anyhow, which is why the API is rather incoherent). If no
// phase is recorded (or rather, the started/ended cases of the momentum phase) then we // momentum phase is recorded (or rather, the started/ended cases of the
// report the touch phase. // momentum phase) then we report the touch phase.
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } { let phase = match unsafe { event.momentumPhase() } {
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
@@ -705,17 +693,13 @@ declare_class!(
self.update_modifiers(event, false); self.update_modifiers(event, false);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta }) app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
);
self.queue_event(WindowEvent::MouseWheel {
device_id: None,
delta,
phase,
}); });
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
} }
#[method(magnifyWithEvent:)] #[unsafe(method(magnifyWithEvent:))]
fn magnify_with_event(&self, event: &NSEvent) { fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:"); trace_scope!("magnifyWithEvent:");
@@ -737,18 +721,16 @@ declare_class!(
}); });
} }
#[method(smartMagnifyWithEvent:)] #[unsafe(method(smartMagnifyWithEvent:))]
fn smart_magnify_with_event(&self, event: &NSEvent) { fn smart_magnify_with_event(&self, event: &NSEvent) {
trace_scope!("smartMagnifyWithEvent:"); trace_scope!("smartMagnifyWithEvent:");
self.mouse_motion(event); self.mouse_motion(event);
self.queue_event(WindowEvent::DoubleTapGesture { self.queue_event(WindowEvent::DoubleTapGesture { device_id: None });
device_id: None,
});
} }
#[method(rotateWithEvent:)] #[unsafe(method(rotateWithEvent:))]
fn rotate_with_event(&self, event: &NSEvent) { fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:"); trace_scope!("rotateWithEvent:");
@@ -770,7 +752,7 @@ declare_class!(
}); });
} }
#[method(pressureChangeWithEvent:)] #[unsafe(method(pressureChangeWithEvent:))]
fn pressure_change_with_event(&self, event: &NSEvent) { fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:"); trace_scope!("pressureChangeWithEvent:");
@@ -784,13 +766,13 @@ declare_class!(
// Allows us to receive Ctrl-Tab and Ctrl-Esc. // Allows us to receive Ctrl-Tab and Ctrl-Esc.
// Note that this *doesn't* help with any missing Cmd inputs. // Note that this *doesn't* help with any missing Cmd inputs.
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 // https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
#[method(_wantsKeyDownForEvent:)] #[unsafe(method(_wantsKeyDownForEvent:))]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool { fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("_wantsKeyDownForEvent:"); trace_scope!("_wantsKeyDownForEvent:");
true true
} }
#[method(acceptsFirstMouse:)] #[unsafe(method(acceptsFirstMouse:))]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:"); trace_scope!("acceptsFirstMouse:");
self.ivars().accepts_first_mouse self.ivars().accepts_first_mouse
@@ -821,7 +803,7 @@ impl WinitView {
accepts_first_mouse, accepts_first_mouse,
option_as_alt: Cell::new(option_as_alt), option_as_alt: Cell::new(option_as_alt),
}); });
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] }; let this: Retained<Self> = unsafe { msg_send![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source(); *this.ivars().input_source.borrow_mut() = this.current_input_source();

View File

@@ -1,10 +1,11 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use dispatch2::MainThreadBound;
use dpi::{Position, Size}; use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained}; use objc2::rc::{autoreleasepool, Retained};
use objc2::{declare_class, mutability, ClassType, DeclaredClass}; use objc2::{define_class, MainThreadMarker, Message};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow}; use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject}; use objc2_foundation::NSObject;
use super::event_loop::ActiveEventLoop; use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate; use super::window_delegate::WindowDelegate;
@@ -332,27 +333,21 @@ impl CoreWindow for Window {
} }
} }
declare_class!( define_class!(
#[unsafe(super(NSWindow, NSResponder, NSObject))]
#[name = "WinitWindow"]
#[derive(Debug)] #[derive(Debug)]
pub struct WinitWindow; pub struct WinitWindow;
unsafe impl ClassType for WinitWindow { /// This documentation attribute makes rustfmt work for some reason?
#[inherits(NSResponder, NSObject)] impl WinitWindow {
type Super = NSWindow; #[unsafe(method(canBecomeMainWindow))]
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindow";
}
impl DeclaredClass for WinitWindow {}
unsafe impl WinitWindow {
#[method(canBecomeMainWindow)]
fn can_become_main_window(&self) -> bool { fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow"); trace_scope!("canBecomeMainWindow");
true true
} }
#[method(canBecomeKeyWindow)] #[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool { fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow"); trace_scope!("canBecomeKeyWindow");
true true
@@ -360,23 +355,17 @@ declare_class!(
} }
); );
declare_class!( define_class!(
#[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))]
#[name = "WinitPanel"]
#[derive(Debug)] #[derive(Debug)]
pub struct WinitPanel; pub struct WinitPanel;
unsafe impl ClassType for WinitPanel { /// This documentation attribute makes rustfmt work for some reason?
#[inherits(NSWindow, NSResponder, NSObject)] impl WinitPanel {
type Super = NSPanel;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitPanel";
}
impl DeclaredClass for WinitPanel {}
unsafe impl WinitPanel {
// although NSPanel can become key window // although NSPanel can become key window
// it doesn't if window doesn't have NSWindowStyleMask::Titled // it doesn't if window doesn't have NSWindowStyleMask::Titled
#[method(canBecomeKeyWindow)] #[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool { fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow"); trace_scope!("canBecomeKeyWindow");
true true

View File

@@ -6,10 +6,12 @@ use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use core_graphics::display::CGDisplay;
use objc2::rc::{autoreleasepool, Retained}; use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::{AnyObject, ProtocolObject}; use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use objc2::{
available, define_class, msg_send, sel, ClassType, DefinedClass, MainThreadMarker,
MainThreadOnly, Message,
};
use objc2_app_kit::{ use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
@@ -19,11 +21,17 @@ use objc2_app_kit::{
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle, NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
}; };
use objc2_core_foundation::{CGFloat, CGPoint};
use objc2_core_graphics::{
CGAcquireDisplayFadeReservation, CGAssociateMouseAndMouseCursorPosition, CGDisplayCapture,
CGDisplayFade, CGDisplayRelease, CGDisplaySetDisplayMode, CGReleaseDisplayFadeReservation,
CGRestorePermanentDisplayConfiguration, CGShieldingWindowLevel, CGWarpMouseCursorPosition,
};
use objc2_foundation::{ use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets, ns_string, NSArray, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey,
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSNotificationCenter, NSObject,
NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming, NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString, NSRect, NSSize, NSString,
}; };
use tracing::{trace, warn}; use tracing::{trace, warn};
@@ -31,6 +39,7 @@ use super::app_state::AppState;
use super::cursor::cursor_from_icon; use super::cursor::cursor_from_icon;
use super::monitor::{self, flip_window_screen_coordinates, get_display_id}; use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop; use super::observer::RunLoop;
use super::util::cgerr;
use super::view::WinitView; use super::view::WinitView;
use super::window::{window_id, WinitPanel, WinitWindow}; use super::window::{window_id, WinitPanel, WinitWindow};
use super::{ffi, Fullscreen, MonitorHandle}; use super::{ffi, Fullscreen, MonitorHandle};
@@ -133,30 +142,24 @@ pub(crate) struct State {
is_borderless_game: Cell<bool>, is_borderless_game: Cell<bool>,
} }
declare_class!( define_class!(
#[unsafe(super(NSObject))]
#[thread_kind = MainThreadOnly]
#[name = "WinitWindowDelegate"]
#[ivars = State]
pub(crate) struct WindowDelegate; pub(crate) struct WindowDelegate;
unsafe impl ClassType for WindowDelegate {
type Super = NSObject;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindowDelegate";
}
impl DeclaredClass for WindowDelegate {
type Ivars = State;
}
unsafe impl NSObjectProtocol for WindowDelegate {} unsafe impl NSObjectProtocol for WindowDelegate {}
unsafe impl NSWindowDelegate for WindowDelegate { unsafe impl NSWindowDelegate for WindowDelegate {
#[method(windowShouldClose:)] #[unsafe(method(windowShouldClose:))]
fn window_should_close(&self, _: Option<&AnyObject>) -> bool { fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
trace_scope!("windowShouldClose:"); trace_scope!("windowShouldClose:");
self.queue_event(WindowEvent::CloseRequested); self.queue_event(WindowEvent::CloseRequested);
false false
} }
#[method(windowWillClose:)] #[unsafe(method(windowWillClose:))]
fn window_will_close(&self, _: Option<&AnyObject>) { fn window_will_close(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillClose:"); trace_scope!("windowWillClose:");
// `setDelegate:` retains the previous value and then autoreleases it // `setDelegate:` retains the previous value and then autoreleases it
@@ -168,14 +171,14 @@ declare_class!(
self.queue_event(WindowEvent::Destroyed); self.queue_event(WindowEvent::Destroyed);
} }
#[method(windowDidResize:)] #[unsafe(method(windowDidResize:))]
fn window_did_resize(&self, _: Option<&AnyObject>) { fn window_did_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResize:"); trace_scope!("windowDidResize:");
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification. // NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
self.emit_move_event(); self.emit_move_event();
} }
#[method(windowWillStartLiveResize:)] #[unsafe(method(windowWillStartLiveResize:))]
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) { fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillStartLiveResize:"); trace_scope!("windowWillStartLiveResize:");
@@ -183,20 +186,20 @@ declare_class!(
self.set_resize_increments_inner(increments); self.set_resize_increments_inner(increments);
} }
#[method(windowDidEndLiveResize:)] #[unsafe(method(windowDidEndLiveResize:))]
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) { fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEndLiveResize:"); trace_scope!("windowDidEndLiveResize:");
self.set_resize_increments_inner(NSSize::new(1., 1.)); self.set_resize_increments_inner(NSSize::new(1., 1.));
} }
// 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.
#[method(windowDidMove:)] #[unsafe(method(windowDidMove:))]
fn window_did_move(&self, _: Option<&AnyObject>) { fn window_did_move(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidMove:"); trace_scope!("windowDidMove:");
self.emit_move_event(); self.emit_move_event();
} }
#[method(windowDidChangeBackingProperties:)] #[unsafe(method(windowDidChangeBackingProperties:))]
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) { fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeBackingProperties:"); trace_scope!("windowDidChangeBackingProperties:");
let scale_factor = self.scale_factor(); let scale_factor = self.scale_factor();
@@ -212,7 +215,7 @@ declare_class!(
}); });
} }
#[method(windowDidBecomeKey:)] #[unsafe(method(windowDidBecomeKey:))]
fn window_did_become_key(&self, _: Option<&AnyObject>) { fn window_did_become_key(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidBecomeKey:"); trace_scope!("windowDidBecomeKey:");
// TODO: center the cursor if the window had mouse grab when it // TODO: center the cursor if the window had mouse grab when it
@@ -220,7 +223,7 @@ declare_class!(
self.queue_event(WindowEvent::Focused(true)); self.queue_event(WindowEvent::Focused(true));
} }
#[method(windowDidResignKey:)] #[unsafe(method(windowDidResignKey:))]
fn window_did_resign_key(&self, _: Option<&AnyObject>) { fn window_did_resign_key(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResignKey:"); trace_scope!("windowDidResignKey:");
// 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
@@ -236,7 +239,7 @@ declare_class!(
} }
/// Invoked when before enter fullscreen /// Invoked when before enter fullscreen
#[method(windowWillEnterFullScreen:)] #[unsafe(method(windowWillEnterFullScreen:))]
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) { fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillEnterFullScreen:"); trace_scope!("windowWillEnterFullScreen:");
@@ -246,7 +249,7 @@ declare_class!(
// 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
@@ -262,14 +265,14 @@ declare_class!(
} }
/// Invoked when before exit fullscreen /// Invoked when before exit fullscreen
#[method(windowWillExitFullScreen:)] #[unsafe(method(windowWillExitFullScreen:))]
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) { fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillExitFullScreen:"); trace_scope!("windowWillExitFullScreen:");
self.ivars().in_fullscreen_transition.set(true); self.ivars().in_fullscreen_transition.set(true);
} }
#[method(window:willUseFullScreenPresentationOptions:)] #[unsafe(method(window:willUseFullScreenPresentationOptions:))]
fn window_will_use_fullscreen_presentation_options( fn window_will_use_fullscreen_presentation_options(
&self, &self,
_: Option<&AnyObject>, _: Option<&AnyObject>,
@@ -286,17 +289,17 @@ declare_class!(
// user-provided options are ignored in exclusive fullscreen. // user-provided options are ignored in exclusive fullscreen.
let mut options = proposed_options; let mut options = proposed_options;
let fullscreen = self.ivars().fullscreen.borrow(); let fullscreen = self.ivars().fullscreen.borrow();
if let Some(Fullscreen::Exclusive(_, _)) = &*fullscreen { if let Some(Fullscreen::Exclusive(..)) = &*fullscreen {
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen options = NSApplicationPresentationOptions::FullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; | NSApplicationPresentationOptions::HideMenuBar;
} }
options options
} }
/// Invoked when entered fullscreen /// Invoked when entered fullscreen
#[method(windowDidEnterFullScreen:)] #[unsafe(method(windowDidEnterFullScreen:))]
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) { fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEnterFullScreen:"); trace_scope!("windowDidEnterFullScreen:");
self.ivars().initial_fullscreen.set(false); self.ivars().initial_fullscreen.set(false);
@@ -307,7 +310,7 @@ declare_class!(
} }
/// Invoked when exited fullscreen /// Invoked when exited fullscreen
#[method(windowDidExitFullScreen:)] #[unsafe(method(windowDidExitFullScreen:))]
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) { fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidExitFullScreen:"); trace_scope!("windowDidExitFullScreen:");
@@ -334,7 +337,7 @@ declare_class!(
/// due to being in the midst of handling some other animation or user gesture. /// due to being in the midst of handling some other animation or user gesture.
/// 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.
#[method(windowDidFailToEnterFullScreen:)] #[unsafe(method(windowDidFailToEnterFullScreen:))]
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) { fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidFailToEnterFullScreen:"); trace_scope!("windowDidFailToEnterFullScreen:");
self.ivars().in_fullscreen_transition.set(false); self.ivars().in_fullscreen_transition.set(false);
@@ -353,14 +356,14 @@ declare_class!(
} }
// Invoked when the occlusion state of the window changes // Invoked when the occlusion state of the window changes
#[method(windowDidChangeOcclusionState:)] #[unsafe(method(windowDidChangeOcclusionState:))]
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) { fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeOcclusionState:"); trace_scope!("windowDidChangeOcclusionState:");
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible); let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
self.queue_event(WindowEvent::Occluded(!visible)); self.queue_event(WindowEvent::Occluded(!visible));
} }
#[method(windowDidChangeScreen:)] #[unsafe(method(windowDidChangeScreen:))]
fn window_did_change_screen(&self, _: Option<&AnyObject>) { fn window_did_change_screen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeScreen:"); trace_scope!("windowDidChangeScreen:");
let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get(); let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get();
@@ -374,44 +377,49 @@ declare_class!(
unsafe impl NSDraggingDestination for WindowDelegate { unsafe impl NSDraggingDestination for WindowDelegate {
/// Invoked when the dragged image enters destination bounds or frame /// Invoked when the dragged image enters destination bounds or frame
#[method(draggingEntered:)] #[unsafe(method(draggingEntered:))]
fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool { fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("draggingEntered:"); trace_scope!("draggingEntered:");
use std::path::PathBuf; use std::path::PathBuf;
let pb = unsafe { sender.draggingPasteboard() }; let pb = unsafe { sender.draggingPasteboard() };
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); let filenames = pb
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) }; .propertyListForType(unsafe { NSFilenamesPboardType })
.unwrap()
.downcast::<NSArray>()
.unwrap();
let paths = filenames let paths = filenames
.into_iter() .into_iter()
.map(|file| PathBuf::from(file.to_string())) .map(|file| PathBuf::from(file.downcast::<NSString>().unwrap().to_string()))
.collect(); .collect();
let dl = unsafe { sender.draggingLocation() }; let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None); let dl = self.view().convertPoint_fromView(dl, None);
let position = LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor()); let position =
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragEntered { paths, position }); self.queue_event(WindowEvent::DragEntered { paths, position });
true true
} }
#[method(wantsPeriodicDraggingUpdates)] #[unsafe(method(wantsPeriodicDraggingUpdates))]
fn wants_periodic_dragging_updates(&self) -> bool { fn wants_periodic_dragging_updates(&self) -> bool {
trace_scope!("wantsPeriodicDraggingUpdates:"); trace_scope!("wantsPeriodicDraggingUpdates:");
true true
} }
/// Invoked periodically as the image is held within the destination area, allowing modification of the dragging operation or mouse-pointer position. /// Invoked periodically as the image is held within the destination area, allowing
#[method(draggingUpdated:)] /// modification of the dragging operation or mouse-pointer position.
#[unsafe(method(draggingUpdated:))]
fn dragging_updated(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool { fn dragging_updated(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("draggingUpdated:"); trace_scope!("draggingUpdated:");
let dl = unsafe { sender.draggingLocation() }; let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None); let dl = self.view().convertPoint_fromView(dl, None);
let position = LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor()); let position =
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragMoved { position }); self.queue_event(WindowEvent::DragMoved { position });
@@ -419,30 +427,34 @@ declare_class!(
} }
/// Invoked when the image is released /// Invoked when the image is released
#[method(prepareForDragOperation:)] #[unsafe(method(prepareForDragOperation:))]
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool { fn prepare_for_drag_operation(&self, _sender: &NSObject) -> 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
#[method(performDragOperation:)] #[unsafe(method(performDragOperation:))]
fn perform_drag_operation(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool { fn perform_drag_operation(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("performDragOperation:"); trace_scope!("performDragOperation:");
use std::path::PathBuf; use std::path::PathBuf;
let pb = unsafe { sender.draggingPasteboard() }; let pb = unsafe { sender.draggingPasteboard() };
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); let filenames = pb
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) }; .propertyListForType(unsafe { NSFilenamesPboardType })
.unwrap()
.downcast::<NSArray>()
.unwrap();
let paths = filenames let paths = filenames
.into_iter() .into_iter()
.map(|file| PathBuf::from(file.to_string())) .map(|file| PathBuf::from(file.downcast::<NSString>().unwrap().to_string()))
.collect(); .collect();
let dl = unsafe { sender.draggingLocation() }; let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None); let dl = self.view().convertPoint_fromView(dl, None);
let position = LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor()); let position =
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor());
self.queue_event(WindowEvent::DragDropped { paths, position }); self.queue_event(WindowEvent::DragDropped { paths, position });
@@ -450,29 +462,29 @@ declare_class!(
} }
/// Invoked when the dragging operation is complete /// Invoked when the dragging operation is complete
#[method(concludeDragOperation:)] #[unsafe(method(concludeDragOperation:))]
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) { fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
trace_scope!("concludeDragOperation:"); trace_scope!("concludeDragOperation:");
} }
/// Invoked when the dragging operation is cancelled /// Invoked when the dragging operation is cancelled
#[method(draggingExited:)] #[unsafe(method(draggingExited:))]
fn dragging_exited(&self, info: Option<&ProtocolObject<dyn NSDraggingInfo>>) { fn dragging_exited(&self, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
trace_scope!("draggingExited:"); trace_scope!("draggingExited:");
let position = info.map(|info| { let position = sender.map(|sender| {
let dl = unsafe { info.draggingLocation() }; let dl = unsafe { sender.draggingLocation() };
let dl = self.view().convertPoint_fromView(dl, None); let dl = self.view().convertPoint_fromView(dl, None);
LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor()) LogicalPosition::<f64>::from((dl.x, dl.y)).to_physical(self.scale_factor())
}); });
self.queue_event(WindowEvent::DragLeft { position } ); self.queue_event(WindowEvent::DragLeft { position });
} }
} }
// Key-Value Observing /// Key-Value Observing
unsafe impl WindowDelegate { impl WindowDelegate {
#[method(observeValueForKeyPath:ofObject:change:context:)] #[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
fn observe_value( fn observe_value(
&self, &self,
key_path: Option<&NSString>, key_path: Option<&NSString>,
@@ -488,19 +500,15 @@ declare_class!(
"requested a change dictionary in `addObserver`, but none was provided", "requested a change dictionary in `addObserver`, but none was provided",
); );
let old = change let old = change
.get(unsafe { NSKeyValueChangeOldKey }) .objectForKey(unsafe { NSKeyValueChangeOldKey })
.expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`"); .expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
let new = change let new = change
.get(unsafe { NSKeyValueChangeNewKey }) .objectForKey(unsafe { NSKeyValueChangeNewKey })
.expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`"); .expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
// SAFETY: The value of `effectiveAppearance` is `NSAppearance` // The value of `effectiveAppearance` is `NSAppearance`
let old: *const AnyObject = old; let old = old.downcast::<NSAppearance>().unwrap();
let old: *const NSAppearance = old.cast(); let new = new.downcast::<NSAppearance>().unwrap();
let old: &NSAppearance = unsafe { &*old };
let new: *const AnyObject = new;
let new: *const NSAppearance = new.cast();
let new: &NSAppearance = unsafe { &*new };
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed"); trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
@@ -511,8 +519,8 @@ declare_class!(
return; return;
} }
let old = appearance_to_theme(old); let old = appearance_to_theme(&old);
let new = appearance_to_theme(new); let new = appearance_to_theme(&new);
// Check that the theme changed in Winit's terms (the theme might have changed on // Check that the theme changed in Winit's terms (the theme might have changed on
// other parameters, such as level of contrast, but the event should not be emitted // other parameters, such as level of contrast, but the event should not be emitted
// in those cases). // in those cases).
@@ -627,11 +635,11 @@ fn new_window(
masks |= NSWindowStyleMask::NonactivatingPanel; masks |= NSWindowStyleMask::NonactivatingPanel;
let window: Option<Retained<WinitPanel>> = unsafe { let window: Option<Retained<WinitPanel>> = unsafe {
msg_send_id![ msg_send![
super(mtm.alloc().set_ivars(())), super(mtm.alloc().set_ivars(())),
initWithContentRect: frame, initWithContentRect: frame,
styleMask: masks, styleMask: masks,
backing: NSBackingStoreType::NSBackingStoreBuffered, backing: NSBackingStoreType::Buffered,
defer: false, defer: false,
] ]
}; };
@@ -639,11 +647,11 @@ fn new_window(
window?.as_super().as_super().retain() window?.as_super().as_super().retain()
} else { } else {
let window: Option<Retained<WinitWindow>> = unsafe { let window: Option<Retained<WinitWindow>> = unsafe {
msg_send_id![ msg_send![
super(mtm.alloc().set_ivars(())), super(mtm.alloc().set_ivars(())),
initWithContentRect: frame, initWithContentRect: frame,
styleMask: masks, styleMask: masks,
backing: NSBackingStoreType::NSBackingStoreBuffered, backing: NSBackingStoreType::Buffered,
defer: false, defer: false,
] ]
}; };
@@ -665,22 +673,22 @@ fn new_window(
} }
if attrs.content_protected { if attrs.content_protected {
window.setSharingType(NSWindowSharingType::NSWindowSharingNone); window.setSharingType(NSWindowSharingType::None);
} }
if attrs.platform_specific.titlebar_transparent { if attrs.platform_specific.titlebar_transparent {
window.setTitlebarAppearsTransparent(true); window.setTitlebarAppearsTransparent(true);
} }
if attrs.platform_specific.title_hidden { if attrs.platform_specific.title_hidden {
window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden); window.setTitleVisibility(NSWindowTitleVisibility::Hidden);
} }
if attrs.platform_specific.titlebar_buttons_hidden { if attrs.platform_specific.titlebar_buttons_hidden {
for titlebar_button in &[ for titlebar_button in &[
#[allow(deprecated)] #[allow(deprecated)]
NSWindowFullScreenButton, NSWindowFullScreenButton,
NSWindowButton::NSWindowMiniaturizeButton, NSWindowButton::MiniaturizeButton,
NSWindowButton::NSWindowCloseButton, NSWindowButton::CloseButton,
NSWindowButton::NSWindowZoomButton, NSWindowButton::ZoomButton,
] { ] {
if let Some(button) = window.standardWindowButton(*titlebar_button) { if let Some(button) = window.standardWindowButton(*titlebar_button) {
button.setHidden(true); button.setHidden(true);
@@ -700,7 +708,7 @@ fn new_window(
} }
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) { if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) { if let Some(button) = window.standardWindowButton(NSWindowButton::ZoomButton) {
button.setEnabled(false); button.setEnabled(false);
} }
} }
@@ -762,10 +770,7 @@ fn new_window(
} }
// register for drag and drop operations. // register for drag and drop operations.
window window.registerForDraggedTypes(&NSArray::from_slice(&[unsafe { NSFilenamesPboardType }]));
.registerForDraggedTypes(&NSArray::from_id_slice(&[
unsafe { NSFilenamesPboardType }.copy()
]));
Some(window) Some(window)
}) })
@@ -793,9 +798,7 @@ impl WindowDelegate {
// SAFETY: We know that there are no parent -> child -> parent cycles since the only // SAFETY: We know that there are no parent -> child -> parent cycles since the only
// place in `winit` where we allow making a window a child window is // place in `winit` where we allow making a window a child window is
// right here, just after it's been created. // right here, just after it's been created.
unsafe { unsafe { parent.addChildWindow_ordered(&window, NSWindowOrderingMode::Above) };
parent.addChildWindow_ordered(&window, NSWindowOrderingMode::NSWindowAbove)
};
}, },
Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"), Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"),
None => (), None => (),
@@ -836,7 +839,7 @@ impl WindowDelegate {
saved_style: Cell::new(None), saved_style: Cell::new(None),
is_borderless_game: Cell::new(attrs.platform_specific.borderless_game), is_borderless_game: Cell::new(attrs.platform_specific.borderless_game),
}); });
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] }; let delegate: Retained<WindowDelegate> = unsafe { msg_send![super(delegate), init] };
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
@@ -847,8 +850,7 @@ impl WindowDelegate {
window.addObserver_forKeyPath_options_context( window.addObserver_forKeyPath_options_context(
&delegate, &delegate,
ns_string!("effectiveAppearance"), ns_string!("effectiveAppearance"),
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Old,
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
ptr::null_mut(), ptr::null_mut(),
) )
}; };
@@ -892,8 +894,8 @@ impl WindowDelegate {
#[track_caller] #[track_caller]
pub(super) fn view(&self) -> Retained<WinitView> { pub(super) fn view(&self) -> Retained<WinitView> {
// SAFETY: The view inside WinitWindow is always `WinitView` // The view inside WinitWindow should always be set and be `WinitView`.
unsafe { Retained::cast(self.window().contentView().unwrap()) } self.window().contentView().unwrap().downcast().unwrap()
} }
#[track_caller] #[track_caller]
@@ -1213,8 +1215,7 @@ impl WindowDelegate {
// We edit the button directly instead of using `NSResizableWindowMask`, // We edit the button directly instead of using `NSResizableWindowMask`,
// since that mask also affect the resizability of the window (which is // since that mask also affect the resizability of the window (which is
// controllable by other means in `winit`). // controllable by other means in `winit`).
if let Some(button) = self.window().standardWindowButton(NSWindowButton::NSWindowZoomButton) if let Some(button) = self.window().standardWindowButton(NSWindowButton::ZoomButton) {
{
button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE)); button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE));
} }
} }
@@ -1227,7 +1228,7 @@ impl WindowDelegate {
} }
if self if self
.window() .window()
.standardWindowButton(NSWindowButton::NSWindowZoomButton) .standardWindowButton(NSWindowButton::ZoomButton)
.map(|b| b.isEnabled()) .map(|b| b.isEnabled())
.unwrap_or(true) .unwrap_or(true)
{ {
@@ -1266,8 +1267,9 @@ impl WindowDelegate {
}; };
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor) cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(associate_mouse_cursor) })?;
.map_err(|status| os_error!(format!("CGError {status}")).into())
Ok(())
} }
#[inline] #[inline]
@@ -1289,14 +1291,12 @@ impl WindowDelegate {
let content_rect = self.window().contentRectForFrameRect(self.window().frame()); let content_rect = self.window().contentRectForFrameRect(self.window().frame());
let window_position = flip_window_screen_coordinates(content_rect); let window_position = flip_window_screen_coordinates(content_rect);
let cursor_position = cursor_position.to_logical::<CGFloat>(self.scale_factor()); let cursor_position = cursor_position.to_logical::<CGFloat>(self.scale_factor());
let point = core_graphics::display::CGPoint { let point = CGPoint {
x: window_position.x + cursor_position.x, x: window_position.x + cursor_position.x,
y: window_position.y + cursor_position.y, y: window_position.y + cursor_position.y,
}; };
CGDisplay::warp_mouse_cursor_position(point) cgerr(unsafe { CGWarpMouseCursorPosition(point) })?;
.map_err(|status| os_error!(format!("CGError {status}")))?; cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(true) })?;
CGDisplay::associate_mouse_and_mouse_cursor_position(true)
.map_err(|status| os_error!(format!("CGError {status}")))?;
Ok(()) Ok(())
} }
@@ -1498,10 +1498,8 @@ impl WindowDelegate {
unsafe { unsafe {
// Fade to black (and wait for the fade to complete) to hide the // Fade to black (and wait for the fade to complete) to hide the
// flicker from capturing the display and switching display mode // flicker from capturing the display and switching display mode
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token) if cgerr(CGAcquireDisplayFadeReservation(5.0, &mut fade_token)).is_ok() {
== ffi::kCGErrorSuccess CGDisplayFade(
{
ffi::CGDisplayFade(
fade_token, fade_token,
0.3, 0.3,
ffi::kCGDisplayBlendNormal, ffi::kCGDisplayBlendNormal,
@@ -1509,11 +1507,11 @@ impl WindowDelegate {
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
ffi::TRUE, true,
); );
} }
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess); cgerr(CGDisplayCapture(display_id)).unwrap();
} }
let video_mode = let video_mode =
@@ -1523,17 +1521,13 @@ impl WindowDelegate {
}; };
unsafe { unsafe {
let result = ffi::CGDisplaySetDisplayMode( cgerr(CGDisplaySetDisplayMode(display_id, Some(&video_mode.native_mode.0), None))
display_id, .expect("failed to set video mode");
video_mode.native_mode.0,
std::ptr::null(),
);
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
// After the display has been configured, fade back in // After the display has been configured, fade back in
// asynchronously // asynchronously
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken { if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
ffi::CGDisplayFade( CGDisplayFade(
fade_token, fade_token,
0.6, 0.6,
ffi::kCGDisplayBlendSolidColor, ffi::kCGDisplayBlendSolidColor,
@@ -1541,9 +1535,9 @@ impl WindowDelegate {
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
ffi::FALSE, false,
); );
ffi::CGReleaseDisplayFadeReservation(fade_token); CGReleaseDisplayFadeReservation(fade_token);
} }
} }
} }
@@ -1575,8 +1569,8 @@ impl WindowDelegate {
// `window:willUseFullScreenPresentationOptions` because for some reason // `window:willUseFullScreenPresentationOptions` because for some reason
// the menu bar remains interactable despite being hidden. // the menu bar remains interactable despite being hidden.
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) { if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock let presentation_options = NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; | NSApplicationPresentationOptions::HideMenuBar;
app.setPresentationOptions(presentation_options); app.setPresentationOptions(presentation_options);
} }
@@ -1601,20 +1595,19 @@ impl WindowDelegate {
// delegate in `window:willUseFullScreenPresentationOptions:`. // delegate in `window:willUseFullScreenPresentationOptions:`.
self.ivars().save_presentation_opts.set(Some(app.presentationOptions())); self.ivars().save_presentation_opts.set(Some(app.presentationOptions()));
let presentation_options = let presentation_options = NSApplicationPresentationOptions::FullScreen
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::HideMenuBar;
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
app.setPresentationOptions(presentation_options); app.setPresentationOptions(presentation_options);
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1; let window_level = unsafe { CGShieldingWindowLevel() } as NSWindowLevel + 1;
self.window().setLevel(window_level); self.window().setLevel(window_level);
}, },
(Some(Fullscreen::Exclusive(ref monitor, _)), Some(Fullscreen::Borderless(_))) => { (Some(Fullscreen::Exclusive(ref monitor, _)), Some(Fullscreen::Borderless(_))) => {
let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or( let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or(
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen NSApplicationPresentationOptions::FullScreen
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | NSApplicationPresentationOptions::AutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationOptions::AutoHideMenuBar,
); );
app.setPresentationOptions(presentation_options); app.setPresentationOptions(presentation_options);
@@ -1726,8 +1719,8 @@ impl WindowDelegate {
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) { pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
let mtm = MainThreadMarker::from(self); let mtm = MainThreadMarker::from(self);
let ns_request_type = request_type.map(|ty| match ty { let ns_request_type = request_type.map(|ty| match ty {
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, UserAttentionType::Critical => NSRequestUserAttentionType::CriticalRequest,
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest, UserAttentionType::Informational => NSRequestUserAttentionType::InformationalRequest,
}); });
if let Some(ty) = ns_request_type { if let Some(ty) = ns_request_type {
NSApplication::sharedApplication(mtm).requestUserAttention(ty); NSApplication::sharedApplication(mtm).requestUserAttention(ty);
@@ -1787,7 +1780,7 @@ impl WindowDelegate {
let mtm = MainThreadMarker::from(self); let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm); let app = NSApplication::sharedApplication(mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) { if available!(macos = 10.14) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else { } else {
Some(Theme::Light) Some(Theme::Light)
@@ -1802,9 +1795,9 @@ impl WindowDelegate {
#[inline] #[inline]
pub fn set_content_protected(&self, protected: bool) { pub fn set_content_protected(&self, protected: bool) {
self.window().setSharingType(if protected { self.window().setSharingType(if protected {
NSWindowSharingType::NSWindowSharingNone NSWindowSharingType::None
} else { } else {
NSWindowSharingType::NSWindowSharingReadOnly NSWindowSharingType::ReadOnly
}) })
} }
@@ -1821,8 +1814,8 @@ fn restore_and_release_display(monitor: &MonitorHandle) {
let available_monitors = monitor::available_monitors(); let available_monitors = monitor::available_monitors();
if available_monitors.contains(monitor) { if available_monitors.contains(monitor) {
unsafe { unsafe {
ffi::CGRestorePermanentDisplayConfiguration(); CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess); cgerr(CGDisplayRelease(monitor.native_identifier())).unwrap();
}; };
} else { } else {
warn!( warn!(
@@ -1867,9 +1860,8 @@ impl WindowExtMacOS for WindowDelegate {
self.ivars().is_simple_fullscreen.set(true); self.ivars().is_simple_fullscreen.set(true);
// Simulate pre-Lion fullscreen by hiding the dock and menu bar // Simulate pre-Lion fullscreen by hiding the dock and menu bar
let presentation_options = let presentation_options = NSApplicationPresentationOptions::AutoHideDock
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | NSApplicationPresentationOptions::AutoHideMenuBar;
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
app.setPresentationOptions(presentation_options); app.setPresentationOptions(presentation_options);
// Hide the titlebar // Hide the titlebar
@@ -1950,10 +1942,14 @@ impl WindowExtMacOS for WindowDelegate {
#[inline] #[inline]
fn select_tab_at_index(&self, index: usize) { fn select_tab_at_index(&self, index: usize) {
if !available!(macos = 10.13) {
tracing::warn!("window tab groups are only available on macOS 10.13+");
return;
}
if let Some(group) = self.window().tabGroup() { if let Some(group) = self.window().tabGroup() {
if let Some(windows) = unsafe { self.window().tabbedWindows() } { if let Some(windows) = unsafe { self.window().tabbedWindows() } {
if index < windows.len() { if index < windows.len() {
group.setSelectedWindow(Some(&windows[index])); group.setSelectedWindow(Some(&windows.objectAtIndex(index)));
} }
} }
} }
@@ -2026,9 +2022,9 @@ fn dark_appearance_name() -> &'static NSString {
} }
pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme { pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[ let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_slice(&[
unsafe { NSAppearanceNameAqua.copy() }, unsafe { NSAppearanceNameAqua },
dark_appearance_name().copy(), dark_appearance_name(),
])); ]));
if let Some(best_match) = best_match { if let Some(best_match) = best_match {
if *best_match == *dark_appearance_name() { if *best_match == *dark_appearance_name() {

View File

@@ -2,7 +2,10 @@ use std::ptr::NonNull;
use block2::RcBlock; use block2::RcBlock;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject}; use objc2::runtime::ProtocolObject;
use objc2_foundation::{
NSNotification, NSNotificationCenter, NSNotificationName, NSObjectProtocol,
};
/// Observe the given notification. /// Observe the given notification.
/// ///
@@ -12,7 +15,7 @@ pub fn create_observer(
center: &NSNotificationCenter, center: &NSNotificationCenter,
name: &NSNotificationName, name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static, handler: impl Fn(&NSNotification) + 'static,
) -> Retained<NSObject> { ) -> Retained<ProtocolObject<dyn NSObjectProtocol>> {
let block = RcBlock::new(move |notification: NonNull<NSNotification>| { let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
handler(unsafe { notification.as_ref() }); handler(unsafe { notification.as_ref() });
}); });

View File

@@ -4,23 +4,19 @@ use std::cell::{OnceCell, RefCell, RefMut};
use std::collections::HashSet; use std::collections::HashSet;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, OnceLock}; use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
use std::{mem, ptr}; use std::{mem, ptr};
use core_foundation::base::CFRelease; use dispatch2::MainThreadBound;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::sel; use objc2::MainThreadMarker;
use objc2_foundation::{ use objc2_core_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopAddTimer,
NSProcessInfo, CFRunLoopGetMain, CFRunLoopTimer, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerSetNextFireDate, CGRect, CGSize,
}; };
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow}; use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView};
use super::super::event_handler::EventHandler; use super::super::event_handler::EventHandler;
use super::window::WinitUIWindow; use super::window::WinitUIWindow;
@@ -48,22 +44,10 @@ macro_rules! bug_assert {
/// This is stored separately from AppState, since AppState needs to be accessible while the handler /// This is stored separately from AppState, since AppState needs to be accessible while the handler
/// is executing. /// is executing.
fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler { fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept // SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept
// of the main thread. // of the main thread.
static GLOBAL: StaticMainThreadBound<OnceCell<EventHandler>> = static GLOBAL: MainThreadBound<OnceCell<EventHandler>> =
StaticMainThreadBound(OnceCell::new()); MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
GLOBAL.get(mtm).get_or_init(EventHandler::new) GLOBAL.get(mtm).get_or_init(EventHandler::new)
} }
@@ -131,7 +115,7 @@ impl AppState {
#[inline(never)] #[inline(never)]
#[cold] #[cold]
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) { fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() }); let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain().unwrap() });
**guard = Some(AppState { **guard = Some(AppState {
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }), app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
control_flow: ControlFlow::default(), control_flow: ControlFlow::default(),
@@ -440,13 +424,7 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
let mut events = Vec::new(); let mut events = Vec::new();
#[allow(deprecated)] #[allow(deprecated)]
for window in application.windows().iter() { for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() { if let Ok(window) = window.downcast::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::Window { events.push(EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::Occluded(occluded), event: WindowEvent::Occluded(occluded),
@@ -510,13 +488,7 @@ pub(crate) fn terminated(application: &UIApplication) {
let mut events = Vec::new(); let mut events = Vec::new();
#[allow(deprecated)] #[allow(deprecated)]
for window in application.windows().iter() { for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() { if let Ok(window) = window.downcast::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::Window { events.push(EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::Destroyed, event: WindowEvent::Destroyed,
@@ -569,46 +541,46 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRec
} }
struct EventLoopWaker { struct EventLoopWaker {
timer: CFRunLoopTimerRef, timer: CFRetained<CFRunLoopTimer>,
} }
impl Drop for EventLoopWaker { impl Drop for EventLoopWaker {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
CFRunLoopTimerInvalidate(self.timer); CFRunLoopTimerInvalidate(&self.timer);
CFRelease(self.timer as _);
} }
} }
} }
impl EventLoopWaker { impl EventLoopWaker {
fn new(rl: CFRunLoopRef) -> EventLoopWaker { fn new(rl: CFRetained<CFRunLoop>) -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
unsafe { unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the // It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching // future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate( let timer = CFRunLoopTimerCreate(
ptr::null_mut(), None,
f64::MAX, f64::MAX,
0.000_000_1, 0.000_000_1,
0, 0,
0, 0,
wakeup_main_loop, Some(wakeup_main_loop),
ptr::null_mut(), ptr::null_mut(),
); )
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes); .unwrap();
CFRunLoopAddTimer(&rl, Some(&timer), kCFRunLoopCommonModes);
EventLoopWaker { timer } EventLoopWaker { timer }
} }
} }
fn stop(&mut self) { fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) }
} }
fn start(&mut self) { fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MIN) }
} }
fn start_at(&mut self, instant: Instant) { fn start_at(&mut self, instant: Instant) {
@@ -621,94 +593,8 @@ impl EventLoopWaker {
let duration = instant - now; let duration = instant - now;
let fsecs = let fsecs =
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) CFRunLoopTimerSetNextFireDate(&self.timer, current + fsecs);
} }
} }
} }
} }
macro_rules! os_capabilities {
(
$(
$(#[$attr:meta])*
$error_name:ident: $objc_call:literal,
$name:ident: $major:literal-$minor:literal
),*
$(,)*
) => {
#[derive(Clone, Debug)]
pub struct OSCapabilities {
$(
pub $name: bool,
)*
os_version: NSOperatingSystemVersion,
}
impl OSCapabilities {
fn from_os_version(os_version: NSOperatingSystemVersion) -> Self {
$(let $name = meets_requirements(os_version, $major, $minor);)*
Self { $($name,)* os_version, }
}
}
impl OSCapabilities {$(
$(#[$attr])*
pub fn $error_name(&self, extra_msg: &str) {
tracing::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
extra_msg
)
}
)*}
};
}
os_capabilities! {
/// <https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc>
#[allow(unused)] // error message unused
safe_area_err_msg: "-[UIView safeAreaInsets]",
safe_area: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc>
home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]",
home_indicator_hidden: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc>
defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]",
defer_system_gestures: 11-0,
/// <https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc>
maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]",
maximum_frames_per_second: 10-3,
/// <https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc>
#[allow(unused)] // error message unused
force_touch_err_msg: "-[UITouch force]",
force_touch: 9-0,
}
fn meets_requirements(
version: NSOperatingSystemVersion,
required_major: NSInteger,
required_minor: NSInteger,
) -> bool {
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
}
fn get_version() -> NSOperatingSystemVersion {
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
// Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
// versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
// not support debugging on devices with an iOS version of less than 8. Another example, in
// order to use an iOS simulator older than iOS 8, you must download an older version of Xcode
// (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
}
pub fn os_capabilities() -> OSCapabilities {
// Cache the version lookup for efficiency
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
}

View File

@@ -3,16 +3,16 @@ use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc; use std::sync::Arc;
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType}; use objc2::runtime::ProtocolObject;
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject}; use objc2::{msg_send, ClassType, MainThreadMarker};
use objc2_core_foundation::{
kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFIndex, CFRetained, CFRunLoopActivity,
CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopObserver,
CFRunLoopObserverCreate, CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use objc2_ui_kit::{ use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification, UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification, UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
@@ -128,13 +128,13 @@ pub struct EventLoop {
// system instead cleans it up next time it would have posted a notification to it. // system instead cleans it up next time it would have posted a notification to it.
// //
// Though we do still need to keep the observers around to prevent them from being deallocated. // Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>, _did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_become_active_observer: Retained<NSObject>, _did_become_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_resign_active_observer: Retained<NSObject>, _will_resign_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_enter_foreground_observer: Retained<NSObject>, _will_enter_foreground_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_enter_background_observer: Retained<NSObject>, _did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<NSObject>, _will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_receive_memory_warning_observer: Retained<NSObject>, _did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -189,9 +189,9 @@ impl EventLoop {
let app = unsafe { notification.object() }.expect( let app = unsafe { notification.object() }.expect(
"UIApplicationWillEnterForegroundNotification to have application object", "UIApplicationWillEnterForegroundNotification to have application object",
); );
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is // The `object` in `UIApplicationWillEnterForegroundNotification` is documented to
// documented to be `UIApplication`. // be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) }; let app = app.downcast::<UIApplication>().unwrap();
send_occluded_event_for_all_windows(&app, false); send_occluded_event_for_all_windows(&app, false);
}, },
); );
@@ -203,9 +203,9 @@ impl EventLoop {
let app = unsafe { notification.object() }.expect( let app = unsafe { notification.object() }.expect(
"UIApplicationDidEnterBackgroundNotification to have application object", "UIApplicationDidEnterBackgroundNotification to have application object",
); );
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is // The `object` in `UIApplicationDidEnterBackgroundNotification` is documented to be
// documented to be `UIApplication`. // `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) }; let app = app.downcast::<UIApplication>().unwrap();
send_occluded_event_for_all_windows(&app, true); send_occluded_event_for_all_windows(&app, true);
}, },
); );
@@ -216,9 +216,9 @@ impl EventLoop {
move |notification| { move |notification| {
let app = unsafe { notification.object() } let app = unsafe { notification.object() }
.expect("UIApplicationWillTerminateNotification to have application object"); .expect("UIApplicationWillTerminateNotification to have application object");
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is // The `object` in `UIApplicationWillTerminateNotification` is (somewhat) documented
// (somewhat) documented to be `UIApplication`. // to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) }; let app = app.downcast::<UIApplication>().unwrap();
app_state::terminated(&app); app_state::terminated(&app);
}, },
); );
@@ -244,7 +244,7 @@ impl EventLoop {
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! { pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
let application: Option<Retained<UIApplication>> = let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] }; unsafe { msg_send![UIApplication::class(), sharedApplication] };
assert!( assert!(
application.is_none(), application.is_none(),
"\ "\
@@ -279,7 +279,7 @@ impl EventLoop {
pub struct EventLoopProxy { pub struct EventLoopProxy {
pub(crate) wake_up: AtomicBool, pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef, source: CFRetained<CFRunLoopSource>,
} }
unsafe impl Send for EventLoopProxy {} unsafe impl Send for EventLoopProxy {}
@@ -287,10 +287,7 @@ unsafe impl Sync for EventLoopProxy {}
impl Drop for EventLoopProxy { impl Drop for EventLoopProxy {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe { CFRunLoopSourceInvalidate(&self.source) };
CFRunLoopSourceInvalidate(self.source);
CFRelease(self.source as _);
}
} }
} }
@@ -298,11 +295,11 @@ impl EventLoopProxy {
pub(crate) fn new() -> EventLoopProxy { pub(crate) fn new() -> EventLoopProxy {
unsafe { unsafe {
// just wake up the eventloop // just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {} extern "C-unwind" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and // adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms. // process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain(); let rl = CFRunLoopGetMain().unwrap();
let mut context = CFRunLoopSourceContext { let mut context = CFRunLoopSourceContext {
version: 0, version: 0,
info: ptr::null_mut(), info: ptr::null_mut(),
@@ -313,11 +310,11 @@ impl EventLoopProxy {
hash: None, hash: None,
schedule: None, schedule: None,
cancel: None, cancel: None,
perform: event_loop_proxy_handler, perform: Some(event_loop_proxy_handler),
}; };
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); let source = CFRunLoopSourceCreate(None, CFIndex::MAX - 1, &mut context).unwrap();
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); CFRunLoopAddSource(&rl, Some(&source), kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl); CFRunLoopWakeUp(&rl);
EventLoopProxy { wake_up: AtomicBool::new(false), source } EventLoopProxy { wake_up: AtomicBool::new(false), source }
} }
@@ -329,9 +326,9 @@ impl EventLoopProxyProvider for EventLoopProxy {
self.wake_up.store(true, AtomicOrdering::Relaxed); self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe { unsafe {
// let the main thread know there's a new event // let the main thread know there's a new event
CFRunLoopSourceSignal(self.source); CFRunLoopSourceSignal(&self.source);
let rl = CFRunLoopGetMain(); let rl = CFRunLoopGetMain().unwrap();
CFRunLoopWakeUp(rl); CFRunLoopWakeUp(&rl);
} }
} }
} }
@@ -340,15 +337,15 @@ fn setup_control_flow_observers() {
unsafe { unsafe {
// begin is queued with the highest priority to ensure it is processed before other // begin is queued with the highest priority to ensure it is processed before other
// observers // observers
extern "C" fn control_flow_begin_handler( extern "C-unwind" fn control_flow_begin_handler(
_: CFRunLoopObserverRef, _: *mut CFRunLoopObserver,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm), CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -364,65 +361,68 @@ fn setup_control_flow_observers() {
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
// //
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
extern "C" fn control_flow_main_end_handler( extern "C-unwind" fn control_flow_main_end_handler(
_: CFRunLoopObserverRef, _: *mut CFRunLoopObserver,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm), CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS CFRunLoopActivity::Exit => {}, // may happen when running on macOS
_ => unreachable!(), _ => unreachable!(),
} }
} }
// end is queued with the lowest priority to ensure it is processed after other observers // end is queued with the lowest priority to ensure it is processed after other observers
extern "C" fn control_flow_end_handler( extern "C-unwind" fn control_flow_end_handler(
_: CFRunLoopObserverRef, _: *mut CFRunLoopObserver,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm), CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => {}, // may happen when running on macOS CFRunLoopActivity::Exit => {}, // may happen when running on macOS
_ => unreachable!(), _ => unreachable!(),
} }
} }
let main_loop = CFRunLoopGetMain(); let main_loop = CFRunLoopGetMain().unwrap();
let begin_observer = CFRunLoopObserverCreate( let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(), None,
kCFRunLoopAfterWaiting, CFRunLoopActivity::AfterWaiting.0,
1, // repeat = true true,
CFIndex::MIN, CFIndex::MIN,
control_flow_begin_handler, Some(control_flow_begin_handler),
ptr::null_mut(), ptr::null_mut(),
); )
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); .unwrap();
CFRunLoopAddObserver(&main_loop, Some(&begin_observer), kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserverCreate( let main_end_observer = CFRunLoopObserverCreate(
ptr::null_mut(), None,
kCFRunLoopExit | kCFRunLoopBeforeWaiting, (CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
1, // repeat = true true,
0, // see comment on `control_flow_main_end_handler` 0, // see comment on `control_flow_main_end_handler`
control_flow_main_end_handler, Some(control_flow_main_end_handler),
ptr::null_mut(), ptr::null_mut(),
); )
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode); .unwrap();
CFRunLoopAddObserver(&main_loop, Some(&main_end_observer), kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate( let end_observer = CFRunLoopObserverCreate(
ptr::null_mut(), None,
kCFRunLoopExit | kCFRunLoopBeforeWaiting, (CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
1, // repeat = true true,
CFIndex::MAX, CFIndex::MAX,
control_flow_end_handler, Some(control_flow_end_handler),
ptr::null_mut(), ptr::null_mut(),
); )
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); .unwrap();
CFRunLoopAddObserver(&main_loop, Some(&end_observer), kCFRunLoopDefaultMode);
} }
} }

View File

@@ -4,13 +4,12 @@ use std::collections::VecDeque;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::{fmt, hash, ptr}; use std::{fmt, hash, ptr};
use objc2::mutability::IsRetainable; use dispatch2::{run_on_main, MainThreadBound};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::Message; use objc2::{available, MainThreadMarker, Message};
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger}; use objc2_foundation::NSInteger;
use objc2_ui_kit::{UIScreen, UIScreenMode}; use objc2_ui_kit::{UIScreen, UIScreenMode};
use super::app_state;
use crate::dpi::PhysicalPosition; use crate::dpi::PhysicalPosition;
use crate::monitor::VideoMode; use crate::monitor::VideoMode;
@@ -18,13 +17,13 @@ use crate::monitor::VideoMode;
#[derive(Debug)] #[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>); struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>);
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> { impl<T: Message> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm))) Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm)))
} }
} }
impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> { impl<T: Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) { fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer // SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
@@ -32,7 +31,7 @@ impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
} }
} }
impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> { impl<T: Message> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer // SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() }; let mtm = unsafe { MainThreadMarker::new_unchecked() };
@@ -40,7 +39,7 @@ impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
} }
} }
impl<T: IsRetainable + Message> Eq for MainThreadBoundDelegateImpls<T> {} impl<T: Message> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoModeHandle { pub struct VideoModeHandle {
@@ -150,7 +149,7 @@ impl MonitorHandle {
#[allow(deprecated)] #[allow(deprecated)]
UIScreen::screens(mtm) UIScreen::screens(mtm)
.iter() .iter()
.position(|rhs| rhs == &**self.ui_screen(mtm)) .position(|rhs| rhs == *self.ui_screen(mtm))
.map(|idx| idx.to_string()) .map(|idx| idx.to_string())
} }
}) })
@@ -211,8 +210,7 @@ impl MonitorHandle {
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> { fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
let refresh_rate_millihertz: NSInteger = { let refresh_rate_millihertz: NSInteger = {
let os_capabilities = app_state::os_capabilities(); if available!(ios = 10.3, tvos = 10.2) {
if os_capabilities.maximum_frames_per_second {
uiscreen.maximumFramesPerSecond() uiscreen.maximumFramesPerSecond()
} else { } else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html // https://developer.apple.com/library/archive/technotes/tn2460/_index.html
@@ -225,7 +223,9 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> Option<NonZeroU32> {
// //
// FIXME: earlier OSs could calculate the refresh rate using // FIXME: earlier OSs could calculate the refresh rate using
// `-[CADisplayLink duration]`. // `-[CADisplayLink duration]`.
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps"); tracing::warn!(
"`maximumFramesPerSecond` requires iOS 10.3+ or tvOS 10.2+. Defaulting to 60 fps"
);
60 60
} }
}; };
@@ -254,7 +254,7 @@ mod tests {
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm))); assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm); let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main))); assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(&*screen, &*main)));
assert!(unsafe { assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm)) NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))

View File

@@ -3,8 +3,9 @@ use std::cell::{Cell, RefCell};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject}; use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use objc2::{available, define_class, msg_send, sel, DefinedClass, MainThreadMarker};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString}; use objc2_core_foundation::{CGFloat, CGPoint, CGRect};
use objc2_foundation::{NSObject, NSSet, NSString};
use objc2_ui_kit::{ use objc2_ui_kit::{
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate, UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
@@ -39,36 +40,26 @@ pub struct WinitViewState {
fingers: Cell<u8>, fingers: Cell<u8>,
} }
declare_class!( define_class!(
#[unsafe(super(UIView, UIResponder, NSObject))]
#[name = "WinitUIView"]
#[ivars = WinitViewState]
pub(crate) struct WinitView; pub(crate) struct WinitView;
unsafe impl ClassType for WinitView { /// This documentation attribute makes rustfmt work for some reason?
#[inherits(UIResponder, NSObject)] impl WinitView {
type Super = UIView; #[unsafe(method(drawRect:))]
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIView";
}
impl DeclaredClass for WinitView {
type Ivars = WinitViewState;
}
unsafe impl WinitView {
#[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) { fn draw_rect(&self, rect: CGRect) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
let window = self.window().unwrap(); let window = self.window().unwrap();
app_state::handle_nonuser_event( app_state::handle_nonuser_event(mtm, EventWrapper::Window {
mtm, window_id: window.id(),
EventWrapper::Window { event: WindowEvent::RedrawRequested,
window_id: window.id(), });
event: WindowEvent::RedrawRequested,
},
);
let _: () = unsafe { msg_send![super(self), drawRect: rect] }; let _: () = unsafe { msg_send![super(self), drawRect: rect] };
} }
#[method(layoutSubviews)] #[unsafe(method(layoutSubviews))]
fn layout_subviews(&self) { fn layout_subviews(&self) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] }; let _: () = unsafe { msg_send![super(self), layoutSubviews] };
@@ -82,16 +73,13 @@ declare_class!(
.to_physical(scale_factor); .to_physical(scale_factor);
let window = self.window().unwrap(); let window = self.window().unwrap();
app_state::handle_nonuser_event( app_state::handle_nonuser_event(mtm, EventWrapper::Window {
mtm, window_id: window.id(),
EventWrapper::Window { event: WindowEvent::SurfaceResized(size),
window_id: window.id(), });
event: WindowEvent::SurfaceResized(size),
},
);
} }
#[method(setContentScaleFactor:)] #[unsafe(method(setContentScaleFactor:))]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
let _: () = let _: () =
@@ -124,49 +112,46 @@ declare_class!(
let window_id = window.id(); let window_id = window.id();
app_state::handle_nonuser_events( app_state::handle_nonuser_events(
mtm, mtm,
std::iter::once(EventWrapper::ScaleFactorChanged( std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged {
app_state::ScaleFactorChanged { window,
window, scale_factor,
scale_factor, suggested_size: size.to_physical(scale_factor),
suggested_size: size.to_physical(scale_factor), }))
},
))
.chain(std::iter::once(EventWrapper::Window { .chain(std::iter::once(EventWrapper::Window {
window_id, window_id,
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)), event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
}, })),
)),
); );
} }
#[method(safeAreaInsetsDidChange)] #[unsafe(method(safeAreaInsetsDidChange))]
fn safe_area_changed(&self) { fn safe_area_changed(&self) {
debug!("safeAreaInsetsDidChange was called, requesting redraw"); debug!("safeAreaInsetsDidChange was called, requesting redraw");
// When the safe area changes we want to make sure to emit a redraw event // When the safe area changes we want to make sure to emit a redraw event
self.setNeedsDisplay(); self.setNeedsDisplay();
} }
#[method(touchesBegan:withEvent:)] #[unsafe(method(touchesBegan:withEvent:))]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[method(touchesMoved:withEvent:)] #[unsafe(method(touchesMoved:withEvent:))]
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[method(touchesEnded:withEvent:)] #[unsafe(method(touchesEnded:withEvent:))]
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[method(touchesCancelled:withEvent:)] #[unsafe(method(touchesCancelled:withEvent:))]
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) { fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[method(pinchGesture:)] #[unsafe(method(pinchGesture:))]
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) { fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
@@ -174,46 +159,40 @@ declare_class!(
UIGestureRecognizerState::Began => { UIGestureRecognizerState::Began => {
self.ivars().pinch_last_delta.set(recognizer.scale()); self.ivars().pinch_last_delta.set(recognizer.scale());
(TouchPhase::Started, 0.0) (TouchPhase::Started, 0.0)
} },
UIGestureRecognizerState::Changed => { UIGestureRecognizerState::Changed => {
let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale()); let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale());
(TouchPhase::Moved, recognizer.scale() - last_scale) (TouchPhase::Moved, recognizer.scale() - last_scale)
} },
UIGestureRecognizerState::Ended => { UIGestureRecognizerState::Ended => {
let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0); let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0);
(TouchPhase::Moved, recognizer.scale() - last_scale) (TouchPhase::Moved, recognizer.scale() - last_scale)
} },
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
self.ivars().rotation_last_delta.set(0.0); self.ivars().rotation_last_delta.set(0.0);
// Pass -delta so that action is reversed // Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.scale()) (TouchPhase::Cancelled, -recognizer.scale())
} },
state => panic!("unexpected recognizer state: {state:?}"), state => panic!("unexpected recognizer state: {state:?}"),
}; };
let gesture_event = EventWrapper::Window { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::PinchGesture { event: WindowEvent::PinchGesture { device_id: None, delta: delta as f64, phase },
device_id: None,
delta: delta as f64,
phase,
},
}; };
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
} }
#[method(doubleTapGesture:)] #[unsafe(method(doubleTapGesture:))]
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) { fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended { if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::Window { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::DoubleTapGesture { event: WindowEvent::DoubleTapGesture { device_id: None },
device_id: None,
},
}; };
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
@@ -221,7 +200,7 @@ declare_class!(
} }
} }
#[method(rotationGesture:)] #[unsafe(method(rotationGesture:))]
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) { fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
@@ -230,23 +209,24 @@ declare_class!(
self.ivars().rotation_last_delta.set(0.0); self.ivars().rotation_last_delta.set(0.0);
(TouchPhase::Started, 0.0) (TouchPhase::Started, 0.0)
} },
UIGestureRecognizerState::Changed => { UIGestureRecognizerState::Changed => {
let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation()); let last_rotation =
self.ivars().rotation_last_delta.replace(recognizer.rotation());
(TouchPhase::Moved, recognizer.rotation() - last_rotation) (TouchPhase::Moved, recognizer.rotation() - last_rotation)
} },
UIGestureRecognizerState::Ended => { UIGestureRecognizerState::Ended => {
let last_rotation = self.ivars().rotation_last_delta.replace(0.0); let last_rotation = self.ivars().rotation_last_delta.replace(0.0);
(TouchPhase::Ended, recognizer.rotation() - last_rotation) (TouchPhase::Ended, recognizer.rotation() - last_rotation)
} },
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
self.ivars().rotation_last_delta.set(0.0); self.ivars().rotation_last_delta.set(0.0);
// Pass -delta so that action is reversed // Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.rotation()) (TouchPhase::Cancelled, -recognizer.rotation())
} },
state => panic!("unexpected recognizer state: {state:?}"), state => panic!("unexpected recognizer state: {state:?}"),
}; };
@@ -264,7 +244,7 @@ declare_class!(
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
} }
#[method(panGesture:)] #[unsafe(method(panGesture:))]
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) { fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
@@ -275,7 +255,7 @@ declare_class!(
self.ivars().pan_last_delta.set(translation); self.ivars().pan_last_delta.set(translation);
(TouchPhase::Started, 0.0, 0.0) (TouchPhase::Started, 0.0, 0.0)
} },
UIGestureRecognizerState::Changed => { UIGestureRecognizerState::Changed => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation); let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation);
@@ -283,25 +263,26 @@ declare_class!(
let dy = translation.y - last_pan.y; let dy = translation.y - last_pan.y;
(TouchPhase::Moved, dx, dy) (TouchPhase::Moved, dx, dy)
} },
UIGestureRecognizerState::Ended => { UIGestureRecognizerState::Ended => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0}); let last_pan: CGPoint =
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
let dx = translation.x - last_pan.x; let dx = translation.x - last_pan.x;
let dy = translation.y - last_pan.y; let dy = translation.y - last_pan.y;
(TouchPhase::Ended, dx, dy) (TouchPhase::Ended, dx, dy)
} },
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => { UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0}); let last_pan: CGPoint =
self.ivars().pan_last_delta.replace(CGPoint { x: 0.0, y: 0.0 });
// Pass -delta so that action is reversed // Pass -delta so that action is reversed
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y) (TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
} },
state => panic!("unexpected recognizer state: {state:?}"), state => panic!("unexpected recognizer state: {state:?}"),
}; };
let gesture_event = EventWrapper::Window { let gesture_event = EventWrapper::Window {
window_id: window.id(), window_id: window.id(),
event: WindowEvent::PanGesture { event: WindowEvent::PanGesture {
@@ -315,7 +296,7 @@ declare_class!(
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
} }
#[method(canBecomeFirstResponder)] #[unsafe(method(canBecomeFirstResponder))]
fn can_become_first_responder(&self) -> bool { fn can_become_first_responder(&self) -> bool {
true true
} }
@@ -324,27 +305,30 @@ declare_class!(
unsafe impl NSObjectProtocol for WinitView {} unsafe impl NSObjectProtocol for WinitView {}
unsafe impl UIGestureRecognizerDelegate for WinitView { unsafe impl UIGestureRecognizerDelegate for WinitView {
#[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)] #[unsafe(method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:))]
fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool { fn should_recognize_simultaneously(
&self,
_gesture_recognizer: &UIGestureRecognizer,
_other_gesture_recognizer: &UIGestureRecognizer,
) -> bool {
true true
} }
} }
unsafe impl UITextInputTraits for WinitView { unsafe impl UITextInputTraits for WinitView {}
}
unsafe impl UIKeyInput for WinitView { unsafe impl UIKeyInput for WinitView {
#[method(hasText)] #[unsafe(method(hasText))]
fn has_text(&self) -> bool { fn has_text(&self) -> bool {
true true
} }
#[method(insertText:)] #[unsafe(method(insertText:))]
fn insert_text(&self, text: &NSString) { fn insert_text(&self, text: &NSString) {
self.handle_insert_text(text) self.handle_insert_text(text)
} }
#[method(deleteBackward)] #[unsafe(method(deleteBackward))]
fn delete_backward(&self) { fn delete_backward(&self) {
self.handle_delete_backward() self.handle_delete_backward()
} }
@@ -370,7 +354,7 @@ impl WinitView {
primary_finger: Cell::new(None), primary_finger: Cell::new(None),
fingers: Cell::new(0), fingers: Cell::new(0),
}); });
let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] }; let this: Retained<Self> = unsafe { msg_send![super(this), initWithFrame: frame] };
this.setMultipleTouchEnabled(true); this.setMultipleTouchEnabled(true);
@@ -382,8 +366,8 @@ impl WinitView {
} }
fn window(&self) -> Option<Retained<WinitUIWindow>> { fn window(&self) -> Option<Retained<WinitUIWindow>> {
// SAFETY: `WinitView`s are always installed in a `WinitUIWindow` // `WinitView`s should always be installed in a `WinitUIWindow`
(**self).window().map(|window| unsafe { Retained::cast(window) }) (**self).window().map(|window| window.downcast().unwrap())
} }
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) { pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
@@ -478,13 +462,12 @@ impl WinitView {
fn handle_touches(&self, touches: &NSSet<UITouch>) { fn handle_touches(&self, touches: &NSSet<UITouch>) {
let window = self.window().unwrap(); let window = self.window().unwrap();
let mut touch_events = Vec::new(); let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches { for touch in touches {
let logical_location = touch.locationInView(None); let logical_location = touch.locationInView(None);
let touch_type = touch.r#type(); let touch_type = touch.r#type();
let force = if let UITouchType::Pencil = touch_type { let force = if let UITouchType::Pencil = touch_type {
None None
} else if os_supports_force { } else if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
let trait_collection = self.traitCollection(); let trait_collection = self.traitCollection();
let touch_capability = trait_collection.forceTouchCapability(); let touch_capability = trait_collection.forceTouchCapability();
// Both the OS _and_ the device need to be checked for force touch support. // Both the OS _and_ the device need to be checked for force touch support.
@@ -501,7 +484,7 @@ impl WinitView {
} else { } else {
None None
}; };
let touch_id = touch as *const UITouch as usize; let touch_id = Retained::as_ptr(&touch) as usize;
let phase = touch.phase(); let phase = touch.phase();
let position = { let position = {
let scale_factor = self.contentScaleFactor(); let scale_factor = self.contentScaleFactor();

View File

@@ -1,14 +1,13 @@
use std::cell::Cell; use std::cell::Cell;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2::{available, define_class, msg_send, DefinedClass, MainThreadMarker};
use objc2_foundation::{MainThreadMarker, NSObject}; use objc2_foundation::NSObject;
use objc2_ui_kit::{ use objc2_ui_kit::{
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle, UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
UIUserInterfaceIdiom, UIView, UIViewController, UIUserInterfaceIdiom, UIView, UIViewController,
}; };
use super::app_state::{self};
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::window::WindowAttributes; use crate::window::WindowAttributes;
@@ -20,51 +19,42 @@ pub struct ViewControllerState {
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>, preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
} }
declare_class!( define_class!(
#[unsafe(super(UIViewController, UIResponder, NSObject))]
#[name = "WinitUIViewController"]
#[ivars = ViewControllerState]
pub(crate) struct WinitViewController; pub(crate) struct WinitViewController;
unsafe impl ClassType for WinitViewController { /// This documentation attribute makes rustfmt work for some reason?
#[inherits(UIResponder, NSObject)] impl WinitViewController {
type Super = UIViewController; #[unsafe(method(shouldAutorotate))]
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIViewController";
}
impl DeclaredClass for WinitViewController {
type Ivars = ViewControllerState;
}
unsafe impl WinitViewController {
#[method(shouldAutorotate)]
fn should_autorotate(&self) -> bool { fn should_autorotate(&self) -> bool {
true true
} }
#[method(prefersStatusBarHidden)] #[unsafe(method(prefersStatusBarHidden))]
fn prefers_status_bar_hidden(&self) -> bool { fn prefers_status_bar_hidden(&self) -> bool {
self.ivars().prefers_status_bar_hidden.get() self.ivars().prefers_status_bar_hidden.get()
} }
#[method(preferredStatusBarStyle)] #[unsafe(method(preferredStatusBarStyle))]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle { fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.ivars().preferred_status_bar_style.get() self.ivars().preferred_status_bar_style.get()
} }
#[method(prefersHomeIndicatorAutoHidden)] #[unsafe(method(prefersHomeIndicatorAutoHidden))]
fn prefers_home_indicator_auto_hidden(&self) -> bool { fn prefers_home_indicator_auto_hidden(&self) -> bool {
self.ivars().prefers_home_indicator_auto_hidden.get() self.ivars().prefers_home_indicator_auto_hidden.get()
} }
#[method(supportedInterfaceOrientations)] #[unsafe(method(supportedInterfaceOrientations))]
fn supported_orientations(&self) -> UIInterfaceOrientationMask { fn supported_orientations(&self) -> UIInterfaceOrientationMask {
self.ivars().supported_orientations.get() self.ivars().supported_orientations.get()
} }
#[method(preferredScreenEdgesDeferringSystemGestures)] #[unsafe(method(preferredScreenEdgesDeferringSystemGestures))]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
self.ivars() self.ivars().preferred_screen_edges_deferring_system_gestures.get()
.preferred_screen_edges_deferring_system_gestures
.get()
} }
} }
); );
@@ -87,11 +77,13 @@ impl WinitViewController {
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) { pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.ivars().prefers_home_indicator_auto_hidden.set(val); self.ivars().prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities(); if available!(ios = 11.0, visionos = 1.0) {
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden(); self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else { } else {
os_capabilities.home_indicator_hidden_err_msg("ignoring") tracing::warn!(
"`setNeedsUpdateOfHomeIndicatorAutoHidden` requires iOS 11.0+ or visionOS. \
Ignoring"
);
} }
} }
@@ -101,11 +93,13 @@ impl WinitViewController {
UIRectEdge(val.bits().into()) UIRectEdge(val.bits().into())
}; };
self.ivars().preferred_screen_edges_deferring_system_gestures.set(val); self.ivars().preferred_screen_edges_deferring_system_gestures.set(val);
let os_capabilities = app_state::os_capabilities(); if available!(ios = 11.0, visionos = 1.0) {
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures(); self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else { } else {
os_capabilities.defer_system_gestures_err_msg("ignoring") tracing::warn!(
"`setNeedsUpdateOfScreenEdgesDeferringSystemGestures` requires iOS 11.0+ or \
visionOS. Ignoring"
);
} }
} }
@@ -146,7 +140,7 @@ impl WinitViewController {
supported_orientations: Cell::new(UIInterfaceOrientationMask::All), supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()), preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()),
}); });
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] }; let this: Retained<Self> = unsafe { msg_send![super(this), init] };
this.set_prefers_status_bar_hidden( this.set_prefers_status_bar_hidden(
window_attributes.platform_specific.prefers_status_bar_hidden, window_attributes.platform_specific.prefers_status_bar_hidden,

View File

@@ -2,11 +2,11 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use dispatch2::MainThreadBound;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2::{available, class, define_class, msg_send, MainThreadMarker};
use objc2_foundation::{ use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize};
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObject, NSObjectProtocol, use objc2_foundation::{NSObject, NSObjectProtocol};
};
use objc2_ui_kit::{ use objc2_ui_kit::{
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen, UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
UIScreenOverscanCompensation, UIViewController, UIWindow, UIScreenOverscanCompensation, UIViewController, UIWindow,
@@ -32,43 +32,31 @@ use crate::window::{
WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowAttributes, WindowButtons, WindowId, WindowLevel,
}; };
declare_class!( define_class!(
#[unsafe(super(UIWindow, UIResponder, NSObject))]
#[name = "WinitUIWindow"]
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow; pub(crate) struct WinitUIWindow;
unsafe impl ClassType for WinitUIWindow { /// This documentation attribute makes rustfmt work for some reason?
#[inherits(UIResponder, NSObject)] impl WinitUIWindow {
type Super = UIWindow; #[unsafe(method(becomeKeyWindow))]
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIWindow";
}
impl DeclaredClass for WinitUIWindow {}
unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)]
fn become_key_window(&self) { fn become_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event( app_state::handle_nonuser_event(mtm, EventWrapper::Window {
mtm, window_id: self.id(),
EventWrapper::Window { event: WindowEvent::Focused(true),
window_id: self.id(), });
event: WindowEvent::Focused(true),
},
);
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
} }
#[method(resignKeyWindow)] #[unsafe(method(resignKeyWindow))]
fn resign_key_window(&self) { fn resign_key_window(&self) {
let mtm = MainThreadMarker::new().unwrap(); let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event( app_state::handle_nonuser_event(mtm, EventWrapper::Window {
mtm, window_id: self.id(),
EventWrapper::Window { event: WindowEvent::Focused(false),
window_id: self.id(), });
event: WindowEvent::Focused(false),
},
);
let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
} }
} }
@@ -86,7 +74,7 @@ impl WinitUIWindow {
// into very confusing issues with the window not being properly activated. // into very confusing issues with the window not being properly activated.
// //
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events. // Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] }; let this: Retained<Self> = unsafe { msg_send![mtm.alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller)); this.setRootViewController(Some(view_controller));
@@ -208,8 +196,7 @@ impl Inner {
} }
pub fn safe_area(&self) -> PhysicalInsets<u32> { pub fn safe_area(&self) -> PhysicalInsets<u32> {
// Only available on iOS 11.0 let insets = if available!(ios = 11.0, tvos = 11.0, visionos = 1.0) {
let insets = if app_state::os_capabilities().safe_area {
self.view.safeAreaInsets() self.view.safeAreaInsets()
} else { } else {
// Assume the status bar frame is the only thing that obscures the view // Assume the status bar frame is the only thing that obscures the view

View File

@@ -27,7 +27,10 @@ use sctk::seat::pointer::{
use sctk::seat::SeatState; use sctk::seat::SeatState;
use crate::dpi::{LogicalPosition, PhysicalPosition}; use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, PointerSource, PointerKind, TouchPhase, WindowEvent}; use crate::event::{
ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase,
WindowEvent,
};
use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, WindowId}; use crate::platform_impl::wayland::{self, WindowId};