2015-02-15 17:52:27 +00:00
|
|
|
/*
|
2015-02-19 00:18:38 +00:00
|
|
|
* Apple // emulator for *nix
|
2015-02-15 17:52:27 +00:00
|
|
|
*
|
|
|
|
* This software package is subject to the GNU General Public License
|
|
|
|
* version 2 or later (your choice) as published by the Free Software
|
|
|
|
* Foundation.
|
|
|
|
*
|
|
|
|
* THERE ARE NO WARRANTIES WHATSOEVER.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2015-02-16 04:08:01 +00:00
|
|
|
#include "common.h"
|
2015-03-01 05:48:08 +00:00
|
|
|
#include "androidkeys.h"
|
2015-02-15 17:52:27 +00:00
|
|
|
|
2015-07-11 21:37:41 +00:00
|
|
|
#include <cpu-features.h>
|
2015-03-23 01:53:13 +00:00
|
|
|
#include <jni.h>
|
|
|
|
|
2015-06-28 19:49:26 +00:00
|
|
|
unsigned long android_deviceSampleRateHz = 0;
|
2015-07-05 01:14:21 +00:00
|
|
|
unsigned long android_monoBufferSubmitSizeSamples = 0;
|
|
|
|
unsigned long android_stereoBufferSubmitSizeSamples = 0;
|
2015-07-11 21:37:41 +00:00
|
|
|
bool android_armNeonEnabled = false;
|
|
|
|
bool android_x86SSSE3Enabled = false;
|
2015-06-28 19:49:26 +00:00
|
|
|
|
2015-04-02 02:59:38 +00:00
|
|
|
enum {
|
|
|
|
ANDROID_ACTION_DOWN = 0x0,
|
|
|
|
ANDROID_ACTION_UP = 0x1,
|
|
|
|
ANDROID_ACTION_MOVE = 0x2,
|
|
|
|
ANDROID_ACTION_CANCEL = 0x3,
|
|
|
|
ANDROID_ACTION_POINTER_DOWN = 0x5,
|
|
|
|
ANDROID_ACTION_POINTER_UP = 0x6,
|
|
|
|
};
|
|
|
|
|
2015-03-14 22:42:02 +00:00
|
|
|
#if TESTING
|
2015-09-07 06:43:26 +00:00
|
|
|
static void _run_tests(void) {
|
2015-03-14 22:42:02 +00:00
|
|
|
char *local_argv[] = {
|
|
|
|
"-f",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
int local_argc = 0;
|
|
|
|
for (char **p = &local_argv[0]; *p != NULL; p++) {
|
|
|
|
++local_argc;
|
|
|
|
}
|
|
|
|
#if defined(TEST_CPU)
|
2015-09-07 06:43:26 +00:00
|
|
|
// Currently this test is the only one that runs as a black screen
|
2015-03-14 22:42:02 +00:00
|
|
|
extern int test_cpu(int, char *[]);
|
|
|
|
test_cpu(local_argc, local_argv);
|
2015-09-07 06:43:26 +00:00
|
|
|
tkill(getpid(), SIGKILL); // and we're done ...
|
2015-03-14 22:42:02 +00:00
|
|
|
#elif defined(TEST_VM)
|
|
|
|
extern int test_vm(int, char *[]);
|
|
|
|
test_vm(local_argc, local_argv);
|
|
|
|
#elif defined(TEST_DISPLAY)
|
|
|
|
extern int test_display(int, char *[]);
|
|
|
|
test_display(local_argc, local_argv);
|
|
|
|
#elif defined(TEST_DISK)
|
|
|
|
extern int test_disk(int, char *[]);
|
|
|
|
test_disk(local_argc, local_argv);
|
|
|
|
#else
|
|
|
|
# error "OOPS, no tests specified"
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-04-02 02:59:38 +00:00
|
|
|
static inline int _androidTouchEvent2JoystickEvent(jint action) {
|
|
|
|
switch (action) {
|
|
|
|
case ANDROID_ACTION_DOWN:
|
|
|
|
return TOUCH_DOWN;
|
|
|
|
case ANDROID_ACTION_MOVE:
|
|
|
|
return TOUCH_MOVE;
|
|
|
|
case ANDROID_ACTION_UP:
|
|
|
|
return TOUCH_UP;
|
|
|
|
case ANDROID_ACTION_POINTER_DOWN:
|
|
|
|
return TOUCH_POINTER_DOWN;
|
|
|
|
case ANDROID_ACTION_POINTER_UP:
|
|
|
|
return TOUCH_POINTER_UP;
|
|
|
|
case ANDROID_ACTION_CANCEL:
|
|
|
|
return TOUCH_CANCEL;
|
|
|
|
default:
|
|
|
|
LOG("Unknown Android event : %d", action);
|
|
|
|
return TOUCH_CANCEL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-27 01:40:05 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// JNI functions
|
|
|
|
|
2015-06-28 19:49:26 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jobject obj, jstring j_dataDir, jint sampleRate, jint monoBufferSize, jint stereoBufferSize) {
|
2015-02-23 19:19:41 +00:00
|
|
|
const char *dataDir = (*env)->GetStringUTFChars(env, j_dataDir, 0);
|
2015-04-15 04:48:55 +00:00
|
|
|
|
|
|
|
// Android lifecycle can call onCreate() multiple times...
|
|
|
|
if (data_dir) {
|
|
|
|
LOG("IGNORING multiple calls to nativeOnCreate ...");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-07-11 21:37:41 +00:00
|
|
|
AndroidCpuFamily family = android_getCpuFamily();
|
|
|
|
uint64_t features = android_getCpuFeatures();
|
|
|
|
if (family == ANDROID_CPU_FAMILY_X86) {
|
|
|
|
if (features & ANDROID_CPU_X86_FEATURE_SSSE3) {
|
|
|
|
LOG("nANDROID_CPU_X86_FEATURE_SSSE3");
|
|
|
|
android_x86SSSE3Enabled = true;
|
|
|
|
}
|
|
|
|
if (features & ANDROID_CPU_X86_FEATURE_MOVBE) {
|
|
|
|
LOG("ANDROID_CPU_X86_FEATURE_MOVBE");
|
|
|
|
}
|
|
|
|
if (features & ANDROID_CPU_X86_FEATURE_POPCNT) {
|
|
|
|
LOG("ANDROID_CPU_X86_FEATURE_POPCNT");
|
|
|
|
}
|
|
|
|
} else if (family == ANDROID_CPU_FAMILY_ARM) {
|
|
|
|
if (features & ANDROID_CPU_ARM_FEATURE_ARMv7) {
|
|
|
|
LOG("ANDROID_CPU_ARM_FEATURE_ARMv7");
|
|
|
|
}
|
|
|
|
if (features & ANDROID_CPU_ARM_FEATURE_VFPv3) {
|
|
|
|
LOG("ANDROID_CPU_ARM_FEATURE_VFPv3");
|
|
|
|
}
|
|
|
|
if (features & ANDROID_CPU_ARM_FEATURE_NEON) {
|
|
|
|
LOG("ANDROID_CPU_ARM_FEATURE_NEON");
|
|
|
|
android_armNeonEnabled = true;
|
|
|
|
}
|
|
|
|
if (features & ANDROID_CPU_ARM_FEATURE_LDREX_STREX) {
|
|
|
|
LOG("ANDROID_CPU_ARM_FEATURE_LDREX_STREX");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-23 19:19:41 +00:00
|
|
|
data_dir = strdup(dataDir);
|
|
|
|
(*env)->ReleaseStringUTFChars(env, j_dataDir, dataDir);
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG("data_dir : %s", data_dir);
|
2015-03-12 21:45:17 +00:00
|
|
|
|
2015-06-28 19:49:26 +00:00
|
|
|
android_deviceSampleRateHz = (unsigned long)sampleRate;
|
2015-07-05 01:14:21 +00:00
|
|
|
android_monoBufferSubmitSizeSamples = (unsigned long)monoBufferSize;
|
|
|
|
android_stereoBufferSubmitSizeSamples = (unsigned long)stereoBufferSize;
|
2015-06-28 19:49:26 +00:00
|
|
|
|
2015-03-23 04:44:03 +00:00
|
|
|
#if TESTING
|
2015-09-07 06:43:26 +00:00
|
|
|
assert(cpu_thread_id == 0 && "CPU thread must not be initialized yet...");
|
2015-03-23 04:44:03 +00:00
|
|
|
_run_tests();
|
2015-09-07 06:43:26 +00:00
|
|
|
// CPU thread is started from testsuite (if needed)
|
2015-03-23 04:44:03 +00:00
|
|
|
#else
|
2015-09-06 21:03:02 +00:00
|
|
|
timing_startCPU();
|
2015-03-23 04:44:03 +00:00
|
|
|
#endif
|
2015-02-25 00:03:21 +00:00
|
|
|
}
|
2015-02-19 00:18:38 +00:00
|
|
|
|
2015-03-11 19:54:50 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeGraphicsChanged(JNIEnv *env, jobject obj, jint width, jint height) {
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG("%s", "");
|
2015-04-18 04:40:56 +00:00
|
|
|
video_backend->reshape(width, height);
|
2015-03-11 19:54:50 +00:00
|
|
|
}
|
|
|
|
|
2015-03-10 20:38:14 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeGraphicsInitialized(JNIEnv *env, jobject obj, jint width, jint height) {
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG("width:%d height:%d", width, height);
|
2015-03-12 21:45:17 +00:00
|
|
|
static bool graphicsPreviouslyInitialized = false;
|
|
|
|
if (graphicsPreviouslyInitialized) {
|
2015-06-07 04:18:27 +00:00
|
|
|
LOG("shutting down previous context");
|
2015-09-09 06:12:20 +00:00
|
|
|
video_shutdown();
|
2015-03-12 21:45:17 +00:00
|
|
|
}
|
|
|
|
graphicsPreviouslyInitialized = true;
|
2015-04-12 22:35:16 +00:00
|
|
|
|
2015-04-18 04:40:56 +00:00
|
|
|
video_backend->reshape(width, height);
|
|
|
|
video_backend->init((void *)0);
|
2015-02-15 17:52:27 +00:00
|
|
|
}
|
|
|
|
|
2015-04-15 05:02:48 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnResume(JNIEnv *env, jobject obj, jboolean isSystemResume) {
|
2015-07-28 05:36:39 +00:00
|
|
|
if (!cpu_isPaused()) {
|
2015-03-11 21:42:57 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG("%s", "");
|
2015-07-28 05:36:39 +00:00
|
|
|
if (!isSystemResume) {
|
2015-09-07 06:43:26 +00:00
|
|
|
#if TESTING
|
|
|
|
// test driver thread is managing CPU
|
|
|
|
#else
|
2015-07-05 01:17:04 +00:00
|
|
|
cpu_resume();
|
2015-09-07 06:43:26 +00:00
|
|
|
#endif
|
2015-04-15 05:02:48 +00:00
|
|
|
}
|
2015-02-15 17:52:27 +00:00
|
|
|
}
|
|
|
|
|
2015-07-28 05:36:39 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnPause(JNIEnv *env, jobject obj, jboolean isSystemPause) {
|
|
|
|
if (cpu_isPaused()) {
|
2015-03-11 21:42:57 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG("%s", "");
|
2015-07-05 01:17:04 +00:00
|
|
|
|
2015-08-22 06:33:08 +00:00
|
|
|
video_backend->animation_hideTouchMenu();
|
|
|
|
|
2015-09-07 06:43:26 +00:00
|
|
|
#if TESTING
|
|
|
|
// test driver thread is managing CPU
|
|
|
|
#else
|
2015-07-28 05:36:39 +00:00
|
|
|
if (isSystemPause) {
|
|
|
|
// going to background
|
|
|
|
cpu_pauseBackground();
|
|
|
|
} else {
|
|
|
|
// going to menu
|
|
|
|
cpu_pause();
|
|
|
|
}
|
2015-09-07 06:43:26 +00:00
|
|
|
#endif
|
2015-02-15 17:52:27 +00:00
|
|
|
}
|
|
|
|
|
2015-03-10 20:38:14 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeRender(JNIEnv *env, jobject obj) {
|
2015-06-07 04:18:27 +00:00
|
|
|
if (UNLIKELY(emulator_shutting_down)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-02-25 00:03:21 +00:00
|
|
|
#if FPS_LOG
|
|
|
|
static uint32_t prevCount = 0;
|
|
|
|
static uint32_t idleCount = 0;
|
|
|
|
|
|
|
|
idleCount++;
|
|
|
|
|
|
|
|
static struct timespec prev = { 0 };
|
|
|
|
struct timespec now;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
|
|
|
|
if (now.tv_sec != prev.tv_sec) {
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG("FPS : %u", idleCount-prevCount);
|
2015-02-25 00:03:21 +00:00
|
|
|
prevCount = idleCount;
|
|
|
|
prev = now;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-04-18 04:40:56 +00:00
|
|
|
video_backend->render();
|
2015-02-25 00:03:21 +00:00
|
|
|
}
|
|
|
|
|
2015-03-11 21:42:57 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeReboot(JNIEnv *env, jobject obj) {
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG("%s", "");
|
2015-03-11 21:42:57 +00:00
|
|
|
cpu65_reboot();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnQuit(JNIEnv *env, jobject obj) {
|
2015-09-07 06:43:26 +00:00
|
|
|
#if TESTING
|
|
|
|
// test driver thread is managing CPU
|
|
|
|
#else
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG("%s", "");
|
2015-03-11 21:42:57 +00:00
|
|
|
|
|
|
|
c_eject_6(0);
|
|
|
|
c_eject_6(1);
|
|
|
|
|
2015-06-02 05:50:44 +00:00
|
|
|
emulator_shutting_down = true;
|
|
|
|
|
2015-07-05 01:17:04 +00:00
|
|
|
cpu_resume();
|
2015-06-02 05:50:44 +00:00
|
|
|
if (pthread_join(cpu_thread_id, NULL)) {
|
|
|
|
ERRLOG("OOPS: pthread_join of CPU thread ...");
|
|
|
|
}
|
2015-09-07 06:43:26 +00:00
|
|
|
#endif
|
2015-03-11 21:42:57 +00:00
|
|
|
}
|
|
|
|
|
2015-06-06 19:23:14 +00:00
|
|
|
#define _JAVA_CRASH_NAME "/jcrash.txt"
|
|
|
|
#define _HALF_PAGE_SIZE (PAGE_SIZE>>1)
|
|
|
|
|
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnUncaughtException(JNIEnv *env, jobject obj, jstring jhome, jstring jstr) {
|
|
|
|
RELEASE_ERRLOG("Uncaught Java Exception ...");
|
|
|
|
|
2015-09-05 20:52:26 +00:00
|
|
|
// Write to /data/data/org.deadc0de.apple2ix.basic/jcrash.txt
|
2015-06-06 19:23:14 +00:00
|
|
|
const char *home = (*env)->GetStringUTFChars(env, jhome, NULL);
|
|
|
|
char *q = (char *)home;
|
|
|
|
char buf[_HALF_PAGE_SIZE] = { 0 };
|
|
|
|
const char *p0 = &buf[0];
|
|
|
|
char *p = (char *)p0;
|
|
|
|
while (*q && (p-p0 < _HALF_PAGE_SIZE-1)) {
|
|
|
|
*p++ = *q++;
|
|
|
|
}
|
|
|
|
(*env)->ReleaseStringUTFChars(env, jhome, home);
|
|
|
|
q = &_JAVA_CRASH_NAME[0];
|
|
|
|
while (*q && (p-p0 < _HALF_PAGE_SIZE-1)) {
|
|
|
|
*p++ = *q++;
|
|
|
|
}
|
|
|
|
|
|
|
|
int fd = TEMP_FAILURE_RETRY(open(buf, (O_CREAT|O_APPEND|O_WRONLY), (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)));
|
|
|
|
if (fd == -1) {
|
|
|
|
RELEASE_ERRLOG("OOPS, could not create/write to java crash file");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *str = (*env)->GetStringUTFChars(env, jstr, NULL);
|
|
|
|
jsize len = (*env)->GetStringUTFLength(env, jstr);
|
|
|
|
TEMP_FAILURE_RETRY(write(fd, str, len));
|
|
|
|
(*env)->ReleaseStringUTFChars(env, jstr, str);
|
|
|
|
|
|
|
|
TEMP_FAILURE_RETRY(fsync(fd));
|
|
|
|
TEMP_FAILURE_RETRY(close(fd));
|
|
|
|
}
|
|
|
|
|
2015-03-10 20:38:14 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyDown(JNIEnv *env, jobject obj, jint keyCode, jint metaState) {
|
2015-03-01 05:48:08 +00:00
|
|
|
android_keycode_to_emulator(keyCode, metaState, true);
|
|
|
|
}
|
|
|
|
|
2015-03-10 20:38:14 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyUp(JNIEnv *env, jobject obj, jint keyCode, jint metaState) {
|
2015-03-01 05:48:08 +00:00
|
|
|
android_keycode_to_emulator(keyCode, metaState, false);
|
|
|
|
}
|
2015-03-14 22:42:02 +00:00
|
|
|
|
2015-07-31 04:36:22 +00:00
|
|
|
jlong Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnTouch(JNIEnv *env, jobject obj, jint action, jint pointerCount, jint pointerIndex, jfloatArray xCoords, jfloatArray yCoords) {
|
2015-08-22 06:41:53 +00:00
|
|
|
//LOG(": %d/%d/%d :", action, pointerCount, pointerIndex);
|
2015-06-07 04:18:27 +00:00
|
|
|
if (UNLIKELY(emulator_shutting_down)) {
|
2015-09-09 06:12:20 +00:00
|
|
|
video_shutdown();
|
2015-07-31 04:36:22 +00:00
|
|
|
return 0x0LL;
|
2015-06-07 04:18:27 +00:00
|
|
|
}
|
2015-04-02 02:59:38 +00:00
|
|
|
|
|
|
|
jfloat *x_coords = (*env)->GetFloatArrayElements(env, xCoords, 0);
|
|
|
|
jfloat *y_coords = (*env)->GetFloatArrayElements(env, yCoords, 0);
|
|
|
|
|
|
|
|
int joyaction = _androidTouchEvent2JoystickEvent(action);
|
|
|
|
|
|
|
|
//for (unsigned int i=0; i<pointerCount; i++) {
|
|
|
|
// LOG("\t[%f,%f]", x_coords[i], y_coords[i]);
|
|
|
|
//}
|
|
|
|
|
2015-07-31 04:36:22 +00:00
|
|
|
int64_t flags = interface_onTouchEvent(joyaction, pointerCount, pointerIndex, x_coords, y_coords);
|
2015-04-02 02:59:38 +00:00
|
|
|
|
|
|
|
(*env)->ReleaseFloatArrayElements(env, xCoords, x_coords, 0);
|
|
|
|
(*env)->ReleaseFloatArrayElements(env, yCoords, y_coords, 0);
|
2015-07-31 04:36:22 +00:00
|
|
|
return flags;
|
2015-03-14 22:42:02 +00:00
|
|
|
}
|
|
|
|
|
2015-04-08 05:40:22 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeChooseDisk(JNIEnv *env, jobject obj, jstring jPath, jboolean driveA, jboolean readOnly) {
|
2015-06-06 19:23:14 +00:00
|
|
|
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
|
2015-04-08 05:40:22 +00:00
|
|
|
int drive = driveA ? 0 : 1;
|
|
|
|
int ro = readOnly ? 1 : 0;
|
2015-04-18 21:53:01 +00:00
|
|
|
|
2015-08-22 06:41:53 +00:00
|
|
|
LOG(": (%s, %s, %s)", path, driveA ? "drive A" : "drive B", readOnly ? "read only" : "read/write");
|
2015-04-08 05:40:22 +00:00
|
|
|
if (c_new_diskette_6(drive, path, ro)) {
|
|
|
|
char *gzPath = NULL;
|
|
|
|
asprintf(&gzPath, "%s.gz", path);
|
|
|
|
if (c_new_diskette_6(drive, gzPath, ro)) {
|
2015-04-18 21:53:01 +00:00
|
|
|
char *diskImageUnreadable = "Disk Image Unreadable";
|
|
|
|
unsigned int cols = strlen(diskImageUnreadable);
|
|
|
|
video_backend->animation_showMessage(diskImageUnreadable, cols, 1);
|
2015-04-08 05:40:22 +00:00
|
|
|
} else {
|
2015-04-18 21:53:01 +00:00
|
|
|
video_backend->animation_showDiskChosen(drive);
|
2015-04-08 05:40:22 +00:00
|
|
|
}
|
|
|
|
FREE(gzPath);
|
|
|
|
} else {
|
2015-04-18 21:53:01 +00:00
|
|
|
video_backend->animation_showDiskChosen(drive);
|
2015-04-08 05:40:22 +00:00
|
|
|
}
|
|
|
|
(*env)->ReleaseStringUTFChars(env, jPath, path);
|
|
|
|
}
|
|
|
|
|
2015-08-22 21:16:56 +00:00
|
|
|
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEjectDisk(JNIEnv *env, jobject obj, jboolean driveA) {
|
|
|
|
LOG("%s", "");
|
|
|
|
c_eject_6(!driveA);
|
|
|
|
}
|
|
|
|
|
2015-04-27 01:40:05 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Constructor
|
|
|
|
|
|
|
|
__attribute__((constructor(CTOR_PRIORITY_LATE)))
|
|
|
|
static void _init_jnihooks(void) {
|
2015-07-31 04:36:22 +00:00
|
|
|
// ...
|
2015-04-27 01:40:05 +00:00
|
|
|
}
|
|
|
|
|