#![feature(libc, set_stdio)]
extern crate libc;
use std::cell::{Cell};
use std::ffi::{CString};
use std::sync::mpsc::{Sender, Receiver, TryRecvError, channel};
use std::sync::Mutex;
use std::thread;
use std::slice;
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
use std::io::Write;
#[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,
multitouch: Cell<bool>,
}
#[derive(Clone, Copy, Debug)]
pub enum Event {
EventMotion(Motion),
EventKeyUp,
EventKeyDown,
InitWindow,
SaveState,
TermWindow,
GainedFocus,
LostFocus,
InputChanged,
WindowResized,
WindowRedrawNeeded,
ContentRectChanged,
ConfigChanged,
LowMemory,
Start,
Resume,
Pause,
Stop,
Destroy,
}
#[derive(Clone, Copy, Debug)]
pub struct Motion {
pub action: MotionAction,
pub pointer_id: i32,
pub x: f32,
pub y: f32,
}
#[derive(Clone, Copy, Debug)]
pub enum MotionAction {
Down,
Move,
Up,
Cancel,
}
#[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;
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),
multitouch: Cell::new(false),
};
app.onAppCmd = commands_callback;
app.onInputEvent = inputs_callback;
app.userData = unsafe { std::mem::transmute(&context) };
std::io::set_print(Box::new(ToLogWriter::new()));
std::io::set_panic(Box::new(ToLogWriter::new()));
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::io::set_print(Box::new(ToLogWriter::new()));
std::io::set_panic(Box::new(ToLogWriter::new()));
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 {
buffer: Vec<u8>,
}
impl ToLogWriter {
fn new() -> ToLogWriter {
ToLogWriter {
buffer: Vec::new(),
}
}
}
impl Write for ToLogWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let tag = CString::new("RustAndroidGlueStdouterr").unwrap();
let tag = tag.as_ptr();
let mut cursor_id = 0;
for i in 0 .. buf.len() {
let c = buf[i];
if c == '\n' as u8 {
self.buffer.extend(&buf[cursor_id..i + 1]);
let message = CString::new(self.buffer.clone()).unwrap();
let message = message.as_ptr();
unsafe {
ffi::__android_log_write(3, tag, message)
};
self.buffer.clear();
cursor_id = i + 1;
}
if i == buf.len() - 1 {
self.buffer.extend(&buf[cursor_id..i + 1]);
}
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
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
{
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)),
},
ffi::AINPUT_EVENT_TYPE_MOTION => {
let motion_action = match action_code {
ffi::AMOTION_EVENT_ACTION_DOWN |
ffi::AMOTION_EVENT_ACTION_POINTER_DOWN => MotionAction::Down,
ffi::AMOTION_EVENT_ACTION_UP |
ffi::AMOTION_EVENT_ACTION_POINTER_UP => MotionAction::Up,
ffi::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
ffi::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
_ => {
write_log(&format!("unknown action_code:{}", action_code));
return 0
}
};
let context = get_context();
let idx = ((action & ffi::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
>> ffi::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)
as libc::size_t;
if idx > 0 && !context.multitouch.get() {
return 0
}
let pointer_id = unsafe {
ffi::AMotionEvent_getPointerId(event, idx)
};
let x = unsafe { ffi::AMotionEvent_getX(event, idx) };
let y = unsafe { ffi::AMotionEvent_getY(event, idx) };
send_event(Event::EventMotion(Motion {
action: motion_action,
pointer_id: pointer_id,
x: x,
y: y,
}));
},
_ => write_log(&format!("unknown input-event-type:{} action_code:{}", etype, action_code)),
}
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)),
}
}
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 set_multitouch(multitouch: bool) {
get_context().multitouch.set(multitouch);
}
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;
}
thread::sleep_ms(10);
}
}
pub fn write_log(message: &str) {
let message = CString::new(message).unwrap();
let message = message.as_ptr();
let tag = CString::new("RustAndroidGlueStdouterr").unwrap();
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::new(filename).unwrap();
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 {
slice::from_raw_parts(buff as *const u8, len as usize).to_vec()
};
Ok(vec)
}