From be09639b6918f5b2c14fbd38eeb29274f3265a97 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 3 Feb 2017 23:05:57 +1100 Subject: [PATCH 01/14] Update the macOS backend to the new futures-compatible API. This is a follow up to the new API introduced in #20. This also fixes the issue where window resize events would not be emitted until the end of the resize. This PR fixese #39 by ensuring that the user callback given to either `EventsLoop::poll_events` or `EventsLoop::run_forever` can be called by each window delegate's resize callback directly. --- src/platform/macos/event.rs | 136 ----- src/platform/macos/events_loop.rs | 487 +++++++++++++++++ src/platform/macos/mod.rs | 852 +---------------------------- src/platform/macos/window.rs | 880 ++++++++++++++++++++++++++++++ src/window.rs | 1 - 5 files changed, 1387 insertions(+), 969 deletions(-) delete mode 100644 src/platform/macos/event.rs create mode 100644 src/platform/macos/events_loop.rs create mode 100644 src/platform/macos/window.rs diff --git a/src/platform/macos/event.rs b/src/platform/macos/event.rs deleted file mode 100644 index 31bed108..00000000 --- a/src/platform/macos/event.rs +++ /dev/null @@ -1,136 +0,0 @@ -use events; - -pub fn vkeycode_to_element(code: u16) -> Option { - Some(match code { - 0x00 => events::VirtualKeyCode::A, - 0x01 => events::VirtualKeyCode::S, - 0x02 => events::VirtualKeyCode::D, - 0x03 => events::VirtualKeyCode::F, - 0x04 => events::VirtualKeyCode::H, - 0x05 => events::VirtualKeyCode::G, - 0x06 => events::VirtualKeyCode::Z, - 0x07 => events::VirtualKeyCode::X, - 0x08 => events::VirtualKeyCode::C, - 0x09 => events::VirtualKeyCode::V, - //0x0a => World 1, - 0x0b => events::VirtualKeyCode::B, - 0x0c => events::VirtualKeyCode::Q, - 0x0d => events::VirtualKeyCode::W, - 0x0e => events::VirtualKeyCode::E, - 0x0f => events::VirtualKeyCode::R, - 0x10 => events::VirtualKeyCode::Y, - 0x11 => events::VirtualKeyCode::T, - 0x12 => events::VirtualKeyCode::Key1, - 0x13 => events::VirtualKeyCode::Key2, - 0x14 => events::VirtualKeyCode::Key3, - 0x15 => events::VirtualKeyCode::Key4, - 0x16 => events::VirtualKeyCode::Key6, - 0x17 => events::VirtualKeyCode::Key5, - 0x18 => events::VirtualKeyCode::Equals, - 0x19 => events::VirtualKeyCode::Key9, - 0x1a => events::VirtualKeyCode::Key7, - 0x1b => events::VirtualKeyCode::Minus, - 0x1c => events::VirtualKeyCode::Key8, - 0x1d => events::VirtualKeyCode::Key0, - 0x1e => events::VirtualKeyCode::RBracket, - 0x1f => events::VirtualKeyCode::O, - 0x20 => events::VirtualKeyCode::U, - 0x21 => events::VirtualKeyCode::LBracket, - 0x22 => events::VirtualKeyCode::I, - 0x23 => events::VirtualKeyCode::P, - 0x24 => events::VirtualKeyCode::Return, - 0x25 => events::VirtualKeyCode::L, - 0x26 => events::VirtualKeyCode::J, - 0x27 => events::VirtualKeyCode::Apostrophe, - 0x28 => events::VirtualKeyCode::K, - 0x29 => events::VirtualKeyCode::Semicolon, - 0x2a => events::VirtualKeyCode::Backslash, - 0x2b => events::VirtualKeyCode::Comma, - 0x2c => events::VirtualKeyCode::Slash, - 0x2d => events::VirtualKeyCode::N, - 0x2e => events::VirtualKeyCode::M, - 0x2f => events::VirtualKeyCode::Period, - 0x30 => events::VirtualKeyCode::Tab, - 0x31 => events::VirtualKeyCode::Space, - 0x32 => events::VirtualKeyCode::Grave, - 0x33 => events::VirtualKeyCode::Back, - //0x34 => unkown, - 0x35 => events::VirtualKeyCode::Escape, - 0x36 => events::VirtualKeyCode::RWin, - 0x37 => events::VirtualKeyCode::LWin, - 0x38 => events::VirtualKeyCode::LShift, - //0x39 => Caps lock, - //0x3a => Left alt, - 0x3b => events::VirtualKeyCode::LControl, - 0x3c => events::VirtualKeyCode::RShift, - //0x3d => Right alt, - 0x3e => events::VirtualKeyCode::RControl, - //0x3f => Fn key, - //0x40 => F17 Key, - 0x41 => events::VirtualKeyCode::Decimal, - //0x42 -> unkown, - 0x43 => events::VirtualKeyCode::Multiply, - //0x44 => unkown, - 0x45 => events::VirtualKeyCode::Add, - //0x46 => unkown, - 0x47 => events::VirtualKeyCode::Numlock, - //0x48 => KeypadClear, - 0x49 => events::VirtualKeyCode::VolumeUp, - 0x4a => events::VirtualKeyCode::VolumeDown, - 0x4b => events::VirtualKeyCode::Divide, - 0x4c => events::VirtualKeyCode::NumpadEnter, - //0x4d => unkown, - 0x4e => events::VirtualKeyCode::Subtract, - //0x4f => F18 key, - //0x50 => F19 Key, - 0x51 => events::VirtualKeyCode::NumpadEquals, - 0x52 => events::VirtualKeyCode::Numpad0, - 0x53 => events::VirtualKeyCode::Numpad1, - 0x54 => events::VirtualKeyCode::Numpad2, - 0x55 => events::VirtualKeyCode::Numpad3, - 0x56 => events::VirtualKeyCode::Numpad4, - 0x57 => events::VirtualKeyCode::Numpad5, - 0x58 => events::VirtualKeyCode::Numpad6, - 0x59 => events::VirtualKeyCode::Numpad7, - //0x5a => F20 Key, - 0x5b => events::VirtualKeyCode::Numpad8, - 0x5c => events::VirtualKeyCode::Numpad9, - //0x5d => unkown, - //0x5e => unkown, - //0x5f => unkown, - 0x60 => events::VirtualKeyCode::F5, - 0x61 => events::VirtualKeyCode::F6, - 0x62 => events::VirtualKeyCode::F7, - 0x63 => events::VirtualKeyCode::F3, - 0x64 => events::VirtualKeyCode::F8, - 0x65 => events::VirtualKeyCode::F9, - //0x66 => unkown, - 0x67 => events::VirtualKeyCode::F11, - //0x68 => unkown, - 0x69 => events::VirtualKeyCode::F13, - //0x6a => F16 Key, - 0x6b => events::VirtualKeyCode::F14, - //0x6c => unkown, - 0x6d => events::VirtualKeyCode::F10, - //0x6e => unkown, - 0x6f => events::VirtualKeyCode::F12, - //0x70 => unkown, - 0x71 => events::VirtualKeyCode::F15, - 0x72 => events::VirtualKeyCode::Insert, - 0x73 => events::VirtualKeyCode::Home, - 0x74 => events::VirtualKeyCode::PageUp, - 0x75 => events::VirtualKeyCode::Delete, - 0x76 => events::VirtualKeyCode::F4, - 0x77 => events::VirtualKeyCode::End, - 0x78 => events::VirtualKeyCode::F2, - 0x79 => events::VirtualKeyCode::PageDown, - 0x7a => events::VirtualKeyCode::F1, - 0x7b => events::VirtualKeyCode::Left, - 0x7c => events::VirtualKeyCode::Right, - 0x7d => events::VirtualKeyCode::Down, - 0x7e => events::VirtualKeyCode::Up, - //0x7f => unkown, - - _ => return None, - }) -} diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs new file mode 100644 index 00000000..096d9275 --- /dev/null +++ b/src/platform/macos/events_loop.rs @@ -0,0 +1,487 @@ +use cocoa::{self, appkit, foundation}; +use cocoa::appkit::{NSApplication, NSEvent, NSView, NSWindow}; +use events::{self, ElementState, Event, MouseButton, TouchPhase, WindowEvent}; +use super::window::Window; +use std; + + +pub struct EventsLoop { + pub windows: std::sync::Mutex>>, + pub pending_events: std::sync::Mutex>, + modifiers: std::sync::Mutex, + interrupted: std::sync::atomic::AtomicBool, + + /// The user's event callback given via either the `poll_events` or `run_forever` method. This + /// will only be `Some` for the duration of whichever of these methods has been called and will + /// always be `None` otherwise. + pub callback: std::sync::Mutex>> +} + +struct Modifiers { + shift_pressed: bool, + ctrl_pressed: bool, + win_pressed: bool, + alt_pressed: bool, +} + + +impl EventsLoop { + + pub fn new() -> Self { + let modifiers = Modifiers { + shift_pressed: false, + ctrl_pressed: false, + win_pressed: false, + alt_pressed: false, + }; + EventsLoop { + windows: std::sync::Mutex::new(Vec::new()), + pending_events: std::sync::Mutex::new(std::collections::VecDeque::new()), + modifiers: std::sync::Mutex::new(modifiers), + interrupted: std::sync::atomic::AtomicBool::new(false), + callback: std::sync::Mutex::new(None), + } + } + + pub fn poll_events(&self, callback: F) + where F: FnMut(Event), + { + unsafe { + self.store_callback(callback); + } + + // Loop as long as we have pending events to return. + loop { + // First, yield all pending events. + while let Some(event) = self.pending_events.lock().unwrap().pop_front() { + if let Ok(mut callback) = self.callback.lock() { + callback.as_mut().unwrap()(event); + } + } + + unsafe { + let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); + + // Poll for the next event, returning `nil` if there are none. + let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( + appkit::NSAnyEventMask.bits() | appkit::NSEventMaskPressure.bits(), + foundation::NSDate::distantPast(cocoa::base::nil), + foundation::NSDefaultRunLoopMode, + cocoa::base::YES); + + let event = self.ns_event_to_event(ns_event); + + let _: () = msg_send![pool, release]; + + match event { + // Call the user's callback. + Some(event) => if let Ok(mut callback) = self.callback.lock() { + callback.as_mut().unwrap()(event); + }, + None => break, + } + } + } + + // Drop the callback to enforce our guarantee that it will never live longer than the + // duration of this method. + self.callback.lock().unwrap().take(); + } + + pub fn run_forever(&self, callback: F) + where F: FnMut(Event) + { + self.interrupted.store(false, std::sync::atomic::Ordering::Relaxed); + + unsafe { + self.store_callback(callback); + } + + loop { + // First, yield all pending events. + while let Some(event) = self.pending_events.lock().unwrap().pop_front() { + if let Ok(mut callback) = self.callback.lock() { + callback.as_mut().unwrap()(event); + } + } + + unsafe { + let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); + + // Wait for the next event. Note that this function blocks during resize. + let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( + appkit::NSAnyEventMask.bits() | appkit::NSEventMaskPressure.bits(), + foundation::NSDate::distantFuture(cocoa::base::nil), + foundation::NSDefaultRunLoopMode, + cocoa::base::YES); + + // FIXME: we should check explicitly for `Awakened` events in `ns_event_to_event` + // and not just assume that an `Awakened` event occurred every time `None` is + // returned. Also, `Awakened` should be a variant of `Event`, not `WindowEvent`, so + // for now we do this hack with a fake ID. + let event = self.ns_event_to_event(ns_event) + .unwrap_or(Event::WindowEvent { + window_id: ::WindowId(super::WindowId(0)), + event: WindowEvent::Awakened, + }); + + if let Ok(mut callback) = self.callback.lock() { + callback.as_mut().unwrap()(event); + } + + let _: () = msg_send![pool, release]; + } + + if self.interrupted.load(std::sync::atomic::Ordering::Relaxed) { + break; + } + } + + // Drop the callback to enforce our guarantee that it will never live longer than the + // duration of this method. + self.callback.lock().unwrap().take(); + } + + pub fn interrupt(&self) { + self.interrupted.store(true, std::sync::atomic::Ordering::Relaxed); + // TODO: We should also signal to "awaken" the cocoa event loop here. + } + + // Here we store user's `callback` behind the `EventsLoop`'s mutex so that it may be safely + // shared between each of the window delegates. + // + // In order to store the `callback` within the `Eventsloop` as a trait object, we must + // `Box` the callback. Normally this would require that `F: 'static`, however we know that + // the callback cannot live longer than the lifetime of this method. Thus, we use `unsafe` + // to work around this requirement and enforce this guarantee ourselves. + // + // This should *only* be called at the beginning of `poll_events` and `run_forever`, both of + // which *must* drop the callback at the end of their scope. + unsafe fn store_callback(&self, callback: F) + where F: FnMut(Event) + { + let boxed: Box = Box::new(callback); + let boxed: Box = std::mem::transmute(boxed as Box); + *self.callback.lock().unwrap() = Some(boxed); + } + + // Convert some given `NSEvent` into a winit `Event`. + unsafe fn ns_event_to_event(&self, ns_event: cocoa::base::id) -> Option { + if ns_event == cocoa::base::nil { + return None; + } + + let event_type = ns_event.eventType(); + let window_id = super::window::get_window_id(ns_event.window()); + let windows = self.windows.lock().unwrap(); + let maybe_window = windows.iter().find(|window| window_id == window.id()); + + let window = match maybe_window { + Some(window) => window, + None => return None, + }; + + // FIXME: Document this. Why do we do this? + match event_type { + appkit::NSKeyDown => (), + _ => appkit::NSApp().sendEvent_(ns_event), + } + + let into_event = |window_event| Event::WindowEvent { + window_id: ::WindowId(window_id), + event: window_event, + }; + + match event_type { + + appkit::NSKeyDown => { + let mut events = std::collections::VecDeque::new(); + let received_c_str = foundation::NSString::UTF8String(ns_event.characters()); + let received_str = std::ffi::CStr::from_ptr(received_c_str); + for received_char in std::str::from_utf8(received_str.to_bytes()).unwrap().chars() { + let window_event = WindowEvent::ReceivedCharacter(received_char); + events.push_back(into_event(window_event)); + } + + let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event)); + let state = ElementState::Pressed; + let code = NSEvent::keyCode(ns_event) as u8; + let window_event = WindowEvent::KeyboardInput(state, code, vkey); + events.push_back(into_event(window_event)); + let event = events.pop_front(); + self.pending_events.lock().unwrap().extend(events.into_iter()); + event + }, + + appkit::NSKeyUp => { + let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event)); + + let state = ElementState::Released; + let code = NSEvent::keyCode(ns_event) as u8; + let window_event = WindowEvent::KeyboardInput(state, code, vkey); + Some(into_event(window_event)) + }, + + appkit::NSFlagsChanged => { + let mut modifiers = self.modifiers.lock().unwrap(); + + unsafe fn modifier_event(event: cocoa::base::id, + keymask: appkit::NSEventModifierFlags, + key: events::VirtualKeyCode, + key_pressed: bool) -> Option + { + if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) { + let state = ElementState::Pressed; + let code = NSEvent::keyCode(event) as u8; + let window_event = WindowEvent::KeyboardInput(state, code, Some(key)); + Some(window_event) + + } else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) { + let state = ElementState::Released; + let code = NSEvent::keyCode(event) as u8; + let window_event = WindowEvent::KeyboardInput(state, code, Some(key)); + Some(window_event) + + } else { + None + } + } + + let mut events = std::collections::VecDeque::new(); + if let Some(window_event) = modifier_event(ns_event, + appkit::NSShiftKeyMask, + events::VirtualKeyCode::LShift, + modifiers.shift_pressed) + { + modifiers.shift_pressed = !modifiers.shift_pressed; + events.push_back(into_event(window_event)); + } + + if let Some(window_event) = modifier_event(ns_event, + appkit::NSControlKeyMask, + events::VirtualKeyCode::LControl, + modifiers.ctrl_pressed) + { + modifiers.ctrl_pressed = !modifiers.ctrl_pressed; + events.push_back(into_event(window_event)); + } + + if let Some(window_event) = modifier_event(ns_event, + appkit::NSCommandKeyMask, + events::VirtualKeyCode::LWin, + modifiers.win_pressed) + { + modifiers.win_pressed = !modifiers.win_pressed; + events.push_back(into_event(window_event)); + } + + if let Some(window_event) = modifier_event(ns_event, + appkit::NSAlternateKeyMask, + events::VirtualKeyCode::LAlt, + modifiers.alt_pressed) + { + modifiers.alt_pressed = !modifiers.alt_pressed; + events.push_back(into_event(window_event)); + } + + let event = events.pop_front(); + self.pending_events.lock().unwrap().extend(events.into_iter()); + event + }, + + appkit::NSLeftMouseDown => { Some(into_event(WindowEvent::MouseInput(ElementState::Pressed, MouseButton::Left))) }, + appkit::NSLeftMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, MouseButton::Left))) }, + appkit::NSRightMouseDown => { Some(into_event(WindowEvent::MouseInput(ElementState::Pressed, MouseButton::Right))) }, + appkit::NSRightMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, MouseButton::Right))) }, + appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput(ElementState::Pressed, MouseButton::Middle))) }, + appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, MouseButton::Middle))) }, + appkit::NSMouseEntered => { Some(into_event(WindowEvent::MouseEntered)) }, + appkit::NSMouseExited => { Some(into_event(WindowEvent::MouseLeft)) }, + appkit::NSMouseMoved | + appkit::NSLeftMouseDragged | + appkit::NSOtherMouseDragged | + appkit::NSRightMouseDragged => { + let window_point = ns_event.locationInWindow(); + let ns_window: cocoa::base::id = msg_send![ns_event, window]; + let view_point = if ns_window == cocoa::base::nil { + let ns_size = foundation::NSSize::new(0.0, 0.0); + let ns_rect = foundation::NSRect::new(window_point, ns_size); + let window_rect = window.window.convertRectFromScreen_(ns_rect); + window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil) + } else { + window.view.convertPoint_fromView_(window_point, cocoa::base::nil) + }; + let view_rect = NSView::frame(*window.view); + let scale_factor = window.hidpi_factor(); + + let x = (scale_factor * view_point.x as f32) as i32; + let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as i32; + let window_event = WindowEvent::MouseMoved(x, y); + Some(into_event(window_event)) + }, + + appkit::NSScrollWheel => { + use events::MouseScrollDelta::{LineDelta, PixelDelta}; + let scale_factor = window.hidpi_factor(); + let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES { + PixelDelta(scale_factor * ns_event.scrollingDeltaX() as f32, + scale_factor * ns_event.scrollingDeltaY() as f32) + } else { + LineDelta(scale_factor * ns_event.scrollingDeltaX() as f32, + scale_factor * ns_event.scrollingDeltaY() as f32) + }; + let phase = match ns_event.phase() { + appkit::NSEventPhaseMayBegin | appkit::NSEventPhaseBegan => TouchPhase::Started, + appkit::NSEventPhaseEnded => TouchPhase::Ended, + _ => TouchPhase::Moved, + }; + let window_event = WindowEvent::MouseWheel(delta, phase); + Some(into_event(window_event)) + }, + appkit::NSEventTypePressure => { + let pressure = ns_event.pressure(); + let stage = ns_event.stage(); + let window_event = WindowEvent::TouchpadPressure(pressure, stage); + Some(into_event(window_event)) + }, + _ => None, + } + } + +} + + +fn to_virtual_key_code(code: u16) -> Option { + Some(match code { + 0x00 => events::VirtualKeyCode::A, + 0x01 => events::VirtualKeyCode::S, + 0x02 => events::VirtualKeyCode::D, + 0x03 => events::VirtualKeyCode::F, + 0x04 => events::VirtualKeyCode::H, + 0x05 => events::VirtualKeyCode::G, + 0x06 => events::VirtualKeyCode::Z, + 0x07 => events::VirtualKeyCode::X, + 0x08 => events::VirtualKeyCode::C, + 0x09 => events::VirtualKeyCode::V, + //0x0a => World 1, + 0x0b => events::VirtualKeyCode::B, + 0x0c => events::VirtualKeyCode::Q, + 0x0d => events::VirtualKeyCode::W, + 0x0e => events::VirtualKeyCode::E, + 0x0f => events::VirtualKeyCode::R, + 0x10 => events::VirtualKeyCode::Y, + 0x11 => events::VirtualKeyCode::T, + 0x12 => events::VirtualKeyCode::Key1, + 0x13 => events::VirtualKeyCode::Key2, + 0x14 => events::VirtualKeyCode::Key3, + 0x15 => events::VirtualKeyCode::Key4, + 0x16 => events::VirtualKeyCode::Key6, + 0x17 => events::VirtualKeyCode::Key5, + 0x18 => events::VirtualKeyCode::Equals, + 0x19 => events::VirtualKeyCode::Key9, + 0x1a => events::VirtualKeyCode::Key7, + 0x1b => events::VirtualKeyCode::Minus, + 0x1c => events::VirtualKeyCode::Key8, + 0x1d => events::VirtualKeyCode::Key0, + 0x1e => events::VirtualKeyCode::RBracket, + 0x1f => events::VirtualKeyCode::O, + 0x20 => events::VirtualKeyCode::U, + 0x21 => events::VirtualKeyCode::LBracket, + 0x22 => events::VirtualKeyCode::I, + 0x23 => events::VirtualKeyCode::P, + 0x24 => events::VirtualKeyCode::Return, + 0x25 => events::VirtualKeyCode::L, + 0x26 => events::VirtualKeyCode::J, + 0x27 => events::VirtualKeyCode::Apostrophe, + 0x28 => events::VirtualKeyCode::K, + 0x29 => events::VirtualKeyCode::Semicolon, + 0x2a => events::VirtualKeyCode::Backslash, + 0x2b => events::VirtualKeyCode::Comma, + 0x2c => events::VirtualKeyCode::Slash, + 0x2d => events::VirtualKeyCode::N, + 0x2e => events::VirtualKeyCode::M, + 0x2f => events::VirtualKeyCode::Period, + 0x30 => events::VirtualKeyCode::Tab, + 0x31 => events::VirtualKeyCode::Space, + 0x32 => events::VirtualKeyCode::Grave, + 0x33 => events::VirtualKeyCode::Back, + //0x34 => unkown, + 0x35 => events::VirtualKeyCode::Escape, + 0x36 => events::VirtualKeyCode::RWin, + 0x37 => events::VirtualKeyCode::LWin, + 0x38 => events::VirtualKeyCode::LShift, + //0x39 => Caps lock, + //0x3a => Left alt, + 0x3b => events::VirtualKeyCode::LControl, + 0x3c => events::VirtualKeyCode::RShift, + //0x3d => Right alt, + 0x3e => events::VirtualKeyCode::RControl, + //0x3f => Fn key, + //0x40 => F17 Key, + 0x41 => events::VirtualKeyCode::Decimal, + //0x42 -> unkown, + 0x43 => events::VirtualKeyCode::Multiply, + //0x44 => unkown, + 0x45 => events::VirtualKeyCode::Add, + //0x46 => unkown, + 0x47 => events::VirtualKeyCode::Numlock, + //0x48 => KeypadClear, + 0x49 => events::VirtualKeyCode::VolumeUp, + 0x4a => events::VirtualKeyCode::VolumeDown, + 0x4b => events::VirtualKeyCode::Divide, + 0x4c => events::VirtualKeyCode::NumpadEnter, + //0x4d => unkown, + 0x4e => events::VirtualKeyCode::Subtract, + //0x4f => F18 key, + //0x50 => F19 Key, + 0x51 => events::VirtualKeyCode::NumpadEquals, + 0x52 => events::VirtualKeyCode::Numpad0, + 0x53 => events::VirtualKeyCode::Numpad1, + 0x54 => events::VirtualKeyCode::Numpad2, + 0x55 => events::VirtualKeyCode::Numpad3, + 0x56 => events::VirtualKeyCode::Numpad4, + 0x57 => events::VirtualKeyCode::Numpad5, + 0x58 => events::VirtualKeyCode::Numpad6, + 0x59 => events::VirtualKeyCode::Numpad7, + //0x5a => F20 Key, + 0x5b => events::VirtualKeyCode::Numpad8, + 0x5c => events::VirtualKeyCode::Numpad9, + //0x5d => unkown, + //0x5e => unkown, + //0x5f => unkown, + 0x60 => events::VirtualKeyCode::F5, + 0x61 => events::VirtualKeyCode::F6, + 0x62 => events::VirtualKeyCode::F7, + 0x63 => events::VirtualKeyCode::F3, + 0x64 => events::VirtualKeyCode::F8, + 0x65 => events::VirtualKeyCode::F9, + //0x66 => unkown, + 0x67 => events::VirtualKeyCode::F11, + //0x68 => unkown, + 0x69 => events::VirtualKeyCode::F13, + //0x6a => F16 Key, + 0x6b => events::VirtualKeyCode::F14, + //0x6c => unkown, + 0x6d => events::VirtualKeyCode::F10, + //0x6e => unkown, + 0x6f => events::VirtualKeyCode::F12, + //0x70 => unkown, + 0x71 => events::VirtualKeyCode::F15, + 0x72 => events::VirtualKeyCode::Insert, + 0x73 => events::VirtualKeyCode::Home, + 0x74 => events::VirtualKeyCode::PageUp, + 0x75 => events::VirtualKeyCode::Delete, + 0x76 => events::VirtualKeyCode::F4, + 0x77 => events::VirtualKeyCode::End, + 0x78 => events::VirtualKeyCode::F2, + 0x79 => events::VirtualKeyCode::PageDown, + 0x7a => events::VirtualKeyCode::F1, + 0x7b => events::VirtualKeyCode::Left, + 0x7c => events::VirtualKeyCode::Right, + 0x7d => events::VirtualKeyCode::Down, + 0x7e => events::VirtualKeyCode::Up, + //0x7f => unkown, + + _ => return None, + }) +} diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 49bcca4c..da43cb5f 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -1,849 +1,37 @@ #![cfg(target_os = "macos")] -use {CreationError, WindowEvent as Event, MouseCursor, CursorState}; -use CreationError::OsError; -use libc; - -use WindowAttributes; -use native_monitor::NativeMonitorId; -use os::macos::ActivationPolicy; - -use objc::runtime::{Class, Object, Sel, BOOL, YES, NO}; -use objc::declare::ClassDecl; - -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSAutoreleasePool, NSDate, NSDefaultRunLoopMode, NSPoint, NSRect, NSSize, - NSString, NSUInteger}; -use cocoa::appkit::{self, NSApplication, NSColor, NSEvent, NSView, NSWindow}; - -use core_graphics::display::{CGAssociateMouseAndMouseCursorPosition, CGMainDisplayID, CGDisplayPixelsHigh, CGWarpMouseCursorPosition}; - -use std::ffi::CStr; -use std::collections::VecDeque; -use std::str::from_utf8; -use std::sync::Mutex; -use std::ops::Deref; -use std::os::raw::c_void; - -use os::macos::WindowExt; -use events::ElementState; -use events::{self, MouseButton, TouchPhase}; - -gen_api_transition!(); - +pub use self::events_loop::EventsLoop; pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor}; +pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window}; -mod monitor; -mod event; +use {CreationError}; -static mut SHIFT_PRESSED: bool = false; -static mut CTRL_PRESSED: bool = false; -static mut WIN_PRESSED: bool = false; -static mut ALT_PRESSED: bool = false; - -struct DelegateState { - view: IdRef, - window: IdRef, - resize_handler: Option, - pending_resize: Mutex>, - - /// Events that have been retreived with XLib but not dispatched with iterators yet - pending_events: Mutex>, +pub struct Window2 { + pub window: ::std::sync::Arc, } -struct WindowDelegate { - state: Box, - _this: IdRef, -} - -impl WindowDelegate { - /// Get the delegate class, initiailizing it neccessary - fn class() -> *const Class { - use std::os::raw::c_void; - use std::sync::{Once, ONCE_INIT}; - - extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { - unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); - let state = state as *mut DelegateState; - (*state).pending_events.lock().unwrap().push_back(Event::Closed); - } - YES - } - - extern fn window_did_resize(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); - let state = &mut *(state as *mut DelegateState); - - // need to notify context before (?) event - // let _: () = msg_send![*state.context, update]; - let rect = NSView::frame(*state.view); - let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32; - let width = (scale_factor * rect.size.width as f32) as u32; - let height = (scale_factor * rect.size.height as f32) as u32; - - if let Some(handler) = state.resize_handler { - (handler)(width, height); - } - *state.pending_resize.lock().unwrap() = Some((width, height)); - } - } - - extern fn window_did_become_key(this: &Object, _: Sel, _: id) { - unsafe { - // TODO: center the cursor if the window had mouse grab when it - // lost focus - - let state: *mut c_void = *this.get_ivar("glutinState"); - let state = state as *mut DelegateState; - (*state).pending_events.lock().unwrap().push_back(Event::Focused(true)); - } - } - - extern fn window_did_resign_key(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); - let state = state as *mut DelegateState; - (*state).pending_events.lock().unwrap().push_back(Event::Focused(false)); - } - } - - static mut DELEGATE_CLASS: *const Class = 0 as *const Class; - static INIT: Once = ONCE_INIT; - - INIT.call_once(|| unsafe { - // Create new NSWindowDelegate - let superclass = Class::get("NSObject").unwrap(); - let mut decl = ClassDecl::new("GlutinWindowDelegate", superclass).unwrap(); - - // Add callback methods - decl.add_method(sel!(windowShouldClose:), - window_should_close as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(windowDidResize:), - window_did_resize as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(windowDidBecomeKey:), - window_did_become_key as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidResignKey:), - window_did_resign_key as extern fn(&Object, Sel, id)); - - // Store internal state as user data - decl.add_ivar::<*mut c_void>("glutinState"); - - DELEGATE_CLASS = decl.register(); - }); - - unsafe { - DELEGATE_CLASS - } - } - - fn new(state: DelegateState) -> WindowDelegate { - // Box the state so we can give a pointer to it - let mut state = Box::new(state); - let state_ptr: *mut DelegateState = &mut *state; - unsafe { - let delegate = IdRef::new(msg_send![WindowDelegate::class(), new]); - - (&mut **delegate).set_ivar("glutinState", state_ptr as *mut ::std::os::raw::c_void); - let _: () = msg_send![*state.window, setDelegate:*delegate]; - - WindowDelegate { state: state, _this: delegate } - } - } -} - -impl Drop for WindowDelegate { - fn drop(&mut self) { - unsafe { - // Nil the window's delegate so it doesn't still reference us - let _: () = msg_send![*self.state.window, setDelegate:nil]; - } - } -} - -#[derive(Clone, Default)] -pub struct PlatformSpecificWindowBuilderAttributes { - pub activation_policy: ActivationPolicy, -} - -pub struct Window { - view: IdRef, - window: IdRef, - delegate: WindowDelegate, -} - -unsafe impl Send for Window {} -unsafe impl Sync for Window {} - -impl WindowExt for Window { +impl ::std::ops::Deref for Window2 { + type Target = Window; #[inline] - fn get_nswindow(&self) -> *mut c_void { - *self.window as *mut c_void - } - - #[inline] - fn get_nsview(&self) -> *mut c_void { - *self.view as *mut c_void + fn deref(&self) -> &Window { + &*self.window } } -#[derive(Clone)] -pub struct WindowProxy; +impl Window2 { -impl WindowProxy { - pub fn wakeup_event_loop(&self) { - unsafe { - let pool = NSAutoreleasePool::new(nil); - let event = - NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_( - nil, appkit::NSApplicationDefined, NSPoint::new(0.0, 0.0), appkit::NSEventModifierFlags::empty(), - 0.0, 0, nil, appkit::NSEventSubtype::NSApplicationActivatedEventType, 0, 0); - appkit::NSApp().postEvent_atStart_(event, NO); - pool.drain(); - } - } -} - -pub struct PollEventsIterator<'a> { - window: &'a Window, -} - -impl<'a> Iterator for PollEventsIterator<'a> { - type Item = Event; - - fn next(&mut self) -> Option { - if let Some((width, height)) = self.window.delegate.state.pending_resize.lock().unwrap().take() { - return Some(Event::Resized(width, height)); - } - - if let Some(ev) = self.window.delegate.state.pending_events.lock().unwrap().pop_front() { - return Some(ev); - } - - let event: Option; - unsafe { - let pool = NSAutoreleasePool::new(nil); - - let nsevent = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( - appkit::NSAnyEventMask.bits() | appkit::NSEventMaskPressure.bits(), - NSDate::distantPast(nil), - NSDefaultRunLoopMode, - YES); - event = NSEventToEvent(self.window, nsevent); - - let _: () = msg_send![pool, release]; - } - event - } -} - -pub struct WaitEventsIterator<'a> { - window: &'a Window, -} - -impl<'a> Iterator for WaitEventsIterator<'a> { - type Item = Event; - - fn next(&mut self) -> Option { - if let Some((width, height)) = self.window.delegate.state.pending_resize.lock().unwrap().take() { - return Some(Event::Resized(width, height)); - } - - if let Some(ev) = self.window.delegate.state.pending_events.lock().unwrap().pop_front() { - return Some(ev); - } - - let event: Option; - unsafe { - let pool = NSAutoreleasePool::new(nil); - - let nsevent = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( - appkit::NSAnyEventMask.bits() | appkit::NSEventMaskPressure.bits(), - NSDate::distantFuture(nil), - NSDefaultRunLoopMode, - YES); - event = NSEventToEvent(self.window, nsevent); - - let _: () = msg_send![pool, release]; - } - - if event.is_none() { - return Some(Event::Awakened); - } else { - return event; - } - } -} - -impl Window { - pub fn new(win_attribs: &WindowAttributes, - pl_attribs: &PlatformSpecificWindowBuilderAttributes) - -> Result + pub fn new(events_loop: ::std::sync::Arc, + attributes: &::WindowAttributes, + pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result { - // let app = match Window::create_app() { - let app = match Window::create_app(pl_attribs.activation_policy) { - Some(app) => app, - None => { return Err(OsError(format!("Couldn't create NSApplication"))); }, - }; - - let window = match Window::create_window(win_attribs) - { - Some(window) => window, - None => { return Err(OsError(format!("Couldn't create NSWindow"))); }, - }; - let view = match Window::create_view(*window) { - Some(view) => view, - None => { return Err(OsError(format!("Couldn't create NSView"))); }, - }; - - unsafe { - if win_attribs.transparent { - (*window as id).setOpaque_(NO); - (*window as id).setBackgroundColor_(NSColor::clearColor(nil)); - } - - app.activateIgnoringOtherApps_(YES); - if win_attribs.visible { - window.makeKeyAndOrderFront_(nil); - } else { - window.makeKeyWindow(); - } - - if let Some((width, height)) = win_attribs.min_dimensions { - nswindow_set_min_dimensions(window.0, width.into(), height.into()); - } - - if let Some((width, height)) = win_attribs.max_dimensions { - nswindow_set_max_dimensions(window.0, width.into(), height.into()); - } - } - - let ds = DelegateState { - view: view.clone(), - window: window.clone(), - resize_handler: None, - pending_resize: Mutex::new(None), - pending_events: Mutex::new(VecDeque::new()), - }; - - let window = Window { - view: view, - window: window, - delegate: WindowDelegate::new(ds), - }; - - Ok(window) + let weak_events_loop = ::std::sync::Arc::downgrade(&events_loop); + let window = ::std::sync::Arc::new(try!(Window::new(weak_events_loop, attributes, pl_attribs))); + events_loop.windows.lock().unwrap().push(window.clone()); + Ok(Window2 { window: window }) } - fn create_app(activation_policy: ActivationPolicy) -> Option { - unsafe { - let app = appkit::NSApp(); - if app == nil { - None - } else { - app.setActivationPolicy_(activation_policy.into()); - app.finishLaunching(); - Some(app) - } - } - } - - fn create_window(attrs: &WindowAttributes) -> Option { - unsafe { - let screen = match attrs.monitor { - Some(ref monitor_id) => { - let native_id = match monitor_id.get_native_identifier() { - NativeMonitorId::Numeric(num) => num, - _ => panic!("OS X monitors should always have a numeric native ID") - }; - let matching_screen = { - let screens = appkit::NSScreen::screens(nil); - let count: NSUInteger = msg_send![screens, count]; - let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); - let mut matching_screen: Option = None; - for i in 0..count { - let screen = msg_send![screens, objectAtIndex:i as NSUInteger]; - let device_description = appkit::NSScreen::deviceDescription(screen); - let value: id = msg_send![device_description, objectForKey:*key]; - if value != nil { - let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue]; - if screen_number as u32 == native_id { - matching_screen = Some(screen); - break; - } - } - } - matching_screen - }; - Some(matching_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) - }, - None => None - }; - let frame = match screen { - Some(screen) => appkit::NSScreen::frame(screen), - None => { - let (width, height) = attrs.dimensions.unwrap_or((800, 600)); - NSRect::new(NSPoint::new(0., 0.), NSSize::new(width as f64, height as f64)) - } - }; - - let masks = if screen.is_some() || attrs.transparent { - // Fullscreen or transparent window - appkit::NSBorderlessWindowMask as NSUInteger | - appkit::NSResizableWindowMask as NSUInteger | - appkit::NSTitledWindowMask as NSUInteger - } else if attrs.decorations { - // Classic opaque window with titlebar - appkit::NSClosableWindowMask as NSUInteger | - appkit::NSMiniaturizableWindowMask as NSUInteger | - appkit::NSResizableWindowMask as NSUInteger | - appkit::NSTitledWindowMask as NSUInteger - } else { - // Opaque window without a titlebar - appkit::NSClosableWindowMask as NSUInteger | - appkit::NSMiniaturizableWindowMask as NSUInteger | - appkit::NSResizableWindowMask as NSUInteger | - appkit::NSTitledWindowMask as NSUInteger | - appkit::NSFullSizeContentViewWindowMask as NSUInteger - }; - - let window = IdRef::new(NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_( - frame, - masks, - appkit::NSBackingStoreBuffered, - NO, - )); - window.non_nil().map(|window| { - let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title)); - window.setReleasedWhenClosed_(NO); - window.setTitle_(*title); - window.setAcceptsMouseMovedEvents_(YES); - - if !attrs.decorations { - window.setTitleVisibility_(appkit::NSWindowTitleVisibility::NSWindowTitleHidden); - window.setTitlebarAppearsTransparent_(YES); - } - - if screen.is_some() { - window.setLevel_(appkit::NSMainMenuWindowLevel as i64 + 1); - } - else { - window.center(); - } - window - }) - } - } - - fn create_view(window: id) -> Option { - unsafe { - let view = IdRef::new(NSView::alloc(nil).init()); - view.non_nil().map(|view| { - view.setWantsBestResolutionOpenGLSurface_(YES); - window.setContentView_(*view); - view - }) - } - } - - pub fn set_title(&self, title: &str) { - unsafe { - let title = IdRef::new(NSString::alloc(nil).init_str(title)); - self.window.setTitle_(*title); - } - } - - #[inline] - pub fn show(&self) { - unsafe { NSWindow::makeKeyAndOrderFront_(*self.window, nil); } - } - - #[inline] - pub fn hide(&self) { - unsafe { NSWindow::orderOut_(*self.window, nil); } - } - - pub fn get_position(&self) -> Option<(i32, i32)> { - unsafe { - let content_rect = NSWindow::contentRectForFrameRect_(*self.window, NSWindow::frame(*self.window)); - - // TODO: consider extrapolating the calculations for the y axis to - // a private method - Some((content_rect.origin.x as i32, (CGDisplayPixelsHigh(CGMainDisplayID()) as f64 - (content_rect.origin.y + content_rect.size.height)) as i32)) - } - } - - pub fn set_position(&self, x: i32, y: i32) { - unsafe { - let frame = NSWindow::frame(*self.view); - - // NOTE: `setFrameOrigin` might not give desirable results when - // setting window, as it treats bottom left as origin. - // `setFrameTopLeftPoint` treats top left as origin (duh), but - // does not equal the value returned by `get_window_position` - // (there is a difference by 22 for me on yosemite) - - // TODO: consider extrapolating the calculations for the y axis to - // a private method - let dummy = NSRect::new(NSPoint::new(x as f64, CGDisplayPixelsHigh(CGMainDisplayID()) as f64 - (frame.size.height + y as f64)), NSSize::new(0f64, 0f64)); - let conv = NSWindow::frameRectForContentRect_(*self.window, dummy); - - // NSWindow::setFrameTopLeftPoint_(*self.window, conv.origin); - NSWindow::setFrameOrigin_(*self.window, conv.origin); - } - } - - #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { - unsafe { - let view_frame = NSView::frame(*self.view); - Some((view_frame.size.width as u32, view_frame.size.height as u32)) - } - } - - #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { - unsafe { - let window_frame = NSWindow::frame(*self.window); - Some((window_frame.size.width as u32, window_frame.size.height as u32)) - } - } - - #[inline] - pub fn set_inner_size(&self, width: u32, height: u32) { - unsafe { - NSWindow::setContentSize_(*self.window, NSSize::new(width as f64, height as f64)); - } - } - - #[inline] - pub fn create_window_proxy(&self) -> WindowProxy { - WindowProxy - } - - #[inline] - pub fn poll_events(&self) -> PollEventsIterator { - PollEventsIterator { - window: self - } - } - - #[inline] - pub fn wait_events(&self) -> WaitEventsIterator { - WaitEventsIterator { - window: self - } - } - - unsafe fn modifier_event(event: id, keymask: appkit::NSEventModifierFlags, key: events::VirtualKeyCode, key_pressed: bool) -> Option { - if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) { - return Some(Event::KeyboardInput(ElementState::Pressed, NSEvent::keyCode(event) as u8, Some(key))); - } else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) { - return Some(Event::KeyboardInput(ElementState::Released, NSEvent::keyCode(event) as u8, Some(key))); - } - - return None; - } - - #[inline] - pub fn platform_display(&self) -> *mut libc::c_void { - unimplemented!() - } - - #[inline] - pub fn platform_window(&self) -> *mut libc::c_void { - *self.window as *mut libc::c_void - } - - #[inline] - pub fn set_window_resize_callback(&mut self, callback: Option) { - self.delegate.state.resize_handler = callback; - } - - pub fn set_cursor(&self, cursor: MouseCursor) { - 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::NoneCursor | - MouseCursor::Wait | MouseCursor::Progress | MouseCursor::Help | - MouseCursor::Move | MouseCursor::AllScroll | MouseCursor::ZoomIn | - MouseCursor::ZoomOut => "arrowCursor", - }; - let sel = Sel::register(cursor_name); - let cls = Class::get("NSCursor").unwrap(); - unsafe { - use objc::Message; - let cursor: id = cls.send_message(sel, ()).unwrap(); - let _: () = msg_send![cursor, set]; - } - } - - pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> { - let cls = Class::get("NSCursor").unwrap(); - - // TODO: Check for errors. - match state { - CursorState::Normal => { - let _: () = unsafe { msg_send![cls, unhide] }; - let _: i32 = unsafe { CGAssociateMouseAndMouseCursorPosition(true) }; - Ok(()) - }, - CursorState::Hide => { - let _: () = unsafe { msg_send![cls, hide] }; - Ok(()) - }, - CursorState::Grab => { - let _: i32 = unsafe { CGAssociateMouseAndMouseCursorPosition(false) }; - Ok(()) - } - } - } - - #[inline] - pub fn hidpi_factor(&self) -> f32 { - unsafe { - NSWindow::backingScaleFactor(*self.window) as f32 - } - } - - #[inline] - pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { - let (window_x, window_y) = self.get_position().unwrap_or((0, 0)); - let (cursor_x, cursor_y) = (window_x + x, window_y + y); - - unsafe { - // TODO: Check for errors. - let _ = CGWarpMouseCursorPosition(appkit::CGPoint { - x: cursor_x as appkit::CGFloat, - y: cursor_y as appkit::CGFloat, - }); - let _ = CGAssociateMouseAndMouseCursorPosition(true); - } - - Ok(()) - } } -unsafe fn nswindow_set_min_dimensions( - window: V, min_width: f64, min_height: f64) -{ - window.setMinSize_(NSSize { - width: min_width, - height: min_height, - }); - // If necessary, resize the window to match constraint - let mut current_rect = NSWindow::frame(window); - if current_rect.size.width < min_width { - current_rect.size.width = min_width; - window.setFrame_display_(current_rect, 0) - } - if current_rect.size.height < min_height { - // The origin point of a rectangle is at its bottom left in Cocoa. To - // ensure the window's top-left point remains the same: - current_rect.origin.y += - current_rect.size.height - min_height; - - current_rect.size.height = min_height; - window.setFrame_display_(current_rect, 0) - } -} - -unsafe fn nswindow_set_max_dimensions( - window: V, max_width: f64, max_height: f64) -{ - window.setMaxSize_(NSSize { - width: max_width, - height: max_height, - }); - // If necessary, resize the window to match constraint - let mut current_rect = NSWindow::frame(window); - if current_rect.size.width > max_width { - current_rect.size.width = max_width; - window.setFrame_display_(current_rect, 0) - } - if current_rect.size.height > max_height { - // The origin point of a rectangle is at its bottom left in - // Cocoa. To ensure the window's top-left point remains the - // same: - current_rect.origin.y += - current_rect.size.height - max_height; - - current_rect.size.height = max_height; - window.setFrame_display_(current_rect, 0) - } -} - -struct IdRef(id); - -impl IdRef { - fn new(i: id) -> IdRef { - IdRef(i) - } - - #[allow(dead_code)] - fn retain(i: id) -> IdRef { - if i != nil { - let _: id = unsafe { msg_send![i, retain] }; - } - IdRef(i) - } - - fn non_nil(self) -> Option { - if self.0 == nil { None } else { Some(self) } - } -} - -impl Drop for IdRef { - fn drop(&mut self) { - if self.0 != nil { - let _: () = unsafe { msg_send![self.0, release] }; - } - } -} - -impl Deref for IdRef { - type Target = id; - fn deref<'a>(&'a self) -> &'a id { - &self.0 - } -} - -impl Clone for IdRef { - fn clone(&self) -> IdRef { - if self.0 != nil { - let _: id = unsafe { msg_send![self.0, retain] }; - } - IdRef(self.0) - } -} - -#[allow(non_snake_case, non_upper_case_globals)] -unsafe fn NSEventToEvent(window: &Window, nsevent: id) -> Option { - if nsevent == nil { return None; } - - let event_type = nsevent.eventType(); - appkit::NSApp().sendEvent_(if let appkit::NSKeyDown = event_type { nil } else { nsevent }); - - match event_type { - appkit::NSLeftMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Left)) }, - appkit::NSLeftMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Left)) }, - appkit::NSRightMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Right)) }, - appkit::NSRightMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Right)) }, - appkit::NSOtherMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Middle)) }, - appkit::NSOtherMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Middle)) }, - appkit::NSMouseEntered => { Some(Event::MouseEntered) }, - appkit::NSMouseExited => { Some(Event::MouseLeft) }, - appkit::NSMouseMoved | - appkit::NSLeftMouseDragged | - appkit::NSOtherMouseDragged | - appkit::NSRightMouseDragged => { - let window_point = nsevent.locationInWindow(); - let cWindow: id = msg_send![nsevent, window]; - let view_point = if cWindow == nil { - let window_rect = window.window.convertRectFromScreen_(NSRect::new(window_point, NSSize::new(0.0, 0.0))); - window.view.convertPoint_fromView_(window_rect.origin, nil) - } else { - window.view.convertPoint_fromView_(window_point, nil) - }; - let view_rect = NSView::frame(*window.view); - let scale_factor = window.hidpi_factor(); - - Some(Event::MouseMoved((scale_factor * view_point.x as f32) as i32, - (scale_factor * (view_rect.size.height - view_point.y) as f32) as i32)) - }, - appkit::NSKeyDown => { - let mut events = VecDeque::new(); - let received_c_str = nsevent.characters().UTF8String(); - let received_str = CStr::from_ptr(received_c_str); - for received_char in from_utf8(received_str.to_bytes()).unwrap().chars() { - events.push_back(Event::ReceivedCharacter(received_char)); - } - - let vkey = event::vkeycode_to_element(NSEvent::keyCode(nsevent)); - events.push_back(Event::KeyboardInput(ElementState::Pressed, NSEvent::keyCode(nsevent) as u8, vkey)); - let event = events.pop_front(); - window.delegate.state.pending_events.lock().unwrap().extend(events.into_iter()); - event - }, - appkit::NSKeyUp => { - let vkey = event::vkeycode_to_element(NSEvent::keyCode(nsevent)); - - Some(Event::KeyboardInput(ElementState::Released, NSEvent::keyCode(nsevent) as u8, vkey)) - }, - appkit::NSFlagsChanged => { - let mut events = VecDeque::new(); - let shift_modifier = Window::modifier_event(nsevent, appkit::NSShiftKeyMask, events::VirtualKeyCode::LShift, SHIFT_PRESSED); - if shift_modifier.is_some() { - SHIFT_PRESSED = !SHIFT_PRESSED; - events.push_back(shift_modifier.unwrap()); - } - let ctrl_modifier = Window::modifier_event(nsevent, appkit::NSControlKeyMask, events::VirtualKeyCode::LControl, CTRL_PRESSED); - if ctrl_modifier.is_some() { - CTRL_PRESSED = !CTRL_PRESSED; - events.push_back(ctrl_modifier.unwrap()); - } - let win_modifier = Window::modifier_event(nsevent, appkit::NSCommandKeyMask, events::VirtualKeyCode::LWin, WIN_PRESSED); - if win_modifier.is_some() { - WIN_PRESSED = !WIN_PRESSED; - events.push_back(win_modifier.unwrap()); - } - let alt_modifier = Window::modifier_event(nsevent, appkit::NSAlternateKeyMask, events::VirtualKeyCode::LAlt, ALT_PRESSED); - if alt_modifier.is_some() { - ALT_PRESSED = !ALT_PRESSED; - events.push_back(alt_modifier.unwrap()); - } - let event = events.pop_front(); - window.delegate.state.pending_events.lock().unwrap().extend(events.into_iter()); - event - }, - appkit::NSScrollWheel => { - use events::MouseScrollDelta::{LineDelta, PixelDelta}; - let scale_factor = window.hidpi_factor(); - let delta = if nsevent.hasPreciseScrollingDeltas() == YES { - PixelDelta(scale_factor * nsevent.scrollingDeltaX() as f32, - scale_factor * nsevent.scrollingDeltaY() as f32) - } else { - LineDelta(scale_factor * nsevent.scrollingDeltaX() as f32, - scale_factor * nsevent.scrollingDeltaY() as f32) - }; - let phase = match nsevent.phase() { - appkit::NSEventPhaseMayBegin | appkit::NSEventPhaseBegan => TouchPhase::Started, - appkit::NSEventPhaseEnded => TouchPhase::Ended, - _ => TouchPhase::Moved, - }; - Some(Event::MouseWheel(delta, phase)) - }, - appkit::NSEventTypePressure => { - Some(Event::TouchpadPressure(nsevent.pressure(), nsevent.stage())) - }, - appkit::NSApplicationDefined => { - match nsevent.subtype() { - appkit::NSEventSubtype::NSApplicationActivatedEventType => { Some(Event::Awakened) } - _ => { None } - } - }, - _ => { None }, - } -} +mod events_loop; +mod monitor; +mod window; diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs new file mode 100644 index 00000000..df020bb6 --- /dev/null +++ b/src/platform/macos/window.rs @@ -0,0 +1,880 @@ +use {CreationError, WindowEvent as Event, MouseCursor, CursorState}; +use CreationError::OsError; +use libc; + +use WindowAttributes; +use native_monitor::NativeMonitorId; +use os::macos::ActivationPolicy; + +use objc; +use objc::runtime::{Class, Object, Sel, BOOL, YES, NO}; +use objc::declare::ClassDecl; + +use cocoa; +use cocoa::base::{id, nil}; +use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger}; +use cocoa::appkit::{self, NSApplication, NSColor, NSView, NSWindow}; + +use core_graphics::display::{CGAssociateMouseAndMouseCursorPosition, CGMainDisplayID, CGDisplayPixelsHigh, CGWarpMouseCursorPosition}; + +use std; +use std::ops::Deref; +use std::os::raw::c_void; + +use os::macos::WindowExt; + + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(pub usize); + +struct DelegateState { + view: IdRef, + window: IdRef, + events_loop: std::sync::Weak, +} + +pub struct WindowDelegate { + state: Box, + _this: IdRef, +} + +impl WindowDelegate { + /// Get the delegate class, initiailizing it neccessary + fn class() -> *const Class { + use std::os::raw::c_void; + + // Emits an event via the `EventsLoop`. + // + // If the `EventsLoop` callback is `Some` the event is immediately emitted via the callback. + // + // If it is `None`, it is pushed to the back of the `EventsLoop`'s `pending_events` deque. + fn emit_event(state: &mut DelegateState, window_event: Event) { + let window_id = get_window_id(*state.window); + let event = ::Event::WindowEvent { + window_id: ::WindowId(window_id), + event: window_event, + }; + + if let Some(events_loop) = state.events_loop.upgrade() { + if let Ok(mut callback) = events_loop.callback.lock() { + if let Some(callback) = callback.as_mut() { + callback(event); + return; + } + } + + if let Ok(mut pending_events) = events_loop.pending_events.lock() { + pending_events.push_back(event); + } + } + } + + extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + emit_event(state, Event::Closed); + } + YES + } + + extern fn window_did_resize(this: &Object, _: Sel, _: id) { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + let rect = NSView::frame(*state.view); + let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32; + let width = (scale_factor * rect.size.width as f32) as u32; + let height = (scale_factor * rect.size.height as f32) as u32; + emit_event(state, Event::Resized(width, height)); + } + } + + extern fn window_did_become_key(this: &Object, _: Sel, _: id) { + unsafe { + // TODO: center the cursor if the window had mouse grab when it + // lost focus + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + emit_event(state, Event::Focused(true)); + } + } + + extern fn window_did_resign_key(this: &Object, _: Sel, _: id) { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + emit_event(state, Event::Focused(false)); + } + } + + static mut DELEGATE_CLASS: *const Class = 0 as *const Class; + static INIT: std::sync::Once = std::sync::ONCE_INIT; + + INIT.call_once(|| unsafe { + // Create new NSWindowDelegate + let superclass = Class::get("NSObject").unwrap(); + let mut decl = ClassDecl::new("GlutinWindowDelegate", superclass).unwrap(); + + // Add callback methods + decl.add_method(sel!(windowShouldClose:), + window_should_close as extern fn(&Object, Sel, id) -> BOOL); + decl.add_method(sel!(windowDidResize:), + window_did_resize as extern fn(&Object, Sel, id)); + + decl.add_method(sel!(windowDidBecomeKey:), + window_did_become_key as extern fn(&Object, Sel, id)); + decl.add_method(sel!(windowDidResignKey:), + window_did_resign_key as extern fn(&Object, Sel, id)); + + // Store internal state as user data + decl.add_ivar::<*mut c_void>("glutinState"); + + DELEGATE_CLASS = decl.register(); + }); + + unsafe { + DELEGATE_CLASS + } + } + + fn new(state: DelegateState) -> WindowDelegate { + // Box the state so we can give a pointer to it + let mut state = Box::new(state); + let state_ptr: *mut DelegateState = &mut *state; + unsafe { + let delegate = IdRef::new(msg_send![WindowDelegate::class(), new]); + + (&mut **delegate).set_ivar("glutinState", state_ptr as *mut ::std::os::raw::c_void); + let _: () = msg_send![*state.window, setDelegate:*delegate]; + + WindowDelegate { state: state, _this: delegate } + } + } +} + +impl Drop for WindowDelegate { + fn drop(&mut self) { + unsafe { + // Nil the window's delegate so it doesn't still reference us + let _: () = msg_send![*self.state.window, setDelegate:nil]; + } + } +} + +#[derive(Clone, Default)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub activation_policy: ActivationPolicy, +} + +pub struct Window { + pub view: IdRef, + pub window: IdRef, + pub delegate: WindowDelegate, +} + +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +impl Drop for Window { + fn drop(&mut self) { + // Remove this window from the `EventLoop`s list of windows. + let id = self.id(); + if let Some(ev) = self.delegate.state.events_loop.upgrade() { + let mut windows = ev.windows.lock().unwrap(); + windows.retain(|w| w.id() != id) + } + } +} + +impl WindowExt for Window { + #[inline] + fn get_nswindow(&self) -> *mut c_void { + *self.window as *mut c_void + } + + #[inline] + fn get_nsview(&self) -> *mut c_void { + *self.view as *mut c_void + } +} + +impl Window { + pub fn new(events_loop: std::sync::Weak, + win_attribs: &WindowAttributes, + pl_attribs: &PlatformSpecificWindowBuilderAttributes) + -> Result + { + let app = match Window::create_app(pl_attribs.activation_policy) { + Some(app) => app, + None => { return Err(OsError(format!("Couldn't create NSApplication"))); }, + }; + + let window = match Window::create_window(win_attribs) + { + Some(window) => window, + None => { return Err(OsError(format!("Couldn't create NSWindow"))); }, + }; + let view = match Window::create_view(*window) { + Some(view) => view, + None => { return Err(OsError(format!("Couldn't create NSView"))); }, + }; + + unsafe { + if win_attribs.transparent { + (*window as id).setOpaque_(NO); + (*window as id).setBackgroundColor_(NSColor::clearColor(nil)); + } + + app.activateIgnoringOtherApps_(YES); + if win_attribs.visible { + window.makeKeyAndOrderFront_(nil); + } else { + window.makeKeyWindow(); + } + + if let Some((width, height)) = win_attribs.min_dimensions { + nswindow_set_min_dimensions(window.0, width.into(), height.into()); + } + + if let Some((width, height)) = win_attribs.max_dimensions { + nswindow_set_max_dimensions(window.0, width.into(), height.into()); + } + } + + let ds = DelegateState { + view: view.clone(), + window: window.clone(), + events_loop: events_loop, + }; + + let window = Window { + view: view, + window: window, + delegate: WindowDelegate::new(ds), + }; + + Ok(window) + } + + pub fn id(&self) -> Id { + get_window_id(*self.window) + } + + fn create_app(activation_policy: ActivationPolicy) -> Option { + unsafe { + let app = appkit::NSApp(); + if app == nil { + None + } else { + app.setActivationPolicy_(activation_policy.into()); + app.finishLaunching(); + Some(app) + } + } + } + + fn create_window(attrs: &WindowAttributes) -> Option { + unsafe { + let screen = match attrs.monitor { + Some(ref monitor_id) => { + let native_id = match monitor_id.get_native_identifier() { + NativeMonitorId::Numeric(num) => num, + _ => panic!("OS X monitors should always have a numeric native ID") + }; + let matching_screen = { + let screens = appkit::NSScreen::screens(nil); + let count: NSUInteger = msg_send![screens, count]; + let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); + let mut matching_screen: Option = None; + for i in 0..count { + let screen = msg_send![screens, objectAtIndex:i as NSUInteger]; + let device_description = appkit::NSScreen::deviceDescription(screen); + let value: id = msg_send![device_description, objectForKey:*key]; + if value != nil { + let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue]; + if screen_number as u32 == native_id { + matching_screen = Some(screen); + break; + } + } + } + matching_screen + }; + Some(matching_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) + }, + None => None + }; + let frame = match screen { + Some(screen) => appkit::NSScreen::frame(screen), + None => { + let (width, height) = attrs.dimensions.unwrap_or((800, 600)); + NSRect::new(NSPoint::new(0., 0.), NSSize::new(width as f64, height as f64)) + } + }; + + let masks = if screen.is_some() || attrs.transparent { + // Fullscreen or transparent window + appkit::NSBorderlessWindowMask as NSUInteger | + appkit::NSResizableWindowMask as NSUInteger | + appkit::NSTitledWindowMask as NSUInteger + } else if attrs.decorations { + // Classic opaque window with titlebar + appkit::NSClosableWindowMask as NSUInteger | + appkit::NSMiniaturizableWindowMask as NSUInteger | + appkit::NSResizableWindowMask as NSUInteger | + appkit::NSTitledWindowMask as NSUInteger + } else { + // Opaque window without a titlebar + appkit::NSClosableWindowMask as NSUInteger | + appkit::NSMiniaturizableWindowMask as NSUInteger | + appkit::NSResizableWindowMask as NSUInteger | + appkit::NSTitledWindowMask as NSUInteger | + appkit::NSFullSizeContentViewWindowMask as NSUInteger + }; + + let window = IdRef::new(NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_( + frame, + masks, + appkit::NSBackingStoreBuffered, + NO, + )); + window.non_nil().map(|window| { + let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title)); + window.setReleasedWhenClosed_(NO); + window.setTitle_(*title); + window.setAcceptsMouseMovedEvents_(YES); + + if !attrs.decorations { + window.setTitleVisibility_(appkit::NSWindowTitleVisibility::NSWindowTitleHidden); + window.setTitlebarAppearsTransparent_(YES); + } + + if screen.is_some() { + window.setLevel_(appkit::NSMainMenuWindowLevel as i64 + 1); + } + else { + window.center(); + } + window + }) + } + } + + fn create_view(window: id) -> Option { + unsafe { + let view = IdRef::new(NSView::alloc(nil).init()); + view.non_nil().map(|view| { + view.setWantsBestResolutionOpenGLSurface_(YES); + window.setContentView_(*view); + view + }) + } + } + + pub fn set_title(&self, title: &str) { + unsafe { + let title = IdRef::new(NSString::alloc(nil).init_str(title)); + self.window.setTitle_(*title); + } + } + + #[inline] + pub fn show(&self) { + unsafe { NSWindow::makeKeyAndOrderFront_(*self.window, nil); } + } + + #[inline] + pub fn hide(&self) { + unsafe { NSWindow::orderOut_(*self.window, nil); } + } + + pub fn get_position(&self) -> Option<(i32, i32)> { + unsafe { + let content_rect = NSWindow::contentRectForFrameRect_(*self.window, NSWindow::frame(*self.window)); + + // TODO: consider extrapolating the calculations for the y axis to + // a private method + Some((content_rect.origin.x as i32, (CGDisplayPixelsHigh(CGMainDisplayID()) as f64 - (content_rect.origin.y + content_rect.size.height)) as i32)) + } + } + + pub fn set_position(&self, x: i32, y: i32) { + unsafe { + let frame = NSWindow::frame(*self.view); + + // NOTE: `setFrameOrigin` might not give desirable results when + // setting window, as it treats bottom left as origin. + // `setFrameTopLeftPoint` treats top left as origin (duh), but + // does not equal the value returned by `get_window_position` + // (there is a difference by 22 for me on yosemite) + + // TODO: consider extrapolating the calculations for the y axis to + // a private method + let dummy = NSRect::new(NSPoint::new(x as f64, CGDisplayPixelsHigh(CGMainDisplayID()) as f64 - (frame.size.height + y as f64)), NSSize::new(0f64, 0f64)); + let conv = NSWindow::frameRectForContentRect_(*self.window, dummy); + + // NSWindow::setFrameTopLeftPoint_(*self.window, conv.origin); + NSWindow::setFrameOrigin_(*self.window, conv.origin); + } + } + + #[inline] + pub fn get_inner_size(&self) -> Option<(u32, u32)> { + unsafe { + let view_frame = NSView::frame(*self.view); + Some((view_frame.size.width as u32, view_frame.size.height as u32)) + } + } + + #[inline] + pub fn get_outer_size(&self) -> Option<(u32, u32)> { + unsafe { + let window_frame = NSWindow::frame(*self.window); + Some((window_frame.size.width as u32, window_frame.size.height as u32)) + } + } + + #[inline] + pub fn set_inner_size(&self, width: u32, height: u32) { + unsafe { + NSWindow::setContentSize_(*self.window, NSSize::new(width as f64, height as f64)); + } + } + + #[inline] + pub fn platform_display(&self) -> *mut libc::c_void { + unimplemented!() + } + + #[inline] + pub fn platform_window(&self) -> *mut libc::c_void { + *self.window as *mut libc::c_void + } + + pub fn set_cursor(&self, cursor: MouseCursor) { + 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::NoneCursor | + MouseCursor::Wait | MouseCursor::Progress | MouseCursor::Help | + MouseCursor::Move | MouseCursor::AllScroll | MouseCursor::ZoomIn | + MouseCursor::ZoomOut => "arrowCursor", + }; + let sel = Sel::register(cursor_name); + let cls = Class::get("NSCursor").unwrap(); + unsafe { + use objc::Message; + let cursor: id = cls.send_message(sel, ()).unwrap(); + let _: () = msg_send![cursor, set]; + } + } + + pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> { + let cls = Class::get("NSCursor").unwrap(); + + // TODO: Check for errors. + match state { + CursorState::Normal => { + let _: () = unsafe { msg_send![cls, unhide] }; + let _: i32 = unsafe { CGAssociateMouseAndMouseCursorPosition(true) }; + Ok(()) + }, + CursorState::Hide => { + let _: () = unsafe { msg_send![cls, hide] }; + Ok(()) + }, + CursorState::Grab => { + let _: i32 = unsafe { CGAssociateMouseAndMouseCursorPosition(false) }; + Ok(()) + } + } + } + + #[inline] + pub fn hidpi_factor(&self) -> f32 { + unsafe { + NSWindow::backingScaleFactor(*self.window) as f32 + } + } + + #[inline] + pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { + let (window_x, window_y) = self.get_position().unwrap_or((0, 0)); + let (cursor_x, cursor_y) = (window_x + x, window_y + y); + + unsafe { + // TODO: Check for errors. + let _ = CGWarpMouseCursorPosition(appkit::CGPoint { + x: cursor_x as appkit::CGFloat, + y: cursor_y as appkit::CGFloat, + }); + let _ = CGAssociateMouseAndMouseCursorPosition(true); + } + + Ok(()) + } +} + +// Convert the `cocoa::base::id` associated with a window to a usize to use as a unique identifier +// for the window. +pub fn get_window_id(window_cocoa_id: cocoa::base::id) -> Id { + Id(window_cocoa_id as *const objc::runtime::Object as usize) +} + +unsafe fn nswindow_set_min_dimensions( + window: V, min_width: f64, min_height: f64) +{ + window.setMinSize_(NSSize { + width: min_width, + height: min_height, + }); + // If necessary, resize the window to match constraint + let mut current_rect = NSWindow::frame(window); + if current_rect.size.width < min_width { + current_rect.size.width = min_width; + window.setFrame_display_(current_rect, 0) + } + if current_rect.size.height < min_height { + // The origin point of a rectangle is at its bottom left in Cocoa. To + // ensure the window's top-left point remains the same: + current_rect.origin.y += + current_rect.size.height - min_height; + + current_rect.size.height = min_height; + window.setFrame_display_(current_rect, 0) + } +} + +unsafe fn nswindow_set_max_dimensions( + window: V, max_width: f64, max_height: f64) +{ + window.setMaxSize_(NSSize { + width: max_width, + height: max_height, + }); + // If necessary, resize the window to match constraint + let mut current_rect = NSWindow::frame(window); + if current_rect.size.width > max_width { + current_rect.size.width = max_width; + window.setFrame_display_(current_rect, 0) + } + if current_rect.size.height > max_height { + // The origin point of a rectangle is at its bottom left in + // Cocoa. To ensure the window's top-left point remains the + // same: + current_rect.origin.y += + current_rect.size.height - max_height; + + current_rect.size.height = max_height; + window.setFrame_display_(current_rect, 0) + } +} + +pub struct IdRef(id); + +impl IdRef { + fn new(i: id) -> IdRef { + IdRef(i) + } + + #[allow(dead_code)] + fn retain(i: id) -> IdRef { + if i != nil { + let _: id = unsafe { msg_send![i, retain] }; + } + IdRef(i) + } + + fn non_nil(self) -> Option { + if self.0 == nil { None } else { Some(self) } + } +} + +impl Drop for IdRef { + fn drop(&mut self) { + if self.0 != nil { + let _: () = unsafe { msg_send![self.0, release] }; + } + } +} + +impl Deref for IdRef { + type Target = id; + fn deref<'a>(&'a self) -> &'a id { + &self.0 + } +} + +impl Clone for IdRef { + fn clone(&self) -> IdRef { + if self.0 != nil { + let _: id = unsafe { msg_send![self.0, retain] }; + } + IdRef(self.0) + } +} + +// #[allow(non_snake_case, non_upper_case_globals)] +// unsafe fn NSEventToEvent(window: &Window, nsevent: id) -> Option { +// if nsevent == nil { return None; } +// +// let event_type = nsevent.eventType(); +// appkit::NSApp().sendEvent_(if let appkit::NSKeyDown = event_type { nil } else { nsevent }); +// +// match event_type { +// appkit::NSLeftMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Left)) }, +// appkit::NSLeftMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Left)) }, +// appkit::NSRightMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Right)) }, +// appkit::NSRightMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Right)) }, +// appkit::NSOtherMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Middle)) }, +// appkit::NSOtherMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Middle)) }, +// appkit::NSMouseEntered => { Some(Event::MouseEntered) }, +// appkit::NSMouseExited => { Some(Event::MouseLeft) }, +// appkit::NSMouseMoved | +// appkit::NSLeftMouseDragged | +// appkit::NSOtherMouseDragged | +// appkit::NSRightMouseDragged => { +// let window_point = nsevent.locationInWindow(); +// let cWindow: id = msg_send![nsevent, window]; +// let view_point = if cWindow == nil { +// let window_rect = window.window.convertRectFromScreen_(NSRect::new(window_point, NSSize::new(0.0, 0.0))); +// window.view.convertPoint_fromView_(window_rect.origin, nil) +// } else { +// window.view.convertPoint_fromView_(window_point, nil) +// }; +// let view_rect = NSView::frame(*window.view); +// let scale_factor = window.hidpi_factor(); +// +// Some(Event::MouseMoved((scale_factor * view_point.x as f32) as i32, +// (scale_factor * (view_rect.size.height - view_point.y) as f32) as i32)) +// }, +// appkit::NSKeyDown => { +// let mut events = VecDeque::new(); +// let received_c_str = nsevent.characters().UTF8String(); +// let received_str = CStr::from_ptr(received_c_str); +// for received_char in from_utf8(received_str.to_bytes()).unwrap().chars() { +// events.push_back(Event::ReceivedCharacter(received_char)); +// } +// +// let vkey = to_virtual_key_code(NSEvent::keyCode(nsevent)); +// events.push_back(Event::KeyboardInput(ElementState::Pressed, NSEvent::keyCode(nsevent) as u8, vkey)); +// let event = events.pop_front(); +// window.delegate.state.pending_events.lock().unwrap().extend(events.into_iter()); +// event +// }, +// appkit::NSKeyUp => { +// let vkey = to_virtual_key_code(NSEvent::keyCode(nsevent)); +// +// Some(Event::KeyboardInput(ElementState::Released, NSEvent::keyCode(nsevent) as u8, vkey)) +// }, +// appkit::NSFlagsChanged => { +// let mut events = VecDeque::new(); +// let shift_modifier = Window::modifier_event(nsevent, appkit::NSShiftKeyMask, events::VirtualKeyCode::LShift, SHIFT_PRESSED); +// if shift_modifier.is_some() { +// SHIFT_PRESSED = !SHIFT_PRESSED; +// events.push_back(shift_modifier.unwrap()); +// } +// let ctrl_modifier = Window::modifier_event(nsevent, appkit::NSControlKeyMask, events::VirtualKeyCode::LControl, CTRL_PRESSED); +// if ctrl_modifier.is_some() { +// CTRL_PRESSED = !CTRL_PRESSED; +// events.push_back(ctrl_modifier.unwrap()); +// } +// let win_modifier = Window::modifier_event(nsevent, appkit::NSCommandKeyMask, events::VirtualKeyCode::LWin, WIN_PRESSED); +// if win_modifier.is_some() { +// WIN_PRESSED = !WIN_PRESSED; +// events.push_back(win_modifier.unwrap()); +// } +// let alt_modifier = Window::modifier_event(nsevent, appkit::NSAlternateKeyMask, events::VirtualKeyCode::LAlt, ALT_PRESSED); +// if alt_modifier.is_some() { +// ALT_PRESSED = !ALT_PRESSED; +// events.push_back(alt_modifier.unwrap()); +// } +// let event = events.pop_front(); +// window.delegate.state.pending_events.lock().unwrap().extend(events.into_iter()); +// event +// }, +// appkit::NSScrollWheel => { +// use events::MouseScrollDelta::{LineDelta, PixelDelta}; +// let scale_factor = window.hidpi_factor(); +// let delta = if nsevent.hasPreciseScrollingDeltas() == YES { +// PixelDelta(scale_factor * nsevent.scrollingDeltaX() as f32, +// scale_factor * nsevent.scrollingDeltaY() as f32) +// } else { +// LineDelta(scale_factor * nsevent.scrollingDeltaX() as f32, +// scale_factor * nsevent.scrollingDeltaY() as f32) +// }; +// let phase = match nsevent.phase() { +// appkit::NSEventPhaseMayBegin | appkit::NSEventPhaseBegan => TouchPhase::Started, +// appkit::NSEventPhaseEnded => TouchPhase::Ended, +// _ => TouchPhase::Moved, +// }; +// Some(Event::MouseWheel(delta, phase)) +// }, +// appkit::NSEventTypePressure => { +// Some(Event::TouchpadPressure(nsevent.pressure(), nsevent.stage())) +// }, +// appkit::NSApplicationDefined => { +// match nsevent.subtype() { +// appkit::NSEventSubtype::NSApplicationActivatedEventType => { Some(Event::Awakened) } +// _ => { None } +// } +// }, +// _ => { None }, +// } +// } +// +// pub fn to_virtual_key_code(code: u16) -> Option { +// Some(match code { +// 0x00 => events::VirtualKeyCode::A, +// 0x01 => events::VirtualKeyCode::S, +// 0x02 => events::VirtualKeyCode::D, +// 0x03 => events::VirtualKeyCode::F, +// 0x04 => events::VirtualKeyCode::H, +// 0x05 => events::VirtualKeyCode::G, +// 0x06 => events::VirtualKeyCode::Z, +// 0x07 => events::VirtualKeyCode::X, +// 0x08 => events::VirtualKeyCode::C, +// 0x09 => events::VirtualKeyCode::V, +// //0x0a => World 1, +// 0x0b => events::VirtualKeyCode::B, +// 0x0c => events::VirtualKeyCode::Q, +// 0x0d => events::VirtualKeyCode::W, +// 0x0e => events::VirtualKeyCode::E, +// 0x0f => events::VirtualKeyCode::R, +// 0x10 => events::VirtualKeyCode::Y, +// 0x11 => events::VirtualKeyCode::T, +// 0x12 => events::VirtualKeyCode::Key1, +// 0x13 => events::VirtualKeyCode::Key2, +// 0x14 => events::VirtualKeyCode::Key3, +// 0x15 => events::VirtualKeyCode::Key4, +// 0x16 => events::VirtualKeyCode::Key6, +// 0x17 => events::VirtualKeyCode::Key5, +// 0x18 => events::VirtualKeyCode::Equals, +// 0x19 => events::VirtualKeyCode::Key9, +// 0x1a => events::VirtualKeyCode::Key7, +// 0x1b => events::VirtualKeyCode::Minus, +// 0x1c => events::VirtualKeyCode::Key8, +// 0x1d => events::VirtualKeyCode::Key0, +// 0x1e => events::VirtualKeyCode::RBracket, +// 0x1f => events::VirtualKeyCode::O, +// 0x20 => events::VirtualKeyCode::U, +// 0x21 => events::VirtualKeyCode::LBracket, +// 0x22 => events::VirtualKeyCode::I, +// 0x23 => events::VirtualKeyCode::P, +// 0x24 => events::VirtualKeyCode::Return, +// 0x25 => events::VirtualKeyCode::L, +// 0x26 => events::VirtualKeyCode::J, +// 0x27 => events::VirtualKeyCode::Apostrophe, +// 0x28 => events::VirtualKeyCode::K, +// 0x29 => events::VirtualKeyCode::Semicolon, +// 0x2a => events::VirtualKeyCode::Backslash, +// 0x2b => events::VirtualKeyCode::Comma, +// 0x2c => events::VirtualKeyCode::Slash, +// 0x2d => events::VirtualKeyCode::N, +// 0x2e => events::VirtualKeyCode::M, +// 0x2f => events::VirtualKeyCode::Period, +// 0x30 => events::VirtualKeyCode::Tab, +// 0x31 => events::VirtualKeyCode::Space, +// 0x32 => events::VirtualKeyCode::Grave, +// 0x33 => events::VirtualKeyCode::Back, +// //0x34 => unkown, +// 0x35 => events::VirtualKeyCode::Escape, +// 0x36 => events::VirtualKeyCode::RWin, +// 0x37 => events::VirtualKeyCode::LWin, +// 0x38 => events::VirtualKeyCode::LShift, +// //0x39 => Caps lock, +// //0x3a => Left alt, +// 0x3b => events::VirtualKeyCode::LControl, +// 0x3c => events::VirtualKeyCode::RShift, +// //0x3d => Right alt, +// 0x3e => events::VirtualKeyCode::RControl, +// //0x3f => Fn key, +// //0x40 => F17 Key, +// 0x41 => events::VirtualKeyCode::Decimal, +// //0x42 -> unkown, +// 0x43 => events::VirtualKeyCode::Multiply, +// //0x44 => unkown, +// 0x45 => events::VirtualKeyCode::Add, +// //0x46 => unkown, +// 0x47 => events::VirtualKeyCode::Numlock, +// //0x48 => KeypadClear, +// 0x49 => events::VirtualKeyCode::VolumeUp, +// 0x4a => events::VirtualKeyCode::VolumeDown, +// 0x4b => events::VirtualKeyCode::Divide, +// 0x4c => events::VirtualKeyCode::NumpadEnter, +// //0x4d => unkown, +// 0x4e => events::VirtualKeyCode::Subtract, +// //0x4f => F18 key, +// //0x50 => F19 Key, +// 0x51 => events::VirtualKeyCode::NumpadEquals, +// 0x52 => events::VirtualKeyCode::Numpad0, +// 0x53 => events::VirtualKeyCode::Numpad1, +// 0x54 => events::VirtualKeyCode::Numpad2, +// 0x55 => events::VirtualKeyCode::Numpad3, +// 0x56 => events::VirtualKeyCode::Numpad4, +// 0x57 => events::VirtualKeyCode::Numpad5, +// 0x58 => events::VirtualKeyCode::Numpad6, +// 0x59 => events::VirtualKeyCode::Numpad7, +// //0x5a => F20 Key, +// 0x5b => events::VirtualKeyCode::Numpad8, +// 0x5c => events::VirtualKeyCode::Numpad9, +// //0x5d => unkown, +// //0x5e => unkown, +// //0x5f => unkown, +// 0x60 => events::VirtualKeyCode::F5, +// 0x61 => events::VirtualKeyCode::F6, +// 0x62 => events::VirtualKeyCode::F7, +// 0x63 => events::VirtualKeyCode::F3, +// 0x64 => events::VirtualKeyCode::F8, +// 0x65 => events::VirtualKeyCode::F9, +// //0x66 => unkown, +// 0x67 => events::VirtualKeyCode::F11, +// //0x68 => unkown, +// 0x69 => events::VirtualKeyCode::F13, +// //0x6a => F16 Key, +// 0x6b => events::VirtualKeyCode::F14, +// //0x6c => unkown, +// 0x6d => events::VirtualKeyCode::F10, +// //0x6e => unkown, +// 0x6f => events::VirtualKeyCode::F12, +// //0x70 => unkown, +// 0x71 => events::VirtualKeyCode::F15, +// 0x72 => events::VirtualKeyCode::Insert, +// 0x73 => events::VirtualKeyCode::Home, +// 0x74 => events::VirtualKeyCode::PageUp, +// 0x75 => events::VirtualKeyCode::Delete, +// 0x76 => events::VirtualKeyCode::F4, +// 0x77 => events::VirtualKeyCode::End, +// 0x78 => events::VirtualKeyCode::F2, +// 0x79 => events::VirtualKeyCode::PageDown, +// 0x7a => events::VirtualKeyCode::F1, +// 0x7b => events::VirtualKeyCode::Left, +// 0x7c => events::VirtualKeyCode::Right, +// 0x7d => events::VirtualKeyCode::Down, +// 0x7e => events::VirtualKeyCode::Up, +// //0x7f => unkown, +// +// _ => return None, +// }) +// } diff --git a/src/window.rs b/src/window.rs index ae9c8d38..920563fc 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2,7 +2,6 @@ use std::collections::vec_deque::IntoIter as VecDequeIter; use CreationError; use CursorState; -use WindowEvent as Event; use EventsLoop; use MouseCursor; use Window; From 35dcf8dab969552584b4d5398fa1c7aacb085b16 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 4 Feb 2017 00:51:38 +1100 Subject: [PATCH 02/14] Only return Awakened on NsApplicationActivated, rather than on every unknown event. --- src/platform/macos/events_loop.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index 096d9275..83301a83 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -115,18 +115,10 @@ impl EventsLoop { foundation::NSDefaultRunLoopMode, cocoa::base::YES); - // FIXME: we should check explicitly for `Awakened` events in `ns_event_to_event` - // and not just assume that an `Awakened` event occurred every time `None` is - // returned. Also, `Awakened` should be a variant of `Event`, not `WindowEvent`, so - // for now we do this hack with a fake ID. - let event = self.ns_event_to_event(ns_event) - .unwrap_or(Event::WindowEvent { - window_id: ::WindowId(super::WindowId(0)), - event: WindowEvent::Awakened, - }); - - if let Ok(mut callback) = self.callback.lock() { - callback.as_mut().unwrap()(event); + if let Some(event) = self.ns_event_to_event(ns_event) { + if let Ok(mut callback) = self.callback.lock() { + callback.as_mut().unwrap()(event); + } } let _: () = msg_send![pool, release]; @@ -338,12 +330,21 @@ impl EventsLoop { let window_event = WindowEvent::MouseWheel(delta, phase); Some(into_event(window_event)) }, + appkit::NSEventTypePressure => { let pressure = ns_event.pressure(); let stage = ns_event.stage(); let window_event = WindowEvent::TouchpadPressure(pressure, stage); Some(into_event(window_event)) }, + + appkit::NSApplicationDefined => match ns_event.subtype() { + appkit::NSEventSubtype::NSApplicationActivatedEventType => { + Some(into_event(WindowEvent::Awakened)) + }, + _ => None, + }, + _ => None, } } From ea19409960fd16704e60e6ffa38aa57573c5add8 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 4 Feb 2017 02:54:40 +1100 Subject: [PATCH 03/14] Make sure EventsLoop::interrupt awakens the waiting event loop --- src/platform/macos/events_loop.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index 83301a83..99ed1a17 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -136,7 +136,25 @@ impl EventsLoop { pub fn interrupt(&self) { self.interrupted.store(true, std::sync::atomic::Ordering::Relaxed); - // TODO: We should also signal to "awaken" the cocoa event loop here. + + // Awaken the event loop by triggering `NSApplicationActivatedEventType`. + unsafe { + let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); + let event = + NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_( + cocoa::base::nil, + appkit::NSApplicationDefined, + foundation::NSPoint::new(0.0, 0.0), + appkit::NSEventModifierFlags::empty(), + 0.0, + 0, + cocoa::base::nil, + appkit::NSEventSubtype::NSApplicationActivatedEventType, + 0, + 0); + appkit::NSApp().postEvent_atStart_(event, cocoa::base::NO); + foundation::NSAutoreleasePool::drain(pool); + } } // Here we store user's `callback` behind the `EventsLoop`'s mutex so that it may be safely From fd0ddee63c9c9b29cffde8b7f341b1f9e95c973c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 4 Feb 2017 02:55:42 +1100 Subject: [PATCH 04/14] Panic if the user attempts to run an event loop on a non-main thread on macOS --- src/platform/macos/events_loop.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index 99ed1a17..b9fbbba2 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -47,6 +47,10 @@ impl EventsLoop { where F: FnMut(Event), { unsafe { + if !msg_send![cocoa::base::class("NSThread"), isMainThread] { + panic!("Events can only be polled from the main thread on macOS"); + } + self.store_callback(callback); } @@ -94,6 +98,10 @@ impl EventsLoop { self.interrupted.store(false, std::sync::atomic::Ordering::Relaxed); unsafe { + if !msg_send![cocoa::base::class("NSThread"), isMainThread] { + panic!("Events can only be polled from the main thread on macOS"); + } + self.store_callback(callback); } From c03311fa2d2bb4d80d0029db17725102ab85d51c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 4 Feb 2017 13:30:34 +1100 Subject: [PATCH 05/14] Add a method to simplify emitting an event from the macos poll_events and run_forever methods --- src/platform/macos/events_loop.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index b9fbbba2..59796d52 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -58,9 +58,7 @@ impl EventsLoop { loop { // First, yield all pending events. while let Some(event) = self.pending_events.lock().unwrap().pop_front() { - if let Ok(mut callback) = self.callback.lock() { - callback.as_mut().unwrap()(event); - } + self.emit_event(event); } unsafe { @@ -79,9 +77,7 @@ impl EventsLoop { match event { // Call the user's callback. - Some(event) => if let Ok(mut callback) = self.callback.lock() { - callback.as_mut().unwrap()(event); - }, + Some(event) => self.emit_event(event), None => break, } } @@ -108,9 +104,7 @@ impl EventsLoop { loop { // First, yield all pending events. while let Some(event) = self.pending_events.lock().unwrap().pop_front() { - if let Ok(mut callback) = self.callback.lock() { - callback.as_mut().unwrap()(event); - } + self.emit_event(event); } unsafe { @@ -124,9 +118,7 @@ impl EventsLoop { cocoa::base::YES); if let Some(event) = self.ns_event_to_event(ns_event) { - if let Ok(mut callback) = self.callback.lock() { - callback.as_mut().unwrap()(event); - } + self.emit_event(event); } let _: () = msg_send![pool, release]; @@ -183,6 +175,13 @@ impl EventsLoop { *self.callback.lock().unwrap() = Some(boxed); } + // Emits the given event via the user-given callback. + fn emit_event(&self, event: Event) { + if let Ok(mut callback) = self.callback.lock() { + callback.as_mut().unwrap()(event); + } + } + // Convert some given `NSEvent` into a winit `Event`. unsafe fn ns_event_to_event(&self, ns_event: cocoa::base::id) -> Option { if ns_event == cocoa::base::nil { From 3ce7904e018f4b0ce033220a948e7480d5972d2e Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 12:51:09 +1100 Subject: [PATCH 06/14] Wrap the temporarily stored user callback in a type to enforce invariants This also removes the need for "box"ing the callback in favour of storing a raw `*mut` pointer. We can do this by ensuring that we never store the pointer for longer than the lifetime of the user callback, which is the duration of a call to `poll_events` or `run_forever`. Also removes old commented out event code from the window module. --- src/platform/macos/events_loop.rs | 147 ++++++++++------- src/platform/macos/window.rs | 266 +----------------------------- tests/events_loop.rs | 11 ++ 3 files changed, 109 insertions(+), 315 deletions(-) create mode 100644 tests/events_loop.rs diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index 59796d52..41d10237 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -11,10 +11,15 @@ pub struct EventsLoop { modifiers: std::sync::Mutex, interrupted: std::sync::atomic::AtomicBool, - /// The user's event callback given via either the `poll_events` or `run_forever` method. This - /// will only be `Some` for the duration of whichever of these methods has been called and will - /// always be `None` otherwise. - pub callback: std::sync::Mutex>> + // The user event callback given via either of the `poll_events` or `run_forever` methods. + // + // We store the user's callback here so that it may be accessed by each of the window delegate + // callbacks (e.g. resize, close, etc) for the duration of a call to either of the + // `poll_events` or `run_forever` methods. + // + // This is *only* `Some` for the duration of a call to either of these methods and will be + // `None` otherwise. + pub user_callback: UserCallback, } struct Modifiers { @@ -24,6 +29,60 @@ struct Modifiers { alt_pressed: bool, } +// Wrapping the user callback in a type allows us to: +// +// - ensure the callback pointer is never accidentally cloned +// - ensure that only the `EventsLoop` can `store` and `drop` the callback pointer +// - `unsafe impl Send` and `Sync` so that `Send` and `Sync` can be implemented for `EventsLoop`. +pub struct UserCallback { + mutex: std::sync::Mutex>, +} + + +unsafe impl Send for UserCallback {} +unsafe impl Sync for UserCallback {} + +impl UserCallback { + + // Here we store user's `callback` behind the mutex so that they may be safely shared between + // each of the window delegates. + // + // In order to make sure that the pointer is always valid, we must manually guarantee that it + // is dropped before the callback itself is dropped. Thus, this should *only* be called at the + // beginning of a call to `poll_events` and `run_forever`, both of which *must* drop the + // callback at the end of their scope using `drop_callback`. + fn store(&self, callback: &mut F) + where F: FnMut(Event) + { + let trait_object = callback as &mut FnMut(Event); + let trait_object_ptr = trait_object as *const FnMut(Event) as *mut FnMut(Event); + *self.mutex.lock().unwrap() = Some(trait_object_ptr); + } + + // Emits the given event via the user-given callback. + // + // This is *only* called within the `poll_events` and `run_forever` methods so we know that it + // is safe to `unwrap` the last callback without causing a panic as there must be at least one + // callback stored. + // + // This is unsafe as it requires dereferencing the pointer to the user-given callback. We + // guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given + // callback. + pub unsafe fn call_with_event(&self, event: Event) { + let callback: *mut FnMut(Event) = self.mutex.lock().unwrap().take().unwrap(); + (*callback)(event); + *self.mutex.lock().unwrap() = Some(callback); + } + + // Used to drop the user callback pointer at the end of the `poll_events` and `run_forever` + // methods. This is done to enforce our guarantee that the top callback will never live longer + // than the call to either `poll_events` or `run_forever` to which it was given. + fn drop(&self) { + self.mutex.lock().unwrap().take(); + } + +} + impl EventsLoop { @@ -39,29 +98,29 @@ impl EventsLoop { pending_events: std::sync::Mutex::new(std::collections::VecDeque::new()), modifiers: std::sync::Mutex::new(modifiers), interrupted: std::sync::atomic::AtomicBool::new(false), - callback: std::sync::Mutex::new(None), + user_callback: UserCallback { mutex: std::sync::Mutex::new(None) }, } } - pub fn poll_events(&self, callback: F) + pub fn poll_events(&self, mut callback: F) where F: FnMut(Event), { unsafe { if !msg_send![cocoa::base::class("NSThread"), isMainThread] { panic!("Events can only be polled from the main thread on macOS"); } - - self.store_callback(callback); } + self.user_callback.store(&mut callback); + // Loop as long as we have pending events to return. loop { - // First, yield all pending events. - while let Some(event) = self.pending_events.lock().unwrap().pop_front() { - self.emit_event(event); - } - unsafe { + // First, yield all pending events. + while let Some(event) = self.pending_events.lock().unwrap().pop_front() { + self.user_callback.call_with_event(event); + } + let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); // Poll for the next event, returning `nil` if there are none. @@ -77,18 +136,16 @@ impl EventsLoop { match event { // Call the user's callback. - Some(event) => self.emit_event(event), + Some(event) => self.user_callback.call_with_event(event), None => break, } } } - // Drop the callback to enforce our guarantee that it will never live longer than the - // duration of this method. - self.callback.lock().unwrap().take(); + self.user_callback.drop(); } - pub fn run_forever(&self, callback: F) + pub fn run_forever(&self, mut callback: F) where F: FnMut(Event) { self.interrupted.store(false, std::sync::atomic::Ordering::Relaxed); @@ -97,17 +154,17 @@ impl EventsLoop { if !msg_send![cocoa::base::class("NSThread"), isMainThread] { panic!("Events can only be polled from the main thread on macOS"); } - - self.store_callback(callback); } - loop { - // First, yield all pending events. - while let Some(event) = self.pending_events.lock().unwrap().pop_front() { - self.emit_event(event); - } + self.user_callback.store(&mut callback); + loop { unsafe { + // First, yield all pending events. + while let Some(event) = self.pending_events.lock().unwrap().pop_front() { + self.user_callback.call_with_event(event); + } + let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); // Wait for the next event. Note that this function blocks during resize. @@ -117,21 +174,24 @@ impl EventsLoop { foundation::NSDefaultRunLoopMode, cocoa::base::YES); - if let Some(event) = self.ns_event_to_event(ns_event) { - self.emit_event(event); - } + let maybe_event = self.ns_event_to_event(ns_event); + // Release the pool before calling the top callback in case the user calls either + // `run_forever` or `poll_events` within the callback. let _: () = msg_send![pool, release]; + + if let Some(event) = maybe_event { + self.user_callback.call_with_event(event); + } } if self.interrupted.load(std::sync::atomic::Ordering::Relaxed) { + self.interrupted.store(false, std::sync::atomic::Ordering::Relaxed); break; } } - // Drop the callback to enforce our guarantee that it will never live longer than the - // duration of this method. - self.callback.lock().unwrap().take(); + self.user_callback.drop(); } pub fn interrupt(&self) { @@ -157,31 +217,6 @@ impl EventsLoop { } } - // Here we store user's `callback` behind the `EventsLoop`'s mutex so that it may be safely - // shared between each of the window delegates. - // - // In order to store the `callback` within the `Eventsloop` as a trait object, we must - // `Box` the callback. Normally this would require that `F: 'static`, however we know that - // the callback cannot live longer than the lifetime of this method. Thus, we use `unsafe` - // to work around this requirement and enforce this guarantee ourselves. - // - // This should *only* be called at the beginning of `poll_events` and `run_forever`, both of - // which *must* drop the callback at the end of their scope. - unsafe fn store_callback(&self, callback: F) - where F: FnMut(Event) - { - let boxed: Box = Box::new(callback); - let boxed: Box = std::mem::transmute(boxed as Box); - *self.callback.lock().unwrap() = Some(boxed); - } - - // Emits the given event via the user-given callback. - fn emit_event(&self, event: Event) { - if let Ok(mut callback) = self.callback.lock() { - callback.as_mut().unwrap()(event); - } - } - // Convert some given `NSEvent` into a winit `Event`. unsafe fn ns_event_to_event(&self, ns_event: cocoa::base::id) -> Option { if ns_event == cocoa::base::nil { diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index df020bb6..04a973b4 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -43,12 +43,13 @@ impl WindowDelegate { fn class() -> *const Class { use std::os::raw::c_void; - // Emits an event via the `EventsLoop`. + // Emits an event via the `EventsLoop`'s callback. // - // If the `EventsLoop` callback is `Some` the event is immediately emitted via the callback. - // - // If it is `None`, it is pushed to the back of the `EventsLoop`'s `pending_events` deque. - fn emit_event(state: &mut DelegateState, window_event: Event) { + // The `Eventloop`'s callback should always be `Some` while the `WindowDelegate`'s methods + // are called as the delegate methods should only be called during a call to + // `nextEventMatchingMask` (called via EventsLoop::poll_events and + // EventsLoop::run_forever). + unsafe fn emit_event(state: &mut DelegateState, window_event: Event) { let window_id = get_window_id(*state.window); let event = ::Event::WindowEvent { window_id: ::WindowId(window_id), @@ -56,16 +57,7 @@ impl WindowDelegate { }; if let Some(events_loop) = state.events_loop.upgrade() { - if let Ok(mut callback) = events_loop.callback.lock() { - if let Some(callback) = callback.as_mut() { - callback(event); - return; - } - } - - if let Ok(mut pending_events) = events_loop.pending_events.lock() { - pending_events.push_back(event); - } + events_loop.user_callback.call_with_event(event); } } @@ -634,247 +626,3 @@ impl Clone for IdRef { IdRef(self.0) } } - -// #[allow(non_snake_case, non_upper_case_globals)] -// unsafe fn NSEventToEvent(window: &Window, nsevent: id) -> Option { -// if nsevent == nil { return None; } -// -// let event_type = nsevent.eventType(); -// appkit::NSApp().sendEvent_(if let appkit::NSKeyDown = event_type { nil } else { nsevent }); -// -// match event_type { -// appkit::NSLeftMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Left)) }, -// appkit::NSLeftMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Left)) }, -// appkit::NSRightMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Right)) }, -// appkit::NSRightMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Right)) }, -// appkit::NSOtherMouseDown => { Some(Event::MouseInput(ElementState::Pressed, MouseButton::Middle)) }, -// appkit::NSOtherMouseUp => { Some(Event::MouseInput(ElementState::Released, MouseButton::Middle)) }, -// appkit::NSMouseEntered => { Some(Event::MouseEntered) }, -// appkit::NSMouseExited => { Some(Event::MouseLeft) }, -// appkit::NSMouseMoved | -// appkit::NSLeftMouseDragged | -// appkit::NSOtherMouseDragged | -// appkit::NSRightMouseDragged => { -// let window_point = nsevent.locationInWindow(); -// let cWindow: id = msg_send![nsevent, window]; -// let view_point = if cWindow == nil { -// let window_rect = window.window.convertRectFromScreen_(NSRect::new(window_point, NSSize::new(0.0, 0.0))); -// window.view.convertPoint_fromView_(window_rect.origin, nil) -// } else { -// window.view.convertPoint_fromView_(window_point, nil) -// }; -// let view_rect = NSView::frame(*window.view); -// let scale_factor = window.hidpi_factor(); -// -// Some(Event::MouseMoved((scale_factor * view_point.x as f32) as i32, -// (scale_factor * (view_rect.size.height - view_point.y) as f32) as i32)) -// }, -// appkit::NSKeyDown => { -// let mut events = VecDeque::new(); -// let received_c_str = nsevent.characters().UTF8String(); -// let received_str = CStr::from_ptr(received_c_str); -// for received_char in from_utf8(received_str.to_bytes()).unwrap().chars() { -// events.push_back(Event::ReceivedCharacter(received_char)); -// } -// -// let vkey = to_virtual_key_code(NSEvent::keyCode(nsevent)); -// events.push_back(Event::KeyboardInput(ElementState::Pressed, NSEvent::keyCode(nsevent) as u8, vkey)); -// let event = events.pop_front(); -// window.delegate.state.pending_events.lock().unwrap().extend(events.into_iter()); -// event -// }, -// appkit::NSKeyUp => { -// let vkey = to_virtual_key_code(NSEvent::keyCode(nsevent)); -// -// Some(Event::KeyboardInput(ElementState::Released, NSEvent::keyCode(nsevent) as u8, vkey)) -// }, -// appkit::NSFlagsChanged => { -// let mut events = VecDeque::new(); -// let shift_modifier = Window::modifier_event(nsevent, appkit::NSShiftKeyMask, events::VirtualKeyCode::LShift, SHIFT_PRESSED); -// if shift_modifier.is_some() { -// SHIFT_PRESSED = !SHIFT_PRESSED; -// events.push_back(shift_modifier.unwrap()); -// } -// let ctrl_modifier = Window::modifier_event(nsevent, appkit::NSControlKeyMask, events::VirtualKeyCode::LControl, CTRL_PRESSED); -// if ctrl_modifier.is_some() { -// CTRL_PRESSED = !CTRL_PRESSED; -// events.push_back(ctrl_modifier.unwrap()); -// } -// let win_modifier = Window::modifier_event(nsevent, appkit::NSCommandKeyMask, events::VirtualKeyCode::LWin, WIN_PRESSED); -// if win_modifier.is_some() { -// WIN_PRESSED = !WIN_PRESSED; -// events.push_back(win_modifier.unwrap()); -// } -// let alt_modifier = Window::modifier_event(nsevent, appkit::NSAlternateKeyMask, events::VirtualKeyCode::LAlt, ALT_PRESSED); -// if alt_modifier.is_some() { -// ALT_PRESSED = !ALT_PRESSED; -// events.push_back(alt_modifier.unwrap()); -// } -// let event = events.pop_front(); -// window.delegate.state.pending_events.lock().unwrap().extend(events.into_iter()); -// event -// }, -// appkit::NSScrollWheel => { -// use events::MouseScrollDelta::{LineDelta, PixelDelta}; -// let scale_factor = window.hidpi_factor(); -// let delta = if nsevent.hasPreciseScrollingDeltas() == YES { -// PixelDelta(scale_factor * nsevent.scrollingDeltaX() as f32, -// scale_factor * nsevent.scrollingDeltaY() as f32) -// } else { -// LineDelta(scale_factor * nsevent.scrollingDeltaX() as f32, -// scale_factor * nsevent.scrollingDeltaY() as f32) -// }; -// let phase = match nsevent.phase() { -// appkit::NSEventPhaseMayBegin | appkit::NSEventPhaseBegan => TouchPhase::Started, -// appkit::NSEventPhaseEnded => TouchPhase::Ended, -// _ => TouchPhase::Moved, -// }; -// Some(Event::MouseWheel(delta, phase)) -// }, -// appkit::NSEventTypePressure => { -// Some(Event::TouchpadPressure(nsevent.pressure(), nsevent.stage())) -// }, -// appkit::NSApplicationDefined => { -// match nsevent.subtype() { -// appkit::NSEventSubtype::NSApplicationActivatedEventType => { Some(Event::Awakened) } -// _ => { None } -// } -// }, -// _ => { None }, -// } -// } -// -// pub fn to_virtual_key_code(code: u16) -> Option { -// Some(match code { -// 0x00 => events::VirtualKeyCode::A, -// 0x01 => events::VirtualKeyCode::S, -// 0x02 => events::VirtualKeyCode::D, -// 0x03 => events::VirtualKeyCode::F, -// 0x04 => events::VirtualKeyCode::H, -// 0x05 => events::VirtualKeyCode::G, -// 0x06 => events::VirtualKeyCode::Z, -// 0x07 => events::VirtualKeyCode::X, -// 0x08 => events::VirtualKeyCode::C, -// 0x09 => events::VirtualKeyCode::V, -// //0x0a => World 1, -// 0x0b => events::VirtualKeyCode::B, -// 0x0c => events::VirtualKeyCode::Q, -// 0x0d => events::VirtualKeyCode::W, -// 0x0e => events::VirtualKeyCode::E, -// 0x0f => events::VirtualKeyCode::R, -// 0x10 => events::VirtualKeyCode::Y, -// 0x11 => events::VirtualKeyCode::T, -// 0x12 => events::VirtualKeyCode::Key1, -// 0x13 => events::VirtualKeyCode::Key2, -// 0x14 => events::VirtualKeyCode::Key3, -// 0x15 => events::VirtualKeyCode::Key4, -// 0x16 => events::VirtualKeyCode::Key6, -// 0x17 => events::VirtualKeyCode::Key5, -// 0x18 => events::VirtualKeyCode::Equals, -// 0x19 => events::VirtualKeyCode::Key9, -// 0x1a => events::VirtualKeyCode::Key7, -// 0x1b => events::VirtualKeyCode::Minus, -// 0x1c => events::VirtualKeyCode::Key8, -// 0x1d => events::VirtualKeyCode::Key0, -// 0x1e => events::VirtualKeyCode::RBracket, -// 0x1f => events::VirtualKeyCode::O, -// 0x20 => events::VirtualKeyCode::U, -// 0x21 => events::VirtualKeyCode::LBracket, -// 0x22 => events::VirtualKeyCode::I, -// 0x23 => events::VirtualKeyCode::P, -// 0x24 => events::VirtualKeyCode::Return, -// 0x25 => events::VirtualKeyCode::L, -// 0x26 => events::VirtualKeyCode::J, -// 0x27 => events::VirtualKeyCode::Apostrophe, -// 0x28 => events::VirtualKeyCode::K, -// 0x29 => events::VirtualKeyCode::Semicolon, -// 0x2a => events::VirtualKeyCode::Backslash, -// 0x2b => events::VirtualKeyCode::Comma, -// 0x2c => events::VirtualKeyCode::Slash, -// 0x2d => events::VirtualKeyCode::N, -// 0x2e => events::VirtualKeyCode::M, -// 0x2f => events::VirtualKeyCode::Period, -// 0x30 => events::VirtualKeyCode::Tab, -// 0x31 => events::VirtualKeyCode::Space, -// 0x32 => events::VirtualKeyCode::Grave, -// 0x33 => events::VirtualKeyCode::Back, -// //0x34 => unkown, -// 0x35 => events::VirtualKeyCode::Escape, -// 0x36 => events::VirtualKeyCode::RWin, -// 0x37 => events::VirtualKeyCode::LWin, -// 0x38 => events::VirtualKeyCode::LShift, -// //0x39 => Caps lock, -// //0x3a => Left alt, -// 0x3b => events::VirtualKeyCode::LControl, -// 0x3c => events::VirtualKeyCode::RShift, -// //0x3d => Right alt, -// 0x3e => events::VirtualKeyCode::RControl, -// //0x3f => Fn key, -// //0x40 => F17 Key, -// 0x41 => events::VirtualKeyCode::Decimal, -// //0x42 -> unkown, -// 0x43 => events::VirtualKeyCode::Multiply, -// //0x44 => unkown, -// 0x45 => events::VirtualKeyCode::Add, -// //0x46 => unkown, -// 0x47 => events::VirtualKeyCode::Numlock, -// //0x48 => KeypadClear, -// 0x49 => events::VirtualKeyCode::VolumeUp, -// 0x4a => events::VirtualKeyCode::VolumeDown, -// 0x4b => events::VirtualKeyCode::Divide, -// 0x4c => events::VirtualKeyCode::NumpadEnter, -// //0x4d => unkown, -// 0x4e => events::VirtualKeyCode::Subtract, -// //0x4f => F18 key, -// //0x50 => F19 Key, -// 0x51 => events::VirtualKeyCode::NumpadEquals, -// 0x52 => events::VirtualKeyCode::Numpad0, -// 0x53 => events::VirtualKeyCode::Numpad1, -// 0x54 => events::VirtualKeyCode::Numpad2, -// 0x55 => events::VirtualKeyCode::Numpad3, -// 0x56 => events::VirtualKeyCode::Numpad4, -// 0x57 => events::VirtualKeyCode::Numpad5, -// 0x58 => events::VirtualKeyCode::Numpad6, -// 0x59 => events::VirtualKeyCode::Numpad7, -// //0x5a => F20 Key, -// 0x5b => events::VirtualKeyCode::Numpad8, -// 0x5c => events::VirtualKeyCode::Numpad9, -// //0x5d => unkown, -// //0x5e => unkown, -// //0x5f => unkown, -// 0x60 => events::VirtualKeyCode::F5, -// 0x61 => events::VirtualKeyCode::F6, -// 0x62 => events::VirtualKeyCode::F7, -// 0x63 => events::VirtualKeyCode::F3, -// 0x64 => events::VirtualKeyCode::F8, -// 0x65 => events::VirtualKeyCode::F9, -// //0x66 => unkown, -// 0x67 => events::VirtualKeyCode::F11, -// //0x68 => unkown, -// 0x69 => events::VirtualKeyCode::F13, -// //0x6a => F16 Key, -// 0x6b => events::VirtualKeyCode::F14, -// //0x6c => unkown, -// 0x6d => events::VirtualKeyCode::F10, -// //0x6e => unkown, -// 0x6f => events::VirtualKeyCode::F12, -// //0x70 => unkown, -// 0x71 => events::VirtualKeyCode::F15, -// 0x72 => events::VirtualKeyCode::Insert, -// 0x73 => events::VirtualKeyCode::Home, -// 0x74 => events::VirtualKeyCode::PageUp, -// 0x75 => events::VirtualKeyCode::Delete, -// 0x76 => events::VirtualKeyCode::F4, -// 0x77 => events::VirtualKeyCode::End, -// 0x78 => events::VirtualKeyCode::F2, -// 0x79 => events::VirtualKeyCode::PageDown, -// 0x7a => events::VirtualKeyCode::F1, -// 0x7b => events::VirtualKeyCode::Left, -// 0x7c => events::VirtualKeyCode::Right, -// 0x7d => events::VirtualKeyCode::Down, -// 0x7e => events::VirtualKeyCode::Up, -// //0x7f => unkown, -// -// _ => return None, -// }) -// } diff --git a/tests/events_loop.rs b/tests/events_loop.rs new file mode 100644 index 00000000..4623b80e --- /dev/null +++ b/tests/events_loop.rs @@ -0,0 +1,11 @@ +extern crate winit; + +// A part of the API requirement for `EventsLoop` is that it is `Send` + `Sync`. +// +// This short test will only compile if the `EventsLoop` is `Send` + `Sync`. +#[test] +fn send_sync() { + fn check_send_sync(_: T) {} + let events_loop = winit::EventsLoop::new(); + check_send_sync(events_loop); +} From 3482c53c89b87962c006618065f1c6bdd604eb2a Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 13:28:56 +1100 Subject: [PATCH 07/14] Rename old glutin ivar to winit --- src/platform/macos/window.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 04a973b4..d9959fb9 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -63,7 +63,7 @@ impl WindowDelegate { extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); emit_event(state, Event::Closed); } @@ -72,7 +72,7 @@ impl WindowDelegate { extern fn window_did_resize(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); let rect = NSView::frame(*state.view); let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32; @@ -86,7 +86,7 @@ impl WindowDelegate { unsafe { // TODO: center the cursor if the window had mouse grab when it // lost focus - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); emit_event(state, Event::Focused(true)); } @@ -94,7 +94,7 @@ impl WindowDelegate { extern fn window_did_resign_key(this: &Object, _: Sel, _: id) { unsafe { - let state: *mut c_void = *this.get_ivar("glutinState"); + let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); emit_event(state, Event::Focused(false)); } @@ -106,7 +106,7 @@ impl WindowDelegate { INIT.call_once(|| unsafe { // Create new NSWindowDelegate let superclass = Class::get("NSObject").unwrap(); - let mut decl = ClassDecl::new("GlutinWindowDelegate", superclass).unwrap(); + let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap(); // Add callback methods decl.add_method(sel!(windowShouldClose:), @@ -120,7 +120,7 @@ impl WindowDelegate { window_did_resign_key as extern fn(&Object, Sel, id)); // Store internal state as user data - decl.add_ivar::<*mut c_void>("glutinState"); + decl.add_ivar::<*mut c_void>("winitState"); DELEGATE_CLASS = decl.register(); }); @@ -137,7 +137,7 @@ impl WindowDelegate { unsafe { let delegate = IdRef::new(msg_send![WindowDelegate::class(), new]); - (&mut **delegate).set_ivar("glutinState", state_ptr as *mut ::std::os::raw::c_void); + (&mut **delegate).set_ivar("winitState", state_ptr as *mut ::std::os::raw::c_void); let _: () = msg_send![*state.window, setDelegate:*delegate]; WindowDelegate { state: state, _this: delegate } From cbadc4bc3102413996d27d904e40266b45f9c8b0 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 18:12:32 +1100 Subject: [PATCH 08/14] Fix issue where window would not focus on startup. Fix issue where key window would lose all mouse events once mouse left that window. Make sure that only window under mouse receives mouse scroll wheel events. --- src/platform/macos/events_loop.rs | 37 +++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index 41d10237..970ab5e2 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -224,16 +224,13 @@ impl EventsLoop { } let event_type = ns_event.eventType(); - let window_id = super::window::get_window_id(ns_event.window()); + let ns_window = ns_event.window(); + let window_id = super::window::get_window_id(ns_window); let windows = self.windows.lock().unwrap(); let maybe_window = windows.iter().find(|window| window_id == window.id()); - let window = match maybe_window { - Some(window) => window, - None => return None, - }; - - // FIXME: Document this. Why do we do this? + // 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. match event_type { appkit::NSKeyDown => (), _ => appkit::NSApp().sendEvent_(ns_event), @@ -244,6 +241,12 @@ impl EventsLoop { event: window_event, }; + // Returns `Some` window if one of our windows is the key window. + let maybe_key_window = || windows.iter().find(|window| { + let is_key_window: cocoa::base::BOOL = msg_send![*window.window, isKeyWindow]; + is_key_window == cocoa::base::YES + }); + match event_type { appkit::NSKeyDown => { @@ -347,14 +350,23 @@ impl EventsLoop { appkit::NSRightMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, MouseButton::Right))) }, appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput(ElementState::Pressed, MouseButton::Middle))) }, appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, MouseButton::Middle))) }, + appkit::NSMouseEntered => { Some(into_event(WindowEvent::MouseEntered)) }, appkit::NSMouseExited => { Some(into_event(WindowEvent::MouseLeft)) }, + appkit::NSMouseMoved | appkit::NSLeftMouseDragged | appkit::NSOtherMouseDragged | appkit::NSRightMouseDragged => { + // If the mouse movement was on one of our windows, use it. + // Otherwise, if one of our windows is the key window (receiving input), use it. + // Otherwise, return `None`. + let window = match maybe_window.or_else(maybe_key_window) { + Some(window) => window, + None => return None, + }; + let window_point = ns_event.locationInWindow(); - let ns_window: cocoa::base::id = msg_send![ns_event, window]; let view_point = if ns_window == cocoa::base::nil { let ns_size = foundation::NSSize::new(0.0, 0.0); let ns_rect = foundation::NSRect::new(window_point, ns_size); @@ -369,10 +381,17 @@ impl EventsLoop { let x = (scale_factor * view_point.x as f32) as i32; let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as i32; let window_event = WindowEvent::MouseMoved(x, y); - Some(into_event(window_event)) + let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event }; + Some(event) }, appkit::NSScrollWheel => { + // If none of the windows received the scroll, return `None`. + let window = match maybe_window { + Some(window) => window, + None => return None, + }; + use events::MouseScrollDelta::{LineDelta, PixelDelta}; let scale_factor = window.hidpi_factor(); let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES { From 06c1b5903ba59ae38607f0f7c348dc903297a819 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 19:01:27 +1100 Subject: [PATCH 09/14] Fix bug where an NSEvent is produced when a user opens spotlight while the NSApplication is in focus. This NSEvent produces an undocumented NSEventType value `21` that has no associated variant within the cocoa-rs crate's `NSEventType` enum, thus causing a segfault when attemptingt to match on the value. This commit adds a check for `21` to avoid the segfault. This fixes #104. --- src/platform/macos/events_loop.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index 970ab5e2..fd416a0b 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -223,6 +223,16 @@ impl EventsLoop { 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 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); From 30e00f0454256806376645c564b1669391e074ee Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 19:21:51 +1100 Subject: [PATCH 10/14] Fix comment on event conversion special case --- src/platform/macos/events_loop.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index fd416a0b..023cb758 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -226,9 +226,9 @@ impl EventsLoop { // 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 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. + // 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; } From b62ecfd517733963d618b6bb61eb15c63bd69689 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 19:37:00 +1100 Subject: [PATCH 11/14] Fix comments on macOS backend UserCallback methods --- src/platform/macos/events_loop.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index 023cb758..458c69b5 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -50,7 +50,7 @@ impl UserCallback { // In order to make sure that the pointer is always valid, we must manually guarantee that it // is dropped before the callback itself is dropped. Thus, this should *only* be called at the // beginning of a call to `poll_events` and `run_forever`, both of which *must* drop the - // callback at the end of their scope using `drop_callback`. + // callback at the end of their scope using the `drop` method. fn store(&self, callback: &mut F) where F: FnMut(Event) { @@ -62,7 +62,7 @@ impl UserCallback { // Emits the given event via the user-given callback. // // This is *only* called within the `poll_events` and `run_forever` methods so we know that it - // is safe to `unwrap` the last callback without causing a panic as there must be at least one + // is safe to `unwrap` the callback without causing a panic as there must be at least one // callback stored. // // This is unsafe as it requires dereferencing the pointer to the user-given callback. We From 926e03039f4a596eac717ff598eaf8a5ac093aa8 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 19:47:39 +1100 Subject: [PATCH 12/14] Panic if the user attempts to create window on non-main thread --- src/platform/macos/window.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index d9959fb9..ccc178eb 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -197,6 +197,10 @@ impl Window { pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result { + if !msg_send![cocoa::base::class("NSThread"), isMainThread] { + panic!("Windows can only be created on the main thread on macOS"); + } + let app = match Window::create_app(pl_attribs.activation_policy) { Some(app) => app, None => { return Err(OsError(format!("Couldn't create NSApplication"))); }, From ba71f6fb77b90c305f637691561cb3988ec19d12 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 19:53:13 +1100 Subject: [PATCH 13/14] Add missing unsafe block to check for main thread macOS --- src/platform/macos/window.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index ccc178eb..9c9e9ef4 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -197,8 +197,10 @@ impl Window { pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result { - if !msg_send![cocoa::base::class("NSThread"), isMainThread] { - panic!("Windows can only be created on the main thread on macOS"); + unsafe { + if !msg_send![cocoa::base::class("NSThread"), isMainThread] { + panic!("Windows can only be created on the main thread on macOS"); + } } let app = match Window::create_app(pl_attribs.activation_policy) { From 24b44e3b8e0527859b6a0d5dd231e9acbcaf0b0d Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 5 Feb 2017 20:41:26 +1100 Subject: [PATCH 14/14] Remove confusingly named event import alias --- src/platform/macos/window.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 9c9e9ef4..51c66018 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,4 +1,4 @@ -use {CreationError, WindowEvent as Event, MouseCursor, CursorState}; +use {CreationError, Event, WindowEvent, WindowId, MouseCursor, CursorState}; use CreationError::OsError; use libc; @@ -49,10 +49,10 @@ impl WindowDelegate { // are called as the delegate methods should only be called during a call to // `nextEventMatchingMask` (called via EventsLoop::poll_events and // EventsLoop::run_forever). - unsafe fn emit_event(state: &mut DelegateState, window_event: Event) { + unsafe fn emit_event(state: &mut DelegateState, window_event: WindowEvent) { let window_id = get_window_id(*state.window); - let event = ::Event::WindowEvent { - window_id: ::WindowId(window_id), + let event = Event::WindowEvent { + window_id: WindowId(window_id), event: window_event, }; @@ -65,7 +65,7 @@ impl WindowDelegate { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, Event::Closed); + emit_event(state, WindowEvent::Closed); } YES } @@ -78,7 +78,7 @@ impl WindowDelegate { let scale_factor = NSWindow::backingScaleFactor(*state.window) as f32; let width = (scale_factor * rect.size.width as f32) as u32; let height = (scale_factor * rect.size.height as f32) as u32; - emit_event(state, Event::Resized(width, height)); + emit_event(state, WindowEvent::Resized(width, height)); } } @@ -88,7 +88,7 @@ impl WindowDelegate { // lost focus let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, Event::Focused(true)); + emit_event(state, WindowEvent::Focused(true)); } } @@ -96,7 +96,7 @@ impl WindowDelegate { unsafe { let state: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state as *mut DelegateState); - emit_event(state, Event::Focused(false)); + emit_event(state, WindowEvent::Focused(false)); } }