summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/SCsub4
-rw-r--r--platform/android/android_keys_utils.cpp (renamed from platform/osx/semaphore_osx.h)36
-rw-r--r--platform/android/android_keys_utils.h280
-rw-r--r--platform/android/audio_driver_jandroid.cpp13
-rw-r--r--platform/android/audio_driver_jandroid.h2
-rw-r--r--platform/android/audio_driver_opensl.cpp18
-rw-r--r--platform/android/audio_driver_opensl.h2
-rw-r--r--platform/android/detect.py1
-rw-r--r--platform/android/dir_access_jandroid.cpp2
-rw-r--r--platform/android/dir_access_jandroid.h2
-rw-r--r--platform/android/export/export.cpp296
-rw-r--r--platform/android/java/app/AndroidManifest.xml25
-rw-r--r--platform/android/java/app/build.gradle68
-rw-r--r--platform/android/java/app/config.gradle72
-rw-r--r--platform/android/java/build.gradle97
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--platform/android/java/lib/build.gradle2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java179
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java18
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java12
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java97
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java67
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java9
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java256
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java199
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt99
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt136
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt230
-rw-r--r--platform/android/java/plugins/godotpayment/build.gradle31
-rw-r--r--platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml11
-rw-r--r--platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java (renamed from platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java)80
-rw-r--r--platform/android/java/settings.gradle1
-rw-r--r--platform/android/java_godot_lib_jni.cpp1038
-rw-r--r--platform/android/java_godot_lib_jni.h58
-rw-r--r--platform/android/java_godot_wrapper.cpp10
-rw-r--r--platform/android/java_godot_wrapper.h2
-rw-r--r--platform/android/jni_utils.cpp434
-rw-r--r--platform/android/jni_utils.h242
-rw-r--r--platform/android/os_android.cpp36
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp115
-rw-r--r--platform/android/plugin/godot_plugin_jni.h (renamed from platform/iphone/semaphore_iphone.h)38
-rw-r--r--platform/android/vulkan/vk_renderer_jni.cpp (renamed from platform/osx/semaphore_osx.cpp)79
-rw-r--r--platform/android/vulkan/vk_renderer_jni.h (renamed from platform/iphone/semaphore_iphone.cpp)92
-rw-r--r--platform/haiku/audio_driver_media_kit.cpp11
-rw-r--r--platform/haiku/audio_driver_media_kit.h2
-rw-r--r--platform/haiku/haiku_direct_window.cpp8
-rw-r--r--platform/iphone/os_iphone.cpp5
-rw-r--r--platform/javascript/SCsub41
-rw-r--r--platform/javascript/audio_driver_javascript.cpp93
-rw-r--r--platform/javascript/audio_driver_javascript.h1
-rw-r--r--platform/javascript/detect.py116
-rw-r--r--platform/javascript/dom_keys.inc2
-rw-r--r--platform/javascript/emscripten_helpers.py37
-rw-r--r--platform/javascript/engine.js411
-rw-r--r--platform/javascript/engine/engine.js184
-rw-r--r--platform/javascript/engine/externs.js3
-rw-r--r--platform/javascript/engine/loader.js33
-rw-r--r--platform/javascript/engine/preloader.js139
-rw-r--r--platform/javascript/engine/utils.js69
-rw-r--r--platform/javascript/export/export.cpp35
-rw-r--r--platform/javascript/http_client.h.inc2
-rw-r--r--platform/javascript/http_client_javascript.cpp23
-rw-r--r--platform/javascript/id_handler.js2
-rw-r--r--platform/javascript/javascript_eval.cpp10
-rw-r--r--platform/javascript/os_javascript.cpp46
-rw-r--r--platform/javascript/pre.js5
-rw-r--r--platform/osx/SCsub1
-rw-r--r--platform/osx/joypad_osx.cpp20
-rw-r--r--platform/osx/joypad_osx.h3
-rw-r--r--platform/osx/os_osx.h3
-rw-r--r--platform/osx/os_osx.mm39
-rw-r--r--platform/server/SCsub1
-rw-r--r--platform/server/detect.py4
-rw-r--r--platform/server/os_server.cpp4
-rw-r--r--platform/uwp/app.cpp6
-rw-r--r--platform/uwp/os_uwp.cpp5
-rw-r--r--platform/uwp/os_uwp.h3
-rw-r--r--platform/windows/key_mapping_windows.cpp164
-rw-r--r--platform/windows/key_mapping_windows.h1
-rw-r--r--platform/windows/os_windows.cpp24
-rw-r--r--platform/x11/detect.py5
-rw-r--r--platform/x11/detect_prime.cpp1
-rw-r--r--platform/x11/joypad_linux.cpp28
-rw-r--r--platform/x11/joypad_linux.h2
-rw-r--r--platform/x11/key_mapping_x11.cpp134
-rw-r--r--platform/x11/key_mapping_x11.h1
-rw-r--r--platform/x11/os_x11.cpp48
89 files changed, 3762 insertions, 2530 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub
index fd2a774c71..46f0703a65 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -18,6 +18,10 @@ android_files = [
'java_class_wrapper.cpp',
'java_godot_wrapper.cpp',
'java_godot_io_wrapper.cpp',
+ 'jni_utils.cpp',
+ 'android_keys_utils.cpp',
+ 'vulkan/vk_renderer_jni.cpp',
+ 'plugin/godot_plugin_jni.cpp'
]
env_android = env.Clone()
diff --git a/platform/osx/semaphore_osx.h b/platform/android/android_keys_utils.cpp
index 9aa2b47bc8..88874ba2c7 100644
--- a/platform/osx/semaphore_osx.h
+++ b/platform/android/android_keys_utils.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* semaphore_osx.h */
+/* android_keys_utils.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,32 +28,16 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef SEMAPHORE_OSX_H
-#define SEMAPHORE_OSX_H
+#include "android_keys_utils.h"
-struct cgsem {
- int pipefd[2];
-};
+unsigned int android_get_keysym(unsigned int p_code) {
+ for (int i = 0; _ak_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
-typedef struct cgsem cgsem_t;
+ if (_ak_to_keycode[i].keycode == p_code) {
-#include "core/os/semaphore.h"
+ return _ak_to_keycode[i].keysym;
+ }
+ }
-class SemaphoreOSX : public SemaphoreOld {
-
- mutable cgsem_t sem;
-
- static SemaphoreOld *create_semaphore_osx();
-
-public:
- virtual Error wait();
- virtual Error post();
- virtual int get() const;
-
- static void make_default();
- SemaphoreOSX();
-
- ~SemaphoreOSX();
-};
-
-#endif // SEMAPHORE_OSX_H
+ return KEY_UNKNOWN;
+}
diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h
new file mode 100644
index 0000000000..f076688ac8
--- /dev/null
+++ b/platform/android/android_keys_utils.h
@@ -0,0 +1,280 @@
+/*************************************************************************/
+/* android_keys_utils.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef ANDROID_KEYS_UTILS_H
+#define ANDROID_KEYS_UTILS_H
+
+#include <core/os/keyboard.h>
+
+/*
+ * Android Key codes.
+ */
+enum {
+ AKEYCODE_UNKNOWN = 0,
+ AKEYCODE_SOFT_LEFT = 1,
+ AKEYCODE_SOFT_RIGHT = 2,
+ AKEYCODE_HOME = 3,
+ AKEYCODE_BACK = 4,
+ AKEYCODE_CALL = 5,
+ AKEYCODE_ENDCALL = 6,
+ AKEYCODE_0 = 7,
+ AKEYCODE_1 = 8,
+ AKEYCODE_2 = 9,
+ AKEYCODE_3 = 10,
+ AKEYCODE_4 = 11,
+ AKEYCODE_5 = 12,
+ AKEYCODE_6 = 13,
+ AKEYCODE_7 = 14,
+ AKEYCODE_8 = 15,
+ AKEYCODE_9 = 16,
+ AKEYCODE_STAR = 17,
+ AKEYCODE_POUND = 18,
+ AKEYCODE_DPAD_UP = 19,
+ AKEYCODE_DPAD_DOWN = 20,
+ AKEYCODE_DPAD_LEFT = 21,
+ AKEYCODE_DPAD_RIGHT = 22,
+ AKEYCODE_DPAD_CENTER = 23,
+ AKEYCODE_VOLUME_UP = 24,
+ AKEYCODE_VOLUME_DOWN = 25,
+ AKEYCODE_POWER = 26,
+ AKEYCODE_CAMERA = 27,
+ AKEYCODE_CLEAR = 28,
+ AKEYCODE_A = 29,
+ AKEYCODE_B = 30,
+ AKEYCODE_C = 31,
+ AKEYCODE_D = 32,
+ AKEYCODE_E = 33,
+ AKEYCODE_F = 34,
+ AKEYCODE_G = 35,
+ AKEYCODE_H = 36,
+ AKEYCODE_I = 37,
+ AKEYCODE_J = 38,
+ AKEYCODE_K = 39,
+ AKEYCODE_L = 40,
+ AKEYCODE_M = 41,
+ AKEYCODE_N = 42,
+ AKEYCODE_O = 43,
+ AKEYCODE_P = 44,
+ AKEYCODE_Q = 45,
+ AKEYCODE_R = 46,
+ AKEYCODE_S = 47,
+ AKEYCODE_T = 48,
+ AKEYCODE_U = 49,
+ AKEYCODE_V = 50,
+ AKEYCODE_W = 51,
+ AKEYCODE_X = 52,
+ AKEYCODE_Y = 53,
+ AKEYCODE_Z = 54,
+ AKEYCODE_COMMA = 55,
+ AKEYCODE_PERIOD = 56,
+ AKEYCODE_ALT_LEFT = 57,
+ AKEYCODE_ALT_RIGHT = 58,
+ AKEYCODE_SHIFT_LEFT = 59,
+ AKEYCODE_SHIFT_RIGHT = 60,
+ AKEYCODE_TAB = 61,
+ AKEYCODE_SPACE = 62,
+ AKEYCODE_SYM = 63,
+ AKEYCODE_EXPLORER = 64,
+ AKEYCODE_ENVELOPE = 65,
+ AKEYCODE_ENTER = 66,
+ AKEYCODE_DEL = 67,
+ AKEYCODE_GRAVE = 68,
+ AKEYCODE_MINUS = 69,
+ AKEYCODE_EQUALS = 70,
+ AKEYCODE_LEFT_BRACKET = 71,
+ AKEYCODE_RIGHT_BRACKET = 72,
+ AKEYCODE_BACKSLASH = 73,
+ AKEYCODE_SEMICOLON = 74,
+ AKEYCODE_APOSTROPHE = 75,
+ AKEYCODE_SLASH = 76,
+ AKEYCODE_AT = 77,
+ AKEYCODE_NUM = 78,
+ AKEYCODE_HEADSETHOOK = 79,
+ AKEYCODE_FOCUS = 80, // *Camera* focus
+ AKEYCODE_PLUS = 81,
+ AKEYCODE_MENU = 82,
+ AKEYCODE_NOTIFICATION = 83,
+ AKEYCODE_SEARCH = 84,
+ AKEYCODE_MEDIA_PLAY_PAUSE = 85,
+ AKEYCODE_MEDIA_STOP = 86,
+ AKEYCODE_MEDIA_NEXT = 87,
+ AKEYCODE_MEDIA_PREVIOUS = 88,
+ AKEYCODE_MEDIA_REWIND = 89,
+ AKEYCODE_MEDIA_FAST_FORWARD = 90,
+ AKEYCODE_MUTE = 91,
+ AKEYCODE_PAGE_UP = 92,
+ AKEYCODE_PAGE_DOWN = 93,
+ AKEYCODE_PICTSYMBOLS = 94,
+ AKEYCODE_SWITCH_CHARSET = 95,
+ AKEYCODE_BUTTON_A = 96,
+ AKEYCODE_BUTTON_B = 97,
+ AKEYCODE_BUTTON_C = 98,
+ AKEYCODE_BUTTON_X = 99,
+ AKEYCODE_BUTTON_Y = 100,
+ AKEYCODE_BUTTON_Z = 101,
+ AKEYCODE_BUTTON_L1 = 102,
+ AKEYCODE_BUTTON_R1 = 103,
+ AKEYCODE_BUTTON_L2 = 104,
+ AKEYCODE_BUTTON_R2 = 105,
+ AKEYCODE_BUTTON_THUMBL = 106,
+ AKEYCODE_BUTTON_THUMBR = 107,
+ AKEYCODE_BUTTON_START = 108,
+ AKEYCODE_BUTTON_SELECT = 109,
+ AKEYCODE_BUTTON_MODE = 110,
+
+ // NOTE: If you add a new keycode here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
+};
+
+struct _WinTranslatePair {
+
+ unsigned int keysym;
+ unsigned int keycode;
+};
+
+static _WinTranslatePair _ak_to_keycode[] = {
+ { KEY_TAB, AKEYCODE_TAB },
+ { KEY_ENTER, AKEYCODE_ENTER },
+ { KEY_SHIFT, AKEYCODE_SHIFT_LEFT },
+ { KEY_SHIFT, AKEYCODE_SHIFT_RIGHT },
+ { KEY_ALT, AKEYCODE_ALT_LEFT },
+ { KEY_ALT, AKEYCODE_ALT_RIGHT },
+ { KEY_MENU, AKEYCODE_MENU },
+ { KEY_PAUSE, AKEYCODE_MEDIA_PLAY_PAUSE },
+ { KEY_ESCAPE, AKEYCODE_BACK },
+ { KEY_SPACE, AKEYCODE_SPACE },
+ { KEY_PAGEUP, AKEYCODE_PAGE_UP },
+ { KEY_PAGEDOWN, AKEYCODE_PAGE_DOWN },
+ { KEY_HOME, AKEYCODE_HOME }, //(0x24)
+ { KEY_LEFT, AKEYCODE_DPAD_LEFT },
+ { KEY_UP, AKEYCODE_DPAD_UP },
+ { KEY_RIGHT, AKEYCODE_DPAD_RIGHT },
+ { KEY_DOWN, AKEYCODE_DPAD_DOWN },
+ { KEY_PERIODCENTERED, AKEYCODE_DPAD_CENTER },
+ { KEY_BACKSPACE, AKEYCODE_DEL },
+ { KEY_0, AKEYCODE_0 }, ////0 key
+ { KEY_1, AKEYCODE_1 }, ////1 key
+ { KEY_2, AKEYCODE_2 }, ////2 key
+ { KEY_3, AKEYCODE_3 }, ////3 key
+ { KEY_4, AKEYCODE_4 }, ////4 key
+ { KEY_5, AKEYCODE_5 }, ////5 key
+ { KEY_6, AKEYCODE_6 }, ////6 key
+ { KEY_7, AKEYCODE_7 }, ////7 key
+ { KEY_8, AKEYCODE_8 }, ////8 key
+ { KEY_9, AKEYCODE_9 }, ////9 key
+ { KEY_A, AKEYCODE_A }, ////A key
+ { KEY_B, AKEYCODE_B }, ////B key
+ { KEY_C, AKEYCODE_C }, ////C key
+ { KEY_D, AKEYCODE_D }, ////D key
+ { KEY_E, AKEYCODE_E }, ////E key
+ { KEY_F, AKEYCODE_F }, ////F key
+ { KEY_G, AKEYCODE_G }, ////G key
+ { KEY_H, AKEYCODE_H }, ////H key
+ { KEY_I, AKEYCODE_I }, ////I key
+ { KEY_J, AKEYCODE_J }, ////J key
+ { KEY_K, AKEYCODE_K }, ////K key
+ { KEY_L, AKEYCODE_L }, ////L key
+ { KEY_M, AKEYCODE_M }, ////M key
+ { KEY_N, AKEYCODE_N }, ////N key
+ { KEY_O, AKEYCODE_O }, ////O key
+ { KEY_P, AKEYCODE_P }, ////P key
+ { KEY_Q, AKEYCODE_Q }, ////Q key
+ { KEY_R, AKEYCODE_R }, ////R key
+ { KEY_S, AKEYCODE_S }, ////S key
+ { KEY_T, AKEYCODE_T }, ////T key
+ { KEY_U, AKEYCODE_U }, ////U key
+ { KEY_V, AKEYCODE_V }, ////V key
+ { KEY_W, AKEYCODE_W }, ////W key
+ { KEY_X, AKEYCODE_X }, ////X key
+ { KEY_Y, AKEYCODE_Y }, ////Y key
+ { KEY_Z, AKEYCODE_Z }, ////Z key
+ { KEY_HOMEPAGE, AKEYCODE_EXPLORER },
+ { KEY_LAUNCH0, AKEYCODE_BUTTON_A },
+ { KEY_LAUNCH1, AKEYCODE_BUTTON_B },
+ { KEY_LAUNCH2, AKEYCODE_BUTTON_C },
+ { KEY_LAUNCH3, AKEYCODE_BUTTON_X },
+ { KEY_LAUNCH4, AKEYCODE_BUTTON_Y },
+ { KEY_LAUNCH5, AKEYCODE_BUTTON_Z },
+ { KEY_LAUNCH6, AKEYCODE_BUTTON_L1 },
+ { KEY_LAUNCH7, AKEYCODE_BUTTON_R1 },
+ { KEY_LAUNCH8, AKEYCODE_BUTTON_L2 },
+ { KEY_LAUNCH9, AKEYCODE_BUTTON_R2 },
+ { KEY_LAUNCHA, AKEYCODE_BUTTON_THUMBL },
+ { KEY_LAUNCHB, AKEYCODE_BUTTON_THUMBR },
+ { KEY_LAUNCHC, AKEYCODE_BUTTON_START },
+ { KEY_LAUNCHD, AKEYCODE_BUTTON_SELECT },
+ { KEY_LAUNCHE, AKEYCODE_BUTTON_MODE },
+ { KEY_VOLUMEMUTE, AKEYCODE_MUTE },
+ { KEY_VOLUMEDOWN, AKEYCODE_VOLUME_DOWN },
+ { KEY_VOLUMEUP, AKEYCODE_VOLUME_UP },
+ { KEY_BACK, AKEYCODE_MEDIA_REWIND },
+ { KEY_FORWARD, AKEYCODE_MEDIA_FAST_FORWARD },
+ { KEY_MEDIANEXT, AKEYCODE_MEDIA_NEXT },
+ { KEY_MEDIAPREVIOUS, AKEYCODE_MEDIA_PREVIOUS },
+ { KEY_MEDIASTOP, AKEYCODE_MEDIA_STOP },
+ { KEY_PLUS, AKEYCODE_PLUS },
+ { KEY_EQUAL, AKEYCODE_EQUALS }, // the '+' key
+ { KEY_COMMA, AKEYCODE_COMMA }, // the ',' key
+ { KEY_MINUS, AKEYCODE_MINUS }, // the '-' key
+ { KEY_SLASH, AKEYCODE_SLASH }, // the '/?' key
+ { KEY_BACKSLASH, AKEYCODE_BACKSLASH },
+ { KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET },
+ { KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET },
+ { KEY_UNKNOWN, 0 }
+};
+/*
+TODO: map these android key:
+ AKEYCODE_SOFT_LEFT = 1,
+ AKEYCODE_SOFT_RIGHT = 2,
+ AKEYCODE_CALL = 5,
+ AKEYCODE_ENDCALL = 6,
+ AKEYCODE_STAR = 17,
+ AKEYCODE_POUND = 18,
+ AKEYCODE_POWER = 26,
+ AKEYCODE_CAMERA = 27,
+ AKEYCODE_CLEAR = 28,
+ AKEYCODE_SYM = 63,
+ AKEYCODE_ENVELOPE = 65,
+ AKEYCODE_GRAVE = 68,
+ AKEYCODE_SEMICOLON = 74,
+ AKEYCODE_APOSTROPHE = 75,
+ AKEYCODE_AT = 77,
+ AKEYCODE_NUM = 78,
+ AKEYCODE_HEADSETHOOK = 79,
+ AKEYCODE_FOCUS = 80, // *Camera* focus
+ AKEYCODE_NOTIFICATION = 83,
+ AKEYCODE_SEARCH = 84,
+ AKEYCODE_PICTSYMBOLS = 94,
+ AKEYCODE_SWITCH_CHARSET = 95,
+*/
+
+unsigned int android_get_keysym(unsigned int p_code);
+
+#endif // ANDROID_KEYS_UTILS_H
diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp
index 5a8e3b94da..e94dad9ac6 100644
--- a/platform/android/audio_driver_jandroid.cpp
+++ b/platform/android/audio_driver_jandroid.cpp
@@ -48,7 +48,7 @@ int AudioDriverAndroid::mix_rate = 44100;
bool AudioDriverAndroid::quit = false;
jobject AudioDriverAndroid::audioBuffer = NULL;
void *AudioDriverAndroid::audioBufferPinned = NULL;
-Mutex *AudioDriverAndroid::mutex = NULL;
+Mutex AudioDriverAndroid::mutex;
int32_t *AudioDriverAndroid::audioBuffer32 = NULL;
const char *AudioDriverAndroid::get_name() const {
@@ -58,7 +58,6 @@ const char *AudioDriverAndroid::get_name() const {
Error AudioDriverAndroid::init() {
- mutex = Mutex::create();
/*
// TODO: pass in/return a (Java) device ID, also whether we're opening for input or output
this->spec.samples = Android_JNI_OpenAudioDevice(this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples);
@@ -133,7 +132,7 @@ void AudioDriverAndroid::thread_func(JNIEnv *env) {
int16_t *ptr = (int16_t *)audioBufferPinned;
int fc = audioBufferFrames;
- if (!s_ad->active || mutex->try_lock() != OK) {
+ if (!s_ad->active || mutex.try_lock() != OK) {
for (int i = 0; i < fc; i++) {
ptr[i] = 0;
@@ -143,7 +142,7 @@ void AudioDriverAndroid::thread_func(JNIEnv *env) {
s_ad->audio_server_process(fc / 2, audioBuffer32);
- mutex->unlock();
+ mutex.unlock();
for (int i = 0; i < fc; i++) {
@@ -167,14 +166,12 @@ AudioDriver::SpeakerMode AudioDriverAndroid::get_speaker_mode() const {
void AudioDriverAndroid::lock() {
- if (mutex)
- mutex->lock();
+ mutex.lock();
}
void AudioDriverAndroid::unlock() {
- if (mutex)
- mutex->unlock();
+ mutex.unlock();
}
void AudioDriverAndroid::finish() {
diff --git a/platform/android/audio_driver_jandroid.h b/platform/android/audio_driver_jandroid.h
index d3d1641c20..b1cc3f9aa0 100644
--- a/platform/android/audio_driver_jandroid.h
+++ b/platform/android/audio_driver_jandroid.h
@@ -37,7 +37,7 @@
class AudioDriverAndroid : public AudioDriver {
- static Mutex *mutex;
+ static Mutex mutex;
static AudioDriverAndroid *s_ad;
static jobject io;
static jmethodID _init_audio;
diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp
index 6e9864c803..222120f81f 100644
--- a/platform/android/audio_driver_opensl.cpp
+++ b/platform/android/audio_driver_opensl.cpp
@@ -44,8 +44,8 @@ void AudioDriverOpenSL::_buffer_callback(
if (pause) {
mix = false;
- } else if (mutex) {
- mix = mutex->try_lock() == OK;
+ } else {
+ mix = mutex.try_lock() == OK;
}
if (mix) {
@@ -58,8 +58,8 @@ void AudioDriverOpenSL::_buffer_callback(
}
}
- if (mutex && mix)
- mutex->unlock();
+ if (mix)
+ mutex.unlock();
const int32_t *src_buff = mixdown_buffer;
@@ -107,7 +107,6 @@ Error AudioDriverOpenSL::init() {
void AudioDriverOpenSL::start() {
- mutex = Mutex::create();
active = false;
SLresult res;
@@ -329,14 +328,14 @@ AudioDriver::SpeakerMode AudioDriverOpenSL::get_speaker_mode() const {
void AudioDriverOpenSL::lock() {
- if (active && mutex)
- mutex->lock();
+ if (active)
+ mutex.lock();
}
void AudioDriverOpenSL::unlock() {
- if (active && mutex)
- mutex->unlock();
+ if (active)
+ mutex.unlock();
}
void AudioDriverOpenSL::finish() {
@@ -359,7 +358,6 @@ void AudioDriverOpenSL::set_pause(bool p_pause) {
AudioDriverOpenSL::AudioDriverOpenSL() {
s_ad = this;
- mutex = Mutex::create(); //NULL;
pause = false;
active = false;
}
diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h
index 57d9b30af6..569e2aa54b 100644
--- a/platform/android/audio_driver_opensl.h
+++ b/platform/android/audio_driver_opensl.h
@@ -40,7 +40,7 @@
class AudioDriverOpenSL : public AudioDriver {
bool active;
- Mutex *mutex;
+ Mutex mutex;
enum {
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 8b62360888..8f74ae0ef0 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -205,7 +205,6 @@ def configure(env):
env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/include"])
env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++abi/include"])
- env.Append(CXXFLAGS=["-std=gnu++14"])
# Disable exceptions and rtti on non-tools (template) builds
if env['tools']:
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index f52b511522..ebcac884db 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -144,7 +144,7 @@ Error DirAccessJAndroid::change_dir(String p_dir) {
return OK;
}
-String DirAccessJAndroid::get_current_dir() {
+String DirAccessJAndroid::get_current_dir(bool p_include_drive) {
return "res://" + current_dir;
}
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index caeb4b58b9..8dab3e50ce 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -65,7 +65,7 @@ public:
virtual String get_drive(int p_drive);
virtual Error change_dir(String p_dir); ///< can be relative or absolute, return false on success
- virtual String get_current_dir(); ///< return current dir location
+ virtual String get_current_dir(bool p_include_drive = true); ///< return current dir location
virtual bool file_exists(String p_file);
virtual bool dir_exists(String p_dir);
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 50bf671a84..e7d4bc6c51 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -257,7 +257,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
Vector<Device> devices;
volatile bool devices_changed;
- Mutex *device_lock;
+ Mutex device_lock;
Thread *device_thread;
volatile bool quit_request;
@@ -288,7 +288,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
ldevices.push_back(d);
}
- ea->device_lock->lock();
+ MutexLock lock(ea->device_lock);
bool different = false;
@@ -381,8 +381,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
ea->devices = ndevices;
ea->devices_changed = true;
}
-
- ea->device_lock->unlock();
}
uint64_t sleep = 200;
@@ -690,6 +688,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ String plugins = p_preset->get("custom_template/plugins");
+
Vector<String> perms;
const char **aperms = android_perms;
@@ -853,6 +853,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}
+ if (tname == "meta-data" && attrname == "value" && value == "custom_template_plugins_value") {
+ // Update the meta-data 'android:value' attribute with the list of enabled plugins.
+ string_table.write[attr_value] = plugins;
+ }
+
iofs += 20;
}
@@ -1365,6 +1370,7 @@ public:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/plugins", PROPERTY_HINT_PLACEHOLDER_TEXT, "Plugin1,Plugin2,..."), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
@@ -1432,11 +1438,8 @@ public:
virtual int get_options_count() const {
- device_lock->lock();
- int dc = devices.size();
- device_lock->unlock();
-
- return dc;
+ MutexLock lock(device_lock);
+ return devices.size();
}
virtual String get_options_tooltip() const {
@@ -1447,16 +1450,14 @@ public:
virtual String get_option_label(int p_index) const {
ERR_FAIL_INDEX_V(p_index, devices.size(), "");
- device_lock->lock();
- String s = devices[p_index].name;
- device_lock->unlock();
- return s;
+ MutexLock lock(device_lock);
+ return devices[p_index].name;
}
virtual String get_option_tooltip(int p_index) const {
ERR_FAIL_INDEX_V(p_index, devices.size(), "");
- device_lock->lock();
+ MutexLock lock(device_lock);
String s = devices[p_index].description;
if (devices.size() == 1) {
// Tooltip will be:
@@ -1464,7 +1465,6 @@ public:
// Description
s = devices[p_index].name + "\n\n" + s;
}
- device_lock->unlock();
return s;
}
@@ -1479,7 +1479,7 @@ public:
return ERR_UNCONFIGURED;
}
- device_lock->lock();
+ MutexLock lock(device_lock);
EditorProgress ep("run", "Running on " + devices[p_device].name, 3);
@@ -1487,7 +1487,6 @@ public:
// Export_temp APK.
if (ep.step("Exporting APK...", 0)) {
- device_lock->unlock();
return ERR_SKIP;
}
@@ -1502,7 +1501,6 @@ public:
#define CLEANUP_AND_RETURN(m_err) \
{ \
DirAccess::remove_file_or_error(tmp_export_path); \
- device_lock->unlock(); \
return m_err; \
}
@@ -1753,260 +1751,6 @@ public:
return list;
}
- void _update_custom_build_project() {
-
- DirAccessRef da = DirAccess::open("res://android");
-
- ERR_FAIL_COND_MSG(!da, "Cannot open directory 'res://android'.");
- Map<String, List<String> > directory_paths;
- Map<String, List<String> > manifest_sections;
- Map<String, List<String> > gradle_sections;
- da->list_dir_begin();
- String d = da->get_next();
- while (d != String()) {
-
- if (!d.begins_with(".") && d != "build" && da->current_is_dir()) { //a dir and not the build dir
- //add directories found
- DirAccessRef ds = DirAccess::open(String("res://android").plus_file(d));
- if (ds) {
- ds->list_dir_begin();
- String sd = ds->get_next();
- while (sd != String()) {
-
- if (!sd.begins_with(".") && ds->current_is_dir()) {
- String key = sd.to_upper();
- if (!directory_paths.has(key)) {
- directory_paths[key] = List<String>();
- }
- String path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android").plus_file(d).plus_file(sd);
- directory_paths[key].push_back(path);
- print_line("Add: " + sd + ":" + path);
- }
-
- sd = ds->get_next();
- }
- ds->list_dir_end();
- }
- //parse manifest
- {
- FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("AndroidManifest.conf"), FileAccess::READ);
- if (f) {
-
- String section;
- while (!f->eof_reached()) {
- String l = f->get_line();
- String k = l.strip_edges();
- if (k.begins_with("[")) {
- section = k.substr(1, k.length() - 2).strip_edges().to_upper();
- print_line("Section: " + section);
- } else if (k != String()) {
- if (!manifest_sections.has(section)) {
- manifest_sections[section] = List<String>();
- }
- manifest_sections[section].push_back(l);
- }
- }
-
- f->close();
- }
- }
- //parse gradle
- {
- FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("gradle.conf"), FileAccess::READ);
- if (f) {
-
- String section;
- while (!f->eof_reached()) {
- String l = f->get_line().strip_edges();
- String k = l.strip_edges();
- if (k.begins_with("[")) {
- section = k.substr(1, k.length() - 2).strip_edges().to_upper();
- print_line("Section: " + section);
- } else if (k != String()) {
- if (!gradle_sections.has(section)) {
- gradle_sections[section] = List<String>();
- }
- gradle_sections[section].push_back(l);
- }
- }
- }
- }
- }
- d = da->get_next();
- }
- da->list_dir_end();
-
- { //fix gradle build
-
- String new_file;
- {
- FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::READ);
- if (f) {
-
- while (!f->eof_reached()) {
- String l = f->get_line();
-
- if (l.begins_with("//CHUNK_")) {
- String text = l.replace_first("//CHUNK_", "");
- int begin_pos = text.find("_BEGIN");
- if (begin_pos != -1) {
- text = text.substr(0, begin_pos);
- text = text.to_upper(); //just in case
-
- String end_marker = "//CHUNK_" + text + "_END";
- size_t pos = f->get_position();
- bool found = false;
- while (!f->eof_reached()) {
- l = f->get_line();
- if (l.begins_with(end_marker)) {
- found = true;
- break;
- }
- }
-
- new_file += "//CHUNK_" + text + "_BEGIN\n";
-
- if (!found) {
- ERR_PRINT("No end marker found in build.gradle for chunk: " + text);
- f->seek(pos);
- } else {
-
- //add chunk lines
- if (gradle_sections.has(text)) {
- for (List<String>::Element *E = gradle_sections[text].front(); E; E = E->next()) {
- new_file += E->get() + "\n";
- }
- }
- new_file += end_marker + "\n";
- }
- } else {
- new_file += l + "\n"; //pass line by
- }
- } else if (l.begins_with("//DIR_")) {
- String text = l.replace_first("//DIR_", "");
- int begin_pos = text.find("_BEGIN");
- if (begin_pos != -1) {
- text = text.substr(0, begin_pos);
- text = text.to_upper(); //just in case
-
- String end_marker = "//DIR_" + text + "_END";
- size_t pos = f->get_position();
- bool found = false;
- while (!f->eof_reached()) {
- l = f->get_line();
- if (l.begins_with(end_marker)) {
- found = true;
- break;
- }
- }
-
- new_file += "//DIR_" + text + "_BEGIN\n";
-
- if (!found) {
- ERR_PRINT("No end marker found in build.gradle for dir: " + text);
- f->seek(pos);
- } else {
- //add chunk lines
- if (directory_paths.has(text)) {
- for (List<String>::Element *E = directory_paths[text].front(); E; E = E->next()) {
- new_file += ",'" + E->get().replace("'", "\'") + "'";
- new_file += "\n";
- }
- }
- new_file += end_marker + "\n";
- }
- } else {
- new_file += l + "\n"; //pass line by
- }
-
- } else {
- new_file += l + "\n";
- }
- }
- }
- }
-
- FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::WRITE);
- f->store_string(new_file);
- f->close();
- }
-
- { //fix manifest
-
- String new_file;
- {
- FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::READ);
- if (f) {
-
- while (!f->eof_reached()) {
- String l = f->get_line();
-
- if (l.begins_with("<!--CHUNK_")) {
- String text = l.replace_first("<!--CHUNK_", "");
- int begin_pos = text.find("_BEGIN-->");
- if (begin_pos != -1) {
- text = text.substr(0, begin_pos);
- text = text.to_upper(); //just in case
-
- String end_marker = "<!--CHUNK_" + text + "_END-->";
- size_t pos = f->get_position();
- bool found = false;
- while (!f->eof_reached()) {
- l = f->get_line();
- if (l.begins_with(end_marker)) {
- found = true;
- break;
- }
- }
-
- new_file += "<!--CHUNK_" + text + "_BEGIN-->\n";
-
- if (!found) {
- ERR_PRINT("No end marker found in AndroidManifest.xml for chunk: " + text);
- f->seek(pos);
- } else {
- //add chunk lines
- if (manifest_sections.has(text)) {
- for (List<String>::Element *E = manifest_sections[text].front(); E; E = E->next()) {
- new_file += E->get() + "\n";
- }
- }
- new_file += end_marker + "\n";
- }
- } else {
- new_file += l + "\n"; //pass line by
- }
-
- } else if (l.strip_edges().begins_with("<application")) {
- String last_tag = "android:icon=\"@mipmap/icon\"";
- int last_tag_pos = l.find(last_tag);
- if (last_tag_pos == -1) {
- ERR_PRINT("Not adding application attributes as the expected tag was not found in '<application': " + last_tag);
- new_file += l + "\n";
- } else {
- String base = l.substr(0, last_tag_pos + last_tag.length());
- if (manifest_sections.has("application_attribs")) {
- for (List<String>::Element *E = manifest_sections["application_attribs"].front(); E; E = E->next()) {
- String to_add = E->get().strip_edges();
- base += " " + to_add + " ";
- }
- }
- base += ">\n";
- new_file += base;
- }
- } else {
- new_file += l + "\n";
- }
- }
- }
- }
-
- FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::WRITE);
- f->store_string(new_file);
- f->close();
- }
- }
-
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
@@ -2035,8 +1779,6 @@ public:
ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
- _update_custom_build_project();
-
OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
String build_command;
@@ -2047,14 +1789,18 @@ public:
#endif
String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
+ String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
build_command = build_path.plus_file(build_command);
String package_name = get_package_name(p_preset->get("package/unique_name"));
+ String plugins = p_preset->get("custom_template/plugins");
List<String> cmdline;
cmdline.push_back("build");
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
+ cmdline.push_back("-Pcustom_template_plugins_dir=" + plugins_dir); // argument to specify the plugins directory.
+ cmdline.push_back("-Pcustom_template_plugins=" + plugins); // argument to specify the list of plugins to enable.
cmdline.push_back("-p"); // argument to specify the start directory.
cmdline.push_back(build_path); // start directory.
/*{ used for debug
@@ -2570,7 +2316,6 @@ public:
run_icon.instance();
run_icon->create_from_image(img);
- device_lock = Mutex::create();
devices_changed = true;
quit_request = false;
device_thread = Thread::create(_device_poll_thread, this);
@@ -2579,7 +2324,6 @@ public:
~EditorExportPlatformAndroid() {
quit_request = true;
Thread::wait_to_finish(device_thread);
- memdelete(device_lock);
memdelete(device_thread);
}
};
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 4d2eb1ef65..cc480d1c84 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -6,9 +6,6 @@
android:versionName="1.0"
android:installLocation="auto" >
- <!-- Adding custom text to the manifest is fine, but do it outside the custom USER and APPLICATION BEGIN/END comments, -->
- <!-- as that gets rewritten. -->
-
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
@@ -19,14 +16,11 @@
android:glEsVersion="0x00020000"
android:required="true" />
-<!-- Custom user permissions XML added by add-ons. It's recommended to add them from the export preset, though. -->
-<!--CHUNK_USER_PERMISSIONS_BEGIN-->
-<!--CHUNK_USER_PERMISSIONS_END-->
-
- <!-- Any tag in this line after android:icon will be erased when doing custom builds. -->
- <!-- If you want to add tags manually, do before it. -->
- <!-- WARNING: This should stay on a single line until the parsing code is improved. See GH-32414. -->
- <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@mipmap/icon" >
+ <application
+ android:label="@string/godot_project_name_string"
+ android:allowBackup="false"
+ tools:ignore="GoogleAppIndexingWarning"
+ android:icon="@mipmap/icon" >
<!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. -->
<!-- Do these changes in the export preset. Adding new ones is fine. -->
@@ -36,6 +30,11 @@
android:name="xr_mode_metadata_name"
android:value="xr_mode_metadata_value" />
+ <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. -->
+ <meta-data
+ android:name="custom_template_plugins"
+ android:value="custom_template_plugins_value"/>
+
<activity
android:name=".GodotApp"
android:label="@string/godot_project_name_string"
@@ -52,10 +51,6 @@
</intent-filter>
</activity>
-<!-- Custom application XML added by add-ons. -->
-<!--CHUNK_APPLICATION_BEGIN-->
-<!--CHUNK_APPLICATION_END-->
-
</application>
</manifest>
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 2e4f2ffab0..5e37f538e9 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -1,7 +1,4 @@
// Gradle build config for Godot Engine's Android port.
-//
-// Do not remove/modify comments ending with BEGIN/END, they are used to inject
-// addon-specific configuration.
apply from: 'config.gradle'
buildscript {
@@ -10,13 +7,10 @@ buildscript {
repositories {
google()
jcenter()
-//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN
-//CHUNK_BUILDSCRIPT_REPOSITORIES_END
}
dependencies {
classpath libraries.androidGradlePlugin
-//CHUNK_BUILDSCRIPT_DEPENDENCIES_BEGIN
-//CHUNK_BUILDSCRIPT_DEPENDENCIES_END
+ classpath libraries.kotlinGradlePlugin
}
}
@@ -27,24 +21,35 @@ allprojects {
mavenCentral()
google()
jcenter()
-//CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN
-//CHUNK_ALLPROJECTS_REPOSITORIES_END
}
}
dependencies {
implementation libraries.supportCoreUtils
+ implementation libraries.kotlinStdLib
+ implementation libraries.v4Support
if (rootProject.findProject(":lib")) {
implementation project(":lib")
+ } else if (rootProject.findProject(":godot:lib")) {
+ implementation project(":godot:lib")
} else {
// Custom build mode. In this scenario this project is the only one around and the Godot
// library is available through the pre-generated godot-lib.*.aar android archive files.
debugImplementation fileTree(dir: 'libs/debug', include: ['*.jar', '*.aar'])
releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar'])
}
-//CHUNK_DEPENDENCIES_BEGIN
-//CHUNK_DEPENDENCIES_END
+
+ // Godot prebuilt plugins
+ implementation fileTree(dir: 'libs/plugins', include: ["GodotPayment*.aar"])
+
+ // Godot user plugins dependencies
+ String pluginsDir = getGodotPluginsDirectory()
+ String[] pluginsBinaries = getGodotPluginsBinaries()
+ if (pluginsDir != null && !pluginsDir.isEmpty() &&
+ pluginsBinaries != null && pluginsBinaries.size() > 0) {
+ implementation fileTree(dir: pluginsDir, include: pluginsBinaries)
+ }
}
android {
@@ -56,8 +61,6 @@ android {
applicationId getExportPackageName()
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
-//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN
-//CHUNK_ANDROID_DEFAULTCONFIG_END
}
lintOptions {
@@ -79,37 +82,13 @@ android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = [
- 'src'
-//DIR_SRC_BEGIN
-//DIR_SRC_END
- ]
- res.srcDirs = [
- 'res'
-//DIR_RES_BEGIN
-//DIR_RES_END
- ]
- aidl.srcDirs = [
- 'aidl'
-//DIR_AIDL_BEGIN
-//DIR_AIDL_END
- ]
- assets.srcDirs = [
- 'assets'
-//DIR_ASSETS_BEGIN
-//DIR_ASSETS_END
- ]
+ java.srcDirs = ['src']
+ res.srcDirs = ['res']
+ aidl.srcDirs = ['aidl']
+ assets.srcDirs = ['assets']
}
- debug.jniLibs.srcDirs = [
- 'libs/debug'
-//DIR_JNI_DEBUG_BEGIN
-//DIR_JNI_DEBUG_END
- ]
- release.jniLibs.srcDirs = [
- 'libs/release'
-//DIR_JNI_RELEASE_BEGIN
-//DIR_JNI_RELEASE_END
- ]
+ debug.jniLibs.srcDirs = ['libs/debug']
+ release.jniLibs.srcDirs = ['libs/release']
}
applicationVariants.all { variant ->
@@ -118,6 +97,3 @@ android {
}
}
}
-
-//CHUNK_GLOBAL_BEGIN
-//CHUNK_GLOBAL_END
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 5550d3099d..eaaefcbccb 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,24 +1,66 @@
ext.versions = [
- androidGradlePlugin : '3.4.2',
- compileSdk : 28,
- minSdk : 18,
- targetSdk : 28,
- buildTools : '28.0.3',
- supportCoreUtils : '28.0.0'
+ androidGradlePlugin: '3.6.0',
+ compileSdk : 29,
+ minSdk : 18,
+ targetSdk : 29,
+ buildTools : '29.0.1',
+ supportCoreUtils : '28.0.0',
+ kotlinVersion : '1.3.61',
+ v4Support : '28.0.0'
]
ext.libraries = [
- androidGradlePlugin : "com.android.tools.build:gradle:$versions.androidGradlePlugin",
- supportCoreUtils : "com.android.support:support-core-utils:$versions.supportCoreUtils"
+ androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin",
+ supportCoreUtils : "com.android.support:support-core-utils:$versions.supportCoreUtils",
+ kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion",
+ kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion",
+ v4Support : "com.android.support:support-v4:$versions.v4Support"
]
ext.getExportPackageName = { ->
- // Retrieve the app id from the project property set by the Godot build command.
- String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : ""
- // Check if the app id is valid, otherwise use the default.
- if (appId == null || appId.isEmpty()) {
- appId = "com.godot.game"
- }
- return appId
+ // Retrieve the app id from the project property set by the Godot build command.
+ String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : ""
+ // Check if the app id is valid, otherwise use the default.
+ if (appId == null || appId.isEmpty()) {
+ appId = "com.godot.game"
+ }
+ return appId
+}
+
+/**
+ * Parse the project properties for the 'custom_template_plugins' property and return
+ * their binaries for inclusion in the build dependencies.
+ *
+ * The listed plugins must have their binaries in the project plugins directory.
+ */
+ext.getGodotPluginsBinaries = { ->
+ String[] binDeps = []
+
+ // Retrieve the list of enabled plugins.
+ if (project.hasProperty("custom_template_plugins")) {
+ String pluginsList = project.property("custom_template_plugins")
+ if (pluginsList != null && !pluginsList.trim().isEmpty()) {
+ for (String plugin : pluginsList.split(",")) {
+ binDeps += plugin.trim() + "*.aar"
+ }
+ }
+ }
+
+ return binDeps
+}
+
+/**
+ * Parse the project properties for the 'custom_template_plugins_dir' property and return
+ * its value.
+ *
+ * The returned value is the directory containing user plugins.
+ */
+ext.getGodotPluginsDirectory = { ->
+ // The plugins directory is provided by the 'custom_template_plugins_dir' property.
+ String pluginsDir = project.hasProperty("custom_template_plugins_dir")
+ ? project.property("custom_template_plugins_dir")
+ : ""
+
+ return pluginsDir
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 2052017888..976a5bda99 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -9,6 +9,7 @@ buildscript {
}
dependencies {
classpath libraries.androidGradlePlugin
+ classpath libraries.kotlinGradlePlugin
}
}
@@ -24,7 +25,7 @@ ext {
sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : ""
supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"]
- supportedTargets = ['release':"release", 'debug':"release_debug"]
+ supportedTargets = ['release': "release", 'debug': "release_debug"]
// Used by gradle to specify which architecture to build for by default when running `./gradlew build`.
// This command is usually used by Android Studio.
@@ -64,10 +65,10 @@ task copyReleaseBinaryToBin(type: Copy) {
}
/**
- * Copy the Godot android library archive debug file into the app debug libs directory.
+ * Copy the Godot android library archive debug file into the app module debug libs directory.
* Depends on the library build task to ensure the AAR file is generated prior to copying.
*/
-task copyDebugAAR(type: Copy) {
+task copyDebugAARToAppModule(type: Copy) {
dependsOn ':lib:assembleDebug'
from('lib/build/outputs/aar')
into('app/libs/debug')
@@ -75,16 +76,45 @@ task copyDebugAAR(type: Copy) {
}
/**
- * Copy the Godot android library archive release file into the app release libs directory.
+ * Copy the Godot android library archive debug file into the root bin directory.
* Depends on the library build task to ensure the AAR file is generated prior to copying.
*/
-task copyReleaseAAR(type: Copy) {
+task copyDebugAARToBin(type: Copy) {
+ dependsOn ':lib:assembleDebug'
+ from('lib/build/outputs/aar')
+ into(binDir)
+ include('godot-lib.debug.aar')
+}
+
+/**
+ * Copy the Godot android library archive release file into the app module release libs directory.
+ * Depends on the library build task to ensure the AAR file is generated prior to copying.
+ */
+task copyReleaseAARToAppModule(type: Copy) {
dependsOn ':lib:assembleRelease'
from('lib/build/outputs/aar')
into('app/libs/release')
include('godot-lib.release.aar')
}
+task copyGodotPaymentPluginToAppModule(type: Copy) {
+ dependsOn ':plugins:godotpayment:assembleRelease'
+ from('plugins/godotpayment/build/outputs/aar')
+ into('app/libs/plugins')
+ include('GodotPayment.release.aar')
+}
+
+/**
+ * Copy the Godot android library archive release file into the root bin directory.
+ * Depends on the library build task to ensure the AAR file is generated prior to copying.
+ */
+task copyReleaseAARToBin(type: Copy) {
+ dependsOn ':lib:assembleRelease'
+ from('lib/build/outputs/aar')
+ into(binDir)
+ include('godot-lib.release.aar')
+}
+
/**
* Generate Godot custom build template by zipping the source files from the app directory, as well
* as the AAR files generated by 'copyDebugAAR' and 'copyReleaseAAR'.
@@ -95,7 +125,7 @@ task zipCustomBuild(type: Zip) {
doFirst {
logger.lifecycle("Generating Godot custom build template")
}
- from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties','gradlew', 'gradlew.bat', 'gradle/**']))
+ from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties', 'gradlew', 'gradlew.bat', 'gradle/**']))
include '**/*'
archiveName 'android_source.zip'
destinationDir(file(binDir))
@@ -110,19 +140,24 @@ task generateGodotTemplates(type: GradleBuild) {
startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType)
}
- tasks = []
+ tasks = ["copyGodotPaymentPluginToAppModule"]
// Only build the apks and aar files for which we have native shared libraries.
for (String target : supportedTargets.keySet()) {
File targetLibs = new File("lib/libs/" + target)
- if (targetLibs != null && targetLibs.isDirectory()) {
- File[] targetLibsContents = targetLibs.listFiles()
- if (targetLibsContents != null && targetLibsContents.length > 0) {
- // Copy the generated aar library files to the custom build directory.
- tasks += "copy" + target.capitalize() + "AAR"
- // Copy the prebuilt binary templates to the bin directory.
- tasks += "copy" + target.capitalize() + "BinaryToBin"
- }
+ if (targetLibs != null
+ && targetLibs.isDirectory()
+ && targetLibs.listFiles() != null
+ && targetLibs.listFiles().length > 0) {
+ String capitalizedTarget = target.capitalize()
+ // Copy the generated aar library files to the custom build directory.
+ tasks += "copy" + capitalizedTarget + "AARToAppModule"
+ // Copy the generated aar library files to the bin directory.
+ tasks += "copy" + capitalizedTarget + "AARToBin"
+ // Copy the prebuilt binary templates to the bin directory.
+ tasks += "copy" + capitalizedTarget + "BinaryToBin"
+ } else {
+ logger.lifecycle("No native shared libs for target $target. Skipping build.")
}
}
@@ -133,20 +168,28 @@ task generateGodotTemplates(type: GradleBuild) {
* Clean the generated artifacts.
*/
task cleanGodotTemplates(type: Delete) {
- // Delete the generated native libs
- delete("lib/libs")
+ // Delete the generated native libs
+ delete("lib/libs")
+
+ // Delete the library generated AAR files
+ delete("lib/build/outputs/aar")
+
+ // Delete the godotpayment libs directory contents
+ delete("plugins/godotpayment/libs")
- // Delete the library generated AAR files
- delete("lib/build/outputs/aar")
+ // Delete the generated godotpayment aar
+ delete("plugins/godotpayment/build/outputs/aar")
- // Delete the app libs directory contents
- delete("app/libs")
+ // Delete the app libs directory contents
+ delete("app/libs")
- // Delete the generated binary apks
- delete("app/build/outputs/apk")
+ // Delete the generated binary apks
+ delete("app/build/outputs/apk")
- // Delete the Godot templates in the Godot bin directory
- delete("$binDir/android_debug.apk")
- delete("$binDir/android_release.apk")
- delete("$binDir/android_source.zip")
+ // Delete the Godot templates in the Godot bin directory
+ delete("$binDir/android_debug.apk")
+ delete("$binDir/android_release.apk")
+ delete("$binDir/android_source.zip")
+ delete("$binDir/godot-lib.debug.aar")
+ delete("$binDir/godot-lib.release.aar")
}
diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
index bf50265715..f56b0f6a5e 100644
--- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties
+++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index eb97484b9c..ca8aaf8af0 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -2,6 +2,8 @@ apply plugin: 'com.android.library'
dependencies {
implementation libraries.supportCoreUtils
+ implementation libraries.kotlinStdLib
+ implementation libraries.v4Support
}
def pathToRootDir = "../../../../"
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 021214b627..7db4aa6597 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -61,8 +61,11 @@ import android.os.Messenger;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings.Secure;
+import android.support.annotation.CallSuper;
import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -87,22 +90,20 @@ import com.google.android.vending.expansion.downloader.IStub;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
-import java.lang.reflect.Method;
import java.security.MessageDigest;
-import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
-import javax.microedition.khronos.opengles.GL10;
import org.godotengine.godot.input.GodotEditText;
import org.godotengine.godot.payments.PaymentsManager;
+import org.godotengine.godot.plugin.GodotPlugin;
+import org.godotengine.godot.plugin.GodotPluginRegistry;
import org.godotengine.godot.utils.GodotNetUtils;
import org.godotengine.godot.utils.PermissionsUtil;
import org.godotengine.godot.xr.XRMode;
-public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient {
+public abstract class Godot extends FragmentActivity implements SensorEventListener, IDownloaderClient {
- static final int MAX_SINGLETONS = 64;
private IStub mDownloaderClientStub;
private TextView mStatusText;
private TextView mProgressFraction;
@@ -126,8 +127,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
private boolean activityResumed;
private int mState;
- // Used to dispatch events to the main thread.
- private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
+ private GodotPluginRegistry pluginRegistry;
static private Intent mCurrentIntent;
@@ -154,83 +154,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
mPauseButton.setText(stringResourceID);
}
- static public class SingletonBase {
-
- protected void registerClass(String p_name, String[] p_methods) {
-
- GodotLib.singleton(p_name, this);
-
- Class clazz = getClass();
- Method[] methods = clazz.getDeclaredMethods();
- for (Method method : methods) {
- boolean found = false;
-
- for (String s : p_methods) {
- if (s.equals(method.getName())) {
- found = true;
- break;
- }
- }
- if (!found)
- continue;
-
- List<String> ptr = new ArrayList<String>();
-
- Class[] paramTypes = method.getParameterTypes();
- for (Class c : paramTypes) {
- ptr.add(c.getName());
- }
-
- String[] pt = new String[ptr.size()];
- ptr.toArray(pt);
-
- GodotLib.method(p_name, method.getName(), method.getReturnType().getName(), pt);
- }
-
- Godot.singletons[Godot.singleton_count++] = this;
- }
-
- /**
- * Invoked once during the Godot Android initialization process after creation of the
- * {@link GodotView} view.
- * <p>
- * This method should be overridden by descendants of this class that would like to add
- * their view/layout to the Godot view hierarchy.
- *
- * @return the view to be included; null if no views should be included.
- */
- @Nullable
- protected View onMainCreateView(Activity activity) {
- return null;
- }
-
- protected void onMainActivityResult(int requestCode, int resultCode, Intent data) {
- }
-
- protected void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- }
-
- protected void onMainPause() {}
- protected void onMainResume() {}
- protected void onMainDestroy() {}
- protected boolean onMainBackPressed() { return false; }
-
- protected void onGLDrawFrame(GL10 gl) {}
- protected void onGLSurfaceChanged(GL10 gl, int width, int height) {} // singletons will always miss first onGLSurfaceChanged call
- //protected void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} // singletons won't be ready until first GodotLib.step()
-
- public void registerMethods() {}
- }
-
- /*
- protected List<SingletonBase> singletons = new ArrayList<SingletonBase>();
- protected void instanceSingleton(SingletonBase s) {
-
- s.registerMethods();
- singletons.add(s);
- }
- */
-
private String[] command_line;
private boolean use_apk_expansion;
@@ -246,9 +169,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
public static GodotIO io;
public static GodotNetUtils netUtils;
- static SingletonBase[] singletons = new SingletonBase[MAX_SINGLETONS];
- static int singleton_count = 0;
-
public interface ResultCallback {
public void callback(int requestCode, int resultCode, Intent data);
}
@@ -265,16 +185,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
result_callback = null;
};
- for (int i = 0; i < singleton_count; i++) {
-
- singletons[i].onMainActivityResult(requestCode, resultCode, data);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainActivityResult(requestCode, resultCode, data);
}
};
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- for (int i = 0; i < singleton_count; i++) {
- singletons[i].onMainRequestPermissionsResult(requestCode, permissions, grantResults);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults);
}
for (int i = 0; i < permissions.length; i++) {
@@ -283,6 +202,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
};
/**
+ * Invoked on the GL thread when the Godot main loop has started.
+ */
+ @CallSuper
+ protected void onGLGodotMainLoopStarted() {
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGLGodotMainLoopStarted();
+ }
+ }
+
+ /**
* Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer.
*/
@Keep
@@ -302,14 +231,13 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
edittext.setView(mView);
io.setEdit(edittext);
- final Godot godot = this;
mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Point fullSize = new Point();
- godot.getWindowManager().getDefaultDisplay().getSize(fullSize);
+ getWindowManager().getDefaultDisplay().getSize(fullSize);
Rect gameSize = new Rect();
- godot.mView.getWindowVisibleDisplayFrame(gameSize);
+ mView.getWindowVisibleDisplayFrame(gameSize);
final int keyboardHeight = fullSize.y - gameSize.bottom;
GodotLib.setVirtualKeyboardHeight(keyboardHeight);
@@ -321,23 +249,23 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
@Override
public void run() {
GodotLib.setup(current_command_line);
- setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")));
- // The Godot Android plugins are setup on completion of GodotLib.setup
- mainThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- // Include the non-null views returned in the Godot view hierarchy.
- for (int i = 0; i < singleton_count; i++) {
- View view = singletons[i].onMainCreateView(Godot.this);
- if (view != null) {
- layout.addView(view);
- }
- }
- }
- });
+ // Must occur after GodotLib.setup has completed.
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGLRegisterPluginWithGodotNative();
+ }
+
+ setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")));
}
});
+
+ // Include the returned non-null views in the Godot view hierarchy.
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ View pluginView = plugin.onMainCreateView(this);
+ if (pluginView != null) {
+ layout.addView(pluginView);
+ }
+ }
}
public void setKeepScreenOn(final boolean p_enabled) {
@@ -535,6 +463,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
mClipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
+ pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
//check for apk expansion API
if (true) {
@@ -675,8 +604,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
protected void onDestroy() {
if (mPaymentsManager != null) mPaymentsManager.destroy();
- for (int i = 0; i < singleton_count; i++) {
- singletons[i].onMainDestroy();
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainDestroy();
}
GodotLib.ondestroy(this);
@@ -703,8 +632,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
mSensorManager.unregisterListener(this);
- for (int i = 0; i < singleton_count; i++) {
- singletons[i].onMainPause();
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainPause();
}
}
@@ -755,9 +684,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
- for (int i = 0; i < singleton_count; i++) {
-
- singletons[i].onMainResume();
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onMainResume();
}
}
@@ -850,8 +778,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
public void onBackPressed() {
boolean shouldQuit = true;
- for (int i = 0; i < singleton_count; i++) {
- if (singletons[i].onMainBackPressed()) {
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ if (plugin.onMainBackPressed()) {
shouldQuit = false;
}
}
@@ -866,6 +794,17 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
}
+ /**
+ * Queue a runnable to be run on the GL thread.
+ * <p>
+ * This must be called after the GL thread has started.
+ */
+ public final void runOnGLThread(@NonNull Runnable action) {
+ if (mView != null) {
+ mView.queueEvent(action);
+ }
+ }
+
private void forceQuit() {
System.exit(0);
}
@@ -990,8 +929,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
int keyCode;
if ((keyCode = cc[i]) != 0) {
// Simulate key down and up...
- GodotLib.key(0, keyCode, true);
- GodotLib.key(0, keyCode, false);
+ GodotLib.key(0, 0, keyCode, true);
+ GodotLib.key(0, 0, keyCode, false);
}
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index e0b46673ba..89a65aea24 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -136,7 +136,7 @@ public class GodotLib {
/**
* Forward regular key events from the main thread to the GL thread.
*/
- public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed);
+ public static native void key(int p_keycode, int p_scancode, int p_unicode_char, boolean p_pressed);
/**
* Forward game device's key events from the main thread to the GL thread.
@@ -176,22 +176,6 @@ public class GodotLib {
public static native void audio();
/**
- * Used to setup a {@link org.godotengine.godot.Godot.SingletonBase} instance.
- * @param p_name Name of the instance.
- * @param p_object Reference to the singleton instance.
- */
- public static native void singleton(String p_name, Object p_object);
-
- /**
- * Used to complete registration of the {@link org.godotengine.godot.Godot.SingletonBase} instance's methods.
- * @param p_sname Name of the instance
- * @param p_name Name of the method to register
- * @param p_ret Return type of the registered method
- * @param p_params Method parameters types
- */
- public static native void method(String p_sname, String p_name, String p_ret, String[] p_params);
-
- /**
* Used to access Godot global properties.
* @param p_key Property key
* @return String value of the property
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
index 26fa033f12..ee9a2aee4f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
@@ -30,9 +30,12 @@
package org.godotengine.godot;
+import android.content.Context;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
+import org.godotengine.godot.plugin.GodotPlugin;
+import org.godotengine.godot.plugin.GodotPluginRegistry;
import org.godotengine.godot.utils.GLUtils;
/**
@@ -40,8 +43,13 @@ import org.godotengine.godot.utils.GLUtils;
*/
class GodotRenderer implements GLSurfaceView.Renderer {
+ private final GodotPluginRegistry pluginRegistry;
private boolean activityJustResumed = false;
+ GodotRenderer() {
+ this.pluginRegistry = GodotPluginRegistry.getPluginRegistry();
+ }
+
public void onDrawFrame(GL10 gl) {
if (activityJustResumed) {
GodotLib.onRendererResumed();
@@ -49,21 +57,23 @@ class GodotRenderer implements GLSurfaceView.Renderer {
}
GodotLib.step();
- for (int i = 0; i < Godot.singleton_count; i++) {
- Godot.singletons[i].onGLDrawFrame(gl);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGLDrawFrame(gl);
}
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
-
GodotLib.resize(width, height);
- for (int i = 0; i < Godot.singleton_count; i++) {
- Godot.singletons[i].onGLSurfaceChanged(gl, width, height);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGLSurfaceChanged(gl, width, height);
}
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GodotLib.newcontext(GLUtils.use_32);
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGLSurfaceCreated(gl, config);
+ }
}
void onActivityResumed() {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index b2b88088e8..e00ca86c41 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -98,11 +98,12 @@ public class GodotInputHandler implements InputDeviceListener {
});
}
} else {
+ final int scanCode = event.getScanCode();
final int chr = event.getUnicodeChar(0);
queueEvent(new Runnable() {
@Override
public void run() {
- GodotLib.key(keyCode, chr, false);
+ GodotLib.key(keyCode, scanCode, chr, false);
}
});
};
@@ -143,11 +144,12 @@ public class GodotInputHandler implements InputDeviceListener {
});
}
} else {
+ final int scanCode = event.getScanCode();
final int chr = event.getUnicodeChar(0);
queueEvent(new Runnable() {
@Override
public void run() {
- GodotLib.key(keyCode, chr, true);
+ GodotLib.key(keyCode, scanCode, chr, true);
}
});
};
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
index 3a154f1bf3..8d9b5461a1 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
@@ -91,8 +91,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
@Override
public void run() {
for (int i = 0; i < count; ++i) {
- GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
- GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false);
}
}
});
@@ -110,8 +110,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
@Override
public void run() {
for (int i = 0; i < count; ++i) {
- GodotLib.key(0, newChars[i], true);
- GodotLib.key(0, newChars[i], false);
+ GodotLib.key(0, 0, newChars[i], true);
+ GodotLib.key(0, 0, newChars[i], false);
}
}
});
@@ -127,8 +127,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
public void run() {
for (int i = 0; i < characters.length(); i++) {
final int ch = characters.codePointAt(i);
- GodotLib.key(0, ch, true);
- GodotLib.key(0, ch, false);
+ GodotLib.key(0, 0, ch, true);
+ GodotLib.key(0, 0, ch, false);
}
}
});
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java b/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java
new file mode 100644
index 0000000000..6ac7338b30
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java
@@ -0,0 +1,97 @@
+/*************************************************************************/
+/* GodotPaymentInterface.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot.payments;
+
+public interface GodotPaymentInterface {
+ void purchase(String sku, String transactionId);
+
+ void consumeUnconsumedPurchases();
+
+ String getSignature();
+
+ void callbackSuccess(String ticket, String signature, String sku);
+
+ void callbackSuccessProductMassConsumed(String ticket, String signature, String sku);
+
+ void callbackSuccessNoUnconsumedPurchases();
+
+ void callbackFailConsume(String message);
+
+ void callbackFail(String message);
+
+ void callbackCancel();
+
+ void callbackAlreadyOwned(String sku);
+
+ int getPurchaseCallbackId();
+
+ void setPurchaseCallbackId(int purchaseCallbackId);
+
+ String getPurchaseValidationUrlPrefix();
+
+ void setPurchaseValidationUrlPrefix(String url);
+
+ String getAccessToken();
+
+ void setAccessToken(String accessToken);
+
+ void setTransactionId(String transactionId);
+
+ String getTransactionId();
+
+ // request purchased items are not consumed
+ void requestPurchased();
+
+ // callback for requestPurchased()
+ void callbackPurchased(String receipt, String signature, String sku);
+
+ void callbackDisconnected();
+
+ void callbackConnected();
+
+ // true if connected, false otherwise
+ boolean isConnected();
+
+ // consume item automatically after purchase. default is true.
+ void setAutoConsume(boolean autoConsume);
+
+ // consume a specific item
+ void consume(String sku);
+
+ // query in app item detail info
+ void querySkuDetails(String[] list);
+
+ void addSkuDetail(String itemJson);
+
+ void completeSkuDetail();
+
+ void errorSkuDetail(String errorMessage);
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java
index 90b958266b..9bf6650f84 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java
@@ -43,7 +43,6 @@ import android.util.Log;
import com.android.vending.billing.IInAppBillingService;
import java.util.ArrayList;
import java.util.Arrays;
-import org.godotengine.godot.GodotPaymentV3;
import org.json.JSONException;
import org.json.JSONObject;
@@ -90,9 +89,9 @@ public class PaymentsManager {
public void onServiceDisconnected(ComponentName name) {
mService = null;
- // At this stage, godotPaymentV3 might not have been initialized yet.
- if (godotPaymentV3 != null) {
- godotPaymentV3.callbackDisconnected();
+ // At this stage, godotPayment might not have been initialized yet.
+ if (godotPayment != null) {
+ godotPayment.callbackDisconnected();
}
}
@@ -100,9 +99,9 @@ public class PaymentsManager {
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IInAppBillingService.Stub.asInterface(service);
- // At this stage, godotPaymentV3 might not have been initialized yet.
- if (godotPaymentV3 != null) {
- godotPaymentV3.callbackConnected();
+ // At this stage, godotPayment might not have been initialized yet.
+ if (godotPayment != null) {
+ godotPayment.callbackConnected();
}
}
};
@@ -111,17 +110,17 @@ public class PaymentsManager {
new PurchaseTask(mService, activity) {
@Override
protected void error(String message) {
- godotPaymentV3.callbackFail(message);
+ godotPayment.callbackFail(message);
}
@Override
protected void canceled() {
- godotPaymentV3.callbackCancel();
+ godotPayment.callbackCancel();
}
@Override
protected void alreadyOwned() {
- godotPaymentV3.callbackAlreadyOwned(sku);
+ godotPayment.callbackAlreadyOwned(sku);
}
}
.purchase(sku, transactionId);
@@ -135,19 +134,19 @@ public class PaymentsManager {
new ReleaseAllConsumablesTask(mService, activity) {
@Override
protected void success(String sku, String receipt, String signature, String token) {
- godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku);
+ godotPayment.callbackSuccessProductMassConsumed(receipt, signature, sku);
}
@Override
protected void error(String message) {
Log.d("godot", "consumeUnconsumedPurchases :" + message);
- godotPaymentV3.callbackFailConsume(message);
+ godotPayment.callbackFailConsume(message);
}
@Override
protected void notRequired() {
Log.d("godot", "callbackSuccessNoUnconsumedPurchases :");
- godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
+ godotPayment.callbackSuccessNoUnconsumedPurchases();
}
}
.consumeItAll();
@@ -168,7 +167,7 @@ public class PaymentsManager {
final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
if (myPurchases == null || myPurchases.size() == 0) {
- godotPaymentV3.callbackPurchased("", "", "");
+ godotPayment.callbackPurchased("", "", "");
return;
}
@@ -186,7 +185,7 @@ public class PaymentsManager {
pc.setConsumableFlag("block", sku, true);
pc.setConsumableValue("token", sku, token);
- godotPaymentV3.callbackPurchased(receipt, signature, sku);
+ godotPayment.callbackPurchased(receipt, signature, sku);
} catch (JSONException e) {
}
}
@@ -203,7 +202,7 @@ public class PaymentsManager {
new HandlePurchaseTask(activity) {
@Override
protected void success(final String sku, final String signature, final String ticket) {
- godotPaymentV3.callbackSuccess(ticket, signature, sku);
+ godotPayment.callbackSuccess(ticket, signature, sku);
if (auto_consume) {
new ConsumeTask(mService, activity) {
@@ -213,7 +212,7 @@ public class PaymentsManager {
@Override
protected void error(String message) {
- godotPaymentV3.callbackFail(message);
+ godotPayment.callbackFail(message);
}
}
.consume(sku);
@@ -222,12 +221,12 @@ public class PaymentsManager {
@Override
protected void error(String message) {
- godotPaymentV3.callbackFail(message);
+ godotPayment.callbackFail(message);
}
@Override
protected void canceled() {
- godotPaymentV3.callbackCancel();
+ godotPayment.callbackCancel();
}
}
.handlePurchaseRequest(resultCode, data);
@@ -235,19 +234,19 @@ public class PaymentsManager {
public void validatePurchase(String purchaseToken, final String sku) {
- new ValidateTask(activity, godotPaymentV3) {
+ new ValidateTask(activity, godotPayment) {
@Override
protected void success() {
new ConsumeTask(mService, activity) {
@Override
protected void success(String ticket) {
- godotPaymentV3.callbackSuccess(ticket, null, sku);
+ godotPayment.callbackSuccess(ticket, null, sku);
}
@Override
protected void error(String message) {
- godotPaymentV3.callbackFail(message);
+ godotPayment.callbackFail(message);
}
}
.consume(sku);
@@ -255,12 +254,12 @@ public class PaymentsManager {
@Override
protected void error(String message) {
- godotPaymentV3.callbackFail(message);
+ godotPayment.callbackFail(message);
}
@Override
protected void canceled() {
- godotPaymentV3.callbackCancel();
+ godotPayment.callbackCancel();
}
}
.validatePurchase(sku);
@@ -274,12 +273,12 @@ public class PaymentsManager {
new ConsumeTask(mService, activity) {
@Override
protected void success(String ticket) {
- godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku);
+ godotPayment.callbackSuccessProductMassConsumed(ticket, "", sku);
}
@Override
protected void error(String message) {
- godotPaymentV3.callbackFailConsume(message);
+ godotPayment.callbackFailConsume(message);
}
}
.consume(sku);
@@ -387,9 +386,9 @@ public class PaymentsManager {
if (!skuDetails.containsKey("DETAILS_LIST")) {
int response = getResponseCodeFromBundle(skuDetails);
if (response != BILLING_RESPONSE_RESULT_OK) {
- godotPaymentV3.errorSkuDetail(getResponseDesc(response));
+ godotPayment.errorSkuDetail(getResponseDesc(response));
} else {
- godotPaymentV3.errorSkuDetail("No error but no detail list.");
+ godotPayment.errorSkuDetail("No error but no detail list.");
}
return;
}
@@ -398,22 +397,22 @@ public class PaymentsManager {
for (String thisResponse : responseList) {
Log.d("godot", "response = " + thisResponse);
- godotPaymentV3.addSkuDetail(thisResponse);
+ godotPayment.addSkuDetail(thisResponse);
}
} catch (RemoteException e) {
e.printStackTrace();
- godotPaymentV3.errorSkuDetail("RemoteException error!");
+ godotPayment.errorSkuDetail("RemoteException error!");
}
}
- godotPaymentV3.completeSkuDetail();
+ godotPayment.completeSkuDetail();
}
}))
.start();
}
- private GodotPaymentV3 godotPaymentV3;
+ private GodotPaymentInterface godotPayment;
- public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
- this.godotPaymentV3 = godotPaymentV3;
+ public void setBaseSingleton(GodotPaymentInterface godotPaymentInterface) {
+ this.godotPayment = godotPaymentInterface;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java
index dbb6b8a783..10c314aecf 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java
@@ -34,7 +34,6 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import java.lang.ref.WeakReference;
-import org.godotengine.godot.GodotPaymentV3;
import org.godotengine.godot.utils.HttpRequester;
import org.godotengine.godot.utils.RequestParams;
import org.json.JSONException;
@@ -43,7 +42,7 @@ import org.json.JSONObject;
abstract public class ValidateTask {
private Activity context;
- private GodotPaymentV3 godotPaymentsV3;
+ private GodotPaymentInterface godotPayments;
private ProgressDialog dialog;
private String mSku;
@@ -80,9 +79,9 @@ abstract public class ValidateTask {
}
}
- public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3) {
+ public ValidateTask(Activity context, GodotPaymentInterface godotPayments) {
this.context = context;
- this.godotPaymentsV3 = godotPaymentsV3;
+ this.godotPayments = godotPayments;
}
public void validatePurchase(final String sku) {
@@ -96,7 +95,7 @@ abstract public class ValidateTask {
private String doInBackground(String... params) {
PaymentsCache pc = new PaymentsCache(context);
- String url = godotPaymentsV3.getPurchaseValidationUrlPrefix();
+ String url = godotPayments.getPurchaseValidationUrlPrefix();
RequestParams param = new RequestParams();
param.setUrl(url);
param.put("ticket", pc.getConsumableValue("ticket", mSku));
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
new file mode 100644
index 0000000000..d5bf4fc70e
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -0,0 +1,256 @@
+/*************************************************************************/
+/* GodotPlugin.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot.plugin;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+import org.godotengine.godot.Godot;
+
+/**
+ * Base class for the Godot Android plugins.
+ * <p>
+ * A Godot Android plugin is a regular Android library packaged as an aar archive file with the following caveats:
+ * <p>
+ * - The library must have a dependency on the Godot Android library (godot-lib.aar).
+ * A stable version is available for each release.
+ * <p>
+ * - The library must include a <meta-data> tag in its manifest file setup as follow:
+ * <meta-data android:name="org.godotengine.plugin.v1.[PluginName]" android:value="[plugin.init.ClassFullName]" />
+ * Where:
+ * - 'PluginName' is the name of the plugin.
+ * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin class
+ * extending {@link GodotPlugin}.
+ *
+ * A plugin can also define and provide c/c++ gdnative libraries and nativescripts for the target
+ * app/game to leverage.
+ * The shared library for the gdnative library will be automatically bundled by the aar build
+ * system.
+ * Godot '*.gdnlib' and '*.gdns' resource files must however be manually defined in the project
+ * 'assets' directory. The recommended path for these resources in the 'assets' directory should be:
+ * 'godot/plugin/v1/[PluginName]/'
+ */
+public abstract class GodotPlugin {
+
+ private final Godot godot;
+
+ public GodotPlugin(Godot godot) {
+ this.godot = godot;
+ }
+
+ /**
+ * Provides access to the Godot engine.
+ */
+ protected Godot getGodot() {
+ return godot;
+ }
+
+ /**
+ * Register the plugin with Godot native code.
+ */
+ public final void onGLRegisterPluginWithGodotNative() {
+ nativeRegisterSingleton(getPluginName());
+
+ Class clazz = getClass();
+ Method[] methods = clazz.getDeclaredMethods();
+ for (Method method : methods) {
+ boolean found = false;
+
+ for (String s : getPluginMethods()) {
+ if (s.equals(method.getName())) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ continue;
+
+ List<String> ptr = new ArrayList<String>();
+
+ Class[] paramTypes = method.getParameterTypes();
+ for (Class c : paramTypes) {
+ ptr.add(c.getName());
+ }
+
+ String[] pt = new String[ptr.size()];
+ ptr.toArray(pt);
+
+ nativeRegisterMethod(getPluginName(), method.getName(), method.getReturnType().getName(), pt);
+ }
+
+ // Get the list of gdnative libraries to register.
+ Set<String> gdnativeLibrariesPaths = getPluginGDNativeLibrariesPaths();
+ if (!gdnativeLibrariesPaths.isEmpty()) {
+ nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0]));
+ }
+ }
+
+ /**
+ * Invoked once during the Godot Android initialization process after creation of the
+ * {@link org.godotengine.godot.GodotView} view.
+ * <p>
+ * This method should be overridden by descendants of this class that would like to add
+ * their view/layout to the Godot view hierarchy.
+ *
+ * @return the view to be included; null if no views should be included.
+ */
+ @Nullable
+ public View onMainCreateView(Activity activity) {
+ return null;
+ }
+
+ /**
+ * @see Activity#onActivityResult(int, int, Intent)
+ */
+ public void onMainActivityResult(int requestCode, int resultCode, Intent data) {
+ }
+
+ /**
+ * @see Activity#onRequestPermissionsResult(int, String[], int[])
+ */
+ public void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ }
+
+ /**
+ * @see Activity#onPause()
+ */
+ public void onMainPause() {}
+
+ /**
+ * @see Activity#onResume()
+ */
+ public void onMainResume() {}
+
+ /**
+ * @see Activity#onDestroy()
+ */
+ public void onMainDestroy() {}
+
+ /**
+ * @see Activity#onBackPressed()
+ */
+ public boolean onMainBackPressed() { return false; }
+
+ /**
+ * Invoked on the GL thread when the Godot main loop has started.
+ */
+ public void onGLGodotMainLoopStarted() {}
+
+ /**
+ * Invoked once per frame on the GL thread after the frame is drawn.
+ */
+ public void onGLDrawFrame(GL10 gl) {}
+
+ /**
+ * Called on the GL thread after the surface is created and whenever the OpenGL ES surface size
+ * changes.
+ */
+ public void onGLSurfaceChanged(GL10 gl, int width, int height) {}
+
+ /**
+ * Called on the GL thread when the surface is created or recreated.
+ */
+ public void onGLSurfaceCreated(GL10 gl, EGLConfig config) {}
+
+ /**
+ * Returns the name of the plugin.
+ * <p>
+ * This value must match the one listed in the plugin '<meta-data>' manifest entry.
+ */
+ @NonNull
+ public abstract String getPluginName();
+
+ /**
+ * Returns the list of methods to be exposed to Godot.
+ */
+ @NonNull
+ public abstract List<String> getPluginMethods();
+
+ /**
+ * Returns the paths for the plugin's gdnative libraries.
+ *
+ * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file.
+ */
+ @NonNull
+ protected Set<String> getPluginGDNativeLibrariesPaths() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Runs the specified action on the UI thread. If the current thread is the UI
+ * thread, then the action is executed immediately. If the current thread is
+ * not the UI thread, the action is posted to the event queue of the UI thread.
+ *
+ * @param action the action to run on the UI thread
+ */
+ protected void runOnUiThread(Runnable action) {
+ godot.runOnUiThread(action);
+ }
+
+ /**
+ * Queue the specified action to be run on the GL thread.
+ *
+ * @param action the action to run on the GL thread
+ */
+ protected void runOnGLThread(Runnable action) {
+ godot.runOnGLThread(action);
+ }
+
+ /**
+ * Used to setup a {@link GodotPlugin} instance.
+ * @param p_name Name of the instance.
+ */
+ private native void nativeRegisterSingleton(String p_name);
+
+ /**
+ * Used to complete registration of the {@link GodotPlugin} instance's methods.
+ * @param p_sname Name of the instance
+ * @param p_name Name of the method to register
+ * @param p_ret Return type of the registered method
+ * @param p_params Method parameters types
+ */
+ private native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params);
+
+ /**
+ * Used to register gdnative libraries bundled by the plugin.
+ * @param gdnlibPaths Paths to the libraries relative to the 'assets' directory.
+ */
+ private native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths);
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
new file mode 100644
index 0000000000..b6d949b7bf
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
@@ -0,0 +1,199 @@
+/*************************************************************************/
+/* GodotPluginRegistry.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot.plugin;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.godotengine.godot.Godot;
+
+/**
+ * Registry used to load and access the registered Godot Android plugins.
+ */
+public final class GodotPluginRegistry {
+
+ private static final String TAG = GodotPluginRegistry.class.getSimpleName();
+
+ private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1.";
+
+ /**
+ * Name for the metadata containing the list of Godot plugins to enable.
+ */
+ private static final String GODOT_ENABLED_PLUGINS_LABEL = "custom_template_plugins";
+
+ private static GodotPluginRegistry instance;
+ private final ConcurrentHashMap<String, GodotPlugin> registry;
+
+ private GodotPluginRegistry(Godot godot) {
+ registry = new ConcurrentHashMap<>();
+ loadPlugins(godot);
+ }
+
+ /**
+ * Retrieve the plugin tied to the given plugin name.
+ * @param pluginName Name of the plugin
+ * @return {@link GodotPlugin} handle if it exists, null otherwise.
+ */
+ @Nullable
+ public GodotPlugin getPlugin(String pluginName) {
+ return registry.get(pluginName);
+ }
+
+ /**
+ * Retrieve the full set of loaded plugins.
+ */
+ public Collection<GodotPlugin> getAllPlugins() {
+ return registry.values();
+ }
+
+ /**
+ * Parse the manifest file and load all included Godot Android plugins.
+ * <p>
+ * A plugin manifest entry is a '<meta-data>' tag setup as described in the {@link GodotPlugin}
+ * documentation.
+ *
+ * @param godot Godot instance
+ * @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance
+ * of each Godot Android plugins is available at runtime.
+ */
+ public static GodotPluginRegistry initializePluginRegistry(Godot godot) {
+ if (instance == null) {
+ instance = new GodotPluginRegistry(godot);
+ }
+
+ return instance;
+ }
+
+ /**
+ * Return the plugin registry if it's initialized.
+ * Throws a {@link IllegalStateException} exception if not.
+ *
+ * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method.
+ */
+ public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException {
+ if (instance == null) {
+ throw new IllegalStateException("Plugin registry hasn't been initialized.");
+ }
+
+ return instance;
+ }
+
+ private void loadPlugins(Godot godot) {
+ try {
+ ApplicationInfo appInfo = godot
+ .getPackageManager()
+ .getApplicationInfo(godot.getPackageName(), PackageManager.GET_META_DATA);
+ Bundle metaData = appInfo.metaData;
+ if (metaData == null || metaData.isEmpty()) {
+ return;
+ }
+
+ // When using the Godot editor for building and exporting the apk, this is used to check
+ // which plugins to enable since the custom build template may contain prebuilt plugins.
+ // When using a custom process to generate the apk, the metadata is not needed since
+ // it's assumed that the developer is aware of the dependencies included in the apk.
+ final Set<String> enabledPluginsSet;
+ if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) {
+ String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, "");
+ String[] enabledPluginsList = enabledPlugins.split(",");
+ if (enabledPluginsList.length == 0) {
+ // No plugins to enable. Aborting early.
+ return;
+ }
+
+ enabledPluginsSet = new HashSet<>();
+ for (String enabledPlugin : enabledPluginsList) {
+ enabledPluginsSet.add(enabledPlugin.trim());
+ }
+ } else {
+ enabledPluginsSet = null;
+ }
+
+ int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length();
+ for (String metaDataName : metaData.keySet()) {
+ // Parse the meta-data looking for entry with the Godot plugin name prefix.
+ if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) {
+ String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim();
+ if (enabledPluginsSet != null && !enabledPluginsSet.contains(pluginName)) {
+ Log.w(TAG, "Plugin " + pluginName + " is listed in the dependencies but is not enabled.");
+ continue;
+ }
+
+ // Retrieve the plugin class full name.
+ String pluginHandleClassFullName = metaData.getString(metaDataName);
+ if (!TextUtils.isEmpty(pluginHandleClassFullName)) {
+ try {
+ // Attempt to create the plugin init class via reflection.
+ @SuppressWarnings("unchecked")
+ Class<GodotPlugin> pluginClass = (Class<GodotPlugin>)Class
+ .forName(pluginHandleClassFullName);
+ Constructor<GodotPlugin> pluginConstructor = pluginClass
+ .getConstructor(Godot.class);
+ GodotPlugin pluginHandle = pluginConstructor.newInstance(godot);
+
+ // Load the plugin initializer into the registry using the plugin name
+ // as key.
+ if (!pluginName.equals(pluginHandle.getPluginName())) {
+ Log.w(TAG,
+ "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName());
+ }
+ registry.put(pluginName, pluginHandle);
+ } catch (ClassNotFoundException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ } catch (IllegalAccessException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ } catch (InstantiationException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ } catch (InvocationTargetException e) {
+ Log.w(TAG, "Unable to load Godot plugin " + pluginName, e);
+ }
+ } else {
+ Log.w(TAG, "Invalid plugin loader class for " + pluginName);
+ }
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable load Godot Android plugins from the manifest file.", e);
+ }
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
new file mode 100644
index 0000000000..67faad8ddd
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
@@ -0,0 +1,99 @@
+/*************************************************************************/
+/* VkRenderer.kt */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+@file:JvmName("VkRenderer")
+package org.godotengine.godot.vulkan
+
+import android.view.Surface
+
+/**
+ * Responsible to setting up and driving the Vulkan rendering logic.
+ *
+ * <h3>Threading</h3>
+ * The renderer will be called on a separate thread, so that rendering
+ * performance is decoupled from the UI thread. Clients typically need to
+ * communicate with the renderer from the UI thread, because that's where
+ * input events are received. Clients can communicate using any of the
+ * standard Java techniques for cross-thread communication, or they can
+ * use the [VkSurfaceView.queueOnVkThread] convenience method.
+ *
+ * @see [VkSurfaceView.startRenderer]
+ */
+internal class VkRenderer {
+
+ /**
+ * Called when the surface is created and signals the beginning of rendering.
+ */
+ fun onVkSurfaceCreated(surface: Surface) {
+ nativeOnVkSurfaceCreated(surface)
+ }
+
+ /**
+ * Called after the surface is created and whenever its size changes.
+ */
+ fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) {
+ nativeOnVkSurfaceChanged(surface, width, height)
+ }
+
+ /**
+ * Called to draw the current frame.
+ */
+ fun onVkDrawFrame() {
+ nativeOnVkDrawFrame()
+ }
+
+ /**
+ * Called when the rendering thread is resumed.
+ */
+ fun onVkResume() {
+ nativeOnVkResume()
+ }
+
+ /**
+ * Called when the rendering thread is paused.
+ */
+ fun onVkPause() {
+ nativeOnVkPause()
+ }
+
+ /**
+ * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic.
+ */
+ fun onVkDestroy() {
+ nativeOnVkDestroy()
+ }
+
+ private external fun nativeOnVkSurfaceCreated(surface: Surface)
+ private external fun nativeOnVkSurfaceChanged(surface: Surface, width: Int, height: Int)
+ private external fun nativeOnVkResume()
+ private external fun nativeOnVkDrawFrame()
+ private external fun nativeOnVkPause()
+ private external fun nativeOnVkDestroy()
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
new file mode 100644
index 0000000000..1c594f3201
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
@@ -0,0 +1,136 @@
+/*************************************************************************/
+/* VkSurfaceView.kt */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+@file:JvmName("VkSurfaceView")
+package org.godotengine.godot.vulkan
+
+import android.content.Context
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying Vulkan rendering.
+ * <p>
+ * A [VkSurfaceView] provides the following features:
+ * <p>
+ * <ul>
+ * <li>Manages a surface, which is a special piece of memory that can be
+ * composited into the Android view system.
+ * <li>Accepts a user-provided [VkRenderer] object that does the actual rendering.
+ * <li>Renders on a dedicated [VkThread] thread to decouple rendering performance from the
+ * UI thread.
+ * </ul>
+ */
+internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
+
+ companion object {
+ fun checkState(expression: Boolean, errorMessage: Any) {
+ check(expression) { errorMessage.toString() }
+ }
+ }
+
+ /**
+ * Thread used to drive the vulkan logic.
+ */
+ private val vkThread: VkThread by lazy {
+ VkThread(this, renderer)
+ }
+
+ /**
+ * Performs the actual rendering.
+ */
+ private lateinit var renderer: VkRenderer
+
+ init {
+ isClickable = true
+ holder.addCallback(this)
+ }
+
+ /**
+ * Set the [VkRenderer] associated with the view, and starts the thread that will drive the vulkan
+ * rendering.
+ *
+ * This method should be called once and only once in the life-cycle of [VkSurfaceView].
+ */
+ fun startRenderer(renderer: VkRenderer) {
+ checkState(!this::renderer.isInitialized, "startRenderer must only be invoked once")
+ this.renderer = renderer
+ vkThread.start()
+ }
+
+ /**
+ * Queues a runnable to be run on the Vulkan rendering thread.
+ *
+ * Must not be called before a [VkRenderer] has been set.
+ */
+ fun queueOnVkThread(runnable: Runnable) {
+ vkThread.queueEvent(runnable)
+ }
+
+ /**
+ * Resumes the rendering thread.
+ *
+ * Must not be called before a [VkRenderer] has been set.
+ */
+ fun onResume() {
+ vkThread.onResume()
+ }
+
+ /**
+ * Pauses the rendering thread.
+ *
+ * Must not be called before a [VkRenderer] has been set.
+ */
+ fun onPause() {
+ vkThread.onPause()
+ }
+
+ /**
+ * Tear down the rendering thread.
+ *
+ * Must not be called before a [VkRenderer] has been set.
+ */
+ fun onDestroy() {
+ vkThread.blockingExit()
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ vkThread.onSurfaceChanged(width, height)
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ vkThread.onSurfaceDestroyed()
+ }
+
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ vkThread.onSurfaceCreated()
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
new file mode 100644
index 0000000000..2e332840bf
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
@@ -0,0 +1,230 @@
+/*************************************************************************/
+/* VkThread.kt */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+@file:JvmName("VkThread")
+package org.godotengine.godot.vulkan
+
+import android.util.Log
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+/**
+ * Thread implementation for the [VkSurfaceView] onto which the vulkan logic is ran.
+ *
+ * The implementation is modeled after [android.opengl.GLSurfaceView]'s GLThread.
+ */
+internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vkRenderer: VkRenderer) : Thread(TAG) {
+
+ companion object {
+ private val TAG = VkThread::class.java.simpleName
+ }
+
+ /**
+ * Used to run events scheduled on the thread.
+ */
+ private val eventQueue = ArrayList<Runnable>()
+
+ /**
+ * Used to synchronize interaction with other threads (e.g: main thread).
+ */
+ private val lock = ReentrantLock()
+ private val lockCondition = lock.newCondition()
+
+ private var shouldExit = false
+ private var exited = false
+ private var rendererInitialized = false
+ private var rendererResumed = false
+ private var resumed = false
+ private var hasSurface = false
+ private var width = 0
+ private var height = 0
+
+ /**
+ * Determine when drawing can occur on the thread. This usually occurs after the
+ * [android.view.Surface] is available, the app is in a resumed state.
+ */
+ private val readyToDraw
+ get() = hasSurface && resumed
+
+ private fun threadExiting() {
+ lock.withLock {
+ exited = true
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Queue an event on the [VkThread].
+ */
+ fun queueEvent(event: Runnable) {
+ lock.withLock {
+ eventQueue.add(event)
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Request the thread to exit and block until it's done.
+ */
+ fun blockingExit() {
+ lock.withLock {
+ shouldExit = true
+ lockCondition.signalAll()
+ while (!exited) {
+ try {
+ Log.i(TAG, "Waiting on exit for $name")
+ lockCondition.await()
+ } catch (ex: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoked when the app resumes.
+ */
+ fun onResume() {
+ lock.withLock {
+ resumed = true
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Invoked when the app pauses.
+ */
+ fun onPause() {
+ lock.withLock {
+ resumed = false
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Invoked when the [android.view.Surface] has been created.
+ */
+ fun onSurfaceCreated() {
+ // This is a no op because surface creation will always be followed by surfaceChanged()
+ // which provide all the needed information.
+ }
+
+ /**
+ * Invoked following structural updates to [android.view.Surface].
+ */
+ fun onSurfaceChanged(width: Int, height: Int) {
+ lock.withLock {
+ hasSurface = true
+ this.width = width
+ this.height = height
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Invoked when the [android.view.Surface] is no longer available.
+ */
+ fun onSurfaceDestroyed() {
+ lock.withLock {
+ hasSurface = false
+ lockCondition.signalAll()
+ }
+ }
+
+ /**
+ * Thread loop modeled after [android.opengl.GLSurfaceView]'s GLThread.
+ */
+ override fun run() {
+ try {
+ while (true) {
+ var event: Runnable? = null
+ lock.withLock {
+ while (true) {
+ // Code path for exiting the thread loop.
+ if (shouldExit) {
+ vkRenderer.onVkDestroy()
+ return
+ }
+
+ // Check for events and execute them outside of the loop if found to avoid
+ // blocking the thread lifecycle by holding onto the lock.
+ if (eventQueue.isNotEmpty()) {
+ event = eventQueue.removeAt(0)
+ break;
+ }
+
+ if (readyToDraw) {
+ if (!rendererResumed) {
+ rendererResumed = true
+ vkRenderer.onVkResume()
+
+ if (!rendererInitialized) {
+ rendererInitialized = true
+ vkRenderer.onVkSurfaceCreated(vkSurfaceView.holder.surface)
+ }
+
+ vkRenderer.onVkSurfaceChanged(vkSurfaceView.holder.surface, width, height)
+ }
+
+ // Break out of the loop so drawing can occur without holding onto the lock.
+ break;
+ } else if (rendererResumed) {
+ // If we aren't ready to draw but are resumed, that means we either lost a surface
+ // or the app was paused.
+ rendererResumed = false
+ vkRenderer.onVkPause()
+ }
+ // We only reach this state if we are not ready to draw and have no queued events, so
+ // we wait.
+ // On state change, the thread will be awoken using the [lock] and [lockCondition], and
+ // we will resume execution.
+ lockCondition.await()
+ }
+ }
+
+ // Run queued event.
+ if (event != null) {
+ event?.run()
+ continue
+ }
+
+ // Draw only when there no more queued events.
+ vkRenderer.onVkDrawFrame()
+ }
+ } catch (ex: InterruptedException) {
+ Log.i(TAG, ex.message)
+ } catch (ex: IllegalStateException) {
+ Log.i(TAG, ex.message)
+ } finally {
+ threadExiting()
+ }
+ }
+
+}
diff --git a/platform/android/java/plugins/godotpayment/build.gradle b/platform/android/java/plugins/godotpayment/build.gradle
new file mode 100644
index 0000000000..4f376c4587
--- /dev/null
+++ b/platform/android/java/plugins/godotpayment/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion versions.compileSdk
+ buildToolsVersion versions.buildTools
+
+ defaultConfig {
+ minSdkVersion versions.minSdk
+ targetSdkVersion versions.targetSdk
+ }
+
+ libraryVariants.all { variant ->
+ variant.outputs.all { output ->
+ output.outputFileName = "GodotPayment.${variant.name}.aar"
+ }
+ }
+
+}
+
+dependencies {
+ implementation libraries.supportCoreUtils
+ implementation libraries.v4Support
+
+ if (rootProject.findProject(":lib")) {
+ compileOnly project(":lib")
+ } else if (rootProject.findProject(":godot:lib")) {
+ compileOnly project(":godot:lib")
+ } else {
+ compileOnly fileTree(dir: 'libs', include: ['godot-lib*.aar'])
+ }
+}
diff --git a/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml b/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..61afa03799
--- /dev/null
+++ b/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.godotengine.godot.plugin.payment">
+
+ <application>
+
+ <meta-data
+ android:name="org.godotengine.plugin.v1.GodotPayment"
+ android:value="org.godotengine.godot.plugin.payment.GodotPayment" />
+
+ </application>
+</manifest>
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java
index 93265d509f..4a6b611c4d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java
+++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* GodotPaymentV3.java */
+/* GodotPayment.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,20 +28,24 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-package org.godotengine.godot;
+package org.godotengine.godot.plugin.payment;
-import android.app.Activity;
+import android.support.annotation.NonNull;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import org.godotengine.godot.Dictionary;
+import org.godotengine.godot.Godot;
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.payments.GodotPaymentInterface;
import org.godotengine.godot.payments.PaymentsManager;
+import org.godotengine.godot.plugin.GodotPlugin;
import org.json.JSONException;
import org.json.JSONObject;
-public class GodotPaymentV3 extends Godot.SingletonBase {
+public class GodotPayment extends GodotPlugin implements GodotPaymentInterface {
- private Godot activity;
private Integer purchaseCallbackId = 0;
private String accessToken;
private String purchaseValidationUrlPrefix;
@@ -49,8 +53,15 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
private PaymentsManager mPaymentManager;
private Dictionary mSkuDetails = new Dictionary();
+ public GodotPayment(Godot godot) {
+ super(godot);
+ mPaymentManager = godot.getPaymentsManager();
+ mPaymentManager.setBaseSingleton(this);
+ }
+
+ @Override
public void purchase(final String sku, final String transactionId) {
- activity.runOnUiThread(new Runnable() {
+ runOnUiThread(new Runnable() {
@Override
public void run() {
mPaymentManager.requestPurchase(sku, transactionId);
@@ -58,21 +69,9 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
});
}
- static public Godot.SingletonBase initialize(Activity p_activity) {
-
- return new GodotPaymentV3(p_activity);
- }
-
- public GodotPaymentV3(Activity p_activity) {
-
- registerClass("GodotPayments", new String[] { "purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected" });
- activity = (Godot)p_activity;
- mPaymentManager = activity.getPaymentsManager();
- mPaymentManager.setBaseSingleton(this);
- }
-
+ @Override
public void consumeUnconsumedPurchases() {
- activity.runOnUiThread(new Runnable() {
+ runOnUiThread(new Runnable() {
@Override
public void run() {
mPaymentManager.consumeUnconsumedPurchases();
@@ -82,74 +81,91 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
private String signature;
+ @Override
public String getSignature() {
return this.signature;
}
+ @Override
public void callbackSuccess(String ticket, String signature, String sku) {
GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku });
}
+ @Override
public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) {
Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > " + ticket + "," + signature + "," + sku);
GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[] { ticket, signature, sku });
}
+ @Override
public void callbackSuccessNoUnconsumedPurchases() {
GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {});
}
+ @Override
public void callbackFailConsume(String message) {
GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message });
}
+ @Override
public void callbackFail(String message) {
GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message });
}
+ @Override
public void callbackCancel() {
GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {});
}
+ @Override
public void callbackAlreadyOwned(String sku) {
GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku });
}
+ @Override
public int getPurchaseCallbackId() {
return purchaseCallbackId;
}
+ @Override
public void setPurchaseCallbackId(int purchaseCallbackId) {
this.purchaseCallbackId = purchaseCallbackId;
}
+ @Override
public String getPurchaseValidationUrlPrefix() {
return this.purchaseValidationUrlPrefix;
}
+ @Override
public void setPurchaseValidationUrlPrefix(String url) {
this.purchaseValidationUrlPrefix = url;
}
+ @Override
public String getAccessToken() {
return accessToken;
}
+ @Override
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
+ @Override
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
+ @Override
public String getTransactionId() {
return this.transactionId;
}
// request purchased items are not consumed
+ @Override
public void requestPurchased() {
- activity.runOnUiThread(new Runnable() {
+ runOnUiThread(new Runnable() {
@Override
public void run() {
mPaymentManager.requestPurchased();
@@ -158,34 +174,41 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
}
// callback for requestPurchased()
+ @Override
public void callbackPurchased(String receipt, String signature, String sku) {
GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku });
}
+ @Override
public void callbackDisconnected() {
GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {});
}
+ @Override
public void callbackConnected() {
GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {});
}
// true if connected, false otherwise
+ @Override
public boolean isConnected() {
return mPaymentManager.isConnected();
}
// consume item automatically after purchase. default is true.
+ @Override
public void setAutoConsume(boolean autoConsume) {
mPaymentManager.setAutoConsume(autoConsume);
}
// consume a specific item
+ @Override
public void consume(String sku) {
mPaymentManager.consume(sku);
}
// query in app item detail info
+ @Override
public void querySkuDetails(String[] list) {
List<String> nKeys = Arrays.asList(list);
List<String> cKeys = Arrays.asList(mSkuDetails.get_keys());
@@ -202,6 +225,7 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
}
}
+ @Override
public void addSkuDetail(String itemJson) {
JSONObject o = null;
try {
@@ -220,11 +244,25 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
}
}
+ @Override
public void completeSkuDetail() {
GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails });
}
+ @Override
public void errorSkuDetail(String errorMessage) {
GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage });
}
+
+ @NonNull
+ @Override
+ public String getPluginName() {
+ return "GodotPayment";
+ }
+
+ @NonNull
+ @Override
+ public List<String> getPluginMethods() {
+ return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected");
+ }
}
diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle
index f6921c70aa..9536d3de6d 100644
--- a/platform/android/java/settings.gradle
+++ b/platform/android/java/settings.gradle
@@ -3,3 +3,4 @@ rootProject.name = "Godot"
include ':app'
include ':lib'
+include ':plugins:godotpayment'
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 900a452024..0b1d070441 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -33,14 +33,15 @@
#include "java_godot_wrapper.h"
#include "android/asset_manager_jni.h"
+#include "android_keys_utils.h"
#include "api/java_class_wrapper.h"
#include "audio_driver_jandroid.h"
#include "core/engine.h"
-#include "core/os/keyboard.h"
#include "core/project_settings.h"
#include "dir_access_jandroid.h"
#include "file_access_android.h"
#include "file_access_jandroid.h"
+#include "jni_utils.h"
#include "main/input_default.h"
#include "main/main.h"
#include "net_socket_android.h"
@@ -54,551 +55,6 @@ static OS_Android *os_android = NULL;
static GodotJavaWrapper *godot_java = NULL;
static GodotIOJavaWrapper *godot_io_java = NULL;
-struct jvalret {
-
- jobject obj;
- jvalue val;
- jvalret() { obj = NULL; }
-};
-
-jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject = false) {
-
- jvalret v;
-
- switch (p_type) {
-
- case Variant::BOOL: {
-
- if (force_jobject) {
- jclass bclass = env->FindClass("java/lang/Boolean");
- jmethodID ctor = env->GetMethodID(bclass, "<init>", "(Z)V");
- jvalue val;
- val.z = (bool)(*p_arg);
- jobject obj = env->NewObjectA(bclass, ctor, &val);
- v.val.l = obj;
- v.obj = obj;
- env->DeleteLocalRef(bclass);
- } else {
- v.val.z = *p_arg;
- };
- } break;
- case Variant::INT: {
-
- if (force_jobject) {
-
- jclass bclass = env->FindClass("java/lang/Integer");
- jmethodID ctor = env->GetMethodID(bclass, "<init>", "(I)V");
- jvalue val;
- val.i = (int)(*p_arg);
- jobject obj = env->NewObjectA(bclass, ctor, &val);
- v.val.l = obj;
- v.obj = obj;
- env->DeleteLocalRef(bclass);
-
- } else {
- v.val.i = *p_arg;
- };
- } break;
- case Variant::FLOAT: {
-
- if (force_jobject) {
-
- jclass bclass = env->FindClass("java/lang/Double");
- jmethodID ctor = env->GetMethodID(bclass, "<init>", "(D)V");
- jvalue val;
- val.d = (double)(*p_arg);
- jobject obj = env->NewObjectA(bclass, ctor, &val);
- v.val.l = obj;
- v.obj = obj;
- env->DeleteLocalRef(bclass);
-
- } else {
- v.val.f = *p_arg;
- };
- } break;
- case Variant::STRING: {
-
- String s = *p_arg;
- jstring jStr = env->NewStringUTF(s.utf8().get_data());
- v.val.l = jStr;
- v.obj = jStr;
- } break;
- case Variant::PACKED_STRING_ARRAY: {
-
- Vector<String> sarray = *p_arg;
- jobjectArray arr = env->NewObjectArray(sarray.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
-
- for (int j = 0; j < sarray.size(); j++) {
-
- jstring str = env->NewStringUTF(sarray[j].utf8().get_data());
- env->SetObjectArrayElement(arr, j, str);
- env->DeleteLocalRef(str);
- }
- v.val.l = arr;
- v.obj = arr;
-
- } break;
-
- case Variant::DICTIONARY: {
-
- Dictionary dict = *p_arg;
- jclass dclass = env->FindClass("org/godotengine/godot/Dictionary");
- jmethodID ctor = env->GetMethodID(dclass, "<init>", "()V");
- jobject jdict = env->NewObject(dclass, ctor);
-
- Array keys = dict.keys();
-
- jobjectArray jkeys = env->NewObjectArray(keys.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
- for (int j = 0; j < keys.size(); j++) {
- jstring str = env->NewStringUTF(String(keys[j]).utf8().get_data());
- env->SetObjectArrayElement(jkeys, j, str);
- env->DeleteLocalRef(str);
- };
-
- jmethodID set_keys = env->GetMethodID(dclass, "set_keys", "([Ljava/lang/String;)V");
- jvalue val;
- val.l = jkeys;
- env->CallVoidMethodA(jdict, set_keys, &val);
- env->DeleteLocalRef(jkeys);
-
- jobjectArray jvalues = env->NewObjectArray(keys.size(), env->FindClass("java/lang/Object"), NULL);
-
- for (int j = 0; j < keys.size(); j++) {
- Variant var = dict[keys[j]];
- jvalret v = _variant_to_jvalue(env, var.get_type(), &var, true);
- env->SetObjectArrayElement(jvalues, j, v.val.l);
- if (v.obj) {
- env->DeleteLocalRef(v.obj);
- }
- };
-
- jmethodID set_values = env->GetMethodID(dclass, "set_values", "([Ljava/lang/Object;)V");
- val.l = jvalues;
- env->CallVoidMethodA(jdict, set_values, &val);
- env->DeleteLocalRef(jvalues);
- env->DeleteLocalRef(dclass);
-
- v.val.l = jdict;
- v.obj = jdict;
- } break;
-
- case Variant::PACKED_INT32_ARRAY: {
-
- Vector<int> array = *p_arg;
- jintArray arr = env->NewIntArray(array.size());
- const int *r = array.ptr();
- env->SetIntArrayRegion(arr, 0, array.size(), r.ptr());
- v.val.l = arr;
- v.obj = arr;
-
- } break;
- case Variant::PACKED_BYTE_ARRAY: {
- Vector<uint8_t> array = *p_arg;
- jbyteArray arr = env->NewByteArray(array.size());
- const uint8_t *r = array.ptr();
- env->SetByteArrayRegion(arr, 0, array.size(), reinterpret_cast<const signed char *>(r.ptr()));
- v.val.l = arr;
- v.obj = arr;
-
- } break;
- case Variant::PACKED_FLOAT32_ARRAY: {
-
- Vector<float> array = *p_arg;
- jfloatArray arr = env->NewFloatArray(array.size());
- const float *r = array.ptr();
- env->SetFloatArrayRegion(arr, 0, array.size(), r.ptr());
- v.val.l = arr;
- v.obj = arr;
-
- } break;
-#ifndef _MSC_VER
-#warning This is missing 64 bits arrays, I have no idea how to do it in JNI
-#endif
-
- default: {
-
- v.val.i = 0;
- } break;
- }
- return v;
-}
-
-String _get_class_name(JNIEnv *env, jclass cls, bool *array) {
-
- jclass cclass = env->FindClass("java/lang/Class");
- jmethodID getName = env->GetMethodID(cclass, "getName", "()Ljava/lang/String;");
- jstring clsName = (jstring)env->CallObjectMethod(cls, getName);
-
- if (array) {
- jmethodID isArray = env->GetMethodID(cclass, "isArray", "()Z");
- jboolean isarr = env->CallBooleanMethod(cls, isArray);
- (*array) = isarr ? true : false;
- }
- String name = jstring_to_string(clsName, env);
- env->DeleteLocalRef(clsName);
-
- return name;
-}
-
-Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
-
- if (obj == NULL) {
- return Variant();
- }
-
- jclass c = env->GetObjectClass(obj);
- bool array;
- String name = _get_class_name(env, c, &array);
-
- if (name == "java.lang.String") {
-
- return jstring_to_string((jstring)obj, env);
- };
-
- if (name == "[Ljava.lang.String;") {
-
- jobjectArray arr = (jobjectArray)obj;
- int stringCount = env->GetArrayLength(arr);
- Vector<String> sarr;
-
- for (int i = 0; i < stringCount; i++) {
- jstring string = (jstring)env->GetObjectArrayElement(arr, i);
- sarr.push_back(jstring_to_string(string, env));
- env->DeleteLocalRef(string);
- }
-
- return sarr;
- };
-
- if (name == "java.lang.Boolean") {
-
- jmethodID boolValue = env->GetMethodID(c, "booleanValue", "()Z");
- bool ret = env->CallBooleanMethod(obj, boolValue);
- return ret;
- };
-
- if (name == "java.lang.Integer" || name == "java.lang.Long") {
-
- jclass nclass = env->FindClass("java/lang/Number");
- jmethodID longValue = env->GetMethodID(nclass, "longValue", "()J");
- jlong ret = env->CallLongMethod(obj, longValue);
- return ret;
- };
-
- if (name == "[I") {
-
- jintArray arr = (jintArray)obj;
- int fCount = env->GetArrayLength(arr);
- Vector<int> sarr;
- sarr.resize(fCount);
-
- int *w = sarr.ptrw();
- env->GetIntArrayRegion(arr, 0, fCount, w.ptr());
- w.release();
- return sarr;
- };
-
- if (name == "[B") {
-
- jbyteArray arr = (jbyteArray)obj;
- int fCount = env->GetArrayLength(arr);
- Vector<uint8_t> sarr;
- sarr.resize(fCount);
-
- uint8_t *w = sarr.ptrw();
- env->GetByteArrayRegion(arr, 0, fCount, reinterpret_cast<signed char *>(w.ptr()));
- w.release();
- return sarr;
- };
-
- if (name == "java.lang.Float" || name == "java.lang.Double") {
-
- jclass nclass = env->FindClass("java/lang/Number");
- jmethodID doubleValue = env->GetMethodID(nclass, "doubleValue", "()D");
- double ret = env->CallDoubleMethod(obj, doubleValue);
- return ret;
- };
-
- if (name == "[D") {
-
- jdoubleArray arr = (jdoubleArray)obj;
- int fCount = env->GetArrayLength(arr);
- PackedFloat32Array sarr;
- sarr.resize(fCount);
-
- real_t *w = sarr.ptrw();
-
- for (int i = 0; i < fCount; i++) {
-
- double n;
- env->GetDoubleArrayRegion(arr, i, 1, &n);
- w.ptr()[i] = n;
- };
- return sarr;
- };
-
- if (name == "[F") {
-
- jfloatArray arr = (jfloatArray)obj;
- int fCount = env->GetArrayLength(arr);
- PackedFloat32Array sarr;
- sarr.resize(fCount);
-
- real_t *w = sarr.ptrw();
-
- for (int i = 0; i < fCount; i++) {
-
- float n;
- env->GetFloatArrayRegion(arr, i, 1, &n);
- w.ptr()[i] = n;
- };
- return sarr;
- };
-
- if (name == "[Ljava.lang.Object;") {
-
- jobjectArray arr = (jobjectArray)obj;
- int objCount = env->GetArrayLength(arr);
- Array varr;
-
- for (int i = 0; i < objCount; i++) {
- jobject jobj = env->GetObjectArrayElement(arr, i);
- Variant v = _jobject_to_variant(env, jobj);
- varr.push_back(v);
- env->DeleteLocalRef(jobj);
- }
-
- return varr;
- };
-
- if (name == "java.util.HashMap" || name == "org.godotengine.godot.Dictionary") {
-
- Dictionary ret;
- jclass oclass = c;
- jmethodID get_keys = env->GetMethodID(oclass, "get_keys", "()[Ljava/lang/String;");
- jobjectArray arr = (jobjectArray)env->CallObjectMethod(obj, get_keys);
-
- PackedStringArray keys = _jobject_to_variant(env, arr);
- env->DeleteLocalRef(arr);
-
- jmethodID get_values = env->GetMethodID(oclass, "get_values", "()[Ljava/lang/Object;");
- arr = (jobjectArray)env->CallObjectMethod(obj, get_values);
-
- Array vals = _jobject_to_variant(env, arr);
- env->DeleteLocalRef(arr);
-
- for (int i = 0; i < keys.size(); i++) {
-
- ret[keys[i]] = vals[i];
- };
-
- return ret;
- };
-
- env->DeleteLocalRef(c);
-
- return Variant();
-}
-
-class JNISingleton : public Object {
-
- GDCLASS(JNISingleton, Object);
-
- struct MethodData {
-
- jmethodID method;
- Variant::Type ret_type;
- Vector<Variant::Type> argtypes;
- };
-
- jobject instance;
- Map<StringName, MethodData> method_map;
-
-public:
- virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
-
- ERR_FAIL_COND_V(!instance, Variant());
-
- r_error.error = Callable::CallError::CALL_OK;
-
- Map<StringName, MethodData>::Element *E = method_map.find(p_method);
- if (!E) {
-
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return Variant();
- }
-
- int ac = E->get().argtypes.size();
- if (ac < p_argcount) {
-
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = ac;
- return Variant();
- }
-
- if (ac > p_argcount) {
-
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.argument = ac;
- return Variant();
- }
-
- for (int i = 0; i < p_argcount; i++) {
-
- if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
-
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = i;
- r_error.expected = E->get().argtypes[i];
- }
- }
-
- jvalue *v = NULL;
-
- if (p_argcount) {
-
- v = (jvalue *)alloca(sizeof(jvalue) * p_argcount);
- }
-
- JNIEnv *env = ThreadAndroid::get_env();
-
- int res = env->PushLocalFrame(16);
-
- ERR_FAIL_COND_V(res != 0, Variant());
-
- List<jobject> to_erase;
- for (int i = 0; i < p_argcount; i++) {
-
- jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]);
- v[i] = vr.val;
- if (vr.obj)
- to_erase.push_back(vr.obj);
- }
-
- Variant ret;
-
- switch (E->get().ret_type) {
-
- case Variant::NIL: {
-
- env->CallVoidMethodA(instance, E->get().method, v);
- } break;
- case Variant::BOOL: {
-
- ret = env->CallBooleanMethodA(instance, E->get().method, v) == JNI_TRUE;
- } break;
- case Variant::INT: {
-
- ret = env->CallIntMethodA(instance, E->get().method, v);
- } break;
- case Variant::FLOAT: {
-
- ret = env->CallFloatMethodA(instance, E->get().method, v);
- } break;
- case Variant::STRING: {
-
- jobject o = env->CallObjectMethodA(instance, E->get().method, v);
- ret = jstring_to_string((jstring)o, env);
- env->DeleteLocalRef(o);
- } break;
- case Variant::PACKED_STRING_ARRAY: {
-
- jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- ret = _jobject_to_variant(env, arr);
-
- env->DeleteLocalRef(arr);
- } break;
- case Variant::PACKED_INT32_ARRAY: {
-
- jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- int fCount = env->GetArrayLength(arr);
- Vector<int> sarr;
- sarr.resize(fCount);
-
- int *w = sarr.ptrw();
- env->GetIntArrayRegion(arr, 0, fCount, w.ptr());
- w.release();
- ret = sarr;
- env->DeleteLocalRef(arr);
- } break;
- case Variant::PACKED_FLOAT32_ARRAY: {
-
- jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- int fCount = env->GetArrayLength(arr);
- Vector<float> sarr;
- sarr.resize(fCount);
-
- float *w = sarr.ptrw();
- env->GetFloatArrayRegion(arr, 0, fCount, w.ptr());
- w.release();
- ret = sarr;
- env->DeleteLocalRef(arr);
- } break;
-
-#ifndef _MSC_VER
-#warning This is missing 64 bits arrays, I have no idea how to do it in JNI
-#endif
- case Variant::DICTIONARY: {
-
- jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
- ret = _jobject_to_variant(env, obj);
- env->DeleteLocalRef(obj);
-
- } break;
- default: {
-
- env->PopLocalFrame(NULL);
- ERR_FAIL_V(Variant());
- } break;
- }
-
- while (to_erase.size()) {
- env->DeleteLocalRef(to_erase.front()->get());
- to_erase.pop_front();
- }
-
- env->PopLocalFrame(NULL);
-
- return ret;
- }
-
- jobject get_instance() const {
-
- return instance;
- }
- void set_instance(jobject p_instance) {
-
- instance = p_instance;
- }
-
- void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) {
-
- MethodData md;
- md.method = p_method;
- md.argtypes = p_args;
- md.ret_type = p_ret_type;
- method_map[p_name] = md;
- }
-
- JNISingleton() {
- instance = NULL;
- }
-};
-
-struct TST {
-
- int a;
- TST() {
-
- a = 5;
- }
-};
-
-TST tst;
-
static bool initialized = false;
static int step = 0;
@@ -607,20 +63,16 @@ static Vector3 accelerometer;
static Vector3 gravity;
static Vector3 magnetometer;
static Vector3 gyroscope;
-static HashMap<String, JNISingleton *> jni_singletons;
-// virtual Error native_video_play(String p_path);
-// virtual bool native_video_is_playing();
-// virtual void native_video_pause();
-// virtual void native_video_stop();
+extern "C" {
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jobject obj, jint p_height) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height) {
if (godot_io_java) {
godot_io_java->set_vk_height(p_height);
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion) {
initialized = true;
@@ -653,7 +105,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en
godot_java->on_video_init(env);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jobject obj, jobject activity) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz, jobject activity) {
// lets cleanup
if (godot_io_java) {
delete godot_io_java;
@@ -666,47 +118,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env
}
}
-static void _initialize_java_modules() {
-
- if (!ProjectSettings::get_singleton()->has_setting("android/modules")) {
- return;
- }
-
- String modules = ProjectSettings::get_singleton()->get("android/modules");
- modules = modules.strip_edges();
- if (modules == String()) {
- return;
- }
- Vector<String> mods = modules.split(",", false);
-
- if (mods.size()) {
- jobject cls = godot_java->get_class_loader();
-
- // TODO create wrapper for class loader
-
- JNIEnv *env = ThreadAndroid::get_env();
- jclass classLoader = env->FindClass("java/lang/ClassLoader");
- jmethodID findClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
-
- for (int i = 0; i < mods.size(); i++) {
-
- String m = mods[i];
-
- print_line("Loading Android module: " + m);
- jstring strClassName = env->NewStringUTF(m.utf8().get_data());
- jclass singletonClass = (jclass)env->CallObjectMethod(cls, findClass, strClassName);
- ERR_CONTINUE_MSG(!singletonClass, "Couldn't find singleton for class: " + m + ".");
-
- jmethodID initialize = env->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lorg/godotengine/godot/Godot$SingletonBase;");
- ERR_CONTINUE_MSG(!initialize, "Couldn't find proper initialize function 'public static Godot.SingletonBase Class::initialize(Activity p_activity)' initializer for singleton class: " + m + ".");
-
- jobject obj = env->CallStaticObjectMethod(singletonClass, initialize, godot_java->get_activity());
- env->NewGlobalRef(obj);
- }
- }
-}
-
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) {
ThreadAndroid::setup_thread();
const char **cmdline = NULL;
@@ -746,16 +158,15 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo
}
java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
- _initialize_java_modules();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jint width, jint height) {
if (os_android)
os_android->set_display_size(Size2(width, height));
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jboolean p_32_bits) {
if (os_android) {
if (step == 0) {
@@ -770,14 +181,14 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) {
if (step == 0)
return;
os_android->main_loop_request_go_back();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) {
if (step == -1)
return;
@@ -796,6 +207,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, job
}
os_android->main_loop_begin();
+ godot_java->on_gl_godot_main_loop_started(env);
++step;
}
@@ -810,7 +222,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, job
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint count, jintArray positions) {
if (step == 0)
return;
@@ -834,282 +246,28 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jo
*/
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jint p_x, jint p_y) {
if (step == 0)
return;
os_android->process_hover(p_type, Point2(p_x, p_y));
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jobject obj, jint p_x, jint p_y) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jclass clazz, jint p_x, jint p_y) {
if (step == 0)
return;
os_android->process_double_tap(Point2(p_x, p_y));
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jobject obj, jint p_x, jint p_y) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y) {
if (step == 0)
return;
os_android->process_scroll(Point2(p_x, p_y));
}
-/*
- * Android Key codes.
- */
-enum {
- AKEYCODE_UNKNOWN = 0,
- AKEYCODE_SOFT_LEFT = 1,
- AKEYCODE_SOFT_RIGHT = 2,
- AKEYCODE_HOME = 3,
- AKEYCODE_BACK = 4,
- AKEYCODE_CALL = 5,
- AKEYCODE_ENDCALL = 6,
- AKEYCODE_0 = 7,
- AKEYCODE_1 = 8,
- AKEYCODE_2 = 9,
- AKEYCODE_3 = 10,
- AKEYCODE_4 = 11,
- AKEYCODE_5 = 12,
- AKEYCODE_6 = 13,
- AKEYCODE_7 = 14,
- AKEYCODE_8 = 15,
- AKEYCODE_9 = 16,
- AKEYCODE_STAR = 17,
- AKEYCODE_POUND = 18,
- AKEYCODE_DPAD_UP = 19,
- AKEYCODE_DPAD_DOWN = 20,
- AKEYCODE_DPAD_LEFT = 21,
- AKEYCODE_DPAD_RIGHT = 22,
- AKEYCODE_DPAD_CENTER = 23,
- AKEYCODE_VOLUME_UP = 24,
- AKEYCODE_VOLUME_DOWN = 25,
- AKEYCODE_POWER = 26,
- AKEYCODE_CAMERA = 27,
- AKEYCODE_CLEAR = 28,
- AKEYCODE_A = 29,
- AKEYCODE_B = 30,
- AKEYCODE_C = 31,
- AKEYCODE_D = 32,
- AKEYCODE_E = 33,
- AKEYCODE_F = 34,
- AKEYCODE_G = 35,
- AKEYCODE_H = 36,
- AKEYCODE_I = 37,
- AKEYCODE_J = 38,
- AKEYCODE_K = 39,
- AKEYCODE_L = 40,
- AKEYCODE_M = 41,
- AKEYCODE_N = 42,
- AKEYCODE_O = 43,
- AKEYCODE_P = 44,
- AKEYCODE_Q = 45,
- AKEYCODE_R = 46,
- AKEYCODE_S = 47,
- AKEYCODE_T = 48,
- AKEYCODE_U = 49,
- AKEYCODE_V = 50,
- AKEYCODE_W = 51,
- AKEYCODE_X = 52,
- AKEYCODE_Y = 53,
- AKEYCODE_Z = 54,
- AKEYCODE_COMMA = 55,
- AKEYCODE_PERIOD = 56,
- AKEYCODE_ALT_LEFT = 57,
- AKEYCODE_ALT_RIGHT = 58,
- AKEYCODE_SHIFT_LEFT = 59,
- AKEYCODE_SHIFT_RIGHT = 60,
- AKEYCODE_TAB = 61,
- AKEYCODE_SPACE = 62,
- AKEYCODE_SYM = 63,
- AKEYCODE_EXPLORER = 64,
- AKEYCODE_ENVELOPE = 65,
- AKEYCODE_ENTER = 66,
- AKEYCODE_DEL = 67,
- AKEYCODE_GRAVE = 68,
- AKEYCODE_MINUS = 69,
- AKEYCODE_EQUALS = 70,
- AKEYCODE_LEFT_BRACKET = 71,
- AKEYCODE_RIGHT_BRACKET = 72,
- AKEYCODE_BACKSLASH = 73,
- AKEYCODE_SEMICOLON = 74,
- AKEYCODE_APOSTROPHE = 75,
- AKEYCODE_SLASH = 76,
- AKEYCODE_AT = 77,
- AKEYCODE_NUM = 78,
- AKEYCODE_HEADSETHOOK = 79,
- AKEYCODE_FOCUS = 80, // *Camera* focus
- AKEYCODE_PLUS = 81,
- AKEYCODE_MENU = 82,
- AKEYCODE_NOTIFICATION = 83,
- AKEYCODE_SEARCH = 84,
- AKEYCODE_MEDIA_PLAY_PAUSE = 85,
- AKEYCODE_MEDIA_STOP = 86,
- AKEYCODE_MEDIA_NEXT = 87,
- AKEYCODE_MEDIA_PREVIOUS = 88,
- AKEYCODE_MEDIA_REWIND = 89,
- AKEYCODE_MEDIA_FAST_FORWARD = 90,
- AKEYCODE_MUTE = 91,
- AKEYCODE_PAGE_UP = 92,
- AKEYCODE_PAGE_DOWN = 93,
- AKEYCODE_PICTSYMBOLS = 94,
- AKEYCODE_SWITCH_CHARSET = 95,
- AKEYCODE_BUTTON_A = 96,
- AKEYCODE_BUTTON_B = 97,
- AKEYCODE_BUTTON_C = 98,
- AKEYCODE_BUTTON_X = 99,
- AKEYCODE_BUTTON_Y = 100,
- AKEYCODE_BUTTON_Z = 101,
- AKEYCODE_BUTTON_L1 = 102,
- AKEYCODE_BUTTON_R1 = 103,
- AKEYCODE_BUTTON_L2 = 104,
- AKEYCODE_BUTTON_R2 = 105,
- AKEYCODE_BUTTON_THUMBL = 106,
- AKEYCODE_BUTTON_THUMBR = 107,
- AKEYCODE_BUTTON_START = 108,
- AKEYCODE_BUTTON_SELECT = 109,
- AKEYCODE_BUTTON_MODE = 110,
-
- // NOTE: If you add a new keycode here you must also add it to several other files.
- // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
-};
-
-struct _WinTranslatePair {
-
- unsigned int keysym;
- unsigned int keycode;
-};
-
-static _WinTranslatePair _ak_to_keycode[] = {
- { KEY_TAB, AKEYCODE_TAB },
- { KEY_ENTER, AKEYCODE_ENTER },
- { KEY_SHIFT, AKEYCODE_SHIFT_LEFT },
- { KEY_SHIFT, AKEYCODE_SHIFT_RIGHT },
- { KEY_ALT, AKEYCODE_ALT_LEFT },
- { KEY_ALT, AKEYCODE_ALT_RIGHT },
- { KEY_MENU, AKEYCODE_MENU },
- { KEY_PAUSE, AKEYCODE_MEDIA_PLAY_PAUSE },
- { KEY_ESCAPE, AKEYCODE_BACK },
- { KEY_SPACE, AKEYCODE_SPACE },
- { KEY_PAGEUP, AKEYCODE_PAGE_UP },
- { KEY_PAGEDOWN, AKEYCODE_PAGE_DOWN },
- { KEY_HOME, AKEYCODE_HOME }, //(0x24)
- { KEY_LEFT, AKEYCODE_DPAD_LEFT },
- { KEY_UP, AKEYCODE_DPAD_UP },
- { KEY_RIGHT, AKEYCODE_DPAD_RIGHT },
- { KEY_DOWN, AKEYCODE_DPAD_DOWN },
- { KEY_PERIODCENTERED, AKEYCODE_DPAD_CENTER },
- { KEY_BACKSPACE, AKEYCODE_DEL },
- { KEY_0, AKEYCODE_0 }, ////0 key
- { KEY_1, AKEYCODE_1 }, ////1 key
- { KEY_2, AKEYCODE_2 }, ////2 key
- { KEY_3, AKEYCODE_3 }, ////3 key
- { KEY_4, AKEYCODE_4 }, ////4 key
- { KEY_5, AKEYCODE_5 }, ////5 key
- { KEY_6, AKEYCODE_6 }, ////6 key
- { KEY_7, AKEYCODE_7 }, ////7 key
- { KEY_8, AKEYCODE_8 }, ////8 key
- { KEY_9, AKEYCODE_9 }, ////9 key
- { KEY_A, AKEYCODE_A }, ////A key
- { KEY_B, AKEYCODE_B }, ////B key
- { KEY_C, AKEYCODE_C }, ////C key
- { KEY_D, AKEYCODE_D }, ////D key
- { KEY_E, AKEYCODE_E }, ////E key
- { KEY_F, AKEYCODE_F }, ////F key
- { KEY_G, AKEYCODE_G }, ////G key
- { KEY_H, AKEYCODE_H }, ////H key
- { KEY_I, AKEYCODE_I }, ////I key
- { KEY_J, AKEYCODE_J }, ////J key
- { KEY_K, AKEYCODE_K }, ////K key
- { KEY_L, AKEYCODE_L }, ////L key
- { KEY_M, AKEYCODE_M }, ////M key
- { KEY_N, AKEYCODE_N }, ////N key
- { KEY_O, AKEYCODE_O }, ////O key
- { KEY_P, AKEYCODE_P }, ////P key
- { KEY_Q, AKEYCODE_Q }, ////Q key
- { KEY_R, AKEYCODE_R }, ////R key
- { KEY_S, AKEYCODE_S }, ////S key
- { KEY_T, AKEYCODE_T }, ////T key
- { KEY_U, AKEYCODE_U }, ////U key
- { KEY_V, AKEYCODE_V }, ////V key
- { KEY_W, AKEYCODE_W }, ////W key
- { KEY_X, AKEYCODE_X }, ////X key
- { KEY_Y, AKEYCODE_Y }, ////Y key
- { KEY_Z, AKEYCODE_Z }, ////Z key
- { KEY_HOMEPAGE, AKEYCODE_EXPLORER },
- { KEY_LAUNCH0, AKEYCODE_BUTTON_A },
- { KEY_LAUNCH1, AKEYCODE_BUTTON_B },
- { KEY_LAUNCH2, AKEYCODE_BUTTON_C },
- { KEY_LAUNCH3, AKEYCODE_BUTTON_X },
- { KEY_LAUNCH4, AKEYCODE_BUTTON_Y },
- { KEY_LAUNCH5, AKEYCODE_BUTTON_Z },
- { KEY_LAUNCH6, AKEYCODE_BUTTON_L1 },
- { KEY_LAUNCH7, AKEYCODE_BUTTON_R1 },
- { KEY_LAUNCH8, AKEYCODE_BUTTON_L2 },
- { KEY_LAUNCH9, AKEYCODE_BUTTON_R2 },
- { KEY_LAUNCHA, AKEYCODE_BUTTON_THUMBL },
- { KEY_LAUNCHB, AKEYCODE_BUTTON_THUMBR },
- { KEY_LAUNCHC, AKEYCODE_BUTTON_START },
- { KEY_LAUNCHD, AKEYCODE_BUTTON_SELECT },
- { KEY_LAUNCHE, AKEYCODE_BUTTON_MODE },
- { KEY_VOLUMEMUTE, AKEYCODE_MUTE },
- { KEY_VOLUMEDOWN, AKEYCODE_VOLUME_DOWN },
- { KEY_VOLUMEUP, AKEYCODE_VOLUME_UP },
- { KEY_BACK, AKEYCODE_MEDIA_REWIND },
- { KEY_FORWARD, AKEYCODE_MEDIA_FAST_FORWARD },
- { KEY_MEDIANEXT, AKEYCODE_MEDIA_NEXT },
- { KEY_MEDIAPREVIOUS, AKEYCODE_MEDIA_PREVIOUS },
- { KEY_MEDIASTOP, AKEYCODE_MEDIA_STOP },
- { KEY_PLUS, AKEYCODE_PLUS },
- { KEY_EQUAL, AKEYCODE_EQUALS }, // the '+' key
- { KEY_COMMA, AKEYCODE_COMMA }, // the ',' key
- { KEY_MINUS, AKEYCODE_MINUS }, // the '-' key
- { KEY_SLASH, AKEYCODE_SLASH }, // the '/?' key
- { KEY_BACKSLASH, AKEYCODE_BACKSLASH },
- { KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET },
- { KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET },
- { KEY_UNKNOWN, 0 }
-};
-/*
-TODO: map these android key:
- AKEYCODE_SOFT_LEFT = 1,
- AKEYCODE_SOFT_RIGHT = 2,
- AKEYCODE_CALL = 5,
- AKEYCODE_ENDCALL = 6,
- AKEYCODE_STAR = 17,
- AKEYCODE_POUND = 18,
- AKEYCODE_POWER = 26,
- AKEYCODE_CAMERA = 27,
- AKEYCODE_CLEAR = 28,
- AKEYCODE_SYM = 63,
- AKEYCODE_ENVELOPE = 65,
- AKEYCODE_GRAVE = 68,
- AKEYCODE_SEMICOLON = 74,
- AKEYCODE_APOSTROPHE = 75,
- AKEYCODE_AT = 77,
- AKEYCODE_NUM = 78,
- AKEYCODE_HEADSETHOOK = 79,
- AKEYCODE_FOCUS = 80, // *Camera* focus
- AKEYCODE_NOTIFICATION = 83,
- AKEYCODE_SEARCH = 84,
- AKEYCODE_PICTSYMBOLS = 94,
- AKEYCODE_SWITCH_CHARSET = 95,
-*/
-
-static unsigned int android_get_keysym(unsigned int p_code) {
- for (int i = 0; _ak_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
-
- if (_ak_to_keycode[i].keycode == p_code) {
-
- return _ak_to_keycode[i].keysym;
- }
- }
-
- return KEY_UNKNOWN;
-}
-
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, jboolean p_pressed) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) {
if (step == 0)
return;
@@ -1122,7 +280,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env
os_android->process_joy_event(jevent);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value) {
if (step == 0)
return;
@@ -1135,7 +293,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env,
os_android->process_joy_event(jevent);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jobject obj, jint p_device, jint p_hat_x, jint p_hat_y) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y) {
if (step == 0)
return;
@@ -1160,58 +318,59 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, j
os_android->process_joy_event(jevent);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jobject obj, jint p_device, jboolean p_connected, jstring p_name) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name) {
if (os_android) {
String name = jstring_to_string(p_name, env);
os_android->joy_connection_changed(p_device, p_connected, name);
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed) {
if (step == 0)
return;
Ref<InputEventKey> ievent;
ievent.instance();
int val = p_unicode_char;
- int scancode = android_get_keysym(p_scancode);
- ievent->set_scancode(scancode);
+ int keycode = android_get_keysym(p_keycode);
+ int phy_keycode = android_get_keysym(p_scancode);
+ ievent->set_keycode(keycode);
+ ievent->set_physical_keycode(phy_keycode);
ievent->set_unicode(val);
ievent->set_pressed(p_pressed);
if (val == '\n') {
- ievent->set_scancode(KEY_ENTER);
+ ievent->set_keycode(KEY_ENTER);
} else if (val == 61448) {
- ievent->set_scancode(KEY_BACKSPACE);
+ ievent->set_keycode(KEY_BACKSPACE);
ievent->set_unicode(KEY_BACKSPACE);
} else if (val == 61453) {
- ievent->set_scancode(KEY_ENTER);
+ ievent->set_keycode(KEY_ENTER);
ievent->set_unicode(KEY_ENTER);
- } else if (p_scancode == 4) {
-
+ } else if (p_keycode == 4) {
os_android->main_loop_request_go_back();
}
os_android->process_event(ievent);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
accelerometer = Vector3(x, y, z);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
gravity = Vector3(x, y, z);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
magnetometer = Vector3(x, y, z);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
gyroscope = Vector3(x, y, z);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jobject obj) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) {
if (step == 0)
return;
@@ -1219,7 +378,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env,
os_android->main_loop_focusin();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jobject obj) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) {
if (step == 0)
return;
@@ -1227,134 +386,22 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env,
os_android->main_loop_focusout();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jobject obj) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz) {
ThreadAndroid::setup_thread();
AudioDriverAndroid::thread_func(env);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jobject obj, jstring name, jobject p_object) {
-
- String singname = jstring_to_string(name, env);
- JNISingleton *s = memnew(JNISingleton);
- s->set_instance(env->NewGlobalRef(p_object));
- jni_singletons[singname] = s;
-
- Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
- ProjectSettings::get_singleton()->set(singname, s);
-}
-
-static Variant::Type get_jni_type(const String &p_type) {
-
- static struct {
- const char *name;
- Variant::Type type;
- } _type_to_vtype[] = {
- { "void", Variant::NIL },
- { "boolean", Variant::BOOL },
- { "int", Variant::INT },
- { "float", Variant::FLOAT },
- { "double", Variant::FLOAT },
- { "java.lang.String", Variant::STRING },
- { "[I", Variant::PACKED_INT32_ARRAY },
- { "[B", Variant::PACKED_BYTE_ARRAY },
- { "[F", Variant::PACKED_FLOAT32_ARRAY },
- { "[Ljava.lang.String;", Variant::PACKED_STRING_ARRAY },
- { "org.godotengine.godot.Dictionary", Variant::DICTIONARY },
- { NULL, Variant::NIL }
- };
-
- int idx = 0;
-
- while (_type_to_vtype[idx].name) {
-
- if (p_type == _type_to_vtype[idx].name)
- return _type_to_vtype[idx].type;
-
- idx++;
- }
-
- return Variant::NIL;
-}
-
-static const char *get_jni_sig(const String &p_type) {
-
- static struct {
- const char *name;
- const char *sig;
- } _type_to_vtype[] = {
- { "void", "V" },
- { "boolean", "Z" },
- { "int", "I" },
- { "float", "F" },
- { "double", "D" },
- { "java.lang.String", "Ljava/lang/String;" },
- { "org.godotengine.godot.Dictionary", "Lorg/godotengine/godot/Dictionary;" },
- { "[I", "[I" },
- { "[B", "[B" },
- { "[F", "[F" },
- { "[Ljava.lang.String;", "[Ljava/lang/String;" },
- { NULL, "V" }
- };
-
- int idx = 0;
-
- while (_type_to_vtype[idx].name) {
-
- if (p_type == _type_to_vtype[idx].name)
- return _type_to_vtype[idx].sig;
-
- idx++;
- }
-
- return "Ljava/lang/Object;";
-}
-
-JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jobject obj, jstring path) {
+JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path) {
String js = jstring_to_string(path, env);
return env->NewStringUTF(ProjectSettings::get_singleton()->get(js).operator String().utf8().get_data());
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) {
-
- String singname = jstring_to_string(sname, env);
-
- ERR_FAIL_COND(!jni_singletons.has(singname));
-
- JNISingleton *s = jni_singletons.get(singname);
-
- String mname = jstring_to_string(name, env);
- String retval = jstring_to_string(ret, env);
- Vector<Variant::Type> types;
- String cs = "(";
-
- int stringCount = env->GetArrayLength(args);
-
- for (int i = 0; i < stringCount; i++) {
-
- jstring string = (jstring)env->GetObjectArrayElement(args, i);
- const String rawString = jstring_to_string(string, env);
- types.push_back(get_jni_type(rawString));
- cs += get_jni_sig(rawString);
- }
-
- cs += ")";
- cs += get_jni_sig(retval);
- jclass cls = env->GetObjectClass(s->get_instance());
- jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
- if (!mid) {
-
- print_line("Failed getting method ID " + mname);
- }
-
- s->add_method(mname, mid, types, get_jni_type(retval));
-}
-
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params) {
- Object *obj = ObjectDB::get_instance(ID);
+ Object *obj = ObjectDB::get_instance(ObjectID((uint64_t)ID));
ERR_FAIL_COND(!obj);
int res = env->PushLocalFrame(16);
@@ -1384,9 +431,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en
env->PopLocalFrame(NULL);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params) {
- Object *obj = ObjectDB::get_instance(ID);
+ Object *obj = ObjectDB::get_instance(ObjectID((uint64_t)ID));
ERR_FAIL_COND(!obj);
int res = env->PushLocalFrame(16);
@@ -1410,7 +457,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *
env->PopLocalFrame(NULL);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jobject p_obj, jstring p_permission, jboolean p_result) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {
String permission = jstring_to_string(p_permission, env);
if (permission == "android.permission.RECORD_AUDIO" && p_result) {
AudioDriver::get_singleton()->capture_start();
@@ -1438,3 +485,4 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE
os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_PAUSED);
}
}
+}
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index 71d4547f65..a7a5970440 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -37,36 +37,34 @@
// These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code.
// See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names)
extern "C" {
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jobject obj, jobject activity);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jobject obj, jint p_x, jint p_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jobject obj, jint p_x, jint p_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, jboolean p_pressed);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jobject obj, jint p_device, jint p_hat_x, jint p_hat_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jobject obj, jint p_device, jboolean p_connected, jstring p_name);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jobject obj);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jobject obj, jstring name, jobject p_object);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args);
-JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jobject obj, jstring path);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jobject obj, jint p_height);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jobject p_obj, jstring p_permission, jboolean p_result);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz, jobject activity);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jint width, jint height);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jboolean p_32_bits);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint count, jintArray positions);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jint p_x, jint p_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jclass clazz, jint p_x, jint p_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz);
+JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz);
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 9ac91b8ef6..7b677c186e 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -66,6 +66,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
_is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z");
_vibrate = p_env->GetMethodID(cls, "vibrate", "(I)V");
_get_input_fallback_mapping = p_env->GetMethodID(cls, "getInputFallbackMapping", "()Ljava/lang/String;");
+ _on_gl_godot_main_loop_started = p_env->GetMethodID(cls, "onGLGodotMainLoopStarted", "()V");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -107,6 +108,15 @@ void GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
p_env->CallVoidMethod(godot_instance, _on_video_init);
}
+void GodotJavaWrapper::on_gl_godot_main_loop_started(JNIEnv *p_env) {
+ if (_on_gl_godot_main_loop_started) {
+ if (p_env == NULL) {
+ p_env = ThreadAndroid::get_env();
+ }
+ }
+ p_env->CallVoidMethod(godot_instance, _on_gl_godot_main_loop_started);
+}
+
void GodotJavaWrapper::restart(JNIEnv *p_env) {
if (_restart)
if (p_env == NULL)
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index f378b1ea38..cdab2ecc9c 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -61,6 +61,7 @@ private:
jmethodID _is_activity_resumed = 0;
jmethodID _vibrate = 0;
jmethodID _get_input_fallback_mapping = 0;
+ jmethodID _on_gl_godot_main_loop_started = 0;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance);
@@ -72,6 +73,7 @@ public:
jobject get_class_loader();
void on_video_init(JNIEnv *p_env = NULL);
+ void on_gl_godot_main_loop_started(JNIEnv *p_env = NULL);
void restart(JNIEnv *p_env = NULL);
void force_quit(JNIEnv *p_env = NULL);
void set_keep_screen_on(bool p_enabled);
diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp
new file mode 100644
index 0000000000..3fa4e80884
--- /dev/null
+++ b/platform/android/jni_utils.cpp
@@ -0,0 +1,434 @@
+/*************************************************************************/
+/* jni_utils.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "jni_utils.h"
+
+jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject) {
+
+ jvalret v;
+
+ switch (p_type) {
+
+ case Variant::BOOL: {
+
+ if (force_jobject) {
+ jclass bclass = env->FindClass("java/lang/Boolean");
+ jmethodID ctor = env->GetMethodID(bclass, "<init>", "(Z)V");
+ jvalue val;
+ val.z = (bool)(*p_arg);
+ jobject obj = env->NewObjectA(bclass, ctor, &val);
+ v.val.l = obj;
+ v.obj = obj;
+ env->DeleteLocalRef(bclass);
+ } else {
+ v.val.z = *p_arg;
+ };
+ } break;
+ case Variant::INT: {
+
+ if (force_jobject) {
+
+ jclass bclass = env->FindClass("java/lang/Integer");
+ jmethodID ctor = env->GetMethodID(bclass, "<init>", "(I)V");
+ jvalue val;
+ val.i = (int)(*p_arg);
+ jobject obj = env->NewObjectA(bclass, ctor, &val);
+ v.val.l = obj;
+ v.obj = obj;
+ env->DeleteLocalRef(bclass);
+
+ } else {
+ v.val.i = *p_arg;
+ };
+ } break;
+ case Variant::FLOAT: {
+
+ if (force_jobject) {
+
+ jclass bclass = env->FindClass("java/lang/Double");
+ jmethodID ctor = env->GetMethodID(bclass, "<init>", "(D)V");
+ jvalue val;
+ val.d = (double)(*p_arg);
+ jobject obj = env->NewObjectA(bclass, ctor, &val);
+ v.val.l = obj;
+ v.obj = obj;
+ env->DeleteLocalRef(bclass);
+
+ } else {
+ v.val.f = *p_arg;
+ };
+ } break;
+ case Variant::STRING: {
+
+ String s = *p_arg;
+ jstring jStr = env->NewStringUTF(s.utf8().get_data());
+ v.val.l = jStr;
+ v.obj = jStr;
+ } break;
+ case Variant::PACKED_STRING_ARRAY: {
+
+ Vector<String> sarray = *p_arg;
+ jobjectArray arr = env->NewObjectArray(sarray.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
+
+ for (int j = 0; j < sarray.size(); j++) {
+
+ jstring str = env->NewStringUTF(sarray[j].utf8().get_data());
+ env->SetObjectArrayElement(arr, j, str);
+ env->DeleteLocalRef(str);
+ }
+ v.val.l = arr;
+ v.obj = arr;
+
+ } break;
+
+ case Variant::DICTIONARY: {
+
+ Dictionary dict = *p_arg;
+ jclass dclass = env->FindClass("org/godotengine/godot/Dictionary");
+ jmethodID ctor = env->GetMethodID(dclass, "<init>", "()V");
+ jobject jdict = env->NewObject(dclass, ctor);
+
+ Array keys = dict.keys();
+
+ jobjectArray jkeys = env->NewObjectArray(keys.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
+ for (int j = 0; j < keys.size(); j++) {
+ jstring str = env->NewStringUTF(String(keys[j]).utf8().get_data());
+ env->SetObjectArrayElement(jkeys, j, str);
+ env->DeleteLocalRef(str);
+ };
+
+ jmethodID set_keys = env->GetMethodID(dclass, "set_keys", "([Ljava/lang/String;)V");
+ jvalue val;
+ val.l = jkeys;
+ env->CallVoidMethodA(jdict, set_keys, &val);
+ env->DeleteLocalRef(jkeys);
+
+ jobjectArray jvalues = env->NewObjectArray(keys.size(), env->FindClass("java/lang/Object"), NULL);
+
+ for (int j = 0; j < keys.size(); j++) {
+ Variant var = dict[keys[j]];
+ jvalret v = _variant_to_jvalue(env, var.get_type(), &var, true);
+ env->SetObjectArrayElement(jvalues, j, v.val.l);
+ if (v.obj) {
+ env->DeleteLocalRef(v.obj);
+ }
+ };
+
+ jmethodID set_values = env->GetMethodID(dclass, "set_values", "([Ljava/lang/Object;)V");
+ val.l = jvalues;
+ env->CallVoidMethodA(jdict, set_values, &val);
+ env->DeleteLocalRef(jvalues);
+ env->DeleteLocalRef(dclass);
+
+ v.val.l = jdict;
+ v.obj = jdict;
+ } break;
+
+ case Variant::PACKED_INT32_ARRAY: {
+
+ Vector<int> array = *p_arg;
+ jintArray arr = env->NewIntArray(array.size());
+ const int *r = array.ptr();
+ env->SetIntArrayRegion(arr, 0, array.size(), r);
+ v.val.l = arr;
+ v.obj = arr;
+
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ Vector<uint8_t> array = *p_arg;
+ jbyteArray arr = env->NewByteArray(array.size());
+ const uint8_t *r = array.ptr();
+ env->SetByteArrayRegion(arr, 0, array.size(), reinterpret_cast<const signed char *>(r));
+ v.val.l = arr;
+ v.obj = arr;
+
+ } break;
+ case Variant::PACKED_FLOAT32_ARRAY: {
+
+ Vector<float> array = *p_arg;
+ jfloatArray arr = env->NewFloatArray(array.size());
+ const float *r = array.ptr();
+ env->SetFloatArrayRegion(arr, 0, array.size(), r);
+ v.val.l = arr;
+ v.obj = arr;
+
+ } break;
+#ifndef _MSC_VER
+#warning This is missing 64 bits arrays, I have no idea how to do it in JNI
+#endif
+
+ default: {
+
+ v.val.i = 0;
+ } break;
+ }
+ return v;
+}
+
+String _get_class_name(JNIEnv *env, jclass cls, bool *array) {
+
+ jclass cclass = env->FindClass("java/lang/Class");
+ jmethodID getName = env->GetMethodID(cclass, "getName", "()Ljava/lang/String;");
+ jstring clsName = (jstring)env->CallObjectMethod(cls, getName);
+
+ if (array) {
+ jmethodID isArray = env->GetMethodID(cclass, "isArray", "()Z");
+ jboolean isarr = env->CallBooleanMethod(cls, isArray);
+ (*array) = isarr ? true : false;
+ }
+ String name = jstring_to_string(clsName, env);
+ env->DeleteLocalRef(clsName);
+
+ return name;
+}
+
+Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
+
+ if (obj == NULL) {
+ return Variant();
+ }
+
+ jclass c = env->GetObjectClass(obj);
+ bool array;
+ String name = _get_class_name(env, c, &array);
+
+ if (name == "java.lang.String") {
+
+ return jstring_to_string((jstring)obj, env);
+ };
+
+ if (name == "[Ljava.lang.String;") {
+
+ jobjectArray arr = (jobjectArray)obj;
+ int stringCount = env->GetArrayLength(arr);
+ Vector<String> sarr;
+
+ for (int i = 0; i < stringCount; i++) {
+ jstring string = (jstring)env->GetObjectArrayElement(arr, i);
+ sarr.push_back(jstring_to_string(string, env));
+ env->DeleteLocalRef(string);
+ }
+
+ return sarr;
+ };
+
+ if (name == "java.lang.Boolean") {
+
+ jmethodID boolValue = env->GetMethodID(c, "booleanValue", "()Z");
+ bool ret = env->CallBooleanMethod(obj, boolValue);
+ return ret;
+ };
+
+ if (name == "java.lang.Integer" || name == "java.lang.Long") {
+
+ jclass nclass = env->FindClass("java/lang/Number");
+ jmethodID longValue = env->GetMethodID(nclass, "longValue", "()J");
+ jlong ret = env->CallLongMethod(obj, longValue);
+ return ret;
+ };
+
+ if (name == "[I") {
+
+ jintArray arr = (jintArray)obj;
+ int fCount = env->GetArrayLength(arr);
+ Vector<int> sarr;
+ sarr.resize(fCount);
+
+ int *w = sarr.ptrw();
+ env->GetIntArrayRegion(arr, 0, fCount, w);
+ return sarr;
+ };
+
+ if (name == "[B") {
+
+ jbyteArray arr = (jbyteArray)obj;
+ int fCount = env->GetArrayLength(arr);
+ Vector<uint8_t> sarr;
+ sarr.resize(fCount);
+
+ uint8_t *w = sarr.ptrw();
+ env->GetByteArrayRegion(arr, 0, fCount, reinterpret_cast<signed char *>(w));
+ return sarr;
+ };
+
+ if (name == "java.lang.Float" || name == "java.lang.Double") {
+
+ jclass nclass = env->FindClass("java/lang/Number");
+ jmethodID doubleValue = env->GetMethodID(nclass, "doubleValue", "()D");
+ double ret = env->CallDoubleMethod(obj, doubleValue);
+ return ret;
+ };
+
+ if (name == "[D") {
+
+ jdoubleArray arr = (jdoubleArray)obj;
+ int fCount = env->GetArrayLength(arr);
+ PackedFloat32Array sarr;
+ sarr.resize(fCount);
+
+ real_t *w = sarr.ptrw();
+
+ for (int i = 0; i < fCount; i++) {
+
+ double n;
+ env->GetDoubleArrayRegion(arr, i, 1, &n);
+ w[i] = n;
+ };
+ return sarr;
+ };
+
+ if (name == "[F") {
+
+ jfloatArray arr = (jfloatArray)obj;
+ int fCount = env->GetArrayLength(arr);
+ PackedFloat32Array sarr;
+ sarr.resize(fCount);
+
+ real_t *w = sarr.ptrw();
+
+ for (int i = 0; i < fCount; i++) {
+
+ float n;
+ env->GetFloatArrayRegion(arr, i, 1, &n);
+ w[i] = n;
+ };
+ return sarr;
+ };
+
+ if (name == "[Ljava.lang.Object;") {
+
+ jobjectArray arr = (jobjectArray)obj;
+ int objCount = env->GetArrayLength(arr);
+ Array varr;
+
+ for (int i = 0; i < objCount; i++) {
+ jobject jobj = env->GetObjectArrayElement(arr, i);
+ Variant v = _jobject_to_variant(env, jobj);
+ varr.push_back(v);
+ env->DeleteLocalRef(jobj);
+ }
+
+ return varr;
+ };
+
+ if (name == "java.util.HashMap" || name == "org.godotengine.godot.Dictionary") {
+
+ Dictionary ret;
+ jclass oclass = c;
+ jmethodID get_keys = env->GetMethodID(oclass, "get_keys", "()[Ljava/lang/String;");
+ jobjectArray arr = (jobjectArray)env->CallObjectMethod(obj, get_keys);
+
+ PackedStringArray keys = _jobject_to_variant(env, arr);
+ env->DeleteLocalRef(arr);
+
+ jmethodID get_values = env->GetMethodID(oclass, "get_values", "()[Ljava/lang/Object;");
+ arr = (jobjectArray)env->CallObjectMethod(obj, get_values);
+
+ Array vals = _jobject_to_variant(env, arr);
+ env->DeleteLocalRef(arr);
+
+ for (int i = 0; i < keys.size(); i++) {
+
+ ret[keys[i]] = vals[i];
+ };
+
+ return ret;
+ };
+
+ env->DeleteLocalRef(c);
+
+ return Variant();
+}
+
+Variant::Type get_jni_type(const String &p_type) {
+
+ static struct {
+ const char *name;
+ Variant::Type type;
+ } _type_to_vtype[] = {
+ { "void", Variant::NIL },
+ { "boolean", Variant::BOOL },
+ { "int", Variant::INT },
+ { "float", Variant::FLOAT },
+ { "double", Variant::FLOAT },
+ { "java.lang.String", Variant::STRING },
+ { "[I", Variant::PACKED_INT32_ARRAY },
+ { "[B", Variant::PACKED_BYTE_ARRAY },
+ { "[F", Variant::PACKED_FLOAT32_ARRAY },
+ { "[Ljava.lang.String;", Variant::PACKED_STRING_ARRAY },
+ { "org.godotengine.godot.Dictionary", Variant::DICTIONARY },
+ { NULL, Variant::NIL }
+ };
+
+ int idx = 0;
+
+ while (_type_to_vtype[idx].name) {
+
+ if (p_type == _type_to_vtype[idx].name)
+ return _type_to_vtype[idx].type;
+
+ idx++;
+ }
+
+ return Variant::NIL;
+}
+
+const char *get_jni_sig(const String &p_type) {
+
+ static struct {
+ const char *name;
+ const char *sig;
+ } _type_to_vtype[] = {
+ { "void", "V" },
+ { "boolean", "Z" },
+ { "int", "I" },
+ { "float", "F" },
+ { "double", "D" },
+ { "java.lang.String", "Ljava/lang/String;" },
+ { "org.godotengine.godot.Dictionary", "Lorg/godotengine/godot/Dictionary;" },
+ { "[I", "[I" },
+ { "[B", "[B" },
+ { "[F", "[F" },
+ { "[Ljava.lang.String;", "[Ljava/lang/String;" },
+ { NULL, "V" }
+ };
+
+ int idx = 0;
+
+ while (_type_to_vtype[idx].name) {
+
+ if (p_type == _type_to_vtype[idx].name)
+ return _type_to_vtype[idx].sig;
+
+ idx++;
+ }
+
+ return "Ljava/lang/Object;";
+}
diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h
new file mode 100644
index 0000000000..925340a680
--- /dev/null
+++ b/platform/android/jni_utils.h
@@ -0,0 +1,242 @@
+/*************************************************************************/
+/* jni_utils.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef JNI_UTILS_H
+#define JNI_UTILS_H
+
+#include "string_android.h"
+#include <core/engine.h>
+#include <core/variant.h>
+#include <jni.h>
+
+struct jvalret {
+
+ jobject obj;
+ jvalue val;
+ jvalret() { obj = NULL; }
+};
+
+jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject = false);
+
+String _get_class_name(JNIEnv *env, jclass cls, bool *array);
+
+Variant _jobject_to_variant(JNIEnv *env, jobject obj);
+
+Variant::Type get_jni_type(const String &p_type);
+
+const char *get_jni_sig(const String &p_type);
+
+class JNISingleton : public Object {
+
+ GDCLASS(JNISingleton, Object);
+
+ struct MethodData {
+
+ jmethodID method;
+ Variant::Type ret_type;
+ Vector<Variant::Type> argtypes;
+ };
+
+ jobject instance;
+ Map<StringName, MethodData> method_map;
+
+public:
+ virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+
+ ERR_FAIL_COND_V(!instance, Variant());
+
+ r_error.error = Callable::CallError::CALL_OK;
+
+ Map<StringName, MethodData>::Element *E = method_map.find(p_method);
+ if (!E) {
+
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
+ return Variant();
+ }
+
+ int ac = E->get().argtypes.size();
+ if (ac < p_argcount) {
+
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
+ r_error.argument = ac;
+ return Variant();
+ }
+
+ if (ac > p_argcount) {
+
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
+ r_error.argument = ac;
+ return Variant();
+ }
+
+ for (int i = 0; i < p_argcount; i++) {
+
+ if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
+
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = i;
+ r_error.expected = E->get().argtypes[i];
+ }
+ }
+
+ jvalue *v = NULL;
+
+ if (p_argcount) {
+
+ v = (jvalue *)alloca(sizeof(jvalue) * p_argcount);
+ }
+
+ JNIEnv *env = ThreadAndroid::get_env();
+
+ int res = env->PushLocalFrame(16);
+
+ ERR_FAIL_COND_V(res != 0, Variant());
+
+ List<jobject> to_erase;
+ for (int i = 0; i < p_argcount; i++) {
+
+ jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]);
+ v[i] = vr.val;
+ if (vr.obj)
+ to_erase.push_back(vr.obj);
+ }
+
+ Variant ret;
+
+ switch (E->get().ret_type) {
+
+ case Variant::NIL: {
+
+ env->CallVoidMethodA(instance, E->get().method, v);
+ } break;
+ case Variant::BOOL: {
+
+ ret = env->CallBooleanMethodA(instance, E->get().method, v) == JNI_TRUE;
+ } break;
+ case Variant::INT: {
+
+ ret = env->CallIntMethodA(instance, E->get().method, v);
+ } break;
+ case Variant::FLOAT: {
+
+ ret = env->CallFloatMethodA(instance, E->get().method, v);
+ } break;
+ case Variant::STRING: {
+
+ jobject o = env->CallObjectMethodA(instance, E->get().method, v);
+ ret = jstring_to_string((jstring)o, env);
+ env->DeleteLocalRef(o);
+ } break;
+ case Variant::PACKED_STRING_ARRAY: {
+
+ jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v);
+
+ ret = _jobject_to_variant(env, arr);
+
+ env->DeleteLocalRef(arr);
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+
+ jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v);
+
+ int fCount = env->GetArrayLength(arr);
+ Vector<int> sarr;
+ sarr.resize(fCount);
+
+ int *w = sarr.ptrw();
+ env->GetIntArrayRegion(arr, 0, fCount, w);
+ ret = sarr;
+ env->DeleteLocalRef(arr);
+ } break;
+ case Variant::PACKED_FLOAT32_ARRAY: {
+
+ jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v);
+
+ int fCount = env->GetArrayLength(arr);
+ Vector<float> sarr;
+ sarr.resize(fCount);
+
+ float *w = sarr.ptrw();
+ env->GetFloatArrayRegion(arr, 0, fCount, w);
+ ret = sarr;
+ env->DeleteLocalRef(arr);
+ } break;
+
+#ifndef _MSC_VER
+#warning This is missing 64 bits arrays, I have no idea how to do it in JNI
+#endif
+ case Variant::DICTIONARY: {
+
+ jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
+ ret = _jobject_to_variant(env, obj);
+ env->DeleteLocalRef(obj);
+
+ } break;
+ default: {
+
+ env->PopLocalFrame(NULL);
+ ERR_FAIL_V(Variant());
+ } break;
+ }
+
+ while (to_erase.size()) {
+ env->DeleteLocalRef(to_erase.front()->get());
+ to_erase.pop_front();
+ }
+
+ env->PopLocalFrame(NULL);
+
+ return ret;
+ }
+
+ jobject get_instance() const {
+
+ return instance;
+ }
+ void set_instance(jobject p_instance) {
+
+ instance = p_instance;
+ }
+
+ void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) {
+
+ MethodData md;
+ md.method = p_method;
+ md.argtypes = p_args;
+ md.ret_type = p_ret_type;
+ method_map[p_name] = md;
+ }
+
+ JNISingleton() {
+ instance = NULL;
+ }
+};
+
+#endif // JNI_UTILS_H
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 15e3ac48c7..7e2b0d948e 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -32,7 +32,9 @@
#include "core/io/file_access_buffered_fa.h"
#include "core/project_settings.h"
+#if defined(OPENGL_ENABLED)
#include "drivers/gles2/rasterizer_gles2.h"
+#endif
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
#include "file_access_android.h"
@@ -120,23 +122,27 @@ int OS_Android::get_current_video_driver() const {
Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
- bool gl_initialization_error = false;
-
// FIXME: Add Vulkan support. Readd fallback code from Vulkan to GLES2?
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- } else {
- gl_initialization_error = true;
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.\n"
- "Please try updating your Android version.",
- "Unable to initialize video driver");
- return ERR_UNAVAILABLE;
+#if defined(OPENGL_ENABLED)
+ if (video_driver_index == VIDEO_DRIVER_GLES2) {
+ bool gl_initialization_error = false;
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
+ }
+
+ if (gl_initialization_error) {
+ OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.\n"
+ "Please try updating your Android version.",
+ "Unable to initialize video driver");
+ return ERR_UNAVAILABLE;
+ }
}
+#endif
video_driver_index = p_video_driver;
@@ -753,6 +759,8 @@ OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_god
//rasterizer = NULL;
use_gl2 = false;
+ visual_server = NULL;
+
godot_java = p_godot_java;
godot_io_java = p_godot_io_java;
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
new file mode 100644
index 0000000000..7413236e5d
--- /dev/null
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -0,0 +1,115 @@
+/*************************************************************************/
+/* godot_plugin_jni.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "godot_plugin_jni.h"
+
+#include <core/engine.h>
+#include <core/error_macros.h>
+#include <core/project_settings.h>
+#include <platform/android/jni_utils.h>
+#include <platform/android/string_android.h>
+
+static HashMap<String, JNISingleton *> jni_singletons;
+
+extern "C" {
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name) {
+
+ String singname = jstring_to_string(name, env);
+ JNISingleton *s = memnew(JNISingleton);
+ s->set_instance(env->NewGlobalRef(obj));
+ jni_singletons[singname] = s;
+
+ Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
+ ProjectSettings::get_singleton()->set(singname, s);
+}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) {
+
+ String singname = jstring_to_string(sname, env);
+
+ ERR_FAIL_COND(!jni_singletons.has(singname));
+
+ JNISingleton *s = jni_singletons.get(singname);
+
+ String mname = jstring_to_string(name, env);
+ String retval = jstring_to_string(ret, env);
+ Vector<Variant::Type> types;
+ String cs = "(";
+
+ int stringCount = env->GetArrayLength(args);
+
+ for (int i = 0; i < stringCount; i++) {
+
+ jstring string = (jstring)env->GetObjectArrayElement(args, i);
+ const String rawString = jstring_to_string(string, env);
+ types.push_back(get_jni_type(rawString));
+ cs += get_jni_sig(rawString);
+ }
+
+ cs += ")";
+ cs += get_jni_sig(retval);
+ jclass cls = env->GetObjectClass(s->get_instance());
+ jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
+ if (!mid) {
+
+ print_line("Failed getting method ID " + mname);
+ }
+
+ s->add_method(mname, mid, types, get_jni_type(retval));
+}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths) {
+ int gdnlib_count = env->GetArrayLength(gdnlib_paths);
+ if (gdnlib_count == 0) {
+ return;
+ }
+
+ // Retrieve the current list of gdnative libraries.
+ Array singletons = Array();
+ if (ProjectSettings::get_singleton()->has_setting("gdnative/singletons")) {
+ singletons = ProjectSettings::get_singleton()->get("gdnative/singletons");
+ }
+
+ // Insert the libraries provided by the plugin
+ for (int i = 0; i < gdnlib_count; i++) {
+ jstring relative_path = (jstring)env->GetObjectArrayElement(gdnlib_paths, i);
+
+ String path = "res://" + jstring_to_string(relative_path, env);
+ if (!singletons.has(path)) {
+ singletons.push_back(path);
+ }
+ env->DeleteLocalRef(relative_path);
+ }
+
+ // Insert the updated list back into project settings.
+ ProjectSettings::get_singleton()->set("gdnative/singletons", singletons);
+}
+}
diff --git a/platform/iphone/semaphore_iphone.h b/platform/android/plugin/godot_plugin_jni.h
index 54ff3c17f9..0d613d3bfe 100644
--- a/platform/iphone/semaphore_iphone.h
+++ b/platform/android/plugin/godot_plugin_jni.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* semaphore_iphone.h */
+/* godot_plugin_jni.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,32 +28,16 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef SEMAPHORE_IPHONE_H
-#define SEMAPHORE_IPHONE_H
+#ifndef GODOT_PLUGIN_JNI_H
+#define GODOT_PLUGIN_JNI_H
-struct cgsem {
- int pipefd[2];
-};
+#include <android/log.h>
+#include <jni.h>
-typedef struct cgsem cgsem_t;
+extern "C" {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths);
+}
-#include "core/os/semaphore.h"
-
-class SemaphoreIphone : public SemaphoreOld {
-
- mutable cgsem_t sem;
-
- static SemaphoreOld *create_semaphore_iphone();
-
-public:
- virtual Error wait();
- virtual Error post();
- virtual int get() const;
-
- static void make_default();
- SemaphoreIphone();
-
- ~SemaphoreIphone();
-};
-
-#endif // SEMAPHORE_IPHONE_H
+#endif // GODOT_PLUGIN_JNI_H
diff --git a/platform/osx/semaphore_osx.cpp b/platform/android/vulkan/vk_renderer_jni.cpp
index e4e5991637..3026e7daad 100644
--- a/platform/osx/semaphore_osx.cpp
+++ b/platform/android/vulkan/vk_renderer_jni.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* semaphore_osx.cpp */
+/* vk_renderer_jni.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,80 +28,31 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "semaphore_osx.h"
+#include "vk_renderer_jni.h"
-#include <fcntl.h>
-#include <unistd.h>
+extern "C" {
-void cgsem_init(cgsem_t *cgsem) {
- int flags, fd, i;
-
- pipe(cgsem->pipefd);
-
- /* Make the pipes FD_CLOEXEC to allow them to close should we call
- * execv on restart. */
- for (i = 0; i < 2; i++) {
- fd = cgsem->pipefd[i];
- flags = fcntl(fd, F_GETFD, 0);
- flags |= FD_CLOEXEC;
- fcntl(fd, F_SETFD, flags);
- }
-}
-
-void cgsem_post(cgsem_t *cgsem) {
- const char buf = 1;
-
- write(cgsem->pipefd[1], &buf, 1);
-}
-
-void cgsem_wait(cgsem_t *cgsem) {
- char buf;
-
- read(cgsem->pipefd[0], &buf, 1);
-}
-
-void cgsem_destroy(cgsem_t *cgsem) {
- close(cgsem->pipefd[1]);
- close(cgsem->pipefd[0]);
-}
-
-#include "core/os/memory.h"
-
-#include <errno.h>
-
-Error SemaphoreOSX::wait() {
-
- cgsem_wait(&sem);
- return OK;
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkSurfaceCreated(JNIEnv *env, jobject obj, jobject j_surface) {
+ // TODO: complete
}
-Error SemaphoreOSX::post() {
-
- cgsem_post(&sem);
-
- return OK;
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkSurfaceChanged(JNIEnv *env, jobject object, jobject j_surface, jint width, jint height) {
+ // TODO: complete
}
-int SemaphoreOSX::get() const {
- return 0;
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkResume(JNIEnv *env, jobject obj) {
+ // TODO: complete
}
-SemaphoreOld *SemaphoreOSX::create_semaphore_osx() {
-
- return memnew(SemaphoreOSX);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkDrawFrame(JNIEnv *env, jobject obj) {
+ // TODO: complete
}
-void SemaphoreOSX::make_default() {
-
- create_func = create_semaphore_osx;
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkPause(JNIEnv *env, jobject obj) {
+ // TODO: complete
}
-SemaphoreOSX::SemaphoreOSX() {
-
- cgsem_init(&sem);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkDestroy(JNIEnv *env, jobject obj) {
+ // TODO: complete
}
-
-SemaphoreOSX::~SemaphoreOSX() {
-
- cgsem_destroy(&sem);
}
diff --git a/platform/iphone/semaphore_iphone.cpp b/platform/android/vulkan/vk_renderer_jni.h
index 0461f58c40..017766fea2 100644
--- a/platform/iphone/semaphore_iphone.cpp
+++ b/platform/android/vulkan/vk_renderer_jni.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* semaphore_iphone.cpp */
+/* vk_renderer_jni.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,85 +28,19 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "semaphore_iphone.h"
+#ifndef VK_RENDERER_JNI_H
+#define VK_RENDERER_JNI_H
-#include <fcntl.h>
-#include <unistd.h>
+#include <android/log.h>
+#include <jni.h>
-void cgsem_init(cgsem_t *);
-void cgsem_post(cgsem_t *);
-void cgsem_wait(cgsem_t *);
-void cgsem_destroy(cgsem_t *);
-
-void cgsem_init(cgsem_t *cgsem) {
- int flags, fd, i;
-
- pipe(cgsem->pipefd);
-
- /* Make the pipes FD_CLOEXEC to allow them to close should we call
- * execv on restart. */
- for (i = 0; i < 2; i++) {
- fd = cgsem->pipefd[i];
- flags = fcntl(fd, F_GETFD, 0);
- flags |= FD_CLOEXEC;
- fcntl(fd, F_SETFD, flags);
- }
-}
-
-void cgsem_post(cgsem_t *cgsem) {
- const char buf = 1;
-
- write(cgsem->pipefd[1], &buf, 1);
-}
-
-void cgsem_wait(cgsem_t *cgsem) {
- char buf;
-
- read(cgsem->pipefd[0], &buf, 1);
+extern "C" {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkSurfaceCreated(JNIEnv *env, jobject obj, jobject j_surface);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkSurfaceChanged(JNIEnv *env, jobject object, jobject j_surface, jint width, jint height);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkResume(JNIEnv *env, jobject obj);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkDrawFrame(JNIEnv *env, jobject obj);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkPause(JNIEnv *env, jobject obj);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkDestroy(JNIEnv *env, jobject obj);
}
-void cgsem_destroy(cgsem_t *cgsem) {
- close(cgsem->pipefd[1]);
- close(cgsem->pipefd[0]);
-}
-
-#include "core/os/memory.h"
-
-#include <errno.h>
-
-Error SemaphoreIphone::wait() {
-
- cgsem_wait(&sem);
- return OK;
-}
-
-Error SemaphoreIphone::post() {
-
- cgsem_post(&sem);
-
- return OK;
-}
-int SemaphoreIphone::get() const {
-
- return 0;
-}
-
-SemaphoreOld *SemaphoreIphone::create_semaphore_iphone() {
-
- return memnew(SemaphoreIphone);
-}
-
-void SemaphoreIphone::make_default() {
-
- create_func = create_semaphore_iphone;
-}
-
-SemaphoreIphone::SemaphoreIphone() {
-
- cgsem_init(&sem);
-}
-
-SemaphoreIphone::~SemaphoreIphone() {
-
- cgsem_destroy(&sem);
-}
+#endif // VK_RENDERER_JNI_H
diff --git a/platform/haiku/audio_driver_media_kit.cpp b/platform/haiku/audio_driver_media_kit.cpp
index b7f6b57244..0a5df14743 100644
--- a/platform/haiku/audio_driver_media_kit.cpp
+++ b/platform/haiku/audio_driver_media_kit.cpp
@@ -67,7 +67,6 @@ Error AudioDriverMediaKit::init() {
ERR_FAIL_COND_V(player == NULL, ERR_CANT_OPEN);
}
- mutex = Mutex::create();
player->Start();
return OK;
@@ -108,14 +107,14 @@ void AudioDriverMediaKit::lock() {
if (!mutex)
return;
- mutex->lock();
+ mutex.lock();
}
void AudioDriverMediaKit::unlock() {
if (!mutex)
return;
- mutex->unlock();
+ mutex.unlock();
}
void AudioDriverMediaKit::finish() {
@@ -124,15 +123,9 @@ void AudioDriverMediaKit::finish() {
if (samples_in) {
memdelete_arr(samples_in);
};
-
- if (mutex) {
- memdelete(mutex);
- mutex = NULL;
- }
}
AudioDriverMediaKit::AudioDriverMediaKit() {
- mutex = NULL;
player = NULL;
}
diff --git a/platform/haiku/audio_driver_media_kit.h b/platform/haiku/audio_driver_media_kit.h
index 06a362a89e..8272780fa7 100644
--- a/platform/haiku/audio_driver_media_kit.h
+++ b/platform/haiku/audio_driver_media_kit.h
@@ -40,7 +40,7 @@
#include <SoundPlayer.h>
class AudioDriverMediaKit : public AudioDriver {
- Mutex *mutex;
+ Mutex mutex;
BSoundPlayer *player;
static int32_t *samples_in;
diff --git a/platform/haiku/haiku_direct_window.cpp b/platform/haiku/haiku_direct_window.cpp
index 3c2b7f8d10..2d7efe6b61 100644
--- a/platform/haiku/haiku_direct_window.cpp
+++ b/platform/haiku/haiku_direct_window.cpp
@@ -273,7 +273,8 @@ void HaikuDirectWindow::HandleKeyboardEvent(BMessage *message) {
event.instance();
GetKeyModifierState(event, modifiers);
event->set_pressed(message->what == B_KEY_DOWN);
- event->set_scancode(KeyMappingHaiku::get_keysym(raw_char, key));
+ event->set_keycode(KeyMappingHaiku::get_keysym(raw_char, key));
+ event->set_physical_keycode(KeyMappingHaiku::get_keysym(raw_char, key));
event->set_echo(message->HasInt32("be:key_repeat"));
event->set_unicode(0);
@@ -283,8 +284,9 @@ void HaikuDirectWindow::HandleKeyboardEvent(BMessage *message) {
}
//make it consistent across platforms.
- if (event->get_scancode() == KEY_BACKTAB) {
- event->set_scancode(KEY_TAB);
+ if (event->get_keycode() == KEY_BACKTAB) {
+ event->set_keycode(KEY_TAB);
+ event->set_physical_keycode(KEY_TAB);
event->set_shift(true);
}
diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp
index db203ff2b3..497f2f747d 100644
--- a/platform/iphone/os_iphone.cpp
+++ b/platform/iphone/os_iphone.cpp
@@ -100,7 +100,6 @@ String OSIPhone::get_unique_id() const {
void OSIPhone::initialize_core() {
OS_Unix::initialize_core();
- SemaphoreIphone::make_default();
set_data_dir(data_dir);
};
@@ -135,7 +134,6 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p
RasterizerRD::make_current();
#endif
-
visual_server = memnew(VisualServerRaster);
// FIXME: Reimplement threaded rendering
if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
@@ -212,7 +210,8 @@ void OSIPhone::key(uint32_t p_key, bool p_pressed) {
ev.instance();
ev->set_echo(false);
ev->set_pressed(p_pressed);
- ev->set_scancode(p_key);
+ ev->set_keycode(p_key);
+ ev->set_physical_keycode(p_key);
ev->set_unicode(p_key);
queue_event(ev);
};
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub
index 85a633442e..d3cd8f76b7 100644
--- a/platform/javascript/SCsub
+++ b/platform/javascript/SCsub
@@ -10,8 +10,11 @@ javascript_files = [
'os_javascript.cpp',
]
-build = env.add_program(['#bin/godot${PROGSUFFIX}.js', '#bin/godot${PROGSUFFIX}.wasm'], javascript_files);
-js, wasm = build
+build_targets = ['#bin/godot${PROGSUFFIX}.js', '#bin/godot${PROGSUFFIX}.wasm']
+if env['threads_enabled']:
+ build_targets.append('#bin/godot${PROGSUFFIX}.worker.js')
+
+build = env.add_program(build_targets, javascript_files)
js_libraries = [
'http_request.js',
@@ -27,18 +30,38 @@ for module in js_modules:
env.Append(LINKFLAGS=['--pre-js', env.File(module).path])
env.Depends(build, js_modules)
-wrapper_start = env.File('pre.js')
-wrapper_end = env.File('engine.js')
-js_wrapped = env.Textfile('#bin/godot', [wrapper_start, js, wrapper_end], TEXTFILESUFFIX='${PROGSUFFIX}.wrapped.js')
+engine = [
+ 'engine/preloader.js',
+ 'engine/loader.js',
+ 'engine/utils.js',
+ 'engine/engine.js',
+]
+externs = [
+ env.File('#platform/javascript/engine/externs.js')
+]
+js_engine = env.CreateEngineFile('#bin/godot${PROGSUFFIX}.engine.js', engine, externs)
+env.Depends(js_engine, externs)
+
+wrap_list = [
+ build[0],
+ js_engine,
+]
+js_wrapped = env.Textfile('#bin/godot', [env.File(f) for f in wrap_list], TEXTFILESUFFIX='${PROGSUFFIX}.wrapped.js')
zip_dir = env.Dir('#bin/.javascript_zip')
-zip_files = env.InstallAs([
+out_files = [
zip_dir.File('godot.js'),
zip_dir.File('godot.wasm'),
zip_dir.File('godot.html')
-], [
+]
+in_files = [
js_wrapped,
- wasm,
+ build[1],
'#misc/dist/html/full-size.html'
-])
+]
+if env['threads_enabled']:
+ in_files.append(build[2])
+ out_files.append(zip_dir.File('godot.worker.js'))
+
+zip_files = env.InstallAs(out_files, in_files)
env.Zip('#bin/godot', zip_files, ZIPROOT=zip_dir, ZIPSUFFIX='${PROGSUFFIX}${ZIPSUFFIX}', ZIPCOMSTR='Archving $SOURCES as $TARGET')
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp
index f1bc7c4382..d63c6a40a5 100644
--- a/platform/javascript/audio_driver_javascript.cpp
+++ b/platform/javascript/audio_driver_javascript.cpp
@@ -69,31 +69,37 @@ void AudioDriverJavaScript::process_capture(float sample) {
Error AudioDriverJavaScript::init() {
/* clang-format off */
- EM_ASM({
- _audioDriver_audioContext = new (window.AudioContext || window.webkitAudioContext);
- _audioDriver_audioInput = null;
- _audioDriver_inputStream = null;
- _audioDriver_scriptNode = null;
+ _driver_id = EM_ASM_INT({
+ return Module.IDHandler.add({
+ 'context': new (window.AudioContext || window.webkitAudioContext),
+ 'input': null,
+ 'stream': null,
+ 'script': null
+ });
});
/* clang-format on */
int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
/* clang-format off */
buffer_length = EM_ASM_INT({
- var CHANNEL_COUNT = $0;
+ var ref = Module.IDHandler.get($0);
+ var ctx = ref['context'];
+ var CHANNEL_COUNT = $1;
- var channelCount = _audioDriver_audioContext.destination.channelCount;
+ var channelCount = ctx.destination.channelCount;
+ var script = null;
try {
// Try letting the browser recommend a buffer length.
- _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(0, 2, channelCount);
+ script = ctx.createScriptProcessor(0, 2, channelCount);
} catch (e) {
// ...otherwise, default to 4096.
- _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(4096, 2, channelCount);
+ script = ctx.createScriptProcessor(4096, 2, channelCount);
}
- _audioDriver_scriptNode.connect(_audioDriver_audioContext.destination);
+ script.connect(ctx.destination);
+ ref['script'] = script;
- return _audioDriver_scriptNode.bufferSize;
- }, channel_count);
+ return script.bufferSize;
+ }, _driver_id, channel_count);
/* clang-format on */
if (!buffer_length) {
return FAILED;
@@ -112,11 +118,12 @@ void AudioDriverJavaScript::start() {
/* clang-format off */
EM_ASM({
- var INTERNAL_BUFFER_PTR = $0;
+ const ref = Module.IDHandler.get($0);
+ var INTERNAL_BUFFER_PTR = $1;
var audioDriverMixFunction = cwrap('audio_driver_js_mix');
var audioDriverProcessCapture = cwrap('audio_driver_process_capture', null, ['number']);
- _audioDriver_scriptNode.onaudioprocess = function(audioProcessingEvent) {
+ ref['script'].onaudioprocess = function(audioProcessingEvent) {
audioDriverMixFunction();
var input = audioProcessingEvent.inputBuffer;
@@ -133,7 +140,7 @@ void AudioDriverJavaScript::start() {
}
}
- if (_audioDriver_audioInput) {
+ if (ref['input']) {
var inputDataL = input.getChannelData(0);
var inputDataR = input.getChannelData(1);
for (var i = 0; i < inputDataL.length; i++) {
@@ -142,34 +149,37 @@ void AudioDriverJavaScript::start() {
}
}
};
- }, internal_buffer);
+ }, _driver_id, internal_buffer);
/* clang-format on */
}
void AudioDriverJavaScript::resume() {
/* clang-format off */
EM_ASM({
- if (_audioDriver_audioContext.resume)
- _audioDriver_audioContext.resume();
- });
+ const ref = Module.IDHandler.get($0);
+ if (ref && ref['context'] && ref['context'].resume)
+ ref['context'].resume();
+ }, _driver_id);
/* clang-format on */
}
int AudioDriverJavaScript::get_mix_rate() const {
/* clang-format off */
- return EM_ASM_INT_V({
- return _audioDriver_audioContext.sampleRate;
- });
+ return EM_ASM_INT({
+ const ref = Module.IDHandler.get($0);
+ return ref && ref['context'] ? ref['context'].sampleRate : 0;
+ }, _driver_id);
/* clang-format on */
}
AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const {
/* clang-format off */
- return get_speaker_mode_by_total_channels(EM_ASM_INT_V({
- return _audioDriver_audioContext.destination.channelCount;
- }));
+ return get_speaker_mode_by_total_channels(EM_ASM_INT({
+ const ref = Module.IDHandler.get($0);
+ return ref && ref['context'] ? ref['context'].destination.channelCount : 0;
+ }, _driver_id));
/* clang-format on */
}
@@ -184,16 +194,15 @@ void AudioDriverJavaScript::finish() {
/* clang-format off */
EM_ASM({
- _audioDriver_audioContext = null;
- _audioDriver_audioInput = null;
- _audioDriver_scriptNode = null;
- });
+ Module.IDHandler.remove($0);
+ }, _driver_id);
/* clang-format on */
if (internal_buffer) {
memdelete_arr(internal_buffer);
internal_buffer = NULL;
}
+ _driver_id = 0;
}
Error AudioDriverJavaScript::capture_start() {
@@ -203,9 +212,10 @@ Error AudioDriverJavaScript::capture_start() {
/* clang-format off */
EM_ASM({
function gotMediaInput(stream) {
- _audioDriver_inputStream = stream;
- _audioDriver_audioInput = _audioDriver_audioContext.createMediaStreamSource(stream);
- _audioDriver_audioInput.connect(_audioDriver_scriptNode);
+ var ref = Module.IDHandler.get($0);
+ ref['stream'] = stream;
+ ref['input'] = ref['context'].createMediaStreamSource(stream);
+ ref['input'].connect(ref['script']);
}
function gotMediaInputError(e) {
@@ -219,7 +229,7 @@ Error AudioDriverJavaScript::capture_start() {
navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
navigator.getUserMedia({"audio": true}, gotMediaInput, gotMediaInputError);
}
- });
+ }, _driver_id);
/* clang-format on */
return OK;
@@ -229,20 +239,21 @@ Error AudioDriverJavaScript::capture_stop() {
/* clang-format off */
EM_ASM({
- if (_audioDriver_inputStream) {
- const tracks = _audioDriver_inputStream.getTracks();
+ var ref = Module.IDHandler.get($0);
+ if (ref['stream']) {
+ const tracks = ref['stream'].getTracks();
for (var i = 0; i < tracks.length; i++) {
tracks[i].stop();
}
- _audioDriver_inputStream = null;
+ ref['stream'] = null;
}
- if (_audioDriver_audioInput) {
- _audioDriver_audioInput.disconnect();
- _audioDriver_audioInput = null;
+ if (ref['input']) {
+ ref['input'].disconnect();
+ ref['input'] = null;
}
- });
+ }, _driver_id);
/* clang-format on */
input_buffer.clear();
@@ -252,7 +263,9 @@ Error AudioDriverJavaScript::capture_stop() {
AudioDriverJavaScript::AudioDriverJavaScript() {
+ _driver_id = 0;
internal_buffer = NULL;
+ buffer_length = 0;
singleton = this;
}
diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h
index 2bb97ba192..f6f2dacd4e 100644
--- a/platform/javascript/audio_driver_javascript.h
+++ b/platform/javascript/audio_driver_javascript.h
@@ -37,6 +37,7 @@ class AudioDriverJavaScript : public AudioDriver {
float *internal_buffer;
+ int _driver_id;
int buffer_length;
public:
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 1766833364..fb02752aa7 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -1,5 +1,6 @@
import os
+from emscripten_helpers import parse_config, run_closure_compiler, create_engine_file
def is_active():
return True
@@ -18,6 +19,8 @@ def get_opts():
return [
# eval() can be a security concern, so it can be disabled.
BoolVariable('javascript_eval', 'Enable JavaScript eval interface', True),
+ BoolVariable('threads_enabled', 'Enable WebAssembly Threads support (limited browser support)', False),
+ BoolVariable('use_closure_compiler', 'Use closure compiler to minimize Javascript code', False),
]
@@ -37,7 +40,7 @@ def configure(env):
## Build type
- if env['target'] != 'debug':
+ if env['target'] == 'release':
# Use -Os to prioritize optimizing for reduced file size. This is
# particularly valuable for the web platform because it directly
# decreases download time.
@@ -46,38 +49,55 @@ def configure(env):
# run-time performance.
env.Append(CCFLAGS=['-Os'])
env.Append(LINKFLAGS=['-Os'])
- if env['target'] == 'release_debug':
- env.Append(CPPDEFINES=['DEBUG_ENABLED'])
- # Retain function names for backtraces at the cost of file size.
- env.Append(LINKFLAGS=['--profiling-funcs'])
- else:
+ elif env['target'] == 'release_debug':
+ env.Append(CCFLAGS=['-Os'])
+ env.Append(LINKFLAGS=['-Os'])
+ env.Append(CPPDEFINES=['DEBUG_ENABLED'])
+ # Retain function names for backtraces at the cost of file size.
+ env.Append(LINKFLAGS=['--profiling-funcs'])
+ else: # 'debug'
env.Append(CPPDEFINES=['DEBUG_ENABLED'])
env.Append(CCFLAGS=['-O1', '-g'])
env.Append(LINKFLAGS=['-O1', '-g'])
env.Append(LINKFLAGS=['-s', 'ASSERTIONS=1'])
- ## Compiler configuration
+ if env['tools']:
+ if not env['threads_enabled']:
+ raise RuntimeError("Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option")
+ # Tools need more memory. Initial stack memory in bytes. See `src/settings.js` in emscripten repository (will be renamed to INITIAL_MEMORY).
+ env.Append(LINKFLAGS=['-s', 'TOTAL_MEMORY=33554432'])
+ else:
+ # Disable exceptions and rtti on non-tools (template) builds
+ # These flags help keep the file size down.
+ env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti'])
+ # Don't use dynamic_cast, necessary with no-rtti.
+ env.Append(CPPDEFINES=['NO_SAFE_CAST'])
+ ## Copy env variables.
env['ENV'] = os.environ
- em_config_file = os.getenv('EM_CONFIG') or os.path.expanduser('~/.emscripten')
- if not os.path.exists(em_config_file):
- raise RuntimeError("Emscripten configuration file '%s' does not exist" % em_config_file)
- with open(em_config_file) as f:
- em_config = {}
- try:
- # Emscripten configuration file is a Python file with simple assignments.
- exec(f.read(), em_config)
- except StandardError as e:
- raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e))
- if 'BINARYEN_ROOT' in em_config and os.path.isdir(os.path.join(em_config.get('BINARYEN_ROOT'), 'emscripten')):
- # New style, emscripten path as a subfolder of BINARYEN_ROOT
- env.PrependENVPath('PATH', os.path.join(em_config.get('BINARYEN_ROOT'), 'emscripten'))
- elif 'EMSCRIPTEN_ROOT' in em_config:
- # Old style (but can be there as a result from previous activation, so do last)
- env.PrependENVPath('PATH', em_config.get('EMSCRIPTEN_ROOT'))
- else:
- raise RuntimeError("'BINARYEN_ROOT' or 'EMSCRIPTEN_ROOT' missing in Emscripten configuration file '%s'" % em_config_file)
+ # LTO
+ if env['use_lto']:
+ env.Append(CCFLAGS=['-s', 'WASM_OBJECT_FILES=0'])
+ env.Append(LINKFLAGS=['-s', 'WASM_OBJECT_FILES=0'])
+ env.Append(LINKFLAGS=['--llvm-lto', '1'])
+
+ # Closure compiler
+ if env['use_closure_compiler']:
+ # For emscripten support code.
+ env.Append(LINKFLAGS=['--closure', '1'])
+ # Register builder for our Engine files
+ jscc = env.Builder(generator=run_closure_compiler, suffix='.cc.js', src_suffix='.js')
+ env.Append(BUILDERS = {'BuildJS' : jscc})
+
+ # Add method that joins/compiles our Engine files.
+ env.AddMethod(create_engine_file, "CreateEngineFile")
+
+ # Closure compiler extern and support for ecmascript specs (const, let, etc).
+ env['ENV']['EMCC_CLOSURE_ARGS'] = '--language_in ECMASCRIPT6'
+
+ em_config = parse_config()
+ env.PrependENVPath('PATH', em_config['EMCC_ROOT'])
env['CC'] = 'emcc'
env['CXX'] = 'em++'
@@ -104,44 +124,31 @@ def configure(env):
env['LIBPREFIXES'] = ['$LIBPREFIX']
env['LIBSUFFIXES'] = ['$LIBSUFFIX']
- ## Compile flags
-
env.Prepend(CPPPATH=['#platform/javascript'])
env.Append(CPPDEFINES=['JAVASCRIPT_ENABLED', 'UNIX_ENABLED'])
- # No multi-threading (SharedArrayBuffer) available yet,
- # once feasible also consider memory buffer size issues.
- env.Append(CPPDEFINES=['NO_THREADS'])
-
- # Disable exceptions and rtti on non-tools (template) builds
- if not env['tools']:
- # These flags help keep the file size down.
- env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti'])
- # Don't use dynamic_cast, necessary with no-rtti.
- env.Append(CPPDEFINES=['NO_SAFE_CAST'])
-
if env['javascript_eval']:
env.Append(CPPDEFINES=['JAVASCRIPT_EVAL_ENABLED'])
- ## Link flags
+ # Thread support (via SharedArrayBuffer).
+ if env['threads_enabled']:
+ env.Append(CPPDEFINES=['PTHREAD_NO_RENAME'])
+ env.Append(CCFLAGS=['-s', 'USE_PTHREADS=1'])
+ env.Append(LINKFLAGS=['-s', 'USE_PTHREADS=1'])
+ env.Append(LINKFLAGS=['-s', 'PTHREAD_POOL_SIZE=4'])
+ env.Append(LINKFLAGS=['-s', 'WASM_MEM_MAX=2048MB'])
+ else:
+ env.Append(CPPDEFINES=['NO_THREADS'])
+
+ # Reduce code size by generating less support code (e.g. skip NodeJS support).
+ env.Append(LINKFLAGS=['-s', 'ENVIRONMENT=web,worker'])
# We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to
# be linked explicitly.
env.Append(LIBS=['idbfs.js'])
env.Append(LINKFLAGS=['-s', 'BINARYEN=1'])
-
- # Only include the JavaScript support code for the web environment
- # (i.e. exclude Node.js and other unused environments).
- # This makes the JavaScript support code about 4 KB smaller.
- env.Append(LINKFLAGS=['-s', 'ENVIRONMENT=web'])
-
- # This needs to be defined for Emscripten using 'fastcomp' (default pre-1.39.0)
- # and undefined if using 'upstream'. And to make things simple, earlier
- # Emscripten versions didn't include 'fastcomp' in their path, so we check
- # against the presence of 'upstream' to conditionally add the flag.
- if not "upstream" in em_config['EMSCRIPTEN_ROOT']:
- env.Append(LINKFLAGS=['-s', 'BINARYEN_TRAP_MODE=\'clamp\''])
+ env.Append(LINKFLAGS=['-s', 'MODULARIZE=1', '-s', 'EXPORT_NAME="Godot"'])
# Allow increasing memory buffer size during runtime. This is efficient
# when using WebAssembly (in comparison to asm.js) and works well for
@@ -153,8 +160,5 @@ def configure(env):
env.Append(LINKFLAGS=['-s', 'INVOKE_RUN=0'])
- # TODO: Reevaluate usage of this setting now that engine.js manages engine runtime.
- env.Append(LINKFLAGS=['-s', 'NO_EXIT_RUNTIME=1'])
-
- #adding flag due to issue with emscripten 1.38.41 callMain method https://github.com/emscripten-core/emscripten/blob/incoming/ChangeLog.md#v13841-08072019
- env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain"]'])
+ # callMain for manual start, FS for preloading.
+ env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain", "FS"]'])
diff --git a/platform/javascript/dom_keys.inc b/platform/javascript/dom_keys.inc
index 25e88f99d1..fd9df765d2 100644
--- a/platform/javascript/dom_keys.inc
+++ b/platform/javascript/dom_keys.inc
@@ -218,7 +218,7 @@
#define DOM_VK_PA1 0xFD
#define DOM_VK_WIN_OEM_CLEAR 0xFE
-int dom2godot_scancode(int dom_keycode) {
+int dom2godot_keycode(int dom_keycode) {
if (DOM_VK_0 <= dom_keycode && dom_keycode <= DOM_VK_Z) {
// ASCII intersection
diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py
new file mode 100644
index 0000000000..bda5b40a74
--- /dev/null
+++ b/platform/javascript/emscripten_helpers.py
@@ -0,0 +1,37 @@
+import os
+
+def parse_config():
+ em_config_file = os.getenv('EM_CONFIG') or os.path.expanduser('~/.emscripten')
+ if not os.path.exists(em_config_file):
+ raise RuntimeError("Emscripten configuration file '%s' does not exist" % em_config_file)
+
+ normalized = {}
+ em_config = {}
+ with open(em_config_file) as f:
+ try:
+ # Emscripten configuration file is a Python file with simple assignments.
+ exec(f.read(), em_config)
+ except StandardError as e:
+ raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e))
+ normalized['EMCC_ROOT'] = em_config.get('EMSCRIPTEN_ROOT')
+ normalized['NODE_JS'] = em_config.get('NODE_JS')
+ normalized['CLOSURE_BIN'] = os.path.join(normalized['EMCC_ROOT'], 'node_modules', '.bin', 'google-closure-compiler')
+ return normalized
+
+
+def run_closure_compiler(target, source, env, for_signature):
+ cfg = parse_config()
+ cmd = [cfg['NODE_JS'], cfg['CLOSURE_BIN']]
+ cmd.extend(['--compilation_level', 'ADVANCED_OPTIMIZATIONS'])
+ for f in env['JSEXTERNS']:
+ cmd.extend(['--externs', f.get_abspath()])
+ for f in source:
+ cmd.extend(['--js', f.get_abspath()])
+ cmd.extend(['--js_output_file', target[0].get_abspath()])
+ return ' '.join(cmd)
+
+
+def create_engine_file(env, target, source, externs):
+ if env['use_closure_compiler']:
+ return env.BuildJS(target, source, JSEXTERNS=externs)
+ return env.Textfile(target, [env.File(s) for s in source])
diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js
deleted file mode 100644
index 227accadb0..0000000000
--- a/platform/javascript/engine.js
+++ /dev/null
@@ -1,411 +0,0 @@
- // The following is concatenated with generated code, and acts as the end
- // of a wrapper for said code. See pre.js for the other part of the
- // wrapper.
- exposedLibs['PATH'] = PATH;
- exposedLibs['FS'] = FS;
- return Module;
- },
-};
-
-(function() {
- var engine = Engine;
-
- var DOWNLOAD_ATTEMPTS_MAX = 4;
-
- var basePath = null;
- var wasmFilenameExtensionOverride = null;
- var engineLoadPromise = null;
-
- var loadingFiles = {};
-
- function getPathLeaf(path) {
-
- while (path.endsWith('/'))
- path = path.slice(0, -1);
- return path.slice(path.lastIndexOf('/') + 1);
- }
-
- function getBasePath(path) {
-
- if (path.endsWith('/'))
- path = path.slice(0, -1);
- if (path.lastIndexOf('.') > path.lastIndexOf('/'))
- path = path.slice(0, path.lastIndexOf('.'));
- return path;
- }
-
- function getBaseName(path) {
-
- return getPathLeaf(getBasePath(path));
- }
-
- Engine = function Engine() {
-
- this.rtenv = null;
-
- var LIBS = {};
-
- var initPromise = null;
- var unloadAfterInit = true;
-
- var preloadedFiles = [];
-
- var resizeCanvasOnStart = true;
- var progressFunc = null;
- var preloadProgressTracker = {};
- var lastProgress = { loaded: 0, total: 0 };
-
- var canvas = null;
- var executableName = null;
- var locale = null;
- var stdout = null;
- var stderr = null;
-
- this.init = function(newBasePath) {
-
- if (!initPromise) {
- initPromise = Engine.load(newBasePath).then(
- instantiate.bind(this)
- );
- requestAnimationFrame(animateProgress);
- if (unloadAfterInit)
- initPromise.then(Engine.unloadEngine);
- }
- return initPromise;
- };
-
- function instantiate(wasmBuf) {
-
- var rtenvProps = {
- engine: this,
- ENV: {},
- };
- if (typeof stdout === 'function')
- rtenvProps.print = stdout;
- if (typeof stderr === 'function')
- rtenvProps.printErr = stderr;
- rtenvProps.instantiateWasm = function(imports, onSuccess) {
- WebAssembly.instantiate(wasmBuf, imports).then(function(result) {
- onSuccess(result.instance);
- });
- return {};
- };
-
- return new Promise(function(resolve, reject) {
- rtenvProps.onRuntimeInitialized = resolve;
- rtenvProps.onAbort = reject;
- rtenvProps.thisProgram = executableName;
- rtenvProps.engine.rtenv = Engine.RuntimeEnvironment(rtenvProps, LIBS);
- });
- }
-
- this.preloadFile = function(pathOrBuffer, destPath) {
-
- if (pathOrBuffer instanceof ArrayBuffer) {
- pathOrBuffer = new Uint8Array(pathOrBuffer);
- } else if (ArrayBuffer.isView(pathOrBuffer)) {
- pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
- }
- if (pathOrBuffer instanceof Uint8Array) {
- preloadedFiles.push({
- path: destPath,
- buffer: pathOrBuffer
- });
- return Promise.resolve();
- } else if (typeof pathOrBuffer === 'string') {
- return loadPromise(pathOrBuffer, preloadProgressTracker).then(function(xhr) {
- preloadedFiles.push({
- path: destPath || pathOrBuffer,
- buffer: xhr.response
- });
- });
- } else {
- throw Promise.reject("Invalid object for preloading");
- }
- };
-
- this.start = function() {
-
- return this.init().then(
- Function.prototype.apply.bind(synchronousStart, this, arguments)
- );
- };
-
- this.startGame = function(execName, mainPack) {
-
- executableName = execName;
- var mainArgs = [ '--main-pack', getPathLeaf(mainPack) ];
-
- return Promise.all([
- this.init(getBasePath(execName)),
- this.preloadFile(mainPack, getPathLeaf(mainPack))
- ]).then(
- Function.prototype.apply.bind(synchronousStart, this, mainArgs)
- );
- };
-
- function synchronousStart() {
-
- if (canvas instanceof HTMLCanvasElement) {
- this.rtenv.canvas = canvas;
- } else {
- var firstCanvas = document.getElementsByTagName('canvas')[0];
- if (firstCanvas instanceof HTMLCanvasElement) {
- this.rtenv.canvas = firstCanvas;
- } else {
- throw new Error("No canvas found");
- }
- }
-
- var actualCanvas = this.rtenv.canvas;
- // canvas can grab focus on click
- if (actualCanvas.tabIndex < 0) {
- actualCanvas.tabIndex = 0;
- }
- // necessary to calculate cursor coordinates correctly
- actualCanvas.style.padding = 0;
- actualCanvas.style.borderWidth = 0;
- actualCanvas.style.borderStyle = 'none';
- // disable right-click context menu
- actualCanvas.addEventListener('contextmenu', function(ev) {
- ev.preventDefault();
- }, false);
- // until context restoration is implemented
- actualCanvas.addEventListener('webglcontextlost', function(ev) {
- alert("WebGL context lost, please reload the page");
- ev.preventDefault();
- }, false);
-
- if (locale) {
- this.rtenv.locale = locale;
- } else {
- this.rtenv.locale = navigator.languages ? navigator.languages[0] : navigator.language;
- }
- this.rtenv.locale = this.rtenv.locale.split('.')[0];
- this.rtenv.resizeCanvasOnStart = resizeCanvasOnStart;
-
- preloadedFiles.forEach(function(file) {
- var dir = LIBS.PATH.dirname(file.path);
- try {
- LIBS.FS.stat(dir);
- } catch (e) {
- if (e.code !== 'ENOENT') {
- throw e;
- }
- LIBS.FS.mkdirTree(dir);
- }
- // With memory growth, canOwn should be false.
- LIBS.FS.createDataFile(file.path, null, new Uint8Array(file.buffer), true, true, false);
- }, this);
-
- preloadedFiles = null;
- initPromise = null;
- this.rtenv.callMain(arguments);
- }
-
- this.setProgressFunc = function(func) {
- progressFunc = func;
- };
-
- this.setResizeCanvasOnStart = function(enabled) {
- resizeCanvasOnStart = enabled;
- };
-
- function animateProgress() {
-
- var loaded = 0;
- var total = 0;
- var totalIsValid = true;
- var progressIsFinal = true;
-
- [loadingFiles, preloadProgressTracker].forEach(function(tracker) {
- Object.keys(tracker).forEach(function(file) {
- if (!tracker[file].final)
- progressIsFinal = false;
- if (!totalIsValid || tracker[file].total === 0) {
- totalIsValid = false;
- total = 0;
- } else {
- total += tracker[file].total;
- }
- loaded += tracker[file].loaded;
- });
- });
- if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
- lastProgress.loaded = loaded;
- lastProgress.total = total;
- if (typeof progressFunc === 'function')
- progressFunc(loaded, total);
- }
- if (!progressIsFinal)
- requestAnimationFrame(animateProgress);
- }
-
- this.setCanvas = function(elem) {
- canvas = elem;
- };
-
- this.setExecutableName = function(newName) {
-
- executableName = newName;
- };
-
- this.setLocale = function(newLocale) {
-
- locale = newLocale;
- };
-
- this.setUnloadAfterInit = function(enabled) {
-
- if (enabled && !unloadAfterInit && initPromise) {
- initPromise.then(Engine.unloadEngine);
- }
- unloadAfterInit = enabled;
- };
-
- this.setStdoutFunc = function(func) {
-
- var print = function(text) {
- if (arguments.length > 1) {
- text = Array.prototype.slice.call(arguments).join(" ");
- }
- func(text);
- };
- if (this.rtenv)
- this.rtenv.print = print;
- stdout = print;
- };
-
- this.setStderrFunc = function(func) {
-
- var printErr = function(text) {
- if (arguments.length > 1)
- text = Array.prototype.slice.call(arguments).join(" ");
- func(text);
- };
- if (this.rtenv)
- this.rtenv.printErr = printErr;
- stderr = printErr;
- };
-
-
- }; // Engine()
-
- Engine.RuntimeEnvironment = engine.RuntimeEnvironment;
-
- Engine.isWebGLAvailable = function(majorVersion = 1) {
-
- var testContext = false;
- try {
- var testCanvas = document.createElement('canvas');
- if (majorVersion === 1) {
- testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
- } else if (majorVersion === 2) {
- testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
- }
- } catch (e) {}
- return !!testContext;
- };
-
- Engine.setWebAssemblyFilenameExtension = function(override) {
-
- if (String(override).length === 0) {
- throw new Error('Invalid WebAssembly filename extension override');
- }
- wasmFilenameExtensionOverride = String(override);
- }
-
- Engine.load = function(newBasePath) {
-
- if (newBasePath !== undefined) basePath = getBasePath(newBasePath);
- if (engineLoadPromise === null) {
- if (typeof WebAssembly !== 'object')
- return Promise.reject(new Error("Browser doesn't support WebAssembly"));
- // TODO cache/retrieve module to/from idb
- engineLoadPromise = loadPromise(basePath + '.' + (wasmFilenameExtensionOverride || 'wasm')).then(function(xhr) {
- return xhr.response;
- });
- engineLoadPromise = engineLoadPromise.catch(function(err) {
- engineLoadPromise = null;
- throw err;
- });
- }
- return engineLoadPromise;
- };
-
- Engine.unload = function() {
- engineLoadPromise = null;
- };
-
- function loadPromise(file, tracker) {
- if (tracker === undefined)
- tracker = loadingFiles;
- return new Promise(function(resolve, reject) {
- loadXHR(resolve, reject, file, tracker);
- });
- }
-
- function loadXHR(resolve, reject, file, tracker) {
-
- var xhr = new XMLHttpRequest;
- xhr.open('GET', file);
- if (!file.endsWith('.js')) {
- xhr.responseType = 'arraybuffer';
- }
- ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) {
- xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
- });
- xhr.send();
- }
-
- function onXHREvent(resolve, reject, file, tracker, ev) {
-
- if (this.status >= 400) {
-
- if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
- reject(new Error("Failed loading file '" + file + "': " + this.statusText));
- this.abort();
- return;
- } else {
- setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
- }
- }
-
- switch (ev.type) {
- case 'loadstart':
- if (tracker[file] === undefined) {
- tracker[file] = {
- total: ev.total,
- loaded: ev.loaded,
- attempts: 0,
- final: false,
- };
- }
- break;
-
- case 'progress':
- tracker[file].loaded = ev.loaded;
- tracker[file].total = ev.total;
- break;
-
- case 'load':
- tracker[file].final = true;
- resolve(this);
- break;
-
- case 'error':
- if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
- tracker[file].final = true;
- reject(new Error("Failed loading file '" + file + "'"));
- } else {
- setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
- }
- break;
-
- case 'abort':
- tracker[file].final = true;
- reject(new Error("Loading file '" + file + "' was aborted."));
- break;
- }
- }
-})();
diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js
new file mode 100644
index 0000000000..6d7509377f
--- /dev/null
+++ b/platform/javascript/engine/engine.js
@@ -0,0 +1,184 @@
+Function('return this')()['Engine'] = (function() {
+
+ var unloadAfterInit = true;
+ var canvas = null;
+ var resizeCanvasOnStart = false;
+ var customLocale = 'en_US';
+ var wasmExt = '.wasm';
+
+ var preloader = new Preloader();
+ var loader = new Loader();
+ var rtenv = null;
+
+ var executableName = '';
+ var loadPath = '';
+ var loadPromise = null;
+ var initPromise = null;
+ var stderr = null;
+ var stdout = null;
+ var progressFunc = null;
+
+ function load(basePath) {
+ if (loadPromise == null) {
+ loadPath = basePath;
+ loadPromise = preloader.loadPromise(basePath + wasmExt);
+ preloader.setProgressFunc(progressFunc);
+ requestAnimationFrame(preloader.animateProgress);
+ }
+ return loadPromise;
+ };
+
+ function unload() {
+ loadPromise = null;
+ };
+
+ /** @constructor */
+ function Engine() {};
+
+ Engine.prototype.init = /** @param {string=} basePath */ function(basePath) {
+ if (initPromise) {
+ return initPromise;
+ }
+ if (!loadPromise) {
+ if (!basePath) {
+ initPromise = Promise.reject(new Error("A base path must be provided when calling `init` and the engine is not loaded."));
+ return initPromise;
+ }
+ load(basePath);
+ }
+ var config = {}
+ if (typeof stdout === 'function')
+ config.print = stdout;
+ if (typeof stderr === 'function')
+ config.printErr = stderr;
+ initPromise = loader.init(loadPromise, loadPath, config).then(function() {
+ return new Promise(function(resolve, reject) {
+ rtenv = loader.env;
+ if (unloadAfterInit) {
+ loadPromise = null;
+ }
+ resolve();
+ });
+ });
+ return initPromise;
+ };
+
+ /** @type {function(string, string):Object} */
+ Engine.prototype.preloadFile = function(file, path) {
+ return preloader.preload(file, path);
+ };
+
+ /** @type {function(...string):Object} */
+ Engine.prototype.start = function() {
+ // Start from arguments.
+ var args = [];
+ for (var i = 0; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ var me = this;
+ return new Promise(function(resolve, reject) {
+ return me.init().then(function() {
+ if (!(canvas instanceof HTMLCanvasElement)) {
+ canvas = Utils.findCanvas();
+ }
+ rtenv['locale'] = customLocale;
+ rtenv['canvas'] = canvas;
+ rtenv['thisProgram'] = executableName;
+ rtenv['resizeCanvasOnStart'] = resizeCanvasOnStart;
+ loader.start(preloader.preloadedFiles, args).then(function() {
+ loader = null;
+ initPromise = null;
+ resolve();
+ });
+ });
+ });
+ };
+
+ Engine.prototype.startGame = function(execName, mainPack) {
+ // Start and init with execName as loadPath if not inited.
+ executableName = execName;
+ var me = this;
+ return Promise.all([
+ this.init(execName),
+ this.preloadFile(mainPack, mainPack)
+ ]).then(function() {
+ return me.start('--main-pack', mainPack);
+ });
+ };
+
+ Engine.prototype.setWebAssemblyFilenameExtension = function(override) {
+ if (String(override).length === 0) {
+ throw new Error('Invalid WebAssembly filename extension override');
+ }
+ wasmExt = String(override);
+ };
+
+ Engine.prototype.setUnloadAfterInit = function(enabled) {
+ unloadAfterInit = enabled;
+ };
+
+ Engine.prototype.setCanvas = function(canvasElem) {
+ canvas = canvasElem;
+ };
+
+ Engine.prototype.setCanvasResizedOnStart = function(enabled) {
+ resizeCanvasOnStart = enabled;
+ };
+
+ Engine.prototype.setLocale = function(locale) {
+ customLocale = locale;
+ };
+
+ Engine.prototype.setExecutableName = function(newName) {
+ executableName = newName;
+ };
+
+ Engine.prototype.setProgressFunc = function(func) {
+ progressFunc = func;
+ }
+
+ Engine.prototype.setStdoutFunc = function(func) {
+
+ var print = function(text) {
+ if (arguments.length > 1) {
+ text = Array.prototype.slice.call(arguments).join(" ");
+ }
+ func(text);
+ };
+ if (rtenv)
+ rtenv.print = print;
+ stdout = print;
+ };
+
+ Engine.prototype.setStderrFunc = function(func) {
+
+ var printErr = function(text) {
+ if (arguments.length > 1)
+ text = Array.prototype.slice.call(arguments).join(" ");
+ func(text);
+ };
+ if (rtenv)
+ rtenv.printErr = printErr;
+ stderr = printErr;
+ };
+
+ // Closure compiler exported engine methods.
+ /** @export */
+ Engine['isWebGLAvailable'] = Utils.isWebGLAvailable;
+ Engine['load'] = load;
+ Engine['unload'] = unload;
+ Engine.prototype['init'] = Engine.prototype.init
+ Engine.prototype['preloadFile'] = Engine.prototype.preloadFile
+ Engine.prototype['start'] = Engine.prototype.start
+ Engine.prototype['startGame'] = Engine.prototype.startGame
+ Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension
+ Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit
+ Engine.prototype['setCanvas'] = Engine.prototype.setCanvas
+ Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart
+ Engine.prototype['setLocale'] = Engine.prototype.setLocale
+ Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName
+ Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc
+ Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc
+ Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc
+ return Engine;
+})();
diff --git a/platform/javascript/engine/externs.js b/platform/javascript/engine/externs.js
new file mode 100644
index 0000000000..1a94dd15ec
--- /dev/null
+++ b/platform/javascript/engine/externs.js
@@ -0,0 +1,3 @@
+var Godot;
+var WebAssembly = {};
+WebAssembly.instantiate = function(buffer, imports) {};
diff --git a/platform/javascript/engine/loader.js b/platform/javascript/engine/loader.js
new file mode 100644
index 0000000000..d27fbf612e
--- /dev/null
+++ b/platform/javascript/engine/loader.js
@@ -0,0 +1,33 @@
+var Loader = /** @constructor */ function() {
+
+ this.env = null;
+
+ this.init = function(loadPromise, basePath, config) {
+ var me = this;
+ return new Promise(function(resolve, reject) {
+ var cfg = config || {};
+ cfg['locateFile'] = Utils.createLocateRewrite(basePath);
+ cfg['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
+ loadPromise = null;
+ Godot(cfg).then(function(module) {
+ me.env = module;
+ resolve();
+ });
+ });
+ }
+
+ this.start = function(preloadedFiles, args) {
+ var me = this;
+ return new Promise(function(resolve, reject) {
+ if (!me.env) {
+ reject(new Error('The engine must be initialized before it can be started'));
+ }
+ preloadedFiles.forEach(function(file) {
+ Utils.copyToFS(me.env['FS'], file.path, file.buffer);
+ });
+ preloadedFiles.length = 0; // Clear memory
+ me.env['callMain'](args);
+ resolve();
+ });
+ }
+};
diff --git a/platform/javascript/engine/preloader.js b/platform/javascript/engine/preloader.js
new file mode 100644
index 0000000000..17918eae38
--- /dev/null
+++ b/platform/javascript/engine/preloader.js
@@ -0,0 +1,139 @@
+var Preloader = /** @constructor */ function() {
+
+ var DOWNLOAD_ATTEMPTS_MAX = 4;
+ var progressFunc = null;
+ var lastProgress = { loaded: 0, total: 0 };
+
+ var loadingFiles = {};
+ this.preloadedFiles = [];
+
+ function loadXHR(resolve, reject, file, tracker) {
+ var xhr = new XMLHttpRequest;
+ xhr.open('GET', file);
+ if (!file.endsWith('.js')) {
+ xhr.responseType = 'arraybuffer';
+ }
+ ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) {
+ xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
+ });
+ xhr.send();
+ }
+
+ function onXHREvent(resolve, reject, file, tracker, ev) {
+
+ if (this.status >= 400) {
+
+ if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
+ reject(new Error("Failed loading file '" + file + "': " + this.statusText));
+ this.abort();
+ return;
+ } else {
+ setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
+ }
+ }
+
+ switch (ev.type) {
+ case 'loadstart':
+ if (tracker[file] === undefined) {
+ tracker[file] = {
+ total: ev.total,
+ loaded: ev.loaded,
+ attempts: 0,
+ final: false,
+ };
+ }
+ break;
+
+ case 'progress':
+ tracker[file].loaded = ev.loaded;
+ tracker[file].total = ev.total;
+ break;
+
+ case 'load':
+ tracker[file].final = true;
+ resolve(this);
+ break;
+
+ case 'error':
+ if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
+ tracker[file].final = true;
+ reject(new Error("Failed loading file '" + file + "'"));
+ } else {
+ setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
+ }
+ break;
+
+ case 'abort':
+ tracker[file].final = true;
+ reject(new Error("Loading file '" + file + "' was aborted."));
+ break;
+ }
+ }
+
+ this.loadPromise = function(file) {
+ return new Promise(function(resolve, reject) {
+ loadXHR(resolve, reject, file, loadingFiles);
+ });
+ }
+
+ this.preload = function(pathOrBuffer, destPath) {
+ if (pathOrBuffer instanceof ArrayBuffer) {
+ pathOrBuffer = new Uint8Array(pathOrBuffer);
+ } else if (ArrayBuffer.isView(pathOrBuffer)) {
+ pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
+ }
+ if (pathOrBuffer instanceof Uint8Array) {
+ this.preloadedFiles.push({
+ path: destPath,
+ buffer: pathOrBuffer
+ });
+ return Promise.resolve();
+ } else if (typeof pathOrBuffer === 'string') {
+ var me = this;
+ return this.loadPromise(pathOrBuffer).then(function(xhr) {
+ me.preloadedFiles.push({
+ path: destPath || pathOrBuffer,
+ buffer: xhr.response
+ });
+ return Promise.resolve();
+ });
+ } else {
+ throw Promise.reject("Invalid object for preloading");
+ }
+ };
+
+ var animateProgress = function() {
+
+ var loaded = 0;
+ var total = 0;
+ var totalIsValid = true;
+ var progressIsFinal = true;
+
+ Object.keys(loadingFiles).forEach(function(file) {
+ const stat = loadingFiles[file];
+ if (!stat.final) {
+ progressIsFinal = false;
+ }
+ if (!totalIsValid || stat.total === 0) {
+ totalIsValid = false;
+ total = 0;
+ } else {
+ total += stat.total;
+ }
+ loaded += stat.loaded;
+ });
+ if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
+ lastProgress.loaded = loaded;
+ lastProgress.total = total;
+ if (typeof progressFunc === 'function')
+ progressFunc(loaded, total);
+ }
+ if (!progressIsFinal)
+ requestAnimationFrame(animateProgress);
+ }
+ this.animateProgress = animateProgress; // Also exposed to start it.
+
+ this.setProgressFunc = function(callback) {
+ progressFunc = callback;
+ }
+};
diff --git a/platform/javascript/engine/utils.js b/platform/javascript/engine/utils.js
new file mode 100644
index 0000000000..fdff90a923
--- /dev/null
+++ b/platform/javascript/engine/utils.js
@@ -0,0 +1,69 @@
+var Utils = {
+
+ createLocateRewrite: function(execName) {
+ function rw(path) {
+ if (path.endsWith('.worker.js')) {
+ return execName + '.worker.js';
+ } else if (path.endsWith('.js')) {
+ return execName + '.js';
+ } else if (path.endsWith('.wasm')) {
+ return execName + '.wasm';
+ }
+ }
+ return rw;
+ },
+
+ createInstantiatePromise: function(wasmLoader) {
+ function instantiateWasm(imports, onSuccess) {
+ wasmLoader.then(function(xhr) {
+ WebAssembly.instantiate(xhr.response, imports).then(function(result) {
+ onSuccess(result['instance'], result['module']);
+ });
+ });
+ wasmLoader = null;
+ return {};
+ };
+
+ return instantiateWasm;
+ },
+
+ copyToFS: function(fs, path, buffer) {
+ var p = path.lastIndexOf("/");
+ var dir = "/";
+ if (p > 0) {
+ dir = path.slice(0, path.lastIndexOf("/"));
+ }
+ try {
+ fs.stat(dir);
+ } catch (e) {
+ if (e.errno !== 44) { // 'ENOENT', see https://github.com/emscripten-core/emscripten/blob/master/system/lib/libc/musl/arch/emscripten/bits/errno.h
+ throw e;
+ }
+ fs['mkdirTree'](dir);
+ }
+ // With memory growth, canOwn should be false.
+ fs['writeFile'](path, new Uint8Array(buffer), {'flags': 'wx+'});
+ },
+
+ findCanvas: function() {
+ var nodes = document.getElementsByTagName('canvas');
+ if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {
+ return nodes[0];
+ }
+ throw new Error("No canvas found");
+ },
+
+ isWebGLAvailable: function(majorVersion = 1) {
+
+ var testContext = false;
+ try {
+ var testCanvas = document.createElement('canvas');
+ if (majorVersion === 1) {
+ testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
+ } else if (majorVersion === 2) {
+ testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
+ }
+ } catch (e) {}
+ return !!testContext;
+ }
+};
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index c44a0270ab..da61425747 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -94,6 +94,9 @@ public:
} else if (req[1] == basereq + ".js") {
filepath += ".js";
ctype = "application/javascript";
+ } else if (req[1] == basereq + ".worker.js") {
+ filepath += ".worker.js";
+ ctype = "application/javascript";
} else if (req[1] == basereq + ".pck") {
filepath += ".pck";
ctype = "application/octet-stream";
@@ -200,7 +203,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
private:
Ref<EditorHTTPServer> server;
bool server_quit;
- Mutex *server_lock;
+ Mutex server_lock;
Thread *server_thread;
static void _server_thread_poll(void *data);
@@ -432,6 +435,10 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
} else if (file == "godot.js") {
file = p_path.get_file().get_basename() + ".js";
+ } else if (file == "godot.worker.js") {
+
+ file = p_path.get_file().get_basename() + ".worker.js";
+
} else if (file == "godot.wasm") {
file = p_path.get_file().get_basename() + ".wasm";
@@ -531,9 +538,8 @@ bool EditorExportPlatformJavaScript::poll_export() {
menu_options = preset.is_valid();
if (server->is_listening()) {
if (menu_options == 0) {
- server_lock->lock();
+ MutexLock lock(server_lock);
server->stop();
- server_lock->unlock();
} else {
menu_options += 1;
}
@@ -553,9 +559,8 @@ int EditorExportPlatformJavaScript::get_options_count() const {
Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
if (p_option == 1) {
- server_lock->lock();
+ MutexLock lock(server_lock);
server->stop();
- server_lock->unlock();
return OK;
}
@@ -565,6 +570,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
// Export generates several files, clean them up on failure.
DirAccess::remove_file_or_error(basepath + ".html");
DirAccess::remove_file_or_error(basepath + ".js");
+ DirAccess::remove_file_or_error(basepath + ".worker.js");
DirAccess::remove_file_or_error(basepath + ".pck");
DirAccess::remove_file_or_error(basepath + ".png");
DirAccess::remove_file_or_error(basepath + ".wasm");
@@ -584,10 +590,12 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
ERR_FAIL_COND_V_MSG(!bind_ip.is_valid(), ERR_INVALID_PARAMETER, "Invalid editor setting 'export/web/http_host': '" + bind_host + "'. Try using '127.0.0.1'.");
// Restart server.
- server_lock->lock();
- server->stop();
- err = server->listen(bind_port, bind_ip);
- server_lock->unlock();
+ {
+ MutexLock lock(server_lock);
+
+ server->stop();
+ err = server->listen(bind_port, bind_ip);
+ }
ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to start HTTP server.");
OS::get_singleton()->shell_open(String("http://" + bind_host + ":" + itos(bind_port) + "/tmp_js_export.html"));
@@ -605,9 +613,10 @@ void EditorExportPlatformJavaScript::_server_thread_poll(void *data) {
EditorExportPlatformJavaScript *ej = (EditorExportPlatformJavaScript *)data;
while (!ej->server_quit) {
OS::get_singleton()->delay_usec(1000);
- ej->server_lock->lock();
- ej->server->poll();
- ej->server_lock->unlock();
+ {
+ MutexLock lock(ej->server_lock);
+ ej->server->poll();
+ }
}
}
@@ -615,7 +624,6 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() {
server.instance();
server_quit = false;
- server_lock = Mutex::create();
server_thread = Thread::create(_server_thread_poll, this);
Ref<Image> img = memnew(Image(_javascript_logo));
@@ -639,7 +647,6 @@ EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() {
server->stop();
server_quit = true;
Thread::wait_to_finish(server_thread);
- memdelete(server_lock);
memdelete(server_thread);
}
diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc
index 03e2ce8b8a..ac275aadbc 100644
--- a/platform/javascript/http_client.h.inc
+++ b/platform/javascript/http_client.h.inc
@@ -45,7 +45,7 @@ String password;
int polled_response_code;
String polled_response_header;
-PoolByteArray polled_response;
+PackedByteArray polled_response;
#ifdef DEBUG_ENABLED
bool has_polled;
diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp
index d7796cc4f4..472384cf30 100644
--- a/platform/javascript/http_client_javascript.cpp
+++ b/platform/javascript/http_client_javascript.cpp
@@ -108,8 +108,7 @@ Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector
Error err = prepare_request(p_method, p_url, p_headers);
if (err != OK)
return err;
- const uint8_t *read = p_body.ptr();
- godot_xhr_send_data(xhr_id, read.ptr(), p_body.size());
+ godot_xhr_send_data(xhr_id, p_body.ptr(), p_body.size());
return OK;
}
@@ -180,11 +179,7 @@ PackedByteArray HTTPClient::read_response_body_chunk() {
int to_read = MIN(read_limit, polled_response.size() - response_read_offset);
PackedByteArray chunk;
chunk.resize(to_read);
- uint8_t *write = chunk.ptrw();
- const uint8_t *read = polled_response.ptr();
- memcpy(write.ptr(), read.ptr() + response_read_offset, to_read);
- write = uint8_t * ();
- read = const uint8_t * ();
+ memcpy(chunk.ptrw(), polled_response.ptr() + response_read_offset, to_read);
response_read_offset += to_read;
if (response_read_offset == polled_response.size()) {
@@ -267,19 +262,13 @@ Error HTTPClient::poll() {
int len = godot_xhr_get_response_headers_length(xhr_id);
bytes.resize(len + 1);
- uint8_t *write = bytes.ptrw();
- godot_xhr_get_response_headers(xhr_id, reinterpret_cast<char *>(write.ptr()), len);
- write[len] = 0;
- write = uint8_t * ();
+ godot_xhr_get_response_headers(xhr_id, reinterpret_cast<char *>(bytes.ptrw()), len);
+ bytes.ptrw()[len] = 0;
- const uint8_t *read = bytes.ptr();
- polled_response_header = String::utf8(reinterpret_cast<const char *>(read.ptr()));
- read = const uint8_t * ();
+ polled_response_header = String::utf8(reinterpret_cast<const char *>(bytes.ptr()));
polled_response.resize(godot_xhr_get_response_length(xhr_id));
- write = polled_response.ptrw();
- godot_xhr_get_response(xhr_id, write.ptr(), polled_response.size());
- write = uint8_t * ();
+ godot_xhr_get_response(xhr_id, polled_response.ptrw(), polled_response.size());
break;
}
diff --git a/platform/javascript/id_handler.js b/platform/javascript/id_handler.js
index 3851123ed1..67d29075b8 100644
--- a/platform/javascript/id_handler.js
+++ b/platform/javascript/id_handler.js
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-var IDHandler = function() {
+var IDHandler = /** @constructor */ function() {
var ids = {};
var size = 0;
diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp
index 44cce28d57..db8050b90e 100644
--- a/platform/javascript/javascript_eval.cpp
+++ b/platform/javascript/javascript_eval.cpp
@@ -33,11 +33,11 @@
#include "api/javascript_eval.h"
#include "emscripten.h"
-extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_PackedByteArray_and_open_write(PackedByteArray *p_arr, uint8_t **r_write, int p_len) {
+extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_PackedByteArray_and_open_write(PackedByteArray *p_arr, VectorWriteProxy<uint8_t> *r_write, int p_len) {
p_arr->resize(p_len);
- *r_write = p_arr->write();
- return r_write->ptr();
+ *r_write = p_arr->write;
+ return p_arr->ptrw();
}
Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
@@ -49,7 +49,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
} js_data;
PackedByteArray arr;
- uint8_t *arr_write;
+ VectorWriteProxy<uint8_t> arr_write;
/* clang-format off */
Variant::Type return_type = static_cast<Variant::Type>(EM_ASM_INT({
@@ -138,7 +138,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
return str;
}
case Variant::PACKED_BYTE_ARRAY:
- arr_write = uint8_t * ();
+ arr_write = VectorWriteProxy<uint8_t>();
return arr;
default:
return Variant();
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index 5acdc5f602..1d7a16db80 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -31,7 +31,8 @@
#include "os_javascript.h"
#include "core/io/file_access_buffered_fa.h"
-#include "drivers/gles2/rasterizer_gles2.h"
+//#include "drivers/gles2/rasterizer_gles2.h"
+#include "drivers/dummy/rasterizer_dummy.h"
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
#include "main/main.h"
@@ -250,7 +251,8 @@ static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscrip
ev.instance();
ev->set_echo(emscripten_event->repeat);
dom2godot_mod(emscripten_event, ev);
- ev->set_scancode(dom2godot_scancode(emscripten_event->keyCode));
+ ev->set_keycode(dom2godot_keycode(emscripten_event->keyCode));
+ ev->set_physical_keycode(dom2godot_keycode(emscripten_event->keyCode));
String unicode = String::utf8(emscripten_event->key);
// Check if empty or multi-character (e.g. `CapsLock`).
@@ -270,7 +272,7 @@ EM_BOOL OS_JavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboa
OS_JavaScript *os = get_singleton();
Ref<InputEventKey> ev = setup_key_event(p_event);
ev->set_pressed(true);
- if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_scancode())) {
+ if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_keycode())) {
// Defer to keypress event for legacy unicode retrieval.
os->deferred_key_event = ev;
// Do not suppress keypress event.
@@ -295,7 +297,7 @@ EM_BOOL OS_JavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboard
Ref<InputEventKey> ev = setup_key_event(p_event);
ev->set_pressed(false);
get_singleton()->input->parse_input_event(ev);
- return ev->get_scancode() != KEY_UNKNOWN && ev->get_scancode() != 0;
+ return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != 0;
}
// Mouse
@@ -538,15 +540,11 @@ void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_s
PackedByteArray png;
size_t len;
- const uint8_t *r = image->get_data().ptr();
- ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, r.ptr(), 0, NULL));
+ PackedByteArray data = image->get_data();
+ ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, NULL));
png.resize(len);
- uint8_t *w = png.ptrw();
- ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, w.ptr(), &len, 0, r.ptr(), 0, NULL));
- w = uint8_t * ();
-
- r = png.ptr();
+ ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, NULL));
char *object_url;
/* clang-format off */
@@ -561,9 +559,8 @@ void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_s
var string_on_wasm_heap = _malloc(length_bytes);
setValue(PTR, string_on_wasm_heap, '*');
stringToUTF8(url, string_on_wasm_heap, length_bytes);
- }, r.ptr(), len, &object_url);
+ }, png.ptr(), len, &object_url);
/* clang-format on */
- r = const uint8_t * ();
String url = String::utf8(object_url) + "?" + itos(p_hotspot.x) + " " + itos(p_hotspot.y);
@@ -895,6 +892,7 @@ void OS_JavaScript::initialize_core() {
Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
+#if 0
EmscriptenWebGLContextAttributes attributes;
emscripten_webgl_init_context_attributes(&attributes);
attributes.alpha = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
@@ -937,6 +935,7 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver,
if (p_desired.fullscreen) {
/* clang-format off */
EM_ASM({
+ const canvas = Module.canvas;
(canvas.requestFullscreen || canvas.msRequestFullscreen ||
canvas.mozRequestFullScreen || canvas.mozRequestFullscreen ||
canvas.webkitRequestFullscreen
@@ -951,6 +950,8 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver,
} else {
set_window_size(get_window_size());
}
+#endif
+ RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu?
char locale_ptr[16];
/* clang-format off */
@@ -981,10 +982,10 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver,
SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, mousedown, mouse_button_callback)
SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mouseup, mouse_button_callback)
SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, wheel, wheel_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, touchstart, touch_press_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, touchmove, touchmove_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, touchend, touch_press_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, touchcancel, touch_press_callback)
+ SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, touchstart, touch_press_callback)
+ SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, touchmove, touchmove_callback)
+ SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, touchend, touch_press_callback)
+ SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, touchcancel, touch_press_callback)
SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keydown, keydown_callback)
SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keypress, keypress_callback)
SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keyup, keyup_callback)
@@ -1180,15 +1181,12 @@ void OS_JavaScript::set_icon(const Ref<Image> &p_icon) {
PackedByteArray png;
size_t len;
- const uint8_t *r = icon->get_data().ptr();
- ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, r.ptr(), 0, NULL));
+ PackedByteArray data = icon->get_data();
+ ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, NULL));
png.resize(len);
- uint8_t *w = png.ptrw();
- ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, w.ptr(), &len, 0, r.ptr(), 0, NULL));
- w = uint8_t * ();
+ ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, NULL));
- r = png.ptr();
/* clang-format off */
EM_ASM_ARGS({
var PNG_PTR = $0;
@@ -1204,7 +1202,7 @@ void OS_JavaScript::set_icon(const Ref<Image> &p_icon) {
document.head.appendChild(link);
}
link.href = url;
- }, r.ptr(), len);
+ }, png.ptr(), len);
/* clang-format on */
}
diff --git a/platform/javascript/pre.js b/platform/javascript/pre.js
deleted file mode 100644
index a870e676ea..0000000000
--- a/platform/javascript/pre.js
+++ /dev/null
@@ -1,5 +0,0 @@
-var Engine = {
- RuntimeEnvironment: function(Module, exposedLibs) {
- // The above is concatenated with generated code, and acts as the start of
- // a wrapper for said code. See engine.js for the other part of the
- // wrapper.
diff --git a/platform/osx/SCsub b/platform/osx/SCsub
index d764ac4b50..0a4e0a45e1 100644
--- a/platform/osx/SCsub
+++ b/platform/osx/SCsub
@@ -9,7 +9,6 @@ files = [
'crash_handler_osx.mm',
'os_osx.mm',
'godot_main_osx.mm',
- 'semaphore_osx.cpp',
'dir_access_osx.mm',
'joypad_osx.cpp',
'vulkan_context_osx.mm',
diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp
index 13ece678f3..e9f46fb5a4 100644
--- a/platform/osx/joypad_osx.cpp
+++ b/platform/osx/joypad_osx.cpp
@@ -208,9 +208,8 @@ void joypad::add_hid_elements(CFArrayRef p_array) {
CFArrayApplyFunction(p_array, range, hid_element_added, this);
}
-static void joypad_removed_callback(void *ctx, IOReturn result, void *sender) {
- int id = (intptr_t)ctx;
- self->_device_removed(id);
+static void joypad_removed_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) {
+ self->_device_removed(res, ioHIDDeviceObject);
}
static void joypad_added_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) {
@@ -261,16 +260,15 @@ void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) {
#endif
device_list.push_back(new_joypad);
}
- IOHIDDeviceRegisterRemovalCallback(p_device, joypad_removed_callback, (void *)(intptr_t)new_joypad.id);
IOHIDDeviceScheduleWithRunLoop(p_device, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE);
}
-void JoypadOSX::_device_removed(int p_id) {
+void JoypadOSX::_device_removed(IOReturn p_res, IOHIDDeviceRef p_device) {
- int device = get_joy_index(p_id);
+ int device = get_joy_ref(p_device);
ERR_FAIL_COND(device == -1);
- input->joy_connection_changed(p_id, false, "");
+ input->joy_connection_changed(device_list[device].id, false, "");
device_list.write[device].free();
device_list.remove(device);
}
@@ -516,6 +514,13 @@ int JoypadOSX::get_joy_index(int p_id) const {
return -1;
}
+int JoypadOSX::get_joy_ref(IOHIDDeviceRef p_device) const {
+ for (int i = 0; i < device_list.size(); i++) {
+ if (device_list[i].device_ref == p_device) return i;
+ }
+ return -1;
+}
+
bool JoypadOSX::have_device(IOHIDDeviceRef p_device) const {
for (int i = 0; i < device_list.size(); i++) {
if (device_list[i].device_ref == p_device) {
@@ -558,6 +563,7 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const {
IOHIDManagerSetDeviceMatchingMultiple(hid_manager, p_matching_array);
IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, joypad_added_callback, NULL);
+ IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, joypad_removed_callback, NULL);
IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE);
while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h
index 388251016b..2c076b3680 100644
--- a/platform/osx/joypad_osx.h
+++ b/platform/osx/joypad_osx.h
@@ -103,6 +103,7 @@ private:
bool configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy);
int get_joy_index(int p_id) const;
+ int get_joy_ref(IOHIDDeviceRef p_device) const;
void poll_joypads() const;
void setup_joypad_objects();
@@ -115,7 +116,7 @@ public:
void process_joypads();
void _device_added(IOReturn p_res, IOHIDDeviceRef p_device);
- void _device_removed(int p_id);
+ void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device);
JoypadOSX();
~JoypadOSX();
diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h
index 75a56bd82c..3140d9bac4 100644
--- a/platform/osx/os_osx.h
+++ b/platform/osx/os_osx.h
@@ -69,7 +69,8 @@ public:
bool pressed;
bool echo;
bool raw;
- uint32_t scancode;
+ uint32_t keycode;
+ uint32_t physical_keycode;
uint32_t unicode;
};
diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm
index f2e5f9369c..4c70beee00 100644
--- a/platform/osx/os_osx.mm
+++ b/platform/osx/os_osx.mm
@@ -44,7 +44,6 @@
#endif
#include "main/main.h"
-#include "semaphore_osx.h"
#include "servers/visual/visual_server_raster.h"
#include "servers/visual/visual_server_wrap_mt.h"
@@ -157,7 +156,8 @@ static NSCursor *cursorFromSelector(SEL selector, SEL fallback = nil) {
get_key_modifier_state([event modifierFlags], k);
k->set_pressed(true);
- k->set_scancode(KEY_PERIOD);
+ k->set_keycode(KEY_PERIOD);
+ k->set_physical_keycode(KEY_PERIOD);
k->set_echo([event isARepeat]);
OS_OSX::singleton->push_input(k);
@@ -635,7 +635,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
ke.pressed = true;
ke.echo = false;
ke.raw = false; // IME input event
- ke.scancode = 0;
+ ke.keycode = 0;
+ ke.physical_keycode = 0;
ke.unicode = codepoint;
push_to_key_event_buffer(ke);
@@ -1158,7 +1159,8 @@ static int remapKey(unsigned int key, unsigned int state) {
ke.osx_state = [event modifierFlags];
ke.pressed = true;
ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode], [event modifierFlags]);
+ ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = translateKey([event keyCode]);
ke.raw = true;
ke.unicode = [characters characterAtIndex:i];
@@ -1170,7 +1172,8 @@ static int remapKey(unsigned int key, unsigned int state) {
ke.osx_state = [event modifierFlags];
ke.pressed = true;
ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode], [event modifierFlags]);
+ ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = translateKey([event keyCode]);
ke.raw = false;
ke.unicode = 0;
@@ -1228,7 +1231,8 @@ static int remapKey(unsigned int key, unsigned int state) {
}
ke.osx_state = mod;
- ke.scancode = remapKey(key, mod);
+ ke.keycode = remapKey(key, mod);
+ ke.physical_keycode = translateKey(key);
ke.unicode = 0;
push_to_key_event_buffer(ke);
@@ -1250,7 +1254,8 @@ static int remapKey(unsigned int key, unsigned int state) {
ke.osx_state = [event modifierFlags];
ke.pressed = false;
ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode], [event modifierFlags]);
+ ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = translateKey([event keyCode]);
ke.raw = true;
ke.unicode = [characters characterAtIndex:i];
@@ -1262,7 +1267,8 @@ static int remapKey(unsigned int key, unsigned int state) {
ke.osx_state = [event modifierFlags];
ke.pressed = false;
ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode], [event modifierFlags]);
+ ke.keycode = remapKey([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = translateKey([event keyCode]);
ke.raw = true;
ke.unicode = 0;
@@ -1457,8 +1463,6 @@ void OS_OSX::initialize_core() {
DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_RESOURCES);
DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_USERDATA);
DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM);
-
- SemaphoreOSX::make_default();
}
static bool keyboard_layout_dirty = true;
@@ -2845,32 +2849,35 @@ void OS_OSX::process_key_events() {
get_key_modifier_state(ke.osx_state, k);
k->set_pressed(ke.pressed);
k->set_echo(ke.echo);
- k->set_scancode(ke.scancode);
+ k->set_keycode(ke.keycode);
+ k->set_physical_keycode(ke.physical_keycode);
k->set_unicode(ke.unicode);
push_input(k);
} else {
// IME input
- if ((i == 0 && ke.scancode == 0) || (i > 0 && key_event_buffer[i - 1].scancode == 0)) {
+ if ((i == 0 && ke.keycode == 0) || (i > 0 && key_event_buffer[i - 1].keycode == 0)) {
k.instance();
get_key_modifier_state(ke.osx_state, k);
k->set_pressed(ke.pressed);
k->set_echo(ke.echo);
- k->set_scancode(0);
+ k->set_keycode(0);
+ k->set_physical_keycode(0);
k->set_unicode(ke.unicode);
push_input(k);
}
- if (ke.scancode != 0) {
+ if (ke.keycode != 0) {
k.instance();
get_key_modifier_state(ke.osx_state, k);
k->set_pressed(ke.pressed);
k->set_echo(ke.echo);
- k->set_scancode(ke.scancode);
+ k->set_keycode(ke.keycode);
+ k->set_physical_keycode(ke.physical_keycode);
- if (i + 1 < key_event_pos && key_event_buffer[i + 1].scancode == 0) {
+ if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == 0) {
k->set_unicode(key_event_buffer[i + 1].unicode);
}
diff --git a/platform/server/SCsub b/platform/server/SCsub
index e8538f03a6..8364164114 100644
--- a/platform/server/SCsub
+++ b/platform/server/SCsub
@@ -10,7 +10,6 @@ common_server = [\
if sys.platform == "darwin":
common_server.append("#platform/osx/crash_handler_osx.mm")
- common_server.append("#platform/osx/semaphore_osx.cpp")
else:
common_server.append("#platform/x11/crash_handler_x11.cpp")
diff --git a/platform/server/detect.py b/platform/server/detect.py
index ef94dc436c..db9ba8d036 100644
--- a/platform/server/detect.py
+++ b/platform/server/detect.py
@@ -163,6 +163,10 @@ def configure(env):
sys.exit(255)
env.ParseConfig('pkg-config bullet --cflags --libs')
+ if False: # not env['builtin_assimp']:
+ # FIXME: Add min version check
+ env.ParseConfig('pkg-config assimp --cflags --libs')
+
if not env['builtin_enet']:
env.ParseConfig('pkg-config libenet --cflags --libs')
diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp
index 8a66332ff1..3257ec261c 100644
--- a/platform/server/os_server.cpp
+++ b/platform/server/os_server.cpp
@@ -68,10 +68,6 @@ void OS_Server::initialize_core() {
crash_handler.initialize();
OS_Unix::initialize_core();
-
-#ifdef __APPLE__
- SemaphoreOSX::make_default();
-#endif
}
Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
diff --git a/platform/uwp/app.cpp b/platform/uwp/app.cpp
index a47fe96c1b..ccb4b43373 100644
--- a/platform/uwp/app.cpp
+++ b/platform/uwp/app.cpp
@@ -410,14 +410,16 @@ void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Wind
ke.type = OS_UWP::KeyEvent::MessageType::KEY_EVENT_MESSAGE;
ke.unicode = 0;
- ke.scancode = KeyMappingWindows::get_keysym((unsigned int)key_args->VirtualKey);
+ ke.keycode = KeyMappingWindows::get_keysym((unsigned int)key_args->VirtualKey);
+ ke.physical_keycode = KeyMappingWindows::get_scansym((unsigned int)key_args->KeyStatus.ScanCode);
ke.echo = (!p_pressed && !key_args->KeyStatus.IsKeyReleased) || (p_pressed && key_args->KeyStatus.WasKeyDown);
} else {
ke.type = OS_UWP::KeyEvent::MessageType::CHAR_EVENT_MESSAGE;
ke.unicode = char_args->KeyCode;
- ke.scancode = 0;
+ ke.keycode = 0;
+ ke.physical_keycode = 0;
ke.echo = (!p_pressed && !char_args->KeyStatus.IsKeyReleased) || (p_pressed && char_args->KeyStatus.WasKeyDown);
}
diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp
index 3cd7a02a94..4ddb5463d0 100644
--- a/platform/uwp/os_uwp.cpp
+++ b/platform/uwp/os_uwp.cpp
@@ -140,8 +140,6 @@ void OS_UWP::initialize_core() {
//RedirectIOToConsole();
ThreadUWP::make_default();
- SemaphoreWindows::make_default();
- MutexWindows::make_default();
RWLockWindows::make_default();
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
@@ -606,7 +604,8 @@ void OS_UWP::process_key_events() {
key_event->set_shift(kev.shift);
key_event->set_control(kev.control);
key_event->set_echo(kev.echo);
- key_event->set_scancode(kev.scancode);
+ key_event->set_keycode(kev.keycode);
+ key_event->set_physical_keycode(kev.physical_keycode);
key_event->set_unicode(kev.unicode);
key_event->set_pressed(kev.pressed);
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index 32b899c0da..ac6e0f3dd5 100644
--- a/platform/uwp/os_uwp.h
+++ b/platform/uwp/os_uwp.h
@@ -61,7 +61,8 @@ public:
bool alt, shift, control;
MessageType type;
bool pressed;
- unsigned int scancode;
+ unsigned int keycode;
+ unsigned int physical_keycode;
unsigned int unicode;
bool echo;
CorePhysicalKeyStatus status;
diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp
index c76b31ca9c..da63e92622 100644
--- a/platform/windows/key_mapping_windows.cpp
+++ b/platform/windows/key_mapping_windows.cpp
@@ -238,6 +238,104 @@ VK_PA1 (0xFD)
VK_OEM_CLEAR (0xFE)
*/
+static _WinTranslatePair _scancode_to_keycode[] = {
+
+ { KEY_ESCAPE, 0x01 },
+ { KEY_1, 0x02 },
+ { KEY_2, 0x03 },
+ { KEY_3, 0x04 },
+ { KEY_4, 0x05 },
+ { KEY_5, 0x06 },
+ { KEY_6, 0x07 },
+ { KEY_7, 0x08 },
+ { KEY_8, 0x09 },
+ { KEY_9, 0x0A },
+ { KEY_0, 0x0B },
+ { KEY_MINUS, 0x0C },
+ { KEY_EQUAL, 0x0D },
+ { KEY_BACKSPACE, 0x0E },
+ { KEY_TAB, 0x0F },
+ { KEY_Q, 0x10 },
+ { KEY_W, 0x11 },
+ { KEY_E, 0x12 },
+ { KEY_R, 0x13 },
+ { KEY_T, 0x14 },
+ { KEY_Y, 0x15 },
+ { KEY_U, 0x16 },
+ { KEY_I, 0x17 },
+ { KEY_O, 0x18 },
+ { KEY_P, 0x19 },
+ { KEY_BRACELEFT, 0x1A },
+ { KEY_BRACERIGHT, 0x1B },
+ { KEY_ENTER, 0x1C },
+ { KEY_CONTROL, 0x1D },
+ { KEY_A, 0x1E },
+ { KEY_S, 0x1F },
+ { KEY_D, 0x20 },
+ { KEY_F, 0x21 },
+ { KEY_G, 0x22 },
+ { KEY_H, 0x23 },
+ { KEY_J, 0x24 },
+ { KEY_K, 0x25 },
+ { KEY_L, 0x26 },
+ { KEY_SEMICOLON, 0x27 },
+ { KEY_APOSTROPHE, 0x28 },
+ { KEY_QUOTELEFT, 0x29 },
+ { KEY_SHIFT, 0x2A },
+ { KEY_BACKSLASH, 0x2B },
+ { KEY_Z, 0x2C },
+ { KEY_X, 0x2D },
+ { KEY_C, 0x2E },
+ { KEY_V, 0x2F },
+ { KEY_B, 0x30 },
+ { KEY_N, 0x31 },
+ { KEY_M, 0x32 },
+ { KEY_COMMA, 0x33 },
+ { KEY_PERIOD, 0x34 },
+ { KEY_SLASH, 0x35 },
+ { KEY_SHIFT, 0x36 },
+ { KEY_PRINT, 0x37 },
+ { KEY_ALT, 0x38 },
+ { KEY_SPACE, 0x39 },
+ { KEY_CAPSLOCK, 0x3A },
+ { KEY_F1, 0x3B },
+ { KEY_F2, 0x3C },
+ { KEY_F3, 0x3D },
+ { KEY_F4, 0x3E },
+ { KEY_F5, 0x3F },
+ { KEY_F6, 0x40 },
+ { KEY_F7, 0x41 },
+ { KEY_F8, 0x42 },
+ { KEY_F9, 0x43 },
+ { KEY_F10, 0x44 },
+ { KEY_NUMLOCK, 0x45 },
+ { KEY_SCROLLLOCK, 0x46 },
+ { KEY_HOME, 0x47 },
+ { KEY_UP, 0x48 },
+ { KEY_PAGEUP, 0x49 },
+ { KEY_KP_SUBTRACT, 0x4A },
+ { KEY_LEFT, 0x4B },
+ { KEY_KP_5, 0x4C },
+ { KEY_RIGHT, 0x4D },
+ { KEY_KP_ADD, 0x4E },
+ { KEY_END, 0x4F },
+ { KEY_DOWN, 0x50 },
+ { KEY_PAGEDOWN, 0x51 },
+ { KEY_INSERT, 0x52 },
+ { KEY_DELETE, 0x53 },
+ //{ KEY_???, 0x56 }, //NON US BACKSLASH
+ { KEY_F11, 0x57 },
+ { KEY_F12, 0x58 },
+ { KEY_META, 0x5B },
+ { KEY_META, 0x5C },
+ { KEY_MENU, 0x5D },
+ { KEY_F13, 0x64 },
+ { KEY_F14, 0x65 },
+ { KEY_F15, 0x66 },
+ { KEY_F16, 0x67 },
+ { KEY_UNKNOWN, 0 }
+};
+
unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) {
for (int i = 0; _vk_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
@@ -251,3 +349,69 @@ unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) {
return KEY_UNKNOWN;
}
+
+unsigned int KeyMappingWindows::get_scansym(unsigned int p_code, bool p_extended) {
+ unsigned int keycode = KEY_UNKNOWN;
+ for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
+
+ if (_scancode_to_keycode[i].keycode == p_code) {
+ keycode = _scancode_to_keycode[i].keysym;
+ break;
+ }
+ }
+
+ if (p_extended) {
+ switch (keycode) {
+ case KEY_ENTER: {
+ keycode = KEY_KP_ENTER;
+ } break;
+ case KEY_SLASH: {
+ keycode = KEY_KP_DIVIDE;
+ } break;
+ case KEY_CAPSLOCK: {
+ keycode = KEY_KP_ADD;
+ } break;
+ }
+ } else {
+ switch (keycode) {
+ case KEY_NUMLOCK: {
+ keycode = KEY_PAUSE;
+ } break;
+ case KEY_HOME: {
+ keycode = KEY_KP_7;
+ } break;
+ case KEY_UP: {
+ keycode = KEY_KP_8;
+ } break;
+ case KEY_PAGEUP: {
+ keycode = KEY_KP_9;
+ } break;
+ case KEY_LEFT: {
+ keycode = KEY_KP_4;
+ } break;
+ case KEY_RIGHT: {
+ keycode = KEY_KP_6;
+ } break;
+ case KEY_END: {
+ keycode = KEY_KP_1;
+ } break;
+ case KEY_DOWN: {
+ keycode = KEY_KP_2;
+ } break;
+ case KEY_PAGEDOWN: {
+ keycode = KEY_KP_3;
+ } break;
+ case KEY_INSERT: {
+ keycode = KEY_KP_0;
+ } break;
+ case KEY_DELETE: {
+ keycode = KEY_KP_PERIOD;
+ } break;
+ case KEY_PRINT: {
+ keycode = KEY_KP_MULTIPLY;
+ } break;
+ }
+ }
+
+ return keycode;
+}
diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h
index 0f9bdecde1..3361ad397f 100644
--- a/platform/windows/key_mapping_windows.h
+++ b/platform/windows/key_mapping_windows.h
@@ -43,6 +43,7 @@ class KeyMappingWindows {
public:
static unsigned int get_keysym(unsigned int p_code);
+ static unsigned int get_scansym(unsigned int p_code, bool p_extended);
};
#endif // KEY_MAPPING_WINDOWS_H
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 716a637993..a112f26ac4 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -33,8 +33,9 @@
#include "os_windows.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/script_debugger.h"
#include "core/io/marshalls.h"
-#include "core/script_language.h"
#include "core/version_generated.gen.h"
#if defined(OPENGL_ENABLED)
@@ -47,9 +48,7 @@
#include "drivers/windows/dir_access_windows.h"
#include "drivers/windows/file_access_windows.h"
-#include "drivers/windows/mutex_windows.h"
#include "drivers/windows/rw_lock_windows.h"
-#include "drivers/windows/semaphore_windows.h"
#include "drivers/windows/thread_windows.h"
#include "joypad_windows.h"
#include "lang_table.h"
@@ -196,13 +195,13 @@ void RedirectIOToConsole() {
}
BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) {
- if (ScriptDebugger::get_singleton() == NULL)
+ if (!EngineDebugger::is_active())
return FALSE;
switch (dwCtrlType) {
case CTRL_C_EVENT:
- ScriptDebugger::get_singleton()->set_depth(-1);
- ScriptDebugger::get_singleton()->set_lines_left(1);
+ EngineDebugger::get_script_debugger()->set_depth(-1);
+ EngineDebugger::get_script_debugger()->set_lines_left(1);
return TRUE;
default:
return FALSE;
@@ -229,8 +228,6 @@ void OS_Windows::initialize_core() {
borderless = false;
ThreadWindows::make_default();
- SemaphoreWindows::make_default();
- MutexWindows::make_default();
RWLockWindows::make_default();
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
@@ -1135,7 +1132,8 @@ void OS_Windows::process_key_events() {
k->set_control(ke.control);
k->set_metakey(ke.meta);
k->set_pressed(true);
- k->set_scancode(KeyMappingWindows::get_keysym(ke.wParam));
+ k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam));
+ k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)));
k->set_unicode(ke.wParam);
if (k->get_unicode() && gr_mem) {
k->set_alt(false);
@@ -1165,11 +1163,13 @@ void OS_Windows::process_key_events() {
if ((ke.lParam & (1 << 24)) && (ke.wParam == VK_RETURN)) {
// Special case for Numpad Enter key
- k->set_scancode(KEY_KP_ENTER);
+ k->set_keycode(KEY_KP_ENTER);
} else {
- k->set_scancode(KeyMappingWindows::get_keysym(ke.wParam));
+ k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam));
}
+ k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)));
+
if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) {
k->set_unicode(key_event_buffer[i + 1].wParam);
}
@@ -2723,7 +2723,7 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
if (p_pipe_mutex) {
p_pipe_mutex->lock();
}
- (*r_pipe) += buf;
+ (*r_pipe) += String::utf8(buf);
if (p_pipe_mutex) {
p_pipe_mutex->unlock();
}
diff --git a/platform/x11/detect.py b/platform/x11/detect.py
index cd22ee9ff6..9a9ab86068 100644
--- a/platform/x11/detect.py
+++ b/platform/x11/detect.py
@@ -1,7 +1,6 @@
import os
import platform
import sys
-from methods import get_compiler_version, using_gcc, using_clang
def is_active():
@@ -226,6 +225,10 @@ def configure(env):
sys.exit(255)
env.ParseConfig('pkg-config bullet --cflags --libs')
+ if False: # not env['builtin_assimp']:
+ # FIXME: Add min version check
+ env.ParseConfig('pkg-config assimp --cflags --libs')
+
if not env['builtin_enet']:
env.ParseConfig('pkg-config libenet --cflags --libs')
diff --git a/platform/x11/detect_prime.cpp b/platform/x11/detect_prime.cpp
index 98a51ff27c..a0e5f835c0 100644
--- a/platform/x11/detect_prime.cpp
+++ b/platform/x11/detect_prime.cpp
@@ -62,6 +62,7 @@ vendor vendormap[] = {
{ "NVIDIA Corporation", 30 },
{ "X.Org", 30 },
{ "Intel Open Source Technology Center", 20 },
+ { "Intel", 20 },
{ "nouveau", 10 },
{ "Mesa Project", 0 },
{ NULL, 0 }
diff --git a/platform/x11/joypad_linux.cpp b/platform/x11/joypad_linux.cpp
index a64a25aeee..a9fe7275c2 100644
--- a/platform/x11/joypad_linux.cpp
+++ b/platform/x11/joypad_linux.cpp
@@ -83,7 +83,6 @@ void JoypadLinux::Joypad::reset() {
JoypadLinux::JoypadLinux(InputDefault *in) {
exit_udev = false;
input = in;
- joy_mutex = Mutex::create();
joy_thread = Thread::create(joy_thread_func, this);
}
@@ -91,7 +90,6 @@ JoypadLinux::~JoypadLinux() {
exit_udev = true;
Thread::wait_to_finish(joy_thread);
memdelete(joy_thread);
- memdelete(joy_mutex);
close_joypad();
}
@@ -137,9 +135,8 @@ void JoypadLinux::enumerate_joypads(udev *p_udev) {
String devnode_str = devnode;
if (devnode_str.find(ignore_str) == -1) {
- joy_mutex->lock();
+ MutexLock lock(joy_mutex);
open_joypad(devnode);
- joy_mutex->unlock();
}
}
udev_device_unref(dev);
@@ -176,7 +173,7 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
if (dev && udev_device_get_devnode(dev) != 0) {
- joy_mutex->lock();
+ MutexLock lock(joy_mutex);
String action = udev_device_get_action(dev);
const char *devnode = udev_device_get_devnode(dev);
if (devnode) {
@@ -192,7 +189,6 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
}
udev_device_unref(dev);
- joy_mutex->unlock();
}
}
usleep(50000);
@@ -204,15 +200,17 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
void JoypadLinux::monitor_joypads() {
while (!exit_udev) {
- joy_mutex->lock();
- for (int i = 0; i < 32; i++) {
- char fname[64];
- sprintf(fname, "/dev/input/event%d", i);
- if (attached_devices.find(fname) == -1) {
- open_joypad(fname);
+ {
+ MutexLock lock(joy_mutex);
+
+ for (int i = 0; i < 32; i++) {
+ char fname[64];
+ sprintf(fname, "/dev/input/event%d", i);
+ if (attached_devices.find(fname) == -1) {
+ open_joypad(fname);
+ }
}
}
- joy_mutex->unlock();
usleep(1000000); // 1s
}
}
@@ -457,7 +455,7 @@ InputDefault::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int
void JoypadLinux::process_joypads() {
- if (joy_mutex->try_lock() != OK) {
+ if (joy_mutex.try_lock() != OK) {
return;
}
for (int i = 0; i < JOYPADS_MAX; i++) {
@@ -548,6 +546,6 @@ void JoypadLinux::process_joypads() {
}
}
}
- joy_mutex->unlock();
+ joy_mutex.unlock();
}
#endif
diff --git a/platform/x11/joypad_linux.h b/platform/x11/joypad_linux.h
index e5638899bf..d5719b6dbe 100644
--- a/platform/x11/joypad_linux.h
+++ b/platform/x11/joypad_linux.h
@@ -72,7 +72,7 @@ private:
};
bool exit_udev;
- Mutex *joy_mutex;
+ Mutex joy_mutex;
Thread *joy_thread;
InputDefault *input;
Joypad joypads[JOYPADS_MAX];
diff --git a/platform/x11/key_mapping_x11.cpp b/platform/x11/key_mapping_x11.cpp
index 54e1e1d357..78bd2b71a0 100644
--- a/platform/x11/key_mapping_x11.cpp
+++ b/platform/x11/key_mapping_x11.cpp
@@ -180,6 +180,140 @@ static _XTranslatePair _xkeysym_to_keycode[] = {
{ 0, 0 }
};
+struct _TranslatePair {
+
+ unsigned int keysym;
+ unsigned int keycode;
+};
+
+static _TranslatePair _scancode_to_keycode[] = {
+
+ { KEY_ESCAPE, 0x09 },
+ { KEY_1, 0x0A },
+ { KEY_2, 0x0B },
+ { KEY_3, 0x0C },
+ { KEY_4, 0x0D },
+ { KEY_5, 0x0E },
+ { KEY_6, 0x0F },
+ { KEY_7, 0x10 },
+ { KEY_8, 0x11 },
+ { KEY_9, 0x12 },
+ { KEY_0, 0x13 },
+ { KEY_MINUS, 0x14 },
+ { KEY_EQUAL, 0x15 },
+ { KEY_BACKSPACE, 0x16 },
+ { KEY_TAB, 0x17 },
+ { KEY_Q, 0x18 },
+ { KEY_W, 0x19 },
+ { KEY_E, 0x1A },
+ { KEY_R, 0x1B },
+ { KEY_T, 0x1C },
+ { KEY_Y, 0x1D },
+ { KEY_U, 0x1E },
+ { KEY_I, 0x1F },
+ { KEY_O, 0x20 },
+ { KEY_P, 0x21 },
+ { KEY_BRACELEFT, 0x22 },
+ { KEY_BRACERIGHT, 0x23 },
+ { KEY_ENTER, 0x24 },
+ { KEY_CONTROL, 0x25 },
+ { KEY_A, 0x26 },
+ { KEY_S, 0x27 },
+ { KEY_D, 0x28 },
+ { KEY_F, 0x29 },
+ { KEY_G, 0x2A },
+ { KEY_H, 0x2B },
+ { KEY_J, 0x2C },
+ { KEY_K, 0x2D },
+ { KEY_L, 0x2E },
+ { KEY_SEMICOLON, 0x2F },
+ { KEY_APOSTROPHE, 0x30 },
+ { KEY_QUOTELEFT, 0x31 },
+ { KEY_SHIFT, 0x32 },
+ { KEY_BACKSLASH, 0x33 },
+ { KEY_Z, 0x34 },
+ { KEY_X, 0x35 },
+ { KEY_C, 0x36 },
+ { KEY_V, 0x37 },
+ { KEY_B, 0x38 },
+ { KEY_N, 0x39 },
+ { KEY_M, 0x3A },
+ { KEY_COMMA, 0x3B },
+ { KEY_PERIOD, 0x3C },
+ { KEY_SLASH, 0x3D },
+ { KEY_SHIFT, 0x3E },
+ { KEY_KP_MULTIPLY, 0x3F },
+ { KEY_ALT, 0x40 },
+ { KEY_SPACE, 0x41 },
+ { KEY_CAPSLOCK, 0x42 },
+ { KEY_F1, 0x43 },
+ { KEY_F2, 0x44 },
+ { KEY_F3, 0x45 },
+ { KEY_F4, 0x46 },
+ { KEY_F5, 0x47 },
+ { KEY_F6, 0x48 },
+ { KEY_F7, 0x49 },
+ { KEY_F8, 0x4A },
+ { KEY_F9, 0x4B },
+ { KEY_F10, 0x4C },
+ { KEY_NUMLOCK, 0x4D },
+ { KEY_SCROLLLOCK, 0x4E },
+ { KEY_KP_7, 0x4F },
+ { KEY_KP_8, 0x50 },
+ { KEY_KP_9, 0x51 },
+ { KEY_KP_SUBTRACT, 0x52 },
+ { KEY_KP_4, 0x53 },
+ { KEY_KP_5, 0x54 },
+ { KEY_KP_6, 0x55 },
+ { KEY_KP_ADD, 0x56 },
+ { KEY_KP_1, 0x57 },
+ { KEY_KP_2, 0x58 },
+ { KEY_KP_3, 0x59 },
+ { KEY_KP_0, 0x5A },
+ { KEY_KP_PERIOD, 0x5B },
+ //{ KEY_???, 0x5E }, //NON US BACKSLASH
+ { KEY_F11, 0x5F },
+ { KEY_F12, 0x60 },
+ { KEY_KP_ENTER, 0x68 },
+ { KEY_CONTROL, 0x69 },
+ { KEY_KP_DIVIDE, 0x6A },
+ { KEY_PRINT, 0x6B },
+ { KEY_ALT, 0x6C },
+ { KEY_ENTER, 0x6D },
+ { KEY_HOME, 0x6E },
+ { KEY_UP, 0x6F },
+ { KEY_PAGEUP, 0x70 },
+ { KEY_LEFT, 0x71 },
+ { KEY_RIGHT, 0x72 },
+ { KEY_END, 0x73 },
+ { KEY_DOWN, 0x74 },
+ { KEY_PAGEDOWN, 0x75 },
+ { KEY_INSERT, 0x76 },
+ { KEY_DELETE, 0x77 },
+ { KEY_VOLUMEMUTE, 0x79 },
+ { KEY_VOLUMEDOWN, 0x7A },
+ { KEY_VOLUMEUP, 0x7B },
+ { KEY_PAUSE, 0x7F },
+ { KEY_SUPER_L, 0x85 },
+ { KEY_SUPER_R, 0x86 },
+ { KEY_MENU, 0x87 },
+ { KEY_UNKNOWN, 0 }
+};
+
+unsigned int KeyMappingX11::get_scancode(unsigned int p_code) {
+
+ unsigned int keycode = KEY_UNKNOWN;
+ for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
+
+ if (_scancode_to_keycode[i].keycode == p_code) {
+ keycode = _scancode_to_keycode[i].keysym;
+ break;
+ }
+ }
+
+ return keycode;
+}
+
unsigned int KeyMappingX11::get_keycode(KeySym p_keysym) {
// kinda bruteforce.. could optimize.
diff --git a/platform/x11/key_mapping_x11.h b/platform/x11/key_mapping_x11.h
index e99bf1694b..10db43bcc4 100644
--- a/platform/x11/key_mapping_x11.h
+++ b/platform/x11/key_mapping_x11.h
@@ -45,6 +45,7 @@ class KeyMappingX11 {
public:
static unsigned int get_keycode(KeySym p_keysym);
+ static unsigned int get_scancode(unsigned int p_code);
static KeySym get_keysym(unsigned int p_code);
static unsigned int get_unicode_from_keysym(KeySym p_keysym);
static KeySym get_keysym_from_unicode(unsigned int p_unicode);
diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp
index 36e9681f5f..ddb5237d1b 100644
--- a/platform/x11/os_x11.cpp
+++ b/platform/x11/os_x11.cpp
@@ -1888,6 +1888,8 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
if (status == XLookupChars) {
bool keypress = xkeyevent->type == KeyPress;
unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
+ unsigned int physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+
if (keycode >= 'a' && keycode <= 'z')
keycode -= 'a' - 'A';
@@ -1896,23 +1898,29 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
for (int i = 0; i < tmp.length(); i++) {
Ref<InputEventKey> k;
k.instance();
- if (keycode == 0 && tmp[i] == 0) {
+ if (physical_keycode == 0 && keycode == 0 && tmp[i] == 0) {
continue;
}
+ if (keycode == 0)
+ keycode = physical_keycode;
+
get_key_modifier_state(xkeyevent->state, k);
k->set_unicode(tmp[i]);
k->set_pressed(keypress);
- k->set_scancode(keycode);
+ k->set_keycode(keycode);
+
+ k->set_physical_keycode(physical_keycode);
k->set_echo(false);
- if (k->get_scancode() == KEY_BACKTAB) {
+ if (k->get_keycode() == KEY_BACKTAB) {
//make it consistent across platforms.
- k->set_scancode(KEY_TAB);
+ k->set_keycode(KEY_TAB);
+ k->set_physical_keycode(KEY_TAB);
k->set_shift(true);
}
@@ -1942,6 +1950,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
// keysym, so it works in all platforms the same.
unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
+ unsigned int physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
/* Phase 3, obtain a unicode character from the keysym */
@@ -1961,9 +1970,12 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
bool keypress = xkeyevent->type == KeyPress;
- if (keycode == 0 && unicode == 0)
+ if (physical_keycode == 0 && keycode == 0 && unicode == 0)
return;
+ if (keycode == 0)
+ keycode = physical_keycode;
+
/* Phase 5, determine modifier mask */
// No problems here, except I had no way to
@@ -2000,8 +2012,10 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
// is correct, but the xorg developers are
// not very helpful today.
- ::Time tresh = ABSDIFF(peek_event.xkey.time, xkeyevent->time);
- if (peek_event.type == KeyPress && tresh < 5) {
+#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
+ ::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time);
+#undef ABSDIFF
+ if (peek_event.type == KeyPress && threshold < 5) {
KeySym rk;
XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, NULL);
if (rk == keysym_keycode) {
@@ -2025,37 +2039,39 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
if (keycode >= 'a' && keycode <= 'z')
keycode -= 'a' - 'A';
- k->set_scancode(keycode);
+ k->set_keycode(keycode);
+ k->set_physical_keycode(physical_keycode);
k->set_unicode(unicode);
k->set_echo(p_echo);
- if (k->get_scancode() == KEY_BACKTAB) {
+ if (k->get_keycode() == KEY_BACKTAB) {
//make it consistent across platforms.
- k->set_scancode(KEY_TAB);
+ k->set_keycode(KEY_TAB);
+ k->set_physical_keycode(KEY_TAB);
k->set_shift(true);
}
//don't set mod state if modifier keys are released by themselves
//else event.is_action() will not work correctly here
if (!k->is_pressed()) {
- if (k->get_scancode() == KEY_SHIFT)
+ if (k->get_keycode() == KEY_SHIFT)
k->set_shift(false);
- else if (k->get_scancode() == KEY_CONTROL)
+ else if (k->get_keycode() == KEY_CONTROL)
k->set_control(false);
- else if (k->get_scancode() == KEY_ALT)
+ else if (k->get_keycode() == KEY_ALT)
k->set_alt(false);
- else if (k->get_scancode() == KEY_META)
+ else if (k->get_keycode() == KEY_META)
k->set_metakey(false);
}
- bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_scancode());
+ bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_keycode());
if (k->is_pressed()) {
if (last_is_pressed) {
k->set_echo(true);
}
}
- //printf("key: %x\n",k->get_scancode());
+ //printf("key: %x\n",k->get_keycode());
input->accumulate_input_event(k);
}