diff --git a/.travis.yml b/.travis.yml index 486fc727..deeb1c39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,13 +13,14 @@ addons: script: - cargo build --verbose + - [ $TRAVIS_OS_NAME = osx ] && cargo build --target x86_64-apple-ios - cargo test --verbose os: - linux - osx -after_success: +after_success: - | [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ] && diff --git a/src/api/ios/delegate.rs b/src/api/ios/delegate.rs deleted file mode 100644 index 62cd89d1..00000000 --- a/src/api/ios/delegate.rs +++ /dev/null @@ -1,226 +0,0 @@ -use libc; -use std::mem; -use super::DelegateState; -use Event; -use events::{ Touch, TouchPhase }; - -use objc::runtime::{ Class, Object, Sel, BOOL, YES }; -use objc::declare::{ ClassDecl }; - -use super::ffi::{ - longjmp, - id, - nil, - CGRect, - CGPoint, - CGFloat, - UIViewAutoresizingFlexibleWidth, - UIViewAutoresizingFlexibleHeight - }; - -use super::jmpbuf; - - -pub fn create_delegate_class() { - extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL { - unsafe { - let main_screen: id = msg_send![Class::get("UIScreen").unwrap(), mainScreen]; - let bounds: CGRect = msg_send![main_screen, bounds]; - let scale: CGFloat = msg_send![main_screen, nativeScale]; - - let window: id = msg_send![Class::get("UIWindow").unwrap(), alloc]; - let window: id = msg_send![window, initWithFrame:bounds.clone()]; - - let size = (bounds.size.width as u32, bounds.size.height as u32); - - let view_controller: id = msg_send![Class::get("MainViewController").unwrap(), alloc]; - let view_controller: id = msg_send![view_controller, init]; - - - let class = Class::get("MainView").unwrap(); - let view:id = msg_send![class, alloc]; - let view:id = msg_send![view, initForGl:&bounds]; - - - let _: () = msg_send![view_controller, setView:view]; - - - let _: () = msg_send![window, setRootViewController:view_controller]; - - let _: () = msg_send![window, addSubview:view]; - let _: () = msg_send![window, makeKeyAndVisible]; - - let state = Box::new(DelegateState::new(window, view_controller, view, size, scale as f32)); - let state_ptr: *mut DelegateState = mem::transmute(state); - this.set_ivar("glutinState", state_ptr as *mut libc::c_void); - - - let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0]; - } - YES - } - - extern fn post_launch(_: &Object, _: Sel, _: id) { - unsafe { longjmp(mem::transmute(&mut jmpbuf),1); } - } - - extern fn did_become_active(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut libc::c_void = *this.get_ivar("glutinState"); - let state = &mut *(state as *mut DelegateState); - state.events_queue.push_back(Event::Focused(true)); - } - } - - extern fn will_resign_active(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut libc::c_void = *this.get_ivar("glutinState"); - let state = &mut *(state as *mut DelegateState); - state.events_queue.push_back(Event::Focused(false)); - } - } - - extern fn will_enter_foreground(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut libc::c_void = *this.get_ivar("glutinState"); - let state = &mut *(state as *mut DelegateState); - state.events_queue.push_back(Event::Suspended(false)); - } - } - - extern fn did_enter_background(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut libc::c_void = *this.get_ivar("glutinState"); - let state = &mut *(state as *mut DelegateState); - state.events_queue.push_back(Event::Suspended(true)); - } - } - - extern fn will_terminate(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut libc::c_void = *this.get_ivar("glutinState"); - let state = &mut *(state as *mut DelegateState); - // push event to the front to garantee that we'll process it - // immidiatly after jump - state.events_queue.push_front(Event::Closed); - longjmp(mem::transmute(&mut jmpbuf),1); - } - } - - extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) { - unsafe { - let state: *mut libc::c_void = *this.get_ivar("glutinState"); - let state = &mut *(state as *mut DelegateState); - - let touches_enum: id = msg_send![touches, objectEnumerator]; - - loop { - let touch: id = msg_send![touches_enum, nextObject]; - if touch == nil { - break - } - let location: CGPoint = msg_send![touch, locationInView:nil]; - let touch_id = touch as u64; - let phase: i32 = msg_send![touch, phase]; - - state.events_queue.push_back(Event::Touch(Touch { - id: touch_id, - location: (location.x as f64, location.y as f64), - phase: match phase { - 0 => TouchPhase::Started, - 1 => TouchPhase::Moved, - // 2 is UITouchPhaseStationary and is not expected here - 3 => TouchPhase::Ended, - 4 => TouchPhase::Cancelled, - _ => panic!("unexpected touch phase: {:?}", phase) - } - })); - } - } - } - - let superclass = Class::get("UIResponder").unwrap(); - let mut decl = ClassDecl::new(superclass, "AppDelegate").unwrap(); - - unsafe { - decl.add_method(sel!(application:didFinishLaunchingWithOptions:), - did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL); - - decl.add_method(sel!(applicationDidBecomeActive:), - did_become_active as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(applicationWillResignActive:), - will_resign_active as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(applicationWillEnterForeground:), - will_enter_foreground as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(applicationDidEnterBackground:), - did_enter_background as extern fn(&Object, Sel, id)); - - decl.add_method(sel!(applicationWillTerminate:), - will_terminate as extern fn(&Object, Sel, id)); - - - decl.add_method(sel!(touchesBegan:withEvent:), - handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); - - decl.add_method(sel!(touchesMoved:withEvent:), - handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); - - decl.add_method(sel!(touchesEnded:withEvent:), - handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); - - decl.add_method(sel!(touchesCancelled:withEvent:), - handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); - - - - decl.add_method(sel!(postLaunch:), - post_launch as extern fn(&Object, Sel, id)); - - decl.add_ivar::<*mut libc::c_void>("glutinState"); - - decl.register(); - } -} - - -pub fn create_view_class() { - let superclass = Class::get("UIViewController").unwrap(); - let decl = ClassDecl::new(superclass, "MainViewController").unwrap(); - - decl.register(); - - extern fn init_for_gl(this: &Object, _: Sel, frame: *const libc::c_void) -> id { - unsafe { - let bounds: *const CGRect = mem::transmute(frame); - let view: id = msg_send![this, initWithFrame:(*bounds).clone()]; - - let _: () = msg_send![view, setAutoresizingMask: UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight]; - let _: () = msg_send![view, setAutoresizesSubviews:YES]; - - let layer: id = msg_send![view, layer]; - let _ : () = msg_send![layer, setOpaque:YES]; - - view - } - } - - extern fn layer_class(_: &Class, _: Sel) -> *const Class { - unsafe { mem::transmute(Class::get("CAEAGLLayer").unwrap()) } - } - - - let superclass = Class::get("UIView").unwrap(); - let mut decl = ClassDecl::new(superclass, "MainView").unwrap(); - - unsafe { - decl.add_method(sel!(initForGl:), - init_for_gl as extern fn(&Object, Sel, *const libc::c_void) -> id); - - decl.add_class_method(sel!(layerClass), - layer_class as extern fn(&Class, Sel) -> *const Class); - decl.register(); - } -} \ No newline at end of file diff --git a/src/api/ios/mod.rs b/src/api/ios/mod.rs deleted file mode 100644 index 8bee5134..00000000 --- a/src/api/ios/mod.rs +++ /dev/null @@ -1,467 +0,0 @@ -//! iOS support -//! -//! # Building app -//! To build ios app you will need rustc built for this targets: -//! -//! - armv7-apple-ios -//! - armv7s-apple-ios -//! - i386-apple-ios -//! - aarch64-apple-ios -//! - x86_64-apple-ios -//! -//! Then -//! -//! ``` -//! cargo build --target=... -//! ``` -//! The simplest way to integrate your app into xcode environment is to build it -//! as a static library. Wrap your main function and export it. -//! -//! ```rust, ignore -//! #[no_mangle] -//! pub extern fn start_glutin_app() { -//! start_inner() -//! } -//! -//! fn start_inner() { -//! ... -//! } -//! -//! ``` -//! -//! Compile project and then drag resulting .a into Xcode project. Add glutin.h to xcode. -//! -//! ```c -//! void start_glutin_app(); -//! ``` -//! -//! Use start_glutin_app inside your xcode's main function. -//! -//! -//! # App lifecycle and events -//! -//! iOS environment is very different from other platforms and you must be very -//! careful with it's events. Familiarize yourself with [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/). -//! -//! -//! This is how those event are represented in glutin: -//! -//! - applicationDidBecomeActive is Focused(true) -//! - applicationWillResignActive is Focused(false) -//! - applicationDidEnterBackground is Suspended(true) -//! - applicationWillEnterForeground is Suspended(false) -//! - applicationWillTerminate is Closed -//! -//! Keep in mind that after Closed event is received every attempt to draw with opengl will result in segfault. -//! -//! Also note that app will not receive Closed event if suspended, it will be SIGKILL'ed - - - - -#![cfg(target_os = "ios")] -#![deny(warnings)] - -use std::collections::VecDeque; -use std::ptr; -use std::io; -use std::mem; -use std::ffi::CString; - -use libc; -use objc::runtime::{Class, BOOL, YES, NO }; - -use native_monitor::NativeMonitorId; -use { Api, PixelFormat, CreationError, GlContext, CursorState, MouseCursor, Event }; -use { PixelFormatRequirements, GlAttributes, WindowAttributes, ContextError }; -use CreationError::OsError; - -mod delegate; -use self::delegate::{ create_delegate_class, create_view_class }; - -mod ffi; -use self::ffi::{ - gles, - setjmp, - dlopen, - dlsym, - UIApplicationMain, - kEAGLColorFormatRGB565, - CFTimeInterval, - CFRunLoopRunInMode, - kCFRunLoopDefaultMode, - kCFRunLoopRunHandledSource, - kEAGLDrawablePropertyRetainedBacking, - kEAGLDrawablePropertyColorFormat, - RTLD_LAZY, - RTLD_GLOBAL, - id, - nil, - NSString, - CGFloat - }; - - -static mut jmpbuf: [libc::c_int;27] = [0;27]; - -#[derive(Clone)] -pub struct MonitorId; - -pub struct Window { - eagl_context: id, - delegate_state: *mut DelegateState -} - -#[derive(Clone)] -pub struct WindowProxy; - -pub struct PollEventsIterator<'a> { - window: &'a Window, -} - -pub struct WaitEventsIterator<'a> { - window: &'a Window, -} - -#[derive(Debug)] -struct DelegateState { - events_queue: VecDeque, - window: id, - controller: id, - view: id, - size: (u32,u32), - scale: f32 -} - - -impl DelegateState { - #[inline] - fn new(window: id, controller:id, view: id, size: (u32,u32), scale: f32) -> DelegateState { - DelegateState { - events_queue: VecDeque::new(), - window: window, - controller: controller, - view: view, - size: size, - scale: scale - } - } -} - -#[inline] -pub fn get_available_monitors() -> VecDeque { - let mut rb = VecDeque::new(); - rb.push_back(MonitorId); - rb -} - -#[inline] -pub fn get_primary_monitor() -> MonitorId { - MonitorId -} - -impl MonitorId { - #[inline] - pub fn get_name(&self) -> Option { - Some("Primary".to_string()) - } - - #[inline] - pub fn get_native_identifier(&self) -> NativeMonitorId { - NativeMonitorId::Unavailable - } - - #[inline] - pub fn get_dimensions(&self) -> (u32, u32) { - unimplemented!() - } -} - -#[derive(Clone, Default)] -pub struct PlatformSpecificWindowBuilderAttributes; - -impl Window { - - pub fn new(builder: &WindowAttributes, _: &PixelFormatRequirements, _: &GlAttributes<&Window>, - _: &PlatformSpecificWindowBuilderAttributes) -> Result - { - unsafe { - if setjmp(mem::transmute(&mut jmpbuf)) != 0 { - let app: id = msg_send![Class::get("UIApplication").unwrap(), sharedApplication]; - let delegate: id = msg_send![app, delegate]; - let state: *mut libc::c_void = *(&*delegate).get_ivar("glutinState"); - let state = state as *mut DelegateState; - - let context = Window::create_context(); - - let mut window = Window { - eagl_context: context, - delegate_state: state - }; - - window.init_context(builder); - - return Ok(window) - } - } - - create_delegate_class(); - create_view_class(); - Window::start_app(); - - Err(CreationError::OsError(format!("Couldn't create UIApplication"))) - } - - unsafe fn init_context(&mut self, builder: &WindowAttributes) { - let draw_props: id = msg_send![Class::get("NSDictionary").unwrap(), alloc]; - let draw_props: id = msg_send![draw_props, - initWithObjects: - vec![ - msg_send![Class::get("NSNumber").unwrap(), numberWithBool: NO], - kEAGLColorFormatRGB565 - ].as_ptr() - forKeys: - vec![ - kEAGLDrawablePropertyRetainedBacking, - kEAGLDrawablePropertyColorFormat - ].as_ptr() - count: 2 - ]; - let _ = self.make_current(); - - let state = &mut *self.delegate_state; - - if builder.multitouch { - let _: () = msg_send![state.view, setMultipleTouchEnabled:YES]; - } - - let _: () = msg_send![state.view, setContentScaleFactor:state.scale as CGFloat]; - - let layer: id = msg_send![state.view, layer]; - let _: () = msg_send![layer, setContentsScale:state.scale as CGFloat]; - let _: () = msg_send![layer, setDrawableProperties: draw_props]; - - let gl = gles::Gles2::load_with(|symbol| self.get_proc_address(symbol)); - let mut color_render_buf: gles::types::GLuint = 0; - let mut frame_buf: gles::types::GLuint = 0; - gl.GenRenderbuffers(1, &mut color_render_buf); - gl.BindRenderbuffer(gles::RENDERBUFFER, color_render_buf); - - let ok: BOOL = msg_send![self.eagl_context, renderbufferStorage:gles::RENDERBUFFER fromDrawable:layer]; - if ok != YES { - panic!("EAGL: could not set renderbufferStorage"); - } - - gl.GenFramebuffers(1, &mut frame_buf); - gl.BindFramebuffer(gles::FRAMEBUFFER, frame_buf); - - gl.FramebufferRenderbuffer(gles::FRAMEBUFFER, gles::COLOR_ATTACHMENT0, gles::RENDERBUFFER, color_render_buf); - - let status = gl.CheckFramebufferStatus(gles::FRAMEBUFFER); - if gl.CheckFramebufferStatus(gles::FRAMEBUFFER) != gles::FRAMEBUFFER_COMPLETE { - panic!("framebuffer status: {:?}", status); - } - } - - fn create_context() -> id { - unsafe { - let eagl_context: id = msg_send![Class::get("EAGLContext").unwrap(), alloc]; - let eagl_context: id = msg_send![eagl_context, initWithAPI:2]; // es2 - eagl_context - } - } - - #[inline] - fn start_app() { - unsafe { - UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate")); - } - } - - #[inline] - pub fn set_title(&self, _: &str) { - } - - #[inline] - pub fn show(&self) { - } - - #[inline] - pub fn hide(&self) { - } - - #[inline] - pub fn get_position(&self) -> Option<(i32, i32)> { - None - } - - #[inline] - pub fn set_position(&self, _x: i32, _y: i32) { - } - - #[inline] - pub fn get_inner_size(&self) -> Option<(u32, u32)> { - unsafe { Some((&*self.delegate_state).size) } - } - - #[inline] - pub fn get_outer_size(&self) -> Option<(u32, u32)> { - self.get_inner_size() - } - - #[inline] - pub fn set_inner_size(&self, _x: u32, _y: u32) { - } - - #[inline] - pub fn poll_events(&self) -> PollEventsIterator { - PollEventsIterator { - window: self - } - } - - #[inline] - pub fn wait_events(&self) -> WaitEventsIterator { - WaitEventsIterator { - window: self - } - } - - #[inline] - pub fn platform_display(&self) -> *mut libc::c_void { - unimplemented!(); - } - - #[inline] - pub fn platform_window(&self) -> *mut libc::c_void { - unimplemented!() - } - - #[inline] - pub fn get_pixel_format(&self) -> PixelFormat { - unimplemented!(); - } - - #[inline] - pub fn set_window_resize_callback(&mut self, _: Option) { - } - - #[inline] - pub fn set_cursor(&self, _: MouseCursor) { - } - - #[inline] - pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> { - Ok(()) - } - - #[inline] - pub fn hidpi_factor(&self) -> f32 { - unsafe { (&*self.delegate_state) }.scale - } - - #[inline] - pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> { - unimplemented!(); - } - - #[inline] - pub fn create_window_proxy(&self) -> WindowProxy { - WindowProxy - } - -} - -impl GlContext for Window { - #[inline] - unsafe fn make_current(&self) -> Result<(), ContextError> { - let res: BOOL = msg_send![Class::get("EAGLContext").unwrap(), setCurrentContext: self.eagl_context]; - if res == YES { - Ok(()) - } else { - Err(ContextError::IoError(io::Error::new(io::ErrorKind::Other, "EAGLContext::setCurrentContext unsuccessful"))) - } - } - - #[inline] - fn is_current(&self) -> bool { - false - } - - fn get_proc_address(&self, addr: &str) -> *const () { - let addr_c = CString::new(addr).unwrap(); - let path = CString::new("/System/Library/Frameworks/OpenGLES.framework/OpenGLES").unwrap(); - unsafe { - let lib = dlopen(path.as_ptr(), RTLD_LAZY | RTLD_GLOBAL); - dlsym(lib, addr_c.as_ptr()) as *const _ - } - } - - #[inline] - fn swap_buffers(&self) -> Result<(), ContextError> { - unsafe { - let res: BOOL = msg_send![self.eagl_context, presentRenderbuffer: gles::RENDERBUFFER]; - if res == YES { - Ok(()) - } else { - Err(ContextError::IoError(io::Error::new(io::ErrorKind::Other, "EAGLContext.presentRenderbuffer unsuccessful"))) - } - } - } - - #[inline] - fn get_api(&self) -> Api { - unimplemented!() - } - - #[inline] - fn get_pixel_format(&self) -> PixelFormat { - unimplemented!() - } -} - -impl WindowProxy { - #[inline] - pub fn wakeup_event_loop(&self) { - unimplemented!() - } -} - - -impl<'a> Iterator for WaitEventsIterator<'a> { - type Item = Event; - - #[inline] - fn next(&mut self) -> Option { - loop { - if let Some(ev) = self.window.poll_events().next() { - return Some(ev); - } - } - } -} - -impl<'a> Iterator for PollEventsIterator<'a> { - type Item = Event; - - fn next(&mut self) -> Option { - unsafe { - let state = &mut *self.window.delegate_state; - - if let Some(event) = state.events_queue.pop_front() { - return Some(event) - } - - // jump hack, so we won't quit on willTerminate event before processing it - if setjmp(mem::transmute(&mut jmpbuf)) != 0 { - return state.events_queue.pop_front() - } - - // run runloop - let seconds: CFTimeInterval = 0.000002; - while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {} - - state.events_queue.pop_front() - } - } -} diff --git a/src/api/mod.rs b/src/api/mod.rs index 3eabaf66..108ab1ad 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -3,4 +3,3 @@ pub mod dlopen; pub mod wayland; pub mod x11; -pub mod ios; diff --git a/src/api/ios/ffi.rs b/src/platform/ios/ffi.rs similarity index 79% rename from src/api/ios/ffi.rs rename to src/platform/ios/ffi.rs index 72f2ff24..42a946d9 100644 --- a/src/api/ios/ffi.rs +++ b/src/platform/ios/ffi.rs @@ -27,12 +27,6 @@ pub type NSUInteger = u32; #[cfg(target_pointer_width = "64")] pub type NSUInteger = u64; -#[allow(non_upper_case_globals)] -pub const UIViewAutoresizingFlexibleWidth: NSUInteger = 1 << 1; -#[allow(non_upper_case_globals)] -pub const UIViewAutoresizingFlexibleHeight: NSUInteger = 1 << 4; - - #[repr(C)] #[derive(Debug, Clone)] pub struct CGPoint { @@ -54,21 +48,12 @@ pub struct CGSize { pub height: CGFloat } -pub mod gles { - include!(concat!(env!("OUT_DIR"), "/gles2_bindings.rs")); -} - #[link(name = "UIKit", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")] #[link(name = "GlKit", kind = "framework")] extern { pub static kCFRunLoopDefaultMode: CFStringRef; - pub static kEAGLColorFormatRGB565: id; - // pub static kEAGLColorFormatRGBA8: id; - pub static kEAGLDrawablePropertyColorFormat: id; - pub static kEAGLDrawablePropertyRetainedBacking: id; - // int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName ); pub fn UIApplicationMain(argc: libc::c_int, argv: *const libc::c_char, principalClassName: id, delegateClassName: id) -> libc::c_int; @@ -81,14 +66,6 @@ extern { pub fn longjmp(env: *mut libc::c_void, val: libc::c_int); } -pub const RTLD_LAZY: libc::c_int = 0x001; -pub const RTLD_GLOBAL: libc::c_int = 0x100; - -extern { - pub fn dlopen(filename: *const libc::c_char, flag: libc::c_int) -> *mut libc::c_void; - pub fn dlsym(handle: *mut libc::c_void, symbol: *const libc::c_char) -> *mut libc::c_void; -} - pub trait NSString { unsafe fn alloc(_: Self) -> id { msg_send![class("NSString"), alloc] diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs index 0b1a5509..547a9623 100644 --- a/src/platform/ios/mod.rs +++ b/src/platform/ios/mod.rs @@ -1,54 +1,495 @@ +//! iOS support +//! +//! # Building app +//! To build ios app you will need rustc built for this targets: +//! +//! - armv7-apple-ios +//! - armv7s-apple-ios +//! - i386-apple-ios +//! - aarch64-apple-ios +//! - x86_64-apple-ios +//! +//! Then +//! +//! ``` +//! cargo build --target=... +//! ``` +//! The simplest way to integrate your app into xcode environment is to build it +//! as a static library. Wrap your main function and export it. +//! +//! ```rust, ignore +//! #[no_mangle] +//! pub extern fn start_glutin_app() { +//! start_inner() +//! } +//! +//! fn start_inner() { +//! ... +//! } +//! +//! ``` +//! +//! Compile project and then drag resulting .a into Xcode project. Add glutin.h to xcode. +//! +//! ```c +//! void start_glutin_app(); +//! ``` +//! +//! Use start_glutin_app inside your xcode's main function. +//! +//! +//! # App lifecycle and events +//! +//! iOS environment is very different from other platforms and you must be very +//! careful with it's events. Familiarize yourself with +//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/). +//! +//! +//! This is how those event are represented in glutin: +//! +//! - applicationDidBecomeActive is Focused(true) +//! - applicationWillResignActive is Focused(false) +//! - applicationDidEnterBackground is Suspended(true) +//! - applicationWillEnterForeground is Suspended(false) +//! - applicationWillTerminate is Closed +//! +//! Keep in mind that after Closed event is received every attempt to draw with +//! opengl will result in segfault. +//! +//! Also note that app will not receive Closed event if suspended, it will be SIGKILL'ed + #![cfg(target_os = "ios")] +#![deny(warnings)] -use GlAttributes; -use CreationError; -use PixelFormat; -use PixelFormatRequirements; -use ContextError; +use std::collections::VecDeque; +use std::ptr; +use std::mem; +use std::os::raw::c_void; -pub use api::ios::*; +use libc; +use libc::c_int; +use objc::runtime::{Class, Object, Sel, BOOL, YES }; +use objc::declare::{ ClassDecl }; -#[derive(Clone, Default)] -pub struct PlatformSpecificHeadlessBuilderAttributes; +use native_monitor::NativeMonitorId; +use { CreationError, CursorState, MouseCursor, Event, WindowAttributes }; +use events::{ Touch, TouchPhase }; -pub struct HeadlessContext(i32); +mod ffi; +use self::ffi::{ + setjmp, + UIApplicationMain, + CFTimeInterval, + CFRunLoopRunInMode, + kCFRunLoopDefaultMode, + kCFRunLoopRunHandledSource, + id, + nil, + NSString, + CGFloat, + longjmp, + CGRect, + CGPoint + }; -impl HeadlessContext { - /// See the docs in the crate root file. - pub fn new(_: (u32, u32), _: &PixelFormatRequirements, _: &GlAttributes<&HeadlessContext>, - _: &PlatformSpecificHeadlessBuilderAttributes) - -> Result - { - unimplemented!() - } +static mut jmpbuf: [c_int;27] = [0;27]; - /// See the docs in the crate root file. - pub unsafe fn make_current(&self) -> Result<(), ContextError> { - unimplemented!() - } +#[derive(Clone)] +pub struct MonitorId; - pub fn swap_buffers(&self) -> Result<(), ContextError> { - unimplemented!() - } +pub struct Window { + delegate_state: *mut DelegateState +} - /// See the docs in the crate root file. - pub fn is_current(&self) -> bool { - unimplemented!() - } +#[derive(Clone)] +pub struct WindowProxy; - /// See the docs in the crate root file. - pub fn get_proc_address(&self, _addr: &str) -> *const () { - unimplemented!() - } +pub struct PollEventsIterator<'a> { + window: &'a Window, +} - pub fn get_api(&self) -> ::Api { - ::Api::OpenGlEs - } +pub struct WaitEventsIterator<'a> { + window: &'a Window, +} - pub fn get_pixel_format(&self) -> PixelFormat { - unimplemented!(); +#[derive(Debug)] +struct DelegateState { + events_queue: VecDeque, + window: id, + controller: id, + size: (u32,u32), + scale: f32 +} + + +impl DelegateState { + #[inline] + fn new(window: id, controller:id, size: (u32,u32), scale: f32) -> DelegateState { + DelegateState { + events_queue: VecDeque::new(), + window: window, + controller: controller, + size: size, + scale: scale + } } } -unsafe impl Send for HeadlessContext {} -unsafe impl Sync for HeadlessContext {} +#[inline] +pub fn get_available_monitors() -> VecDeque { + let mut rb = VecDeque::new(); + rb.push_back(MonitorId); + rb +} + +#[inline] +pub fn get_primary_monitor() -> MonitorId { + MonitorId +} + +impl MonitorId { + #[inline] + pub fn get_name(&self) -> Option { + Some("Primary".to_string()) + } + + #[inline] + pub fn get_native_identifier(&self) -> NativeMonitorId { + NativeMonitorId::Unavailable + } + + #[inline] + pub fn get_dimensions(&self) -> (u32, u32) { + unimplemented!() + } +} + +#[derive(Clone, Default)] +pub struct PlatformSpecificWindowBuilderAttributes; + +impl Window { + + pub fn new(_: &WindowAttributes, _: &PlatformSpecificWindowBuilderAttributes) -> Result + { + unsafe { + if setjmp(mem::transmute(&mut jmpbuf)) != 0 { + let app: id = msg_send![Class::get("UIApplication").unwrap(), sharedApplication]; + let delegate: id = msg_send![app, delegate]; + let state: *mut c_void = *(&*delegate).get_ivar("glutinState"); + let state = state as *mut DelegateState; + + let window = Window { + delegate_state: state + }; + + return Ok(window) + } + } + + Window::create_delegate_class(); + Window::create_view_class(); + Window::start_app(); + + Err(CreationError::OsError(format!("Couldn't create UIApplication"))) + } + + fn create_delegate_class() { + extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL { + unsafe { + let main_screen: id = msg_send![Class::get("UIScreen").unwrap(), mainScreen]; + let bounds: CGRect = msg_send![main_screen, bounds]; + let scale: CGFloat = msg_send![main_screen, nativeScale]; + + let window: id = msg_send![Class::get("UIWindow").unwrap(), alloc]; + let window: id = msg_send![window, initWithFrame:bounds.clone()]; + + let size = (bounds.size.width as u32, bounds.size.height as u32); + + let view_controller: id = msg_send![Class::get("MainViewController").unwrap(), alloc]; + let view_controller: id = msg_send![view_controller, init]; + + let _: () = msg_send![window, setRootViewController:view_controller]; + let _: () = msg_send![window, makeKeyAndVisible]; + + let state = Box::new(DelegateState::new(window, view_controller, size, scale as f32)); + let state_ptr: *mut DelegateState = mem::transmute(state); + this.set_ivar("glutinState", state_ptr as *mut c_void); + + + let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0]; + } + YES + } + + extern fn post_launch(_: &Object, _: Sel, _: id) { + unsafe { longjmp(mem::transmute(&mut jmpbuf),1); } + } + + extern fn did_become_active(this: &Object, _: Sel, _: id) { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + state.events_queue.push_back(Event::Focused(true)); + } + } + + extern fn will_resign_active(this: &Object, _: Sel, _: id) { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + state.events_queue.push_back(Event::Focused(false)); + } + } + + extern fn will_enter_foreground(this: &Object, _: Sel, _: id) { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + state.events_queue.push_back(Event::Suspended(false)); + } + } + + extern fn did_enter_background(this: &Object, _: Sel, _: id) { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + state.events_queue.push_back(Event::Suspended(true)); + } + } + + extern fn will_terminate(this: &Object, _: Sel, _: id) { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + // push event to the front to garantee that we'll process it + // immidiatly after jump + state.events_queue.push_front(Event::Closed); + longjmp(mem::transmute(&mut jmpbuf),1); + } + } + + extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) { + unsafe { + let state: *mut c_void = *this.get_ivar("glutinState"); + let state = &mut *(state as *mut DelegateState); + + let touches_enum: id = msg_send![touches, objectEnumerator]; + + loop { + let touch: id = msg_send![touches_enum, nextObject]; + if touch == nil { + break + } + let location: CGPoint = msg_send![touch, locationInView:nil]; + let touch_id = touch as u64; + let phase: i32 = msg_send![touch, phase]; + + state.events_queue.push_back(Event::Touch(Touch { + id: touch_id, + location: (location.x as f64, location.y as f64), + phase: match phase { + 0 => TouchPhase::Started, + 1 => TouchPhase::Moved, + // 2 is UITouchPhaseStationary and is not expected here + 3 => TouchPhase::Ended, + 4 => TouchPhase::Cancelled, + _ => panic!("unexpected touch phase: {:?}", phase) + } + })); + } + } + } + + let app_delegate_class = Class::get("AppDelegate").unwrap(); + let mut decl = ClassDecl::new("UIResponder", app_delegate_class).unwrap(); + + unsafe { + decl.add_method(sel!(application:didFinishLaunchingWithOptions:), + did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL); + + decl.add_method(sel!(applicationDidBecomeActive:), + did_become_active as extern fn(&Object, Sel, id)); + + decl.add_method(sel!(applicationWillResignActive:), + will_resign_active as extern fn(&Object, Sel, id)); + + decl.add_method(sel!(applicationWillEnterForeground:), + will_enter_foreground as extern fn(&Object, Sel, id)); + + decl.add_method(sel!(applicationDidEnterBackground:), + did_enter_background as extern fn(&Object, Sel, id)); + + decl.add_method(sel!(applicationWillTerminate:), + will_terminate as extern fn(&Object, Sel, id)); + + + decl.add_method(sel!(touchesBegan:withEvent:), + handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); + + decl.add_method(sel!(touchesMoved:withEvent:), + handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); + + decl.add_method(sel!(touchesEnded:withEvent:), + handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); + + decl.add_method(sel!(touchesCancelled:withEvent:), + handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id)); + + + decl.add_method(sel!(postLaunch:), + post_launch as extern fn(&Object, Sel, id)); + + decl.add_ivar::<*mut c_void>("glutinState"); + + decl.register(); + } + } + + fn create_view_class() { + let main_vc_class = Class::get("MainViewController").unwrap(); + let decl = ClassDecl::new("UIViewController", main_vc_class).unwrap(); + + decl.register(); + } + + #[inline] + fn start_app() { + unsafe { + UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate")); + } + } + + #[inline] + pub fn set_title(&self, _: &str) { + } + + #[inline] + pub fn show(&self) { + } + + #[inline] + pub fn hide(&self) { + } + + #[inline] + pub fn get_position(&self) -> Option<(i32, i32)> { + None + } + + #[inline] + pub fn set_position(&self, _x: i32, _y: i32) { + } + + #[inline] + pub fn get_inner_size(&self) -> Option<(u32, u32)> { + unsafe { Some((&*self.delegate_state).size) } + } + + #[inline] + pub fn get_outer_size(&self) -> Option<(u32, u32)> { + self.get_inner_size() + } + + #[inline] + pub fn set_inner_size(&self, _x: u32, _y: u32) { + } + + #[inline] + pub fn poll_events(&self) -> PollEventsIterator { + PollEventsIterator { + window: self + } + } + + #[inline] + pub fn wait_events(&self) -> WaitEventsIterator { + WaitEventsIterator { + window: self + } + } + + #[inline] + pub fn platform_display(&self) -> *mut libc::c_void { + unimplemented!(); + } + + #[inline] + pub fn platform_window(&self) -> *mut libc::c_void { + unimplemented!() + } + + #[inline] + pub fn set_window_resize_callback(&mut self, _: Option) { + } + + #[inline] + pub fn set_cursor(&self, _: MouseCursor) { + } + + #[inline] + pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> { + Ok(()) + } + + #[inline] + pub fn hidpi_factor(&self) -> f32 { + unsafe { (&*self.delegate_state) }.scale + } + + #[inline] + pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> { + unimplemented!(); + } + + #[inline] + pub fn create_window_proxy(&self) -> WindowProxy { + WindowProxy + } + +} + +impl WindowProxy { + #[inline] + pub fn wakeup_event_loop(&self) { + unimplemented!() + } +} + + +impl<'a> Iterator for WaitEventsIterator<'a> { + type Item = Event; + + #[inline] + fn next(&mut self) -> Option { + loop { + if let Some(ev) = self.window.poll_events().next() { + return Some(ev); + } + } + } +} + +impl<'a> Iterator for PollEventsIterator<'a> { + type Item = Event; + + fn next(&mut self) -> Option { + unsafe { + let state = &mut *self.window.delegate_state; + + if let Some(event) = state.events_queue.pop_front() { + return Some(event) + } + + // jump hack, so we won't quit on willTerminate event before processing it + if setjmp(mem::transmute(&mut jmpbuf)) != 0 { + return state.events_queue.pop_front() + } + + // run runloop + let seconds: CFTimeInterval = 0.000002; + while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {} + + state.events_queue.pop_front() + } + } +} diff --git a/src/platform/macos/monitor.rs b/src/platform/macos/monitor.rs index 87d2c97b..22df205c 100644 --- a/src/platform/macos/monitor.rs +++ b/src/platform/macos/monitor.rs @@ -11,9 +11,7 @@ pub fn get_available_monitors() -> VecDeque { let max_displays = 10u32; let mut active_displays = [0u32; 10]; let mut display_count = 0; - display::CGGetActiveDisplayList(max_displays, - &mut active_displays[0], - &mut display_count); + display::CGGetActiveDisplayList(max_displays, &mut active_displays[0], &mut display_count); for i in 0..display_count as usize { monitors.push_back(MonitorId(active_displays[i])); } @@ -23,18 +21,14 @@ pub fn get_available_monitors() -> VecDeque { #[inline] pub fn get_primary_monitor() -> MonitorId { - let id = unsafe { - MonitorId(display::CGMainDisplayID()) - }; + let id = unsafe { MonitorId(display::CGMainDisplayID()) }; id } impl MonitorId { pub fn get_name(&self) -> Option { let MonitorId(display_id) = *self; - let screen_num = unsafe { - display::CGDisplayModelNumber(display_id) - }; + let screen_num = unsafe { display::CGDisplayModelNumber(display_id) }; Some(format!("Monitor #{}", screen_num)) }