Migrate the Android port to SDL2

This commit is contained in:
Gregor Parzefall 2024-03-25 23:06:51 +01:00 committed by grorp
parent fca60e2a41
commit 07fdf7158d
30 changed files with 217 additions and 1538 deletions

View File

@ -0,0 +1,18 @@
diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
index fd5a056e3..83e3cf657 100644
--- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -1345,7 +1345,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
}
}
- if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
+ if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE ||
+ /*
+ * CUSTOM ADDITION FOR MINETEST
+ * should be upstreamed
+ */
+ (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) {
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
// they are ignored here because sending them as mouse input to SDL is messy
if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {

View File

@ -20,7 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
package net.minetest.minetest;
import android.app.NativeActivity;
import org.libsdl.app.SDLActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -32,6 +33,7 @@ import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.content.res.Configuration;
import androidx.annotation.Keep;
import androidx.appcompat.app.AlertDialog;
@ -45,12 +47,29 @@ import java.util.Objects;
// This annotation prevents the minifier/Proguard from mangling them.
@Keep
@SuppressWarnings("unused")
public class GameActivity extends NativeActivity {
static {
System.loadLibrary("c++_shared");
System.loadLibrary("minetest");
public class GameActivity extends SDLActivity {
@Override
protected String getMainSharedObject() {
return getContext().getApplicationInfo().nativeLibraryDir + "/libminetest.so";
}
@Override
protected String getMainFunction() {
return "SDL_Main";
}
@Override
protected String[] getLibraries() {
return new String[] {
"minetest"
};
}
// Prevent SDL from changing orientation settings since we already set the
// correct orientation in our AndroidManifest.xml
@Override
public void setOrientationBis(int w, int h, boolean resizable, String hint) {}
enum DialogType { TEXT_INPUT, SELECTION_INPUT }
enum DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
@ -59,32 +78,6 @@ public class GameActivity extends NativeActivity {
private String messageReturnValue = "";
private int selectionReturnValue = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
private void makeFullScreen() {
this.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
makeFullScreen();
}
@Override
protected void onResume() {
super.onResume();
makeFullScreen();
}
private native void saveSettings();
@Override
@ -96,11 +89,6 @@ public class GameActivity extends NativeActivity {
saveSettings();
}
@Override
public void onBackPressed() {
// Ignore the back press so Minetest can handle it
}
public void showTextInputDialog(String hint, String current, int editType) {
runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
}
@ -265,4 +253,8 @@ public class GameActivity extends NativeActivity {
return langCode;
}
public boolean hasPhysicalKeyboard() {
return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
}
}

View File

@ -1345,7 +1345,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
}
}
if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE ||
/*
* CUSTOM ADDITION FOR MINETEST
* should be upstreamed
*/
(source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) {
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
// they are ignored here because sending them as mouse input to SDL is messy
if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {

View File

@ -153,8 +153,6 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
[*Touchscreen]
# Enables touchscreen mode, allowing you to play the game with a touchscreen.
#
# Requires: !android
enable_touch (Enable touchscreen) bool true
# Touchscreen sensitivity multiplier.

View File

@ -25,3 +25,5 @@ set(VORBIS_LIBRARY ${DEPS}/Vorbis/libvorbis.a)
set(VORBISFILE_LIBRARY ${DEPS}/Vorbis/libvorbisfile.a)
set(ZSTD_INCLUDE_DIR ${DEPS}/Zstd/include)
set(ZSTD_LIBRARY ${DEPS}/Zstd/libzstd.a)
set(SDL2_INCLUDE_DIRS ${DEPS}/SDL2/include/SDL2)
set(SDL2_LIBRARIES ${DEPS}/SDL2/libSDL2.a)

View File

@ -79,9 +79,6 @@ enum EEVENT_TYPE
*/
EET_USER_EVENT,
//! Pass on raw events from the OS
EET_SYSTEM_EVENT,
//! Application state events like a resume, pause etc.
EET_APPLICATION_EVENT,
@ -187,17 +184,6 @@ enum ETOUCH_INPUT_EVENT
ETIE_COUNT
};
enum ESYSTEM_EVENT_TYPE
{
//! From Android command handler for native activity messages
ESET_ANDROID_CMD = 0,
// TODO: for example ESET_WINDOWS_MESSAGE for win32 message loop events
//! No real event, but to get number of event types
ESET_COUNT
};
//! Enumeration for a commonly used application state events (it's useful mainly for mobile devices)
enum EAPPLICATION_EVENT_TYPE
{
@ -528,25 +514,6 @@ struct SEvent
size_t UserData2;
};
// Raw events from the OS
struct SSystemEvent
{
//! Android command handler native activity messages.
struct SAndroidCmd
{
//! APP_CMD_ enums defined in android_native_app_glue.h from the Android NDK
s32 Cmd;
};
// TOOD: more structs for iphone, Windows, X11, etc.
ESYSTEM_EVENT_TYPE EventType;
union
{
struct SAndroidCmd AndroidCmd;
};
};
// Application state event
struct SApplicationEvent
{
@ -567,7 +534,6 @@ struct SEvent
struct SJoystickEvent JoystickEvent;
struct SLogEvent LogEvent;
struct SUserEvent UserEvent;
struct SSystemEvent SystemEvent;
struct SApplicationEvent ApplicationEvent;
};
};

View File

@ -180,10 +180,7 @@ public:
virtual bool isFullscreen() const = 0;
//! Checks if the window could possibly be visible.
//! Currently, this only returns false when the activity is stopped on
//! Android. Note that for Android activities, "stopped" means something
//! different than you might expect (and also something different than
//! "paused"). Read the Android lifecycle documentation.
/** If this returns false, you should not do any rendering. */
virtual bool isWindowVisible() const { return true; };
//! Get the current color format of the window

View File

@ -1,100 +0,0 @@
// Copyright (C) 2002-2011 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CAndroidAssetReader.h"
#include "CReadFile.h"
#include "coreutil.h"
#include "CAndroidAssetFileArchive.h"
#include "CIrrDeviceAndroid.h"
#include "os.h" // for logging (just keep it in even when not needed right now as it's used all the time)
#include <android_native_app_glue.h>
#include <android/native_activity.h>
#include <android/log.h>
namespace irr
{
namespace io
{
CAndroidAssetFileArchive::CAndroidAssetFileArchive(AAssetManager *assetManager, bool ignoreCase, bool ignorePaths) :
CFileList("/asset", ignoreCase, ignorePaths), AssetManager(assetManager)
{
}
CAndroidAssetFileArchive::~CAndroidAssetFileArchive()
{
}
//! get the archive type
E_FILE_ARCHIVE_TYPE CAndroidAssetFileArchive::getType() const
{
return EFAT_ANDROID_ASSET;
}
const IFileList *CAndroidAssetFileArchive::getFileList() const
{
// The assert_manager can not read directory names, so
// getFileList returns only files in folders which have been added.
return this;
}
//! opens a file by file name
IReadFile *CAndroidAssetFileArchive::createAndOpenFile(const io::path &filename)
{
CAndroidAssetReader *reader = new CAndroidAssetReader(AssetManager, filename);
if (reader->isOpen())
return reader;
reader->drop();
return NULL;
}
//! opens a file by index
IReadFile *CAndroidAssetFileArchive::createAndOpenFile(u32 index)
{
const io::path &filename(getFullFileName(index));
if (filename.empty())
return 0;
return createAndOpenFile(filename);
}
void CAndroidAssetFileArchive::addDirectoryToFileList(const io::path &dirname_)
{
io::path dirname(dirname_);
fschar_t lastChar = dirname.lastChar();
if (lastChar == '/' || lastChar == '\\')
dirname.erase(dirname.size() - 1);
// os::Printer::log("addDirectoryToFileList:", dirname.c_str(), ELL_DEBUG);
if (findFile(dirname, true) >= 0)
return; // was already added
AAssetDir *dir = AAssetManager_openDir(AssetManager, core::stringc(dirname).c_str());
if (!dir)
return;
// add directory itself
addItem(dirname, 0, 0, /*isDir*/ true, getFileCount());
// add all files in folder.
// Note: AAssetDir_getNextFileName does not return directory names (neither does any other NDK function)
while (const char *filename = AAssetDir_getNextFileName(dir)) {
core::stringc full_filename = dirname == "" ? filename
: dirname + "/" + filename;
// We can't get the size without opening the file - so for performance
// reasons we set the file size to 0.
// TODO: Does this really cost so much performance that it's worth losing this information? Dirs are usually just added once at startup...
addItem(full_filename, /*offet*/ 0, /*size*/ 0, /*isDir*/ false, getFileCount());
// os::Printer::log("addItem:", full_filename.c_str(), ELL_DEBUG);
}
AAssetDir_close(dir);
}
} // end namespace io
} // end namespace irr

View File

@ -1,58 +0,0 @@
// Copyright (C) 2012 Joerg Henrichs
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#pragma once
#include "IReadFile.h"
#include "IFileArchive.h"
#include "CFileList.h"
#include <android/native_activity.h>
namespace irr
{
namespace io
{
/*!
Android asset file system written August 2012 by J.Henrichs (later reworked by others).
*/
class CAndroidAssetFileArchive : public virtual IFileArchive,
virtual CFileList
{
public:
//! constructor
CAndroidAssetFileArchive(AAssetManager *assetManager, bool ignoreCase, bool ignorePaths);
//! destructor
virtual ~CAndroidAssetFileArchive();
//! opens a file by file name
virtual IReadFile *createAndOpenFile(const io::path &filename);
//! opens a file by index
virtual IReadFile *createAndOpenFile(u32 index);
//! returns the list of files
virtual const IFileList *getFileList() const;
//! get the archive type
virtual E_FILE_ARCHIVE_TYPE getType() const;
//! Add a directory to read files from. Since the Android
//! API does not return names of directories, they need to
//! be added manually.
virtual void addDirectoryToFileList(const io::path &filename);
//! return the name (id) of the file Archive
const io::path &getArchiveName() const override { return Path; }
protected:
//! Android's asset manager
AAssetManager *AssetManager;
}; // CAndroidAssetFileArchive
} // end namespace io
} // end namespace irr

View File

@ -1,65 +0,0 @@
// Copyright (C) 2002-2011 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CAndroidAssetReader.h"
#include "CReadFile.h"
#include "coreutil.h"
#include "CAndroidAssetReader.h"
#include "CIrrDeviceAndroid.h"
#include <android_native_app_glue.h>
#include <android/native_activity.h>
namespace irr
{
namespace io
{
CAndroidAssetReader::CAndroidAssetReader(AAssetManager *assetManager, const io::path &filename) :
AssetManager(assetManager), Filename(filename)
{
Asset = AAssetManager_open(AssetManager,
core::stringc(filename).c_str(),
AASSET_MODE_RANDOM);
}
CAndroidAssetReader::~CAndroidAssetReader()
{
if (Asset)
AAsset_close(Asset);
}
size_t CAndroidAssetReader::read(void *buffer, size_t sizeToRead)
{
int readBytes = AAsset_read(Asset, buffer, sizeToRead);
if (readBytes >= 0)
return size_t(readBytes);
return 0; // direct fd access is not possible (for example, if the asset is compressed).
}
bool CAndroidAssetReader::seek(long finalPos, bool relativeMovement)
{
off_t status = AAsset_seek(Asset, finalPos, relativeMovement ? SEEK_CUR : SEEK_SET);
return status + 1;
}
long CAndroidAssetReader::getSize() const
{
return AAsset_getLength(Asset);
}
long CAndroidAssetReader::getPos() const
{
return AAsset_getLength(Asset) - AAsset_getRemainingLength(Asset);
}
const io::path &CAndroidAssetReader::getFileName() const
{
return Filename;
}
} // end namespace io
} // end namespace irr

View File

@ -1,64 +0,0 @@
// Copyright (C) 2012 Joerg Henrichs
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#pragma once
#include "IReadFile.h"
struct AAssetManager;
struct AAsset;
struct ANativeActivity;
namespace irr
{
namespace io
{
class CAndroidAssetReader : public virtual IReadFile
{
public:
CAndroidAssetReader(AAssetManager *assetManager, const io::path &filename);
virtual ~CAndroidAssetReader();
//! Reads an amount of bytes from the file.
/** \param buffer Pointer to buffer where read bytes are written to.
\param sizeToRead Amount of bytes to read from the file.
\return How many bytes were read. */
virtual size_t read(void *buffer, size_t sizeToRead);
//! Changes position in file
/** \param finalPos Destination position in the file.
\param relativeMovement If set to true, the position in the file is
changed relative to current position. Otherwise the position is changed
from beginning of file.
\return True if successful, otherwise false. */
virtual bool seek(long finalPos, bool relativeMovement = false);
//! Get size of file.
/** \return Size of the file in bytes. */
virtual long getSize() const;
//! Get the current position in the file.
/** \return Current position in the file in bytes. */
virtual long getPos() const;
//! Get name of file.
/** \return File name as zero terminated character string. */
virtual const io::path &getFileName() const;
/** Return true if the file could be opened. */
bool isOpen() const { return Asset != NULL; }
private:
//! Android's asset manager
AAssetManager *AssetManager;
// An asset, i.e. file
AAsset *Asset;
path Filename;
};
} // end namespace io
} // end namespace irr

View File

@ -1,828 +0,0 @@
// Copyright (C) 2002-2007 Nikolaus Gebhardt
// Copyright (C) 2007-2011 Christian Stehno
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CIrrDeviceAndroid.h"
#include "os.h"
#include "CFileSystem.h"
#include "CAndroidAssetReader.h"
#include "CAndroidAssetFileArchive.h"
#include "CKeyEventWrapper.h"
#include "CEGLManager.h"
#include "ISceneManager.h"
#include "IGUIEnvironment.h"
#include "CEGLManager.h"
namespace irr
{
namespace video
{
IVideoDriver *createOGLES1Driver(const SIrrlichtCreationParameters &params,
io::IFileSystem *io, video::IContextManager *contextManager);
IVideoDriver *createOGLES2Driver(const SIrrlichtCreationParameters &params,
io::IFileSystem *io, video::IContextManager *contextManager);
}
}
namespace irr
{
CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters &param) :
CIrrDeviceStub(param), Accelerometer(0), Gyroscope(0), Initialized(false),
Stopped(true), Paused(true), Focused(false), JNIEnvAttachedToVM(0)
{
#ifdef _DEBUG
setDebugName("CIrrDeviceAndroid");
#endif
// Get the interface to the native Android activity.
Android = (android_app *)(param.PrivateData);
// Set the private data so we can use it in any static callbacks.
Android->userData = this;
// Set the default command handler. This is a callback function that the Android
// OS invokes to send the native activity messages.
Android->onAppCmd = handleAndroidCommand;
createKeyMap();
// Create a sensor manager to receive touch screen events from the java activity.
SensorManager = ASensorManager_getInstance();
SensorEventQueue = ASensorManager_createEventQueue(SensorManager, Android->looper, LOOPER_ID_USER, 0, 0);
Android->onInputEvent = handleInput;
// Create EGL manager.
ContextManager = new video::CEGLManager();
os::Printer::log("Waiting for Android activity window to be created.", ELL_DEBUG);
do {
s32 Events = 0;
android_poll_source *Source = 0;
while ((ALooper_pollAll((!Initialized || isWindowActive()) ? 0 : -1, 0, &Events, (void **)&Source)) >= 0) {
if (Source)
Source->process(Android, Source);
}
} while (!Initialized);
}
CIrrDeviceAndroid::~CIrrDeviceAndroid()
{
if (GUIEnvironment) {
GUIEnvironment->drop();
GUIEnvironment = 0;
}
if (SceneManager) {
SceneManager->drop();
SceneManager = 0;
}
if (VideoDriver) {
VideoDriver->drop();
VideoDriver = 0;
}
}
bool CIrrDeviceAndroid::run()
{
if (!Initialized)
return false;
os::Timer::tick();
s32 id;
s32 Events = 0;
android_poll_source *Source = 0;
while ((id = ALooper_pollAll(0, 0, &Events, (void **)&Source)) >= 0) {
if (Source)
Source->process(Android, Source);
// if a sensor has data, we'll process it now.
if (id == LOOPER_ID_USER) {
ASensorEvent sensorEvent;
while (ASensorEventQueue_getEvents(SensorEventQueue, &sensorEvent, 1) > 0) {
switch (sensorEvent.type) {
case ASENSOR_TYPE_ACCELEROMETER:
SEvent accEvent;
accEvent.EventType = EET_ACCELEROMETER_EVENT;
accEvent.AccelerometerEvent.X = sensorEvent.acceleration.x;
accEvent.AccelerometerEvent.Y = sensorEvent.acceleration.y;
accEvent.AccelerometerEvent.Z = sensorEvent.acceleration.z;
postEventFromUser(accEvent);
break;
case ASENSOR_TYPE_GYROSCOPE:
SEvent gyroEvent;
gyroEvent.EventType = EET_GYROSCOPE_EVENT;
gyroEvent.GyroscopeEvent.X = sensorEvent.vector.x;
gyroEvent.GyroscopeEvent.Y = sensorEvent.vector.y;
gyroEvent.GyroscopeEvent.Z = sensorEvent.vector.z;
postEventFromUser(gyroEvent);
break;
default:
break;
}
}
}
if (!Initialized)
break;
}
return Initialized;
}
void CIrrDeviceAndroid::yield()
{
struct timespec ts = {0, 1};
nanosleep(&ts, NULL);
}
void CIrrDeviceAndroid::sleep(u32 timeMs, bool pauseTimer)
{
const bool wasStopped = Timer ? Timer->isStopped() : true;
struct timespec ts;
ts.tv_sec = (time_t)(timeMs / 1000);
ts.tv_nsec = (long)(timeMs % 1000) * 1000000;
if (pauseTimer && !wasStopped)
Timer->stop();
nanosleep(&ts, NULL);
if (pauseTimer && !wasStopped)
Timer->start();
}
void CIrrDeviceAndroid::setWindowCaption(const wchar_t *text)
{
}
bool CIrrDeviceAndroid::isWindowActive() const
{
return (Focused && !Paused && !Stopped);
}
bool CIrrDeviceAndroid::isWindowFocused() const
{
return Focused;
}
bool CIrrDeviceAndroid::isWindowMinimized() const
{
return !Focused;
}
bool CIrrDeviceAndroid::isWindowVisible() const
{
return !Stopped;
}
void CIrrDeviceAndroid::closeDevice()
{
ANativeActivity_finish(Android->activity);
}
void CIrrDeviceAndroid::setResizable(bool resize)
{
}
void CIrrDeviceAndroid::minimizeWindow()
{
}
void CIrrDeviceAndroid::maximizeWindow()
{
}
void CIrrDeviceAndroid::restoreWindow()
{
}
core::position2di CIrrDeviceAndroid::getWindowPosition()
{
return core::position2di(0, 0);
}
E_DEVICE_TYPE CIrrDeviceAndroid::getType() const
{
return EIDT_ANDROID;
}
void CIrrDeviceAndroid::handleAndroidCommand(android_app *app, int32_t cmd)
{
CIrrDeviceAndroid *device = (CIrrDeviceAndroid *)app->userData;
SEvent event;
event.EventType = EET_SYSTEM_EVENT;
event.SystemEvent.EventType = ESET_ANDROID_CMD;
event.SystemEvent.AndroidCmd.Cmd = cmd;
if (device->postEventFromUser(event))
return;
switch (cmd) {
case APP_CMD_INPUT_CHANGED:
os::Printer::log("Android command APP_CMD_INPUT_CHANGED", ELL_DEBUG);
break;
case APP_CMD_WINDOW_RESIZED:
os::Printer::log("Android command APP_CMD_WINDOW_RESIZED", ELL_DEBUG);
break;
case APP_CMD_WINDOW_REDRAW_NEEDED:
os::Printer::log("Android command APP_CMD_WINDOW_REDRAW_NEEDED", ELL_DEBUG);
break;
case APP_CMD_SAVE_STATE:
os::Printer::log("Android command APP_CMD_SAVE_STATE", ELL_DEBUG);
break;
case APP_CMD_CONTENT_RECT_CHANGED:
os::Printer::log("Android command APP_CMD_CONTENT_RECT_CHANGED", ELL_DEBUG);
break;
case APP_CMD_CONFIG_CHANGED:
os::Printer::log("Android command APP_CMD_CONFIG_CHANGED", ELL_DEBUG);
break;
case APP_CMD_LOW_MEMORY:
os::Printer::log("Android command APP_CMD_LOW_MEMORY", ELL_DEBUG);
break;
case APP_CMD_START:
os::Printer::log("Android command APP_CMD_START", ELL_DEBUG);
device->Stopped = false;
break;
case APP_CMD_INIT_WINDOW:
os::Printer::log("Android command APP_CMD_INIT_WINDOW", ELL_DEBUG);
device->getExposedVideoData().OGLESAndroid.Window = app->window;
if (device->CreationParams.WindowSize.Width == 0 || device->CreationParams.WindowSize.Height == 0) {
device->CreationParams.WindowSize.Width = ANativeWindow_getWidth(app->window);
device->CreationParams.WindowSize.Height = ANativeWindow_getHeight(app->window);
}
device->getContextManager()->initialize(device->CreationParams, device->ExposedVideoData);
device->getContextManager()->generateSurface();
device->getContextManager()->generateContext();
device->getContextManager()->activateContext(device->getContextManager()->getContext());
if (!device->Initialized) {
io::CAndroidAssetFileArchive *assets = new io::CAndroidAssetFileArchive(device->Android->activity->assetManager, false, false);
assets->addDirectoryToFileList("");
device->FileSystem->addFileArchive(assets);
assets->drop();
device->createDriver();
if (device->VideoDriver)
device->createGUIAndScene();
}
device->Initialized = true;
break;
case APP_CMD_TERM_WINDOW:
os::Printer::log("Android command APP_CMD_TERM_WINDOW", ELL_DEBUG);
device->getContextManager()->destroySurface();
break;
case APP_CMD_GAINED_FOCUS:
os::Printer::log("Android command APP_CMD_GAINED_FOCUS", ELL_DEBUG);
device->Focused = true;
break;
case APP_CMD_LOST_FOCUS:
os::Printer::log("Android command APP_CMD_LOST_FOCUS", ELL_DEBUG);
device->Focused = false;
break;
case APP_CMD_DESTROY:
os::Printer::log("Android command APP_CMD_DESTROY", ELL_DEBUG);
if (device->JNIEnvAttachedToVM) {
device->JNIEnvAttachedToVM = 0;
device->Android->activity->vm->DetachCurrentThread();
}
device->Initialized = false;
break;
case APP_CMD_PAUSE:
os::Printer::log("Android command APP_CMD_PAUSE", ELL_DEBUG);
device->Paused = true;
break;
case APP_CMD_STOP:
os::Printer::log("Android command APP_CMD_STOP", ELL_DEBUG);
device->Stopped = true;
break;
case APP_CMD_RESUME:
os::Printer::log("Android command APP_CMD_RESUME", ELL_DEBUG);
device->Paused = false;
break;
default:
break;
}
}
s32 CIrrDeviceAndroid::handleInput(android_app *app, AInputEvent *androidEvent)
{
CIrrDeviceAndroid *device = (CIrrDeviceAndroid *)app->userData;
s32 status = 0;
switch (AInputEvent_getType(androidEvent)) {
case AINPUT_EVENT_TYPE_MOTION: {
SEvent event;
event.EventType = EET_TOUCH_INPUT_EVENT;
s32 eventAction = AMotionEvent_getAction(androidEvent);
s32 eventType = eventAction & AMOTION_EVENT_ACTION_MASK;
#if 0
// Useful for debugging. We might have to pass some of those infos on at some point.
// but preferably device independent (so iphone can use same irrlicht flags).
int32_t flags = AMotionEvent_getFlags(androidEvent);
os::Printer::log("flags: ", core::stringc(flags).c_str(), ELL_DEBUG);
int32_t metaState = AMotionEvent_getMetaState(androidEvent);
os::Printer::log("metaState: ", core::stringc(metaState).c_str(), ELL_DEBUG);
int32_t edgeFlags = AMotionEvent_getEdgeFlags(androidEvent);
os::Printer::log("edgeFlags: ", core::stringc(flags).c_str(), ELL_DEBUG);
#endif
bool touchReceived = true;
switch (eventType) {
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
event.TouchInput.Event = ETIE_PRESSED_DOWN;
break;
case AMOTION_EVENT_ACTION_MOVE:
event.TouchInput.Event = ETIE_MOVED;
break;
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
case AMOTION_EVENT_ACTION_CANCEL:
event.TouchInput.Event = ETIE_LEFT_UP;
break;
default:
touchReceived = false;
break;
}
if (touchReceived) {
// Process all touches for move action.
if (event.TouchInput.Event == ETIE_MOVED) {
s32 pointerCount = AMotionEvent_getPointerCount(androidEvent);
for (s32 i = 0; i < pointerCount; ++i) {
event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i);
event.TouchInput.X = AMotionEvent_getX(androidEvent, i);
event.TouchInput.Y = AMotionEvent_getY(androidEvent, i);
event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
device->postEventFromUser(event);
}
} else // Process one touch for other actions.
{
s32 pointerIndex = (eventAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, pointerIndex);
event.TouchInput.X = AMotionEvent_getX(androidEvent, pointerIndex);
event.TouchInput.Y = AMotionEvent_getY(androidEvent, pointerIndex);
event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
device->postEventFromUser(event);
}
status = 1;
}
} break;
case AINPUT_EVENT_TYPE_KEY: {
SEvent event;
event.EventType = EET_KEY_INPUT_EVENT;
int32_t keyCode = AKeyEvent_getKeyCode(androidEvent);
// os::Printer::log("keyCode: ", core::stringc(keyCode).c_str(), ELL_DEBUG);
int32_t keyAction = AKeyEvent_getAction(androidEvent);
int32_t keyMetaState = AKeyEvent_getMetaState(androidEvent);
if (keyCode >= 0 && (u32)keyCode < device->KeyMap.size())
event.KeyInput.Key = device->KeyMap[keyCode];
else
event.KeyInput.Key = KEY_UNKNOWN;
event.KeyInput.SystemKeyCode = (u32)keyCode;
if (keyAction == AKEY_EVENT_ACTION_DOWN)
event.KeyInput.PressedDown = true;
else if (keyAction == AKEY_EVENT_ACTION_UP)
event.KeyInput.PressedDown = false;
else if (keyAction == AKEY_EVENT_ACTION_MULTIPLE) {
// TODO: Multiple duplicate key events have occurred in a row,
// or a complex string is being delivered. The repeat_count
// property of the key event contains the number of times the
// given key code should be executed.
// I guess this might necessary for more complicated i18n key input,
// but don't see yet how to handle this correctly.
}
/* no use for meta keys so far.
if ( keyMetaState & AMETA_ALT_ON
|| keyMetaState & AMETA_ALT_LEFT_ON
|| keyMetaState & AMETA_ALT_RIGHT_ON )
;
// what is a sym?
if ( keyMetaState & AMETA_SYM_ON )
;
*/
if (keyMetaState & AMETA_SHIFT_ON || keyMetaState & AMETA_SHIFT_LEFT_ON || keyMetaState & AMETA_SHIFT_RIGHT_ON)
event.KeyInput.Shift = true;
else
event.KeyInput.Shift = false;
event.KeyInput.Control = false;
// Having memory allocations + going through JNI for each key-press is pretty bad (slow).
// So we do it only for those keys which are likely text-characters and avoid it for all other keys.
// So it's fast for keys like game controller input and special keys. And text keys are typically
// only used or entering text and not for gaming on Android, so speed likely doesn't matter there too much.
if (event.KeyInput.Key > 0) {
// TODO:
// Not sure why we have to attach a JNIEnv here, but it won't work when doing that in the constructor or
// trying to use the activity->env. My best guess is that the event-handling happens in an own thread.
// It means JNIEnvAttachedToVM will never get detached as I don't know a safe way where to do that
// (we could attach & detach each time, but that would probably be slow)
// Also - it has to be each time as it get's invalid when the application mode changes.
if (device->Initialized && device->Android && device->Android->activity && device->Android->activity->vm) {
JavaVMAttachArgs attachArgs;
attachArgs.version = JNI_VERSION_1_6;
attachArgs.name = 0;
attachArgs.group = NULL;
// Not a big problem calling it each time - it's a no-op when the thread already is attached.
// And we have to do that as someone else can have detached the thread in the meantime.
jint result = device->Android->activity->vm->AttachCurrentThread(&device->JNIEnvAttachedToVM, &attachArgs);
if (result == JNI_ERR) {
os::Printer::log("AttachCurrentThread for the JNI environment failed.", ELL_WARNING);
device->JNIEnvAttachedToVM = 0;
}
if (device->JNIEnvAttachedToVM) {
jni::CKeyEventWrapper *keyEventWrapper = new jni::CKeyEventWrapper(device->JNIEnvAttachedToVM, keyAction, keyCode);
event.KeyInput.Char = keyEventWrapper->getUnicodeChar(keyMetaState);
delete keyEventWrapper;
}
}
if (event.KeyInput.Key == KEY_BACK) {
event.KeyInput.Char = 0x08; // same key-code as on other operating systems. Otherwise we have to handle too much system specific stuff in the editbox.
}
// os::Printer::log("char-code: ", core::stringc((int)event.KeyInput.Char).c_str(), ELL_DEBUG);
} else {
// os::Printer::log("keyCode: ", core::stringc(keyCode).c_str(), ELL_DEBUG);
event.KeyInput.Char = 0;
}
status = device->postEventFromUser(event);
} break;
default:
break;
}
return status;
}
void CIrrDeviceAndroid::createDriver()
{
switch (CreationParams.DriverType) {
case video::EDT_OGLES1:
#ifdef _IRR_COMPILE_WITH_OGLES1_
VideoDriver = video::createOGLES1Driver(CreationParams, FileSystem, ContextManager);
#else
os::Printer::log("No OpenGL ES 1.0 support compiled in.", ELL_ERROR);
#endif
break;
case video::EDT_OGLES2:
#ifdef _IRR_COMPILE_WITH_OGLES2_
VideoDriver = video::createOGLES2Driver(CreationParams, FileSystem, ContextManager);
#else
os::Printer::log("No OpenGL ES 2.0 support compiled in.", ELL_ERROR);
#endif
break;
case video::EDT_NULL:
VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize);
break;
case video::EDT_OPENGL:
os::Printer::log("This driver is not available in Android. Try OpenGL ES 1.0 or ES 2.0.", ELL_ERROR);
break;
default:
os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR);
break;
}
}
video::SExposedVideoData &CIrrDeviceAndroid::getExposedVideoData()
{
return ExposedVideoData;
}
void CIrrDeviceAndroid::createKeyMap()
{
KeyMap.set_used(223);
KeyMap[0] = KEY_UNKNOWN; // AKEYCODE_UNKNOWN
KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT
KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT
KeyMap[3] = KEY_HOME; // AKEYCODE_HOME
KeyMap[4] = KEY_CANCEL; // AKEYCODE_BACK
KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL
KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL
KeyMap[7] = KEY_KEY_0; // AKEYCODE_0
KeyMap[8] = KEY_KEY_1; // AKEYCODE_1
KeyMap[9] = KEY_KEY_2; // AKEYCODE_2
KeyMap[10] = KEY_KEY_3; // AKEYCODE_3
KeyMap[11] = KEY_KEY_4; // AKEYCODE_4
KeyMap[12] = KEY_KEY_5; // AKEYCODE_5
KeyMap[13] = KEY_KEY_6; // AKEYCODE_6
KeyMap[14] = KEY_KEY_7; // AKEYCODE_7
KeyMap[15] = KEY_KEY_8; // AKEYCODE_8
KeyMap[16] = KEY_KEY_9; // AKEYCODE_9
KeyMap[17] = KEY_UNKNOWN; // AKEYCODE_STAR
KeyMap[18] = KEY_UNKNOWN; // AKEYCODE_POUND
KeyMap[19] = KEY_UP; // AKEYCODE_DPAD_UP
KeyMap[20] = KEY_DOWN; // AKEYCODE_DPAD_DOWN
KeyMap[21] = KEY_LEFT; // AKEYCODE_DPAD_LEFT
KeyMap[22] = KEY_RIGHT; // AKEYCODE_DPAD_RIGHT
KeyMap[23] = KEY_SELECT; // AKEYCODE_DPAD_CENTER
KeyMap[24] = KEY_VOLUME_DOWN; // AKEYCODE_VOLUME_UP
KeyMap[25] = KEY_VOLUME_UP; // AKEYCODE_VOLUME_DOWN
KeyMap[26] = KEY_UNKNOWN; // AKEYCODE_POWER
KeyMap[27] = KEY_UNKNOWN; // AKEYCODE_CAMERA
KeyMap[28] = KEY_CLEAR; // AKEYCODE_CLEAR
KeyMap[29] = KEY_KEY_A; // AKEYCODE_A
KeyMap[30] = KEY_KEY_B; // AKEYCODE_B
KeyMap[31] = KEY_KEY_C; // AKEYCODE_C
KeyMap[32] = KEY_KEY_D; // AKEYCODE_D
KeyMap[33] = KEY_KEY_E; // AKEYCODE_E
KeyMap[34] = KEY_KEY_F; // AKEYCODE_F
KeyMap[35] = KEY_KEY_G; // AKEYCODE_G
KeyMap[36] = KEY_KEY_H; // AKEYCODE_H
KeyMap[37] = KEY_KEY_I; // AKEYCODE_I
KeyMap[38] = KEY_KEY_J; // AKEYCODE_J
KeyMap[39] = KEY_KEY_K; // AKEYCODE_K
KeyMap[40] = KEY_KEY_L; // AKEYCODE_L
KeyMap[41] = KEY_KEY_M; // AKEYCODE_M
KeyMap[42] = KEY_KEY_N; // AKEYCODE_N
KeyMap[43] = KEY_KEY_O; // AKEYCODE_O
KeyMap[44] = KEY_KEY_P; // AKEYCODE_P
KeyMap[45] = KEY_KEY_Q; // AKEYCODE_Q
KeyMap[46] = KEY_KEY_R; // AKEYCODE_R
KeyMap[47] = KEY_KEY_S; // AKEYCODE_S
KeyMap[48] = KEY_KEY_T; // AKEYCODE_T
KeyMap[49] = KEY_KEY_U; // AKEYCODE_U
KeyMap[50] = KEY_KEY_V; // AKEYCODE_V
KeyMap[51] = KEY_KEY_W; // AKEYCODE_W
KeyMap[52] = KEY_KEY_X; // AKEYCODE_X
KeyMap[53] = KEY_KEY_Y; // AKEYCODE_Y
KeyMap[54] = KEY_KEY_Z; // AKEYCODE_Z
KeyMap[55] = KEY_COMMA; // AKEYCODE_COMMA
KeyMap[56] = KEY_PERIOD; // AKEYCODE_PERIOD
KeyMap[57] = KEY_MENU; // AKEYCODE_ALT_LEFT
KeyMap[58] = KEY_MENU; // AKEYCODE_ALT_RIGHT
KeyMap[59] = KEY_LSHIFT; // AKEYCODE_SHIFT_LEFT
KeyMap[60] = KEY_RSHIFT; // AKEYCODE_SHIFT_RIGHT
KeyMap[61] = KEY_TAB; // AKEYCODE_TAB
KeyMap[62] = KEY_SPACE; // AKEYCODE_SPACE
KeyMap[63] = KEY_UNKNOWN; // AKEYCODE_SYM
KeyMap[64] = KEY_UNKNOWN; // AKEYCODE_EXPLORER
KeyMap[65] = KEY_UNKNOWN; // AKEYCODE_ENVELOPE
KeyMap[66] = KEY_RETURN; // AKEYCODE_ENTER
KeyMap[67] = KEY_BACK; // AKEYCODE_DEL
KeyMap[68] = KEY_OEM_3; // AKEYCODE_GRAVE
KeyMap[69] = KEY_MINUS; // AKEYCODE_MINUS
KeyMap[70] = KEY_UNKNOWN; // AKEYCODE_EQUALS
KeyMap[71] = KEY_UNKNOWN; // AKEYCODE_LEFT_BRACKET
KeyMap[72] = KEY_UNKNOWN; // AKEYCODE_RIGHT_BRACKET
KeyMap[73] = KEY_UNKNOWN; // AKEYCODE_BACKSLASH
KeyMap[74] = KEY_UNKNOWN; // AKEYCODE_SEMICOLON
KeyMap[75] = KEY_UNKNOWN; // AKEYCODE_APOSTROPHE
KeyMap[76] = KEY_UNKNOWN; // AKEYCODE_SLASH
KeyMap[77] = KEY_UNKNOWN; // AKEYCODE_AT
KeyMap[78] = KEY_UNKNOWN; // AKEYCODE_NUM
KeyMap[79] = KEY_UNKNOWN; // AKEYCODE_HEADSETHOOK
KeyMap[80] = KEY_UNKNOWN; // AKEYCODE_FOCUS (*Camera* focus)
KeyMap[81] = KEY_PLUS; // AKEYCODE_PLUS
KeyMap[82] = KEY_MENU; // AKEYCODE_MENU
KeyMap[83] = KEY_UNKNOWN; // AKEYCODE_NOTIFICATION
KeyMap[84] = KEY_UNKNOWN; // AKEYCODE_SEARCH
KeyMap[85] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PLAY_PAUSE
KeyMap[86] = KEY_MEDIA_STOP; // AKEYCODE_MEDIA_STOP
KeyMap[87] = KEY_MEDIA_NEXT_TRACK; // AKEYCODE_MEDIA_NEXT
KeyMap[88] = KEY_MEDIA_PREV_TRACK; // AKEYCODE_MEDIA_PREVIOUS
KeyMap[89] = KEY_UNKNOWN; // AKEYCODE_MEDIA_REWIND
KeyMap[90] = KEY_UNKNOWN; // AKEYCODE_MEDIA_FAST_FORWARD
KeyMap[91] = KEY_VOLUME_MUTE; // AKEYCODE_MUTE
KeyMap[92] = KEY_PRIOR; // AKEYCODE_PAGE_UP
KeyMap[93] = KEY_NEXT; // AKEYCODE_PAGE_DOWN
KeyMap[94] = KEY_UNKNOWN; // AKEYCODE_PICTSYMBOLS
KeyMap[95] = KEY_UNKNOWN; // AKEYCODE_SWITCH_CHARSET
// following look like controller inputs
KeyMap[96] = KEY_UNKNOWN; // AKEYCODE_BUTTON_A
KeyMap[97] = KEY_UNKNOWN; // AKEYCODE_BUTTON_B
KeyMap[98] = KEY_UNKNOWN; // AKEYCODE_BUTTON_C
KeyMap[99] = KEY_UNKNOWN; // AKEYCODE_BUTTON_X
KeyMap[100] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Y
KeyMap[101] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Z
KeyMap[102] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L1
KeyMap[103] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R1
KeyMap[104] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L2
KeyMap[105] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R2
KeyMap[106] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBL
KeyMap[107] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBR
KeyMap[108] = KEY_UNKNOWN; // AKEYCODE_BUTTON_START
KeyMap[109] = KEY_UNKNOWN; // AKEYCODE_BUTTON_SELECT
KeyMap[110] = KEY_UNKNOWN; // AKEYCODE_BUTTON_MODE
KeyMap[111] = KEY_ESCAPE; // AKEYCODE_ESCAPE
KeyMap[112] = KEY_DELETE; // AKEYCODE_FORWARD_DEL
KeyMap[113] = KEY_CONTROL; // AKEYCODE_CTRL_LEFT
KeyMap[114] = KEY_CONTROL; // AKEYCODE_CTRL_RIGHT
KeyMap[115] = KEY_CAPITAL; // AKEYCODE_CAPS_LOCK
KeyMap[116] = KEY_SCROLL; // AKEYCODE_SCROLL_LOCK
KeyMap[117] = KEY_UNKNOWN; // AKEYCODE_META_LEFT
KeyMap[118] = KEY_UNKNOWN; // AKEYCODE_META_RIGHT
KeyMap[119] = KEY_UNKNOWN; // AKEYCODE_FUNCTION
KeyMap[120] = KEY_SNAPSHOT; // AKEYCODE_SYSRQ
KeyMap[121] = KEY_PAUSE; // AKEYCODE_BREAK
KeyMap[122] = KEY_HOME; // AKEYCODE_MOVE_HOME
KeyMap[123] = KEY_END; // AKEYCODE_MOVE_END
KeyMap[124] = KEY_INSERT; // AKEYCODE_INSERT
KeyMap[125] = KEY_UNKNOWN; // AKEYCODE_FORWARD
KeyMap[126] = KEY_PLAY; // AKEYCODE_MEDIA_PLAY
KeyMap[127] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PAUSE
KeyMap[128] = KEY_UNKNOWN; // AKEYCODE_MEDIA_CLOSE
KeyMap[129] = KEY_UNKNOWN; // AKEYCODE_MEDIA_EJECT
KeyMap[130] = KEY_UNKNOWN; // AKEYCODE_MEDIA_RECORD
KeyMap[131] = KEY_F1; // AKEYCODE_F1
KeyMap[132] = KEY_F2; // AKEYCODE_F2
KeyMap[133] = KEY_F3; // AKEYCODE_F3
KeyMap[134] = KEY_F4; // AKEYCODE_F4
KeyMap[135] = KEY_F5; // AKEYCODE_F5
KeyMap[136] = KEY_F6; // AKEYCODE_F6
KeyMap[137] = KEY_F7; // AKEYCODE_F7
KeyMap[138] = KEY_F8; // AKEYCODE_F8
KeyMap[139] = KEY_F9; // AKEYCODE_F9
KeyMap[140] = KEY_F10; // AKEYCODE_F10
KeyMap[141] = KEY_F11; // AKEYCODE_F11
KeyMap[142] = KEY_F12; // AKEYCODE_F12
KeyMap[143] = KEY_NUMLOCK; // AKEYCODE_NUM_LOCK
KeyMap[144] = KEY_NUMPAD0; // AKEYCODE_NUMPAD_0
KeyMap[145] = KEY_NUMPAD1; // AKEYCODE_NUMPAD_1
KeyMap[146] = KEY_NUMPAD2; // AKEYCODE_NUMPAD_2
KeyMap[147] = KEY_NUMPAD3; // AKEYCODE_NUMPAD_3
KeyMap[148] = KEY_NUMPAD4; // AKEYCODE_NUMPAD_4
KeyMap[149] = KEY_NUMPAD5; // AKEYCODE_NUMPAD_5
KeyMap[150] = KEY_NUMPAD6; // AKEYCODE_NUMPAD_6
KeyMap[151] = KEY_NUMPAD7; // AKEYCODE_NUMPAD_7
KeyMap[152] = KEY_NUMPAD8; // AKEYCODE_NUMPAD_8
KeyMap[153] = KEY_NUMPAD9; // AKEYCODE_NUMPAD_9
KeyMap[154] = KEY_DIVIDE; // AKEYCODE_NUMPAD_DIVIDE
KeyMap[155] = KEY_MULTIPLY; // AKEYCODE_NUMPAD_MULTIPLY
KeyMap[156] = KEY_SUBTRACT; // AKEYCODE_NUMPAD_SUBTRACT
KeyMap[157] = KEY_ADD; // AKEYCODE_NUMPAD_ADD
KeyMap[158] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_DOT
KeyMap[159] = KEY_COMMA; // AKEYCODE_NUMPAD_COMMA
KeyMap[160] = KEY_RETURN; // AKEYCODE_NUMPAD_ENTER
KeyMap[161] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_EQUALS
KeyMap[162] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_LEFT_PAREN
KeyMap[163] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_RIGHT_PAREN
KeyMap[164] = KEY_VOLUME_MUTE; // AKEYCODE_VOLUME_MUTE
KeyMap[165] = KEY_UNKNOWN; // AKEYCODE_INFO
KeyMap[166] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_UP
KeyMap[167] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_DOWN
KeyMap[168] = KEY_ZOOM; // AKEYCODE_ZOOM_IN
KeyMap[169] = KEY_UNKNOWN; // AKEYCODE_ZOOM_OUT
KeyMap[170] = KEY_UNKNOWN; // AKEYCODE_TV
KeyMap[171] = KEY_UNKNOWN; // AKEYCODE_WINDOW
KeyMap[172] = KEY_UNKNOWN; // AKEYCODE_GUIDE
KeyMap[173] = KEY_UNKNOWN; // AKEYCODE_DVR
KeyMap[174] = KEY_UNKNOWN; // AKEYCODE_BOOKMARK
KeyMap[175] = KEY_UNKNOWN; // AKEYCODE_CAPTIONS
KeyMap[176] = KEY_UNKNOWN; // AKEYCODE_SETTINGS
KeyMap[177] = KEY_UNKNOWN; // AKEYCODE_TV_POWER
KeyMap[178] = KEY_UNKNOWN; // AKEYCODE_TV_INPUT
KeyMap[179] = KEY_UNKNOWN; // AKEYCODE_STB_POWER
KeyMap[180] = KEY_UNKNOWN; // AKEYCODE_STB_INPUT
KeyMap[181] = KEY_UNKNOWN; // AKEYCODE_AVR_POWER
KeyMap[182] = KEY_UNKNOWN; // AKEYCODE_AVR_INPUT
KeyMap[183] = KEY_UNKNOWN; // AKEYCODE_PROG_RED
KeyMap[184] = KEY_UNKNOWN; // AKEYCODE_PROG_GREEN
KeyMap[185] = KEY_UNKNOWN; // AKEYCODE_PROG_YELLOW
KeyMap[186] = KEY_UNKNOWN; // AKEYCODE_PROG_BLUE
KeyMap[187] = KEY_UNKNOWN; // AKEYCODE_APP_SWITCH
KeyMap[188] = KEY_UNKNOWN; // AKEYCODE_BUTTON_1
KeyMap[189] = KEY_UNKNOWN; // AKEYCODE_BUTTON_2
KeyMap[190] = KEY_UNKNOWN; // AKEYCODE_BUTTON_3
KeyMap[191] = KEY_UNKNOWN; // AKEYCODE_BUTTON_4
KeyMap[192] = KEY_UNKNOWN; // AKEYCODE_BUTTON_5
KeyMap[193] = KEY_UNKNOWN; // AKEYCODE_BUTTON_6
KeyMap[194] = KEY_UNKNOWN; // AKEYCODE_BUTTON_7
KeyMap[195] = KEY_UNKNOWN; // AKEYCODE_BUTTON_8
KeyMap[196] = KEY_UNKNOWN; // AKEYCODE_BUTTON_9
KeyMap[197] = KEY_UNKNOWN; // AKEYCODE_BUTTON_10
KeyMap[198] = KEY_UNKNOWN; // AKEYCODE_BUTTON_11
KeyMap[199] = KEY_UNKNOWN; // AKEYCODE_BUTTON_12
KeyMap[200] = KEY_UNKNOWN; // AKEYCODE_BUTTON_13
KeyMap[201] = KEY_UNKNOWN; // AKEYCODE_BUTTON_14
KeyMap[202] = KEY_UNKNOWN; // AKEYCODE_BUTTON_15
KeyMap[203] = KEY_UNKNOWN; // AKEYCODE_BUTTON_16
KeyMap[204] = KEY_UNKNOWN; // AKEYCODE_LANGUAGE_SWITCH
KeyMap[205] = KEY_UNKNOWN; // AKEYCODE_MANNER_MODE
KeyMap[206] = KEY_UNKNOWN; // AKEYCODE_3D_MODE
KeyMap[207] = KEY_UNKNOWN; // AKEYCODE_CONTACTS
KeyMap[208] = KEY_UNKNOWN; // AKEYCODE_CALENDAR
KeyMap[209] = KEY_UNKNOWN; // AKEYCODE_MUSIC
KeyMap[210] = KEY_UNKNOWN; // AKEYCODE_CALCULATOR
KeyMap[211] = KEY_UNKNOWN; // AKEYCODE_ZENKAKU_HANKAKU
KeyMap[212] = KEY_UNKNOWN; // AKEYCODE_EISU
KeyMap[213] = KEY_UNKNOWN; // AKEYCODE_MUHENKAN
KeyMap[214] = KEY_UNKNOWN; // AKEYCODE_HENKAN
KeyMap[215] = KEY_UNKNOWN; // AKEYCODE_KATAKANA_HIRAGANA
KeyMap[216] = KEY_UNKNOWN; // AKEYCODE_YEN
KeyMap[217] = KEY_UNKNOWN; // AKEYCODE_RO
KeyMap[218] = KEY_UNKNOWN; // AKEYCODE_KANA
KeyMap[219] = KEY_UNKNOWN; // AKEYCODE_ASSIST
KeyMap[220] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_DOWN
KeyMap[221] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_UP ,
KeyMap[222] = KEY_UNKNOWN; // AKEYCODE_MEDIA_AUDIO_TRACK
}
bool CIrrDeviceAndroid::activateAccelerometer(float updateInterval)
{
if (!isAccelerometerAvailable())
return false;
ASensorEventQueue_enableSensor(SensorEventQueue, Accelerometer);
ASensorEventQueue_setEventRate(SensorEventQueue, Accelerometer, (int32_t)(updateInterval * 1000.f * 1000.f)); // in microseconds
os::Printer::log("Activated accelerometer", ELL_DEBUG);
return true;
}
bool CIrrDeviceAndroid::deactivateAccelerometer()
{
if (Accelerometer) {
ASensorEventQueue_disableSensor(SensorEventQueue, Accelerometer);
Accelerometer = 0;
os::Printer::log("Deactivated accelerometer", ELL_DEBUG);
return true;
}
return false;
}
bool CIrrDeviceAndroid::isAccelerometerActive()
{
return (Accelerometer != 0);
}
bool CIrrDeviceAndroid::isAccelerometerAvailable()
{
if (!Accelerometer)
Accelerometer = ASensorManager_getDefaultSensor(SensorManager, ASENSOR_TYPE_ACCELEROMETER);
return (Accelerometer != 0);
}
bool CIrrDeviceAndroid::activateGyroscope(float updateInterval)
{
if (!isGyroscopeAvailable())
return false;
ASensorEventQueue_enableSensor(SensorEventQueue, Gyroscope);
ASensorEventQueue_setEventRate(SensorEventQueue, Gyroscope, (int32_t)(updateInterval * 1000.f * 1000.f)); // in microseconds
os::Printer::log("Activated gyroscope", ELL_DEBUG);
return true;
}
bool CIrrDeviceAndroid::deactivateGyroscope()
{
if (Gyroscope) {
ASensorEventQueue_disableSensor(SensorEventQueue, Gyroscope);
Gyroscope = 0;
os::Printer::log("Deactivated gyroscope", ELL_DEBUG);
return true;
}
return false;
}
bool CIrrDeviceAndroid::isGyroscopeActive()
{
return (Gyroscope != 0);
}
bool CIrrDeviceAndroid::isGyroscopeAvailable()
{
if (!Gyroscope)
Gyroscope = ASensorManager_getDefaultSensor(SensorManager, ASENSOR_TYPE_GYROSCOPE);
return (Gyroscope != 0);
}
} // end namespace irr

View File

@ -1,98 +0,0 @@
// Copyright (C) 2002-2011 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#pragma once
#include "CIrrDeviceStub.h"
#include "IrrlichtDevice.h"
#include "ICursorControl.h"
#include <android/sensor.h>
#include <android_native_app_glue.h>
namespace irr
{
class CIrrDeviceAndroid : public CIrrDeviceStub
{
public:
CIrrDeviceAndroid(const SIrrlichtCreationParameters &param);
virtual ~CIrrDeviceAndroid();
virtual bool run();
virtual void yield();
virtual void sleep(u32 timeMs, bool pauseTimer = false);
virtual void setWindowCaption(const wchar_t *text);
virtual bool isWindowActive() const;
virtual bool isWindowFocused() const;
virtual bool isWindowMinimized() const;
virtual bool isWindowVisible() const;
virtual void closeDevice();
virtual void setResizable(bool resize = false);
virtual void minimizeWindow();
virtual void maximizeWindow();
virtual void restoreWindow();
virtual core::position2di getWindowPosition();
virtual E_DEVICE_TYPE getType() const;
virtual bool activateAccelerometer(float updateInterval);
virtual bool deactivateAccelerometer();
virtual bool isAccelerometerActive();
virtual bool isAccelerometerAvailable();
virtual bool activateGyroscope(float updateInterval);
virtual bool deactivateGyroscope();
virtual bool isGyroscopeActive();
virtual bool isGyroscopeAvailable();
private:
static void handleAndroidCommand(android_app *app, int32_t cmd);
static s32 handleInput(android_app *app, AInputEvent *event);
void createDriver();
void createKeyMap();
video::SExposedVideoData &getExposedVideoData();
android_app *Android;
ASensorManager *SensorManager;
ASensorEventQueue *SensorEventQueue;
const ASensor *Accelerometer;
const ASensor *Gyroscope;
bool Initialized;
bool Stopped;
bool Paused;
bool Focused;
JNIEnv *JNIEnvAttachedToVM;
video::SExposedVideoData ExposedVideoData;
core::array<EKEY_CODE> KeyMap;
};
} // end namespace irr

View File

@ -1,52 +0,0 @@
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CKeyEventWrapper.h"
#include "os.h"
namespace irr
{
namespace jni
{
jclass CKeyEventWrapper::Class_KeyEvent = 0;
jmethodID CKeyEventWrapper::Method_constructor = 0;
jmethodID CKeyEventWrapper::Method_getUnicodeChar = 0;
CKeyEventWrapper::CKeyEventWrapper(JNIEnv *jniEnv, int action, int code) :
JniEnv(jniEnv), JniKeyEvent(0)
{
if (JniEnv) {
if (!Class_KeyEvent) {
// Find java classes & functions on first call
os::Printer::log("CKeyEventWrapper first initialize", ELL_DEBUG);
jclass localClass = JniEnv->FindClass("android/view/KeyEvent");
if (localClass) {
Class_KeyEvent = reinterpret_cast<jclass>(JniEnv->NewGlobalRef(localClass));
}
Method_constructor = JniEnv->GetMethodID(Class_KeyEvent, "<init>", "(II)V");
Method_getUnicodeChar = JniEnv->GetMethodID(Class_KeyEvent, "getUnicodeChar", "(I)I");
}
if (Class_KeyEvent && Method_constructor) {
JniKeyEvent = JniEnv->NewObject(Class_KeyEvent, Method_constructor, action, code);
} else {
os::Printer::log("CKeyEventWrapper didn't find JNI classes/methods", ELL_WARNING);
}
}
}
CKeyEventWrapper::~CKeyEventWrapper()
{
JniEnv->DeleteLocalRef(JniKeyEvent);
}
int CKeyEventWrapper::getUnicodeChar(int metaState)
{
return JniEnv->CallIntMethod(JniKeyEvent, Method_getUnicodeChar, metaState);
}
} // namespace jni
} // namespace irr

View File

@ -1,35 +0,0 @@
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#pragma once
#include <jni.h>
struct android_app;
namespace irr
{
namespace jni
{
//! Minimal JNI wrapper class around android.view.KeyEvent
//! NOTE: Only functions we actually use in the engine are wrapped
//! This is currently not written to support multithreading - meaning threads are not attached/detached to the Java VM (to be discussed)
class CKeyEventWrapper
{
public:
CKeyEventWrapper(JNIEnv *jniEnv, int action, int code);
~CKeyEventWrapper();
int getUnicodeChar(int metaState);
private:
static jclass Class_KeyEvent;
static jmethodID Method_getUnicodeChar;
static jmethodID Method_constructor;
JNIEnv *JniEnv;
jobject JniKeyEvent; // this object in java
};
} // namespace jni
} // namespace irr

View File

@ -10,10 +10,6 @@
#include "irrArray.h"
#include "os.h"
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
#include <android/native_activity.h>
#endif
namespace irr
{
namespace video
@ -55,9 +51,6 @@ bool CEGLManager::initialize(const SIrrlichtCreationParameters &params, const SE
#elif defined(_IRR_COMPILE_WITH_X11_DEVICE_)
EglWindow = (NativeWindowType)Data.OpenGLLinux.X11Window;
EglDisplay = eglGetDisplay((NativeDisplayType)Data.OpenGLLinux.X11Display);
#elif defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
EglWindow = (ANativeWindow *)Data.OGLESAndroid.Window;
EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
#elif defined(_IRR_COMPILE_WITH_FB_DEVICE_)
EglWindow = (NativeWindowType)Data.OpenGLFB.Window;
EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
@ -119,10 +112,6 @@ bool CEGLManager::generateSurface()
// at this time only Android support this feature.
// this needs an update method instead!
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
EglWindow = (ANativeWindow *)Data.OGLESAndroid.Window;
#endif
#if defined(_IRR_EMSCRIPTEN_PLATFORM_)
// eglChooseConfig is currently only implemented as stub in emscripten (version 1.37.22 at point of writing)
// But the other solution would also be fine as it also only generates a single context so there is not much to choose from.
@ -136,13 +125,6 @@ bool CEGLManager::generateSurface()
return false;
}
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
EGLint Format = 0;
eglGetConfigAttrib(EglDisplay, EglConfig, EGL_NATIVE_VISUAL_ID, &Format);
ANativeWindow_setBuffersGeometry(EglWindow, 0, 0, Format);
#endif
// Now we are able to create EGL surface.
EglSurface = eglCreateWindowSurface(EglDisplay, EglConfig, EglWindow, 0);

View File

@ -252,13 +252,31 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters &param) :
Window((SDL_Window *)param.WindowId), SDL_Flags(0),
MouseX(0), MouseY(0), MouseXRel(0), MouseYRel(0), MouseButtonStates(0),
Width(param.WindowSize.Width), Height(param.WindowSize.Height),
Resizable(param.WindowResizable == 1 ? true : false), CurrentTouchCount(0)
Resizable(param.WindowResizable == 1 ? true : false), CurrentTouchCount(0),
IsInBackground(false)
{
#ifdef _DEBUG
setDebugName("CIrrDeviceSDL");
#endif
if (++SDLDeviceInstances == 1) {
#ifdef __ANDROID__
// Blocking on pause causes problems with multiplayer.
// See https://github.com/minetest/minetest/issues/10842.
SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE, "0");
SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO, "0");
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
// Minetest does its own screen keyboard handling.
SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "0");
#endif
// Minetest has its own code to synthesize mouse events from touch events,
// so we prevent SDL from doing it.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
u32 flags = SDL_INIT_TIMER | SDL_INIT_EVENTS;
if (CreationParams.DriverType != video::EDT_NULL)
flags |= SDL_INIT_VIDEO;
@ -273,11 +291,6 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters &param) :
}
}
// Minetest has its own code to synthesize mouse events from touch events,
// so we prevent SDL from doing it.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
// create keymap
createKeyMap();
@ -448,9 +461,13 @@ bool CIrrDeviceSDL::createWindow()
default:;
}
/*
Makes context creation fail on some Android devices.
See discussion in https://github.com/minetest/minetest/pull/14498.
#ifdef _DEBUG
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG | SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG);
#endif
*/
if (CreationParams.Bits == 16) {
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
@ -510,6 +527,14 @@ bool CIrrDeviceSDL::createWindow()
return false;
}
// Update Width and Height to match the actual window size.
// In fullscreen mode, the window size specified in SIrrlichtCreationParameters
// is ignored, so we cannot rely on it.
int w = 0, h = 0;
SDL_GetWindowSize(Window, &w, &h);
Width = w;
Height = h;
return true;
#endif // !_IRR_EMSCRIPTEN_PLATFORM_
}
@ -621,7 +646,17 @@ bool CIrrDeviceSDL::run()
}
#endif
switch (SDL_event.button.button) {
auto button = SDL_event.button.button;
#ifdef __ANDROID__
// Android likes to send the right mouse button as the back button.
// According to some web searches I did, this is probably
// vendor/device-specific.
// Since a working right mouse button is very important for
// Minetest, we have this little hack.
if (button == SDL_BUTTON_X2)
button = SDL_BUTTON_RIGHT;
#endif
switch (button) {
case SDL_BUTTON_LEFT:
if (SDL_event.type == SDL_MOUSEBUTTONDOWN) {
irrevent.MouseInput.Event = irr::EMIE_LMOUSE_PRESSED_DOWN;
@ -772,6 +807,20 @@ bool CIrrDeviceSDL::run()
postEventFromUser(irrevent);
break;
// Contrary to what the SDL documentation says, SDL_APP_WILLENTERBACKGROUND
// and SDL_APP_WILLENTERFOREGROUND are actually sent in onStop/onStart,
// not onPause/onResume, on recent Android versions. This can be verified
// by testing or by looking at the org.libsdl.app.SDLActivity Java code.
// -> This means we can use them to implement isWindowVisible().
case SDL_APP_WILLENTERBACKGROUND:
IsInBackground = true;
break;
case SDL_APP_WILLENTERFOREGROUND:
IsInBackground = false;
break;
default:
break;
} // end switch
@ -1053,6 +1102,11 @@ bool CIrrDeviceSDL::isFullscreen() const
#endif
}
bool CIrrDeviceSDL::isWindowVisible() const
{
return !IsInBackground;
}
//! returns if window is active. if not, nothing need to be drawn
bool CIrrDeviceSDL::isWindowActive() const
{
@ -1111,6 +1165,8 @@ void CIrrDeviceSDL::createKeyMap()
// buttons missing
KeyMap.push_back(SKeyMap(SDLK_AC_BACK, KEY_CANCEL));
KeyMap.push_back(SKeyMap(SDLK_BACKSPACE, KEY_BACK));
KeyMap.push_back(SKeyMap(SDLK_TAB, KEY_TAB));
KeyMap.push_back(SKeyMap(SDLK_CLEAR, KEY_CLEAR));

View File

@ -86,6 +86,9 @@ public:
/** \return True if window is fullscreen. */
bool isFullscreen() const override;
//! Checks if the window could possibly be visible.
bool isWindowVisible() const override;
//! Get the position of this window on screen
core::position2di getWindowPosition() override;
@ -319,6 +322,7 @@ private:
SDL_SysWMinfo Info;
s32 CurrentTouchCount;
bool IsInBackground;
};
} // end namespace irr

View File

@ -1,4 +1,4 @@
if(NOT ANDROID AND NOT APPLE)
if(NOT APPLE)
set(DEFAULT_SDL2 ON)
endif()
@ -77,10 +77,9 @@ elseif(APPLE)
set(DEVICE "OSX")
elseif(ANDROID)
add_definitions(-D_IRR_ANDROID_PLATFORM_)
if(USE_SDL2)
message(FATAL_ERROR "SDL2 device is not (yet) supported on Android")
if(NOT USE_SDL2)
message(FATAL_ERROR "The Android build requires SDL2")
endif()
set(DEVICE "ANDROID")
elseif(EMSCRIPTEN)
add_definitions(-D_IRR_EMSCRIPTEN_PLATFORM_ -D_IRR_COMPILE_WITH_EGL_MANAGER_)
set(LINUX_PLATFORM TRUE)
@ -131,7 +130,10 @@ endif()
# OpenGL
if(USE_SDL2)
option(ENABLE_OPENGL3 "Enable OpenGL 3+" TRUE)
if(NOT ANDROID)
set(DEFAULT_OPENGL3 TRUE)
endif()
option(ENABLE_OPENGL3 "Enable OpenGL 3+" ${DEFAULT_OPENGL3})
else()
set(ENABLE_OPENGL3 FALSE)
endif()
@ -142,13 +144,10 @@ else()
option(ENABLE_OPENGL "Enable OpenGL" TRUE)
endif()
if(EMSCRIPTEN OR APPLE)
if(USE_SDL2 OR EMSCRIPTEN OR APPLE)
set(ENABLE_GLES1 FALSE)
else()
if(ANDROID)
set(DEFAULT_GLES1 TRUE)
endif()
option(ENABLE_GLES1 "Enable OpenGL ES" ${DEFAULT_GLES1})
option(ENABLE_GLES1 "Enable OpenGL ES" FALSE)
endif()
if(APPLE)
@ -188,19 +187,16 @@ if(ENABLE_OPENGL3)
endif()
if(ENABLE_GLES1)
if (USE_SDL2)
message(FATAL_ERROR "OpenGL ES 1 is not supported with SDL2")
endif()
add_definitions(-D_IRR_COMPILE_WITH_OGLES1_)
set(OPENGLES_DIRECT_LINK TRUE)
if(DEVICE MATCHES "^(WINDOWS|X11|ANDROID)$")
if(DEVICE MATCHES "^(WINDOWS|X11)$")
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
endif()
endif()
if(ENABLE_GLES2)
add_definitions(-D_IRR_COMPILE_WITH_OGLES2_)
if(DEVICE MATCHES "^(WINDOWS|X11|ANDROID)$" OR EMSCRIPTEN)
if(DEVICE MATCHES "^(WINDOWS|X11)$" OR EMSCRIPTEN)
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
endif()
endif()
@ -248,11 +244,15 @@ endif()
if(ENABLE_GLES2)
find_package(OpenGLES2 REQUIRED)
endif()
if(ENABLE_OPENGL OR ENABLE_OPENGL3)
if(ENABLE_OPENGL)
find_package(OpenGL REQUIRED)
endif()
if(USE_SDL2)
find_package(SDL2 REQUIRED)
if(NOT ANDROID)
find_package(SDL2 REQUIRED)
else()
# provided by MinetestAndroidLibs.cmake
endif()
message(STATUS "Found SDL2: ${SDL2_LIBRARIES}")
# unfortunately older SDL does not provide its version to cmake, so check header.
@ -323,7 +323,6 @@ set(link_includes
${OPENGLES2_INCLUDE_DIR}
${EGL_INCLUDE_DIR}
"$<$<PLATFORM_ID:Android>:${ANDROID_NDK}/sources/android/native_app_glue>"
"$<$<BOOL:${USE_X11}>:${X11_INCLUDE_DIR}>"
)
@ -453,14 +452,7 @@ if(ENABLE_OPENGL3)
target_compile_definitions(IRROTHEROBJ PRIVATE ENABLE_OPENGL3)
endif()
if(ANDROID)
target_sources(IRROTHEROBJ PRIVATE
Android/CIrrDeviceAndroid.cpp
Android/CAndroidAssetReader.cpp
Android/CAndroidAssetFileArchive.cpp
Android/CKeyEventWrapper.cpp
)
elseif(APPLE)
if(APPLE)
# Build all IRROTHEROBJ sources as objc++, including the .cpp's
set_target_properties(IRROTHEROBJ PROPERTIES COMPILE_OPTIONS "-xobjective-c++")
target_sources(IRROTHEROBJ PRIVATE
@ -535,7 +527,8 @@ target_link_libraries(IrrlichtMt PRIVATE
"$<$<BOOL:${OPENGLES_DIRECT_LINK}>:${OPENGLES_LIBRARY}>"
${EGL_LIBRARY}
"$<$<PLATFORM_ID:Android>:-landroid -llog>"
# incl. transitive SDL2 dependencies for static linking
"$<$<PLATFORM_ID:Android>:-landroid -llog -lGLESv2 -lGLESv1_CM -lOpenSLES>"
${COCOA_LIB}
${IOKIT_LIB}
"$<$<PLATFORM_ID:Windows>:gdi32>"

View File

@ -19,10 +19,6 @@
#include "CImage.h"
#include "os.h"
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
#include "android_native_app_glue.h"
#endif
namespace irr
{
namespace video

View File

@ -12,7 +12,7 @@
#include "SMaterial.h"
#include "fast_atof.h"
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) || defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
#include <EGL/egl.h>
#else
#include <GLES/egl.h>

View File

@ -28,10 +28,6 @@ static const char *const copyright = "Irrlicht Engine (c) 2002-2017 Nikolaus Geb
#include "CIrrDeviceSDL.h"
#endif
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
#include "Android/CIrrDeviceAndroid.h"
#endif
namespace irr
{
//! stub for calling createDeviceEx
@ -74,11 +70,6 @@ extern "C" IRRLICHT_API IrrlichtDevice *IRRCALLCONV createDeviceEx(const SIrrlic
dev = new CIrrDeviceLinux(params);
#endif
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
if (params.DeviceType == EIDT_ANDROID || (!dev && params.DeviceType == EIDT_BEST))
dev = new CIrrDeviceAndroid(params);
#endif
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
if (params.DeviceType == EIDT_SDL || (!dev && params.DeviceType == EIDT_BEST))
dev = new CIrrDeviceSDL(params);

View File

@ -21,10 +21,6 @@
#include "CImage.h"
#include "os.h"
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
#include "android_native_app_glue.h"
#endif
#include "mt_opengl.h"
namespace irr

View File

@ -307,10 +307,6 @@ else()
endif()
if (ANDROID)
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
add_library(native_app_glue OBJECT ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
set(PLATFORM_LIBS ${PLATFORM_LIBS} native_app_glue)
set(PLATFORM_LIBS ${PLATFORM_LIBS} android log)
endif()
endif()
@ -518,6 +514,9 @@ include_directories(SYSTEM
${GMP_INCLUDE_DIR}
${JSON_INCLUDE_DIR}
${LUA_BIT_INCLUDE_DIR}
# on Android, Minetest depends on SDL2 directly
# on other platforms, only IrrlichtMt depends on SDL2
"$<$<PLATFORM_ID:Android>:${SDL2_INCLUDE_DIRS}>"
)
if(USE_GETTEXT)
@ -562,6 +561,9 @@ if(BUILD_CLIENT)
${LUA_BIT_LIBRARY}
${FREETYPE_LIBRARY}
${PLATFORM_LIBS}
# on Android, Minetest depends on SDL2 directly
# on other platforms, only IrrlichtMt depends on SDL2
"$<$<PLATFORM_ID:Android>:${SDL2_LIBRARIES}>"
)
if(NOT USE_LUAJIT)
set_target_properties(${PROJECT_NAME} PROPERTIES

View File

@ -2258,9 +2258,11 @@ void Game::openConsole(float scale, const wchar_t *line)
assert(scale > 0.0f && scale <= 1.0f);
#ifdef __ANDROID__
porting::showTextInputDialog("", "", 2);
m_android_chat_open = true;
#else
if (!porting::hasPhysicalKeyboardAndroid()) {
porting::showTextInputDialog("", "", 2);
m_android_chat_open = true;
} else {
#endif
if (gui_chat_console->isOpenInhibited())
return;
gui_chat_console->openConsole(scale);
@ -2268,6 +2270,8 @@ void Game::openConsole(float scale, const wchar_t *line)
gui_chat_console->setCloseOnEnter(true);
gui_chat_console->replaceAndAddToHistory(line);
}
#ifdef __ANDROID__
} // else
#endif
}

View File

@ -229,9 +229,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
params.Stencilbuffer = false;
params.Vsync = vsync;
params.EventReceiver = receiver;
#ifdef __ANDROID__
params.PrivateData = porting::app_global;
#endif
// there is no standardized path for these on desktop
std::string rel_path = std::string("client") + DIR_DELIM
+ "shaders" + DIR_DELIM + "Irrlicht";

View File

@ -239,7 +239,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
#ifdef __ANDROID__
// display software keyboard when clicking edit boxes
if (event.EventType == EET_MOUSE_INPUT_EVENT &&
event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN &&
!porting::hasPhysicalKeyboardAndroid()) {
gui::IGUIElement *hovered =
Environment->getRootGUIElement()->getElementFromPoint(
core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));

View File

@ -29,6 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/numeric.h"
#include "log.h"
#ifdef __ANDROID__
#include <android/log.h>
#endif
#include <sstream>
#include <iostream>
#include <algorithm>

View File

@ -30,6 +30,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "log.h"
#include "settings.h"
#include <jni.h>
#define SDL_MAIN_HANDLED 1
#include <SDL.h>
#include <sstream>
#include <exception>
#include <cstdlib>
@ -53,10 +57,8 @@ namespace porting {
bool setSystemPaths(); // used in porting.cpp
}
void android_main(android_app *app)
extern "C" int SDL_Main(int _argc, char *_argv[])
{
porting::app_global = app;
Thread::setName("Main");
char *argv[] = {strdup(PROJECT_NAME), strdup("--verbose"), nullptr};
@ -70,45 +72,15 @@ void android_main(android_app *app)
}
namespace porting {
android_app *app_global = nullptr;
JNIEnv *jnienv = nullptr;
jclass nativeActivity;
jclass findClass(const std::string &classname)
{
if (jnienv == nullptr)
return nullptr;
jclass nativeactivity = jnienv->FindClass("android/app/NativeActivity");
jmethodID getClassLoader = jnienv->GetMethodID(
nativeactivity, "getClassLoader", "()Ljava/lang/ClassLoader;");
jobject cls = jnienv->CallObjectMethod(
app_global->activity->clazz, getClassLoader);
jclass classLoader = jnienv->FindClass("java/lang/ClassLoader");
jmethodID findClass = jnienv->GetMethodID(classLoader, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
jstring strClassName = jnienv->NewStringUTF(classname.c_str());
return (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName);
}
jobject activity;
jclass activityClass;
void osSpecificInit()
{
JavaVM *jvm = app_global->activity->vm;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = PROJECT_NAME_C "NativeThread";
lJavaVMAttachArgs.group = nullptr;
if (jvm->AttachCurrentThread(&porting::jnienv, &lJavaVMAttachArgs) == JNI_ERR) {
errorstream << "Failed to attach native thread to jvm" << std::endl;
exit(-1);
}
nativeActivity = findClass("net/minetest/minetest/GameActivity");
if (nativeActivity == nullptr)
errorstream <<
"porting::initAndroid unable to find Java native activity class" <<
std::endl;
jnienv = (JNIEnv*)SDL_AndroidGetJNIEnv();
activity = (jobject)SDL_AndroidGetActivity();
activityClass = jnienv->GetObjectClass(activity);
// Set default language
auto lang = getLanguageAndroid();
@ -129,9 +101,6 @@ void cleanupAndroid()
setenv("CPUPROFILE", (path_user + DIR_DELIM + "gmon.out").c_str(), 1);
moncleanup();
#endif
JavaVM *jvm = app_global->activity->vm;
jvm->DetachCurrentThread();
}
static std::string readJavaString(jstring j_str)
@ -149,11 +118,11 @@ bool setSystemPaths()
{
// Set user and share paths
{
jmethodID getUserDataPath = jnienv->GetMethodID(nativeActivity,
jmethodID getUserDataPath = jnienv->GetMethodID(activityClass,
"getUserDataPath", "()Ljava/lang/String;");
FATAL_ERROR_IF(getUserDataPath==nullptr,
"porting::initializePathsAndroid unable to find Java getUserDataPath method");
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getUserDataPath);
jobject result = jnienv->CallObjectMethod(activity, getUserDataPath);
std::string str = readJavaString((jstring) result);
path_user = str;
path_share = str;
@ -161,11 +130,11 @@ bool setSystemPaths()
// Set cache path
{
jmethodID getCachePath = jnienv->GetMethodID(nativeActivity,
jmethodID getCachePath = jnienv->GetMethodID(activityClass,
"getCachePath", "()Ljava/lang/String;");
FATAL_ERROR_IF(getCachePath==nullptr,
"porting::initializePathsAndroid unable to find Java getCachePath method");
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getCachePath);
jobject result = jnienv->CallObjectMethod(activity, getCachePath);
path_cache = readJavaString((jstring) result);
}
@ -174,7 +143,7 @@ bool setSystemPaths()
void showTextInputDialog(const std::string &hint, const std::string &current, int editType)
{
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showTextInputDialog",
jmethodID showdialog = jnienv->GetMethodID(activityClass, "showTextInputDialog",
"(Ljava/lang/String;Ljava/lang/String;I)V");
FATAL_ERROR_IF(showdialog == nullptr,
@ -184,13 +153,13 @@ void showTextInputDialog(const std::string &hint, const std::string &current, in
jstring jcurrent = jnienv->NewStringUTF(current.c_str());
jint jeditType = editType;
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog,
jnienv->CallVoidMethod(activity, showdialog,
jhint, jcurrent, jeditType);
}
void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 selectedIdx)
{
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showSelectionInputDialog",
jmethodID showdialog = jnienv->GetMethodID(activityClass, "showSelectionInputDialog",
"([Ljava/lang/String;I)V");
FATAL_ERROR_IF(showdialog == nullptr,
@ -205,79 +174,79 @@ void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 select
jnienv->NewStringUTF(optionList[i].c_str()));
}
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, jOptionList,
jnienv->CallVoidMethod(activity, showdialog, jOptionList,
jselectedIdx);
}
void openURIAndroid(const char *url)
{
jmethodID url_open = jnienv->GetMethodID(nativeActivity, "openURI",
jmethodID url_open = jnienv->GetMethodID(activityClass, "openURI",
"(Ljava/lang/String;)V");
FATAL_ERROR_IF(url_open == nullptr,
"porting::openURIAndroid unable to find Java openURI method");
jstring jurl = jnienv->NewStringUTF(url);
jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
jnienv->CallVoidMethod(activity, url_open, jurl);
}
void shareFileAndroid(const std::string &path)
{
jmethodID url_open = jnienv->GetMethodID(nativeActivity, "shareFile",
jmethodID url_open = jnienv->GetMethodID(activityClass, "shareFile",
"(Ljava/lang/String;)V");
FATAL_ERROR_IF(url_open == nullptr,
"porting::shareFileAndroid unable to find Java shareFile method");
jstring jurl = jnienv->NewStringUTF(path.c_str());
jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
jnienv->CallVoidMethod(activity, url_open, jurl);
}
AndroidDialogType getLastInputDialogType()
{
jmethodID lastdialogtype = jnienv->GetMethodID(nativeActivity,
jmethodID lastdialogtype = jnienv->GetMethodID(activityClass,
"getLastDialogType", "()I");
FATAL_ERROR_IF(lastdialogtype == nullptr,
"porting::getLastInputDialogType unable to find Java getLastDialogType method");
int dialogType = jnienv->CallIntMethod(app_global->activity->clazz, lastdialogtype);
int dialogType = jnienv->CallIntMethod(activity, lastdialogtype);
return static_cast<AndroidDialogType>(dialogType);
}
AndroidDialogState getInputDialogState()
{
jmethodID inputdialogstate = jnienv->GetMethodID(nativeActivity,
jmethodID inputdialogstate = jnienv->GetMethodID(activityClass,
"getInputDialogState", "()I");
FATAL_ERROR_IF(inputdialogstate == nullptr,
"porting::getInputDialogState unable to find Java getInputDialogState method");
int dialogState = jnienv->CallIntMethod(app_global->activity->clazz, inputdialogstate);
int dialogState = jnienv->CallIntMethod(activity, inputdialogstate);
return static_cast<AndroidDialogState>(dialogState);
}
std::string getInputDialogMessage()
{
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity,
jmethodID dialogvalue = jnienv->GetMethodID(activityClass,
"getDialogMessage", "()Ljava/lang/String;");
FATAL_ERROR_IF(dialogvalue == nullptr,
"porting::getInputDialogMessage unable to find Java getDialogMessage method");
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
jobject result = jnienv->CallObjectMethod(activity,
dialogvalue);
return readJavaString((jstring) result);
}
int getInputDialogSelection()
{
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, "getDialogSelection", "()I");
jmethodID dialogvalue = jnienv->GetMethodID(activityClass, "getDialogSelection", "()I");
FATAL_ERROR_IF(dialogvalue == nullptr,
"porting::getInputDialogSelection unable to find Java getDialogSelection method");
return jnienv->CallIntMethod(app_global->activity->clazz, dialogvalue);
return jnienv->CallIntMethod(activity, dialogvalue);
}
#ifndef SERVER
@ -287,13 +256,13 @@ float getDisplayDensity()
static float value = 0;
if (firstrun) {
jmethodID getDensity = jnienv->GetMethodID(nativeActivity,
jmethodID getDensity = jnienv->GetMethodID(activityClass,
"getDensity", "()F");
FATAL_ERROR_IF(getDensity == nullptr,
"porting::getDisplayDensity unable to find Java getDensity method");
value = jnienv->CallFloatMethod(app_global->activity->clazz, getDensity);
value = jnienv->CallFloatMethod(activity, getDensity);
firstrun = false;
}
@ -306,22 +275,22 @@ v2u32 getDisplaySize()
static v2u32 retval;
if (firstrun) {
jmethodID getDisplayWidth = jnienv->GetMethodID(nativeActivity,
jmethodID getDisplayWidth = jnienv->GetMethodID(activityClass,
"getDisplayWidth", "()I");
FATAL_ERROR_IF(getDisplayWidth == nullptr,
"porting::getDisplayWidth unable to find Java getDisplayWidth method");
retval.X = jnienv->CallIntMethod(app_global->activity->clazz,
retval.X = jnienv->CallIntMethod(activity,
getDisplayWidth);
jmethodID getDisplayHeight = jnienv->GetMethodID(nativeActivity,
jmethodID getDisplayHeight = jnienv->GetMethodID(activityClass,
"getDisplayHeight", "()I");
FATAL_ERROR_IF(getDisplayHeight == nullptr,
"porting::getDisplayHeight unable to find Java getDisplayHeight method");
retval.Y = jnienv->CallIntMethod(app_global->activity->clazz,
retval.Y = jnienv->CallIntMethod(activity,
getDisplayHeight);
firstrun = false;
@ -332,16 +301,29 @@ v2u32 getDisplaySize()
std::string getLanguageAndroid()
{
jmethodID getLanguage = jnienv->GetMethodID(nativeActivity,
jmethodID getLanguage = jnienv->GetMethodID(activityClass,
"getLanguage", "()Ljava/lang/String;");
FATAL_ERROR_IF(getLanguage == nullptr,
"porting::getLanguageAndroid unable to find Java getLanguage method");
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
jobject result = jnienv->CallObjectMethod(activity,
getLanguage);
return readJavaString((jstring) result);
}
bool hasPhysicalKeyboardAndroid()
{
jmethodID hasPhysicalKeyboard = jnienv->GetMethodID(activityClass,
"hasPhysicalKeyboard", "()Z");
FATAL_ERROR_IF(hasPhysicalKeyboard == nullptr,
"porting::hasPhysicalKeyboardAndroid unable to find Java hasPhysicalKeyboard method");
jboolean result = jnienv->CallBooleanMethod(activity,
hasPhysicalKeyboard);
return result;
}
#endif // ndef SERVER
}

View File

@ -23,21 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#error This header has to be included on Android port only!
#endif
#include <jni.h>
#include <android_native_app_glue.h>
#include <android/log.h>
#include "irrlichttypes_bloated.h"
#include <string>
namespace porting {
// Java app
extern android_app *app_global;
// Java <-> C++ interaction interface
extern JNIEnv *jnienv;
/**
* Show a text input dialog in Java
* @param hint Hint to be shown
@ -105,6 +94,9 @@ std::string getInputDialogMessage();
*/
int getInputDialogSelection();
bool hasPhysicalKeyboardAndroid();
#ifndef SERVER
float getDisplayDensity();
v2u32 getDisplaySize();