#![feature(box_syntax, plugin, libc, core, old_io, collections, std_misc)]
#![unstable]
extern crate libc;
use std::ffi::{CString};
use std::sync::mpsc::{Sender, Receiver, TryRecvError, channel};
use std::sync::Mutex;
use std::thread::Thread;
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
#[doc(hidden)]
pub mod ffi;
static mut ANDROID_APP: *mut ffi::android_app = 0 as *mut ffi::android_app;
#[doc(hidden)]
struct Context {
senders: Mutex<Vec<Sender<Event>>>,
missed: Mutex<Vec<Event>>,
missedcnt: AtomicUsize,
missedmax: usize,
shutdown: AtomicBool,
}
#[derive(Debug)]
pub enum Event {
EventUp,
EventDown,
EventMove(i32, i32),
EventKeyUp,
EventKeyDown,
InitWindow,
SaveState,
TermWindow,
GainedFocus,
LostFocus,
InputChanged,
WindowResized,
WindowRedrawNeeded,
ContentRectChanged,
ConfigChanged,
LowMemory,
Start,
Resume,
Pause,
Stop,
Destroy,
}
impl Copy for Event {}
#[cfg(not(target_os = "android"))]
use this_platform_is_not_supported;
#[macro_export]
macro_rules! android_start(
($main: ident) => (
pub mod __android_start {
extern crate android_glue;
#[start]
pub fn start(_: isize, _: *const *const u8) -> isize {
unsafe { android_glue::ffi::app_dummy() };
1
}
#[no_mangle]
#[inline(never)]
#[allow(non_snake_case)]
pub extern "C" fn android_main(app: *mut ()) {
android_glue::android_main2(app, move|| super::$main());
}
}
)
);
static mut g_mainthread_boxed: Option<*mut Receiver<()>> = Option::None;
fn is_app_thread_terminated() -> (bool, bool) {
if unsafe { g_mainthread_boxed.is_some() } {
let raw = unsafe { g_mainthread_boxed.unwrap() };
let br: &mut Receiver<()> = unsafe { std::mem::transmute(raw) };
let result = br.try_recv();
let terminated = if result.is_err() {
match result.err().unwrap() {
TryRecvError::Disconnected => (true, true),
TryRecvError::Empty => (false, false),
}
} else {
(true, false)
};
unsafe { g_mainthread_boxed = Option::Some(raw) };
terminated
} else {
(true, false)
}
}
pub fn get_app<'a>() -> &'a mut ffi::android_app {
unsafe { std::mem::transmute(ANDROID_APP) }
}
#[doc(hidden)]
pub fn android_main2<F>(app: *mut (), main_function: F)
where F: FnOnce(), F: 'static, F: Send
{
use std::{mem, ptr};
write_log("Entering android_main");
unsafe { ANDROID_APP = std::mem::transmute(app) };
let app: &mut ffi::android_app = unsafe { std::mem::transmute(app) };
let context = Context {
senders: Mutex::new(Vec::new()),
missed: Mutex::new(Vec::new()),
missedcnt: AtomicUsize::new(0),
missedmax: 1024,
shutdown: AtomicBool::new(false),
};
app.onAppCmd = commands_callback;
app.onInputEvent = inputs_callback;
app.userData = unsafe { std::mem::transmute(&context) };
std::old_io::stdio::set_stdout(box std::old_io::LineBufferedWriter::new(ToLogWriter));
std::old_io::stdio::set_stderr(box std::old_io::LineBufferedWriter::new(ToLogWriter));
let terminated = is_app_thread_terminated();
if terminated.1 {
write_log("abnormal exit of main application thread detected");
}
if terminated.0 {
let (mtx, mrx) = channel::<()>();
Thread::spawn(move || {
std::old_io::stdio::set_stdout(box std::old_io::LineBufferedWriter::new(ToLogWriter));
std::old_io::stdio::set_stderr(box std::old_io::LineBufferedWriter::new(ToLogWriter));
main_function();
mtx.send(()).unwrap();
});
unsafe { g_mainthread_boxed = Option::Some(std::mem::transmute(Box::new(mrx))) };
write_log("created application thread");
} else {
write_log("application thread was still running - not creating new one");
}
unsafe {
loop {
let mut events = mem::uninitialized();
let mut source = mem::uninitialized();
if context.shutdown.load(Ordering::Relaxed) {
break;
}
ffi::ALooper_pollAll(-1, ptr::null_mut(), &mut events,
&mut source);
if is_app_thread_terminated().0 {
}
if !source.is_null() {
let source: *mut ffi::android_poll_source = mem::transmute(source);
((*source).process)(ANDROID_APP, source);
}
}
}
unsafe { ANDROID_APP = 0 as *mut ffi::android_app };
}
struct ToLogWriter;
impl Writer for ToLogWriter {
fn write_all(&mut self, buf: &[u8]) -> std::old_io::IoResult<()> {
let message = CString::from_slice(buf);
let message = message.as_ptr();
let tag = b"RustAndroidGlueStdouterr";
let tag = CString::from_slice(tag);
let tag = tag.as_ptr();
unsafe { ffi::__android_log_write(3, tag, message) };
Ok(())
}
}
fn send_event(event: Event) {
let ctx = get_context();
let senders = ctx.senders.lock().ok().unwrap();
if senders.len() < 1 {
if ctx.missedcnt.load(Ordering::SeqCst) < ctx.missedmax {
let mut missed = ctx.missed.lock().unwrap();
missed.push(event);
ctx.missedcnt.fetch_add(1, Ordering::SeqCst);
}
}
for sender in senders.iter() {
sender.send(event).unwrap();
}
}
pub extern fn inputs_callback(_: *mut ffi::android_app, event: *const ffi::AInputEvent)
-> libc::int32_t
{
fn get_xy(event: *const ffi::AInputEvent) -> (i32, i32) {
let x = unsafe { ffi::AMotionEvent_getX(event, 0) };
let y = unsafe { ffi::AMotionEvent_getY(event, 0) };
(x as i32, y as i32)
}
let etype = unsafe { ffi::AInputEvent_getType(event) };
let action = unsafe { ffi::AMotionEvent_getAction(event) };
let action_code = action & ffi::AMOTION_EVENT_ACTION_MASK;
match etype {
ffi::AINPUT_EVENT_TYPE_KEY => match action_code {
ffi::AKEY_EVENT_ACTION_DOWN => { send_event(Event::EventKeyDown); },
ffi::AKEY_EVENT_ACTION_UP => send_event(Event::EventKeyUp),
_ => write_log(format!("unknown input-event-type:{} action_code:{}", etype, action_code).as_slice()),
},
ffi::AINPUT_EVENT_TYPE_MOTION => match action_code {
ffi::AMOTION_EVENT_ACTION_UP
| ffi::AMOTION_EVENT_ACTION_OUTSIDE
| ffi::AMOTION_EVENT_ACTION_CANCEL
| ffi::AMOTION_EVENT_ACTION_POINTER_UP =>
{
send_event(Event::EventUp);
},
ffi::AMOTION_EVENT_ACTION_DOWN
| ffi::AMOTION_EVENT_ACTION_POINTER_DOWN =>
{
let (x, y) = get_xy(event);
send_event(Event::EventMove(x, y));
send_event(Event::EventDown);
},
_ => {
let (x, y) = get_xy(event);
send_event(Event::EventMove(x, y));
},
},
_ => write_log(format!("unknown input-event-type:{} action_code:{}", etype, action_code).as_slice()),
}
0
}
#[doc(hidden)]
pub extern fn commands_callback(_: *mut ffi::android_app, command: libc::int32_t) {
let context = get_context();
match command {
ffi::APP_CMD_INIT_WINDOW => send_event(Event::InitWindow),
ffi::APP_CMD_SAVE_STATE => send_event(Event::SaveState),
ffi::APP_CMD_TERM_WINDOW => send_event(Event::TermWindow),
ffi::APP_CMD_GAINED_FOCUS => send_event(Event::GainedFocus),
ffi::APP_CMD_LOST_FOCUS => send_event(Event::LostFocus),
ffi::APP_CMD_INPUT_CHANGED => send_event(Event::InputChanged),
ffi::APP_CMD_WINDOW_RESIZED => send_event(Event::WindowResized),
ffi::APP_CMD_WINDOW_REDRAW_NEEDED => send_event(Event::WindowRedrawNeeded),
ffi::APP_CMD_CONTENT_RECT_CHANGED => send_event(Event::ContentRectChanged),
ffi::APP_CMD_CONFIG_CHANGED => send_event(Event::ConfigChanged),
ffi::APP_CMD_LOW_MEMORY => send_event(Event::LowMemory),
ffi::APP_CMD_START => send_event(Event::Start),
ffi::APP_CMD_RESUME => send_event(Event::Resume),
ffi::APP_CMD_PAUSE => send_event(Event::Pause),
ffi::APP_CMD_STOP => send_event(Event::Stop),
ffi::APP_CMD_DESTROY => {
send_event(Event::Destroy);
context.shutdown.store(true, Ordering::Relaxed);
},
_ => write_log(format!("unknown command {}", command).as_slice()),
}
}
fn get_context() -> &'static Context {
let context = unsafe { (*ANDROID_APP).userData };
unsafe { std::mem::transmute(context) }
}
pub fn add_sender(sender: Sender<Event>) {
get_context().senders.lock().unwrap().push(sender);
}
pub fn add_sender_missing(sender: Sender<Event>) {
let ctx = get_context();
let mut senders = ctx.senders.lock().ok().unwrap();
if senders.len() == 0 {
let mut missed = ctx.missed.lock().unwrap();
while missed.len() > 0 {
sender.send(missed.remove(0)).unwrap();
}
ctx.missedcnt.store(0, Ordering::Relaxed);
}
senders.push(sender);
}
pub unsafe fn get_native_window() -> ffi::NativeWindowType {
if ANDROID_APP.is_null() {
panic!("The application was not initialized from android_main");
}
loop {
let value = (*ANDROID_APP).window;
if !value.is_null() {
return value;
}
std::old_io::timer::sleep(std::time::Duration::milliseconds(10));
}
}
pub fn write_log(message: &str) {
let message = CString::from_slice(message.as_bytes());
let message = message.as_ptr();
let tag = b"RustAndroidGlueStdouterr";
let tag = CString::from_slice(tag);
let tag = tag.as_ptr();
unsafe { ffi::__android_log_write(3, tag, message) };
}
pub enum AssetError {
AssetMissing,
EmptyBuffer,
}
pub fn load_asset(filename: &str) -> Result<Vec<u8>, AssetError> {
struct AssetCloser {
asset: *mut ffi::Asset,
}
impl Drop for AssetCloser {
fn drop(&mut self) {
unsafe {
ffi::AAsset_close(self.asset)
};
}
}
unsafe fn get_asset_manager() -> *mut ffi::AAssetManager {
let app = &*ANDROID_APP;
let activity = &*app.activity;
activity.assetManager
}
let filename_c_str = CString::from_slice(filename.as_bytes());
let filename_c_str = filename_c_str.as_ptr();
let asset = unsafe {
ffi::AAssetManager_open(
get_asset_manager(), filename_c_str, ffi::MODE_STREAMING)
};
if asset.is_null() {
return Err(AssetError::AssetMissing);
}
let _asset_closer = AssetCloser{asset: asset};
let len = unsafe {
ffi::AAsset_getLength(asset)
};
let buff = unsafe {
ffi::AAsset_getBuffer(asset)
};
if buff.is_null() {
return Err(AssetError::EmptyBuffer);
}
let vec = unsafe {
Vec::from_raw_buf(buff as *const u8, len as usize)
};
Ok(vec)
}