diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs new file mode 100644 index 0000000000..292e82752a --- /dev/null +++ b/src/platform_impl/macos/app.rs @@ -0,0 +1,46 @@ +use cocoa::{appkit, base::id}; +use objc::{declare::ClassDecl, runtime::{Class, Object, Sel}}; + +use platform_impl::platform::util; + +pub struct AppClass(pub *const Class); +unsafe impl Send for AppClass {} +unsafe impl Sync for AppClass {} + +lazy_static! { + pub static ref APP_CLASS: AppClass = unsafe { + let superclass = class!(NSApplication); + let mut decl = ClassDecl::new("WinitApp", superclass).unwrap(); + + decl.add_method( + sel!(sendEvent:), + send_event as extern fn(&Object, Sel, id), + ); + + AppClass(decl.register()) + }; +} + +// 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) +extern fn send_event(this: &Object, _sel: Sel, event: id) { + unsafe { + use self::appkit::NSEvent; + // For posterity, there are some undocumented event types + // (https://github.com/servo/cocoa-rs/issues/155) + // but that doesn't really matter here. + let event_type = event.eventType(); + let modifier_flags = event.modifierFlags(); + if event_type == appkit::NSKeyUp && util::has_flag( + modifier_flags, + appkit::NSEventModifierFlags::NSCommandKeyMask, + ) { + let key_window: id = msg_send![this, keyWindow]; + let _: () = msg_send![key_window, sendEvent:event]; + } else { + let superclass = util::superclass(this); + let _: () = msg_send![super(this, superclass), sendEvent:event]; + } + } +} diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 364de98366..f021284041 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -6,35 +6,27 @@ use std::{ use cocoa::{ appkit::{ - self, NSApp, NSApplication, NSApplicationDefined, NSEvent, NSEventMask, - NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSView, NSWindow, + self, NSApp, NSApplication, NSApplicationDefined, NSEvent, + NSEventModifierFlags, NSEventSubtype, }, base::{BOOL, id, nil, NO, YES}, - foundation::{NSAutoreleasePool, NSDate, NSDefaultRunLoopMode, NSPoint, NSRect, NSSize}, + foundation::{NSAutoreleasePool, NSPoint}, }; use { event::{ self, DeviceEvent, ElementState, Event, KeyboardInput, - ModifiersState, StartCause, TouchPhase, WindowEvent, + ModifiersState, StartCause, WindowEvent, }, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, - window, }; use platform_impl::platform::{ - app_delegate::APP_DELEGATE_CLASS, DEVICE_ID, monitor::{self, MonitorHandle}, + app::APP_CLASS, app_delegate::APP_DELEGATE_CLASS, DEVICE_ID, + monitor::{self, MonitorHandle}, observer::{EventLoopWaker, setup_control_flow_observers}, - util::IdRef, window::UnownedWindow, + util::{Access, IdRef}, window::UnownedWindow, }; -#[derive(Default)] -struct Modifiers { - shift_pressed: bool, - ctrl_pressed: bool, - win_pressed: bool, - alt_pressed: bool, -} - // Change to `!` once stable pub enum Never {} @@ -100,6 +92,7 @@ pub struct Handler { control_flow_prev: ControlFlow, callback: Option>, waker: EventLoopWaker, + pending_events: Weak>, } unsafe impl Send for Handler {} @@ -134,7 +127,7 @@ impl Handler { } } },*/ - ControlFlow::Exit => panic!("unexpected `ControlFlow::Exit`"), + ControlFlow::Exit => StartCause::Poll,//panic!("unexpected `ControlFlow::Exit`"), _ => unimplemented!(), }; if let Some(ref mut callback) = self.callback { @@ -145,6 +138,14 @@ impl Handler { pub fn cleared(&mut self) { if let Some(ref mut callback) = self.callback { callback.handle_nonuser_event(Event::EventsCleared, &mut self.control_flow); + //trace!("Locked pending events in `run`"); + let mut pending = self.pending_events + .access(|pending| pending.take()) + .unwrap(); + //trace!("Unlocked pending events in `run`"); + for event in pending.drain(0..) { + callback.handle_nonuser_event(event, &mut self.control_flow); + } } let old = self.control_flow_prev; let new = self.control_flow; @@ -155,7 +156,9 @@ impl Handler { (_, ControlFlow::Wait) => self.waker.stop(), (_, ControlFlow::WaitUntil(new_instant)) => self.waker.start_at(new_instant), (_, ControlFlow::Poll) => self.waker.start(), - (_, ControlFlow::Exit) => (), + (_, ControlFlow::Exit) => { + let _: () = unsafe { msg_send![NSApp(), stop:nil] }; + }, } } } @@ -220,8 +223,7 @@ impl Default for EventLoopWindowTarget { pub struct EventLoop { elw_target: RootELW, - delegate: IdRef, - modifiers: Modifiers, + _delegate: IdRef, } impl EventLoop { @@ -229,7 +231,7 @@ impl EventLoop { let delegate = unsafe { if !msg_send![class!(NSThread), isMainThread] { // This check should be in `new` instead - panic!("Events can only be polled from the main thread on macOS"); + panic!("On macOS, `EventLoop` must be created on the main thread!"); } // Mark this thread as the main thread of the Cocoa event system. @@ -237,7 +239,7 @@ impl EventLoop { // This must be done before any worker threads get a chance to call it // (e.g., via `EventLoopProxy::wakeup()`), causing a wrong thread to be // marked as the main thread. - let app = NSApp(); + let app: id = msg_send![APP_CLASS.0, sharedApplication]; let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); let pool = NSAutoreleasePool::new(nil); @@ -248,8 +250,7 @@ impl EventLoop { setup_control_flow_observers(); EventLoop { elw_target: RootELW::new(Default::default()), - delegate, // is this necessary? - modifiers: Default::default(), + _delegate: delegate, // is this necessary? } } @@ -267,47 +268,33 @@ impl EventLoop { &self.elw_target } - pub fn run(mut self, mut callback: F) -> ! + pub fn run(self, callback: F) -> ! where F: 'static + FnMut(Event, &RootELW, &mut ControlFlow), { - /* - loop { - { - trace!("Locked pending events in `run`"); - let mut pending = self.elw_target - .inner - .pending_events - .lock() - .unwrap() - .take(); - trace!("Unlocked pending events in `run`"); - for event in pending.drain(0..) { - callback( - event.userify(), - self.window_target(), - &mut control_flow, - ); - } - } - - if let ControlFlow::Exit = control_flow { - callback(Event::LoopDestroyed, self.window_target(), &mut control_flow); - exit(0); - } - } - */ - unsafe { let _pool = NSAutoreleasePool::new(nil); let app = NSApp(); - assert!(!app.is_null()); - HANDLER.lock().unwrap().callback = Some(Box::new(EventLoopHandler { - callback, - event_loop: self.elw_target, - })); + assert_ne!(app, nil); + { + let pending_events = Arc::downgrade(&self.elw_target.inner.pending_events); + let mut handler = HANDLER.lock().unwrap(); + handler.callback = Some(Box::new(EventLoopHandler { + callback, + event_loop: self.elw_target, + })); + handler.pending_events = pending_events; + } let _: () = msg_send![app, run]; - // This is probably wrong - unreachable_unchecked() + { + let mut handler = HANDLER.lock().unwrap(); + if let Some(mut callback) = handler.callback.take() { + callback.handle_nonuser_event( + Event::LoopDestroyed, + &mut handler.control_flow, + ); + } + } + exit(0) } } @@ -319,28 +306,10 @@ impl EventLoop { // Converts an `NSEvent` to a winit `Event`. unsafe fn translate_event(&mut self, ns_event: id) -> Option> { - if ns_event == nil { - return None; - } - - // FIXME: Despite not being documented anywhere, an `NSEvent` is produced when a user opens - // Spotlight while the NSApplication is in focus. This `NSEvent` produces a `NSEventType` - // with value `21`. This causes a SEGFAULT as soon as we try to match on the `NSEventType` - // enum as there is no variant associated with the value. Thus, we return early if this - // sneaky event occurs. If someone does find some documentation on this, please fix this by - // adding an appropriate variant to the `NSEventType` enum in the cocoa-rs crate. - if ns_event.eventType() as u64 == 21 { - return None; - } - let event_type = ns_event.eventType(); let ns_window = ns_event.window(); let window_id = super::window::get_window_id(ns_window); - // FIXME: Document this. Why do we do this? Seems like it passes on events to window/app. - // If we don't do this, window does not become main for some reason. - NSApp().sendEvent_(ns_event); - let windows = self.elw_target.inner.window_list.lock().unwrap(); let maybe_window = (*windows) .windows @@ -348,11 +317,6 @@ impl EventLoop { .filter_map(Weak::upgrade) .find(|window| window_id == window.id()); - let into_event = |window_event| Event::WindowEvent { - window_id: window::WindowId(window_id), - event: window_event, - }; - // Returns `Some` window if one of our windows is the key window. let maybe_key_window = || (*windows) .windows @@ -364,111 +328,6 @@ impl EventLoop { }); match event_type { - // https://github.com/glfw/glfw/blob/50eccd298a2bbc272b4977bd162d3e4b55f15394/src/cocoa_window.m#L881 - appkit::NSKeyUp => { - if let Some(key_window) = maybe_key_window() { - if event_mods(ns_event).logo { - let _: () = msg_send![*key_window.nswindow, sendEvent:ns_event]; - } - } - None - }, - // similar to above, but for ``, the keyDown is suppressed instead of the - // KeyUp, and the above trick does not appear to work. - appkit::NSKeyDown => { - let modifiers = event_mods(ns_event); - let keycode = NSEvent::keyCode(ns_event); - if modifiers.logo && keycode == 47 { - modifier_event(ns_event, NSEventModifierFlags::NSCommandKeyMask, false) - .map(into_event) - } else { - None - } - }, - appkit::NSFlagsChanged => { - let mut events = VecDeque::new(); - - if let Some(window_event) = modifier_event( - ns_event, - NSEventModifierFlags::NSShiftKeyMask, - self.modifiers.shift_pressed, - ) { - self.modifiers.shift_pressed = !self.modifiers.shift_pressed; - events.push_back(into_event(window_event)); - } - - if let Some(window_event) = modifier_event( - ns_event, - NSEventModifierFlags::NSControlKeyMask, - self.modifiers.ctrl_pressed, - ) { - self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed; - events.push_back(into_event(window_event)); - } - - if let Some(window_event) = modifier_event( - ns_event, - NSEventModifierFlags::NSCommandKeyMask, - self.modifiers.win_pressed, - ) { - self.modifiers.win_pressed = !self.modifiers.win_pressed; - events.push_back(into_event(window_event)); - } - - if let Some(window_event) = modifier_event( - ns_event, - NSEventModifierFlags::NSAlternateKeyMask, - self.modifiers.alt_pressed, - ) { - self.modifiers.alt_pressed = !self.modifiers.alt_pressed; - events.push_back(into_event(window_event)); - } - - let event = events.pop_front(); - trace!("Locked pending events in `translate_event`"); - self.elw_target.inner.pending_events - .lock() - .unwrap() - .queue_events(events); - trace!("Unlocked pending events in `translate_event`"); - event - }, - - appkit::NSMouseEntered => { - let window = match maybe_window.or_else(maybe_key_window) { - Some(window) => window, - None => return None, - }; - - let window_point = ns_event.locationInWindow(); - let view_point = if ns_window == nil { - let ns_size = NSSize::new(0.0, 0.0); - let ns_rect = NSRect::new(window_point, ns_size); - let window_rect = window.nswindow.convertRectFromScreen_(ns_rect); - window.nsview.convertPoint_fromView_(window_rect.origin, nil) - } else { - window.nsview.convertPoint_fromView_(window_point, nil) - }; - - let view_rect = NSView::frame(*window.nsview); - let x = view_point.x as f64; - let y = (view_rect.size.height - view_point.y) as f64; - let window_event = WindowEvent::CursorMoved { - device_id: DEVICE_ID, - position: (x, y).into(), - modifiers: event_mods(ns_event), - }; - let event = Event::WindowEvent { window_id: window::WindowId(window.id()), event: window_event }; - trace!("Locked pending events in `translate_event`"); - self.elw_target.inner.pending_events - .lock() - .unwrap() - .queue_event(event); - trace!("Unlocked pending events in `translate_event`"); - Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID })) - }, - appkit::NSMouseExited => { Some(into_event(WindowEvent::CursorLeft { device_id: DEVICE_ID })) }, - appkit::NSMouseMoved | appkit::NSLeftMouseDragged | appkit::NSOtherMouseDragged | @@ -513,75 +372,6 @@ impl EventLoop { event }, - appkit::NSScrollWheel => { - // If none of the windows received the scroll, return `None`. - if maybe_window.is_none() { - return None; - } - - use event::MouseScrollDelta::{LineDelta, PixelDelta}; - let delta = if ns_event.hasPreciseScrollingDeltas() == YES { - PixelDelta(( - ns_event.scrollingDeltaX() as f64, - ns_event.scrollingDeltaY() as f64, - ).into()) - } else { - // TODO: This is probably wrong - LineDelta( - ns_event.scrollingDeltaX() as f32, - ns_event.scrollingDeltaY() as f32, - ) - }; - let phase = match ns_event.phase() { - NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, - NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, - _ => TouchPhase::Moved, - }; - trace!("Locked pending events in `translate_event`"); - self.elw_target.inner.pending_events.lock().unwrap().queue_event(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::MouseWheel { - delta: if ns_event.hasPreciseScrollingDeltas() == YES { - PixelDelta(( - ns_event.scrollingDeltaX() as f64, - ns_event.scrollingDeltaY() as f64, - ).into()) - } else { - LineDelta( - ns_event.scrollingDeltaX() as f32, - ns_event.scrollingDeltaY() as f32, - ) - }, - } - }); - trace!("Unlocked pending events in `translate_event`"); - let window_event = WindowEvent::MouseWheel { - device_id: DEVICE_ID, - delta, - phase, - modifiers: event_mods(ns_event), - }; - Some(into_event(window_event)) - }, - - appkit::NSEventTypePressure => { - let pressure = ns_event.pressure(); - let stage = ns_event.stage(); - let window_event = WindowEvent::TouchpadPressure { - device_id: DEVICE_ID, - pressure, - stage, - }; - Some(into_event(window_event)) - }, - - NSApplicationDefined => match ns_event.subtype() { - NSEventSubtype::NSApplicationActivatedEventType => { - unimplemented!(); - }, - _ => None, - }, - _ => None, } } @@ -685,8 +475,8 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option { 0x33 => event::VirtualKeyCode::Back, //0x34 => unkown, 0x35 => event::VirtualKeyCode::Escape, - 0x36 => event::VirtualKeyCode::LWin, - 0x37 => event::VirtualKeyCode::RWin, + 0x36 => event::VirtualKeyCode::RWin, + 0x37 => event::VirtualKeyCode::LWin, 0x38 => event::VirtualKeyCode::LShift, //0x39 => Caps lock, 0x3a => event::VirtualKeyCode::LAlt, @@ -794,7 +584,7 @@ pub fn event_mods(event: id) -> ModifiersState { } } -unsafe fn modifier_event( +pub unsafe fn modifier_event( ns_event: id, keymask: NSEventModifierFlags, was_key_pressed: bool, diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index c76e743748..26b15bf11b 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -1,5 +1,6 @@ #![cfg(target_os = "macos")] +mod app; mod app_delegate; mod event_loop; mod ffi; diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index 92479ad74f..64caf35383 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -129,9 +129,9 @@ extern fn control_flow_begin_handler( #[allow(non_upper_case_globals)] match activity { kCFRunLoopAfterWaiting => { - trace!("Triggered `CFRunLoopAfterWaiting`"); + //trace!("Triggered `CFRunLoopAfterWaiting`"); HANDLER.lock().unwrap().wakeup(); - trace!("Completed `CFRunLoopAfterWaiting`"); + //trace!("Completed `CFRunLoopAfterWaiting`"); }, kCFRunLoopEntry => unimplemented!(), // not expected to ever happen _ => unreachable!(), @@ -150,9 +150,9 @@ extern fn control_flow_end_handler( #[allow(non_upper_case_globals)] match activity { kCFRunLoopBeforeWaiting => { - trace!("Triggered `CFRunLoopBeforeWaiting`"); + //trace!("Triggered `CFRunLoopBeforeWaiting`"); HANDLER.lock().unwrap().cleared(); - trace!("Completed `CFRunLoopBeforeWaiting`"); + //trace!("Completed `CFRunLoopBeforeWaiting`"); }, kCFRunLoopExit => (),//unimplemented!(), // not expected to ever happen _ => unreachable!(), diff --git a/src/platform_impl/macos/util.rs b/src/platform_impl/macos/util.rs index ddbe225e0a..c59929dd0d 100644 --- a/src/platform_impl/macos/util.rs +++ b/src/platform_impl/macos/util.rs @@ -1,12 +1,12 @@ -use std::{ops::Deref, sync::{Arc, Mutex, Weak}}; +use std::{ops::{BitAnd, Deref}, sync::{Arc, Mutex, Weak}}; use cocoa::{ - appkit::NSWindowStyleMask, + appkit::{NSApp, NSWindowStyleMask}, base::{id, nil}, foundation::{NSAutoreleasePool, NSRect, NSUInteger}, }; use core_graphics::display::CGDisplay; -use objc::runtime::{BOOL, Object, Sel, YES}; +use objc::runtime::{BOOL, Class, Object, Sel, YES}; use platform_impl::platform::ffi; @@ -89,14 +89,27 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) } +pub fn has_flag(bitset: T, flag: T) -> bool +where T: + Copy + PartialEq + BitAnd +{ + bitset & flag == flag +} + pub unsafe fn set_style_mask(nswindow: id, nsview: id, mask: NSWindowStyleMask) { trace!("`set_style_mask` {:?} {:?} {:?}", nswindow, nsview, mask); use cocoa::appkit::NSWindow; nswindow.setStyleMask_(mask); - // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! + // If we don't do this, key handling will break (at least until the window + // is clicked again/etc.); therefore, never call `setStyleMask` directly! nswindow.makeFirstResponder_(nsview); } +pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class { + let superclass: id = msg_send![this, superclass]; + &*(superclass as *const _) +} + pub unsafe fn create_input_context(view: id) -> IdRef { let input_context: id = msg_send![class!(NSTextInputContext), alloc]; let input_context: id = msg_send![input_context, initWithClient:view]; @@ -105,8 +118,7 @@ pub unsafe fn create_input_context(view: id) -> IdRef { #[allow(dead_code)] pub unsafe fn open_emoji_picker() { - let app: id = msg_send![class!(NSApplication), sharedApplication]; - let _: () = msg_send![app, orderFrontCharacterPalette:nil]; + let _: () = msg_send![NSApp(), orderFrontCharacterPalette:nil]; } pub extern fn yes(_: &Object, _: Sel) -> BOOL { diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 0dc4233a7d..d91e409bf1 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -7,30 +7,42 @@ use std::{ }; use cocoa::{ - base::{id, nil}, appkit::{NSEvent, NSView, NSWindow}, - foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger}, + appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow}, + base::{id, nil}, foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger}, }; -use objc::{declare::ClassDecl, runtime::{Class, Object, Protocol, Sel, BOOL, YES}}; +use objc::{declare::ClassDecl, runtime::{BOOL, Class, NO, Object, Protocol, Sel, YES}}; use { - event::{ElementState, Event, KeyboardInput, MouseButton, WindowEvent}, + event::{ + DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, + MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent, + }, window::WindowId, }; use platform_impl::platform::{ DEVICE_ID, event_loop::{ - check_additional_virtual_key_codes, event_mods, + check_additional_virtual_key_codes, event_mods, modifier_event, PendingEvents, to_virtual_key_code, }, util::{self, Access, IdRef}, ffi::*, window::get_window_id, }; +#[derive(Default)] +struct Modifiers { + shift_pressed: bool, + ctrl_pressed: bool, + win_pressed: bool, + alt_pressed: bool, +} + struct ViewState { nswindow: id, pending_events: Weak>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, + modifiers: Modifiers, } pub fn new_view(nswindow: id, pending_events: Weak>) -> IdRef { @@ -40,6 +52,7 @@ pub fn new_view(nswindow: id, pending_events: Weak>) -> IdR ime_spot: None, raw_characters: None, is_key_down: false, + modifiers: Default::default(), }; unsafe { // This is free'd in `dealloc` @@ -80,6 +93,14 @@ lazy_static! { sel!(initWithWinit:), init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id, ); + decl.add_method( + sel!(viewDidMoveToWindow), + view_did_move_to_window as extern fn(&Object, Sel), + ); + decl.add_method( + sel!(acceptsFirstResponder), + accepts_first_responder as extern fn(&Object, Sel) -> BOOL, + ); decl.add_method( sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL, @@ -132,6 +153,10 @@ lazy_static! { sel!(keyUp:), key_up as extern fn(&Object, Sel, id), ); + decl.add_method( + sel!(flagsChanged:), + flags_changed as extern fn(&Object, Sel, id), + ); decl.add_method( sel!(insertTab:), insert_tab as extern fn(&Object, Sel, id), @@ -180,10 +205,30 @@ lazy_static! { sel!(otherMouseDragged:), other_mouse_dragged as extern fn(&Object, Sel, id), ); + decl.add_method( + sel!(mouseEntered:), + mouse_entered as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseExited:), + mouse_exited as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(scrollWheel:), + scroll_wheel as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(pressureChangeWithEvent:), + pressure_change_with_event as extern fn(&Object, Sel, id), + ); decl.add_method( sel!(_wantsKeyDownForEvent:), wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL, ); + decl.add_method( + sel!(cancelOperation:), + cancel_operation as extern fn(&Object, Sel, id), + ); decl.add_ivar::<*mut c_void>("winitState"); decl.add_ivar::("markedText"); let protocol = Protocol::get("NSTextInputClient").unwrap(); @@ -215,6 +260,24 @@ extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id { } } +extern fn view_did_move_to_window(this: &Object, _sel: Sel) { + trace!("Triggered `viewDidMoveToWindow`"); + unsafe { + let state: *mut c_void = *this.get_ivar("winitState"); + let rect: NSRect = msg_send![this, visibleRect]; + let _: () = msg_send![this, + addTrackingRect:rect + owner:this + userData:nil + assumeInside:NO + ]; + } +} + +extern fn accepts_first_responder(_this: &Object, _sel: Sel) -> BOOL { + YES +} + extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { trace!("Triggered `hasMarkedText`"); unsafe { @@ -344,7 +407,7 @@ extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: state.is_key_down = true; // We don't need this now, but it's here if that changes. - //let event: id = msg_send![class!(NSApp), currentEvent]; + //let event: id = msg_send![NSApp(), currentEvent]; let mut events = VecDeque::with_capacity(characters.len()); for character in string.chars() { @@ -509,6 +572,59 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) { } } +extern fn flags_changed(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `flagsChanged`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let mut events = VecDeque::with_capacity(4); + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSShiftKeyMask, + state.modifiers.shift_pressed, + ) { + state.modifiers.shift_pressed = !state.modifiers.shift_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSControlKeyMask, + state.modifiers.ctrl_pressed, + ) { + state.modifiers.ctrl_pressed = !state.modifiers.ctrl_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSCommandKeyMask, + state.modifiers.win_pressed, + ) { + state.modifiers.win_pressed = !state.modifiers.win_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSAlternateKeyMask, + state.modifiers.alt_pressed, + ) { + state.modifiers.alt_pressed = !state.modifiers.alt_pressed; + events.push_back(window_event); + } + + state.pending_events.access(|pending| for event in events { + pending.queue_event(Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event, + }); + }); + } +} + extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) { unsafe { let window: id = msg_send![this, window]; @@ -531,6 +647,39 @@ extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) { } } +// Allows us to receive Cmd-. (the shortcut for closing a dialog) +// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 +extern fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { + trace!("Triggered `cancelOperation`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let scancode = 0x2f; + let virtual_keycode = to_virtual_key_code(scancode); + debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period)); + + let event: id = msg_send![NSApp(), currentEvent]; + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Pressed, + scancode: scancode as _, + virtual_keycode, + modifiers: event_mods(event), + }, + }, + }; + + state.pending_events.access(|pending| { + pending.queue_event(window_event); + }); + } +} + fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) { unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); @@ -626,7 +775,129 @@ extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) { mouse_motion(this, event); } +extern fn mouse_entered(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `mouseEntered`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let enter_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::CursorEntered { device_id: DEVICE_ID }, + }; + + let move_event = { + let window_point = event.locationInWindow(); + let view_point: NSPoint = msg_send![this, + convertPoint:window_point + fromView:nil // convert from window coordinates + ]; + let view_rect: NSRect = msg_send![this, frame]; + let x = view_point.x as f64; + let y = (view_rect.size.height - view_point.y) as f64; + Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::CursorMoved { + device_id: DEVICE_ID, + position: (x, y).into(), + modifiers: event_mods(event), + } + } + }; + + state.pending_events.access(|pending| { + pending.queue_event(enter_event); + pending.queue_event(move_event); + }); + } +} + +extern fn mouse_exited(this: &Object, _sel: Sel, _event: id) { + trace!("Triggered `mouseExited`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::CursorLeft { device_id: DEVICE_ID }, + }; + + state.pending_events.access(|pending| { + pending.queue_event(window_event); + }); + } +} + +extern fn scroll_wheel(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `scrollWheel`"); + unsafe { + let delta = { + let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); + if event.hasPreciseScrollingDeltas() == YES { + MouseScrollDelta::PixelDelta((x as f64, y as f64).into()) + } else { + MouseScrollDelta::LineDelta(x as f32, y as f32) + } + }; + let phase = match event.phase() { + NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => TouchPhase::Moved, + }; + + let device_event = Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::MouseWheel { delta }, + }; + + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta, + phase, + modifiers: event_mods(event), + }, + }; + + state.pending_events.access(|pending| { + pending.queue_event(device_event); + pending.queue_event(window_event); + }); + } +} + +extern fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `pressureChangeWithEvent`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let pressure = event.pressure(); + let stage = event.stage(); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::TouchpadPressure { + device_id: DEVICE_ID, + pressure, + stage, + }, + }; + + state.pending_events.access(|pending| { + pending.queue_event(window_event); + }); + } +} + +// 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 -extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL { +extern fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL { YES } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 9ad7f28885..91af369caf 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -6,8 +6,8 @@ use std::{ use cocoa::{ appkit::{ self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy, - NSColor, NSRequestUserAttentionType, NSScreen, NSView, - NSWindow, NSWindowButton, NSWindowStyleMask, + NSColor, NSRequestUserAttentionType, NSScreen, NSView, NSWindow, + NSWindowButton, NSWindowStyleMask, }, base::{id, nil}, foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, @@ -305,6 +305,7 @@ impl UnownedWindow { let fullscreen = win_attribs.fullscreen.take(); let maximized = win_attribs.maximized; let visible = win_attribs.visible; + let decorations = win_attribs.decorations; let window = Arc::new(UnownedWindow { nsview, @@ -312,7 +313,7 @@ impl UnownedWindow { input_context, window_list: Arc::downgrade(&elw_target.window_list), shared_state: Mutex::new(win_attribs.into()), - decorations: Default::default(), + decorations: AtomicBool::new(decorations), cursor_hidden: Default::default(), }); @@ -683,6 +684,7 @@ impl UnownedWindow { #[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);