2019-06-18 09:34:27 +03:00
|
|
|
use std::{
|
2020-01-19 10:47:55 -08:00
|
|
|
ops::Deref,
|
2019-06-18 09:34:27 +03:00
|
|
|
sync::{Mutex, Weak},
|
|
|
|
|
};
|
2019-05-01 17:03:30 -06:00
|
|
|
|
|
|
|
|
use cocoa::{
|
2019-06-18 09:34:27 +03:00
|
|
|
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
|
2019-05-01 17:03:30 -06:00
|
|
|
base::{id, nil},
|
2020-01-19 10:47:55 -08:00
|
|
|
foundation::{NSPoint, NSSize, NSString},
|
2019-05-01 17:03:30 -06:00
|
|
|
};
|
2020-01-19 10:47:55 -08:00
|
|
|
use dispatch::Queue;
|
2022-09-02 15:48:02 +02:00
|
|
|
use objc::foundation::is_main_thread;
|
2020-01-19 10:47:55 -08:00
|
|
|
use objc::rc::autoreleasepool;
|
2022-09-02 15:48:02 +02:00
|
|
|
use objc::runtime::Bool;
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2019-06-21 11:33:15 -04:00
|
|
|
use crate::{
|
|
|
|
|
dpi::LogicalSize,
|
2022-01-23 21:35:26 +01:00
|
|
|
platform_impl::platform::{
|
|
|
|
|
ffi,
|
|
|
|
|
util::IdRef,
|
|
|
|
|
window::{SharedState, SharedStateMutexGuard},
|
|
|
|
|
},
|
2019-06-21 11:33:15 -04:00
|
|
|
};
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2020-01-19 10:47:55 -08:00
|
|
|
// Unsafe wrapper type that allows us to dispatch things that aren't Send.
|
|
|
|
|
// This should *only* be used to dispatch to the main queue.
|
|
|
|
|
// While it is indeed not guaranteed that these types can safely be sent to
|
|
|
|
|
// other threads, we know that they're safe to use on the main thread.
|
|
|
|
|
struct MainThreadSafe<T>(T);
|
|
|
|
|
|
|
|
|
|
unsafe impl<T> Send for MainThreadSafe<T> {}
|
|
|
|
|
|
|
|
|
|
impl<T> Deref for MainThreadSafe<T> {
|
|
|
|
|
type Target = T;
|
|
|
|
|
fn deref(&self) -> &T {
|
|
|
|
|
&self.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-11 01:09:38 +02:00
|
|
|
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
|
|
|
|
ns_window.setStyleMask_(mask);
|
2019-05-01 17:03:30 -06:00
|
|
|
// If we don't do this, key handling will break
|
|
|
|
|
// (at least until the window is clicked again/etc.)
|
2019-06-11 01:09:38 +02:00
|
|
|
ns_window.makeFirstResponder_(ns_view);
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Always use this function instead of trying to modify `styleMask` directly!
|
|
|
|
|
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
|
|
|
|
|
// Otherwise, this would vomit out errors about not being on the main thread
|
|
|
|
|
// and fail to do anything.
|
2019-06-11 01:09:38 +02:00
|
|
|
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
let ns_view = MainThreadSafe(ns_view);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
set_style_mask(*ns_window, *ns_view, mask);
|
|
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
2019-06-11 01:09:38 +02:00
|
|
|
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
2022-09-02 15:48:02 +02:00
|
|
|
if is_main_thread() {
|
2020-01-19 10:47:55 -08:00
|
|
|
set_style_mask(ns_window, ns_view, mask);
|
2019-12-20 10:03:41 +09:00
|
|
|
} else {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
let ns_view = MainThreadSafe(ns_view);
|
|
|
|
|
Queue::main().exec_sync(move || {
|
|
|
|
|
set_style_mask(*ns_window, *ns_view, mask);
|
|
|
|
|
})
|
2019-12-20 10:03:41 +09:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors
|
|
|
|
|
// and just fails silently. Anyway, GCD to the rescue!
|
2020-01-04 01:33:07 -05:00
|
|
|
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat));
|
|
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
|
|
|
|
|
// to log errors.
|
2019-06-11 01:09:38 +02:00
|
|
|
pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
ns_window.setFrameTopLeftPoint_(point);
|
|
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
|
2019-06-11 01:09:38 +02:00
|
|
|
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
ns_window.setLevel_(level as _);
|
|
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
2022-04-12 19:10:46 +02:00
|
|
|
// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently.
|
|
|
|
|
pub unsafe fn set_ignore_mouse_events(ns_window: id, ignore: bool) {
|
|
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
Queue::main().exec_async(move || {
|
2022-09-02 15:48:02 +02:00
|
|
|
ns_window.setIgnoresMouseEvents_(Bool::from(ignore).as_raw());
|
2022-04-12 19:10:46 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-01 17:03:30 -06:00
|
|
|
// `toggleFullScreen` is thread-safe, but our additional logic to account for
|
|
|
|
|
// window styles isn't.
|
|
|
|
|
pub unsafe fn toggle_full_screen_async(
|
2019-06-11 01:09:38 +02:00
|
|
|
ns_window: id,
|
|
|
|
|
ns_view: id,
|
2019-05-01 17:03:30 -06:00
|
|
|
not_fullscreen: bool,
|
|
|
|
|
shared_state: Weak<Mutex<SharedState>>,
|
|
|
|
|
) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
let ns_view = MainThreadSafe(ns_view);
|
|
|
|
|
let shared_state = MainThreadSafe(shared_state);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
|
|
|
|
// set a normal style temporarily. The previous state will be
|
|
|
|
|
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
|
|
|
|
if not_fullscreen {
|
|
|
|
|
let curr_mask = ns_window.styleMask();
|
|
|
|
|
let required =
|
|
|
|
|
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
|
|
|
|
|
if !curr_mask.contains(required) {
|
|
|
|
|
set_style_mask(*ns_window, *ns_view, required);
|
|
|
|
|
if let Some(shared_state) = shared_state.upgrade() {
|
2022-01-23 21:35:26 +01:00
|
|
|
let mut shared_state_lock = SharedStateMutexGuard::new(
|
|
|
|
|
shared_state.lock().unwrap(),
|
|
|
|
|
"toggle_full_screen_callback",
|
|
|
|
|
);
|
2020-01-19 10:47:55 -08:00
|
|
|
(*shared_state_lock).saved_style = Some(curr_mask);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Window level must be restored from `CGShieldingWindowLevel()
|
|
|
|
|
// + 1` back to normal in order for `toggleFullScreen` to do
|
|
|
|
|
// anything
|
|
|
|
|
ns_window.setLevel_(0);
|
|
|
|
|
ns_window.toggleFullScreen_(nil);
|
|
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
2019-09-16 02:59:37 +03:00
|
|
|
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
|
2020-01-19 10:47:55 -08:00
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
ffi::CGRestorePermanentDisplayConfiguration();
|
|
|
|
|
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
|
|
|
|
|
});
|
2019-06-18 09:34:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// `setMaximized` is not thread-safe
|
|
|
|
|
pub unsafe fn set_maximized_async(
|
|
|
|
|
ns_window: id,
|
|
|
|
|
is_zoomed: bool,
|
|
|
|
|
maximized: bool,
|
|
|
|
|
shared_state: Weak<Mutex<SharedState>>,
|
|
|
|
|
) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
let shared_state = MainThreadSafe(shared_state);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
if let Some(shared_state) = shared_state.upgrade() {
|
2022-01-23 21:35:26 +01:00
|
|
|
let mut shared_state_lock =
|
|
|
|
|
SharedStateMutexGuard::new(shared_state.lock().unwrap(), "set_maximized");
|
2020-01-19 10:47:55 -08:00
|
|
|
|
|
|
|
|
// Save the standard frame sized if it is not zoomed
|
|
|
|
|
if !is_zoomed {
|
|
|
|
|
shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window));
|
|
|
|
|
}
|
2019-06-18 09:34:27 +03:00
|
|
|
|
2020-01-19 10:47:55 -08:00
|
|
|
shared_state_lock.maximized = maximized;
|
|
|
|
|
|
|
|
|
|
if shared_state_lock.fullscreen.is_some() {
|
|
|
|
|
// Handle it in window_did_exit_fullscreen
|
|
|
|
|
return;
|
2022-06-10 13:43:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ns_window
|
|
|
|
|
.styleMask()
|
|
|
|
|
.contains(NSWindowStyleMask::NSResizableWindowMask)
|
|
|
|
|
{
|
2020-01-19 10:47:55 -08:00
|
|
|
// Just use the native zoom if resizable
|
|
|
|
|
ns_window.zoom_(nil);
|
|
|
|
|
} else {
|
|
|
|
|
// if it's not resizable, we set the frame directly
|
|
|
|
|
let new_rect = if maximized {
|
|
|
|
|
let screen = NSScreen::mainScreen(nil);
|
|
|
|
|
NSScreen::visibleFrame(screen)
|
|
|
|
|
} else {
|
|
|
|
|
shared_state_lock.saved_standard_frame()
|
|
|
|
|
};
|
2022-09-02 15:48:02 +02:00
|
|
|
ns_window.setFrame_display_(new_rect, Bool::NO.as_raw());
|
2020-01-19 10:47:55 -08:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
2020-01-19 10:47:55 -08:00
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
2020-01-19 10:47:55 -08:00
|
|
|
|
2019-05-01 17:03:30 -06:00
|
|
|
// `orderOut:` isn't thread-safe. Calling it from another thread actually works,
|
|
|
|
|
// but with an odd delay.
|
2019-06-11 01:09:38 +02:00
|
|
|
pub unsafe fn order_out_async(ns_window: id) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
ns_window.orderOut_(nil);
|
|
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
|
|
|
|
|
// actually works, but with an odd delay.
|
2019-06-11 01:09:38 +02:00
|
|
|
pub unsafe fn make_key_and_order_front_async(ns_window: id) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
ns_window.makeKeyAndOrderFront_(nil);
|
|
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
|
|
|
|
|
2019-06-18 09:34:27 +03:00
|
|
|
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
|
|
|
|
|
// window drag regions, which throws an exception when not done in the main
|
|
|
|
|
// thread
|
|
|
|
|
pub unsafe fn set_title_async(ns_window: id, title: String) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
Queue::main().exec_async(move || {
|
|
|
|
|
let title = IdRef::new(NSString::alloc(nil).init_str(&title));
|
|
|
|
|
ns_window.setTitle_(*title);
|
|
|
|
|
});
|
2019-06-18 09:34:27 +03:00
|
|
|
}
|
|
|
|
|
|
2019-05-01 17:03:30 -06:00
|
|
|
// `close:` is thread-safe, but we want the event to be triggered from the main
|
|
|
|
|
// thread. Though, it's a good idea to look into that more...
|
2021-03-06 11:17:23 +01:00
|
|
|
//
|
|
|
|
|
// ArturKovacs: It's important that this operation keeps the underlying window alive
|
|
|
|
|
// through the `IdRef` because otherwise it would dereference free'd memory
|
|
|
|
|
pub unsafe fn close_async(ns_window: IdRef) {
|
2020-01-19 10:47:55 -08:00
|
|
|
let ns_window = MainThreadSafe(ns_window);
|
|
|
|
|
Queue::main().exec_async(move || {
|
2022-09-02 15:48:02 +02:00
|
|
|
autoreleasepool(move |_| {
|
2020-01-19 10:47:55 -08:00
|
|
|
ns_window.close();
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|