From 84703027d63e22361197a1fc74a4949becf5fccb Mon Sep 17 00:00:00 2001 From: Evgeny Rozaliev Date: Fri, 5 Jun 2015 16:38:21 +0300 Subject: [add] ios support --- src/api/ios/delegate.rs | 223 ++++++++++++++++++++++++++ src/api/ios/ffi.rs | 130 +++++++++++++++ src/api/ios/mod.rs | 415 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 768 insertions(+) create mode 100644 src/api/ios/delegate.rs create mode 100644 src/api/ios/ffi.rs create mode 100644 src/api/ios/mod.rs (limited to 'src/api/ios') diff --git a/src/api/ios/delegate.rs b/src/api/ios/delegate.rs new file mode 100644 index 0000000..1d9f90e --- /dev/null +++ b/src/api/ios/delegate.rs @@ -0,0 +1,223 @@ +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, + 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 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)); + 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/ffi.rs b/src/api/ios/ffi.rs new file mode 100644 index 0000000..72f2ff2 --- /dev/null +++ b/src/api/ios/ffi.rs @@ -0,0 +1,130 @@ +use std::ffi::CString; + +use libc; +use objc::runtime::{ Object, Class }; + +#[allow(non_camel_case_types)] +pub type id = *mut Object; + +#[allow(non_camel_case_types)] +#[allow(non_upper_case_globals)] +pub const nil: id = 0 as id; + +pub type CFStringRef = *const libc::c_void; +pub type CFTimeInterval = f64; +pub type Boolean = u32; + +#[allow(non_upper_case_globals)] +pub const kCFRunLoopRunHandledSource: i32 = 4; + +#[cfg(target_pointer_width = "32")] +pub type CGFloat = f32; +#[cfg(target_pointer_width = "64")] +pub type CGFloat = f64; + +#[cfg(target_pointer_width = "32")] +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 { + pub x: CGFloat, + pub y: CGFloat, +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct CGRect { + pub origin: CGPoint, + pub size: CGSize +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct CGSize { + pub width: CGFloat, + 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; + + // SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled ); + pub fn CFRunLoopRunInMode(mode: CFStringRef, seconds: CFTimeInterval, returnAfterSourceHandled: Boolean) -> i32; +} + +extern { + pub fn setjmp(env: *mut libc::c_void) -> libc::c_int; + 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] + } + + #[allow(non_snake_case)] + unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id; + #[allow(non_snake_case)] + unsafe fn stringByAppendingString_(self, other: id) -> id; + unsafe fn init_str(self, string: &str) -> Self; + #[allow(non_snake_case)] + unsafe fn UTF8String(self) -> *const libc::c_char; +} + +impl NSString for id { + unsafe fn initWithUTF8String_(self, c_string: *const i8) -> id { + msg_send![self, initWithUTF8String:c_string as id] + } + + unsafe fn stringByAppendingString_(self, other: id) -> id { + msg_send![self, stringByAppendingString:other] + } + + unsafe fn init_str(self, string: &str) -> id { + let cstring = CString::new(string).unwrap(); + self.initWithUTF8String_(cstring.as_ptr()) + } + + unsafe fn UTF8String(self) -> *const libc::c_char { + msg_send![self, UTF8String] + } +} + +#[inline] +pub fn class(name: &str) -> *mut Class { + unsafe { + ::std::mem::transmute(Class::get(name)) + } +} diff --git a/src/api/ios/mod.rs b/src/api/ios/mod.rs new file mode 100644 index 0000000..fbb0c93 --- /dev/null +++ b/src/api/ios/mod.rs @@ -0,0 +1,415 @@ +//! 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::mem; +use std::ffi::CString; + +use libc; +use objc::runtime::{Class, BOOL, YES, NO }; + +use native_monitor::NativeMonitorId; +use { Api, PixelFormat, CreationError, BuilderAttribs, GlContext, CursorState, MouseCursor, Event }; +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 + }; + + +static mut jmpbuf: [libc::c_int;27] = [0;27]; + +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) +} + + +impl DelegateState { + fn new(window: id, controller:id, view: id, size: (u32,u32)) -> DelegateState { + DelegateState { + events_queue: VecDeque::new(), + window: window, + controller: controller, + view: view, + size: size + } + } +} + + +pub fn get_available_monitors() -> VecDeque { + let mut rb = VecDeque::new(); + rb.push_back(MonitorID); + rb +} + +pub fn get_primary_monitor() -> MonitorID { + MonitorID +} + +impl MonitorID { + pub fn get_name(&self) -> Option { + Some("Primary".to_string()) + } + + pub fn get_native_identifier(&self) -> NativeMonitorId { + NativeMonitorId::Unavailable + } + + pub fn get_dimensions(&self) -> (u32, u32) { + unimplemented!() + } +} + + +impl Window { + + pub fn new(builder: BuilderAttribs) -> 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: BuilderAttribs) { + 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 + ]; + self.make_current(); + + let state = &mut *self.delegate_state; + + if builder.multitouch { + let _: () = msg_send![state.view, setMultipleTouchEnabled:YES]; + } + + let layer: id = msg_send![state.view, layer]; + 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 + } + } + + fn start_app() { + unsafe { + UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate")); + } + } + + pub fn is_closed(&self) -> bool { + false + } + + pub fn set_title(&self, _: &str) { + } + + pub fn show(&self) { + } + + pub fn hide(&self) { + } + + pub fn get_position(&self) -> Option<(i32, i32)> { + None + } + + pub fn set_position(&self, _x: i32, _y: i32) { + } + + pub fn get_inner_size(&self) -> Option<(u32, u32)> { + unsafe { Some((&*self.delegate_state).size) } + } + + pub fn get_outer_size(&self) -> Option<(u32, u32)> { + self.get_inner_size() + } + + pub fn set_inner_size(&self, _x: u32, _y: u32) { + } + + pub fn poll_events(&self) -> PollEventsIterator { + PollEventsIterator { + window: self + } + } + + pub fn wait_events(&self) -> WaitEventsIterator { + WaitEventsIterator { + window: self + } + } + + pub fn platform_display(&self) -> *mut libc::c_void { + unimplemented!(); + } + + pub fn platform_window(&self) -> *mut libc::c_void { + unimplemented!() + } + + pub fn get_pixel_format(&self) -> PixelFormat { + unimplemented!(); + } + + pub fn set_window_resize_callback(&mut self, _: Option) { + } + + pub fn set_cursor(&self, _: MouseCursor) { + } + + pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> { + Ok(()) + } + + pub fn hidpi_factor(&self) -> f32 { + 1.0 + } + + pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> { + unimplemented!(); + } + + pub fn create_window_proxy(&self) -> WindowProxy { + WindowProxy + } + +} + +impl GlContext for Window { + unsafe fn make_current(&self) { + let _:BOOL = msg_send![Class::get("EAGLContext").unwrap(), setCurrentContext: self.eagl_context]; + } + + fn is_current(&self) -> bool { + false + } + + fn get_proc_address(&self, addr: &str) -> *const libc::c_void { + 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()) + } + } + + fn swap_buffers(&self) { + unsafe { let _:BOOL = msg_send![self.eagl_context, presentRenderbuffer: gles::RENDERBUFFER]; } + } + + fn get_api(&self) -> Api { + unimplemented!() + } + + fn get_pixel_format(&self) -> PixelFormat { + unimplemented!() + } +} + +impl WindowProxy { + pub fn wakeup_event_loop(&self) { + unimplemented!() + } +} + + +impl<'a> Iterator for WaitEventsIterator<'a> { + type Item = Event; + + 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() + } + } +} -- cgit v1.2.3