diff --git a/Cargo.toml b/Cargo.toml index dc9ac5598a..65162b7608 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ log = "0.4" image = { version = "0.20.1", optional = true } serde = { version = "1", optional = true, features = ["serde_derive"] } +[dev-dependencies] +env_logger = "0.5" + [target.'cfg(target_os = "android")'.dependencies.android_glue] version = "0.2" diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs new file mode 100644 index 0000000000..2d98a8ecd7 --- /dev/null +++ b/examples/multithreaded.rs @@ -0,0 +1,114 @@ +extern crate env_logger; +extern crate winit; + +use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; + +use winit::{ + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, window::{MouseCursor, WindowBuilder}, +}; + +const WINDOW_COUNT: usize = 3; +const WINDOW_SIZE: (u32, u32) = (600, 400); + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); + for _ in 0..WINDOW_COUNT { + let window = WindowBuilder::new() + .with_dimensions(WINDOW_SIZE.into()) + .build(&event_loop) + .unwrap(); + let (tx, rx) = mpsc::channel(); + window_senders.insert(window.id(), tx); + thread::spawn(move || { + while let Ok(event) = rx.recv() { + match event { + WindowEvent::KeyboardInput { input: KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + modifiers, + .. + }, .. } => { + window.set_title(&format!("{:?}", key)); + let state = !modifiers.shift; + use self::VirtualKeyCode::*; + match key { + A => window.set_always_on_top(state), + C => window.set_cursor(match state { + true => MouseCursor::Progress, + false => MouseCursor::Default, + }), + D => window.set_decorations(!state), + F => window.set_fullscreen(match state { + true => Some(window.get_current_monitor()), + false => None, + }), + G => window.grab_cursor(state).unwrap(), + H => window.hide_cursor(state), + I => { + println!("Info:"); + println!("-> position : {:?}", window.get_position()); + println!("-> inner_position : {:?}", window.get_inner_position()); + println!("-> outer_size : {:?}", window.get_outer_size()); + println!("-> inner_size : {:?}", window.get_inner_size()); + }, + L => window.set_min_dimensions(match state { + true => Some(WINDOW_SIZE.into()), + false => None, + }), + M => window.set_maximized(state), + P => window.set_position({ + let mut position = window.get_position().unwrap(); + let sign = if state { 1.0 } else { -1.0 }; + position.x += 10.0 * sign; + position.y += 10.0 * sign; + position + }), + R => window.set_resizable(state), + S => window.set_inner_size(match state { + true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100), + false => WINDOW_SIZE, + }.into()), + W => window.set_cursor_position(( + WINDOW_SIZE.0 as i32 / 2, + WINDOW_SIZE.1 as i32 / 2, + ).into()).unwrap(), + Z => { + window.hide(); + thread::sleep(Duration::from_secs(1)); + window.show(); + }, + _ => (), + } + }, + _ => (), + } + } + }); + } + event_loop.run(move |event, _event_loop, control_flow| { + *control_flow = match !window_senders.is_empty() { + true => ControlFlow::Wait, + false => ControlFlow::Exit, + }; + match event { + Event::WindowEvent { event, window_id } => { + match event { + WindowEvent::CloseRequested + | WindowEvent::Destroyed + | WindowEvent::KeyboardInput { input: KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape), + .. }, .. } => { + window_senders.remove(&window_id); + }, + _ => if let Some(tx) = window_senders.get(&window_id) { + tx.send(event).unwrap(); + }, + } + } + _ => (), + } + }) +} diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 4ff889f471..14118d747e 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -15,7 +15,10 @@ fn main() { } event_loop.run(move |event, event_loop, control_flow| { - *control_flow = ControlFlow::Wait; + *control_flow = match !windows.is_empty() { + true => ControlFlow::Wait, + false => ControlFlow::Exit, + }; match event { Event::WindowEvent { event, window_id } => { match event { @@ -24,11 +27,6 @@ fn main() { // This drops the window, causing it to close. windows.remove(&window_id); }, - WindowEvent::Destroyed => { - if windows.is_empty() { - *control_flow = ControlFlow::Exit; - } - }, WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, virtual_keycode, .. }, .. } => { if Some(VirtualKeyCode::Escape) == virtual_keycode { windows.remove(&window_id); diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 3834a0cca3..911ce4c9f5 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -1,6 +1,7 @@ use std::{ - self, collections::VecDeque, fmt::{self, Debug, Formatter}, - hint::unreachable_unchecked, mem, sync::{Mutex, MutexGuard}, time::Instant, + collections::VecDeque, fmt::{self, Debug, Formatter}, + hint::unreachable_unchecked, mem, + sync::{atomic::{AtomicBool, Ordering}, Mutex, MutexGuard}, time::Instant, }; use cocoa::{appkit::NSApp, base::nil}; @@ -68,6 +69,7 @@ where #[derive(Default)] struct Handler { + ready: AtomicBool, control_flow: Mutex, control_flow_prev: Mutex, start_time: Mutex>, @@ -88,6 +90,18 @@ impl Handler { self.waker.lock().unwrap() } + fn is_ready(&self) -> bool { + self.ready.load(Ordering::Acquire) + } + + fn set_ready(&self) { + self.ready.store(true, Ordering::Release); + } + + fn is_control_flow_exit(&self) -> bool { + *self.control_flow.lock().unwrap() == ControlFlow::Exit + } + fn get_control_flow_and_update_prev(&self) -> ControlFlow { let control_flow = self.control_flow.lock().unwrap(); *self.control_flow_prev.lock().unwrap() = *control_flow; @@ -136,17 +150,18 @@ impl AppState { })); } - pub fn exit() -> ! { + pub fn exit() { HANDLER.handle_nonuser_event(Event::LoopDestroyed); - std::process::exit(0) } pub fn launched() { + HANDLER.set_ready(); HANDLER.waker().start(); HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init)); } pub fn wakeup() { + if !HANDLER.is_ready() { return } let start = HANDLER.get_start_time().unwrap(); let cause = match HANDLER.get_control_flow_and_update_prev() { ControlFlow::Poll => StartCause::Poll, @@ -173,29 +188,39 @@ impl AppState { } pub fn queue_event(event: Event) { + if !unsafe { msg_send![class!(NSThread), isMainThread] } { + panic!("uh-oh"); + } HANDLER.events().push_back(event); } pub fn queue_events(mut events: VecDeque>) { + if !unsafe { msg_send![class!(NSThread), isMainThread] } { + panic!("uh-ohs"); + } HANDLER.events().append(&mut events); } pub fn cleared() { - HANDLER.handle_nonuser_event(Event::EventsCleared); + if !HANDLER.is_ready() { return } + let mut will_stop = HANDLER.is_control_flow_exit(); for event in HANDLER.take_events() { HANDLER.handle_nonuser_event(event); + will_stop |= HANDLER.is_control_flow_exit(); + } + HANDLER.handle_nonuser_event(Event::EventsCleared); + will_stop |= HANDLER.is_control_flow_exit(); + if will_stop { + let _: () = unsafe { msg_send![NSApp(), stop:nil] }; + return } HANDLER.update_start_time(); match HANDLER.get_old_and_new_control_flow() { - (ControlFlow::Poll, ControlFlow::Poll) => (), - (ControlFlow::Wait, ControlFlow::Wait) => (), - (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) if old_instant == new_instant => (), + (ControlFlow::Exit, _) | (_, ControlFlow::Exit) => unreachable!(), + (old, new) if old == new => (), (_, ControlFlow::Wait) => HANDLER.waker().stop(), - (_, ControlFlow::WaitUntil(new_instant)) => HANDLER.waker().start_at(new_instant), + (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), (_, ControlFlow::Poll) => HANDLER.waker().start(), - (_, ControlFlow::Exit) => { - let _: () = unsafe { msg_send![NSApp(), stop:nil] }; - }, } } } diff --git a/src/platform_impl/macos/dispatch.rs b/src/platform_impl/macos/dispatch.rs new file mode 100644 index 0000000000..7072fb9dcd --- /dev/null +++ b/src/platform_impl/macos/dispatch.rs @@ -0,0 +1,29 @@ +#![allow(non_camel_case_types)] + +use std::os::raw::c_void; + +#[repr(C)] +pub struct dispatch_object_s { _private: [u8; 0] } + +pub type dispatch_function_t = extern fn(*mut c_void); +pub type dispatch_queue_t = *mut dispatch_object_s; + +pub fn dispatch_get_main_queue() -> dispatch_queue_t { + unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t } +} + +#[link(name = "System", kind = "dylib")] +extern { + static _dispatch_main_q: dispatch_object_s; + + pub fn dispatch_async_f( + queue: dispatch_queue_t, + context: *mut c_void, + work: Option, + ); + pub fn dispatch_sync_f( + queue: dispatch_queue_t, + context: *mut c_void, + work: Option, + ); +} diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 1ae21d9552..4ab0b8db3e 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -1,5 +1,5 @@ use std::{ - collections::VecDeque, marker::PhantomData, + collections::VecDeque, marker::PhantomData, process, }; use cocoa::{appkit::NSApp, base::{id, nil}, foundation::NSAutoreleasePool}; @@ -78,7 +78,8 @@ impl EventLoop { assert_ne!(app, nil); AppState::set_callback(callback, self.window_target); let _: () = msg_send![app, run]; - AppState::exit() + AppState::exit(); + process::exit(0) } } diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 31c9ed149e..d199ebb6a8 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -95,6 +95,7 @@ pub const kCGDesktopIconWindowLevelKey: NSInteger = 18; pub const kCGCursorWindowLevelKey: NSInteger = 19; pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; +#[derive(Debug, Clone, Copy)] pub enum NSWindowLevel { NSNormalWindowLevel = kCGBaseWindowLevelKey as _, NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _, diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 6ed4bb11ad..e8149d1529 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -3,6 +3,7 @@ mod app; mod app_delegate; mod app_state; +mod dispatch; mod event; mod event_loop; mod ffi; @@ -39,6 +40,9 @@ pub struct Window { _delegate: WindowDelegate, } +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + impl Deref for Window { type Target = UnownedWindow; #[inline] diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index 374a53c632..1ecd207c29 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -251,8 +251,8 @@ impl EventLoopWaker { unsafe { let current = CFAbsoluteTimeGetCurrent(); let duration = instant - now; - let fsecs = - duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; + let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + + duration.as_secs() as f64; CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) } } diff --git a/src/platform_impl/macos/util.rs b/src/platform_impl/macos/util.rs index 3b7bc54416..a4fc0e9dc4 100644 --- a/src/platform_impl/macos/util.rs +++ b/src/platform_impl/macos/util.rs @@ -1,15 +1,19 @@ -use std::ops::Deref; +use std::{ops::Deref, os::raw::c_void, sync::{Mutex, Weak}}; use cocoa::{ - appkit::{NSApp, NSWindowStyleMask}, + appkit::{CGFloat, NSApp, NSImage, NSWindow, NSWindowStyleMask}, base::{id, nil}, - foundation::{NSAutoreleasePool, NSRect, NSUInteger}, + foundation::{ + NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString, + NSUInteger, + }, }; use core_graphics::display::CGDisplay; use objc::runtime::{BOOL, Class, Object, Sel, YES}; pub use util::*; -use platform_impl::platform::ffi; +use dpi::LogicalSize; +use platform_impl::platform::{dispatch::*, ffi, window::SharedState}; pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange { location: ffi::NSNotFound as NSUInteger, @@ -71,15 +75,322 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) } -pub unsafe fn set_style_mask(nswindow: id, nsview: id, mask: NSWindowStyleMask) { - trace!("`set_style_mask` {:?} {:?} {:?}", nswindow, nsview, mask); - use cocoa::appkit::NSWindow; +unsafe fn set_style_mask(nswindow: id, nsview: id, mask: NSWindowStyleMask) { nswindow.setStyleMask_(mask); - // If we don't do this, key handling will break (at least until the window - // is clicked again/etc.); therefore, never call `setStyleMask` directly! + // If we don't do this, key handling will break + // (at least until the window is clicked again/etc.) nswindow.makeFirstResponder_(nsview); } +struct SetStyleMaskData { + nswindow: id, + nsview: id, + mask: NSWindowStyleMask, +} +impl SetStyleMaskData { + fn new_ptr( + nswindow: id, + nsview: id, + mask: NSWindowStyleMask, + ) -> *mut Self { + Box::into_raw(Box::new(SetStyleMaskData { nswindow, nsview, mask })) + } +} +extern fn set_style_mask_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetStyleMaskData; + { + let context = &*context_ptr; + set_style_mask(context.nswindow, context.nsview, context.mask); + } + Box::from_raw(context_ptr); + } +} +// 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. +pub unsafe fn set_style_mask_async(nswindow: id, nsview: id, mask: NSWindowStyleMask) { + let context = SetStyleMaskData::new_ptr(nswindow, nsview, mask); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + Some(set_style_mask_callback), + ); +} +pub unsafe fn set_style_mask_sync(nswindow: id, nsview: id, mask: NSWindowStyleMask) { + let context = SetStyleMaskData::new_ptr(nswindow, nsview, mask); + dispatch_sync_f( + dispatch_get_main_queue(), + context as *mut _, + Some(set_style_mask_callback), + ); +} + +struct SetContentSizeData { + nswindow: id, + size: LogicalSize, +} +impl SetContentSizeData { + fn new_ptr( + nswindow: id, + size: LogicalSize, + ) -> *mut Self { + Box::into_raw(Box::new(SetContentSizeData { nswindow, size })) + } +} +extern fn set_content_size_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetContentSizeData; + { + let context = &*context_ptr; + NSWindow::setContentSize_( + context.nswindow, + NSSize::new( + context.size.width as CGFloat, + context.size.height as CGFloat, + ), + ); + } + Box::from_raw(context_ptr); + } +} +// `setContentSize:` isn't thread-safe either, though it doesn't log any errors +// and just fails silently. Anyway, GCD to the rescue! +pub unsafe fn set_content_size_async(nswindow: id, size: LogicalSize) { + let context = SetContentSizeData::new_ptr(nswindow, size); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + Some(set_content_size_callback), + ); +} + +struct SetFrameTopLeftPointData { + nswindow: id, + point: NSPoint, +} +impl SetFrameTopLeftPointData { + fn new_ptr( + nswindow: id, + point: NSPoint, + ) -> *mut Self { + Box::into_raw(Box::new(SetFrameTopLeftPointData { nswindow, point })) + } +} +extern fn set_frame_top_left_point_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetFrameTopLeftPointData; + { + let context = &*context_ptr; + NSWindow::setFrameTopLeftPoint_(context.nswindow, context.point); + } + Box::from_raw(context_ptr); + } +} +// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy +// to log errors. +pub unsafe fn set_frame_top_left_point_async(nswindow: id, point: NSPoint) { + let context = SetFrameTopLeftPointData::new_ptr(nswindow, point); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + Some(set_frame_top_left_point_callback), + ); +} + +struct SetLevelData { + nswindow: id, + level: ffi::NSWindowLevel, +} +impl SetLevelData { + fn new_ptr( + nswindow: id, + level: ffi::NSWindowLevel, + ) -> *mut Self { + Box::into_raw(Box::new(SetLevelData { nswindow, level })) + } +} +extern fn set_level_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetLevelData; + { + let context = &*context_ptr; + context.nswindow.setLevel_(context.level as _); + } + Box::from_raw(context_ptr); + } +} +// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. +pub unsafe fn set_level_async(nswindow: id, level: ffi::NSWindowLevel) { + let context = SetLevelData::new_ptr(nswindow, level); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + Some(set_level_callback), + ); +} + +struct ToggleFullScreenData { + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, +} +impl ToggleFullScreenData { + fn new_ptr( + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, + ) -> *mut Self { + Box::into_raw(Box::new(ToggleFullScreenData { + nswindow, + nsview, + not_fullscreen, + shared_state, + })) + } +} +extern fn toggle_full_screen_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut ToggleFullScreenData; + { + let context = &*context_ptr; + + // `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 context.not_fullscreen { + let curr_mask = context.nswindow.styleMask(); + let required = NSWindowStyleMask::NSTitledWindowMask + | NSWindowStyleMask::NSResizableWindowMask; + if !curr_mask.contains(required) { + set_style_mask(context.nswindow, context.nsview, required); + if let Some(shared_state) = context.shared_state.upgrade() { + trace!("Locked shared state in `toggle_full_screen_callback`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + (*shared_state_lock).saved_style = Some(curr_mask); + trace!("Unlocked shared state in `toggle_full_screen_callback`"); + } + } + } + + context.nswindow.toggleFullScreen_(nil); + } + Box::from_raw(context_ptr); + } +} +// `toggleFullScreen` is thread-safe, but our additional logic to account for +// window styles isn't. +pub unsafe fn toggle_full_screen_async( + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, +) { + let context = ToggleFullScreenData::new_ptr( + nswindow, + nsview, + not_fullscreen, + shared_state, + ); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + Some(toggle_full_screen_callback), + ); +} + +struct OrderOutData { + nswindow: id, +} +impl OrderOutData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(OrderOutData { nswindow })) + } +} +extern fn order_out_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut OrderOutData; + { + let context = &*context_ptr; + context.nswindow.orderOut_(nil); + } + Box::from_raw(context_ptr); + } +} +// `orderOut:` isn't thread-safe. Calling it from another thread actually works, +// but with an odd delay. +pub unsafe fn order_out_async(nswindow: id) { + let context = OrderOutData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + Some(order_out_callback), + ); +} + +struct MakeKeyAndOrderFrontData { + nswindow: id, +} +impl MakeKeyAndOrderFrontData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(MakeKeyAndOrderFrontData { nswindow })) + } +} +extern fn make_key_and_order_front_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut MakeKeyAndOrderFrontData; + { + let context = &*context_ptr; + context.nswindow.makeKeyAndOrderFront_(nil); + } + Box::from_raw(context_ptr); + } +} +// `makeKeyAndOrderFront::` isn't thread-safe. Calling it from another thread +// actually works, but with an odd delay. +pub unsafe fn make_key_and_order_front_async(nswindow: id) { + let context = MakeKeyAndOrderFrontData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + Some(make_key_and_order_front_callback), + ); +} + +struct CloseData { + nswindow: id, +} +impl CloseData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(CloseData { nswindow })) + } +} +extern fn close_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut CloseData; + { + let context = &*context_ptr; + let pool = NSAutoreleasePool::new(nil); + context.nswindow.close(); + pool.drain(); + } + Box::from_raw(context_ptr); + } +} +// `makeKeyAndOrderFront::` isn't thread-safe. Calling it from another thread +// actually works, but with an odd delay. +pub unsafe fn close_async(nswindow: id) { + let context = CloseData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + Some(close_callback), + ); +} + pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class { let superclass: id = msg_send![this, superclass]; &*(superclass as *const _) @@ -91,6 +402,45 @@ pub unsafe fn create_input_context(view: id) -> IdRef { IdRef::new(input_context) } +// Note that loading `busybutclickable` with this code won't animate the frames; +// instead you'll just get them all in a column. +pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { + static CURSOR_ROOT: &'static str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors"; + let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT); + let cursor_name = NSString::alloc(nil).init_str(cursor_name); + let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf"); + let cursor_plist = NSString::alloc(nil).init_str("info.plist"); + let key_x = NSString::alloc(nil).init_str("hotx"); + let key_y = NSString::alloc(nil).init_str("hoty"); + + let cursor_path: id = msg_send![cursor_root, + stringByAppendingPathComponent:cursor_name + ]; + let pdf_path: id = msg_send![cursor_path, + stringByAppendingPathComponent:cursor_pdf + ]; + let info_path: id = msg_send![cursor_path, + stringByAppendingPathComponent:cursor_plist + ]; + + let image = NSImage::alloc(nil).initByReferencingFile_(pdf_path); + let info = NSDictionary::dictionaryWithContentsOfFile_( + nil, + info_path, + ); + let x = info.valueForKey_(key_x); + let y = info.valueForKey_(key_y); + let point = NSPoint::new( + msg_send![x, doubleValue], + msg_send![y, doubleValue], + ); + let cursor: id = msg_send![class!(NSCursor), alloc]; + msg_send![cursor, + initWithImage:image + hotSpot:point + ] +} + #[allow(dead_code)] pub unsafe fn open_emoji_picker() { let _: () = msg_send![NSApp(), orderFrontCharacterPalette:nil]; diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 516b54f5f8..67cc4f110b 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -263,6 +263,7 @@ extern fn view_did_move_to_window(this: &Object, _sel: Sel) { assumeInside:NO ]; } + trace!("Completed `viewDidMoveToWindow`"); } extern fn accepts_first_responder(_this: &Object, _sel: Sel) -> BOOL { @@ -270,18 +271,20 @@ extern fn accepts_first_responder(_this: &Object, _sel: Sel) -> BOOL { } extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { - trace!("Triggered `hasMarkedText`"); unsafe { + trace!("Triggered `hasMarkedText`"); let marked_text: id = *this.get_ivar("markedText"); + trace!("Completed `hasMarkedText`"); (marked_text.length() > 0) as i8 } } extern fn marked_range(this: &Object, _sel: Sel) -> NSRange { - trace!("Triggered `markedRange`"); unsafe { + trace!("Triggered `markedRange`"); let marked_text: id = *this.get_ivar("markedText"); let length = marked_text.length(); + trace!("Completed `markedRange`"); if length > 0 { NSRange::new(0, length - 1) } else { @@ -292,6 +295,7 @@ extern fn marked_range(this: &Object, _sel: Sel) -> NSRange { extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange { trace!("Triggered `selectedRange`"); + trace!("Completed `selectedRange`"); util::EMPTY_RANGE } @@ -315,6 +319,7 @@ extern fn set_marked_text( }; *marked_text_ref = marked_text; } + trace!("Completed `setMarkedText`"); } extern fn unmark_text(this: &Object, _sel: Sel) { @@ -326,10 +331,12 @@ extern fn unmark_text(this: &Object, _sel: Sel) { let input_context: id = msg_send![this, inputContext]; let _: () = msg_send![input_context, discardMarkedText]; } + trace!("Completed `unmarkText`"); } extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id { trace!("Triggered `validAttributesForMarkedText`"); + trace!("Completed `validAttributesForMarkedText`"); unsafe { msg_send![class!(NSArray), array] } } @@ -340,11 +347,13 @@ extern fn attributed_substring_for_proposed_range( _actual_range: *mut c_void, // *mut NSRange ) -> id { trace!("Triggered `attributedSubstringForProposedRange`"); + trace!("Completed `attributedSubstringForProposedRange`"); nil } extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger { trace!("Triggered `characterIndexForPoint`"); + trace!("Completed `characterIndexForPoint`"); 0 } @@ -354,8 +363,8 @@ extern fn first_rect_for_character_range( _range: NSRange, _actual_range: *mut c_void, // *mut NSRange ) -> NSRect { - trace!("Triggered `firstRectForCharacterRange`"); unsafe { + trace!("Triggered `firstRectForCharacterRange`"); let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); let (x, y) = state.ime_spot.unwrap_or_else(|| { @@ -367,7 +376,7 @@ extern fn first_rect_for_character_range( let y = util::bottom_left_to_top_left(content_rect); (x, y) }); - + trace!("Completed `firstRectForCharacterRange`"); NSRect::new( NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0), @@ -410,6 +419,7 @@ extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: AppState::queue_events(events); } + trace!("Completed `insertText`"); } extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { @@ -443,6 +453,7 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { AppState::queue_events(events); } + trace!("Completed `doCommandBySelector`"); } fn get_characters(event: id) -> Option { @@ -524,6 +535,7 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { let _: () = msg_send![this, interpretKeyEvents:array]; } } + trace!("Completed `keyDown`"); } extern fn key_up(this: &Object, _sel: Sel, event: id) { @@ -559,6 +571,7 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) { AppState::queue_event(window_event); } + trace!("Completed `keyUp`"); } extern fn flags_changed(this: &Object, _sel: Sel, event: id) { @@ -612,6 +625,7 @@ extern fn flags_changed(this: &Object, _sel: Sel, event: id) { }); } } + trace!("Completed `flagsChanged`"); } extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) { @@ -665,6 +679,7 @@ extern fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { AppState::queue_event(window_event); } + trace!("Completed `cancelOperation`"); } fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) { @@ -795,6 +810,7 @@ extern fn mouse_entered(this: &Object, _sel: Sel, event: id) { AppState::queue_event(enter_event); AppState::queue_event(move_event); } + trace!("Completed `mouseEntered`"); } extern fn mouse_exited(this: &Object, _sel: Sel, _event: id) { @@ -810,6 +826,7 @@ extern fn mouse_exited(this: &Object, _sel: Sel, _event: id) { AppState::queue_event(window_event); } + trace!("Completed `mouseExited`"); } extern fn scroll_wheel(this: &Object, _sel: Sel, event: id) { @@ -850,6 +867,7 @@ extern fn scroll_wheel(this: &Object, _sel: Sel, event: id) { AppState::queue_event(device_event); AppState::queue_event(window_event); } + trace!("Completed `scrollWheel`"); } extern fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { @@ -872,6 +890,7 @@ extern fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { AppState::queue_event(window_event); } + trace!("Completed `pressureChangeWithEvent`"); } // Allows us to receive Ctrl-Tab and Ctrl-Esc. diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 8a42923f12..b87884a517 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -208,7 +208,7 @@ pub struct SharedState { pub fullscreen: Option, pub maximized: bool, standard_frame: Option, - saved_style: Option, + pub saved_style: Option, } impl From for SharedState { @@ -232,7 +232,7 @@ pub struct UnownedWindow { pub nswindow: IdRef, // never changes pub nsview: IdRef, // never changes input_context: IdRef, // never changes - pub shared_state: Mutex, + pub shared_state: Arc>, decorations: AtomicBool, cursor_hidden: AtomicBool, } @@ -251,20 +251,20 @@ impl UnownedWindow { } } - let autoreleasepool = unsafe { NSAutoreleasePool::new(nil) }; + let pool = unsafe { NSAutoreleasePool::new(nil) }; let nsapp = create_app(pl_attribs.activation_policy).ok_or_else(|| { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; + unsafe { pool.drain() }; CreationError::OsError(format!("Couldn't create `NSApplication`")) })?; let nswindow = create_window(&win_attribs, &pl_attribs).ok_or_else(|| { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; + unsafe { pool.drain() }; CreationError::OsError(format!("Couldn't create `NSWindow`")) })?; let nsview = unsafe { create_view(*nswindow) }.ok_or_else(|| { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; + unsafe { pool.drain() }; CreationError::OsError(format!("Couldn't create `NSView`")) })?; @@ -303,7 +303,7 @@ impl UnownedWindow { nsview, nswindow, input_context, - shared_state: Mutex::new(win_attribs.into()), + shared_state: Arc::new(Mutex::new(win_attribs.into())), decorations: AtomicBool::new(decorations), cursor_hidden: Default::default(), }); @@ -340,11 +340,27 @@ impl UnownedWindow { window.set_maximized(maximized); } - let _: () = unsafe { msg_send![autoreleasepool, drain] }; + let _: () = unsafe { msg_send![pool, drain] }; Ok((window, delegate)) } + fn set_style_mask_async(&self, mask: NSWindowStyleMask) { + unsafe { util::set_style_mask_async( + *self.nswindow, + *self.nsview, + mask, + ) }; + } + + fn set_style_mask_sync(&self, mask: NSWindowStyleMask) { + unsafe { util::set_style_mask_sync( + *self.nswindow, + *self.nsview, + mask, + ) }; + } + pub fn id(&self) -> Id { get_window_id(*self.nswindow) } @@ -358,12 +374,13 @@ impl UnownedWindow { #[inline] pub fn show(&self) { - unsafe { NSWindow::makeKeyAndOrderFront_(*self.nswindow, nil); } + //unsafe { NSWindow::makeKeyAndOrderFront_(*self.nswindow, nil); } + unsafe { util::make_key_and_order_front_async(*self.nswindow) }; } #[inline] pub fn hide(&self) { - unsafe { NSWindow::orderOut_(*self.nswindow, nil); } + unsafe { util::order_out_async(*self.nswindow) }; } pub fn request_redraw(&self) { @@ -395,14 +412,14 @@ impl UnownedWindow { let dummy = NSRect::new( NSPoint::new( position.x, - // While it's true that we're setting the top-left position, it still needs to be - // in a bottom-left coordinate system. + // While it's true that we're setting the top-left position, + // it still needs to be in a bottom-left coordinate system. CGDisplay::main().pixels_high() as f64 - position.y, ), NSSize::new(0f64, 0f64), ); unsafe { - NSWindow::setFrameTopLeftPoint_(*self.nswindow, dummy.origin); + util::set_frame_top_left_point_async(*self.nswindow, dummy.origin); } } @@ -421,7 +438,7 @@ impl UnownedWindow { #[inline] pub fn set_inner_size(&self, size: LogicalSize) { unsafe { - NSWindow::setContentSize_(*self.nswindow, NSSize::new(size.width as CGFloat, size.height as CGFloat)); + util::set_content_size_async(*self.nswindow, size); } } @@ -441,56 +458,105 @@ impl UnownedWindow { #[inline] pub fn set_resizable(&self, resizable: bool) { - trace!("Locked shared state in `set_resizable`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.resizable = resizable; - if shared_state_lock.fullscreen.is_none() { + let fullscreen = { + trace!("Locked shared state in `set_resizable`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.resizable = resizable; + trace!("Unlocked shared state in `set_resizable`"); + shared_state_lock.fullscreen.is_some() + }; + if !fullscreen { let mut mask = unsafe { self.nswindow.styleMask() }; if resizable { mask |= NSWindowStyleMask::NSResizableWindowMask; } else { mask &= !NSWindowStyleMask::NSResizableWindowMask; } - unsafe { util::set_style_mask(*self.nswindow, *self.nsview, mask) }; + self.set_style_mask_async(mask); } // Otherwise, we don't change the mask until we exit fullscreen. - trace!("Unlocked shared state in `set_resizable`"); } pub fn set_cursor(&self, cursor: MouseCursor) { + enum CursorType { + Native(&'static str), + Undocumented(&'static str), + WebKit(&'static str), + } + let cursor_name = match cursor { - MouseCursor::Arrow | MouseCursor::Default => "arrowCursor", - MouseCursor::Hand => "pointingHandCursor", - MouseCursor::Grabbing | MouseCursor::Grab => "closedHandCursor", - MouseCursor::Text => "IBeamCursor", - MouseCursor::VerticalText => "IBeamCursorForVerticalLayout", - MouseCursor::Copy => "dragCopyCursor", - MouseCursor::Alias => "dragLinkCursor", - MouseCursor::NotAllowed | MouseCursor::NoDrop => "operationNotAllowedCursor", - MouseCursor::ContextMenu => "contextualMenuCursor", - MouseCursor::Crosshair => "crosshairCursor", - MouseCursor::EResize => "resizeRightCursor", - MouseCursor::NResize => "resizeUpCursor", - MouseCursor::WResize => "resizeLeftCursor", - MouseCursor::SResize => "resizeDownCursor", - MouseCursor::EwResize | MouseCursor::ColResize => "resizeLeftRightCursor", - MouseCursor::NsResize | MouseCursor::RowResize => "resizeUpDownCursor", - - // TODO: Find appropriate OSX cursors - MouseCursor::NeResize | MouseCursor::NwResize | - MouseCursor::SeResize | MouseCursor::SwResize | - MouseCursor::NwseResize | MouseCursor::NeswResize | - - MouseCursor::Cell | - MouseCursor::Wait | MouseCursor::Progress | MouseCursor::Help | - MouseCursor::Move | MouseCursor::AllScroll | MouseCursor::ZoomIn | - MouseCursor::ZoomOut => "arrowCursor", + MouseCursor::Arrow | MouseCursor::Default => CursorType::Native("arrowCursor"), + MouseCursor::Hand => CursorType::Native("pointingHandCursor"), + MouseCursor::Grabbing | MouseCursor::Grab => CursorType::Native("closedHandCursor"), + MouseCursor::Text => CursorType::Native("IBeamCursor"), + MouseCursor::VerticalText => CursorType::Native("IBeamCursorForVerticalLayout"), + MouseCursor::Copy => CursorType::Native("dragCopyCursor"), + MouseCursor::Alias => CursorType::Native("dragLinkCursor"), + MouseCursor::NotAllowed | MouseCursor::NoDrop => CursorType::Native("operationNotAllowedCursor"), + MouseCursor::ContextMenu => CursorType::Native("contextualMenuCursor"), + MouseCursor::Crosshair => CursorType::Native("crosshairCursor"), + MouseCursor::EResize => CursorType::Native("resizeRightCursor"), + MouseCursor::NResize => CursorType::Native("resizeUpCursor"), + MouseCursor::WResize => CursorType::Native("resizeLeftCursor"), + MouseCursor::SResize => CursorType::Native("resizeDownCursor"), + MouseCursor::EwResize | MouseCursor::ColResize => CursorType::Native("resizeLeftRightCursor"), + MouseCursor::NsResize | MouseCursor::RowResize => CursorType::Native("resizeUpDownCursor"), + + // Undocumented cursors: https://stackoverflow.com/a/46635398/5435443 + MouseCursor::Help => CursorType::Undocumented("_helpCursor"), + MouseCursor::ZoomIn => CursorType::Undocumented("_zoomInCursor"), + MouseCursor::ZoomOut => CursorType::Undocumented("_zoomOutCursor"), + MouseCursor::NeResize => CursorType::Undocumented("_windowResizeNorthEastCursor"), + MouseCursor::NwResize => CursorType::Undocumented("_windowResizeNorthWestCursor"), + MouseCursor::SeResize => CursorType::Undocumented("_windowResizeSouthEastCursor"), + MouseCursor::SwResize => CursorType::Undocumented("_windowResizeSouthWestCursor"), + MouseCursor::NeswResize => CursorType::Undocumented("_windowResizeNorthEastSouthWestCursor"), + MouseCursor::NwseResize => CursorType::Undocumented("_windowResizeNorthWestSouthEastCursor"), + + // While these are available, the former just loads a white arrow, + // and the latter loads an ugly deflated beachball! + // MouseCursor::Move => CursorType::Undocumented("_moveCursor"), + // MouseCursor::Wait => CursorType::Undocumented("_waitCursor"), + + // An even more undocumented cursor... + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349 + // This is the wrong semantics for `Wait`, but it's the same as + // what's used in Safari and Chrome. + MouseCursor::Wait | MouseCursor::Progress => CursorType::Undocumented("busyButClickableCursor"), + + // For the rest, we can just snatch the cursors from WebKit... + // They fit the style of the native cursors, and will seem + // completely standard to macOS users. + // https://stackoverflow.com/a/21786835/5435443 + MouseCursor::Move | MouseCursor::AllScroll => CursorType::WebKit("move"), + MouseCursor::Cell => CursorType::WebKit("cell"), }; - let sel = Sel::register(cursor_name); - let cls = class!(NSCursor); - unsafe { - use objc::Message; - let cursor: id = cls.send_message(sel, ()).unwrap(); - let _: () = msg_send![cursor, set]; + + let class = class!(NSCursor); + match cursor_name { + CursorType::Native(cursor_name) => { + let sel = Sel::register(cursor_name); + unsafe { + let cursor: id = msg_send![class, performSelector:sel]; + let _: () = msg_send![cursor, set]; + } + }, + CursorType::Undocumented(cursor_name) => { + let sel = Sel::register(cursor_name); + unsafe { + let sel = if msg_send![class, respondsToSelector:sel] { + sel + } else { + warn!("Cursor `{}` appears to be invalid", cursor_name); + sel!(arrowCursor) + }; + let cursor: id = msg_send![class, performSelector:sel]; + let _: () = msg_send![cursor, set]; + } + }, + CursorType::WebKit(cursor_name) => unsafe { + let cursor = util::load_webkit_cursor(cursor_name); + let _: () = msg_send![cursor, set]; + }, } } @@ -546,14 +612,14 @@ impl UnownedWindow { | NSWindowStyleMask::NSResizableWindowMask; let needs_temp_mask = !curr_mask.contains(required); if needs_temp_mask { - unsafe { util::set_style_mask(*self.nswindow, *self.nsview, required) }; + self.set_style_mask_sync(required); } let is_zoomed: BOOL = unsafe { msg_send![*self.nswindow, isZoomed] }; // Roll back temp styles if needs_temp_mask { - unsafe { util::set_style_mask(*self.nswindow, *self.nsview, curr_mask) }; + self.set_style_mask_sync(curr_mask); } is_zoomed != 0 @@ -577,7 +643,7 @@ impl UnownedWindow { } }; - unsafe { util::set_style_mask(*self.nswindow, *self.nsview, mask) }; + self.set_style_mask_async(mask); shared_state_lock.maximized }; trace!("Unocked shared state in `restore_state_from_fullscreen`"); @@ -627,63 +693,55 @@ impl UnownedWindow { trace!("Unlocked shared state in `set_maximized`"); } - /// TODO: Right now set_fullscreen do not work on switching monitors - /// in fullscreen mode + // TODO: `set_fullscreen` is only usable if you fullscreen on the same + // monitor the window's currently on. #[inline] pub fn set_fullscreen(&self, monitor: Option) { - trace!("Locked shared state in `set_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - let not_fullscreen = { + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); let current = &shared_state_lock.fullscreen; match (current, monitor) { - (&None, None) => { return }, (&Some(ref a), Some(ref b)) if a.inner != b.inner => { - unimplemented!(); - } - (&Some(_), Some(_)) => { return }, + // Our best bet is probably to move to the origin of the + // target monitor. + unimplemented!() + }, + (&None, None) | (&Some(_), Some(_)) => return, _ => (), } + trace!("Unlocked shared state in `set_fullscreen`"); current.is_none() }; - // Because toggleFullScreen will not work if the StyleMask is none, - // We set a normal style to it temporary. - // It will clean up at window_did_exit_fullscreen. - if not_fullscreen { - let curr_mask = unsafe { self.nswindow.styleMask() }; - let required = NSWindowStyleMask::NSTitledWindowMask - | NSWindowStyleMask::NSResizableWindowMask; - if !curr_mask.contains(required) { - unsafe { util::set_style_mask( - *self.nswindow, - *self.nsview, - required, - ) }; - shared_state_lock.saved_style = Some(curr_mask); - } - } - - drop(shared_state_lock); - trace!("Unlocked shared state in `set_fullscreen`"); - - unsafe { self.nswindow.toggleFullScreen_(nil) }; + unsafe { util::toggle_full_screen_async( + *self.nswindow, + *self.nsview, + not_fullscreen, + Arc::downgrade(&self.shared_state), + ) }; } #[inline] pub fn set_decorations(&self, decorations: bool) { - trace!("`set_decorations` {:?}", decorations); if decorations != self.decorations.load(Ordering::Acquire) { self.decorations.store(decorations, Ordering::Release); - trace!("Locked shared state in `set_decorations`"); - let shared_state_lock = self.shared_state.lock().unwrap(); + let (fullscreen, resizable) = { + trace!("Locked shared state in `set_decorations`"); + let shared_state_lock = self.shared_state.lock().unwrap(); + trace!("Unlocked shared state in `set_decorations`"); + ( + shared_state_lock.fullscreen.is_some(), + shared_state_lock.resizable, + ) + }; - // Don't apply yet if we're in fullscreen mode. - // This will be applied in `window_did_exit_fullscreen`. - if shared_state_lock.fullscreen.is_some() { return }; + // If we're in fullscreen mode, we wait to apply decoration changes + // until we're in `window_did_exit_fullscreen`. + if fullscreen { return } - unsafe { + let new_mask = { let mut new_mask = if decorations { NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSMiniaturizableWindowMask @@ -693,26 +751,23 @@ impl UnownedWindow { NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask }; - if !shared_state_lock.resizable { + if !resizable { new_mask &= !NSWindowStyleMask::NSResizableWindowMask; } - util::set_style_mask(*self.nswindow, *self.nsview, new_mask); - } - - trace!("Unlocked shared state in `set_decorations`"); + new_mask + }; + self.set_style_mask_async(new_mask); } } #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { - unsafe { - let level = if always_on_top { - ffi::NSWindowLevel::NSFloatingWindowLevel - } else { - ffi::NSWindowLevel::NSNormalWindowLevel - }; - let _: () = msg_send![*self.nswindow, setLevel:level]; - } + let level = if always_on_top { + ffi::NSWindowLevel::NSFloatingWindowLevel + } else { + ffi::NSWindowLevel::NSNormalWindowLevel + }; + unsafe { util::set_level_async(*self.nswindow, level) }; } #[inline] @@ -769,10 +824,9 @@ impl WindowExtMacOS for UnownedWindow { #[inline] fn request_user_attention(&self, is_critical: bool) { unsafe { - NSApp().requestUserAttention_(if is_critical { - NSRequestUserAttentionType::NSCriticalRequest - } else { - NSRequestUserAttentionType::NSInformationalRequest + NSApp().requestUserAttention_(match is_critical { + true => NSRequestUserAttentionType::NSCriticalRequest, + false => NSRequestUserAttentionType::NSInformationalRequest, }); } } @@ -781,18 +835,10 @@ impl WindowExtMacOS for UnownedWindow { impl Drop for UnownedWindow { fn drop(&mut self) { trace!("Dropping `UnownedWindow` ({:?})", self as *mut _); - - // nswindow::close uses autorelease - // so autorelease pool - let pool = unsafe { NSAutoreleasePool::new(nil) }; - // Close the window if it has not yet been closed. - let nswindow = *self.nswindow; - if nswindow != nil { - let _: () = unsafe { msg_send![nswindow, close] }; + if *self.nswindow != nil { + unsafe { util::close_async(*self.nswindow) }; } - - let _: () = unsafe { msg_send![pool, drain] }; } } diff --git a/src/util.rs b/src/util.rs index 1ee0765a17..6d50fb1388 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,7 @@ use std::ops::BitAnd; // Replace with `!` once stable +#[derive(Debug)] pub enum Never {} pub fn has_flag(bitset: T, flag: T) -> bool