nkeynes@1239: /** nkeynes@1239: * $Id$ nkeynes@1239: * nkeynes@1245: * Native shims for the Android front-end (implemented in Java). nkeynes@1245: * nkeynes@1245: * This is complicated by the fact that all the emulation runs in a nkeynes@1245: * separate thread. nkeynes@1239: * nkeynes@1239: * Copyright (c) 2012 Nathan Keynes. nkeynes@1239: * nkeynes@1239: * This program is free software; you can redistribute it and/or modify nkeynes@1239: * it under the terms of the GNU General Public License as published by nkeynes@1239: * the Free Software Foundation; either version 2 of the License, or nkeynes@1239: * (at your option) any later version. nkeynes@1239: * nkeynes@1239: * This program is distributed in the hope that it will be useful, nkeynes@1239: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@1239: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@1239: * GNU General Public License for more details. nkeynes@1239: */ nkeynes@1239: nkeynes@1239: #include nkeynes@1245: #include nkeynes@1239: #include nkeynes@1245: #include nkeynes@1239: #include nkeynes@1245: #include "dream.h" nkeynes@1239: #include "dreamcast.h" nkeynes@1239: #include "gui.h" nkeynes@1239: #include "config.h" nkeynes@1241: #include "lxpaths.h" nkeynes@1245: #include "tqueue.h" nkeynes@1239: #include "display.h" nkeynes@1239: #include "gdlist.h" nkeynes@1245: #include "gdrom/gdrom.h" nkeynes@1239: #include "hotkeys.h" nkeynes@1239: #include "serial.h" nkeynes@1239: #include "aica/audio.h" nkeynes@1245: #include "drivers/video_egl.h" nkeynes@1239: #include "maple/maple.h" nkeynes@1239: #include "vmu/vmulist.h" nkeynes@1239: nkeynes@1245: struct surface_info { nkeynes@1245: ANativeWindow *win; nkeynes@1245: int width, height, format; nkeynes@1245: }; nkeynes@1245: nkeynes@1275: static struct surface_info current_surface = { NULL, 0, 0, 0 }; nkeynes@1245: static const char *appHome = NULL; nkeynes@1245: nkeynes@1245: /** nkeynes@1245: * Count of running nanoseconds - used to cut back on the GUI runtime nkeynes@1245: */ nkeynes@1245: static uint32_t android_gui_nanos = 0; nkeynes@1245: static uint32_t android_gui_ticks = 0; nkeynes@1245: static struct timeval android_gui_lasttv; nkeynes@1245: nkeynes@1245: nkeynes@1245: void android_gui_start( void ) nkeynes@1239: { nkeynes@1245: /* Dreamcast starting up hook */ nkeynes@1245: } nkeynes@1245: nkeynes@1245: void android_gui_stop( void ) nkeynes@1245: { nkeynes@1245: /* Dreamcast stopping hook */ nkeynes@1245: } nkeynes@1245: nkeynes@1245: void android_gui_reset( void ) nkeynes@1245: { nkeynes@1245: /* Dreamcast reset hook */ nkeynes@1245: } nkeynes@1245: nkeynes@1245: /** nkeynes@1245: * The main emulation thread. (as opposed to the UI thread). nkeynes@1245: */ nkeynes@1245: void *android_thread_main(void *data) nkeynes@1245: { nkeynes@1245: while(1) { nkeynes@1245: tqueue_process_wait(); nkeynes@1245: } nkeynes@1245: return NULL; nkeynes@1245: } nkeynes@1245: nkeynes@1245: int android_set_surface(void *data) nkeynes@1245: { nkeynes@1245: struct surface_info *surface = (struct surface_info *)data; nkeynes@1245: video_egl_set_window(surface->win, surface->width, surface->height, surface->format); nkeynes@1277: INFO( "set_surface" ); nkeynes@1245: return 0; nkeynes@1245: } nkeynes@1245: nkeynes@1275: int android_do_pause(void *data) nkeynes@1275: { nkeynes@1275: if( dreamcast_is_running() ) { nkeynes@1275: dreamcast_stop(); nkeynes@1275: } nkeynes@1275: INFO( "Paused" ); nkeynes@1275: return 0; nkeynes@1275: } nkeynes@1275: nkeynes@1275: int android_do_resume(void *data) nkeynes@1275: { nkeynes@1275: INFO( "Resumed" ); nkeynes@1275: return 0; nkeynes@1275: } nkeynes@1275: nkeynes@1245: int android_clear_surface(void *data) nkeynes@1245: { nkeynes@1245: struct surface_info *surface = (struct surface_info *)data; nkeynes@1245: nkeynes@1277: if( dreamcast_is_running() ) { nkeynes@1277: dreamcast_stop(); /* Should already be stopped, but just in case */ nkeynes@1277: } nkeynes@1277: video_egl_clear_window(); nkeynes@1245: ANativeWindow_release(surface->win); nkeynes@1245: surface->win = NULL; nkeynes@1277: INFO( "clear_surface" ); nkeynes@1245: return 0; nkeynes@1245: } nkeynes@1245: nkeynes@1245: static pthread_t dreamcast_thread; nkeynes@1245: nkeynes@1245: void android_start_thread() nkeynes@1245: { nkeynes@1245: pthread_attr_t attr; nkeynes@1245: nkeynes@1245: pthread_attr_init(&attr); nkeynes@1245: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); nkeynes@1245: int status = pthread_create(&dreamcast_thread, &attr, android_thread_main, NULL); nkeynes@1245: if( status != 0 ) { nkeynes@1245: /* Handle errors */ nkeynes@1245: } nkeynes@1245: } nkeynes@1245: nkeynes@1245: /** tqueue callback wrapper to get the right call type for simple events */ nkeynes@1245: int android_callback_wrapper( void *fn ) nkeynes@1245: { nkeynes@1245: void (*cast_fn)(void) = fn; nkeynes@1245: cast_fn(); nkeynes@1245: } nkeynes@1245: nkeynes@1245: int android_mount_disc( void *data ) nkeynes@1245: { nkeynes@1245: char *s = (char *)data; nkeynes@1245: ERROR err; nkeynes@1245: gboolean result = gdrom_mount_image( s, &err ); /* TODO: Report error */ nkeynes@1241: return result; nkeynes@1241: } nkeynes@1241: nkeynes@1245: int android_toggle_run( void *data ) nkeynes@1245: { nkeynes@1245: if( dreamcast_is_running() ) { nkeynes@1245: dreamcast_stop(); nkeynes@1245: } else { nkeynes@1245: dreamcast_run(); nkeynes@1245: } nkeynes@1245: } nkeynes@1241: nkeynes@1245: uint32_t android_gui_run_slice( uint32_t nanosecs ) nkeynes@1241: { nkeynes@1245: android_gui_nanos += nanosecs; nkeynes@1245: if( android_gui_nanos > GUI_TICK_PERIOD ) { /* 10 ms */ nkeynes@1245: android_gui_nanos -= GUI_TICK_PERIOD; nkeynes@1245: android_gui_ticks ++; nkeynes@1245: uint32_t current_period = android_gui_ticks * GUI_TICK_PERIOD; nkeynes@1245: nkeynes@1245: // Run the event loop nkeynes@1245: tqueue_process_all(); nkeynes@1245: nkeynes@1245: struct timeval tv; nkeynes@1245: gettimeofday(&tv,NULL); nkeynes@1245: uint32_t ns = ((tv.tv_sec - android_gui_lasttv.tv_sec) * 1000000000) + nkeynes@1245: (tv.tv_usec - android_gui_lasttv.tv_usec)*1000; nkeynes@1245: if( (ns * 1.05) < current_period ) { nkeynes@1245: // We've gotten ahead - sleep for a little bit nkeynes@1245: struct timespec tv; nkeynes@1245: tv.tv_sec = 0; nkeynes@1245: tv.tv_nsec = current_period - ns; nkeynes@1245: nanosleep(&tv, &tv); nkeynes@1245: } nkeynes@1245: nkeynes@1245: #if 0 nkeynes@1245: /* Update the display every 10 ticks (ie 10 times a second) and nkeynes@1245: * save the current tv value */ nkeynes@1245: if( android_gui_ticks > 10 ) { nkeynes@1245: android_gui_ticks -= 10; nkeynes@1245: nkeynes@1245: double speed = (float)( (double)current_period * 100.0 / ns ); nkeynes@1245: android_gui_lasttv.tv_sec = tv.tv_sec; nkeynes@1245: android_gui_lasttv.tv_usec = tv.tv_usec; nkeynes@1245: main_window_set_speed( main_win, speed ); nkeynes@1245: } nkeynes@1245: #endif nkeynes@1245: } nkeynes@1245: return nanosecs; nkeynes@1245: nkeynes@1245: nkeynes@1245: } nkeynes@1245: nkeynes@1245: struct dreamcast_module android_gui_module = { "gui", NULL, nkeynes@1245: android_gui_reset, nkeynes@1245: android_gui_start, nkeynes@1245: android_gui_run_slice, nkeynes@1245: android_gui_stop, nkeynes@1245: NULL, NULL }; nkeynes@1245: nkeynes@1245: gboolean gui_error_dialog( const char *fmt, ... ) nkeynes@1245: { nkeynes@1245: return FALSE; /* TODO */ nkeynes@1245: } nkeynes@1245: nkeynes@1245: void gui_update_state() nkeynes@1245: { nkeynes@1245: /* TODO */ nkeynes@1245: } nkeynes@1245: nkeynes@1245: void gui_set_use_grab( gboolean grab ) nkeynes@1245: { nkeynes@1245: /* No implementation - mouse grab doesn't exist */ nkeynes@1245: } nkeynes@1245: nkeynes@1245: void gui_update_io_activity( io_activity_type activity, gboolean active ) nkeynes@1245: { nkeynes@1245: /* No implementation */ nkeynes@1245: } nkeynes@1245: nkeynes@1245: void gui_do_later( do_later_callback_t func ) nkeynes@1245: { nkeynes@1245: func(); /* TODO */ nkeynes@1245: } nkeynes@1245: nkeynes@1245: static void android_init( const char *appHomeDir ) nkeynes@1245: { nkeynes@1245: set_global_log_level("info"); nkeynes@1245: appHome = appHomeDir; nkeynes@1241: const char *confFile = g_strdup_printf("%s/lxdreamrc", appHome); nkeynes@1241: set_user_data_path(appHome); nkeynes@1241: lxdream_set_config_filename( confFile ); nkeynes@1239: lxdream_make_config_dir( ); nkeynes@1239: lxdream_load_config( ); nkeynes@1239: iso_init(); nkeynes@1239: gdrom_list_init(); nkeynes@1239: vmulist_init(); nkeynes@1239: dreamcast_init(1); nkeynes@1239: nkeynes@1245: dreamcast_register_module( &android_gui_module ); nkeynes@1239: audio_init_driver(NULL); nkeynes@1239: display_driver_t display_driver = get_display_driver_by_name(NULL); nkeynes@1239: display_set_driver(display_driver); nkeynes@1239: nkeynes@1239: hotkeys_init(); nkeynes@1239: serial_init(); nkeynes@1239: maple_reattach_all(); nkeynes@1253: nkeynes@1253: ERROR err; nkeynes@1253: gchar *disc_file = lxdream_get_global_config_path_value( CONFIG_GDROM ); nkeynes@1253: if( disc_file != NULL ) { nkeynes@1253: gboolean ok = gdrom_mount_image( disc_file, &err ); nkeynes@1253: g_free(disc_file); nkeynes@1253: if( !ok ) { nkeynes@1253: WARN( err.msg ); nkeynes@1253: } else { nkeynes@1253: INFO( "Mounted %s", disc_file ); nkeynes@1253: } nkeynes@1253: } nkeynes@1253: nkeynes@1239: INFO( "%s! ready...", APP_NAME ); nkeynes@1245: android_start_thread(); nkeynes@1239: } nkeynes@1239: nkeynes@1245: nkeynes@1245: /************************* Dreamcast native entry points **************************/ nkeynes@1245: nkeynes@1245: static char *getStringChars( JNIEnv *env, jstring str ); nkeynes@1245: nkeynes@1245: /** nkeynes@1245: * Main initialization entry point. We need to do all the setup that main() nkeynes@1245: * would normally do, as well as any UI specific setup. nkeynes@1245: */ nkeynes@1245: JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_init(JNIEnv * env, jclass obj, jstring homeDir ) nkeynes@1239: { nkeynes@1245: android_init( getStringChars(env, homeDir) ); nkeynes@1239: } nkeynes@1239: nkeynes@1241: JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_run(JNIEnv * env, jclass obj) nkeynes@1239: { nkeynes@1245: tqueue_post_message( android_callback_wrapper, dreamcast_run ); nkeynes@1245: } nkeynes@1245: nkeynes@1245: JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_toggleRun(JNIEnv * env, jclass obj) nkeynes@1245: { nkeynes@1245: tqueue_post_message( android_toggle_run, NULL ); nkeynes@1245: } nkeynes@1245: nkeynes@1245: JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_reset(JNIEnv * env, jclass obj) nkeynes@1245: { nkeynes@1245: tqueue_post_message( android_callback_wrapper, dreamcast_reset ); nkeynes@1239: } nkeynes@1239: nkeynes@1241: JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_stop(JNIEnv * env, jclass obj) nkeynes@1239: { nkeynes@1245: /* Need to make sure this completely shuts down before we return */ nkeynes@1245: tqueue_send_message( android_callback_wrapper, dreamcast_stop ); nkeynes@1239: } nkeynes@1239: nkeynes@1275: JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_onAppPause(JNIEnv * env, jclass obj) nkeynes@1275: { nkeynes@1275: /* Need to make sure this completely shuts down before we return */ nkeynes@1275: tqueue_send_message( android_do_pause, ¤t_surface ); nkeynes@1275: } nkeynes@1275: nkeynes@1275: JNIEXPORT void JNICALL Java_org_lxdream_Dreamcast_onAppResume(JNIEnv * env, jclass obj) nkeynes@1275: { nkeynes@1275: tqueue_post_message( android_do_resume, ¤t_surface ); nkeynes@1275: } nkeynes@1275: nkeynes@1245: JNIEXPORT jboolean JNICALL Java_org_lxdream_Dreamcast_isRunning(JNIEnv *env, jclass obj) nkeynes@1239: { nkeynes@1245: return dreamcast_is_running(); nkeynes@1239: } nkeynes@1239: nkeynes@1245: JNIEXPORT jboolean JNICALL Java_org_lxdream_Dreamcast_isRunnable(JNIEnv *env, jclass obj) nkeynes@1239: { nkeynes@1245: return dreamcast_can_run(); nkeynes@1239: } nkeynes@1239: nkeynes@1245: JNIEXPORT jboolean JNICALL Java_org_lxdream_Dreamcast_mount(JNIEnv *env, jclass obj, jstring str) nkeynes@1245: { nkeynes@1245: char *s = getStringChars(env, str); nkeynes@1245: return tqueue_send_message( android_mount_disc, s ); nkeynes@1239: } nkeynes@1239: nkeynes@1245: JNIEXPORT jboolean JNICALL Java_org_lxdream_Dreamcast_unmount(JNIEnv *env, jclass obj) nkeynes@1239: { nkeynes@1245: tqueue_post_message( android_callback_wrapper, gdrom_unmount_disc ); nkeynes@1239: } nkeynes@1239: nkeynes@1245: nkeynes@1245: /************************* LxdreamView native entry points **************************/ nkeynes@1245: nkeynes@1245: JNIEXPORT void JNICALL Java_org_lxdream_LxdreamView_setSurface(JNIEnv * env, jobject view, jobject surface, jint width, jint height) nkeynes@1239: { nkeynes@1245: current_surface.win = ANativeWindow_fromSurface(env, surface); nkeynes@1245: current_surface.width = width; nkeynes@1245: current_surface.height = height; nkeynes@1245: int fmt = ANativeWindow_getFormat(current_surface.win); nkeynes@1245: if( fmt == WINDOW_FORMAT_RGB_565 ) { nkeynes@1245: current_surface.format = COLFMT_RGB565; nkeynes@1245: } else { nkeynes@1245: current_surface.format = COLFMT_RGB888; nkeynes@1245: } nkeynes@1275: INFO( "Setting surface" ); nkeynes@1245: tqueue_post_message( android_set_surface, ¤t_surface ); nkeynes@1239: } nkeynes@1239: nkeynes@1245: JNIEXPORT void JNICALL Java_org_lxdream_LxdreamView_clearSurface(JNIEnv * env, jobject view, jobject surface) nkeynes@1239: { nkeynes@1245: /* Need to make sure this completely shuts down before we return */ nkeynes@1245: tqueue_send_message( android_clear_surface, ¤t_surface ); nkeynes@1239: } nkeynes@1239: nkeynes@1245: nkeynes@1245: /************************* JNI Support functions **************************/ nkeynes@1245: nkeynes@1245: static char *getStringChars( JNIEnv *env, jstring str ) nkeynes@1239: { nkeynes@1245: jboolean iscopy; nkeynes@1245: const char *p = (*env)->GetStringUTFChars(env, str, &iscopy); nkeynes@1245: char *result = strdup(p); nkeynes@1245: (*env)->ReleaseStringUTFChars(env,str,p); nkeynes@1245: return result; nkeynes@1239: }