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:
parent
f5dcd2aabe
commit
953d9b4268
25 changed files with 787 additions and 1103 deletions
|
|
@ -2,30 +2,23 @@
|
|||
|
||||
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_foundation::{MainThreadMarker, NSObject};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
use super::app_state::AppState;
|
||||
use crate::event::{DeviceEvent, ElementState};
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(NSApplication, NSResponder, NSObject))]
|
||||
#[name = "WinitApplication"]
|
||||
pub(super) struct WinitApplication;
|
||||
|
||||
unsafe impl ClassType for WinitApplication {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSApplication;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplication";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitApplication {}
|
||||
|
||||
unsafe impl WinitApplication {
|
||||
impl WinitApplication {
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
|
||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
||||
#[method(sendEvent:)]
|
||||
#[unsafe(method(sendEvent:))]
|
||||
fn send_event(&self, event: &NSEvent) {
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
|
|
@ -33,7 +26,7 @@ declare_class!(
|
|||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventType::KeyUp
|
||||
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
|
||||
&& modifier_flags.contains(NSEventModifierFlags::Command)
|
||||
{
|
||||
if let Some(key_window) = self.keyWindow() {
|
||||
key_window.sendEvent(event);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ use std::sync::atomic::Ordering as AtomicOrdering;
|
|||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
|
||||
use objc2_foundation::{MainThreadMarker, NSNotification};
|
||||
use objc2_foundation::NSNotification;
|
||||
|
||||
use super::super::event_handler::EventHandler;
|
||||
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.
|
||||
}
|
||||
|
||||
// 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 of the
|
||||
// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the
|
||||
// main thread.
|
||||
static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
|
||||
StaticMainThreadBound(OnceCell::new());
|
||||
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> =
|
||||
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
|
||||
|
||||
impl AppState {
|
||||
pub(super) fn setup_global(
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ use std::sync::OnceLock;
|
|||
|
||||
use objc2::rc::Retained;
|
||||
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_foundation::{
|
||||
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
|
||||
NSString,
|
||||
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
|
||||
};
|
||||
|
||||
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>> {
|
||||
let cls = NSCursor::class();
|
||||
if msg_send![cls, respondsToSelector: sel] {
|
||||
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
|
||||
if unsafe { msg_send![cls, respondsToSelector: sel] } {
|
||||
let cursor: Retained<NSCursor> = unsafe { msg_send![cls, performSelector: sel] };
|
||||
Some(cursor)
|
||||
} else {
|
||||
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
|
||||
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
|
||||
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
msg_send_id![
|
||||
msg_send![
|
||||
<NSDictionary<NSObject, NSObject>>::class(),
|
||||
dictionaryWithContentsOfFile: &*info_path,
|
||||
]
|
||||
};
|
||||
let mut x = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
x = unsafe { &*ptr }.as_cgfloat()
|
||||
if let Some(n) = info.objectForKey(ns_string!("hotx")) {
|
||||
if let Ok(n) = n.downcast::<NSNumber>() {
|
||||
x = n.as_cgfloat();
|
||||
}
|
||||
}
|
||||
let mut y = 0.0;
|
||||
if let Some(n) = info.get(&*ns_string!("hotx")) {
|
||||
if n.is_kind_of::<NSNumber>() {
|
||||
let ptr: *const NSObject = n;
|
||||
let ptr: *const NSNumber = ptr.cast();
|
||||
y = unsafe { &*ptr }.as_cgfloat()
|
||||
if let Some(n) = info.objectForKey(ns_string!("hoty")) {
|
||||
if let Ok(n) = n.downcast::<NSNumber>() {
|
||||
y = n.as_cgfloat();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +183,7 @@ pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
|
|||
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
|
||||
match icon {
|
||||
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::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
|
||||
CursorIcon::Help => _helpCursor(),
|
||||
CursorIcon::ZoomIn if available!(macos = 15.0) => unsafe { NSCursor::zoomInCursor() },
|
||||
CursorIcon::ZoomIn => _zoomInCursor(),
|
||||
CursorIcon::ZoomOut if available!(macos = 15.0) => unsafe { NSCursor::zoomOutCursor() },
|
||||
CursorIcon::ZoomOut => _zoomOutCursor(),
|
||||
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
|
||||
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use core_foundation::base::CFRelease;
|
||||
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
|
||||
use dispatch2::run_on_main;
|
||||
use objc2::rc::Retained;
|
||||
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 super::ffi;
|
||||
|
|
@ -22,29 +22,27 @@ pub struct KeyEventExtra {
|
|||
|
||||
/// Ignores ALL modifiers.
|
||||
pub fn get_modifierless_char(scancode: u16) -> Key {
|
||||
let mut string = [0; 16];
|
||||
let input_source;
|
||||
let layout;
|
||||
unsafe {
|
||||
input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if input_source.is_null() {
|
||||
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
let layout_data =
|
||||
ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData);
|
||||
if layout_data.is_null() {
|
||||
CFRelease(input_source as *mut c_void);
|
||||
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
}
|
||||
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
|
||||
}
|
||||
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else {
|
||||
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
};
|
||||
let input_source = unsafe { CFRetained::from_raw(ptr) };
|
||||
|
||||
let layout_data = unsafe {
|
||||
ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData)
|
||||
};
|
||||
let Some(layout_data) = (unsafe { layout_data.cast::<CFData>().as_ref() }) else {
|
||||
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
|
||||
return Key::Unidentified(NativeKey::MacOS(scancode));
|
||||
};
|
||||
|
||||
let layout = unsafe { CFDataGetBytePtr(layout_data).cast() };
|
||||
let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
|
||||
|
||||
let mut result_len = 0;
|
||||
let mut dead_keys = 0;
|
||||
let modifiers = 0;
|
||||
let mut string = [0; 16];
|
||||
let translate_result = unsafe {
|
||||
ffi::UCKeyTranslate(
|
||||
layout,
|
||||
|
|
@ -59,9 +57,6 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
|
|||
string.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
CFRelease(input_source as *mut c_void);
|
||||
}
|
||||
if translate_result != 0 {
|
||||
tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
|
||||
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 modifiers = unsafe { ns_event.modifierFlags() };
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
|
||||
let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::Control);
|
||||
let has_cmd = modifiers.contains(NSEventModifierFlags::Command);
|
||||
|
||||
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
|
||||
|
|
@ -308,26 +303,19 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
|||
let mut state = ModifiersState::empty();
|
||||
let mut pressed_mods = ModifiersKeys::empty();
|
||||
|
||||
state
|
||||
.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
|
||||
state.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::Shift));
|
||||
pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::CONTROL,
|
||||
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
|
||||
);
|
||||
state.set(ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::Control));
|
||||
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
|
||||
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::RALT, flags.contains(NX_DEVICERALTKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::SUPER,
|
||||
flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
|
||||
);
|
||||
state.set(ModifiersState::SUPER, flags.contains(NSEventModifierFlags::Command));
|
||||
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));
|
||||
|
||||
|
|
|
|||
|
|
@ -8,18 +8,19 @@ use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
|||
use std::sync::Arc;
|
||||
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::{msg_send_id, sel, ClassType};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{available, msg_send, ClassType, MainThreadMarker};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
|
||||
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 super::super::notification_center::create_observer;
|
||||
|
|
@ -129,7 +130,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
|
|||
fn system_theme(&self) -> Option<Theme> {
|
||||
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()))
|
||||
} else {
|
||||
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.
|
||||
//
|
||||
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||
_did_finish_launching_observer: Retained<NSObject>,
|
||||
_will_terminate_observer: Retained<NSObject>,
|
||||
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
|
|
@ -208,9 +210,9 @@ impl EventLoop {
|
|||
.expect("on macOS, `EventLoop` must be created on the main thread!");
|
||||
|
||||
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!(
|
||||
"`winit` requires control over the principal class. You must create the event \
|
||||
loop before other parts of your application initialize NSApplication"
|
||||
|
|
@ -301,8 +303,8 @@ impl EventLoop {
|
|||
self.app_state.dispatch_init_events();
|
||||
}
|
||||
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
|
||||
self.app.run();
|
||||
|
||||
// While the app is running it's possible that we catch a panic
|
||||
// to avoid unwinding across an objective-c ffi boundary, which
|
||||
|
|
@ -333,8 +335,7 @@ impl EventLoop {
|
|||
debug_assert!(!self.app_state.is_running());
|
||||
|
||||
self.app_state.set_stop_on_launch();
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
self.app.run();
|
||||
|
||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
|
||||
// has launched
|
||||
|
|
@ -366,8 +367,7 @@ impl EventLoop {
|
|||
},
|
||||
}
|
||||
self.app_state.set_stop_on_redraw(true);
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
self.app.run();
|
||||
}
|
||||
|
||||
// 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)]
|
||||
pub struct EventLoopProxy {
|
||||
pub(crate) wake_up: AtomicBool,
|
||||
source: CFRunLoopSourceRef,
|
||||
source: CFRetained<CFRunLoopSource>,
|
||||
}
|
||||
|
||||
unsafe impl Send for EventLoopProxy {}
|
||||
unsafe impl Sync for EventLoopProxy {}
|
||||
|
||||
impl Drop for EventLoopProxy {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRelease(self.source as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub(crate) fn new() -> Self {
|
||||
unsafe {
|
||||
// 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
|
||||
// process user events through the normal OS EventLoop mechanisms.
|
||||
let rl = CFRunLoopGetMain();
|
||||
let rl = CFRunLoopGetMain().unwrap();
|
||||
let mut context = CFRunLoopSourceContext {
|
||||
version: 0,
|
||||
info: ptr::null_mut(),
|
||||
|
|
@ -470,11 +462,11 @@ impl EventLoopProxy {
|
|||
hash: None,
|
||||
schedule: 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);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
let source = CFRunLoopSourceCreate(None, CFIndex::MAX - 1, &mut context).unwrap();
|
||||
CFRunLoopAddSource(&rl, Some(&source), kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(&rl);
|
||||
|
||||
EventLoopProxy { wake_up: AtomicBool::new(false), source }
|
||||
}
|
||||
|
|
@ -486,9 +478,9 @@ impl EventLoopProxyProvider for EventLoopProxy {
|
|||
self.wake_up.store(true, AtomicOrdering::Relaxed);
|
||||
unsafe {
|
||||
// Let the main thread know there's a new event.
|
||||
CFRunLoopSourceSignal(self.source);
|
||||
let rl = CFRunLoopGetMain();
|
||||
CFRunLoopWakeUp(rl);
|
||||
CFRunLoopSourceSignal(&self.source);
|
||||
let rl = CFRunLoopGetMain().unwrap();
|
||||
CFRunLoopWakeUp(&rl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,10 @@
|
|||
|
||||
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::runtime::AnyObject;
|
||||
|
||||
pub type CGDisplayFadeInterval = f32;
|
||||
pub type CGDisplayReservationInterval = f32;
|
||||
pub type CGDisplayBlendFraction = f32;
|
||||
use objc2_core_foundation::{cf_type, CFString, CFUUID};
|
||||
use objc2_core_graphics::CGDirectDisplayID;
|
||||
|
||||
pub const kCGDisplayBlendNormal: f32 = 0.0;
|
||||
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
|
||||
|
|
@ -23,22 +15,6 @@ pub const kCGDisplayBlendSolidColor: f32 = 1.0;
|
|||
pub type CGDisplayFadeReservationToken = u32;
|
||||
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 IO2BitIndexedPixels: &str = "PP";
|
||||
pub const IO4BitIndexedPixels: &str = "PPPP";
|
||||
|
|
@ -55,9 +31,6 @@ pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
|
|||
pub const IOYUV422Pixels: &str = "Y4U2V2";
|
||||
pub const IO8BitOverlayPixels: &str = "O8";
|
||||
|
||||
pub type CGWindowLevel = i32;
|
||||
pub type CGDisplayModeRef = *mut c_void;
|
||||
|
||||
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
|
||||
// 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
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID;
|
||||
}
|
||||
|
||||
#[link(name = "CoreGraphics", kind = "framework")]
|
||||
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.
|
||||
pub fn CGSMainConnectionID() -> *mut AnyObject;
|
||||
pub fn CGSSetWindowBackgroundBlurRadius(
|
||||
|
|
@ -124,50 +54,13 @@ extern "C" {
|
|||
) -> 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)]
|
||||
pub struct TISInputSource(std::ffi::c_void);
|
||||
pub type TISInputSourceRef = *mut TISInputSource;
|
||||
|
||||
cf_type!(
|
||||
#[encoding_name = "__TISInputSource"]
|
||||
unsafe impl TISInputSource {}
|
||||
);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct UCKeyboardLayout(std::ffi::c_void);
|
||||
|
|
@ -184,15 +77,15 @@ pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
|
|||
|
||||
#[link(name = "Carbon", kind = "framework")]
|
||||
extern "C" {
|
||||
pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
|
||||
pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn TISGetInputSourceProperty(
|
||||
inputSource: TISInputSourceRef,
|
||||
propertyKey: CFStringRef,
|
||||
inputSource: &TISInputSource,
|
||||
propertyKey: &CFString,
|
||||
) -> *mut c_void;
|
||||
|
||||
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
|
||||
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource;
|
||||
|
||||
pub fn LMGetKbdType() -> u8;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use objc2::rc::Retained;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::sel;
|
||||
use objc2::{sel, MainThreadMarker};
|
||||
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> {
|
||||
key: &'a NSString,
|
||||
|
|
@ -48,10 +48,7 @@ pub fn initialize(app: &NSApplication) {
|
|||
Some(sel!(hideOtherApplications:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSEventModifierFlagOption
|
||||
| NSEventModifierFlags::NSEventModifierFlagCommand,
|
||||
),
|
||||
masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,33 @@
|
|||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::ptr::NonNull;
|
||||
use std::{fmt, ptr};
|
||||
|
||||
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
|
||||
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 dispatch2::run_on_main;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::MainThreadMarker;
|
||||
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::util::cgerr;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
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 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 {
|
||||
fn new(
|
||||
monitor: MonitorHandle,
|
||||
|
|
@ -79,10 +74,9 @@ impl VideoModeHandle {
|
|||
refresh_rate_millihertz: Option<NonZeroU32>,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
let pixel_encoding = CFString::wrap_under_create_rule(
|
||||
ffi::CGDisplayModeCopyPixelEncoding(native_mode.0),
|
||||
)
|
||||
.to_string();
|
||||
#[allow(deprecated)]
|
||||
let pixel_encoding =
|
||||
CGDisplayModeCopyPixelEncoding(Some(&native_mode.0)).unwrap().to_string();
|
||||
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
|
||||
32
|
||||
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
|
||||
|
|
@ -95,8 +89,8 @@ impl VideoModeHandle {
|
|||
|
||||
let mode = VideoMode {
|
||||
size: PhysicalSize::new(
|
||||
ffi::CGDisplayModeGetPixelWidth(native_mode.0) as u32,
|
||||
ffi::CGDisplayModeGetPixelHeight(native_mode.0) as u32,
|
||||
CGDisplayModeGetPixelWidth(Some(&native_mode.0)) as u32,
|
||||
CGDisplayModeGetPixelHeight(Some(&native_mode.0)) as u32,
|
||||
),
|
||||
refresh_rate_millihertz,
|
||||
bit_depth: NonZeroU16::new(bit_depth),
|
||||
|
|
@ -110,33 +104,12 @@ impl VideoModeHandle {
|
|||
#[derive(Clone)]
|
||||
pub struct MonitorHandle(CGDirectDisplayID);
|
||||
|
||||
type MonitorUuid = [u8; 16];
|
||||
|
||||
impl MonitorHandle {
|
||||
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
|
||||
fn uuid(&self) -> MonitorUuid {
|
||||
let cf_uuid = unsafe {
|
||||
CFUUID::wrap_under_create_rule(ffi::CGDisplayCreateUUIDFromDisplayID(self.0))
|
||||
};
|
||||
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,
|
||||
])
|
||||
fn uuid(&self) -> [u8; 16] {
|
||||
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
|
||||
let cf_uuid = unsafe { CFRetained::from_raw(NonNull::new(ptr).unwrap()) };
|
||||
unsafe { CFUUIDGetUUIDBytes(&cf_uuid) }.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -170,19 +143,32 @@ impl std::hash::Hash for MonitorHandle {
|
|||
}
|
||||
|
||||
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for display in displays {
|
||||
monitors.push_back(MonitorHandle(display));
|
||||
}
|
||||
monitors
|
||||
} else {
|
||||
VecDeque::with_capacity(0)
|
||||
let mut expected_count = 0;
|
||||
let res = cgerr(unsafe { CGGetActiveDisplayList(0, ptr::null_mut(), &mut expected_count) });
|
||||
if res.is_err() {
|
||||
return 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 {
|
||||
MonitorHandle(CGDisplay::main().id)
|
||||
MonitorHandle(unsafe { CGMainDisplayID() })
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
|
|
@ -204,8 +190,7 @@ impl MonitorHandle {
|
|||
// TODO: Be smarter about this:
|
||||
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
|
||||
pub fn name(&self) -> Option<String> {
|
||||
let MonitorHandle(display_id) = *self;
|
||||
let screen_num = CGDisplay::new(display_id).model_number();
|
||||
let screen_num = unsafe { CGDisplayModelNumber(self.0) };
|
||||
Some(format!("Monitor #{screen_num}"))
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +204,7 @@ impl MonitorHandle {
|
|||
// This is already in screen coordinates. If we were using `NSScreen`,
|
||||
// then a conversion would've been needed:
|
||||
// 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);
|
||||
Some(position.to_physical(self.scale_factor()))
|
||||
}
|
||||
|
|
@ -235,12 +220,12 @@ impl MonitorHandle {
|
|||
|
||||
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
||||
let current_display_mode =
|
||||
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
|
||||
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) }.unwrap());
|
||||
refresh_rate_millihertz(self.0, ¤t_display_mode)
|
||||
}
|
||||
|
||||
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);
|
||||
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
|
||||
}
|
||||
|
|
@ -255,22 +240,20 @@ impl MonitorHandle {
|
|||
|
||||
unsafe {
|
||||
let modes = {
|
||||
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
|
||||
assert!(!array.is_null(), "failed to get list of display modes");
|
||||
let array_count = CFArrayGetCount(array);
|
||||
let array = CGDisplayCopyAllDisplayModes(self.0, None)
|
||||
.expect("failed to get list of display modes");
|
||||
let array_count = CFArrayGetCount(&array);
|
||||
let modes: Vec<_> = (0..array_count)
|
||||
.map(move |i| {
|
||||
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
|
||||
ffi::CGDisplayModeRetain(mode);
|
||||
mode
|
||||
let mode = CFArrayGetValueAtIndex(&array, i) as *mut CGDisplayMode;
|
||||
CFRetained::from_raw(NonNull::new(mode).unwrap())
|
||||
})
|
||||
.collect();
|
||||
CFRelease(array as *const _);
|
||||
modes
|
||||
};
|
||||
|
||||
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
|
||||
// isn't a CRT
|
||||
|
|
@ -307,15 +290,14 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
|
|||
|
||||
// Retrieve the CGDirectDisplayID associated with this screen
|
||||
//
|
||||
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for `deviceDescription` for details:
|
||||
// The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for details:
|
||||
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
|
||||
let obj = device_description
|
||||
.get(key)
|
||||
.expect("failed getting screen display id from device description");
|
||||
let obj: *const AnyObject = obj;
|
||||
let obj: *const NSNumber = obj.cast();
|
||||
let obj: &NSNumber = unsafe { &*obj };
|
||||
.objectForKey(key)
|
||||
.expect("failed getting screen display id from device description")
|
||||
.downcast::<NSNumber>()
|
||||
.expect("NSScreenNumber must be NSNumber");
|
||||
|
||||
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
|
||||
// `NSScreen::mainScreen`), because that's what the screen coordinates
|
||||
// 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;
|
||||
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> {
|
||||
unsafe {
|
||||
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0);
|
||||
let refresh_rate = CGDisplayModeGetRefreshRate(Some(&mode.0));
|
||||
if refresh_rate > 0.0 {
|
||||
return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
|
||||
ffi::CVDisplayLinkRelease(display_link);
|
||||
let display_link = CFRetained::from_raw(NonNull::new(display_link).unwrap());
|
||||
#[allow(deprecated)]
|
||||
let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(&display_link);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
(time.time_scale as i64)
|
||||
.checked_div(time.time_value)
|
||||
(time.timeScale as i64)
|
||||
.checked_div(time.timeValue)
|
||||
.map(|v| (v * 1000) as u32)
|
||||
.and_then(NonZeroU32::new)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,18 @@ use std::ptr;
|
|||
use std::rc::Weak;
|
||||
use std::time::Instant;
|
||||
|
||||
use block2::Block;
|
||||
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
|
||||
use core_foundation::date::CFAbsoluteTimeGetCurrent;
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
|
||||
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
|
||||
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
|
||||
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained,
|
||||
CFRunLoop, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
|
||||
CFRunLoopObserver, CFRunLoopObserverCallBack, CFRunLoopObserverContext,
|
||||
CFRunLoopObserverCreate, CFRunLoopPerformBlock, CFRunLoopTimer, CFRunLoopTimerCreate,
|
||||
CFRunLoopTimerInvalidate, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
use tracing::error;
|
||||
|
||||
use super::app_state::AppState;
|
||||
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)
|
||||
where
|
||||
|
|
@ -48,8 +44,8 @@ where
|
|||
}
|
||||
|
||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
||||
extern "C" fn control_flow_begin_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
panic_info: *mut c_void,
|
||||
) {
|
||||
|
|
@ -57,7 +53,7 @@ extern "C" fn control_flow_begin_handler(
|
|||
control_flow_handler(panic_info, |panic_info| {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopAfterWaiting => {
|
||||
CFRunLoopActivity::AfterWaiting => {
|
||||
// trace!("Triggered `CFRunLoopAfterWaiting`");
|
||||
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
|
||||
// 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
|
||||
// without that, LoopExiting would get sent after AboutToWait
|
||||
extern "C" fn control_flow_end_handler(
|
||||
_: CFRunLoopObserverRef,
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
panic_info: *mut c_void,
|
||||
) {
|
||||
|
|
@ -79,12 +75,12 @@ extern "C" fn control_flow_end_handler(
|
|||
control_flow_handler(panic_info, |panic_info| {
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
kCFRunLoopBeforeWaiting => {
|
||||
CFRunLoopActivity::BeforeWaiting => {
|
||||
// trace!("Triggered `CFRunLoopBeforeWaiting`");
|
||||
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
|
||||
// trace!("Completed `CFRunLoopBeforeWaiting`");
|
||||
},
|
||||
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
|
||||
CFRunLoopActivity::Exit => (), /* unimplemented!(), // not expected to ever happen */
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
|
@ -92,44 +88,32 @@ extern "C" fn control_flow_end_handler(
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RunLoop(CFRunLoopRef);
|
||||
|
||||
impl Default for RunLoop {
|
||||
fn default() -> Self {
|
||||
Self(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
pub struct RunLoop(CFRetained<CFRunLoop>);
|
||||
|
||||
impl RunLoop {
|
||||
pub fn main(mtm: MainThreadMarker) -> Self {
|
||||
// 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.
|
||||
let _ = mtm;
|
||||
RunLoop(unsafe { CFRunLoopGetMain() })
|
||||
RunLoop(unsafe { CFRunLoopGetMain() }.unwrap())
|
||||
}
|
||||
|
||||
pub fn wakeup(&self) {
|
||||
unsafe { CFRunLoopWakeUp(self.0) }
|
||||
unsafe { CFRunLoopWakeUp(&self.0) }
|
||||
}
|
||||
|
||||
unsafe fn add_observer(
|
||||
&self,
|
||||
flags: CFOptionFlags,
|
||||
flags: CFRunLoopActivity,
|
||||
// The lower the value, the sooner this will run
|
||||
priority: CFIndex,
|
||||
handler: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) {
|
||||
let observer = unsafe {
|
||||
CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
flags,
|
||||
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) };
|
||||
let observer =
|
||||
unsafe { CFRunLoopObserverCreate(None, flags.0, true, priority, handler, context) }
|
||||
.unwrap();
|
||||
unsafe { CFRunLoopAddObserver(&self.0, Some(&observer), kCFRunLoopCommonModes) };
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// handling whatever event it's currently handling.
|
||||
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()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
|
|
@ -195,10 +175,10 @@ impl RunLoop {
|
|||
// and be delivered to the application afterwards.
|
||||
//
|
||||
// [#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`.
|
||||
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,
|
||||
};
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopAfterWaiting,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
Some(control_flow_begin_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
Some(control_flow_end_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
}
|
||||
|
|
@ -229,7 +209,7 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopWaker {
|
||||
timer: CFRunLoopTimerRef,
|
||||
timer: CFRetained<CFRunLoopTimer>,
|
||||
|
||||
/// 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
|
||||
|
|
@ -244,30 +224,28 @@ pub struct EventLoopWaker {
|
|||
|
||||
impl Drop for EventLoopWaker {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
CFRunLoopTimerInvalidate(self.timer);
|
||||
CFRelease(self.timer as _);
|
||||
}
|
||||
unsafe { CFRunLoopTimerInvalidate(&self.timer) };
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker {
|
||||
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 {
|
||||
// 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
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
wakeup_main_loop,
|
||||
Some(wakeup_main_loop),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
|
||||
)
|
||||
.unwrap();
|
||||
CFRunLoopAddTimer(&CFRunLoopGetMain().unwrap(), Some(&timer), kCFRunLoopCommonModes);
|
||||
Self { timer, start_instant: Instant::now(), next_fire_date: None }
|
||||
}
|
||||
}
|
||||
|
|
@ -275,14 +253,14 @@ impl EventLoopWaker {
|
|||
pub fn stop(&mut self) {
|
||||
if self.next_fire_date.is_some() {
|
||||
self.next_fire_date = None;
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(&self.timer, f64::MAX) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if 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 fsecs = 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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use objc2_core_graphics::CGError;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::error::OsError;
|
||||
|
||||
macro_rules! trace_scope {
|
||||
($s:literal) => {
|
||||
let _crate =
|
||||
|
|
@ -26,3 +29,12 @@ impl Drop for TraceGuard {
|
|||
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:?}")))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,14 @@ use std::rc::Rc;
|
|||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{AnyObject, Sel};
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
|
||||
NSTrackingRectTag, NSView, NSWindow,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
|
||||
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
|
||||
NSSize, NSString, NSUInteger,
|
||||
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
|
||||
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||
};
|
||||
|
||||
use super::app_state::AppState;
|
||||
|
|
@ -138,28 +137,21 @@ pub struct ViewState {
|
|||
option_as_alt: Cell<OptionAsAlt>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(NSView, NSResponder, NSObject))]
|
||||
#[ivars = ViewState]
|
||||
#[name = "WinitView"]
|
||||
pub(super) struct WinitView;
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSView;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitView";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitView {
|
||||
type Ivars = ViewState;
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(isFlipped)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitView {
|
||||
#[unsafe(method(isFlipped))]
|
||||
fn is_flipped(&self) -> bool {
|
||||
// `winit` uses the upper-left corner as the origin.
|
||||
true
|
||||
}
|
||||
|
||||
#[method(viewDidMoveToWindow)]
|
||||
#[unsafe(method(viewDidMoveToWindow))]
|
||||
fn view_did_move_to_window(&self) {
|
||||
trace_scope!("viewDidMoveToWindow");
|
||||
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`.
|
||||
#[method(viewFrameDidChangeNotification:)]
|
||||
#[unsafe(method(viewFrameDidChangeNotification:))]
|
||||
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
|
||||
trace_scope!("NSViewFrameDidChangeNotification");
|
||||
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
|
||||
|
|
@ -190,14 +182,16 @@ declare_class!(
|
|||
self.ivars().tracking_rect.set(Some(tracking_rect));
|
||||
|
||||
// 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.
|
||||
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height).
|
||||
// 1. When a new window is created as a tab, the frame size may change without a window
|
||||
// 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 size = logical_size.to_physical::<u32>(self.scale_factor());
|
||||
self.queue_event(WindowEvent::SurfaceResized(size));
|
||||
}
|
||||
|
||||
#[method(drawRect:)]
|
||||
#[unsafe(method(drawRect:))]
|
||||
fn draw_rect(&self, _rect: NSRect) {
|
||||
trace_scope!("drawRect:");
|
||||
|
||||
|
|
@ -206,22 +200,27 @@ declare_class!(
|
|||
// This is a direct subclass of NSView, no need to call superclass' drawRect:
|
||||
}
|
||||
|
||||
#[method(acceptsFirstResponder)]
|
||||
#[unsafe(method(acceptsFirstResponder))]
|
||||
fn accepts_first_responder(&self) -> bool {
|
||||
trace_scope!("acceptsFirstResponder");
|
||||
true
|
||||
}
|
||||
|
||||
// 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
|
||||
// TODO: Add an API extension for using `NSTouchBar`
|
||||
#[method_id(touchBar)]
|
||||
// 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 TODO: Add an API
|
||||
// extension for using `NSTouchBar`
|
||||
#[unsafe(method_id(touchBar))]
|
||||
fn touch_bar(&self) -> Option<Retained<NSObject>> {
|
||||
trace_scope!("touchBar");
|
||||
None
|
||||
}
|
||||
|
||||
#[method(resetCursorRects)]
|
||||
#[unsafe(method(resetCursorRects))]
|
||||
fn reset_cursor_rects(&self) {
|
||||
trace_scope!("resetCursorRects");
|
||||
let bounds = self.bounds();
|
||||
|
|
@ -236,13 +235,13 @@ declare_class!(
|
|||
}
|
||||
|
||||
unsafe impl NSTextInputClient for WinitView {
|
||||
#[method(hasMarkedText)]
|
||||
#[unsafe(method(hasMarkedText))]
|
||||
fn has_marked_text(&self) -> bool {
|
||||
trace_scope!("hasMarkedText");
|
||||
self.ivars().marked_text.borrow().length() > 0
|
||||
}
|
||||
|
||||
#[method(markedRange)]
|
||||
#[unsafe(method(markedRange))]
|
||||
fn marked_range(&self) -> NSRange {
|
||||
trace_scope!("markedRange");
|
||||
let length = self.ivars().marked_text.borrow().length();
|
||||
|
|
@ -254,14 +253,14 @@ declare_class!(
|
|||
}
|
||||
}
|
||||
|
||||
#[method(selectedRange)]
|
||||
#[unsafe(method(selectedRange))]
|
||||
fn selected_range(&self) -> NSRange {
|
||||
trace_scope!("selectedRange");
|
||||
// Documented to return `{NSNotFound, 0}` if there is no selection.
|
||||
NSRange::new(NSNotFound as NSUInteger, 0)
|
||||
}
|
||||
|
||||
#[method(setMarkedText:selectedRange:replacementRange:)]
|
||||
#[unsafe(method(setMarkedText:selectedRange:replacementRange:))]
|
||||
fn set_marked_text(
|
||||
&self,
|
||||
string: &NSObject,
|
||||
|
|
@ -271,23 +270,15 @@ declare_class!(
|
|||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
trace_scope!("setMarkedText:selectedRange:replacementRange:");
|
||||
|
||||
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSAttributedString = string.cast();
|
||||
let string = unsafe { &*string };
|
||||
(
|
||||
NSMutableAttributedString::from_attributed_nsstring(string),
|
||||
string.string(),
|
||||
)
|
||||
let (marked_text, string) = if let Some(string) =
|
||||
string.downcast_ref::<NSAttributedString>()
|
||||
{
|
||||
(NSMutableAttributedString::from_attributed_nsstring(string), string.string())
|
||||
} else if let Some(string) = string.downcast_ref::<NSString>() {
|
||||
(NSMutableAttributedString::from_nsstring(string), string.copy())
|
||||
} else {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSString = string.cast();
|
||||
let string = unsafe { &*string };
|
||||
(
|
||||
NSMutableAttributedString::from_nsstring(string),
|
||||
string.copy(),
|
||||
)
|
||||
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
panic!("unexpected text {string:?}")
|
||||
};
|
||||
|
||||
// Update marked text.
|
||||
|
|
@ -323,7 +314,7 @@ declare_class!(
|
|||
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
|
||||
}
|
||||
|
||||
#[method(unmarkText)]
|
||||
#[unsafe(method(unmarkText))]
|
||||
fn unmark_text(&self) {
|
||||
trace_scope!("unmarkText");
|
||||
*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>> {
|
||||
trace_scope!("validAttributesForMarkedText");
|
||||
NSArray::new()
|
||||
}
|
||||
|
||||
#[method_id(attributedSubstringForProposedRange:actualRange:)]
|
||||
#[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))]
|
||||
fn attributed_substring_for_proposed_range(
|
||||
&self,
|
||||
_range: NSRange,
|
||||
|
|
@ -356,42 +347,36 @@ declare_class!(
|
|||
None
|
||||
}
|
||||
|
||||
#[method(characterIndexForPoint:)]
|
||||
#[unsafe(method(characterIndexForPoint:))]
|
||||
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
|
||||
trace_scope!("characterIndexForPoint:");
|
||||
0
|
||||
}
|
||||
|
||||
#[method(firstRectForCharacterRange:actualRange:)]
|
||||
#[unsafe(method(firstRectForCharacterRange:actualRange:))]
|
||||
fn first_rect_for_character_range(
|
||||
&self,
|
||||
_range: NSRange,
|
||||
_actual_range: *mut NSRange,
|
||||
) -> NSRect {
|
||||
trace_scope!("firstRectForCharacterRange:actualRange:");
|
||||
let rect = NSRect::new(
|
||||
self.ivars().ime_position.get(),
|
||||
self.ivars().ime_size.get()
|
||||
);
|
||||
let rect = NSRect::new(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
|
||||
self.window()
|
||||
.convertRectToScreen(self.convertRect_toView(rect, None))
|
||||
self.window().convertRectToScreen(self.convertRect_toView(rect, None))
|
||||
}
|
||||
|
||||
#[method(insertText:replacementRange:)]
|
||||
#[unsafe(method(insertText:replacementRange:))]
|
||||
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
trace_scope!("insertText:replacementRange:");
|
||||
|
||||
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
let string = if string.is_kind_of::<NSAttributedString>() {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSAttributedString = string.cast();
|
||||
unsafe { &*string }.string().to_string()
|
||||
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
|
||||
string.string().to_string()
|
||||
} else if let Some(string) = string.downcast_ref::<NSString>() {
|
||||
string.to_string()
|
||||
} else {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSString = string.cast();
|
||||
unsafe { &*string }.to_string()
|
||||
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
panic!("unexpected text {string:?}")
|
||||
};
|
||||
|
||||
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
|
||||
// readable" character happens, i.e. newlines, tabs, and Ctrl+C.
|
||||
#[method(doCommandBySelector:)]
|
||||
// Basically, we're sent this message whenever a keyboard event that doesn't generate a
|
||||
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
|
||||
#[unsafe(method(doCommandBySelector:))]
|
||||
fn do_command_by_selector(&self, command: Sel) {
|
||||
trace_scope!("doCommandBySelector:");
|
||||
|
||||
// We shouldn't forward any character from just committed text, since we'll end up sending
|
||||
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
|
||||
// which is not desired given it was used to confirm IME input.
|
||||
// We shouldn't forward any character from just committed text, since we'll end up
|
||||
// sending it twice with some IMEs like Korean one. We'll also always send
|
||||
// `Enter` in that case, which is not desired given it was used to confirm
|
||||
// IME input.
|
||||
if self.ivars().ime_state.get() == ImeState::Committed {
|
||||
return;
|
||||
}
|
||||
|
|
@ -429,7 +415,11 @@ declare_class!(
|
|||
let window_id = window_id(&self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
if let Some(handler) = app.macos_handler() {
|
||||
handler.standard_key_binding(event_loop, window_id, command.name());
|
||||
handler.standard_key_binding(
|
||||
event_loop,
|
||||
window_id,
|
||||
command.name().to_str().unwrap(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -439,8 +429,9 @@ declare_class!(
|
|||
}
|
||||
}
|
||||
|
||||
unsafe impl WinitView {
|
||||
#[method(keyDown:)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitView {
|
||||
#[unsafe(method(keyDown:))]
|
||||
fn key_down(&self, event: &NSEvent) {
|
||||
trace_scope!("keyDown:");
|
||||
{
|
||||
|
|
@ -483,7 +474,7 @@ declare_class!(
|
|||
// Allow normal input after the commit.
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
true
|
||||
}
|
||||
},
|
||||
ImeState::Preedit => true,
|
||||
// `key_down` could result in preedit clear, so compare old and current state.
|
||||
_ => 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) {
|
||||
trace_scope!("keyUp:");
|
||||
|
||||
|
|
@ -507,10 +498,7 @@ declare_class!(
|
|||
self.update_modifiers(&event, false);
|
||||
|
||||
// We want to send keyboard input when we are currently in the ground state.
|
||||
if matches!(
|
||||
self.ivars().ime_state.get(),
|
||||
ImeState::Ground | ImeState::Disabled
|
||||
) {
|
||||
if matches!(self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled) {
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: None,
|
||||
event: create_key_event(&event, false, false),
|
||||
|
|
@ -519,14 +507,14 @@ declare_class!(
|
|||
}
|
||||
}
|
||||
|
||||
#[method(flagsChanged:)]
|
||||
#[unsafe(method(flagsChanged:))]
|
||||
fn flags_changed(&self, event: &NSEvent) {
|
||||
trace_scope!("flagsChanged:");
|
||||
|
||||
self.update_modifiers(event, true);
|
||||
}
|
||||
|
||||
#[method(insertTab:)]
|
||||
#[unsafe(method(insertTab:))]
|
||||
fn insert_tab(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("insertTab:");
|
||||
let window = self.window();
|
||||
|
|
@ -537,7 +525,7 @@ declare_class!(
|
|||
}
|
||||
}
|
||||
|
||||
#[method(insertBackTab:)]
|
||||
#[unsafe(method(insertBackTab:))]
|
||||
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("insertBackTab:");
|
||||
let window = self.window();
|
||||
|
|
@ -550,7 +538,7 @@ declare_class!(
|
|||
|
||||
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
|
||||
#[method(cancelOperation:)]
|
||||
#[unsafe(method(cancelOperation:))]
|
||||
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
trace_scope!("cancelOperation:");
|
||||
|
|
@ -580,42 +568,42 @@ declare_class!(
|
|||
//
|
||||
// See https://github.com/rust-windowing/winit/pull/1490 for history.
|
||||
|
||||
#[method(mouseDown:)]
|
||||
#[unsafe(method(mouseDown:))]
|
||||
fn mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(mouseUp:)]
|
||||
#[unsafe(method(mouseUp:))]
|
||||
fn mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
#[method(rightMouseDown:)]
|
||||
#[unsafe(method(rightMouseDown:))]
|
||||
fn right_mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("rightMouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(rightMouseUp:)]
|
||||
#[unsafe(method(rightMouseUp:))]
|
||||
fn right_mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("rightMouseUp:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Released);
|
||||
}
|
||||
|
||||
#[method(otherMouseDown:)]
|
||||
#[unsafe(method(otherMouseDown:))]
|
||||
fn other_mouse_down(&self, event: &NSEvent) {
|
||||
trace_scope!("otherMouseDown:");
|
||||
self.mouse_motion(event);
|
||||
self.mouse_click(event, ElementState::Pressed);
|
||||
}
|
||||
|
||||
#[method(otherMouseUp:)]
|
||||
#[unsafe(method(otherMouseUp:))]
|
||||
fn other_mouse_up(&self, event: &NSEvent) {
|
||||
trace_scope!("otherMouseUp:");
|
||||
self.mouse_motion(event);
|
||||
|
|
@ -624,27 +612,27 @@ declare_class!(
|
|||
|
||||
// No tracing on these because that would be overly verbose
|
||||
|
||||
#[method(mouseMoved:)]
|
||||
#[unsafe(method(mouseMoved:))]
|
||||
fn mouse_moved(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(mouseDragged:)]
|
||||
#[unsafe(method(mouseDragged:))]
|
||||
fn mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(rightMouseDragged:)]
|
||||
#[unsafe(method(rightMouseDragged:))]
|
||||
fn right_mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(otherMouseDragged:)]
|
||||
#[unsafe(method(otherMouseDragged:))]
|
||||
fn other_mouse_dragged(&self, event: &NSEvent) {
|
||||
self.mouse_motion(event);
|
||||
}
|
||||
|
||||
#[method(mouseEntered:)]
|
||||
#[unsafe(method(mouseEntered:))]
|
||||
fn mouse_entered(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseEntered:");
|
||||
|
||||
|
|
@ -658,7 +646,7 @@ declare_class!(
|
|||
});
|
||||
}
|
||||
|
||||
#[method(mouseExited:)]
|
||||
#[unsafe(method(mouseExited:))]
|
||||
fn mouse_exited(&self, event: &NSEvent) {
|
||||
trace_scope!("mouseExited:");
|
||||
|
||||
|
|
@ -672,7 +660,7 @@ declare_class!(
|
|||
});
|
||||
}
|
||||
|
||||
#[method(scrollWheel:)]
|
||||
#[unsafe(method(scrollWheel:))]
|
||||
fn scroll_wheel(&self, event: &NSEvent) {
|
||||
trace_scope!("scrollWheel:");
|
||||
|
||||
|
|
@ -689,9 +677,9 @@ declare_class!(
|
|||
};
|
||||
|
||||
// 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
|
||||
// phase is recorded (or rather, the started/ended cases of the momentum phase) then we
|
||||
// report the touch phase.
|
||||
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no
|
||||
// momentum phase is recorded (or rather, the started/ended cases of the
|
||||
// momentum phase) then we report the touch phase.
|
||||
#[allow(non_upper_case_globals)]
|
||||
let phase = match unsafe { event.momentumPhase() } {
|
||||
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
|
||||
|
|
@ -705,17 +693,13 @@ declare_class!(
|
|||
|
||||
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 })
|
||||
);
|
||||
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) {
|
||||
trace_scope!("magnifyWithEvent:");
|
||||
|
||||
|
|
@ -737,18 +721,16 @@ declare_class!(
|
|||
});
|
||||
}
|
||||
|
||||
#[method(smartMagnifyWithEvent:)]
|
||||
#[unsafe(method(smartMagnifyWithEvent:))]
|
||||
fn smart_magnify_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("smartMagnifyWithEvent:");
|
||||
|
||||
self.mouse_motion(event);
|
||||
|
||||
self.queue_event(WindowEvent::DoubleTapGesture {
|
||||
device_id: None,
|
||||
});
|
||||
self.queue_event(WindowEvent::DoubleTapGesture { device_id: None });
|
||||
}
|
||||
|
||||
#[method(rotateWithEvent:)]
|
||||
#[unsafe(method(rotateWithEvent:))]
|
||||
fn rotate_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("rotateWithEvent:");
|
||||
|
||||
|
|
@ -770,7 +752,7 @@ declare_class!(
|
|||
});
|
||||
}
|
||||
|
||||
#[method(pressureChangeWithEvent:)]
|
||||
#[unsafe(method(pressureChangeWithEvent:))]
|
||||
fn pressure_change_with_event(&self, event: &NSEvent) {
|
||||
trace_scope!("pressureChangeWithEvent:");
|
||||
|
||||
|
|
@ -784,13 +766,13 @@ declare_class!(
|
|||
// Allows us to receive Ctrl-Tab and Ctrl-Esc.
|
||||
// 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
|
||||
#[method(_wantsKeyDownForEvent:)]
|
||||
#[unsafe(method(_wantsKeyDownForEvent:))]
|
||||
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
|
||||
trace_scope!("_wantsKeyDownForEvent:");
|
||||
true
|
||||
}
|
||||
|
||||
#[method(acceptsFirstMouse:)]
|
||||
#[unsafe(method(acceptsFirstMouse:))]
|
||||
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
|
||||
trace_scope!("acceptsFirstMouse:");
|
||||
self.ivars().accepts_first_mouse
|
||||
|
|
@ -821,7 +803,7 @@ impl WinitView {
|
|||
accepts_first_mouse,
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use dispatch2::MainThreadBound;
|
||||
use dpi::{Position, Size};
|
||||
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_foundation::{MainThreadBound, MainThreadMarker, NSObject};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
use super::event_loop::ActiveEventLoop;
|
||||
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)]
|
||||
pub struct WinitWindow;
|
||||
|
||||
unsafe impl ClassType for WinitWindow {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSWindow;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitWindow";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitWindow {}
|
||||
|
||||
unsafe impl WinitWindow {
|
||||
#[method(canBecomeMainWindow)]
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitWindow {
|
||||
#[unsafe(method(canBecomeMainWindow))]
|
||||
fn can_become_main_window(&self) -> bool {
|
||||
trace_scope!("canBecomeMainWindow");
|
||||
true
|
||||
}
|
||||
|
||||
#[method(canBecomeKeyWindow)]
|
||||
#[unsafe(method(canBecomeKeyWindow))]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
|
|
@ -360,23 +355,17 @@ declare_class!(
|
|||
}
|
||||
);
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))]
|
||||
#[name = "WinitPanel"]
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPanel;
|
||||
|
||||
unsafe impl ClassType for WinitPanel {
|
||||
#[inherits(NSWindow, NSResponder, NSObject)]
|
||||
type Super = NSPanel;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitPanel";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitPanel {}
|
||||
|
||||
unsafe impl WinitPanel {
|
||||
/// This documentation attribute makes rustfmt work for some reason?
|
||||
impl WinitPanel {
|
||||
// although NSPanel can become key window
|
||||
// it doesn't if window doesn't have NSWindowStyleMask::Titled
|
||||
#[method(canBecomeKeyWindow)]
|
||||
#[unsafe(method(canBecomeKeyWindow))]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ use std::ptr;
|
|||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use core_graphics::display::CGDisplay;
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
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::{
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
|
||||
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||
|
|
@ -19,11 +21,17 @@ use objc2_app_kit::{
|
|||
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
|
||||
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::{
|
||||
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
|
||||
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
|
||||
NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming,
|
||||
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
|
||||
ns_string, NSArray, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey,
|
||||
NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSNotificationCenter, NSObject,
|
||||
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
|
||||
NSRect, NSSize, NSString,
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
|
|
@ -31,6 +39,7 @@ use super::app_state::AppState;
|
|||
use super::cursor::cursor_from_icon;
|
||||
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
|
||||
use super::observer::RunLoop;
|
||||
use super::util::cgerr;
|
||||
use super::view::WinitView;
|
||||
use super::window::{window_id, WinitPanel, WinitWindow};
|
||||
use super::{ffi, Fullscreen, MonitorHandle};
|
||||
|
|
@ -133,30 +142,24 @@ pub(crate) struct State {
|
|||
is_borderless_game: Cell<bool>,
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
define_class!(
|
||||
#[unsafe(super(NSObject))]
|
||||
#[thread_kind = MainThreadOnly]
|
||||
#[name = "WinitWindowDelegate"]
|
||||
#[ivars = State]
|
||||
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 NSWindowDelegate for WindowDelegate {
|
||||
#[method(windowShouldClose:)]
|
||||
#[unsafe(method(windowShouldClose:))]
|
||||
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
|
||||
trace_scope!("windowShouldClose:");
|
||||
self.queue_event(WindowEvent::CloseRequested);
|
||||
false
|
||||
}
|
||||
|
||||
#[method(windowWillClose:)]
|
||||
#[unsafe(method(windowWillClose:))]
|
||||
fn window_will_close(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillClose:");
|
||||
// `setDelegate:` retains the previous value and then autoreleases it
|
||||
|
|
@ -168,14 +171,14 @@ declare_class!(
|
|||
self.queue_event(WindowEvent::Destroyed);
|
||||
}
|
||||
|
||||
#[method(windowDidResize:)]
|
||||
#[unsafe(method(windowDidResize:))]
|
||||
fn window_did_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidResize:");
|
||||
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[method(windowWillStartLiveResize:)]
|
||||
#[unsafe(method(windowWillStartLiveResize:))]
|
||||
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillStartLiveResize:");
|
||||
|
||||
|
|
@ -183,20 +186,20 @@ declare_class!(
|
|||
self.set_resize_increments_inner(increments);
|
||||
}
|
||||
|
||||
#[method(windowDidEndLiveResize:)]
|
||||
#[unsafe(method(windowDidEndLiveResize:))]
|
||||
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEndLiveResize:");
|
||||
self.set_resize_increments_inner(NSSize::new(1., 1.));
|
||||
}
|
||||
|
||||
// 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>) {
|
||||
trace_scope!("windowDidMove:");
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[method(windowDidChangeBackingProperties:)]
|
||||
#[unsafe(method(windowDidChangeBackingProperties:))]
|
||||
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeBackingProperties:");
|
||||
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>) {
|
||||
trace_scope!("windowDidBecomeKey:");
|
||||
// TODO: center the cursor if the window had mouse grab when it
|
||||
|
|
@ -220,7 +223,7 @@ declare_class!(
|
|||
self.queue_event(WindowEvent::Focused(true));
|
||||
}
|
||||
|
||||
#[method(windowDidResignKey:)]
|
||||
#[unsafe(method(windowDidResignKey:))]
|
||||
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidResignKey:");
|
||||
// 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
|
||||
#[method(windowWillEnterFullScreen:)]
|
||||
#[unsafe(method(windowWillEnterFullScreen:))]
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillEnterFullScreen:");
|
||||
|
||||
|
|
@ -246,7 +249,7 @@ declare_class!(
|
|||
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||
// can't enter exclusive mode by other means (like the
|
||||
// fullscreen button on the window decorations)
|
||||
Some(Fullscreen::Exclusive(_, _)) => (),
|
||||
Some(Fullscreen::Exclusive(..)) => (),
|
||||
// `window_will_enter_fullscreen` was triggered and we're already
|
||||
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||
// as it updates the state
|
||||
|
|
@ -262,14 +265,14 @@ declare_class!(
|
|||
}
|
||||
|
||||
/// Invoked when before exit fullscreen
|
||||
#[method(windowWillExitFullScreen:)]
|
||||
#[unsafe(method(windowWillExitFullScreen:))]
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
self.ivars().in_fullscreen_transition.set(true);
|
||||
}
|
||||
|
||||
#[method(window:willUseFullScreenPresentationOptions:)]
|
||||
#[unsafe(method(window:willUseFullScreenPresentationOptions:))]
|
||||
fn window_will_use_fullscreen_presentation_options(
|
||||
&self,
|
||||
_: Option<&AnyObject>,
|
||||
|
|
@ -286,17 +289,17 @@ declare_class!(
|
|||
// user-provided options are ignored in exclusive fullscreen.
|
||||
let mut options = proposed_options;
|
||||
let fullscreen = self.ivars().fullscreen.borrow();
|
||||
if let Some(Fullscreen::Exclusive(_, _)) = &*fullscreen {
|
||||
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
if let Some(Fullscreen::Exclusive(..)) = &*fullscreen {
|
||||
options = NSApplicationPresentationOptions::FullScreen
|
||||
| NSApplicationPresentationOptions::HideDock
|
||||
| NSApplicationPresentationOptions::HideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
#[method(windowDidEnterFullScreen:)]
|
||||
#[unsafe(method(windowDidEnterFullScreen:))]
|
||||
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidEnterFullScreen:");
|
||||
self.ivars().initial_fullscreen.set(false);
|
||||
|
|
@ -307,7 +310,7 @@ declare_class!(
|
|||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
#[method(windowDidExitFullScreen:)]
|
||||
#[unsafe(method(windowDidExitFullScreen:))]
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidExitFullScreen:");
|
||||
|
||||
|
|
@ -334,7 +337,7 @@ declare_class!(
|
|||
/// 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
|
||||
/// 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>) {
|
||||
trace_scope!("windowDidFailToEnterFullScreen:");
|
||||
self.ivars().in_fullscreen_transition.set(false);
|
||||
|
|
@ -353,14 +356,14 @@ declare_class!(
|
|||
}
|
||||
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[method(windowDidChangeOcclusionState:)]
|
||||
#[unsafe(method(windowDidChangeOcclusionState:))]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
|
||||
self.queue_event(WindowEvent::Occluded(!visible));
|
||||
}
|
||||
|
||||
#[method(windowDidChangeScreen:)]
|
||||
#[unsafe(method(windowDidChangeScreen:))]
|
||||
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeScreen:");
|
||||
let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get();
|
||||
|
|
@ -374,44 +377,49 @@ declare_class!(
|
|||
|
||||
unsafe impl NSDraggingDestination for WindowDelegate {
|
||||
/// Invoked when the dragged image enters destination bounds or frame
|
||||
#[method(draggingEntered:)]
|
||||
#[unsafe(method(draggingEntered:))]
|
||||
fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
|
||||
trace_scope!("draggingEntered:");
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb = unsafe { sender.draggingPasteboard() };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
||||
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap()
|
||||
.downcast::<NSArray>()
|
||||
.unwrap();
|
||||
let paths = filenames
|
||||
.into_iter()
|
||||
.map(|file| PathBuf::from(file.to_string()))
|
||||
.map(|file| PathBuf::from(file.downcast::<NSString>().unwrap().to_string()))
|
||||
.collect();
|
||||
|
||||
let dl = unsafe { sender.draggingLocation() };
|
||||
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 });
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[method(wantsPeriodicDraggingUpdates)]
|
||||
#[unsafe(method(wantsPeriodicDraggingUpdates))]
|
||||
fn wants_periodic_dragging_updates(&self) -> bool {
|
||||
trace_scope!("wantsPeriodicDraggingUpdates:");
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked periodically as the image is held within the destination area, allowing modification of the dragging operation or mouse-pointer position.
|
||||
#[method(draggingUpdated:)]
|
||||
/// Invoked periodically as the image is held within the destination area, allowing
|
||||
/// modification of the dragging operation or mouse-pointer position.
|
||||
#[unsafe(method(draggingUpdated:))]
|
||||
fn dragging_updated(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
|
||||
trace_scope!("draggingUpdated:");
|
||||
|
||||
let dl = unsafe { sender.draggingLocation() };
|
||||
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 });
|
||||
|
||||
|
|
@ -419,30 +427,34 @@ declare_class!(
|
|||
}
|
||||
|
||||
/// Invoked when the image is released
|
||||
#[method(prepareForDragOperation:)]
|
||||
#[unsafe(method(prepareForDragOperation:))]
|
||||
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool {
|
||||
trace_scope!("prepareForDragOperation:");
|
||||
true
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
trace_scope!("performDragOperation:");
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb = unsafe { sender.draggingPasteboard() };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
||||
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap()
|
||||
.downcast::<NSArray>()
|
||||
.unwrap();
|
||||
let paths = filenames
|
||||
.into_iter()
|
||||
.map(|file| PathBuf::from(file.to_string()))
|
||||
.map(|file| PathBuf::from(file.downcast::<NSString>().unwrap().to_string()))
|
||||
.collect();
|
||||
|
||||
let dl = unsafe { sender.draggingLocation() };
|
||||
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 });
|
||||
|
||||
|
|
@ -450,29 +462,29 @@ declare_class!(
|
|||
}
|
||||
|
||||
/// Invoked when the dragging operation is complete
|
||||
#[method(concludeDragOperation:)]
|
||||
#[unsafe(method(concludeDragOperation:))]
|
||||
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
|
||||
trace_scope!("concludeDragOperation:");
|
||||
}
|
||||
|
||||
/// Invoked when the dragging operation is cancelled
|
||||
#[method(draggingExited:)]
|
||||
fn dragging_exited(&self, info: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
|
||||
#[unsafe(method(draggingExited:))]
|
||||
fn dragging_exited(&self, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
|
||||
trace_scope!("draggingExited:");
|
||||
|
||||
let position = info.map(|info| {
|
||||
let dl = unsafe { info.draggingLocation() };
|
||||
let position = sender.map(|sender| {
|
||||
let dl = unsafe { sender.draggingLocation() };
|
||||
let dl = self.view().convertPoint_fromView(dl, None);
|
||||
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
|
||||
unsafe impl WindowDelegate {
|
||||
#[method(observeValueForKeyPath:ofObject:change:context:)]
|
||||
/// Key-Value Observing
|
||||
impl WindowDelegate {
|
||||
#[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
|
||||
fn observe_value(
|
||||
&self,
|
||||
key_path: Option<&NSString>,
|
||||
|
|
@ -488,19 +500,15 @@ declare_class!(
|
|||
"requested a change dictionary in `addObserver`, but none was provided",
|
||||
);
|
||||
let old = change
|
||||
.get(unsafe { NSKeyValueChangeOldKey })
|
||||
.objectForKey(unsafe { NSKeyValueChangeOldKey })
|
||||
.expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
|
||||
let new = change
|
||||
.get(unsafe { NSKeyValueChangeNewKey })
|
||||
.objectForKey(unsafe { NSKeyValueChangeNewKey })
|
||||
.expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
|
||||
|
||||
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
|
||||
let old: *const AnyObject = old;
|
||||
let old: *const NSAppearance = old.cast();
|
||||
let old: &NSAppearance = unsafe { &*old };
|
||||
let new: *const AnyObject = new;
|
||||
let new: *const NSAppearance = new.cast();
|
||||
let new: &NSAppearance = unsafe { &*new };
|
||||
// The value of `effectiveAppearance` is `NSAppearance`
|
||||
let old = old.downcast::<NSAppearance>().unwrap();
|
||||
let new = new.downcast::<NSAppearance>().unwrap();
|
||||
|
||||
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
|
||||
|
||||
|
|
@ -511,8 +519,8 @@ declare_class!(
|
|||
return;
|
||||
}
|
||||
|
||||
let old = appearance_to_theme(old);
|
||||
let new = appearance_to_theme(new);
|
||||
let old = appearance_to_theme(&old);
|
||||
let new = appearance_to_theme(&new);
|
||||
// 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
|
||||
// in those cases).
|
||||
|
|
@ -627,11 +635,11 @@ fn new_window(
|
|||
masks |= NSWindowStyleMask::NonactivatingPanel;
|
||||
|
||||
let window: Option<Retained<WinitPanel>> = unsafe {
|
||||
msg_send_id![
|
||||
msg_send![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
backing: NSBackingStoreType::Buffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
|
|
@ -639,11 +647,11 @@ fn new_window(
|
|||
window?.as_super().as_super().retain()
|
||||
} else {
|
||||
let window: Option<Retained<WinitWindow>> = unsafe {
|
||||
msg_send_id![
|
||||
msg_send![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
backing: NSBackingStoreType::Buffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
|
|
@ -665,22 +673,22 @@ fn new_window(
|
|||
}
|
||||
|
||||
if attrs.content_protected {
|
||||
window.setSharingType(NSWindowSharingType::NSWindowSharingNone);
|
||||
window.setSharingType(NSWindowSharingType::None);
|
||||
}
|
||||
|
||||
if attrs.platform_specific.titlebar_transparent {
|
||||
window.setTitlebarAppearsTransparent(true);
|
||||
}
|
||||
if attrs.platform_specific.title_hidden {
|
||||
window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden);
|
||||
window.setTitleVisibility(NSWindowTitleVisibility::Hidden);
|
||||
}
|
||||
if attrs.platform_specific.titlebar_buttons_hidden {
|
||||
for titlebar_button in &[
|
||||
#[allow(deprecated)]
|
||||
NSWindowFullScreenButton,
|
||||
NSWindowButton::NSWindowMiniaturizeButton,
|
||||
NSWindowButton::NSWindowCloseButton,
|
||||
NSWindowButton::NSWindowZoomButton,
|
||||
NSWindowButton::MiniaturizeButton,
|
||||
NSWindowButton::CloseButton,
|
||||
NSWindowButton::ZoomButton,
|
||||
] {
|
||||
if let Some(button) = window.standardWindowButton(*titlebar_button) {
|
||||
button.setHidden(true);
|
||||
|
|
@ -700,7 +708,7 @@ fn new_window(
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -762,10 +770,7 @@ fn new_window(
|
|||
}
|
||||
|
||||
// register for drag and drop operations.
|
||||
window
|
||||
.registerForDraggedTypes(&NSArray::from_id_slice(&[
|
||||
unsafe { NSFilenamesPboardType }.copy()
|
||||
]));
|
||||
window.registerForDraggedTypes(&NSArray::from_slice(&[unsafe { NSFilenamesPboardType }]));
|
||||
|
||||
Some(window)
|
||||
})
|
||||
|
|
@ -793,9 +798,7 @@ impl WindowDelegate {
|
|||
// 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
|
||||
// right here, just after it's been created.
|
||||
unsafe {
|
||||
parent.addChildWindow_ordered(&window, NSWindowOrderingMode::NSWindowAbove)
|
||||
};
|
||||
unsafe { parent.addChildWindow_ordered(&window, NSWindowOrderingMode::Above) };
|
||||
},
|
||||
Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"),
|
||||
None => (),
|
||||
|
|
@ -836,7 +839,7 @@ impl WindowDelegate {
|
|||
saved_style: Cell::new(None),
|
||||
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)));
|
||||
|
||||
|
|
@ -847,8 +850,7 @@ impl WindowDelegate {
|
|||
window.addObserver_forKeyPath_options_context(
|
||||
&delegate,
|
||||
ns_string!("effectiveAppearance"),
|
||||
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
|
||||
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
|
||||
NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Old,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
|
@ -892,8 +894,8 @@ impl WindowDelegate {
|
|||
|
||||
#[track_caller]
|
||||
pub(super) fn view(&self) -> Retained<WinitView> {
|
||||
// SAFETY: The view inside WinitWindow is always `WinitView`
|
||||
unsafe { Retained::cast(self.window().contentView().unwrap()) }
|
||||
// The view inside WinitWindow should always be set and be `WinitView`.
|
||||
self.window().contentView().unwrap().downcast().unwrap()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
|
@ -1213,8 +1215,7 @@ impl WindowDelegate {
|
|||
// We edit the button directly instead of using `NSResizableWindowMask`,
|
||||
// since that mask also affect the resizability of the window (which is
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -1227,7 +1228,7 @@ impl WindowDelegate {
|
|||
}
|
||||
if self
|
||||
.window()
|
||||
.standardWindowButton(NSWindowButton::NSWindowZoomButton)
|
||||
.standardWindowButton(NSWindowButton::ZoomButton)
|
||||
.map(|b| b.isEnabled())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
|
|
@ -1266,8 +1267,9 @@ impl WindowDelegate {
|
|||
};
|
||||
|
||||
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
|
||||
CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor)
|
||||
.map_err(|status| os_error!(format!("CGError {status}")).into())
|
||||
cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(associate_mouse_cursor) })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -1289,14 +1291,12 @@ impl WindowDelegate {
|
|||
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
|
||||
let window_position = flip_window_screen_coordinates(content_rect);
|
||||
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,
|
||||
y: window_position.y + cursor_position.y,
|
||||
};
|
||||
CGDisplay::warp_mouse_cursor_position(point)
|
||||
.map_err(|status| os_error!(format!("CGError {status}")))?;
|
||||
CGDisplay::associate_mouse_and_mouse_cursor_position(true)
|
||||
.map_err(|status| os_error!(format!("CGError {status}")))?;
|
||||
cgerr(unsafe { CGWarpMouseCursorPosition(point) })?;
|
||||
cgerr(unsafe { CGAssociateMouseAndMouseCursorPosition(true) })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1498,10 +1498,8 @@ impl WindowDelegate {
|
|||
unsafe {
|
||||
// Fade to black (and wait for the fade to complete) to hide the
|
||||
// flicker from capturing the display and switching display mode
|
||||
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
|
||||
== ffi::kCGErrorSuccess
|
||||
{
|
||||
ffi::CGDisplayFade(
|
||||
if cgerr(CGAcquireDisplayFadeReservation(5.0, &mut fade_token)).is_ok() {
|
||||
CGDisplayFade(
|
||||
fade_token,
|
||||
0.3,
|
||||
ffi::kCGDisplayBlendNormal,
|
||||
|
|
@ -1509,11 +1507,11 @@ impl WindowDelegate {
|
|||
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 =
|
||||
|
|
@ -1523,17 +1521,13 @@ impl WindowDelegate {
|
|||
};
|
||||
|
||||
unsafe {
|
||||
let result = ffi::CGDisplaySetDisplayMode(
|
||||
display_id,
|
||||
video_mode.native_mode.0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
|
||||
cgerr(CGDisplaySetDisplayMode(display_id, Some(&video_mode.native_mode.0), None))
|
||||
.expect("failed to set video mode");
|
||||
|
||||
// After the display has been configured, fade back in
|
||||
// asynchronously
|
||||
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
|
||||
ffi::CGDisplayFade(
|
||||
CGDisplayFade(
|
||||
fade_token,
|
||||
0.6,
|
||||
ffi::kCGDisplayBlendSolidColor,
|
||||
|
|
@ -1541,9 +1535,9 @@ impl WindowDelegate {
|
|||
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
|
||||
// the menu bar remains interactable despite being hidden.
|
||||
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
|
||||
let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
let presentation_options = NSApplicationPresentationOptions::HideDock
|
||||
| NSApplicationPresentationOptions::HideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
}
|
||||
|
||||
|
|
@ -1601,20 +1595,19 @@ impl WindowDelegate {
|
|||
// delegate in `window:willUseFullScreenPresentationOptions:`.
|
||||
self.ivars().save_presentation_opts.set(Some(app.presentationOptions()));
|
||||
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
let presentation_options = NSApplicationPresentationOptions::FullScreen
|
||||
| NSApplicationPresentationOptions::HideDock
|
||||
| NSApplicationPresentationOptions::HideMenuBar;
|
||||
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);
|
||||
},
|
||||
(Some(Fullscreen::Exclusive(ref monitor, _)), Some(Fullscreen::Borderless(_))) => {
|
||||
let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or(
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
|
||||
NSApplicationPresentationOptions::FullScreen
|
||||
| NSApplicationPresentationOptions::AutoHideDock
|
||||
| NSApplicationPresentationOptions::AutoHideMenuBar,
|
||||
);
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
|
|
@ -1726,8 +1719,8 @@ impl WindowDelegate {
|
|||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let ns_request_type = request_type.map(|ty| match ty {
|
||||
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
|
||||
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest,
|
||||
UserAttentionType::Critical => NSRequestUserAttentionType::CriticalRequest,
|
||||
UserAttentionType::Informational => NSRequestUserAttentionType::InformationalRequest,
|
||||
});
|
||||
if let Some(ty) = ns_request_type {
|
||||
NSApplication::sharedApplication(mtm).requestUserAttention(ty);
|
||||
|
|
@ -1787,7 +1780,7 @@ impl WindowDelegate {
|
|||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
if app.respondsToSelector(sel!(effectiveAppearance)) {
|
||||
if available!(macos = 10.14) {
|
||||
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
|
||||
} else {
|
||||
Some(Theme::Light)
|
||||
|
|
@ -1802,9 +1795,9 @@ impl WindowDelegate {
|
|||
#[inline]
|
||||
pub fn set_content_protected(&self, protected: bool) {
|
||||
self.window().setSharingType(if protected {
|
||||
NSWindowSharingType::NSWindowSharingNone
|
||||
NSWindowSharingType::None
|
||||
} else {
|
||||
NSWindowSharingType::NSWindowSharingReadOnly
|
||||
NSWindowSharingType::ReadOnly
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1821,8 +1814,8 @@ fn restore_and_release_display(monitor: &MonitorHandle) {
|
|||
let available_monitors = monitor::available_monitors();
|
||||
if available_monitors.contains(monitor) {
|
||||
unsafe {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess);
|
||||
CGRestorePermanentDisplayConfiguration();
|
||||
cgerr(CGDisplayRelease(monitor.native_identifier())).unwrap();
|
||||
};
|
||||
} else {
|
||||
warn!(
|
||||
|
|
@ -1867,9 +1860,8 @@ impl WindowExtMacOS for WindowDelegate {
|
|||
self.ivars().is_simple_fullscreen.set(true);
|
||||
|
||||
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
|
||||
let presentation_options = NSApplicationPresentationOptions::AutoHideDock
|
||||
| NSApplicationPresentationOptions::AutoHideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
// Hide the titlebar
|
||||
|
|
@ -1950,10 +1942,14 @@ impl WindowExtMacOS for WindowDelegate {
|
|||
|
||||
#[inline]
|
||||
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(windows) = unsafe { self.window().tabbedWindows() } {
|
||||
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 {
|
||||
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
unsafe { NSAppearanceNameAqua.copy() },
|
||||
dark_appearance_name().copy(),
|
||||
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_slice(&[
|
||||
unsafe { NSAppearanceNameAqua },
|
||||
dark_appearance_name(),
|
||||
]));
|
||||
if let Some(best_match) = best_match {
|
||||
if *best_match == *dark_appearance_name() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue