mirror of
https://github.com/libretro/scummvm.git
synced 2025-05-13 17:46:22 +00:00
1441 lines
40 KiB
C++
1441 lines
40 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* $URL$
|
|
* $Id$
|
|
*
|
|
*/
|
|
|
|
#include "backends/base-backend.h"
|
|
#include "base/main.h"
|
|
#include "graphics/surface.h"
|
|
|
|
#include "backends/platform/android/video.h"
|
|
|
|
#if defined(ANDROID_BACKEND)
|
|
|
|
#include <jni.h>
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
|
|
#include <GLES/gl.h>
|
|
#include <GLES/glext.h>
|
|
#include <android/log.h>
|
|
|
|
#include "common/archive.h"
|
|
#include "common/util.h"
|
|
#include "common/rect.h"
|
|
#include "common/queue.h"
|
|
#include "common/mutex.h"
|
|
#include "common/events.h"
|
|
#include "common/config-manager.h"
|
|
|
|
#include "backends/fs/posix/posix-fs-factory.h"
|
|
#include "backends/keymapper/keymapper.h"
|
|
#include "backends/saves/default/default-saves.h"
|
|
#include "backends/timer/default/default-timer.h"
|
|
#include "backends/plugins/posix/posix-provider.h"
|
|
#include "sound/mixer_intern.h"
|
|
|
|
#include "backends/platform/android/asset-archive.h"
|
|
|
|
#undef LOG_TAG
|
|
#define LOG_TAG "ScummVM"
|
|
|
|
#if 0
|
|
#define ENTER(args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, args)
|
|
#else
|
|
#define ENTER(args...) /**/
|
|
#endif
|
|
|
|
// Fix JNIEXPORT declaration to actually do something useful
|
|
#undef JNIEXPORT
|
|
#define JNIEXPORT __attribute__ ((visibility("default")))
|
|
|
|
// This replaces the bionic libc assert message with something that
|
|
// actually prints the assertion failure before aborting.
|
|
extern "C"
|
|
void __assert(const char *file, int line, const char *expr) {
|
|
__android_log_assert(expr, LOG_TAG, "%s:%d: Assertion failure: %s",
|
|
file, line, expr);
|
|
}
|
|
|
|
static JavaVM *cached_jvm;
|
|
static jfieldID FID_Event_type;
|
|
static jfieldID FID_Event_synthetic;
|
|
static jfieldID FID_Event_kbd_keycode;
|
|
static jfieldID FID_Event_kbd_ascii;
|
|
static jfieldID FID_Event_kbd_flags;
|
|
static jfieldID FID_Event_mouse_x;
|
|
static jfieldID FID_Event_mouse_y;
|
|
static jfieldID FID_Event_mouse_relative;
|
|
static jfieldID FID_ScummVM_nativeScummVM;
|
|
static jmethodID MID_Object_wait;
|
|
|
|
JNIEnv* JNU_GetEnv() {
|
|
JNIEnv* env;
|
|
bool version_unsupported =
|
|
cached_jvm->GetEnv((void**)&env, JNI_VERSION_1_2);
|
|
assert(! version_unsupported);
|
|
return env;
|
|
}
|
|
|
|
static void JNU_ThrowByName(JNIEnv* env, const char* name, const char* msg) {
|
|
jclass cls = env->FindClass(name);
|
|
// if cls is NULL, an exception has already been thrown
|
|
if (cls != NULL)
|
|
env->ThrowNew(cls, msg);
|
|
env->DeleteLocalRef(cls);
|
|
}
|
|
|
|
// floating point. use sparingly.
|
|
template <class T>
|
|
static inline T scalef(T in, float numerator, float denominator) {
|
|
return static_cast<float>(in) * numerator / denominator;
|
|
}
|
|
|
|
static inline GLfixed xdiv(int numerator, int denominator) {
|
|
assert(numerator < (1<<16));
|
|
return (numerator << 16) / denominator;
|
|
}
|
|
|
|
#ifdef DYNAMIC_MODULES
|
|
class AndroidPluginProvider : public POSIXPluginProvider {
|
|
protected:
|
|
virtual void addCustomDirectories(Common::FSList &dirs) const;
|
|
};
|
|
#endif
|
|
|
|
|
|
#if 0
|
|
#define CHECK_GL_ERROR() checkGlError(__FILE__, __LINE__)
|
|
static const char* getGlErrStr(GLenum error) {
|
|
switch (error) {
|
|
case GL_NO_ERROR: return "GL_NO_ERROR";
|
|
case GL_INVALID_ENUM: return "GL_INVALID_ENUM";
|
|
case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
|
|
case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW";
|
|
case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW";
|
|
case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
|
|
}
|
|
|
|
static char buf[40];
|
|
snprintf(buf, sizeof(buf), "(Unknown GL error code 0x%x)", error);
|
|
return buf;
|
|
}
|
|
static void checkGlError(const char* file, int line) {
|
|
GLenum error = glGetError();
|
|
if (error != GL_NO_ERROR)
|
|
warning("%s:%d: GL error: %s", file, line, getGlErrStr(error));
|
|
}
|
|
#else
|
|
#define CHECK_GL_ERROR() do {} while (false)
|
|
#endif
|
|
|
|
class OSystem_Android : public BaseBackend, public PaletteManager {
|
|
private:
|
|
jobject _back_ptr; // back pointer to (java) peer instance
|
|
jmethodID MID_displayMessageOnOSD;
|
|
jmethodID MID_setWindowCaption;
|
|
jmethodID MID_initBackend;
|
|
jmethodID MID_audioSampleRate;
|
|
jmethodID MID_showVirtualKeyboard;
|
|
jmethodID MID_getSysArchives;
|
|
jmethodID MID_getPluginDirectories;
|
|
jmethodID MID_setupScummVMSurface;
|
|
jmethodID MID_destroyScummVMSurface;
|
|
jmethodID MID_swapBuffers;
|
|
|
|
int _screen_changeid;
|
|
int _egl_surface_width;
|
|
int _egl_surface_height;
|
|
|
|
bool _force_redraw;
|
|
|
|
// Game layer
|
|
GLESPaletteTexture* _game_texture;
|
|
int _shake_offset;
|
|
Common::Rect _focus_rect;
|
|
|
|
// Overlay layer
|
|
GLES4444Texture* _overlay_texture;
|
|
bool _show_overlay;
|
|
|
|
// Mouse layer
|
|
GLESPaletteATexture* _mouse_texture;
|
|
Common::Point _mouse_hotspot;
|
|
int _mouse_targetscale;
|
|
bool _show_mouse;
|
|
bool _use_mouse_palette;
|
|
|
|
Common::Queue<Common::Event> _event_queue;
|
|
MutexRef _event_queue_lock;
|
|
|
|
bool _timer_thread_exit;
|
|
pthread_t _timer_thread;
|
|
static void* timerThreadFunc(void* arg);
|
|
|
|
bool _enable_zoning;
|
|
bool _virtkeybd_on;
|
|
|
|
Common::SaveFileManager *_savefile;
|
|
Audio::MixerImpl *_mixer;
|
|
Common::TimerManager *_timer;
|
|
FilesystemFactory *_fsFactory;
|
|
Common::Archive *_asset_archive;
|
|
timeval _startTime;
|
|
|
|
void setupScummVMSurface();
|
|
void destroyScummVMSurface();
|
|
void setupKeymapper();
|
|
void _setCursorPalette(const byte *colors, uint start, uint num);
|
|
|
|
public:
|
|
OSystem_Android(jobject am);
|
|
virtual ~OSystem_Android();
|
|
bool initJavaHooks(JNIEnv* env, jobject self);
|
|
|
|
static OSystem_Android* fromJavaObject(JNIEnv* env, jobject obj);
|
|
virtual void initBackend();
|
|
void addPluginDirectories(Common::FSList &dirs) const;
|
|
void enableZoning(bool enable) { _enable_zoning = enable; }
|
|
void setSurfaceSize(int width, int height) {
|
|
_egl_surface_width = width;
|
|
_egl_surface_height = height;
|
|
}
|
|
|
|
virtual bool hasFeature(Feature f);
|
|
virtual void setFeatureState(Feature f, bool enable);
|
|
virtual bool getFeatureState(Feature f);
|
|
virtual const GraphicsMode *getSupportedGraphicsModes() const;
|
|
virtual int getDefaultGraphicsMode() const;
|
|
bool setGraphicsMode(const char *name);
|
|
virtual bool setGraphicsMode(int mode);
|
|
virtual int getGraphicsMode() const;
|
|
virtual void initSize(uint width, uint height,
|
|
const Graphics::PixelFormat *format);
|
|
virtual int getScreenChangeID() const { return _screen_changeid; }
|
|
virtual int16 getHeight();
|
|
virtual int16 getWidth();
|
|
|
|
virtual PaletteManager *getPaletteManager() { return this; }
|
|
protected:
|
|
// PaletteManager API
|
|
virtual void setPalette(const byte *colors, uint start, uint num);
|
|
virtual void grabPalette(byte *colors, uint start, uint num);
|
|
|
|
public:
|
|
virtual void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h);
|
|
virtual void updateScreen();
|
|
virtual Graphics::Surface *lockScreen();
|
|
virtual void unlockScreen();
|
|
virtual void setShakePos(int shakeOffset);
|
|
virtual void fillScreen(uint32 col);
|
|
virtual void setFocusRectangle(const Common::Rect& rect);
|
|
virtual void clearFocusRectangle();
|
|
|
|
virtual void showOverlay();
|
|
virtual void hideOverlay();
|
|
virtual void clearOverlay();
|
|
virtual void grabOverlay(OverlayColor *buf, int pitch);
|
|
virtual void copyRectToOverlay(const OverlayColor *buf, int pitch, int x, int y, int w, int h);
|
|
virtual int16 getOverlayHeight();
|
|
virtual int16 getOverlayWidth();
|
|
virtual Graphics::PixelFormat getOverlayFormat() const {
|
|
// RGBA 4444
|
|
Graphics::PixelFormat format;
|
|
format.bytesPerPixel = 2;
|
|
format.rLoss = 8 - 4;
|
|
format.gLoss = 8 - 4;
|
|
format.bLoss = 8 - 4;
|
|
format.aLoss = 8 - 4;
|
|
format.rShift = 3*4;
|
|
format.gShift = 2*4;
|
|
format.bShift = 1*4;
|
|
format.aShift = 0*4;
|
|
return format;
|
|
}
|
|
|
|
virtual bool showMouse(bool visible);
|
|
|
|
virtual void warpMouse(int x, int y);
|
|
virtual void setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int cursorTargetScale, const Graphics::PixelFormat *format);
|
|
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
|
virtual void disableCursorPalette(bool disable);
|
|
|
|
virtual bool pollEvent(Common::Event &event);
|
|
void pushEvent(const Common::Event& event);
|
|
virtual uint32 getMillis();
|
|
virtual void delayMillis(uint msecs);
|
|
|
|
virtual MutexRef createMutex(void);
|
|
virtual void lockMutex(MutexRef mutex);
|
|
virtual void unlockMutex(MutexRef mutex);
|
|
virtual void deleteMutex(MutexRef mutex);
|
|
|
|
virtual void quit();
|
|
|
|
virtual void setWindowCaption(const char *caption);
|
|
virtual void displayMessageOnOSD(const char *msg);
|
|
virtual void showVirtualKeyboard(bool enable);
|
|
|
|
virtual Common::SaveFileManager *getSavefileManager();
|
|
virtual Audio::Mixer *getMixer();
|
|
virtual void getTimeAndDate(TimeDate &t) const;
|
|
virtual Common::TimerManager *getTimerManager();
|
|
virtual FilesystemFactory *getFilesystemFactory();
|
|
virtual void logMessage(LogMessageType::Type type, const char *message);
|
|
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
|
|
};
|
|
|
|
OSystem_Android::OSystem_Android(jobject am)
|
|
: _back_ptr(0),
|
|
_screen_changeid(0),
|
|
_force_redraw(false),
|
|
_game_texture(NULL),
|
|
_overlay_texture(NULL),
|
|
_mouse_texture(NULL),
|
|
_use_mouse_palette(false),
|
|
_show_mouse(false),
|
|
_show_overlay(false),
|
|
_enable_zoning(false),
|
|
_savefile(0),
|
|
_mixer(0),
|
|
_timer(0),
|
|
_fsFactory(new POSIXFilesystemFactory()),
|
|
_asset_archive(new AndroidAssetArchive(am)),
|
|
_shake_offset(0),
|
|
_event_queue_lock(createMutex()) {
|
|
}
|
|
|
|
OSystem_Android::~OSystem_Android() {
|
|
ENTER("~OSystem_Android()");
|
|
delete _game_texture;
|
|
delete _overlay_texture;
|
|
delete _mouse_texture;
|
|
destroyScummVMSurface();
|
|
JNIEnv* env = JNU_GetEnv();
|
|
//env->DeleteWeakGlobalRef(_back_ptr);
|
|
env->DeleteGlobalRef(_back_ptr);
|
|
delete _savefile;
|
|
delete _mixer;
|
|
delete _timer;
|
|
delete _fsFactory;
|
|
delete _asset_archive;
|
|
deleteMutex(_event_queue_lock);
|
|
}
|
|
|
|
OSystem_Android* OSystem_Android::fromJavaObject(JNIEnv* env, jobject obj) {
|
|
jlong peer = env->GetLongField(obj, FID_ScummVM_nativeScummVM);
|
|
return (OSystem_Android*)peer;
|
|
}
|
|
|
|
bool OSystem_Android::initJavaHooks(JNIEnv* env, jobject self) {
|
|
// weak global ref to allow class to be unloaded
|
|
// ... except dalvik doesn't implement NewWeakGlobalRef (yet)
|
|
//_back_ptr = env->NewWeakGlobalRef(self);
|
|
_back_ptr = env->NewGlobalRef(self);
|
|
|
|
jclass cls = env->GetObjectClass(_back_ptr);
|
|
|
|
#define FIND_METHOD(name, signature) do { \
|
|
MID_ ## name = env->GetMethodID(cls, #name, signature); \
|
|
if (MID_ ## name == NULL) \
|
|
return false; \
|
|
} while (0)
|
|
|
|
FIND_METHOD(setWindowCaption, "(Ljava/lang/String;)V");
|
|
FIND_METHOD(displayMessageOnOSD, "(Ljava/lang/String;)V");
|
|
FIND_METHOD(initBackend, "()V");
|
|
FIND_METHOD(audioSampleRate, "()I");
|
|
FIND_METHOD(showVirtualKeyboard, "(Z)V");
|
|
FIND_METHOD(getSysArchives, "()[Ljava/lang/String;");
|
|
FIND_METHOD(getPluginDirectories, "()[Ljava/lang/String;");
|
|
FIND_METHOD(setupScummVMSurface, "()V");
|
|
FIND_METHOD(destroyScummVMSurface, "()V");
|
|
FIND_METHOD(swapBuffers, "()Z");
|
|
|
|
#undef FIND_METHOD
|
|
|
|
return true;
|
|
}
|
|
|
|
static void ScummVM_create(JNIEnv* env, jobject self, jobject am) {
|
|
OSystem_Android* cpp_obj = new OSystem_Android(am);
|
|
if (!cpp_obj->initJavaHooks(env, self))
|
|
// Exception already thrown by initJavaHooks
|
|
return;
|
|
|
|
env->SetLongField(self, FID_ScummVM_nativeScummVM, (jlong)cpp_obj);
|
|
|
|
#ifdef DYNAMIC_MODULES
|
|
PluginManager::instance().addPluginProvider(new AndroidPluginProvider());
|
|
#endif
|
|
}
|
|
|
|
static void ScummVM_nativeDestroy(JNIEnv* env, jobject self) {
|
|
OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
|
|
delete cpp_obj;
|
|
}
|
|
|
|
static void ScummVM_audioMixCallback(JNIEnv* env, jobject self,
|
|
jbyteArray jbuf) {
|
|
OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
|
|
jsize len = env->GetArrayLength(jbuf);
|
|
jbyte* buf = env->GetByteArrayElements(jbuf, NULL);
|
|
if (buf == NULL) {
|
|
warning("Unable to get Java audio byte array. Skipping");
|
|
return;
|
|
}
|
|
Audio::MixerImpl* mixer =
|
|
static_cast<Audio::MixerImpl*>(cpp_obj->getMixer());
|
|
assert(mixer);
|
|
mixer->mixCallback(reinterpret_cast<byte*>(buf), len);
|
|
env->ReleaseByteArrayElements(jbuf, buf, 0);
|
|
}
|
|
|
|
static void ScummVM_setConfManInt(JNIEnv* env, jclass cls,
|
|
jstring key_obj, jint value) {
|
|
ENTER("setConfManInt(%p, %d)", key_obj, (int)value);
|
|
const char* key = env->GetStringUTFChars(key_obj, NULL);
|
|
if (key == NULL)
|
|
return;
|
|
ConfMan.setInt(key, value);
|
|
env->ReleaseStringUTFChars(key_obj, key);
|
|
}
|
|
|
|
static void ScummVM_setConfManString(JNIEnv* env, jclass cls, jstring key_obj,
|
|
jstring value_obj) {
|
|
ENTER("setConfManStr(%p, %p)", key_obj, value_obj);
|
|
const char* key = env->GetStringUTFChars(key_obj, NULL);
|
|
if (key == NULL)
|
|
return;
|
|
const char* value = env->GetStringUTFChars(value_obj, NULL);
|
|
if (value == NULL) {
|
|
env->ReleaseStringUTFChars(key_obj, key);
|
|
return;
|
|
}
|
|
ConfMan.set(key, value);
|
|
env->ReleaseStringUTFChars(value_obj, value);
|
|
env->ReleaseStringUTFChars(key_obj, key);
|
|
}
|
|
|
|
void* OSystem_Android::timerThreadFunc(void* arg) {
|
|
OSystem_Android* system = (OSystem_Android*)arg;
|
|
DefaultTimerManager* timer = (DefaultTimerManager*)(system->_timer);
|
|
|
|
struct timespec tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_nsec = 100 * 1000 * 1000; // 100ms
|
|
|
|
while (!system->_timer_thread_exit) {
|
|
timer->handler();
|
|
nanosleep(&tv, NULL);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void OSystem_Android::initBackend() {
|
|
ENTER("initBackend()");
|
|
JNIEnv* env = JNU_GetEnv();
|
|
|
|
ConfMan.setInt("autosave_period", 0);
|
|
ConfMan.setInt("FM_medium_quality", true);
|
|
|
|
// must happen before creating TimerManager to avoid race in
|
|
// creating EventManager
|
|
setupKeymapper();
|
|
|
|
// BUG: "transient" ConfMan settings get nuked by the options
|
|
// screen. Passing the savepath in this way makes it stick
|
|
// (via ConfMan.registerDefault)
|
|
_savefile = new DefaultSaveFileManager(ConfMan.get("savepath"));
|
|
_timer = new DefaultTimerManager();
|
|
|
|
gettimeofday(&_startTime, NULL);
|
|
|
|
jint sample_rate = env->CallIntMethod(_back_ptr, MID_audioSampleRate);
|
|
if (env->ExceptionCheck()) {
|
|
warning("Error finding audio sample rate - assuming 11025HZ");
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
sample_rate = 11025;
|
|
}
|
|
_mixer = new Audio::MixerImpl(this, sample_rate);
|
|
_mixer->setReady(true);
|
|
|
|
env->CallVoidMethod(_back_ptr, MID_initBackend);
|
|
if (env->ExceptionCheck()) {
|
|
error("Error in Java initBackend");
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
}
|
|
|
|
_timer_thread_exit = false;
|
|
pthread_create(&_timer_thread, NULL, timerThreadFunc, this);
|
|
|
|
OSystem::initBackend();
|
|
|
|
setupScummVMSurface();
|
|
}
|
|
|
|
void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const {
|
|
ENTER("OSystem_Android::addPluginDirectories()");
|
|
JNIEnv* env = JNU_GetEnv();
|
|
|
|
jobjectArray array =
|
|
(jobjectArray)env->CallObjectMethod(_back_ptr, MID_getPluginDirectories);
|
|
if (env->ExceptionCheck()) {
|
|
warning("Error finding plugin directories");
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
return;
|
|
}
|
|
|
|
jsize size = env->GetArrayLength(array);
|
|
for (jsize i = 0; i < size; ++i) {
|
|
jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
|
|
if (path_obj == NULL)
|
|
continue;
|
|
const char* path = env->GetStringUTFChars(path_obj, NULL);
|
|
if (path == NULL) {
|
|
warning("Error getting string characters from plugin directory");
|
|
env->ExceptionClear();
|
|
env->DeleteLocalRef(path_obj);
|
|
continue;
|
|
}
|
|
dirs.push_back(Common::FSNode(path));
|
|
env->ReleaseStringUTFChars(path_obj, path);
|
|
env->DeleteLocalRef(path_obj);
|
|
}
|
|
}
|
|
|
|
bool OSystem_Android::hasFeature(Feature f) {
|
|
return (f == kFeatureCursorHasPalette ||
|
|
f == kFeatureVirtualKeyboard ||
|
|
f == kFeatureOverlaySupportsAlpha);
|
|
}
|
|
|
|
void OSystem_Android::setFeatureState(Feature f, bool enable) {
|
|
ENTER("setFeatureState(%d, %d)", f, enable);
|
|
switch (f) {
|
|
case kFeatureVirtualKeyboard:
|
|
_virtkeybd_on = enable;
|
|
showVirtualKeyboard(enable);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool OSystem_Android::getFeatureState(Feature f) {
|
|
switch (f) {
|
|
case kFeatureVirtualKeyboard:
|
|
return _virtkeybd_on;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const OSystem::GraphicsMode* OSystem_Android::getSupportedGraphicsModes() const {
|
|
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
|
|
{"default", "Default", 1},
|
|
{0, 0, 0},
|
|
};
|
|
return s_supportedGraphicsModes;
|
|
}
|
|
|
|
|
|
int OSystem_Android::getDefaultGraphicsMode() const {
|
|
return 1;
|
|
}
|
|
|
|
bool OSystem_Android::setGraphicsMode(const char *mode) {
|
|
ENTER("setGraphicsMode(%s)", mode);
|
|
return true;
|
|
}
|
|
|
|
bool OSystem_Android::setGraphicsMode(int mode) {
|
|
ENTER("setGraphicsMode(%d)", mode);
|
|
return true;
|
|
}
|
|
|
|
int OSystem_Android::getGraphicsMode() const {
|
|
return 1;
|
|
}
|
|
|
|
void OSystem_Android::setupScummVMSurface() {
|
|
ENTER("setupScummVMSurface");
|
|
JNIEnv* env = JNU_GetEnv();
|
|
env->CallVoidMethod(_back_ptr, MID_setupScummVMSurface);
|
|
if (env->ExceptionCheck())
|
|
return;
|
|
|
|
// EGL set up with a new surface. Initialise OpenGLES context.
|
|
|
|
GLESTexture::initGLExtensions();
|
|
|
|
// Turn off anything that looks like 3D ;)
|
|
glDisable(GL_CULL_FACE);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_LIGHTING);
|
|
glDisable(GL_FOG);
|
|
glDisable(GL_DITHER);
|
|
glShadeModel(GL_FLAT);
|
|
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
if (!_game_texture)
|
|
_game_texture = new GLESPaletteTexture();
|
|
else
|
|
_game_texture->reinitGL();
|
|
|
|
if (!_overlay_texture)
|
|
_overlay_texture = new GLES4444Texture();
|
|
else
|
|
_overlay_texture->reinitGL();
|
|
|
|
if (!_mouse_texture)
|
|
_mouse_texture = new GLESPaletteATexture();
|
|
else
|
|
_mouse_texture->reinitGL();
|
|
|
|
glViewport(0, 0, _egl_surface_width, _egl_surface_height);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glOrthof(0, _egl_surface_width, _egl_surface_height, 0, -1, 1);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
|
|
clearFocusRectangle();
|
|
CHECK_GL_ERROR();
|
|
}
|
|
|
|
void OSystem_Android::destroyScummVMSurface() {
|
|
JNIEnv* env = JNU_GetEnv();
|
|
env->CallVoidMethod(_back_ptr, MID_destroyScummVMSurface);
|
|
// Can't use OpenGLES functions after this
|
|
}
|
|
|
|
void OSystem_Android::initSize(uint width, uint height,
|
|
const Graphics::PixelFormat *format) {
|
|
ENTER("initSize(%d,%d,%p)", width, height, format);
|
|
|
|
_game_texture->allocBuffer(width, height);
|
|
|
|
// Cap at 320x200 or the ScummVM themes abort :/
|
|
GLuint overlay_width = MIN(_egl_surface_width, 320);
|
|
GLuint overlay_height = MIN(_egl_surface_height, 200);
|
|
_overlay_texture->allocBuffer(overlay_width, overlay_height);
|
|
|
|
// Don't know mouse size yet - it gets reallocated in
|
|
// setMouseCursor. We need the palette allocated before
|
|
// setMouseCursor however, so just take a guess at the desired
|
|
// size (it's small).
|
|
_mouse_texture->allocBuffer(20, 20);
|
|
}
|
|
|
|
int16 OSystem_Android::getHeight() {
|
|
return _game_texture->height();
|
|
}
|
|
|
|
int16 OSystem_Android::getWidth() {
|
|
return _game_texture->width();
|
|
}
|
|
|
|
void OSystem_Android::setPalette(const byte* colors, uint start, uint num) {
|
|
ENTER("setPalette(%p, %u, %u)", colors, start, num);
|
|
|
|
if (!_use_mouse_palette)
|
|
_setCursorPalette(colors, start, num);
|
|
|
|
byte* palette = _game_texture->palette() + start*3;
|
|
do {
|
|
for (int i = 0; i < 3; ++i)
|
|
palette[i] = colors[i];
|
|
palette += 3;
|
|
colors += 4;
|
|
} while (--num);
|
|
}
|
|
|
|
void OSystem_Android::grabPalette(byte *colors, uint start, uint num) {
|
|
ENTER("grabPalette(%p, %u, %u)", colors, start, num);
|
|
const byte* palette = _game_texture->palette_const() + start*3;
|
|
do {
|
|
for (int i = 0; i < 3; ++i)
|
|
colors[i] = palette[i];
|
|
colors[3] = 0xff; // alpha
|
|
|
|
palette += 3;
|
|
colors += 4;
|
|
} while (--num);
|
|
}
|
|
|
|
void OSystem_Android::copyRectToScreen(const byte *buf, int pitch,
|
|
int x, int y, int w, int h) {
|
|
ENTER("copyRectToScreen(%p, %d, %d, %d, %d, %d)",
|
|
buf, pitch, x, y, w, h);
|
|
|
|
_game_texture->updateBuffer(x, y, w, h, buf, pitch);
|
|
}
|
|
|
|
void OSystem_Android::updateScreen() {
|
|
//ENTER("updateScreen()");
|
|
|
|
if (!_force_redraw &&
|
|
!_game_texture->dirty() &&
|
|
!_overlay_texture->dirty() &&
|
|
!_mouse_texture->dirty())
|
|
return;
|
|
|
|
_force_redraw = false;
|
|
|
|
glPushMatrix();
|
|
|
|
if (_shake_offset != 0 ||
|
|
(!_focus_rect.isEmpty() &&
|
|
!Common::Rect(_game_texture->width(),
|
|
_game_texture->height()).contains(_focus_rect))) {
|
|
// These are the only cases where _game_texture doesn't
|
|
// cover the entire screen.
|
|
glClearColorx(0, 0, 0, 1 << 16);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
// Move everything up by _shake_offset (game) pixels
|
|
glTranslatex(0, -_shake_offset << 16, 0);
|
|
}
|
|
|
|
if (_focus_rect.isEmpty()) {
|
|
_game_texture->drawTexture(0, 0,
|
|
_egl_surface_width, _egl_surface_height);
|
|
} else {
|
|
glPushMatrix();
|
|
glScalex(xdiv(_egl_surface_width, _focus_rect.width()),
|
|
xdiv(_egl_surface_height, _focus_rect.height()),
|
|
1 << 16);
|
|
glTranslatex(-_focus_rect.left << 16, -_focus_rect.top << 16, 0);
|
|
glScalex(xdiv(_game_texture->width(), _egl_surface_width),
|
|
xdiv(_game_texture->height(), _egl_surface_height),
|
|
1 << 16);
|
|
_game_texture->drawTexture(0, 0,
|
|
_egl_surface_width, _egl_surface_height);
|
|
glPopMatrix();
|
|
}
|
|
|
|
CHECK_GL_ERROR();
|
|
|
|
if (_show_overlay) {
|
|
_overlay_texture->drawTexture(0, 0,
|
|
_egl_surface_width,
|
|
_egl_surface_height);
|
|
CHECK_GL_ERROR();
|
|
}
|
|
|
|
if (_show_mouse) {
|
|
glPushMatrix();
|
|
|
|
glTranslatex(-_mouse_hotspot.x << 16,
|
|
-_mouse_hotspot.y << 16,
|
|
0);
|
|
|
|
// Scale up ScummVM -> OpenGL (pixel) coordinates
|
|
int texwidth, texheight;
|
|
if (_show_overlay) {
|
|
texwidth = getOverlayWidth();
|
|
texheight = getOverlayHeight();
|
|
} else {
|
|
texwidth = getWidth();
|
|
texheight = getHeight();
|
|
}
|
|
glScalex(xdiv(_egl_surface_width, texwidth),
|
|
xdiv(_egl_surface_height, texheight),
|
|
1 << 16);
|
|
|
|
// Note the extra half texel to position the mouse in
|
|
// the middle of the x,y square:
|
|
const Common::Point& mouse = getEventManager()->getMousePos();
|
|
glTranslatex((mouse.x << 16) | 1 << 15,
|
|
(mouse.y << 16) | 1 << 15, 0);
|
|
|
|
// Mouse targetscale just seems to make the cursor way
|
|
// too big :/
|
|
//glScalex(_mouse_targetscale << 16, _mouse_targetscale << 16,
|
|
// 1 << 16);
|
|
|
|
_mouse_texture->drawTexture();
|
|
|
|
glPopMatrix();
|
|
}
|
|
|
|
glPopMatrix();
|
|
|
|
CHECK_GL_ERROR();
|
|
|
|
JNIEnv* env = JNU_GetEnv();
|
|
if (!env->CallBooleanMethod(_back_ptr, MID_swapBuffers)) {
|
|
// Context lost -> need to reinit GL
|
|
destroyScummVMSurface();
|
|
setupScummVMSurface();
|
|
}
|
|
}
|
|
|
|
Graphics::Surface *OSystem_Android::lockScreen() {
|
|
ENTER("lockScreen()");
|
|
Graphics::Surface* surface = _game_texture->surface();
|
|
assert(surface->pixels);
|
|
return surface;
|
|
}
|
|
|
|
void OSystem_Android::unlockScreen() {
|
|
ENTER("unlockScreen()");
|
|
assert(_game_texture->dirty());
|
|
}
|
|
|
|
void OSystem_Android::setShakePos(int shake_offset) {
|
|
ENTER("setShakePos(%d)", shake_offset);
|
|
if (_shake_offset != shake_offset) {
|
|
_shake_offset = shake_offset;
|
|
_force_redraw = true;
|
|
}
|
|
}
|
|
|
|
void OSystem_Android::fillScreen(uint32 col) {
|
|
ENTER("fillScreen(%u)", col);
|
|
assert(col < 256);
|
|
_game_texture->fillBuffer(col);
|
|
}
|
|
|
|
void OSystem_Android::setFocusRectangle(const Common::Rect& rect) {
|
|
ENTER("setFocusRectangle(%d,%d,%d,%d)",
|
|
rect.left, rect.top, rect.right, rect.bottom);
|
|
if (_enable_zoning) {
|
|
_focus_rect = rect;
|
|
_force_redraw = true;
|
|
}
|
|
}
|
|
|
|
void OSystem_Android::clearFocusRectangle() {
|
|
ENTER("clearFocusRectangle()");
|
|
if (_enable_zoning) {
|
|
_focus_rect = Common::Rect();
|
|
_force_redraw = true;
|
|
}
|
|
}
|
|
|
|
void OSystem_Android::showOverlay() {
|
|
ENTER("showOverlay()");
|
|
_show_overlay = true;
|
|
_force_redraw = true;
|
|
}
|
|
|
|
void OSystem_Android::hideOverlay() {
|
|
ENTER("hideOverlay()");
|
|
_show_overlay = false;
|
|
_force_redraw = true;
|
|
}
|
|
|
|
void OSystem_Android::clearOverlay() {
|
|
ENTER("clearOverlay()");
|
|
_overlay_texture->fillBuffer(0);
|
|
|
|
// Shouldn't need this, but works around a 'blank screen' bug on Nexus1
|
|
updateScreen();
|
|
}
|
|
|
|
void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) {
|
|
ENTER("grabOverlay(%p, %d)", buf, pitch);
|
|
// We support overlay alpha blending, so the pixel data here
|
|
// shouldn't actually be used. Let's fill it with zeros, I'm sure
|
|
// it will be fine...
|
|
const Graphics::Surface* surface = _overlay_texture->surface_const();
|
|
assert(surface->bytesPerPixel == sizeof(buf[0]));
|
|
int h = surface->h;
|
|
do {
|
|
memset(buf, 0, surface->w * sizeof(buf[0]));
|
|
buf += pitch; // This 'pitch' is pixels not bytes
|
|
} while (--h);
|
|
}
|
|
|
|
void OSystem_Android::copyRectToOverlay(const OverlayColor *buf, int pitch,
|
|
int x, int y, int w, int h) {
|
|
ENTER("copyRectToOverlay(%p, %d, %d, %d, %d, %d)",
|
|
buf, pitch, x, y, w, h);
|
|
const Graphics::Surface* surface = _overlay_texture->surface_const();
|
|
assert(surface->bytesPerPixel == sizeof(buf[0]));
|
|
|
|
// This 'pitch' is pixels not bytes
|
|
_overlay_texture->updateBuffer(x, y, w, h, buf, pitch * sizeof(buf[0]));
|
|
|
|
// Shouldn't need this, but works around a 'blank screen' bug on Nexus1?
|
|
updateScreen();
|
|
}
|
|
|
|
int16 OSystem_Android::getOverlayHeight() {
|
|
return _overlay_texture->height();
|
|
}
|
|
|
|
int16 OSystem_Android::getOverlayWidth() {
|
|
return _overlay_texture->width();
|
|
}
|
|
|
|
bool OSystem_Android::showMouse(bool visible) {
|
|
ENTER("showMouse(%d)", visible);
|
|
_show_mouse = visible;
|
|
return true;
|
|
}
|
|
|
|
void OSystem_Android::warpMouse(int x, int y) {
|
|
ENTER("warpMouse(%d, %d)", x, y);
|
|
// We use only the eventmanager's idea of the current mouse
|
|
// position, so there is nothing extra to do here.
|
|
}
|
|
|
|
void OSystem_Android::setMouseCursor(const byte *buf, uint w, uint h,
|
|
int hotspotX, int hotspotY,
|
|
uint32 keycolor, int cursorTargetScale,
|
|
const Graphics::PixelFormat *format) {
|
|
ENTER("setMouseCursor(%p, %u, %u, %d, %d, %d, %d, %p)",
|
|
buf, w, h, hotspotX, hotspotY, (int)keycolor, cursorTargetScale,
|
|
format);
|
|
|
|
assert(keycolor < 256);
|
|
|
|
_mouse_texture->allocBuffer(w, h);
|
|
|
|
// Update palette alpha based on keycolor
|
|
byte* palette = _mouse_texture->palette();
|
|
int i = 256;
|
|
do {
|
|
palette[3] = 0xff;
|
|
palette += 4;
|
|
} while (--i);
|
|
palette = _mouse_texture->palette();
|
|
palette[keycolor*4 + 3] = 0x00;
|
|
_mouse_texture->updateBuffer(0, 0, w, h, buf, w);
|
|
|
|
_mouse_hotspot = Common::Point(hotspotX, hotspotY);
|
|
_mouse_targetscale = cursorTargetScale;
|
|
}
|
|
|
|
void OSystem_Android::_setCursorPalette(const byte *colors,
|
|
uint start, uint num) {
|
|
byte* palette = _mouse_texture->palette() + start*4;
|
|
do {
|
|
for (int i = 0; i < 3; ++i)
|
|
palette[i] = colors[i];
|
|
// Leave alpha untouched to preserve keycolor
|
|
|
|
palette += 4;
|
|
colors += 4;
|
|
} while (--num);
|
|
}
|
|
|
|
void OSystem_Android::setCursorPalette(const byte *colors,
|
|
uint start, uint num) {
|
|
ENTER("setCursorPalette(%p, %u, %u)", colors, start, num);
|
|
_setCursorPalette(colors, start, num);
|
|
_use_mouse_palette = true;
|
|
}
|
|
|
|
void OSystem_Android::disableCursorPalette(bool disable) {
|
|
ENTER("disableCursorPalette(%d)", disable);
|
|
_use_mouse_palette = !disable;
|
|
}
|
|
|
|
void OSystem_Android::setupKeymapper() {
|
|
#ifdef ENABLE_KEYMAPPER
|
|
using namespace Common;
|
|
|
|
Keymapper *mapper = getEventManager()->getKeymapper();
|
|
|
|
HardwareKeySet *keySet = new HardwareKeySet();
|
|
keySet->addHardwareKey(
|
|
new HardwareKey("n", KeyState(KEYCODE_n), "n (vk)",
|
|
kTriggerLeftKeyType,
|
|
kVirtualKeyboardActionType));
|
|
mapper->registerHardwareKeySet(keySet);
|
|
|
|
Keymap *globalMap = new Keymap("global");
|
|
Action *act;
|
|
|
|
act = new Action(globalMap, "VIRT", "Display keyboard",
|
|
kVirtualKeyboardActionType);
|
|
act->addKeyEvent(KeyState(KEYCODE_F7, ASCII_F7, 0));
|
|
|
|
mapper->addGlobalKeymap(globalMap);
|
|
|
|
mapper->pushKeymap("global");
|
|
#endif
|
|
}
|
|
|
|
bool OSystem_Android::pollEvent(Common::Event &event) {
|
|
//ENTER("pollEvent()");
|
|
lockMutex(_event_queue_lock);
|
|
if (_event_queue.empty()) {
|
|
unlockMutex(_event_queue_lock);
|
|
return false;
|
|
}
|
|
event = _event_queue.pop();
|
|
unlockMutex(_event_queue_lock);
|
|
|
|
switch (event.type) {
|
|
case Common::EVENT_MOUSEMOVE:
|
|
// TODO: only dirty/redraw move bounds
|
|
_force_redraw = true;
|
|
// fallthrough
|
|
case Common::EVENT_LBUTTONDOWN:
|
|
case Common::EVENT_LBUTTONUP:
|
|
case Common::EVENT_RBUTTONDOWN:
|
|
case Common::EVENT_RBUTTONUP:
|
|
case Common::EVENT_WHEELUP:
|
|
case Common::EVENT_WHEELDOWN:
|
|
case Common::EVENT_MBUTTONDOWN:
|
|
case Common::EVENT_MBUTTONUP: {
|
|
if (event.kbd.flags == 1) { // relative mouse hack
|
|
// Relative (trackball) mouse hack.
|
|
const Common::Point& mouse_pos =
|
|
getEventManager()->getMousePos();
|
|
event.mouse.x += mouse_pos.x;
|
|
event.mouse.y += mouse_pos.y;
|
|
event.mouse.x = CLIP(event.mouse.x, (int16)0, _show_overlay ?
|
|
getOverlayWidth() : getWidth());
|
|
event.mouse.y = CLIP(event.mouse.y, (int16)0, _show_overlay ?
|
|
getOverlayHeight() : getHeight());
|
|
} else {
|
|
// Touchscreen events need to be converted
|
|
// from device to game coords first.
|
|
const GLESTexture* tex = _show_overlay
|
|
? static_cast<GLESTexture*>(_overlay_texture)
|
|
: static_cast<GLESTexture*>(_game_texture);
|
|
event.mouse.x = scalef(event.mouse.x, tex->width(),
|
|
_egl_surface_width);
|
|
event.mouse.y = scalef(event.mouse.y, tex->height(),
|
|
_egl_surface_height);
|
|
event.mouse.x -= _shake_offset;
|
|
}
|
|
break;
|
|
}
|
|
case Common::EVENT_SCREEN_CHANGED:
|
|
debug("EVENT_SCREEN_CHANGED");
|
|
_screen_changeid++;
|
|
destroyScummVMSurface();
|
|
setupScummVMSurface();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void OSystem_Android::pushEvent(const Common::Event& event) {
|
|
lockMutex(_event_queue_lock);
|
|
|
|
// Try to combine multiple queued mouse move events
|
|
if (event.type == Common::EVENT_MOUSEMOVE &&
|
|
!_event_queue.empty() &&
|
|
_event_queue.back().type == Common::EVENT_MOUSEMOVE) {
|
|
Common::Event tail = _event_queue.back();
|
|
if (event.kbd.flags) {
|
|
// relative movement hack
|
|
tail.mouse.x += event.mouse.x;
|
|
tail.mouse.y += event.mouse.y;
|
|
} else {
|
|
// absolute position
|
|
tail.kbd.flags = 0; // clear relative flag
|
|
tail.mouse.x = event.mouse.x;
|
|
tail.mouse.y = event.mouse.y;
|
|
}
|
|
}
|
|
else
|
|
_event_queue.push(event);
|
|
|
|
unlockMutex(_event_queue_lock);
|
|
}
|
|
|
|
static void ScummVM_pushEvent(JNIEnv* env, jobject self, jobject java_event) {
|
|
OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
|
|
|
|
Common::Event event;
|
|
event.type = (Common::EventType)env->GetIntField(java_event,
|
|
FID_Event_type);
|
|
event.synthetic =
|
|
env->GetBooleanField(java_event, FID_Event_synthetic);
|
|
|
|
switch (event.type) {
|
|
case Common::EVENT_KEYDOWN:
|
|
case Common::EVENT_KEYUP:
|
|
event.kbd.keycode = (Common::KeyCode)env->GetIntField(
|
|
java_event, FID_Event_kbd_keycode);
|
|
event.kbd.ascii = static_cast<int>(env->GetIntField(
|
|
java_event, FID_Event_kbd_ascii));
|
|
event.kbd.flags = static_cast<int>(env->GetIntField(
|
|
java_event, FID_Event_kbd_flags));
|
|
break;
|
|
case Common::EVENT_MOUSEMOVE:
|
|
case Common::EVENT_LBUTTONDOWN:
|
|
case Common::EVENT_LBUTTONUP:
|
|
case Common::EVENT_RBUTTONDOWN:
|
|
case Common::EVENT_RBUTTONUP:
|
|
case Common::EVENT_WHEELUP:
|
|
case Common::EVENT_WHEELDOWN:
|
|
case Common::EVENT_MBUTTONDOWN:
|
|
case Common::EVENT_MBUTTONUP:
|
|
event.mouse.x =
|
|
env->GetIntField(java_event, FID_Event_mouse_x);
|
|
event.mouse.y =
|
|
env->GetIntField(java_event, FID_Event_mouse_y);
|
|
// This is a terrible hack. We stash "relativeness"
|
|
// in the kbd.flags field until pollEvent() can work
|
|
// it out.
|
|
event.kbd.flags = env->GetBooleanField(
|
|
java_event, FID_Event_mouse_relative) ? 1 : 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cpp_obj->pushEvent(event);
|
|
}
|
|
|
|
uint32 OSystem_Android::getMillis() {
|
|
timeval curTime;
|
|
gettimeofday(&curTime, NULL);
|
|
return (uint32)(((curTime.tv_sec - _startTime.tv_sec) * 1000) + \
|
|
((curTime.tv_usec - _startTime.tv_usec) / 1000));
|
|
}
|
|
|
|
void OSystem_Android::delayMillis(uint msecs) {
|
|
usleep(msecs * 1000);
|
|
}
|
|
|
|
OSystem::MutexRef OSystem_Android::createMutex() {
|
|
pthread_mutexattr_t attr;
|
|
pthread_mutexattr_init(&attr);
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
|
|
|
pthread_mutex_t *mutex = new pthread_mutex_t;
|
|
if (pthread_mutex_init(mutex, &attr) != 0) {
|
|
warning("pthread_mutex_init() failed");
|
|
delete mutex;
|
|
return NULL;
|
|
}
|
|
return (MutexRef)mutex;
|
|
}
|
|
|
|
void OSystem_Android::lockMutex(MutexRef mutex) {
|
|
if (pthread_mutex_lock((pthread_mutex_t*)mutex) != 0)
|
|
warning("pthread_mutex_lock() failed");
|
|
}
|
|
|
|
void OSystem_Android::unlockMutex(MutexRef mutex) {
|
|
if (pthread_mutex_unlock((pthread_mutex_t*)mutex) != 0)
|
|
warning("pthread_mutex_unlock() failed");
|
|
}
|
|
|
|
void OSystem_Android::deleteMutex(MutexRef mutex) {
|
|
pthread_mutex_t* m = (pthread_mutex_t*)mutex;
|
|
if (pthread_mutex_destroy(m) != 0)
|
|
warning("pthread_mutex_destroy() failed");
|
|
else
|
|
delete m;
|
|
}
|
|
|
|
void OSystem_Android::quit() {
|
|
ENTER("quit()");
|
|
|
|
_timer_thread_exit = true;
|
|
pthread_join(_timer_thread, NULL);
|
|
}
|
|
|
|
void OSystem_Android::setWindowCaption(const char *caption) {
|
|
ENTER("setWindowCaption(%s)", caption);
|
|
JNIEnv* env = JNU_GetEnv();
|
|
jstring java_caption = env->NewStringUTF(caption);
|
|
env->CallVoidMethod(_back_ptr, MID_setWindowCaption, java_caption);
|
|
if (env->ExceptionCheck()) {
|
|
warning("Failed to set window caption");
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
}
|
|
env->DeleteLocalRef(java_caption);
|
|
}
|
|
|
|
void OSystem_Android::displayMessageOnOSD(const char *msg) {
|
|
ENTER("displayMessageOnOSD(%s)", msg);
|
|
JNIEnv* env = JNU_GetEnv();
|
|
jstring java_msg = env->NewStringUTF(msg);
|
|
env->CallVoidMethod(_back_ptr, MID_displayMessageOnOSD, java_msg);
|
|
if (env->ExceptionCheck()) {
|
|
warning("Failed to display OSD message");
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
}
|
|
env->DeleteLocalRef(java_msg);
|
|
}
|
|
|
|
void OSystem_Android::showVirtualKeyboard(bool enable) {
|
|
ENTER("showVirtualKeyboard(%d)", enable);
|
|
JNIEnv* env = JNU_GetEnv();
|
|
env->CallVoidMethod(_back_ptr, MID_showVirtualKeyboard, enable);
|
|
if (env->ExceptionCheck()) {
|
|
error("Error trying to show virtual keyboard");
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
}
|
|
}
|
|
|
|
Common::SaveFileManager *OSystem_Android::getSavefileManager() {
|
|
assert(_savefile);
|
|
return _savefile;
|
|
}
|
|
|
|
Audio::Mixer *OSystem_Android::getMixer() {
|
|
assert(_mixer);
|
|
return _mixer;
|
|
}
|
|
|
|
Common::TimerManager *OSystem_Android::getTimerManager() {
|
|
assert(_timer);
|
|
return _timer;
|
|
}
|
|
|
|
void OSystem_Android::getTimeAndDate(TimeDate &td) const {
|
|
struct tm tm;
|
|
const time_t curTime = time(NULL);
|
|
localtime_r(&curTime, &tm);
|
|
td.tm_sec = tm.tm_sec;
|
|
td.tm_min = tm.tm_min;
|
|
td.tm_hour = tm.tm_hour;
|
|
td.tm_mday = tm.tm_mday;
|
|
td.tm_mon = tm.tm_mon;
|
|
td.tm_year = tm.tm_year;
|
|
}
|
|
|
|
FilesystemFactory *OSystem_Android::getFilesystemFactory() {
|
|
return _fsFactory;
|
|
}
|
|
|
|
void OSystem_Android::addSysArchivesToSearchSet(Common::SearchSet &s,
|
|
int priority) {
|
|
s.add("ASSET", _asset_archive, priority, false);
|
|
|
|
JNIEnv* env = JNU_GetEnv();
|
|
|
|
jobjectArray array =
|
|
(jobjectArray)env->CallObjectMethod(_back_ptr, MID_getSysArchives);
|
|
if (env->ExceptionCheck()) {
|
|
warning("Error finding system archive path");
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
return;
|
|
}
|
|
|
|
jsize size = env->GetArrayLength(array);
|
|
for (jsize i = 0; i < size; ++i) {
|
|
jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
|
|
const char* path = env->GetStringUTFChars(path_obj, NULL);
|
|
if (path != NULL) {
|
|
s.addDirectory(path, path, priority);
|
|
env->ReleaseStringUTFChars(path_obj, path);
|
|
}
|
|
env->DeleteLocalRef(path_obj);
|
|
}
|
|
}
|
|
|
|
void OSystem_Android::logMessage(LogMessageType::Type type, const char *message) {
|
|
switch (type) {
|
|
case LogMessageType::kDebug:
|
|
BaseBackend::logMessage(type, message);
|
|
break;
|
|
|
|
case LogMessageType::kWarning:
|
|
__android_log_write(ANDROID_LOG_WARN, "ScummVM", message);
|
|
break;
|
|
|
|
case LogMessageType::kError:
|
|
// FIXME: From the name it looks like this will also quit the program.
|
|
// This shouldn't do that though.
|
|
__android_log_assert("Fatal error", "ScummVM", "%s", message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static jint ScummVM_scummVMMain(JNIEnv* env, jobject self, jobjectArray args) {
|
|
OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
|
|
|
|
const int MAX_NARGS = 32;
|
|
int res = -1;
|
|
|
|
int argc = env->GetArrayLength(args);
|
|
if (argc > MAX_NARGS) {
|
|
JNU_ThrowByName(env, "java/lang/IllegalArgumentException",
|
|
"too many arguments");
|
|
return 0;
|
|
}
|
|
|
|
char* argv[MAX_NARGS];
|
|
int nargs; // note use in cleanup loop below
|
|
for (nargs = 0; nargs < argc; ++nargs) {
|
|
jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
|
|
if (arg == NULL) {
|
|
argv[nargs] = NULL;
|
|
} else {
|
|
const char* cstr = env->GetStringUTFChars(arg, NULL);
|
|
argv[nargs] = const_cast<char*>(cstr);
|
|
if (cstr == NULL)
|
|
goto cleanup; // exception already thrown
|
|
}
|
|
env->DeleteLocalRef(arg);
|
|
}
|
|
|
|
g_system = cpp_obj;
|
|
assert(g_system);
|
|
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,
|
|
"Entering scummvm_main with %d args", argc);
|
|
res = scummvm_main(argc, argv);
|
|
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Exiting scummvm_main");
|
|
g_system->quit();
|
|
|
|
cleanup:
|
|
nargs--;
|
|
for (int i = 0; i < nargs; ++i) {
|
|
if (argv[i] == NULL)
|
|
continue;
|
|
jstring arg = (jstring)env->GetObjectArrayElement(args, nargs);
|
|
if (arg == NULL)
|
|
// Exception already thrown
|
|
return res;
|
|
env->ReleaseStringUTFChars(arg, argv[i]);
|
|
env->DeleteLocalRef(arg);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
#ifdef DYNAMIC_MODULES
|
|
void AndroidPluginProvider::addCustomDirectories(Common::FSList &dirs) const {
|
|
OSystem_Android* g_system_android = (OSystem_Android*)g_system;
|
|
g_system_android->addPluginDirectories(dirs);
|
|
}
|
|
#endif
|
|
|
|
static void ScummVM_enableZoning(JNIEnv* env, jobject self, jboolean enable) {
|
|
OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
|
|
cpp_obj->enableZoning(enable);
|
|
}
|
|
|
|
static void ScummVM_setSurfaceSize(JNIEnv* env, jobject self,
|
|
jint width, jint height) {
|
|
OSystem_Android* cpp_obj = OSystem_Android::fromJavaObject(env, self);
|
|
cpp_obj->setSurfaceSize(width, height);
|
|
}
|
|
|
|
const static JNINativeMethod gMethods[] = {
|
|
{ "create", "(Landroid/content/res/AssetManager;)V",
|
|
(void*)ScummVM_create },
|
|
{ "nativeDestroy", "()V", (void*)ScummVM_nativeDestroy },
|
|
{ "scummVMMain", "([Ljava/lang/String;)I",
|
|
(void*)ScummVM_scummVMMain },
|
|
{ "pushEvent", "(Lorg/inodes/gus/scummvm/Event;)V",
|
|
(void*)ScummVM_pushEvent },
|
|
{ "audioMixCallback", "([B)V",
|
|
(void*)ScummVM_audioMixCallback },
|
|
{ "setConfMan", "(Ljava/lang/String;I)V",
|
|
(void*)ScummVM_setConfManInt },
|
|
{ "setConfMan", "(Ljava/lang/String;Ljava/lang/String;)V",
|
|
(void*)ScummVM_setConfManString },
|
|
{ "enableZoning", "(Z)V",
|
|
(void*)ScummVM_enableZoning },
|
|
{ "setSurfaceSize", "(II)V",
|
|
(void*)ScummVM_setSurfaceSize },
|
|
};
|
|
|
|
JNIEXPORT jint JNICALL
|
|
JNI_OnLoad(JavaVM* jvm, void* reserved) {
|
|
cached_jvm = jvm;
|
|
|
|
JNIEnv* env;
|
|
if (jvm->GetEnv((void**)&env, JNI_VERSION_1_2))
|
|
return JNI_ERR;
|
|
|
|
jclass cls = env->FindClass("org/inodes/gus/scummvm/ScummVM");
|
|
if (cls == NULL)
|
|
return JNI_ERR;
|
|
if (env->RegisterNatives(cls, gMethods, ARRAYSIZE(gMethods)) < 0)
|
|
return JNI_ERR;
|
|
|
|
FID_ScummVM_nativeScummVM = env->GetFieldID(cls, "nativeScummVM", "J");
|
|
if (FID_ScummVM_nativeScummVM == NULL)
|
|
return JNI_ERR;
|
|
|
|
jclass event = env->FindClass("org/inodes/gus/scummvm/Event");
|
|
if (event == NULL)
|
|
return JNI_ERR;
|
|
FID_Event_type = env->GetFieldID(event, "type", "I");
|
|
if (FID_Event_type == NULL)
|
|
return JNI_ERR;
|
|
FID_Event_synthetic = env->GetFieldID(event, "synthetic", "Z");
|
|
if (FID_Event_synthetic == NULL)
|
|
return JNI_ERR;
|
|
FID_Event_kbd_keycode = env->GetFieldID(event, "kbd_keycode", "I");
|
|
if (FID_Event_kbd_keycode == NULL)
|
|
return JNI_ERR;
|
|
FID_Event_kbd_ascii = env->GetFieldID(event, "kbd_ascii", "I");
|
|
if (FID_Event_kbd_ascii == NULL)
|
|
return JNI_ERR;
|
|
FID_Event_kbd_flags = env->GetFieldID(event, "kbd_flags", "I");
|
|
if (FID_Event_kbd_flags == NULL)
|
|
return JNI_ERR;
|
|
FID_Event_mouse_x = env->GetFieldID(event, "mouse_x", "I");
|
|
if (FID_Event_mouse_x == NULL)
|
|
return JNI_ERR;
|
|
FID_Event_mouse_y = env->GetFieldID(event, "mouse_y", "I");
|
|
if (FID_Event_mouse_y == NULL)
|
|
return JNI_ERR;
|
|
FID_Event_mouse_relative = env->GetFieldID(event, "mouse_relative", "Z");
|
|
if (FID_Event_mouse_relative == NULL)
|
|
return JNI_ERR;
|
|
|
|
cls = env->FindClass("java/lang/Object");
|
|
if (cls == NULL)
|
|
return JNI_ERR;
|
|
MID_Object_wait = env->GetMethodID(cls, "wait", "()V");
|
|
if (MID_Object_wait == NULL)
|
|
return JNI_ERR;
|
|
|
|
return JNI_VERSION_1_2;
|
|
}
|
|
|
|
#endif
|