diff options
Diffstat (limited to 'platform')
323 files changed, 15489 insertions, 14072 deletions
diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp index 88874ba2c7..b5b4fb9a4b 100644 --- a/platform/android/android_keys_utils.cpp +++ b/platform/android/android_keys_utils.cpp @@ -32,9 +32,7 @@ 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; } } diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h index f076688ac8..a10afa1df8 100644 --- a/platform/android/android_keys_utils.h +++ b/platform/android/android_keys_utils.h @@ -31,130 +31,10 @@ #ifndef ANDROID_KEYS_UTILS_H #define ANDROID_KEYS_UTILS_H +#include <android/input.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; }; @@ -247,6 +127,8 @@ static _WinTranslatePair _ak_to_keycode[] = { { KEY_BACKSLASH, AKEYCODE_BACKSLASH }, { KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET }, { KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET }, + { KEY_CONTROL, AKEYCODE_CTRL_LEFT }, + { KEY_CONTROL, AKEYCODE_CTRL_RIGHT }, { KEY_UNKNOWN, 0 } }; /* diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp index 4fe868d4f0..8b82733d2d 100644 --- a/platform/android/api/api.cpp +++ b/platform/android/api/api.cpp @@ -30,7 +30,7 @@ #include "api.h" -#include "core/engine.h" +#include "core/config/engine.h" #include "java_class_wrapper.h" #include "jni_singleton.h" @@ -39,7 +39,6 @@ static JavaClassWrapper *java_class_wrapper = nullptr; #endif void register_android_api() { - #if !defined(ANDROID_ENABLED) // On Android platforms, the `java_class_wrapper` instantiation and the // `JNISingleton` registration occurs in @@ -54,14 +53,12 @@ void register_android_api() { } void unregister_android_api() { - #if !defined(ANDROID_ENABLED) memdelete(java_class_wrapper); #endif } void JavaClassWrapper::_bind_methods() { - ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap); } diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index 59fcd94b4d..64da049407 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -31,7 +31,7 @@ #ifndef JAVA_CLASS_WRAPPER_H #define JAVA_CLASS_WRAPPER_H -#include "core/reference.h" +#include "core/object/reference.h" #ifdef ANDROID_ENABLED #include <android/log.h> @@ -43,12 +43,10 @@ class JavaObject; #endif class JavaClass : public Reference { - GDCLASS(JavaClass, Reference); #ifdef ANDROID_ENABLED enum ArgumentType{ - ARG_TYPE_VOID, ARG_TYPE_BOOLEAN, ARG_TYPE_BYTE, @@ -68,7 +66,6 @@ class JavaClass : public Reference { Map<StringName, Variant> constant_map; struct MethodInfo { - bool _static; Vector<uint32_t> param_types; Vector<StringName> param_sigs; @@ -77,15 +74,17 @@ class JavaClass : public Reference { }; _FORCE_INLINE_ static void _convert_to_variant_type(int p_sig, Variant::Type &r_type, float &likelihood) { - likelihood = 1.0; r_type = Variant::NIL; switch (p_sig) { - - case ARG_TYPE_VOID: r_type = Variant::NIL; break; + case ARG_TYPE_VOID: + r_type = Variant::NIL; + break; case ARG_TYPE_BOOLEAN | ARG_NUMBER_CLASS_BIT: - case ARG_TYPE_BOOLEAN: r_type = Variant::BOOL; break; + case ARG_TYPE_BOOLEAN: + r_type = Variant::BOOL; + break; case ARG_TYPE_BYTE | ARG_NUMBER_CLASS_BIT: case ARG_TYPE_BYTE: r_type = Variant::INT; @@ -121,10 +120,18 @@ class JavaClass : public Reference { r_type = Variant::FLOAT; likelihood = 0.5; break; - case ARG_TYPE_STRING: r_type = Variant::STRING; break; - case ARG_TYPE_CLASS: r_type = Variant::OBJECT; break; - case ARG_ARRAY_BIT | ARG_TYPE_VOID: r_type = Variant::NIL; break; - case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: r_type = Variant::ARRAY; break; + case ARG_TYPE_STRING: + r_type = Variant::STRING; + break; + case ARG_TYPE_CLASS: + r_type = Variant::OBJECT; + break; + case ARG_ARRAY_BIT | ARG_TYPE_VOID: + r_type = Variant::NIL; + break; + case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: + r_type = Variant::ARRAY; + break; case ARG_ARRAY_BIT | ARG_TYPE_BYTE: r_type = Variant::PACKED_BYTE_ARRAY; likelihood = 1.0; @@ -153,8 +160,12 @@ class JavaClass : public Reference { r_type = Variant::PACKED_FLOAT32_ARRAY; likelihood = 0.5; break; - case ARG_ARRAY_BIT | ARG_TYPE_STRING: r_type = Variant::PACKED_STRING_ARRAY; break; - case ARG_ARRAY_BIT | ARG_TYPE_CLASS: r_type = Variant::ARRAY; break; + case ARG_ARRAY_BIT | ARG_TYPE_STRING: + r_type = Variant::PACKED_STRING_ARRAY; + break; + case ARG_ARRAY_BIT | ARG_TYPE_CLASS: + r_type = Variant::ARRAY; + break; } } @@ -168,13 +179,12 @@ class JavaClass : public Reference { #endif public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; JavaClass(); }; class JavaObject : public Reference { - GDCLASS(JavaObject, Reference); #ifdef ANDROID_ENABLED @@ -185,7 +195,7 @@ class JavaObject : public Reference { #endif public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; #ifdef ANDROID_ENABLED JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance); @@ -194,7 +204,6 @@ public: }; class JavaClassWrapper : public Object { - GDCLASS(JavaClassWrapper, Object); #ifdef ANDROID_ENABLED diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index 917c3f5029..7f20c354a0 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -31,19 +31,17 @@ #ifndef JNI_SINGLETON_H #define JNI_SINGLETON_H -#include <core/engine.h> -#include <core/variant.h> +#include <core/config/engine.h> +#include <core/variant/variant.h> #ifdef ANDROID_ENABLED #include <platform/android/jni_utils.h> #endif class JNISingleton : public Object { - GDCLASS(JNISingleton, Object); #ifdef ANDROID_ENABLED struct MethodData { - jmethodID method; Variant::Type ret_type; Vector<Variant::Type> argtypes; @@ -54,7 +52,7 @@ class JNISingleton : public Object { #endif public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { #ifdef ANDROID_ENABLED Map<StringName, MethodData>::Element *E = method_map.find(p_method); @@ -63,7 +61,6 @@ public: bool call_error = !E || E->get().argtypes.size() != p_argcount; if (!call_error) { for (int i = 0; i < p_argcount; i++) { - if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) { call_error = true; break; @@ -83,7 +80,6 @@ public: jvalue *v = nullptr; if (p_argcount) { - v = (jvalue *)alloca(sizeof(jvalue) * p_argcount); } @@ -95,7 +91,6 @@ public: 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) @@ -105,31 +100,24 @@ public: 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); @@ -137,7 +125,6 @@ public: env->DeleteLocalRef(arr); } break; case Variant::PACKED_INT32_ARRAY: { - jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v); int fCount = env->GetArrayLength(arr); @@ -150,7 +137,6 @@ public: env->DeleteLocalRef(arr); } break; case Variant::PACKED_FLOAT32_ARRAY: { - jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v); int fCount = env->GetArrayLength(arr); @@ -167,14 +153,12 @@ public: #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(nullptr); ERR_FAIL_V(Variant()); } break; @@ -197,17 +181,14 @@ public: #ifdef ANDROID_ENABLED 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; diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp index 802d85e7be..1363c5ac1e 100644 --- a/platform/android/audio_driver_jandroid.cpp +++ b/platform/android/audio_driver_jandroid.cpp @@ -30,8 +30,8 @@ #include "audio_driver_jandroid.h" +#include "core/config/project_settings.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "thread_jandroid.h" AudioDriverAndroid *AudioDriverAndroid::s_ad = nullptr; @@ -52,12 +52,10 @@ Mutex AudioDriverAndroid::mutex; int32_t *AudioDriverAndroid::audioBuffer32 = nullptr; const char *AudioDriverAndroid::get_name() const { - return "Android"; } Error AudioDriverAndroid::init() { - /* // 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); @@ -75,9 +73,9 @@ Error AudioDriverAndroid::init() { // __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device"); JNIEnv *env = ThreadAndroid::get_env(); - int mix_rate = GLOBAL_DEF_RST("audio/mix_rate", 44100); + int mix_rate = GLOBAL_GET("audio/mix_rate"); - int latency = GLOBAL_DEF_RST("audio/output_latency", 25); + int latency = GLOBAL_GET("audio/output_latency"); unsigned int buffer_size = next_power_of_2(latency * mix_rate / 1000); print_verbose("Audio buffer size: " + itos(buffer_size)); @@ -100,7 +98,6 @@ void AudioDriverAndroid::start() { } void AudioDriverAndroid::setup(jobject p_io) { - JNIEnv *env = ThreadAndroid::get_env(); io = p_io; @@ -114,10 +111,8 @@ void AudioDriverAndroid::setup(jobject p_io) { } void AudioDriverAndroid::thread_func(JNIEnv *env) { - jclass cls = env->FindClass("org/godotengine/godot/Godot"); if (cls) { - cls = (jclass)env->NewGlobalRef(cls); } jfieldID fid = env->GetStaticFieldID(cls, "io", "Lorg/godotengine/godot/GodotIO;"); @@ -128,24 +123,20 @@ void AudioDriverAndroid::thread_func(JNIEnv *env) { _write_buffer = env->GetMethodID(lcls, "audioWriteShortBuffer", "([S)V"); while (!quit) { - int16_t *ptr = (int16_t *)audioBufferPinned; int fc = audioBufferFrames; if (!s_ad->active || mutex.try_lock() != OK) { - for (int i = 0; i < fc; i++) { ptr[i] = 0; } } else { - s_ad->audio_server_process(fc / 2, audioBuffer32); mutex.unlock(); for (int i = 0; i < fc; i++) { - ptr[i] = audioBuffer32[i] >> 16; } } @@ -155,27 +146,22 @@ void AudioDriverAndroid::thread_func(JNIEnv *env) { } int AudioDriverAndroid::get_mix_rate() const { - return mix_rate; } AudioDriver::SpeakerMode AudioDriverAndroid::get_speaker_mode() const { - return SPEAKER_MODE_STEREO; } void AudioDriverAndroid::lock() { - mutex.lock(); } void AudioDriverAndroid::unlock() { - mutex.unlock(); } void AudioDriverAndroid::finish() { - JNIEnv *env = ThreadAndroid::get_env(); env->CallVoidMethod(io, _quit); @@ -189,13 +175,11 @@ void AudioDriverAndroid::finish() { } void AudioDriverAndroid::set_pause(bool p_pause) { - JNIEnv *env = ThreadAndroid::get_env(); env->CallVoidMethod(io, _pause, p_pause); } AudioDriverAndroid::AudioDriverAndroid() { - s_ad = this; active = false; } diff --git a/platform/android/audio_driver_jandroid.h b/platform/android/audio_driver_jandroid.h index b1cc3f9aa0..953ade9311 100644 --- a/platform/android/audio_driver_jandroid.h +++ b/platform/android/audio_driver_jandroid.h @@ -36,7 +36,6 @@ #include "java_godot_lib_jni.h" class AudioDriverAndroid : public AudioDriver { - static Mutex mutex; static AudioDriverAndroid *s_ad; static jobject io; diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp index e59850e016..740e9a3132 100644 --- a/platform/android/audio_driver_opensl.cpp +++ b/platform/android/audio_driver_opensl.cpp @@ -39,7 +39,6 @@ void AudioDriverOpenSL::_buffer_callback( SLAndroidSimpleBufferQueueItf queueItf) { - bool mix = true; if (pause) { @@ -51,7 +50,6 @@ void AudioDriverOpenSL::_buffer_callback( if (mix) { audio_server_process(buffer_size, mixdown_buffer); } else { - int32_t *src_buff = mixdown_buffer; for (unsigned int i = 0; i < buffer_size * 2; i++) { src_buff[i] = 0; @@ -67,7 +65,6 @@ void AudioDriverOpenSL::_buffer_callback( last_free = (last_free + 1) % BUFFER_COUNT; for (unsigned int i = 0; i < buffer_size * 2; i++) { - ptr[i] = src_buff[i] >> 16; } @@ -77,7 +74,6 @@ void AudioDriverOpenSL::_buffer_callback( void AudioDriverOpenSL::_buffer_callbacks( SLAndroidSimpleBufferQueueItf queueItf, void *pContext) { - AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext; ad->_buffer_callback(queueItf); @@ -86,12 +82,10 @@ void AudioDriverOpenSL::_buffer_callbacks( AudioDriverOpenSL *AudioDriverOpenSL::s_ad = nullptr; const char *AudioDriverOpenSL::get_name() const { - return "Android"; } Error AudioDriverOpenSL::init() { - SLresult res; SLEngineOption EngineOption[] = { { (SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE } @@ -106,7 +100,6 @@ Error AudioDriverOpenSL::init() { } void AudioDriverOpenSL::start() { - active = false; SLresult res; @@ -114,7 +107,6 @@ void AudioDriverOpenSL::start() { buffer_size = 1024; for (int i = 0; i < BUFFER_COUNT; i++) { - buffers[i] = memnew_arr(int16_t, buffer_size * 2); memset(buffers[i], 0, buffer_size * 4); } @@ -204,7 +196,6 @@ void AudioDriverOpenSL::start() { } void AudioDriverOpenSL::_record_buffer_callback(SLAndroidSimpleBufferQueueItf queueItf) { - for (int i = 0; i < rec_buffer.size(); i++) { int32_t sample = rec_buffer[i] << 16; input_buffer_write(sample); @@ -216,14 +207,12 @@ void AudioDriverOpenSL::_record_buffer_callback(SLAndroidSimpleBufferQueueItf qu } void AudioDriverOpenSL::_record_buffer_callbacks(SLAndroidSimpleBufferQueueItf queueItf, void *pContext) { - AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext; ad->_record_buffer_callback(queueItf); } Error AudioDriverOpenSL::capture_init_device() { - SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, @@ -291,7 +280,6 @@ Error AudioDriverOpenSL::capture_init_device() { } Error AudioDriverOpenSL::capture_start() { - if (OS::get_singleton()->request_permission("RECORD_AUDIO")) { return capture_init_device(); } @@ -300,7 +288,6 @@ Error AudioDriverOpenSL::capture_start() { } Error AudioDriverOpenSL::capture_stop() { - SLuint32 state; SLresult res = (*recordItf)->GetRecordState(recordItf, &state); ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); @@ -317,34 +304,28 @@ Error AudioDriverOpenSL::capture_stop() { } int AudioDriverOpenSL::get_mix_rate() const { - return 44100; // hardcoded for Android, as selected by SL_SAMPLINGRATE_44_1 } AudioDriver::SpeakerMode AudioDriverOpenSL::get_speaker_mode() const { - return SPEAKER_MODE_STEREO; } void AudioDriverOpenSL::lock() { - if (active) mutex.lock(); } void AudioDriverOpenSL::unlock() { - if (active) mutex.unlock(); } void AudioDriverOpenSL::finish() { - (*sl)->Destroy(sl); } void AudioDriverOpenSL::set_pause(bool p_pause) { - pause = p_pause; if (active) { diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h index 569e2aa54b..b30711705b 100644 --- a/platform/android/audio_driver_opensl.h +++ b/platform/android/audio_driver_opensl.h @@ -38,12 +38,10 @@ #include <SLES/OpenSLES_Android.h> class AudioDriverOpenSL : public AudioDriver { - bool active; Mutex mutex; enum { - BUFFER_COUNT = 2 }; diff --git a/platform/android/detect.py b/platform/android/detect.py index 6da1e5f3d6..0accacb679 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -115,7 +115,8 @@ def configure(env): if env["android_arch"] == "x86_64": if get_platform(env["ndk_platform"]) < 21: print( - "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21" + "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting" + " ndk_platform=android-21" ) env["ndk_platform"] = "android-21" env["ARCH"] = "arch-x86_64" @@ -136,7 +137,8 @@ def configure(env): elif env["android_arch"] == "arm64v8": if get_platform(env["ndk_platform"]) < 21: print( - "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21" + "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting" + " ndk_platform=android-21" ) env["ndk_platform"] = "android-21" env["ARCH"] = "arch-arm64" @@ -164,7 +166,7 @@ def configure(env): elif env["target"] == "debug": env.Append(LINKFLAGS=["-O0"]) env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"]) - env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED"]) env.Append(CPPFLAGS=["-UNDEBUG"]) # Compiler configuration @@ -231,7 +233,10 @@ def configure(env): env.Append(CPPDEFINES=[("__ANDROID_API__", str(get_platform(env["ndk_platform"])))]) env.Append( - CCFLAGS="-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split() + CCFLAGS=( + "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden" + " -fno-strict-aliasing".split() + ) ) env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"]) diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index f8571e6277..ba75a4b10c 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "dir_access_jandroid.h" -#include "core/print_string.h" +#include "core/string/print_string.h" #include "file_access_jandroid.h" #include "string_android.h" #include "thread_jandroid.h" @@ -42,12 +42,10 @@ jmethodID DirAccessJAndroid::_dir_close = nullptr; jmethodID DirAccessJAndroid::_dir_is_dir = nullptr; DirAccess *DirAccessJAndroid::create_fs() { - return memnew(DirAccessJAndroid); } Error DirAccessJAndroid::list_dir_begin() { - list_dir_end(); JNIEnv *env = ThreadAndroid::get_env(); @@ -62,7 +60,6 @@ Error DirAccessJAndroid::list_dir_begin() { } String DirAccessJAndroid::get_next() { - ERR_FAIL_COND_V(id == 0, ""); JNIEnv *env = ThreadAndroid::get_env(); @@ -76,19 +73,16 @@ String DirAccessJAndroid::get_next() { } bool DirAccessJAndroid::current_is_dir() const { - JNIEnv *env = ThreadAndroid::get_env(); return env->CallBooleanMethod(io, _dir_is_dir, id); } bool DirAccessJAndroid::current_is_hidden() const { - return current != "." && current != ".." && current.begins_with("."); } void DirAccessJAndroid::list_dir_end() { - if (id == 0) return; @@ -98,17 +92,14 @@ void DirAccessJAndroid::list_dir_end() { } int DirAccessJAndroid::get_drive_count() { - return 0; } String DirAccessJAndroid::get_drive(int p_drive) { - return ""; } Error DirAccessJAndroid::change_dir(String p_dir) { - JNIEnv *env = ThreadAndroid::get_env(); if (p_dir == "" || p_dir == "." || (p_dir == ".." && current_dir == "")) @@ -145,12 +136,10 @@ Error DirAccessJAndroid::change_dir(String p_dir) { } String DirAccessJAndroid::get_current_dir(bool p_include_drive) { - return "res://" + current_dir; } bool DirAccessJAndroid::file_exists(String p_file) { - String sd; if (current_dir == "") sd = p_file; @@ -165,7 +154,6 @@ bool DirAccessJAndroid::file_exists(String p_file) { } bool DirAccessJAndroid::dir_exists(String p_dir) { - JNIEnv *env = ThreadAndroid::get_env(); String sd; @@ -198,33 +186,27 @@ bool DirAccessJAndroid::dir_exists(String p_dir) { } Error DirAccessJAndroid::make_dir(String p_dir) { - ERR_FAIL_V(ERR_UNAVAILABLE); } Error DirAccessJAndroid::rename(String p_from, String p_to) { - ERR_FAIL_V(ERR_UNAVAILABLE); } Error DirAccessJAndroid::remove(String p_name) { - ERR_FAIL_V(ERR_UNAVAILABLE); } String DirAccessJAndroid::get_filesystem_type() const { - return "APK"; } //FileType get_file_type() const; size_t DirAccessJAndroid::get_space_left() { - return 0; } void DirAccessJAndroid::setup(jobject p_io) { - JNIEnv *env = ThreadAndroid::get_env(); io = p_io; @@ -240,11 +222,9 @@ void DirAccessJAndroid::setup(jobject p_io) { } DirAccessJAndroid::DirAccessJAndroid() { - id = 0; } DirAccessJAndroid::~DirAccessJAndroid() { - list_dir_end(); } diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index 8dab3e50ce..7d0def137a 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -36,7 +36,6 @@ #include <stdio.h> class DirAccessJAndroid : public DirAccess { - //AAssetDir* aad; static jobject io; diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 9534387d35..e82a12ece5 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -31,14 +31,13 @@ #include "display_server_android.h" #include "android_keys_utils.h" -#include "core/project_settings.h" +#include "core/config/project_settings.h" #include "java_godot_io_wrapper.h" #include "java_godot_wrapper.h" #include "os_android.h" -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif +#include <android/input.h> + #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_device_vulkan.h" #include "platform/android/vulkan/vulkan_context_android.h" @@ -58,7 +57,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const { //case FEATURE_HIDPI: //case FEATURE_ICON: //case FEATURE_IME: - //case FEATURE_MOUSE: + case FEATURE_MOUSE: //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: //case FEATURE_NATIVE_ICON: @@ -140,8 +139,11 @@ Size2i DisplayServerAndroid::screen_get_size(int p_screen) const { } Rect2i DisplayServerAndroid::screen_get_usable_rect(int p_screen) const { - Size2i display_size = OS_Android::get_singleton()->get_display_size(); - return Rect2i(0, 0, display_size.width, display_size.height); + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + ERR_FAIL_COND_V(!godot_io_java, Rect2i()); + int xywh[4]; + godot_io_java->screen_get_usable_rect(xywh); + return Rect2i(xywh[0], xywh[1], xywh[2], xywh[3]); } int DisplayServerAndroid::screen_get_dpi(int p_screen) const { @@ -155,12 +157,12 @@ bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const { return true; } -void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length) { +void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) { GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); ERR_FAIL_COND(!godot_io_java); if (godot_io_java->has_vk()) { - godot_io_java->show_vk(p_existing_text, p_max_length); + godot_io_java->show_vk(p_existing_text, p_multiline, p_max_length, p_cursor_start, p_cursor_end); } else { ERR_PRINT("Virtual keyboard not available"); } @@ -343,7 +345,7 @@ void DisplayServerAndroid::alert(const String &p_alert, const String &p_title) { } void DisplayServerAndroid::process_events() { - // Nothing to do + Input::get_singleton()->flush_accumulated_events(); } Vector<String> DisplayServerAndroid::get_rendering_drivers_func() { @@ -360,13 +362,36 @@ Vector<String> DisplayServerAndroid::get_rendering_drivers_func() { } DisplayServer *DisplayServerAndroid::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - return memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + DisplayServer *ds = memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + if (r_error != OK) { + ds->alert("Your video card driver does not support any of the supported Vulkan versions.", "Unable to initialize Video driver"); + } + return ds; } void DisplayServerAndroid::register_android_driver() { register_create_function("android", create_func, get_rendering_drivers_func); } +void DisplayServerAndroid::reset_window() { +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window(); + ERR_FAIL_COND(!native_window); + + ERR_FAIL_COND(!context_vulkan); + context_vulkan->window_destroy(MAIN_WINDOW_ID); + + Size2i display_size = OS_Android::get_singleton()->get_display_size(); + if (context_vulkan->window_create(native_window, display_size.width, display_size.height) == -1) { + memdelete(context_vulkan); + context_vulkan = nullptr; + ERR_FAIL_MSG("Failed to reset Vulkan window."); + } + } +#endif +} + DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { rendering_driver = p_rendering_driver; @@ -375,6 +400,8 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis keep_screen_on = GLOBAL_GET("display/window/energy_saving/keep_screen_on"); + buttons_state = 0; + #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl") { bool gl_initialization_error = false; @@ -424,7 +451,9 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis } #endif - InputFilter::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + + r_error = OK; } DisplayServerAndroid::~DisplayServerAndroid() { @@ -445,33 +474,56 @@ DisplayServerAndroid::~DisplayServerAndroid() { void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p_event) { switch (p_event.type) { case JOY_EVENT_BUTTON: - InputFilter::get_singleton()->joy_button(p_event.device, p_event.index, p_event.pressed); + Input::get_singleton()->joy_button(p_event.device, p_event.index, p_event.pressed); break; case JOY_EVENT_AXIS: - InputFilter::JoyAxis value; + Input::JoyAxis value; value.min = -1; value.value = p_event.value; - InputFilter::get_singleton()->joy_axis(p_event.device, p_event.index, value); + Input::get_singleton()->joy_axis(p_event.device, p_event.index, value); break; case JOY_EVENT_HAT: - InputFilter::get_singleton()->joy_hat(p_event.device, p_event.hat); + Input::get_singleton()->joy_hat(p_event.device, p_event.hat); break; default: return; } } +void DisplayServerAndroid::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) { + ev->set_shift(shift_mem); + ev->set_alt(alt_mem); + ev->set_metakey(meta_mem); + ev->set_control(control_mem); +} + void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) { Ref<InputEventKey> ev; ev.instance(); int val = p_unicode_char; int keycode = android_get_keysym(p_keycode); int phy_keycode = android_get_keysym(p_scancode); + + if (keycode == KEY_SHIFT) { + shift_mem = p_pressed; + } + if (keycode == KEY_ALT) { + alt_mem = p_pressed; + } + if (keycode == KEY_CONTROL) { + control_mem = p_pressed; + } + if (keycode == KEY_META) { + meta_mem = p_pressed; + } + ev->set_keycode(keycode); ev->set_physical_keycode(phy_keycode); ev->set_unicode(val); ev->set_pressed(p_pressed); + _set_key_modifier_state(ev); + if (val == '\n') { ev->set_keycode(KEY_ENTER); } else if (val == 61448) { @@ -484,22 +536,21 @@ void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int OS_Android::get_singleton()->main_loop_request_go_back(); } - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); } -void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector<DisplayServerAndroid::TouchPos> &p_points) { - switch (p_what) { - case 0: { //gesture begin +void DisplayServerAndroid::process_touch(int p_event, int p_pointer, const Vector<DisplayServerAndroid::TouchPos> &p_points) { + switch (p_event) { + case AMOTION_EVENT_ACTION_DOWN: { //gesture begin if (touch.size()) { //end all if exist for (int i = 0; i < touch.size(); i++) { - Ref<InputEventScreenTouch> ev; ev.instance(); ev->set_index(touch[i].id); ev->set_pressed(false); ev->set_position(touch[i].pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); } } @@ -511,24 +562,21 @@ void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector //send touch for (int i = 0; i < touch.size(); i++) { - Ref<InputEventScreenTouch> ev; ev.instance(); ev->set_index(touch[i].id); ev->set_pressed(true); ev->set_position(touch[i].pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); } } break; - case 1: { //motion + case AMOTION_EVENT_ACTION_MOVE: { //motion ERR_FAIL_COND(touch.size() != p_points.size()); for (int i = 0; i < touch.size(); i++) { - int idx = -1; for (int j = 0; j < p_points.size(); j++) { - if (touch[i].id == p_points[j].id) { idx = j; break; @@ -545,27 +593,27 @@ void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector ev->set_index(touch[i].id); ev->set_position(p_points[idx].pos); ev->set_relative(p_points[idx].pos - touch[i].pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); touch.write[i].pos = p_points[idx].pos; } } break; - case 2: { //release + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: { //release if (touch.size()) { //end all if exist for (int i = 0; i < touch.size(); i++) { - Ref<InputEventScreenTouch> ev; ev.instance(); ev->set_index(touch[i].id); ev->set_pressed(false); ev->set_position(touch[i].pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); } touch.clear(); } } break; - case 3: { // add touch + case AMOTION_EVENT_ACTION_POINTER_DOWN: { // add touch for (int i = 0; i < p_points.size(); i++) { if (p_points[i].id == p_pointer) { TouchPos tp = p_points[i]; @@ -577,22 +625,21 @@ void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector ev->set_index(tp.id); ev->set_pressed(true); ev->set_position(tp.pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); break; } } } break; - case 4: { // remove touch + case AMOTION_EVENT_ACTION_POINTER_UP: { // remove touch for (int i = 0; i < touch.size(); i++) { if (touch[i].id == p_pointer) { - Ref<InputEventScreenTouch> ev; ev.instance(); ev->set_index(touch[i].id); ev->set_pressed(false); ev->set_position(touch[i].pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); touch.remove(i); break; @@ -605,51 +652,169 @@ void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) { // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER switch (p_type) { - case 7: // hover move - case 9: // hover enter - case 10: { // hover exit + case AMOTION_EVENT_ACTION_HOVER_MOVE: // hover move + case AMOTION_EVENT_ACTION_HOVER_ENTER: // hover enter + case AMOTION_EVENT_ACTION_HOVER_EXIT: { // hover exit Ref<InputEventMouseMotion> ev; ev.instance(); + _set_key_modifier_state(ev); ev->set_position(p_pos); ev->set_global_position(p_pos); ev->set_relative(p_pos - hover_prev_pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); hover_prev_pos = p_pos; } break; } } -void DisplayServerAndroid::process_double_tap(Point2 p_pos) { +void DisplayServerAndroid::process_mouse_event(int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor, float event_horizontal_factor) { + int event_buttons_mask = _android_button_mask_to_godot_button_mask(event_android_buttons_mask); + switch (event_action) { + case AMOTION_EVENT_ACTION_BUTTON_PRESS: + case AMOTION_EVENT_ACTION_BUTTON_RELEASE: { + Ref<InputEventMouseButton> ev; + ev.instance(); + _set_key_modifier_state(ev); + ev->set_position(event_pos); + ev->set_global_position(event_pos); + ev->set_pressed(event_action == AMOTION_EVENT_ACTION_BUTTON_PRESS); + int changed_button_mask = buttons_state ^ event_buttons_mask; + + buttons_state = event_buttons_mask; + + ev->set_button_index(_button_index_from_mask(changed_button_mask)); + ev->set_button_mask(event_buttons_mask); + Input::get_singleton()->accumulate_input_event(ev); + } break; + + case AMOTION_EVENT_ACTION_MOVE: { + Ref<InputEventMouseMotion> ev; + ev.instance(); + _set_key_modifier_state(ev); + ev->set_position(event_pos); + ev->set_global_position(event_pos); + ev->set_relative(event_pos - hover_prev_pos); + ev->set_button_mask(event_buttons_mask); + Input::get_singleton()->accumulate_input_event(ev); + hover_prev_pos = event_pos; + } break; + case AMOTION_EVENT_ACTION_SCROLL: { + Ref<InputEventMouseButton> ev; + ev.instance(); + ev->set_position(event_pos); + ev->set_global_position(event_pos); + ev->set_pressed(true); + buttons_state = event_buttons_mask; + if (event_vertical_factor > 0) { + _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_UP, event_vertical_factor); + } else if (event_vertical_factor < 0) { + _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_DOWN, -event_vertical_factor); + } + + if (event_horizontal_factor > 0) { + _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_RIGHT, event_horizontal_factor); + } else if (event_horizontal_factor < 0) { + _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_LEFT, -event_horizontal_factor); + } + } break; + } +} + +void DisplayServerAndroid::_wheel_button_click(int event_buttons_mask, const Ref<InputEventMouseButton> &ev, int wheel_button, float factor) { + Ref<InputEventMouseButton> evd = ev->duplicate(); + _set_key_modifier_state(evd); + evd->set_button_index(wheel_button); + evd->set_button_mask(event_buttons_mask ^ (1 << (wheel_button - 1))); + evd->set_factor(factor); + Input::get_singleton()->accumulate_input_event(evd); + Ref<InputEventMouseButton> evdd = evd->duplicate(); + evdd->set_pressed(false); + evdd->set_button_mask(event_buttons_mask); + Input::get_singleton()->accumulate_input_event(evdd); +} + +void DisplayServerAndroid::process_double_tap(int event_android_button_mask, Point2 p_pos) { + int event_button_mask = _android_button_mask_to_godot_button_mask(event_android_button_mask); Ref<InputEventMouseButton> ev; ev.instance(); + _set_key_modifier_state(ev); ev->set_position(p_pos); ev->set_global_position(p_pos); - ev->set_pressed(false); + ev->set_pressed(event_button_mask != 0); + ev->set_button_index(_button_index_from_mask(event_button_mask)); + ev->set_button_mask(event_button_mask); ev->set_doubleclick(true); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); +} + +int DisplayServerAndroid::_button_index_from_mask(int button_mask) { + switch (button_mask) { + case BUTTON_MASK_LEFT: + return BUTTON_LEFT; + case BUTTON_MASK_RIGHT: + return BUTTON_RIGHT; + case BUTTON_MASK_MIDDLE: + return BUTTON_MIDDLE; + case BUTTON_MASK_XBUTTON1: + return BUTTON_XBUTTON1; + case BUTTON_MASK_XBUTTON2: + return BUTTON_XBUTTON2; + default: + return 0; + } } void DisplayServerAndroid::process_scroll(Point2 p_pos) { Ref<InputEventPanGesture> ev; ev.instance(); + _set_key_modifier_state(ev); ev->set_position(p_pos); ev->set_delta(p_pos - scroll_prev_pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); scroll_prev_pos = p_pos; } void DisplayServerAndroid::process_accelerometer(const Vector3 &p_accelerometer) { - InputFilter::get_singleton()->set_accelerometer(p_accelerometer); + Input::get_singleton()->set_accelerometer(p_accelerometer); } void DisplayServerAndroid::process_gravity(const Vector3 &p_gravity) { - InputFilter::get_singleton()->set_gravity(p_gravity); + Input::get_singleton()->set_gravity(p_gravity); } void DisplayServerAndroid::process_magnetometer(const Vector3 &p_magnetometer) { - InputFilter::get_singleton()->set_magnetometer(p_magnetometer); + Input::get_singleton()->set_magnetometer(p_magnetometer); } void DisplayServerAndroid::process_gyroscope(const Vector3 &p_gyroscope) { - InputFilter::get_singleton()->set_gyroscope(p_gyroscope); + Input::get_singleton()->set_gyroscope(p_gyroscope); +} + +Point2i DisplayServerAndroid::mouse_get_position() const { + return hover_prev_pos; +} + +int DisplayServerAndroid::mouse_get_button_state() const { + return buttons_state; +} + +int DisplayServerAndroid::_android_button_mask_to_godot_button_mask(int android_button_mask) { + int godot_button_mask = 0; + if (android_button_mask & AMOTION_EVENT_BUTTON_PRIMARY) { + godot_button_mask |= BUTTON_MASK_LEFT; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { + godot_button_mask |= BUTTON_MASK_RIGHT; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_TERTIARY) { + godot_button_mask |= BUTTON_MASK_MIDDLE; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) { + godot_button_mask |= BUTTON_MASK_XBUTTON1; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { + godot_button_mask |= BUTTON_MASK_XBUTTON2; + } + + return godot_button_mask; } diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 2096ba68f1..aa5a2c1185 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -52,7 +52,6 @@ public: }; struct JoypadEvent { - int device; int type; int index; @@ -64,6 +63,13 @@ public: private: String rendering_driver; + bool alt_mem = false; + bool shift_mem = false; + bool control_mem = false; + bool meta_mem = false; + + int buttons_state; + bool keep_screen_on; Vector<TouchPos> touch; @@ -85,6 +91,14 @@ private: static void _dispatch_input_events(const Ref<InputEvent> &p_event); + void _set_key_modifier_state(Ref<InputEventWithModifiers> ev); + + static int _button_index_from_mask(int button_mask); + + static int _android_button_mask_to_godot_button_mask(int android_button_mask); + + void _wheel_button_click(int event_buttons_mask, const Ref<InputEventMouseButton> &ev, int wheel_button, float factor); + public: static DisplayServerAndroid *get_singleton(); @@ -107,7 +121,7 @@ public: virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const; virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; - virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_length = -1); + virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); virtual void virtual_keyboard_hide(); virtual int virtual_keyboard_get_height() const; @@ -156,9 +170,10 @@ public: void process_gravity(const Vector3 &p_gravity); void process_magnetometer(const Vector3 &p_magnetometer); void process_gyroscope(const Vector3 &p_gyroscope); - void process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points); + void process_touch(int p_event, int p_pointer, const Vector<TouchPos> &p_points); void process_hover(int p_type, Point2 p_pos); - void process_double_tap(Point2 p_pos); + void process_mouse_event(int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor = 0, float event_horizontal_factor = 0); + void process_double_tap(int event_android_button_mask, Point2 p_pos); void process_scroll(Point2 p_pos); void process_joy_event(JoypadEvent p_event); void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed); @@ -167,6 +182,11 @@ public: static Vector<String> get_rendering_drivers_func(); static void register_android_driver(); + void reset_window(); + + virtual Point2i mouse_get_position() const; + virtual int mouse_get_button_state() const; + DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); ~DisplayServerAndroid(); }; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 71febd3433..53b43ae0e8 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -30,20 +30,23 @@ #include "export.h" +#include "core/config/project_settings.h" #include "core/io/image_loader.h" #include "core/io/marshalls.h" #include "core/io/zip_io.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "core/version.h" #include "drivers/png/png_driver_common.h" #include "editor/editor_export.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "main/splash.gen.h" +#include "platform/android/export/gradle_export_util.h" #include "platform/android/logo.gen.h" +#include "platform/android/plugin/godot_plugin_config.h" #include "platform/android/run_icon.gen.h" #include <string.h> @@ -197,6 +200,9 @@ static const char *android_perms[] = { nullptr }; +static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable/splash.png"; +static const char *SPLASH_BG_COLOR_PATH = "res/drawable/splash_bg_color.png"; + struct LauncherIcon { const char *export_path; int dimensions; @@ -235,14 +241,12 @@ static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_coun }; class EditorExportPlatformAndroid : public EditorExportPlatform { - GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform); Ref<ImageTexture> logo; Ref<ImageTexture> run_icon; struct Device { - String id; String name; String description; @@ -250,26 +254,53 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { }; struct APKExportData { - zipFile apk; EditorProgress *ep; }; + Vector<PluginConfig> plugins; + String last_plugin_names; + uint64_t last_custom_build_time = 0; + volatile bool plugins_changed; + Mutex plugins_lock; Vector<Device> devices; volatile bool devices_changed; Mutex device_lock; - Thread *device_thread; + Thread *check_for_changes_thread; volatile bool quit_request; - static void _device_poll_thread(void *ud) { - + static void _check_for_changes_poll_thread(void *ud) { EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud; while (!ea->quit_request) { + // Check for plugins updates + { + // Nothing to do if we already know the plugins have changed. + if (!ea->plugins_changed) { + Vector<PluginConfig> loaded_plugins = get_plugins(); + + MutexLock lock(ea->plugins_lock); + + if (ea->plugins.size() != loaded_plugins.size()) { + ea->plugins_changed = true; + } else { + for (int i = 0; i < ea->plugins.size(); i++) { + if (ea->plugins[i].name != loaded_plugins[i].name) { + ea->plugins_changed = true; + break; + } + } + } + + if (ea->plugins_changed) { + ea->plugins = loaded_plugins; + } + } + } + // Check for devices updates String adb = EditorSettings::get_singleton()->get("export/android/adb"); if (FileAccess::exists(adb)) { - String devices; List<String> args; args.push_back("devices"); @@ -279,11 +310,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { Vector<String> ds = devices.split("\n"); Vector<String> ldevices; for (int i = 1; i < ds.size(); i++) { - String d = ds[i]; int dpos = d.find("device"); - if (dpos == -1) + if (dpos == -1) { continue; + } d = d.substr(0, dpos).strip_edges(); ldevices.push_back(d); } @@ -293,12 +324,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { bool different = false; if (ea->devices.size() != ldevices.size()) { - different = true; } else { - for (int i = 0; i < ea->devices.size(); i++) { - if (ea->devices[i].id != ldevices[i]) { different = true; break; @@ -307,11 +335,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } if (different) { - Vector<Device> ndevices; for (int i = 0; i < ldevices.size(); i++) { - Device d; d.id = ldevices[i]; for (int j = 0; j < ea->devices.size(); j++) { @@ -340,7 +366,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { d.description = "Device ID: " + d.id + "\n"; d.api_level = 0; for (int j = 0; j < props.size(); j++) { - // got information by `shell cat /system/build.prop` before and its format is "property=value" // it's now changed to use `shell getporp` because of permission issue with Android 8.0 and above // its format is "[property]: [value]" so changed it as like build.prop @@ -372,7 +397,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } d.name = vendor + " " + device; - if (device == String()) continue; + if (device == String()) { + continue; + } } ndevices.push_back(d); @@ -388,8 +415,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { uint64_t time = OS::get_singleton()->get_ticks_usec(); while (OS::get_singleton()->get_ticks_usec() - time < wait) { OS::get_singleton()->delay_usec(1000 * sleep); - if (ea->quit_request) + if (ea->quit_request) { break; + } } } @@ -406,7 +434,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } String get_project_name(const String &p_name) const { - String aname; if (p_name != "") { aname = p_name; @@ -422,7 +449,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } String get_package_name(const String &p_package) const { - String pname = p_package; String basename = ProjectSettings::get_singleton()->get("application/config/name"); basename = basename.to_lower(); @@ -430,7 +456,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String name; bool first = true; for (int i = 0; i < basename.length(); i++) { - CharType c = basename[i]; + char32_t c = basename[i]; if (c >= '0' && c <= '9' && first) { continue; } @@ -439,8 +465,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { first = false; } } - if (name == "") + if (name == "") { name = "noname"; + } pname = pname.replace("$genname", name); @@ -448,7 +475,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { - String pname = p_package; if (pname.length() == 0) { @@ -461,7 +487,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int segments = 0; bool first = true; for (int i = 0; i < pname.length(); i++) { - CharType c = pname[i]; + char32_t c = pname[i]; if (first && c == '.') { if (r_error) { *r_error = TTR("Package segments must be of non-zero length."); @@ -512,7 +538,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) { - /* * By not compressing files with little or not benefit in doing so, * a performance gain is expected attime. Moreover, if the APK is @@ -559,7 +584,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } static zip_fileinfo get_zip_fileinfo() { - OS::Time time = OS::get_singleton()->get_time(); OS::Date date = OS::get_singleton()->get_date(); @@ -567,7 +591,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.min; - zipfi.tmz_date.tm_mon = date.month; + zipfi.tmz_date.tm_mon = date.month - 1; // tm_mon is zero indexed zipfi.tmz_date.tm_sec = time.sec; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; @@ -586,6 +610,73 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { return abis; } + /// List the gdap files in the directory specified by the p_path parameter. + static Vector<String> list_gdap_files(const String &p_path) { + Vector<String> dir_files; + DirAccessRef da = DirAccess::open(p_path); + if (da) { + da->list_dir_begin(); + while (true) { + String file = da->get_next(); + if (file == "") { + break; + } + + if (da->current_is_dir() || da->current_is_hidden()) { + continue; + } + + if (file.ends_with(PLUGIN_CONFIG_EXT)) { + dir_files.push_back(file); + } + } + da->list_dir_end(); + } + + return dir_files; + } + + static Vector<PluginConfig> get_plugins() { + Vector<PluginConfig> loaded_plugins; + + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins"); + + // Add the prebuilt plugins + loaded_plugins.append_array(get_prebuilt_plugins(plugins_dir)); + + if (DirAccess::exists(plugins_dir)) { + Vector<String> plugins_filenames = list_gdap_files(plugins_dir); + + if (!plugins_filenames.empty()) { + Ref<ConfigFile> config_file = memnew(ConfigFile); + for (int i = 0; i < plugins_filenames.size(); i++) { + PluginConfig config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i])); + if (config.valid_config) { + loaded_plugins.push_back(config); + } else { + print_error("Invalid plugin config file " + plugins_filenames[i]); + } + } + } + } + + return loaded_plugins; + } + + static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) { + Vector<PluginConfig> enabled_plugins; + Vector<PluginConfig> all_plugins = get_plugins(); + for (int i = 0; i < all_plugins.size(); i++) { + PluginConfig plugin = all_plugins[i]; + bool enabled = p_presets->get("plugins/" + plugin.name); + if (enabled) { + enabled_plugins.push_back(plugin); + } + } + + return enabled_plugins; + } + static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED) { zip_fileinfo zipfi = get_zip_fileinfo(); zipOpenNewFileInZip(ed->apk, @@ -635,7 +726,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { return OK; } - static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { + static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { APKExportData *ed = (APKExportData *)p_userdata; String dst_path = p_path.replace_first("res://", "assets/"); @@ -643,12 +734,68 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { return OK; } - static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { + static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { return OK; } - void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) { + void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) { + const char **aperms = android_perms; + while (*aperms) { + bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower()); + if (enabled) { + r_permissions.push_back("android.permission." + String(*aperms)); + } + aperms++; + } + PackedStringArray user_perms = p_preset->get("permissions/custom_permissions"); + for (int i = 0; i < user_perms.size(); i++) { + String user_perm = user_perms[i].strip_edges(); + if (!user_perm.empty()) { + r_permissions.push_back(user_perm); + } + } + if (p_give_internet) { + if (r_permissions.find("android.permission.INTERNET") == -1) { + r_permissions.push_back("android.permission.INTERNET"); + } + } + + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + if (xr_mode_index == 1 /* XRMode.OVR */) { + int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required + if (hand_tracking_index > 0) { + if (r_permissions.find("com.oculus.permission.HAND_TRACKING") == -1) { + r_permissions.push_back("com.oculus.permission.HAND_TRACKING"); + } + } + } + } + + void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { + String manifest_text = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\">\n"; + + manifest_text += _get_screen_sizes_tag(p_preset); + manifest_text += _get_gles_tag(); + + Vector<String> perms; + _get_permissions(p_preset, p_give_internet, perms); + for (int i = 0; i < perms.size(); i++) { + manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i)); + } + + manifest_text += _get_xr_features_tag(p_preset); + manifest_text += _get_instrumentation_tag(p_preset); + String plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); + manifest_text += _get_application_tag(p_preset, plugins_names); + manifest_text += "</manifest>\n"; + String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); + store_string_at_path(manifest_path, manifest_text); + } + void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) { // Leaving the unused types commented because looking these constants up // again later would be annoying // const int CHUNK_AXML_FILE = 0x00080003; @@ -679,7 +826,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int version_code = p_preset->get("version/code"); String package_name = p_preset->get("package/unique_name"); - int orientation = p_preset->get("screen/orientation"); + const int screen_orientation = _get_android_orientation_value(_get_screen_orientation()); bool screen_support_small = p_preset->get("screen/support_small"); bool screen_support_normal = p_preset->get("screen/support_normal"); @@ -687,43 +834,20 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { bool screen_support_xlarge = p_preset->get("screen/support_xlarge"); int xr_mode_index = p_preset->get("xr_features/xr_mode"); + bool focus_awareness = p_preset->get("xr_features/focus_awareness"); - String plugins = p_preset->get("custom_template/plugins"); + String plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); Vector<String> perms; - - const char **aperms = android_perms; - while (*aperms) { - - bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower()); - if (enabled) - perms.push_back("android.permission." + String(*aperms)); - aperms++; - } - - PackedStringArray user_perms = p_preset->get("permissions/custom_permissions"); - - for (int i = 0; i < user_perms.size(); i++) { - String user_perm = user_perms[i].strip_edges(); - if (!user_perm.empty()) { - perms.push_back(user_perm); - } - } - - if (p_give_internet) { - if (perms.find("android.permission.INTERNET") == -1) - perms.push_back("android.permission.INTERNET"); - } + // Write permissions into the perms variable. + _get_permissions(p_preset, p_give_internet, perms); while (ofs < (uint32_t)p_manifest.size()) { - uint32_t chunk = decode_uint32(&p_manifest[ofs]); uint32_t size = decode_uint32(&p_manifest[ofs + 4]); switch (chunk) { - case CHUNK_STRINGS: { - int iofs = ofs + 8; string_count = decode_uint32(&p_manifest[iofs]); @@ -744,17 +868,15 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { string_table_begins = st_offset; for (uint32_t i = 0; i < string_count; i++) { - uint32_t string_at = decode_uint32(&p_manifest[st_offset + i * 4]); string_at += st_offset + string_count * 4; ERR_FAIL_COND_MSG(string_flags & UTF8_FLAG, "Unimplemented, can't read UTF-8 string table."); if (string_flags & UTF8_FLAG) { - } else { uint32_t len = decode_uint16(&p_manifest[string_at]); - Vector<CharType> ucstring; + Vector<char32_t> ucstring; ucstring.resize(len + 1); for (uint32_t j = 0; j < len; j++) { uint16_t c = decode_uint16(&p_manifest[string_at + 2 + 2 * j]); @@ -774,13 +896,13 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } break; case CHUNK_XML_START_TAG: { - int iofs = ofs + 8; uint32_t name = decode_uint32(&p_manifest[iofs + 12]); String tname = string_table[name]; uint32_t attrcount = decode_uint32(&p_manifest[iofs + 20]); iofs += 28; + bool is_focus_aware_metadata = false; for (uint32_t i = 0; i < attrcount; i++) { uint32_t attr_nspace = decode_uint32(&p_manifest[iofs]); @@ -804,8 +926,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { if (tname == "manifest" && attrname == "versionName") { if (attr_value == 0xFFFFFFFF) { WARN_PRINT("Version name in a resource, should be plain text"); - } else + } else { string_table.write[attr_value] = version_name; + } } if (tname == "instrumentation" && attrname == "targetPackage") { @@ -813,26 +936,20 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } if (tname == "activity" && attrname == "screenOrientation") { - - encode_uint32(orientation == 0 ? 0 : 1, &p_manifest.write[iofs + 16]); + encode_uint32(screen_orientation, &p_manifest.write[iofs + 16]); } if (tname == "supports-screens") { - if (attrname == "smallScreens") { - encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } else if (attrname == "normalScreens") { - encode_uint32(screen_support_normal ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } else if (attrname == "largeScreens") { - encode_uint32(screen_support_large ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } else if (attrname == "xlargeScreens") { - encode_uint32(screen_support_xlarge ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } } @@ -853,11 +970,17 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } - if (tname == "meta-data" && attrname == "value" && value == "custom_template_plugins_value") { + if (tname == "meta-data" && attrname == "value" && is_focus_aware_metadata) { + // Update the focus awareness meta-data value + encode_uint32(xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); + } + + if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.empty()) { // Update the meta-data 'android:value' attribute with the list of enabled plugins. - string_table.write[attr_value] = plugins; + string_table.write[attr_value] = plugins_names; } + is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware"; iofs += 20; } @@ -888,10 +1011,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { feature_names.push_back("oculus.software.handtracking"); feature_required_list.push_back(hand_tracking_index == 2); feature_versions.push_back(-1); // no version attribute should be added. - - if (perms.find("oculus.permission.handtracking") == -1) { - perms.push_back("oculus.permission.handtracking"); - } } } @@ -1037,7 +1156,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } if (tname == "manifest") { - // save manifest ending so we can restore it Vector<uint8_t> manifest_end; uint32_t manifest_cur_size = p_manifest.size(); @@ -1123,13 +1241,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { ret.resize(string_table_begins + string_table.size() * 4); for (uint32_t i = 0; i < string_table_begins; i++) { - ret.write[i] = p_manifest[i]; } ofs = 0; for (int i = 0; i < string_table.size(); i++) { - encode_uint32(ofs, &ret.write[string_table_begins + i * 4]); ofs += string_table[i].length() * 2 + 2 + 2; } @@ -1138,7 +1254,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { string_data_offset = ret.size() - ofs; uint8_t *chars = &ret.write[string_data_offset]; for (int i = 0; i < string_table.size(); i++) { - String s = string_table[i]; encode_uint16(s.length(), chars); chars += 2; @@ -1155,18 +1270,21 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } //pad - while (ret.size() % 4) + while (ret.size() % 4) { ret.push_back(0); + } uint32_t new_stable_end = ret.size(); uint32_t extra = (p_manifest.size() - string_table_ends); ret.resize(new_stable_end + extra); - for (uint32_t i = 0; i < extra; i++) + for (uint32_t i = 0; i < extra; i++) { ret.write[new_stable_end + i] = p_manifest[string_table_ends + i]; + } - while (ret.size() % 4) + while (ret.size() % 4) { ret.push_back(0); + } encode_uint32(ret.size(), &ret.write[4]); //update new file size encode_uint32(new_stable_end - 8, &ret.write[12]); //update new string table size @@ -1177,19 +1295,36 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } static String _parse_string(const uint8_t *p_bytes, bool p_utf8) { - uint32_t offset = 0; - uint32_t len = decode_uint16(&p_bytes[offset]); + uint32_t len = 0; if (p_utf8) { - //don't know how to read extended utf8, this will have to be for now - len >>= 8; + uint8_t byte = p_bytes[offset]; + if (byte & 0x80) { + offset += 2; + } else { + offset += 1; + } + byte = p_bytes[offset]; + offset++; + if (byte & 0x80) { + len = byte & 0x7F; + len = (len << 8) + p_bytes[offset]; + offset++; + } else { + len = byte; + } + } else { + len = decode_uint16(&p_bytes[offset]); + offset += 2; + if (len & 0x8000) { + len &= 0x7FFF; + len = (len << 16) + decode_uint16(&p_bytes[offset]); + offset += 2; + } } - offset += 2; - //printf("len %i, unicode: %i\n",len,int(p_utf8)); if (p_utf8) { - Vector<uint8_t> str8; str8.resize(len + 1); for (uint32_t i = 0; i < len; i++) { @@ -1200,24 +1335,24 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { str.parse_utf8((const char *)str8.ptr()); return str; } else { - String str; for (uint32_t i = 0; i < len; i++) { - CharType c = decode_uint16(&p_bytes[offset + i * 2]); - if (c == 0) + char32_t c = decode_uint16(&p_bytes[offset + i * 2]); + if (c == 0) { break; + } str += String::chr(c); } return str; } } - void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest) { + void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &r_manifest) { const int UTF8_FLAG = 0x00000100; - uint32_t string_block_len = decode_uint32(&p_manifest[16]); - uint32_t string_count = decode_uint32(&p_manifest[20]); - uint32_t string_flags = decode_uint32(&p_manifest[28]); + uint32_t string_block_len = decode_uint32(&r_manifest[16]); + uint32_t string_count = decode_uint32(&r_manifest[20]); + uint32_t string_flags = decode_uint32(&r_manifest[28]); const uint32_t string_table_begins = 40; Vector<String> string_table; @@ -1225,21 +1360,18 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String package_name = p_preset->get("package/name"); for (uint32_t i = 0; i < string_count; i++) { - - uint32_t offset = decode_uint32(&p_manifest[string_table_begins + i * 4]); + uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]); offset += string_table_begins + string_count * 4; - String str = _parse_string(&p_manifest[offset], string_flags & UTF8_FLAG); + String str = _parse_string(&r_manifest[offset], string_flags & UTF8_FLAG); if (str.begins_with("godot-project-name")) { - if (str == "godot-project-name") { //project name str = get_project_name(package_name); } else { - - String lang = str.substr(str.find_last("-") + 1, str.length()).replace("-", "_"); + String lang = str.substr(str.rfind("-") + 1, str.length()).replace("-", "_"); String prop = "application/config/name_" + lang; if (ProjectSettings::get_singleton()->has_setting(prop)) { str = ProjectSettings::get_singleton()->get(prop); @@ -1257,13 +1389,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { ret.resize(string_table_begins + string_table.size() * 4); for (uint32_t i = 0; i < string_table_begins; i++) { - - ret.write[i] = p_manifest[i]; + ret.write[i] = r_manifest[i]; } int ofs = 0; for (int i = 0; i < string_table.size(); i++) { - encode_uint32(ofs, &ret.write[string_table_begins + i * 4]); ofs += string_table[i].length() * 2 + 2 + 2; } @@ -1271,7 +1401,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { ret.resize(ret.size() + ofs); uint8_t *chars = &ret.write[ret.size() - ofs]; for (int i = 0; i < string_table.size(); i++) { - String s = string_table[i]; encode_uint16(s.length(), chars); chars += 2; @@ -1284,8 +1413,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } //pad - while (ret.size() % 4) + while (ret.size() % 4) { ret.push_back(0); + } //change flags to not use utf8 encode_uint32(string_flags & ~0x100, &ret.write[28]); @@ -1294,35 +1424,155 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { //append the rest... int rest_from = 12 + string_block_len; int rest_to = ret.size(); - int rest_len = (p_manifest.size() - rest_from); - ret.resize(ret.size() + (p_manifest.size() - rest_from)); + int rest_len = (r_manifest.size() - rest_from); + ret.resize(ret.size() + (r_manifest.size() - rest_from)); for (int i = 0; i < rest_len; i++) { - ret.write[rest_to + i] = p_manifest[rest_from + i]; + ret.write[rest_to + i] = r_manifest[rest_from + i]; } //finally update the size encode_uint32(ret.size(), &ret.write[4]); - p_manifest = ret; + r_manifest = ret; //printf("end\n"); } - void _process_launcher_icons(const String &p_processing_file_name, const Ref<Image> &p_source_image, const LauncherIcon p_icon, Vector<uint8_t> &p_data) { - if (p_processing_file_name == p_icon.export_path) { - Ref<Image> working_image = p_source_image; + void _load_image_data(const Ref<Image> &p_splash_image, Vector<uint8_t> &p_data) { + Vector<uint8_t> png_buffer; + Error err = PNGDriverCommon::image_to_png(p_splash_image, png_buffer); + if (err == OK) { + p_data.resize(png_buffer.size()); + memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size()); + } else { + String err_str = String("Failed to convert splash image to png."); + WARN_PRINT(err_str.utf8().get_data()); + } + } + + void _process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data) { + Ref<Image> working_image = p_source_image; + + if (p_source_image->get_width() != dimension || p_source_image->get_height() != dimension) { + working_image = p_source_image->duplicate(); + working_image->resize(dimension, dimension, Image::Interpolation::INTERPOLATE_LANCZOS); + } + + Vector<uint8_t> png_buffer; + Error err = PNGDriverCommon::image_to_png(working_image, png_buffer); + if (err == OK) { + p_data.resize(png_buffer.size()); + memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size()); + } else { + String err_str = String("Failed to convert resized icon (") + p_file_name + ") to png."; + WARN_PRINT(err_str.utf8().get_data()); + } + } + + void load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) { + // TODO: Figure out how to handle remaining boot splash parameters (e.g: fullsize, filter) + String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); - if (p_source_image->get_width() != p_icon.dimensions || p_source_image->get_height() != p_icon.dimensions) { - working_image = p_source_image->duplicate(); - working_image->resize(p_icon.dimensions, p_icon.dimensions, Image::Interpolation::INTERPOLATE_LANCZOS); + if (!project_splash_path.empty()) { + splash_image.instance(); + const Error err = ImageLoader::load_image(project_splash_path, splash_image); + if (err) { + splash_image.unref(); } + } - Vector<uint8_t> png_buffer; - Error err = PNGDriverCommon::image_to_png(working_image, png_buffer); - if (err == OK) { - p_data.resize(png_buffer.size()); - memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size()); - } else { - String err_str = String("Failed to convert resized icon (") + p_processing_file_name + ") to png."; - WARN_PRINT(err_str.utf8().get_data()); + if (splash_image.is_null()) { + // Use the default + splash_image = Ref<Image>(memnew(Image(boot_splash_png))); + } + + // Setup the splash bg color + bool bg_color_valid; + Color bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color", &bg_color_valid); + if (!bg_color_valid) { + bg_color = boot_splash_bg_color; + } + + splash_bg_color_image.instance(); + splash_bg_color_image->create(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format()); + splash_bg_color_image->fill(bg_color); + } + + void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) { + String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); + + icon.instance(); + foreground.instance(); + background.instance(); + + // Regular icon: user selection -> project icon -> default. + String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges(); + if (path.empty() || ImageLoader::load_image(path, icon) != OK) { + ImageLoader::load_image(project_icon_path, icon); + } + + // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). + path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges(); + if (path.empty() || ImageLoader::load_image(path, foreground) != OK) { + foreground = icon; + } + + // Adaptive background: user selection -> default. + path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges(); + if (!path.empty()) { + ImageLoader::load_image(path, background); + } + } + + void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) { + store_image(launcher_icon.export_path, data); + } + + void store_image(const String &export_path, const Vector<uint8_t> &data) { + String img_path = export_path.insert(0, "res://android/build/"); + store_file_at_path(img_path, data); + } + + void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, + const Ref<Image> &splash_image, + const Ref<Image> &splash_bg_color_image, + const Ref<Image> &main_image, + const Ref<Image> &foreground, + const Ref<Image> &background) { + // Store the splash image + if (splash_image.is_valid() && !splash_image->empty()) { + Vector<uint8_t> data; + _load_image_data(splash_image, data); + store_image(SPLASH_IMAGE_EXPORT_PATH, data); + } + + // Store the splash bg color image + if (splash_bg_color_image.is_valid() && !splash_bg_color_image->empty()) { + Vector<uint8_t> data; + _load_image_data(splash_bg_color_image, data); + store_image(SPLASH_BG_COLOR_PATH, data); + } + + // Prepare images to be resized for the icons. If some image ends up being uninitialized, + // the default image from the export template will be used. + + for (int i = 0; i < icon_densities_count; ++i) { + if (main_image.is_valid() && !main_image->empty()) { + Vector<uint8_t> data; + _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data); + store_image(launcher_icons[i], data); + } + + if (foreground.is_valid() && !foreground->empty()) { + Vector<uint8_t> data; + _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground, + launcher_adaptive_icon_foregrounds[i].dimensions, data); + store_image(launcher_adaptive_icon_foregrounds[i], data); + } + + if (background.is_valid() && !background->empty()) { + Vector<uint8_t> data; + _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background, + launcher_adaptive_icon_backgrounds[i].dimensions, data); + store_image(launcher_adaptive_icon_backgrounds[i], data); } } } @@ -1340,11 +1590,10 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } public: - typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total); + typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); public: - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { - + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override { String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); if (driver == "GLES2") { r_features->push_back("etc"); @@ -1360,17 +1609,25 @@ public: } } - virtual void get_export_options(List<ExportOption> *r_options) { - + virtual void get_export_options(List<ExportOption> *r_options) override { r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "xr_features/focus_awareness"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); 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::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), 0)); + + Vector<PluginConfig> plugins_configs = get_plugins(); + for (int i = 0; i < plugins_configs.size(); i++) { + print_verbose("Found Android plugin " + plugins_configs[i].name); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + plugins_configs[i].name), false)); + } + plugins_changed = false; + 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")); @@ -1378,7 +1635,6 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "screen/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_normal"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true)); @@ -1387,10 +1643,10 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_background_option, PROPERTY_HINT_FILE, "*.png"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false)); @@ -1408,26 +1664,33 @@ public: const char **perms = android_perms; while (*perms) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "permissions/" + String(*perms).to_lower()), false)); perms++; } } - virtual String get_name() const { + virtual String get_name() const override { return "Android"; } - virtual String get_os_name() const { + virtual String get_os_name() const override { return "Android"; } - virtual Ref<Texture2D> get_logo() const { + virtual Ref<Texture2D> get_logo() const override { return logo; } - virtual bool poll_export() { + virtual bool should_update_export_options() override { + bool export_options_changed = plugins_changed; + if (export_options_changed) { + // don't clear unless we're reporting true, to avoid race + plugins_changed = false; + } + return export_options_changed; + } + virtual bool poll_export() override { bool dc = devices_changed; if (dc) { // don't clear unless we're reporting true, to avoid race @@ -1436,26 +1699,22 @@ public: return dc; } - virtual int get_options_count() const { - + virtual int get_options_count() const override { MutexLock lock(device_lock); return devices.size(); } - virtual String get_options_tooltip() const { - + virtual String get_options_tooltip() const override { return TTR("Select device from the list"); } - virtual String get_option_label(int p_index) const { - + virtual String get_option_label(int p_index) const override { ERR_FAIL_INDEX_V(p_index, devices.size(), ""); MutexLock lock(device_lock); return devices[p_index].name; } - virtual String get_option_tooltip(int p_index) const { - + virtual String get_option_tooltip(int p_index) const override { ERR_FAIL_INDEX_V(p_index, devices.size(), ""); MutexLock lock(device_lock); String s = devices[p_index].description; @@ -1468,8 +1727,7 @@ public: return s; } - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { - + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override { ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); String can_export_error; @@ -1493,8 +1751,9 @@ public: const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); const bool use_reverse = devices[p_device].api_level >= 21; - if (use_reverse) + if (use_reverse) { p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; + } String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk"); @@ -1553,7 +1812,6 @@ public: if (use_remote) { if (use_reverse) { - static const char *const msg = "--- Device API >= 21; debugging over USB ---"; EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR); print_line(String(msg).to_upper()); @@ -1566,7 +1824,6 @@ public: OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv); if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { - int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); args.clear(); args.push_back("-s"); @@ -1580,7 +1837,6 @@ public: } if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { - int fs_port = EditorSettings::get_singleton()->get("filesystem/file_server/port"); args.clear(); @@ -1594,7 +1850,6 @@ public: print_line("Reverse result2: " + itos(rv)); } } else { - static const char *const msg = "--- Device API < 21; debugging over Wi-Fi ---"; EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR); print_line(String(msg).to_upper()); @@ -1629,35 +1884,43 @@ public: #undef CLEANUP_AND_RETURN } - virtual Ref<Texture2D> get_run_icon() const { + virtual Ref<Texture2D> get_run_icon() const override { return run_icon; } - virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override { String err; bool valid = false; // Look for export templates (first official, and if defined custom templates). if (!bool(p_preset->get("custom_template/use_custom_build"))) { - bool dvalid = exists_export_template("android_debug.apk", &err); - bool rvalid = exists_export_template("android_release.apk", &err); + String template_err; + bool dvalid = false; + bool rvalid = false; if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); if (!dvalid) { - err += TTR("Custom debug template not found.") + "\n"; + template_err += TTR("Custom debug template not found.") + "\n"; } + } else { + dvalid = exists_export_template("android_debug.apk", &template_err); } + if (p_preset->get("custom_template/release") != "") { rvalid = FileAccess::exists(p_preset->get("custom_template/release")); if (!rvalid) { - err += TTR("Custom release template not found.") + "\n"; + template_err += TTR("Custom release template not found.") + "\n"; } + } else { + rvalid = exists_export_template("android_release.apk", &template_err); } valid = dvalid || rvalid; + if (!valid) { + err += template_err; + } } else { valid = exists_export_template("android_source.zip", &err); } @@ -1668,7 +1931,6 @@ public: String adb = EditorSettings::get_singleton()->get("export/android/adb"); if (!FileAccess::exists(adb)) { - valid = false; err += TTR("ADB executable not configured in the Editor Settings.") + "\n"; } @@ -1676,7 +1938,6 @@ public: String js = EditorSettings::get_singleton()->get("export/android/jarsigner"); if (!FileAccess::exists(js)) { - valid = false; err += TTR("OpenJDK jarsigner not configured in the Editor Settings.") + "\n"; } @@ -1684,7 +1945,6 @@ public: String dk = p_preset->get("keystore/debug"); if (!FileAccess::exists(dk)) { - dk = EditorSettings::get_singleton()->get("export/android/debug_keystore"); if (!FileAccess::exists(dk)) { valid = false; @@ -1692,6 +1952,13 @@ public: } } + String rk = p_preset->get("keystore/release"); + + if (!rk.empty() && !FileAccess::exists(rk)) { + valid = false; + err += TTR("Release keystore incorrectly configured in the export preset.") + "\n"; + } + if (bool(p_preset->get("custom_template/use_custom_build"))) { String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path"); if (sdk_path == "") { @@ -1699,15 +1966,26 @@ public: valid = false; } else { Error errn; + // Check for the platform-tools directory. DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn); if (errn != OK) { - err += TTR("Invalid Android SDK path for custom build in Editor Settings.") + "\n"; + err += TTR("Invalid Android SDK path for custom build in Editor Settings."); + err += TTR("Missing 'platform-tools' directory!"); + err += "\n"; + valid = false; + } + + // Check for the build-tools directory. + DirAccessRef build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn); + if (errn != OK) { + err += TTR("Invalid Android SDK path for custom build in Editor Settings."); + err += TTR("Missing 'build-tools' directory!"); + err += "\n"; valid = false; } } if (!FileAccess::exists("res://android/build/build.gradle")) { - err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n"; valid = false; } @@ -1716,7 +1994,6 @@ public: bool apk_expansion = p_preset->get("apk_expansion/enable"); if (apk_expansion) { - String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); if (apk_expansion_pkey == "") { @@ -1730,7 +2007,6 @@ public: String pn_err; if (!is_package_name_valid(get_package_name(pn), &pn_err)) { - valid = false; err += TTR("Invalid package name:") + " " + pn_err + "\n"; } @@ -1741,47 +2017,406 @@ public: err += etc_error; } + // Ensure that `Use Custom Build` is enabled if a plugin is selected. + String enabled_plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); + bool custom_build_enabled = p_preset->get("custom_template/use_custom_build"); + if (!enabled_plugins_names.empty() && !custom_build_enabled) { + valid = false; + err += TTR("\"Use Custom Build\" must be enabled to use the plugins."); + err += "\n"; + } + + // Validate the Xr features are properly populated + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + int degrees_of_freedom = p_preset->get("xr_features/degrees_of_freedom"); + int hand_tracking = p_preset->get("xr_features/hand_tracking"); + bool focus_awareness = p_preset->get("xr_features/focus_awareness"); + if (xr_mode_index != /* XRMode.OVR*/ 1) { + if (degrees_of_freedom > 0) { + valid = false; + err += TTR("\"Degrees Of Freedom\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); + err += "\n"; + } + + if (hand_tracking > 0) { + valid = false; + err += TTR("\"Hand Tracking\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); + err += "\n"; + } + + if (focus_awareness) { + valid = false; + err += TTR("\"Focus Awareness\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); + err += "\n"; + } + } + + if (int(p_preset->get("custom_template/export_format")) == 1 && /*AAB*/ + !bool(p_preset->get("custom_template/use_custom_build"))) { + valid = false; + err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled."); + err += "\n"; + } + r_error = err; return valid; } - virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { List<String> list; list.push_back("apk"); + list.push_back("aab"); return list; } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { + inline bool is_clean_build_required(Vector<PluginConfig> enabled_plugins) { + String plugin_names = get_plugins_names(enabled_plugins); + bool first_build = last_custom_build_time == 0; + bool have_plugins_changed = false; + + if (!first_build) { + have_plugins_changed = plugin_names != last_plugin_names; + if (!have_plugins_changed) { + for (int i = 0; i < enabled_plugins.size(); i++) { + if (enabled_plugins.get(i).last_updated > last_custom_build_time) { + have_plugins_changed = true; + break; + } + } + } + } + + last_custom_build_time = OS::get_singleton()->get_unix_time(); + last_plugin_names = plugin_names; + + return have_plugins_changed || first_build; + } + + String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) { + int version_code = p_preset->get("version/code"); + String package_name = p_preset->get("package/unique_name"); + String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb"; + String fullpath = p_path.get_base_dir().plus_file(apk_file_name); + return fullpath; + } + + Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) { + String fullpath = get_apk_expansion_fullpath(p_preset, p_path); + Error err = save_pack(p_preset, fullpath); + return err; + } + + void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) { + String cmdline = p_preset->get("command_line/extra_args"); + Vector<String> command_line_strings = cmdline.strip_edges().split(" "); + for (int i = 0; i < command_line_strings.size(); i++) { + if (command_line_strings[i].strip_edges().length() == 0) { + command_line_strings.remove(i); + i--; + } + } + + gen_export_flags(command_line_strings, p_flags); + + bool apk_expansion = p_preset->get("apk_expansion/enable"); + if (apk_expansion) { + String fullpath = get_apk_expansion_fullpath(p_preset, p_path); + String apk_expansion_public_key = p_preset->get("apk_expansion/public_key"); + + command_line_strings.push_back("--use_apk_expansion"); + command_line_strings.push_back("--apk_expansion_md5"); + command_line_strings.push_back(FileAccess::get_md5(fullpath)); + command_line_strings.push_back("--apk_expansion_key"); + command_line_strings.push_back(apk_expansion_public_key.strip_edges()); + } + + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + if (xr_mode_index == 1) { + command_line_strings.push_back("--xr_mode_ovr"); + } else { // XRMode.REGULAR is the default. + command_line_strings.push_back("--xr_mode_regular"); + } + + bool use_32_bit_framebuffer = p_preset->get("graphics/32_bits_framebuffer"); + if (use_32_bit_framebuffer) { + command_line_strings.push_back("--use_depth_32"); + } + + bool immersive = p_preset->get("screen/immersive_mode"); + if (immersive) { + command_line_strings.push_back("--use_immersive"); + } + + bool debug_opengl = p_preset->get("screen/opengl_debug"); + if (debug_opengl) { + command_line_strings.push_back("--debug_opengl"); + } + + if (command_line_strings.size()) { + r_command_line_flags.resize(4); + encode_uint32(command_line_strings.size(), &r_command_line_flags.write[0]); + for (int i = 0; i < command_line_strings.size(); i++) { + print_line(itos(i) + " param: " + command_line_strings[i]); + CharString command_line_argument = command_line_strings[i].utf8(); + int base = r_command_line_flags.size(); + int length = command_line_argument.length(); + if (length == 0) + continue; + r_command_line_flags.resize(base + 4 + length); + encode_uint32(length, &r_command_line_flags.write[base]); + copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length); + } + } + } + + Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String export_path, EditorProgress ep) { + int export_format = int(p_preset->get("custom_template/export_format")); + String export_label = export_format == 1 ? "AAB" : "APK"; + String release_keystore = p_preset->get("keystore/release"); + String release_username = p_preset->get("keystore/release_user"); + String release_password = p_preset->get("keystore/release_password"); + + String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner"); + if (!FileAccess::exists(jarsigner)) { + EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting " + export_label + " is unsigned."); + return OK; + } + + String keystore; + String password; + String user; + if (p_debug) { + keystore = p_preset->get("keystore/debug"); + password = p_preset->get("keystore/debug_password"); + user = p_preset->get("keystore/debug_user"); + + if (keystore.empty()) { + keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); + password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); + user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); + } + + if (ep.step("Signing debug " + export_label + "...", 103)) { + return ERR_SKIP; + } + + } else { + keystore = release_keystore; + password = release_password; + user = release_username; + + if (ep.step("Signing release " + export_label + "...", 103)) { + return ERR_SKIP; + } + } + + if (!FileAccess::exists(keystore)) { + EditorNode::add_io_error("Could not find keystore, unable to export."); + return ERR_FILE_CANT_OPEN; + } + + List<String> args; + args.push_back("-digestalg"); + args.push_back("SHA-256"); + args.push_back("-sigalg"); + args.push_back("SHA256withRSA"); + String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url"); + if (tsa_url != "") { + args.push_back("-tsa"); + args.push_back(tsa_url); + } + args.push_back("-verbose"); + args.push_back("-keystore"); + args.push_back(keystore); + args.push_back("-storepass"); + args.push_back(password); + args.push_back(export_path); + args.push_back(user); + int retval; + OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); + if (retval) { + EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval)); + return ERR_CANT_CREATE; + } + + if (ep.step("Verifying " + export_label + "...", 104)) { + return ERR_SKIP; + } + args.clear(); + args.push_back("-verify"); + args.push_back("-keystore"); + args.push_back(keystore); + args.push_back(export_path); + args.push_back("-verbose"); + + OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); + if (retval) { + EditorNode::add_io_error("'jarsigner' verification of " + export_label + " failed. Make sure to use a jarsigner from OpenJDK 8."); + return ERR_CANT_CREATE; + } + return OK; + } + + void _clear_assets_directory() { + DirAccessRef da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da_res->dir_exists("res://android/build/assets")) { + DirAccessRef da_assets = DirAccess::open("res://android/build/assets"); + da_assets->erase_contents_recursive(); + da_res->remove("res://android/build/assets"); + } + } + + Error _zip_align_project(const String &sdk_path, const String &unaligned_file_path, const String &aligned_file_path) { + // Look for the zipalign tool. + String zipalign_command; + Error errn; + String build_tools_dir = sdk_path.plus_file("build-tools"); + DirAccessRef da = DirAccess::open(build_tools_dir, &errn); + if (errn != OK) { + return errn; + } + + // There are additional versions directories we need to go through. + da->list_dir_begin(); + String sub_dir = da->get_next(); + while (!sub_dir.empty()) { + if (!sub_dir.begins_with(".") && da->current_is_dir()) { + // Check if the tool is here. + String tool_path = build_tools_dir.plus_file(sub_dir).plus_file("zipalign"); + if (FileAccess::exists(tool_path)) { + zipalign_command = tool_path; + break; + } + } + sub_dir = da->get_next(); + } + da->list_dir_end(); + + if (zipalign_command.empty()) { + EditorNode::get_singleton()->show_warning(TTR("Unable to find the zipalign tool.")); + return ERR_CANT_CREATE; + } + + List<String> zipalign_args; + zipalign_args.push_back("-f"); + zipalign_args.push_back("-v"); + zipalign_args.push_back("4"); + zipalign_args.push_back(unaligned_file_path); // source file + zipalign_args.push_back(aligned_file_path); // destination file + + int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Aligning APK..."), zipalign_command, zipalign_args); + if (result != 0) { + EditorNode::get_singleton()->show_warning(TTR("Unable to complete APK alignment.")); + return ERR_CANT_CREATE; + } + + // Delete the unaligned path. + errn = da->remove(unaligned_file_path); + if (errn != OK) { + EditorNode::get_singleton()->show_warning(TTR("Unable to delete unaligned APK.")); + } + return OK; + } + + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); String src_apk; + Error err; EditorProgress ep("export", "Exporting for Android", 105, true); - if (bool(p_preset->get("custom_template/use_custom_build"))) { //custom build - //re-generate build.gradle and AndroidManifest.xml + bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build")); + int export_format = int(p_preset->get("custom_template/export_format")); + bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); + bool _signed = p_preset->get("package/signed"); + bool apk_expansion = p_preset->get("apk_expansion/enable"); + Vector<String> enabled_abis = get_enabled_abis(p_preset); + + Ref<Image> splash_image; + Ref<Image> splash_bg_color_image; + load_splash_refs(splash_image, splash_bg_color_image); + + Ref<Image> main_image; + Ref<Image> foreground; + Ref<Image> background; - { //test that installed build version is alright + load_icon_refs(p_preset, main_image, foreground, background); + + Vector<uint8_t> command_line_flags; + // Write command line flags into the command_line_flags variable. + get_command_line_flags(p_preset, p_path, p_flags, command_line_flags); + + if (export_format == 1) { + if (!p_path.ends_with(".aab")) { + EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension.")); + return ERR_UNCONFIGURED; + } + if (apk_expansion) { + EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle.")); + return ERR_UNCONFIGURED; + } + } + if (export_format == 0 && !p_path.ends_with(".apk")) { + EditorNode::get_singleton()->show_warning( + TTR("Invalid filename! Android APK requires the *.apk extension.")); + return ERR_UNCONFIGURED; + } + if (export_format > 1 || export_format < 0) { + EditorNode::add_io_error("Unsupported export format!\n"); + return ERR_UNCONFIGURED; //TODO: is this the right error? + } + + if (use_custom_build) { + //test that installed build version is alright + { FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ); if (!f) { EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); return ERR_UNCONFIGURED; } String version = f->get_line().strip_edges(); + f->close(); if (version != VERSION_FULL_CONFIG) { EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); return ERR_UNCONFIGURED; } } - //build project if custom build is enabled String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path"); - 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'."); - OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required + // TODO: should we use "package/name" or "application/config/name"? + String project_name = get_project_name(p_preset->get("package/name")); + err = _create_project_name_strings_files(p_preset, project_name); //project name localization. + if (err != OK) { + EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name"); + } + // Copies the project icon files into the appropriate Gradle project directory. + _copy_icons_to_gradle_project(p_preset, splash_image, splash_bg_color_image, main_image, foreground, background); + // Write an AndroidManifest.xml file into the Gradle project directory. + _write_tmp_manifest(p_preset, p_give_internet, p_debug); + + //stores all the project files inside the Gradle project directory. Also includes all ABIs + _clear_assets_directory(); + if (!apk_expansion) { + err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file); + if (err != OK) { + EditorNode::add_io_error("Could not export project files to gradle project\n"); + return err; + } + } else { + err = save_apk_expansion_file(p_preset, p_path); + if (err != OK) { + EditorNode::add_io_error("Could not write expansion package file!"); + return err; + } + } + store_file_at_path("res://android/build/assets/_cl_", command_line_flags); + OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required String build_command; + #ifdef WINDOWS_ENABLED build_command = "gradlew.bat"; #else @@ -1789,18 +2424,40 @@ 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"); + String version_code = itos(p_preset->get("version/code")); + String version_name = p_preset->get("version/name"); + String enabled_abi_string = String("|").join(enabled_abis); + + Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset); + String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins); + String remote_plugins_binaries = get_plugins_binaries(BINARY_TYPE_REMOTE, enabled_plugins); + String custom_maven_repos = get_plugins_custom_maven_repos(enabled_plugins); + bool clean_build_required = is_clean_build_required(enabled_plugins); List<String> cmdline; - cmdline.push_back("build"); + if (clean_build_required) { + cmdline.push_back("clean"); + } + + String build_type = p_debug ? "Debug" : "Release"; + if (export_format == 1) { + String bundle_build_command = vformat("bundle%s", build_type); + cmdline.push_back(bundle_build_command); + } else if (export_format == 0) { + String apk_build_command = vformat("assemble%s", build_type); + cmdline.push_back(apk_build_command); + } + 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("-Pexport_version_code=" + version_code); // argument to specify the version code. + cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name. + cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs. + cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies. + cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies. + cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies. cmdline.push_back("-p"); // argument to specify the start directory. cmdline.push_back(build_path); // start directory. /*{ used for debug @@ -1815,36 +2472,73 @@ public: EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation.")); return ERR_CANT_CREATE; } - if (p_debug) { - src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk"); - } else { - src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk"); - } - if (!FileAccess::exists(src_apk)) { - EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk); - return ERR_CANT_CREATE; + List<String> copy_args; + String copy_command; + if (export_format == 1) { + copy_command = vformat("copyAndRename%sAab", build_type); + } else if (export_format == 0) { + copy_command = vformat("copyAndRename%sApk", build_type); } - } else { + copy_args.push_back(copy_command); - if (p_debug) - src_apk = p_preset->get("custom_template/debug"); - else - src_apk = p_preset->get("custom_template/release"); + copy_args.push_back("-p"); // argument to specify the start directory. + copy_args.push_back(build_path); // start directory. - src_apk = src_apk.strip_edges(); - if (src_apk == "") { - if (p_debug) { - src_apk = find_export_template("android_debug.apk"); - } else { - src_apk = find_export_template("android_release.apk"); + String export_filename = p_path.get_file(); + if (export_format == 0) { + // By default, generated apk are not aligned. + export_filename += ".unaligned"; + } + String export_path = p_path.get_base_dir(); + if (export_path.is_rel_path()) { + export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path); + } + export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path(); + String export_file_path = export_path.plus_file(export_filename); + + copy_args.push_back("-Pexport_path=file:" + export_path); + copy_args.push_back("-Pexport_filename=" + export_filename); + + int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args); + if (copy_result != 0) { + EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs.")); + return ERR_CANT_CREATE; + } + if (_signed) { + err = sign_apk(p_preset, p_debug, export_file_path, ep); + if (err != OK) { + return err; } - if (src_apk == "") { - EditorNode::add_io_error("Package not found: " + src_apk); - return ERR_FILE_NOT_FOUND; + } + + if (export_format == 0) { + // Perform zip alignment + err = _zip_align_project(sdk_path, export_file_path, export_path.plus_file(p_path.get_file())); + if (err != OK) { + return err; } } + + return OK; + } + // This is the start of the Legacy build system + if (p_debug) + src_apk = p_preset->get("custom_template/debug"); + else + src_apk = p_preset->get("custom_template/release"); + src_apk = src_apk.strip_edges(); + if (src_apk == "") { + if (p_debug) { + src_apk = find_export_template("android_debug.apk"); + } else { + src_apk = find_export_template("android_release.apk"); + } + if (src_apk == "") { + EditorNode::add_io_error("Package not found: " + src_apk); + return ERR_FILE_NOT_FOUND; + } } if (!DirAccess::exists(p_path.get_base_dir())) { @@ -1860,7 +2554,6 @@ public: unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io); if (!pkg) { - EditorNode::add_io_error("Could not find template APK to export:\n" + src_apk); return ERR_FILE_NOT_FOUND; } @@ -1881,60 +2574,15 @@ public: zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2); - bool use_32_fb = p_preset->get("graphics/32_bits_framebuffer"); - bool immersive = p_preset->get("screen/immersive_mode"); - bool debug_opengl = p_preset->get("screen/opengl_debug"); - - bool _signed = p_preset->get("package/signed"); - - bool apk_expansion = p_preset->get("apk_expansion/enable"); - String cmdline = p_preset->get("command_line/extra_args"); - int version_code = p_preset->get("version/code"); String version_name = p_preset->get("version/name"); String package_name = p_preset->get("package/unique_name"); String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); - String release_keystore = p_preset->get("keystore/release"); - String release_username = p_preset->get("keystore/release_user"); - String release_password = p_preset->get("keystore/release_password"); - - Vector<String> enabled_abis = get_enabled_abis(p_preset); - - String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); - - // Prepare images to be resized for the icons. If some image ends up being uninitialized, the default image from the export template will be used. - Ref<Image> launcher_icon_image; - Ref<Image> launcher_adaptive_icon_foreground_image; - Ref<Image> launcher_adaptive_icon_background_image; - - launcher_icon_image.instance(); - launcher_adaptive_icon_foreground_image.instance(); - launcher_adaptive_icon_background_image.instance(); - - // Regular icon: user selection -> project icon -> default. - String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges(); - if (path.empty() || ImageLoader::load_image(path, launcher_icon_image) != OK) { - ImageLoader::load_image(project_icon_path, launcher_icon_image); - } - - // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). - path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges(); - if (path.empty() || ImageLoader::load_image(path, launcher_adaptive_icon_foreground_image) != OK) { - launcher_adaptive_icon_foreground_image = launcher_icon_image; - } - - // Adaptive background: user selection -> default. - path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges(); - if (!path.empty()) { - ImageLoader::load_image(path, launcher_adaptive_icon_background_image); - } - Vector<String> invalid_abis(enabled_abis); while (ret == UNZ_OK) { - //get filename unz_file_info info; char fname[16384]; @@ -1953,24 +2601,38 @@ public: unzCloseCurrentFile(pkg); //write - if (file == "AndroidManifest.xml") { - _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG)); + _fix_manifest(p_preset, data, p_give_internet); } - if (file == "resources.arsc") { _fix_resources(p_preset, data); } + // Process the splash image + if (file == SPLASH_IMAGE_EXPORT_PATH && splash_image.is_valid() && !splash_image->empty()) { + _load_image_data(splash_image, data); + } + + // Process the splash bg color image + if (file == SPLASH_BG_COLOR_PATH && splash_bg_color_image.is_valid() && !splash_bg_color_image->empty()) { + _load_image_data(splash_bg_color_image, data); + } + for (int i = 0; i < icon_densities_count; ++i) { - if (launcher_icon_image.is_valid() && !launcher_icon_image->empty()) { - _process_launcher_icons(file, launcher_icon_image, launcher_icons[i], data); + if (main_image.is_valid() && !main_image->empty()) { + if (file == launcher_icons[i].export_path) { + _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data); + } } - if (launcher_adaptive_icon_foreground_image.is_valid() && !launcher_adaptive_icon_foreground_image->empty()) { - _process_launcher_icons(file, launcher_adaptive_icon_foreground_image, launcher_adaptive_icon_foregrounds[i], data); + if (foreground.is_valid() && !foreground->empty()) { + if (file == launcher_adaptive_icon_foregrounds[i].export_path) { + _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); + } } - if (launcher_adaptive_icon_background_image.is_valid() && !launcher_adaptive_icon_background_image->empty()) { - _process_launcher_icons(file, launcher_adaptive_icon_background_image, launcher_adaptive_icon_backgrounds[i], data); + if (background.is_valid() && !background->empty()) { + if (file == launcher_adaptive_icon_backgrounds[i].export_path) { + _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); + } } } @@ -2028,107 +2690,47 @@ public: if (ep.step("Adding files...", 1)) { CLEANUP_AND_RETURN(ERR_SKIP); } - Error err = OK; - Vector<String> cl = cmdline.strip_edges().split(" "); - for (int i = 0; i < cl.size(); i++) { - if (cl[i].strip_edges().length() == 0) { - cl.remove(i); - i--; - } - } - - gen_export_flags(cl, p_flags); + err = OK; if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so); } else { - //all files - if (apk_expansion) { - - String apkfname = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb"; - String fullpath = p_path.get_base_dir().plus_file(apkfname); - err = save_pack(p_preset, fullpath); - + err = save_apk_expansion_file(p_preset, p_path); if (err != OK) { - unzClose(pkg); - EditorNode::add_io_error("Could not write expansion package file: " + apkfname); - - CLEANUP_AND_RETURN(ERR_SKIP); + EditorNode::add_io_error("Could not write expansion package file!"); + return err; } - - cl.push_back("--use_apk_expansion"); - cl.push_back("--apk_expansion_md5"); - cl.push_back(FileAccess::get_md5(fullpath)); - cl.push_back("--apk_expansion_key"); - cl.push_back(apk_expansion_pkey.strip_edges()); - } else { - APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so); } } - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - if (xr_mode_index == 1 /* XRMode.OVR */) { - cl.push_back("--xr_mode_ovr"); - } else { - // XRMode.REGULAR is the default. - cl.push_back("--xr_mode_regular"); - } - - if (use_32_fb) - cl.push_back("--use_depth_32"); - - if (immersive) - cl.push_back("--use_immersive"); - - if (debug_opengl) - cl.push_back("--debug_opengl"); - - if (cl.size()) { - //add comandline - Vector<uint8_t> clf; - clf.resize(4); - encode_uint32(cl.size(), &clf.write[0]); - for (int i = 0; i < cl.size(); i++) { - - print_line(itos(i) + " param: " + cl[i]); - CharString txt = cl[i].utf8(); - int base = clf.size(); - int length = txt.length(); - if (!length) - continue; - clf.resize(base + 4 + length); - encode_uint32(length, &clf.write[base]); - copymem(&clf.write[base + 4], txt.ptr(), length); - } - - zip_fileinfo zipfi = get_zip_fileinfo(); - - zipOpenNewFileInZip(unaligned_apk, - "assets/_cl_", - &zipfi, - nullptr, - 0, - nullptr, - 0, - nullptr, - 0, // No compress (little size gain and potentially slower startup) - Z_DEFAULT_COMPRESSION); - - zipWriteInFileInZip(unaligned_apk, clf.ptr(), clf.size()); - zipCloseFileInZip(unaligned_apk); + if (err != OK) { + unzClose(pkg); + EditorNode::add_io_error("Could not export project files"); + CLEANUP_AND_RETURN(ERR_SKIP); } + zip_fileinfo zipfi = get_zip_fileinfo(); + zipOpenNewFileInZip(unaligned_apk, + "assets/_cl_", + &zipfi, + NULL, + 0, + NULL, + 0, + NULL, + 0, // No compress (little size gain and potentially slower startup) + Z_DEFAULT_COMPRESSION); + zipWriteInFileInZip(unaligned_apk, command_line_flags.ptr(), command_line_flags.size()); + zipCloseFileInZip(unaligned_apk); zipClose(unaligned_apk, nullptr); unzClose(pkg); @@ -2137,87 +2739,9 @@ public: } if (_signed) { - - String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner"); - if (!FileAccess::exists(jarsigner)) { - EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned."); - CLEANUP_AND_RETURN(OK); - } - - String keystore; - String password; - String user; - if (p_debug) { - - keystore = p_preset->get("keystore/debug"); - password = p_preset->get("keystore/debug_password"); - user = p_preset->get("keystore/debug_user"); - - if (keystore.empty()) { - - keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); - password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); - user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); - } - - if (ep.step("Signing debug APK...", 103)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } - - } else { - keystore = release_keystore; - password = release_password; - user = release_username; - - if (ep.step("Signing release APK...", 103)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } - } - - if (!FileAccess::exists(keystore)) { - EditorNode::add_io_error("Could not find keystore, unable to export."); - CLEANUP_AND_RETURN(ERR_FILE_CANT_OPEN); - } - - List<String> args; - args.push_back("-digestalg"); - args.push_back("SHA-256"); - args.push_back("-sigalg"); - args.push_back("SHA256withRSA"); - String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url"); - if (tsa_url != "") { - args.push_back("-tsa"); - args.push_back(tsa_url); - } - args.push_back("-verbose"); - args.push_back("-keystore"); - args.push_back(keystore); - args.push_back("-storepass"); - args.push_back(password); - args.push_back(tmp_unaligned_path); - args.push_back(user); - int retval; - OS::get_singleton()->execute(jarsigner, args, true, nullptr, nullptr, &retval); - if (retval) { - EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval)); - CLEANUP_AND_RETURN(ERR_CANT_CREATE); - } - - if (ep.step("Verifying APK...", 104)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } - - args.clear(); - args.push_back("-verify"); - args.push_back("-keystore"); - args.push_back(keystore); - args.push_back(tmp_unaligned_path); - args.push_back("-verbose"); - - OS::get_singleton()->execute(jarsigner, args, true, nullptr, nullptr, &retval); - if (retval) { - EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8."); - CLEANUP_AND_RETURN(ERR_CANT_CREATE); + err = sign_apk(p_preset, p_debug, tmp_unaligned_path, ep); + if (err != OK) { + CLEANUP_AND_RETURN(err); } } @@ -2231,7 +2755,6 @@ public: unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io); if (!tmp_unaligned) { - EditorNode::add_io_error("Could not unzip temporary unaligned APK."); CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND); } @@ -2248,7 +2771,6 @@ public: // following what is done in https://github.com/android/platform_build/blob/master/tools/zipalign/ZipAlign.cpp int bias = 0; while (ret == UNZ_OK) { - unz_file_info info; memset(&info, 0, sizeof(info)); @@ -2278,12 +2800,10 @@ public: memset(extra + info.size_file_extra, 0, padding); - // write - zip_fileinfo zipfi = get_zip_fileinfo(); - + zip_fileinfo fileinfo = get_zip_fileinfo(); zipOpenNewFileInZip2(final_apk, file.utf8().get_data(), - &zipfi, + &fileinfo, extra, info.size_file_extra + padding, nullptr, @@ -2306,17 +2826,15 @@ public: CLEANUP_AND_RETURN(OK); } - virtual void get_platform_features(List<String> *r_features) { - + virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("mobile"); r_features->push_back("Android"); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { } EditorExportPlatformAndroid() { - Ref<Image> img = memnew(Image(_android_logo)); logo.instance(); logo->create_from_image(img); @@ -2326,19 +2844,19 @@ public: run_icon->create_from_image(img); devices_changed = true; + plugins_changed = true; quit_request = false; - device_thread = Thread::create(_device_poll_thread, this); + check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this); } ~EditorExportPlatformAndroid() { quit_request = true; - Thread::wait_to_finish(device_thread); - memdelete(device_thread); + Thread::wait_to_finish(check_for_changes_thread); + memdelete(check_for_changes_thread); } }; void register_android_exporter() { - String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { exe_ext = "*.exe"; @@ -2349,7 +2867,7 @@ void register_android_exporter() { EDITOR_DEF("export/android/jarsigner", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/jarsigner", PROPERTY_HINT_GLOBAL_FILE, exe_ext)); EDITOR_DEF("export/android/debug_keystore", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore")); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks")); EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey"); EDITOR_DEF("export/android/debug_keystore_pass", "android"); EDITOR_DEF("export/android/force_system_user", false); diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h new file mode 100644 index 0000000000..3bc3651712 --- /dev/null +++ b/platform/android/export/gradle_export_util.h @@ -0,0 +1,306 @@ +/*************************************************************************/ +/* gradle_export_util.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 GODOT_GRADLE_EXPORT_UTIL_H +#define GODOT_GRADLE_EXPORT_UTIL_H + +#include "core/io/zip_io.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "editor/editor_export.h" + +const String godot_project_name_xml_string = R"(<?xml version="1.0" encoding="utf-8"?> +<!--WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">%s</string> +</resources> +)"; + +DisplayServer::ScreenOrientation _get_screen_orientation() { + String orientation_settings = ProjectSettings::get_singleton()->get("display/window/handheld/orientation"); + DisplayServer::ScreenOrientation screen_orientation; + if (orientation_settings == "portrait") + screen_orientation = DisplayServer::SCREEN_PORTRAIT; + else if (orientation_settings == "reverse_landscape") + screen_orientation = DisplayServer::SCREEN_REVERSE_LANDSCAPE; + else if (orientation_settings == "reverse_portrait") + screen_orientation = DisplayServer::SCREEN_REVERSE_PORTRAIT; + else if (orientation_settings == "sensor_landscape") + screen_orientation = DisplayServer::SCREEN_SENSOR_LANDSCAPE; + else if (orientation_settings == "sensor_portrait") + screen_orientation = DisplayServer::SCREEN_SENSOR_PORTRAIT; + else if (orientation_settings == "sensor") + screen_orientation = DisplayServer::SCREEN_SENSOR; + else + screen_orientation = DisplayServer::SCREEN_LANDSCAPE; + + return screen_orientation; +} + +int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation) { + switch (screen_orientation) { + case DisplayServer::SCREEN_PORTRAIT: + return 1; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + return 8; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + return 9; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + return 11; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + return 12; + case DisplayServer::SCREEN_SENSOR: + return 13; + case DisplayServer::SCREEN_LANDSCAPE: + default: + return 0; + } +} + +String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_orientation) { + switch (screen_orientation) { + case DisplayServer::SCREEN_PORTRAIT: + return "portrait"; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + return "reverseLandscape"; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + return "reversePortrait"; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + return "userLandscape"; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + return "userPortrait"; + case DisplayServer::SCREEN_SENSOR: + return "fullUser"; + case DisplayServer::SCREEN_LANDSCAPE: + default: + return "landscape"; + } +} + +// Utility method used to create a directory. +Error create_directory(const String &p_dir) { + if (!DirAccess::exists(p_dir)) { + DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'."); + Error err = filesystem_da->make_dir_recursive(p_dir); + ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'."); + memdelete(filesystem_da); + } + return OK; +} + +// Implementation of EditorExportSaveSharedObject. +// This method will only be called as an input to export_project_files. +// This method lets the .so files for all ABIs to be copied +// into the gradle project from the .AAR file +Error ignore_so_file(void *p_userdata, const SharedObject &p_so) { + return OK; +} + +// Writes p_data into a file at p_path, creating directories if necessary. +// Note: this will overwrite the file at p_path if it already exists. +Error store_file_at_path(const String &p_path, const Vector<uint8_t> &p_data) { + String dir = p_path.get_base_dir(); + Error err = create_directory(dir); + if (err != OK) { + return err; + } + FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'."); + fa->store_buffer(p_data.ptr(), p_data.size()); + memdelete(fa); + return OK; +} + +// Writes string p_data into a file at p_path, creating directories if necessary. +// Note: this will overwrite the file at p_path if it already exists. +Error store_string_at_path(const String &p_path, const String &p_data) { + String dir = p_path.get_base_dir(); + Error err = create_directory(dir); + if (err != OK) { + return err; + } + FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'."); + fa->store_string(p_data); + memdelete(fa); + return OK; +} + +// Implementation of EditorExportSaveFunction. +// This method will only be called as an input to export_project_files. +// It is used by the export_project_files method to save all the asset files into the gradle project. +// It's functionality mirrors that of the method save_apk_file. +// This method will be called ONLY when custom build is enabled. +Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { + String dst_path = p_path.replace_first("res://", "res://android/build/assets/"); + Error err = store_file_at_path(dst_path, p_data); + return err; +} + +// Creates strings.xml files inside the gradle project for different locales. +Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name) { + // Stores the string into the default values directory. + String processed_default_xml_string = vformat(godot_project_name_xml_string, project_name.xml_escape(true)); + store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string); + + // Searches the Gradle project res/ directory to find all supported locales + DirAccessRef da = DirAccess::open("res://android/build/res"); + if (!da) { + return ERR_CANT_OPEN; + } + da->list_dir_begin(); + while (true) { + String file = da->get_next(); + if (file == "") { + break; + } + if (!file.begins_with("values-")) { + // NOTE: This assumes all directories that start with "values-" are for localization. + continue; + } + String locale = file.replace("values-", "").replace("-r", "_"); + String property_name = "application/config/name_" + locale; + String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml"; + if (ProjectSettings::get_singleton()->has_setting(property_name)) { + String locale_project_name = ProjectSettings::get_singleton()->get(property_name); + String processed_xml_string = vformat(godot_project_name_xml_string, locale_project_name.xml_escape(true)); + store_string_at_path(locale_directory, processed_xml_string); + } else { + // TODO: Once the legacy build system is deprecated we don't need to have xml files for this else branch + store_string_at_path(locale_directory, processed_default_xml_string); + } + } + da->list_dir_end(); + return OK; +} + +String bool_to_string(bool v) { + return v ? "true" : "false"; +} + +String _get_gles_tag() { + bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" && + !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2"); + return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : ""; +} + +String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) { + String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\""; + String sizes[] = { "small", "normal", "large", "xlarge" }; + size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]); + for (size_t i = 0; i < num_sizes; i++) { + String feature_name = vformat("screen/support_%s", sizes[i]); + String feature_support = bool_to_string(p_preset->get(feature_name)); + String xml_entry = vformat("\n android:%sScreens=\"%s\"", sizes[i], feature_support); + manifest_screen_sizes += xml_entry; + } + manifest_screen_sizes += " />\n"; + return manifest_screen_sizes; +} + +String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { + String manifest_xr_features; + bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; + if (uses_xr) { + int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof + if (dof_index == 1) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n"; + } else if (dof_index == 2) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n"; + } + int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required + if (hand_tracking_index == 1) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n"; + } else if (hand_tracking_index == 2) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n"; + } + } + return manifest_xr_features; +} + +String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) { + String package_name = p_preset->get("package/unique_name"); + String manifest_instrumentation_text = vformat( + " <instrumentation\n" + " tools:node=\"replace\"\n" + " android:name=\".GodotInstrumentation\"\n" + " android:icon=\"@mipmap/icon\"\n" + " android:label=\"@string/godot_project_name_string\"\n" + " android:targetPackage=\"%s\" />\n", + package_name); + return manifest_instrumentation_text; +} + +String _get_plugins_tag(const String &plugins_names) { + if (!plugins_names.empty()) { + return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names); + } else { + return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n"; + } +} + +String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { + bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; + String orientation = _get_android_orientation_label(_get_screen_orientation()); + String manifest_activity_text = vformat( + " <activity android:name=\"com.godot.game.GodotApp\" " + "tools:replace=\"android:screenOrientation\" " + "android:screenOrientation=\"%s\">\n", + orientation); + if (uses_xr) { + String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness")); + manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness); + } else { + manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n"; + } + manifest_activity_text += " </activity>\n"; + return manifest_activity_text; +} + +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) { + bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; + String manifest_application_text = + " <application android:label=\"@string/godot_project_name_string\"\n" + " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n" + " android:icon=\"@mipmap/icon\">)\n\n" + " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n"; + + manifest_application_text += _get_plugins_tag(plugins_names); + if (uses_xr) { + manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n"; + } + manifest_application_text += _get_activity_tag(p_preset); + manifest_application_text += " </application>\n"; + return manifest_application_text; +} + +#endif //GODOT_GRADLE_EXPORT_UTIL_H diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index fa805ec4f3..2446ca2829 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -29,22 +29,19 @@ /*************************************************************************/ #include "file_access_android.h" -#include "core/print_string.h" +#include "core/string/print_string.h" AAssetManager *FileAccessAndroid::asset_manager = nullptr; /*void FileAccessAndroid::make_default() { - create_func=create_android; }*/ FileAccess *FileAccessAndroid::create_android() { - return memnew(FileAccessAndroid); } Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) { - String path = fix_path(p_path).simplify_path(); if (path.begins_with("/")) path = path.substr(1, path.length()); @@ -64,7 +61,6 @@ Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) { } void FileAccessAndroid::close() { - if (!a) return; AAsset_close(a); @@ -72,12 +68,10 @@ void FileAccessAndroid::close() { } bool FileAccessAndroid::is_open() const { - return a != nullptr; } void FileAccessAndroid::seek(size_t p_position) { - ERR_FAIL_COND(!a); AAsset_seek(a, p_position, SEEK_SET); pos = p_position; @@ -90,29 +84,24 @@ void FileAccessAndroid::seek(size_t p_position) { } void FileAccessAndroid::seek_end(int64_t p_position) { - ERR_FAIL_COND(!a); AAsset_seek(a, p_position, SEEK_END); pos = len + p_position; } size_t FileAccessAndroid::get_position() const { - return pos; } size_t FileAccessAndroid::get_len() const { - return len; } bool FileAccessAndroid::eof_reached() const { - return eof; } uint8_t FileAccessAndroid::get_8() const { - if (pos >= len) { eof = true; return 0; @@ -125,7 +114,6 @@ uint8_t FileAccessAndroid::get_8() const { } int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { - off_t r = AAsset_read(a, p_dst, p_length); if (pos + p_length > len) { @@ -133,7 +121,6 @@ int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { } if (r >= 0) { - pos += r; if (pos > len) { pos = len; @@ -143,22 +130,18 @@ int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { } Error FileAccessAndroid::get_error() const { - return eof ? ERR_FILE_EOF : OK; //not sure what else it may happen } void FileAccessAndroid::flush() { - ERR_FAIL(); } void FileAccessAndroid::store_8(uint8_t p_dest) { - ERR_FAIL(); } bool FileAccessAndroid::file_exists(const String &p_path) { - String path = fix_path(p_path).simplify_path(); if (path.begins_with("/")) path = path.substr(1, path.length()); diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index 6b5ec541fd..a347c63ffb 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -38,7 +38,6 @@ //#include <android_native_app_glue.h> class FileAccessAndroid : public FileAccess { - static FileAccess *create_android(); mutable AAsset *a; mutable size_t len; diff --git a/platform/android/file_access_jandroid.cpp b/platform/android/file_access_jandroid.cpp index e088eca8ef..df8b57fd3a 100644 --- a/platform/android/file_access_jandroid.cpp +++ b/platform/android/file_access_jandroid.cpp @@ -44,12 +44,10 @@ jmethodID FileAccessJAndroid::_file_eof = 0; jmethodID FileAccessJAndroid::_file_close = 0; FileAccess *FileAccessJAndroid::create_jandroid() { - return memnew(FileAccessJAndroid); } Error FileAccessJAndroid::_open(const String &p_path, int p_mode_flags) { - if (is_open()) close(); @@ -75,7 +73,6 @@ Error FileAccessJAndroid::_open(const String &p_path, int p_mode_flags) { } void FileAccessJAndroid::close() { - if (!is_open()) return; @@ -86,12 +83,10 @@ void FileAccessJAndroid::close() { } bool FileAccessJAndroid::is_open() const { - return id != 0; } void FileAccessJAndroid::seek(size_t p_position) { - JNIEnv *env = ThreadAndroid::get_env(); ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); @@ -99,42 +94,37 @@ void FileAccessJAndroid::seek(size_t p_position) { } void FileAccessJAndroid::seek_end(int64_t p_position) { - ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); seek(get_len()); } size_t FileAccessJAndroid::get_position() const { - JNIEnv *env = ThreadAndroid::get_env(); ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_tell, id); } size_t FileAccessJAndroid::get_len() const { - JNIEnv *env = ThreadAndroid::get_env(); ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_get_size, id); } bool FileAccessJAndroid::eof_reached() const { - JNIEnv *env = ThreadAndroid::get_env(); ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_eof, id); } uint8_t FileAccessJAndroid::get_8() const { - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); uint8_t byte; get_buffer(&byte, 1); return byte; } -int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const { +int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const { ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); if (p_length == 0) return 0; @@ -150,7 +140,6 @@ int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const { } Error FileAccessJAndroid::get_error() const { - if (eof_reached()) return ERR_FILE_EOF; return OK; @@ -163,7 +152,6 @@ void FileAccessJAndroid::store_8(uint8_t p_dest) { } bool FileAccessJAndroid::file_exists(const String &p_path) { - JNIEnv *env = ThreadAndroid::get_env(); String path = fix_path(p_path).simplify_path(); @@ -184,7 +172,6 @@ bool FileAccessJAndroid::file_exists(const String &p_path) { } void FileAccessJAndroid::setup(jobject p_io) { - io = p_io; JNIEnv *env = ThreadAndroid::get_env(); @@ -201,12 +188,10 @@ void FileAccessJAndroid::setup(jobject p_io) { } FileAccessJAndroid::FileAccessJAndroid() { - id = 0; } FileAccessJAndroid::~FileAccessJAndroid() { - if (is_open()) close(); } diff --git a/platform/android/file_access_jandroid.h b/platform/android/file_access_jandroid.h index b361c64922..e252a4d3ac 100644 --- a/platform/android/file_access_jandroid.h +++ b/platform/android/file_access_jandroid.h @@ -34,7 +34,6 @@ #include "core/os/file_access.h" #include "java_godot_lib_jni.h" class FileAccessJAndroid : public FileAccess { - static jobject io; static jclass cls; diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index cc480d1c84..e94681659c 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -32,19 +32,22 @@ <!-- 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"/> + android:name="plugins" + android:value="plugins_value"/> <activity android:name=".GodotApp" android:label="@string/godot_project_name_string" - android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" + android:theme="@style/GodotAppSplashTheme" android:launchMode="singleTask" android:screenOrientation="landscape" android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" android:resizeableActivity="false" tools:ignore="UnusedAttribute" > + <!-- Focus awareness metadata is updated at export time if the user enables it in the 'Xr Features' section. --> + <meta-data android:name="com.oculus.vr.focusaware" android:value="false" /> + <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 9ae47d6174..6de1d2dd30 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -21,6 +21,16 @@ allprojects { mavenCentral() google() jcenter() + + // Godot user plugins custom maven repos + String[] mavenRepos = getGodotPluginsMavenRepos() + if (mavenRepos != null && mavenRepos.size() > 0) { + for (String repoUrl : mavenRepos) { + maven { + url repoUrl + } + } + } } } @@ -40,15 +50,18 @@ dependencies { releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar']) } - // Godot prebuilt plugins - implementation fileTree(dir: 'libs/plugins', include: ["GodotPayment*.aar"]) + // Godot user plugins remote dependencies + String[] remoteDeps = getGodotPluginsRemoteBinaries() + if (remoteDeps != null && remoteDeps.size() > 0) { + for (String dep : remoteDeps) { + implementation dep + } + } - // 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) + // Godot user plugins local dependencies + String[] pluginsBinaries = getGodotPluginsLocalBinaries() + if (pluginsBinaries != null && pluginsBinaries.size() > 0) { + implementation files(pluginsBinaries) } } @@ -57,13 +70,25 @@ android { buildToolsVersion versions.buildTools compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion } defaultConfig { + // The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects. + aaptOptions { + ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~" + } + + ndk { + String[] export_abi_list = getExportEnabledABIs() + abiFilters export_abi_list + } + // Feel free to modify the application id to your own. applicationId getExportPackageName() + versionCode getExportVersionCode() + versionName getExportVersionName() minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk } @@ -105,3 +130,27 @@ android { } } } + +task copyAndRenameDebugApk(type: Copy) { + from "$buildDir/outputs/apk/debug/android_debug.apk" + into getExportPath() + rename "android_debug.apk", getExportFilename() +} + +task copyAndRenameReleaseApk(type: Copy) { + from "$buildDir/outputs/apk/release/android_release.apk" + into getExportPath() + rename "android_release.apk", getExportFilename() +} + +task copyAndRenameDebugAab(type: Copy) { + from "$buildDir/outputs/bundle/debug/build-debug.aab" + into getExportPath() + rename "build-debug.aab", getExportFilename() +} + +task copyAndRenameReleaseAab(type: Copy) { + from "$buildDir/outputs/bundle/release/build-release.aab" + into getExportPath() + rename "build-release.aab", getExportFilename() +} diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index aa98194a10..e6c45b73a7 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,21 +1,22 @@ ext.versions = [ - androidGradlePlugin: '3.5.3', + androidGradlePlugin: '4.1.0', compileSdk : 29, minSdk : 18, targetSdk : 29, - buildTools : '29.0.3', - supportCoreUtils : '28.0.0', - kotlinVersion : '1.3.61', - v4Support : '28.0.0' + buildTools : '30.0.1', + supportCoreUtils : '1.0.0', + kotlinVersion : '1.4.10', + v4Support : '1.0.0', + javaVersion : 1.8 ] ext.libraries = [ androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin", - supportCoreUtils : "com.android.support:support-core-utils:$versions.supportCoreUtils", + supportCoreUtils : "androidx.legacy:legacy-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" + v4Support : "androidx.legacy:legacy-support-v4:$versions.v4Support" ] ext.getExportPackageName = { -> @@ -28,39 +29,110 @@ ext.getExportPackageName = { -> return appId } +ext.getExportVersionCode = { -> + String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : "" + if (versionCode == null || versionCode.isEmpty()) { + versionCode = "1" + } + return Integer.parseInt(versionCode) +} + +ext.getExportVersionName = { -> + String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : "" + if (versionName == null || versionName.isEmpty()) { + versionName = "1.0" + } + return versionName +} + +final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|" + +// get the list of ABIs the project should be exported to +ext.getExportEnabledABIs = { -> + String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : ""; + if (enabledABIs == null || enabledABIs.isEmpty()) { + enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|" + } + Set<String> exportAbiFilter = []; + for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + if (!abi_name.trim().isEmpty()){ + exportAbiFilter.add(abi_name); + } + } + return exportAbiFilter; +} + +ext.getExportPath = { + String exportPath = project.hasProperty("export_path") ? project.property("export_path") : "" + if (exportPath == null || exportPath.isEmpty()) { + exportPath = "." + } + return exportPath +} + +ext.getExportFilename = { + String exportFilename = project.hasProperty("export_filename") ? project.property("export_filename") : "" + if (exportFilename == null || exportFilename.isEmpty()) { + exportFilename = "godot_android" + } + return exportFilename +} + /** - * 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. + * Parse the project properties for the 'plugins_maven_repos' property and return the list + * of maven repos. */ -ext.getGodotPluginsBinaries = { -> - String[] binDeps = [] +ext.getGodotPluginsMavenRepos = { -> + Set<String> mavenRepos = [] - // 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" + // Retrieve the list of maven repos. + if (project.hasProperty("plugins_maven_repos")) { + String mavenReposProperty = project.property("plugins_maven_repos") + if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) { + for (String mavenRepoUrl : mavenReposProperty.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + mavenRepos += mavenRepoUrl.trim() } } } - return binDeps + return mavenRepos +} + +/** + * Parse the project properties for the 'plugins_remote_binaries' property and return + * it for inclusion in the build dependencies. + */ +ext.getGodotPluginsRemoteBinaries = { -> + Set<String> remoteDeps = [] + + // Retrieve the list of remote plugins binaries. + if (project.hasProperty("plugins_remote_binaries")) { + String remoteDepsList = project.property("plugins_remote_binaries") + if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) { + for (String dep: remoteDepsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + remoteDeps += dep.trim() + } + } + } + return remoteDeps } /** - * Parse the project properties for the 'custom_template_plugins_dir' property and return - * its value. - * - * The returned value is the directory containing user plugins. + * Parse the project properties for the 'plugins_local_binaries' property and return + * their binaries for inclusion in the build dependencies. */ -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") - : "" +ext.getGodotPluginsLocalBinaries = { -> + Set<String> binDeps = [] - return pluginsDir + // Retrieve the list of local plugins binaries. + if (project.hasProperty("plugins_local_binaries")) { + String pluginsList = project.property("plugins_local_binaries") + if (pluginsList != null && !pluginsList.trim().isEmpty()) { + for (String plugin : pluginsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + binDeps += plugin.trim() + } + } + } + + return binDeps } diff --git a/platform/android/java/app/res/drawable/splash.png b/platform/android/java/app/res/drawable/splash.png Binary files differnew file mode 100644 index 0000000000..7bddd4325a --- /dev/null +++ b/platform/android/java/app/res/drawable/splash.png diff --git a/platform/android/java/app/res/drawable/splash_bg_color.png b/platform/android/java/app/res/drawable/splash_bg_color.png Binary files differnew file mode 100644 index 0000000000..004b6fd508 --- /dev/null +++ b/platform/android/java/app/res/drawable/splash_bg_color.png diff --git a/platform/android/java/app/res/drawable/splash_drawable.xml b/platform/android/java/app/res/drawable/splash_drawable.xml new file mode 100644 index 0000000000..2794a40817 --- /dev/null +++ b/platform/android/java/app/res/drawable/splash_drawable.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:drawable="@drawable/splash_bg_color" /> + + <item> + <bitmap + android:gravity="center" + android:src="@drawable/splash" /> + </item> + +</layer-list> diff --git a/platform/android/java/lib/res/values-ar/strings.xml b/platform/android/java/app/res/values-ar/godot_project_name_string.xml index 9f3dc6d6ac..23aa5cf3e1 100644 --- a/platform/android/java/lib/res/values-ar/strings.xml +++ b/platform/android/java/app/res/values-ar/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ar</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-bg/strings.xml b/platform/android/java/app/res/values-bg/godot_project_name_string.xml index bd8109277e..dbb7e04ae5 100644 --- a/platform/android/java/lib/res/values-bg/strings.xml +++ b/platform/android/java/app/res/values-bg/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-bg</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-ca/strings.xml b/platform/android/java/app/res/values-ca/godot_project_name_string.xml index 494cb88468..709d0961e6 100644 --- a/platform/android/java/lib/res/values-ca/strings.xml +++ b/platform/android/java/app/res/values-ca/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ca</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-cs/strings.xml b/platform/android/java/app/res/values-cs/godot_project_name_string.xml index 30ce00f895..ab248a8032 100644 --- a/platform/android/java/lib/res/values-cs/strings.xml +++ b/platform/android/java/app/res/values-cs/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-cs</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-da/strings.xml b/platform/android/java/app/res/values-da/godot_project_name_string.xml index 4c2a1cf0f4..906bf44f57 100644 --- a/platform/android/java/lib/res/values-da/strings.xml +++ b/platform/android/java/app/res/values-da/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-da</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-de/strings.xml b/platform/android/java/app/res/values-de/godot_project_name_string.xml index 52946d4cce..0cacb0175f 100644 --- a/platform/android/java/lib/res/values-de/strings.xml +++ b/platform/android/java/app/res/values-de/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-de</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-el/strings.xml b/platform/android/java/app/res/values-el/godot_project_name_string.xml index 181dc51762..047de616a5 100644 --- a/platform/android/java/lib/res/values-el/strings.xml +++ b/platform/android/java/app/res/values-el/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-el</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-en/strings.xml b/platform/android/java/app/res/values-en/godot_project_name_string.xml index 976a565013..bb3a5dbef3 100644 --- a/platform/android/java/lib/res/values-en/strings.xml +++ b/platform/android/java/app/res/values-en/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-en</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-es-rES/strings.xml b/platform/android/java/app/res/values-es-rES/godot_project_name_string.xml index 73f63a08f8..d4537f3496 100644 --- a/platform/android/java/lib/res/values-es-rES/strings.xml +++ b/platform/android/java/app/res/values-es-rES/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-es_ES</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-es/strings.xml b/platform/android/java/app/res/values-es/godot_project_name_string.xml index 07b718a641..d63a16022e 100644 --- a/platform/android/java/lib/res/values-es/strings.xml +++ b/platform/android/java/app/res/values-es/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-es</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/app/res/values-fa/godot_project_name_string.xml b/platform/android/java/app/res/values-fa/godot_project_name_string.xml new file mode 100644 index 0000000000..c303f13d5f --- /dev/null +++ b/platform/android/java/app/res/values-fa/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name-fa</string> +</resources> diff --git a/platform/android/java/lib/res/values-fi/strings.xml b/platform/android/java/app/res/values-fi/godot_project_name_string.xml index 323d82aff1..bd6005574a 100644 --- a/platform/android/java/lib/res/values-fi/strings.xml +++ b/platform/android/java/app/res/values-fi/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-fi</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-fr/strings.xml b/platform/android/java/app/res/values-fr/godot_project_name_string.xml index 32bead2661..2e94b65a20 100644 --- a/platform/android/java/lib/res/values-fr/strings.xml +++ b/platform/android/java/app/res/values-fr/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-fr</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-hi/strings.xml b/platform/android/java/app/res/values-hi/godot_project_name_string.xml index 8aab2a8c63..0bf75dcd56 100644 --- a/platform/android/java/lib/res/values-hi/strings.xml +++ b/platform/android/java/app/res/values-hi/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-hi</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-hr/strings.xml b/platform/android/java/app/res/values-hr/godot_project_name_string.xml index caf55e2241..d3f75910f9 100644 --- a/platform/android/java/lib/res/values-hr/strings.xml +++ b/platform/android/java/app/res/values-hr/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-hr</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-hu/strings.xml b/platform/android/java/app/res/values-hu/godot_project_name_string.xml index e7f9e51226..012b613af3 100644 --- a/platform/android/java/lib/res/values-hu/strings.xml +++ b/platform/android/java/app/res/values-hu/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-hu</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/app/res/values-in/godot_project_name_string.xml b/platform/android/java/app/res/values-in/godot_project_name_string.xml new file mode 100644 index 0000000000..eedecff7a1 --- /dev/null +++ b/platform/android/java/app/res/values-in/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name-in</string> +</resources> diff --git a/platform/android/java/lib/res/values-it/strings.xml b/platform/android/java/app/res/values-it/godot_project_name_string.xml index 1f5e5a049e..7e734047c4 100644 --- a/platform/android/java/lib/res/values-it/strings.xml +++ b/platform/android/java/app/res/values-it/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-it</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/app/res/values-iw/godot_project_name_string.xml b/platform/android/java/app/res/values-iw/godot_project_name_string.xml new file mode 100644 index 0000000000..03893f0cbb --- /dev/null +++ b/platform/android/java/app/res/values-iw/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name-iw</string> +</resources> diff --git a/platform/android/java/lib/res/values-ja/strings.xml b/platform/android/java/app/res/values-ja/godot_project_name_string.xml index 7f85f57df7..f9dd4fab0d 100644 --- a/platform/android/java/lib/res/values-ja/strings.xml +++ b/platform/android/java/app/res/values-ja/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ja</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/app/res/values-ko/godot_project_name_string.xml b/platform/android/java/app/res/values-ko/godot_project_name_string.xml new file mode 100644 index 0000000000..26f5dac176 --- /dev/null +++ b/platform/android/java/app/res/values-ko/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name-ko</string> +</resources> diff --git a/platform/android/java/lib/res/values-lt/strings.xml b/platform/android/java/app/res/values-lt/godot_project_name_string.xml index 6e3677fde7..1c2e976cc5 100644 --- a/platform/android/java/lib/res/values-lt/strings.xml +++ b/platform/android/java/app/res/values-lt/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-lt</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-lv/strings.xml b/platform/android/java/app/res/values-lv/godot_project_name_string.xml index 701fc271ac..b5e638ed73 100644 --- a/platform/android/java/lib/res/values-lv/strings.xml +++ b/platform/android/java/app/res/values-lv/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-lv</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-nb/strings.xml b/platform/android/java/app/res/values-nb/godot_project_name_string.xml index 73147ca1af..e6d89d6a3f 100644 --- a/platform/android/java/lib/res/values-nb/strings.xml +++ b/platform/android/java/app/res/values-nb/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-nb</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-nl/strings.xml b/platform/android/java/app/res/values-nl/godot_project_name_string.xml index e501928a35..93cb3a3878 100644 --- a/platform/android/java/lib/res/values-nl/strings.xml +++ b/platform/android/java/app/res/values-nl/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-nl</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-pl/strings.xml b/platform/android/java/app/res/values-pl/godot_project_name_string.xml index ea5da73b6f..e5d6ac74fb 100644 --- a/platform/android/java/lib/res/values-pl/strings.xml +++ b/platform/android/java/app/res/values-pl/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-pl</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-pt/strings.xml b/platform/android/java/app/res/values-pt/godot_project_name_string.xml index bdda7cd2c7..a4624655c5 100644 --- a/platform/android/java/lib/res/values-pt/strings.xml +++ b/platform/android/java/app/res/values-pt/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-pt</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-ro/strings.xml b/platform/android/java/app/res/values-ro/godot_project_name_string.xml index 3686da4c19..19e026637e 100644 --- a/platform/android/java/lib/res/values-ro/strings.xml +++ b/platform/android/java/app/res/values-ro/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ro</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-ru/strings.xml b/platform/android/java/app/res/values-ru/godot_project_name_string.xml index 954067658b..284845241f 100644 --- a/platform/android/java/lib/res/values-ru/strings.xml +++ b/platform/android/java/app/res/values-ru/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ru</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-sk/strings.xml b/platform/android/java/app/res/values-sk/godot_project_name_string.xml index 37d1283124..f8ab4a5b59 100644 --- a/platform/android/java/lib/res/values-sk/strings.xml +++ b/platform/android/java/app/res/values-sk/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-sk</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-sl/strings.xml b/platform/android/java/app/res/values-sl/godot_project_name_string.xml index 0bb249c375..98bd53e8d2 100644 --- a/platform/android/java/lib/res/values-sl/strings.xml +++ b/platform/android/java/app/res/values-sl/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-sl</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-sr/strings.xml b/platform/android/java/app/res/values-sr/godot_project_name_string.xml index 0e83cab1a1..3f400f2a4d 100644 --- a/platform/android/java/lib/res/values-sr/strings.xml +++ b/platform/android/java/app/res/values-sr/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-sr</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-sv/strings.xml b/platform/android/java/app/res/values-sv/godot_project_name_string.xml index e3a04ac2ec..8670b7c9aa 100644 --- a/platform/android/java/lib/res/values-sv/strings.xml +++ b/platform/android/java/app/res/values-sv/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-sv</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-th/strings.xml b/platform/android/java/app/res/values-th/godot_project_name_string.xml index 0aa893b8bf..a1cc1bcd49 100644 --- a/platform/android/java/lib/res/values-th/strings.xml +++ b/platform/android/java/app/res/values-th/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-th</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-tl/strings.xml b/platform/android/java/app/res/values-tl/godot_project_name_string.xml index e7e2af4909..6d66d114cf 100644 --- a/platform/android/java/lib/res/values-tl/strings.xml +++ b/platform/android/java/app/res/values-tl/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-tl</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-tr/strings.xml b/platform/android/java/app/res/values-tr/godot_project_name_string.xml index 97af1243a6..ba3bd7de36 100644 --- a/platform/android/java/lib/res/values-tr/strings.xml +++ b/platform/android/java/app/res/values-tr/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-tr</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-uk/strings.xml b/platform/android/java/app/res/values-uk/godot_project_name_string.xml index 3dea6908a9..5f14ab25a0 100644 --- a/platform/android/java/lib/res/values-uk/strings.xml +++ b/platform/android/java/app/res/values-uk/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-uk</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-vi/strings.xml b/platform/android/java/app/res/values-vi/godot_project_name_string.xml index a6552130b0..295378e111 100644 --- a/platform/android/java/lib/res/values-vi/strings.xml +++ b/platform/android/java/app/res/values-vi/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-vi</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-zh-rHK/strings.xml b/platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml index 8a6269da0f..40ab0f285a 100644 --- a/platform/android/java/lib/res/values-zh-rHK/strings.xml +++ b/platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-zh_HK</string> </resources> diff --git a/platform/android/java/lib/res/values-zh-rTW/strings.xml b/platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml index b1bb39d5d6..095bd564e2 100644 --- a/platform/android/java/lib/res/values-zh-rTW/strings.xml +++ b/platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-zh_TW</string> </resources> diff --git a/platform/android/java/lib/res/values-zh-rCN/strings.xml b/platform/android/java/app/res/values-zh/godot_project_name_string.xml index 6668c56bd9..31aa8c273a 100644 --- a/platform/android/java/lib/res/values-zh-rCN/strings.xml +++ b/platform/android/java/app/res/values-zh/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-zh</string> </resources> diff --git a/platform/android/java/app/res/values/godot_project_name_string.xml b/platform/android/java/app/res/values/godot_project_name_string.xml new file mode 100644 index 0000000000..7ec2738896 --- /dev/null +++ b/platform/android/java/app/res/values/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name</string> +</resources> diff --git a/platform/android/java/app/res/values/themes.xml b/platform/android/java/app/res/values/themes.xml new file mode 100644 index 0000000000..99f723f5ba --- /dev/null +++ b/platform/android/java/app/res/values/themes.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <style name="GodotAppMainTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen"/> + + <style name="GodotAppSplashTheme" parent="@style/GodotAppMainTheme"> + <item name="android:windowBackground">@drawable/splash_drawable</item> + <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + </style> +</resources> diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java index eb884404cd..51df70969e 100644 --- a/platform/android/java/app/src/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -30,11 +30,18 @@ package com.godot.game; -import org.godotengine.godot.Godot; +import org.godotengine.godot.FullScreenGodotApp; + +import android.os.Bundle; /** * Template activity for Godot Android custom builds. * Feel free to extend and modify this class for your custom logic. */ -public class GodotApp extends Godot { +public class GodotApp extends FullScreenGodotApp { + @Override + public void onCreate(Bundle savedInstanceState) { + setTheme(R.style.GodotAppMainTheme); + super.onCreate(savedInstanceState); + } } diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 865b61956c..73c136ed0e 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -22,8 +22,6 @@ allprojects { } ext { - sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : "" - supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] supportedTargets = ["release", "debug"] @@ -97,13 +95,6 @@ task copyReleaseAARToAppModule(type: Copy) { 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. @@ -140,7 +131,7 @@ task generateGodotTemplates(type: GradleBuild) { startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType) } - tasks = ["copyGodotPaymentPluginToAppModule"] + tasks = [] // Only build the apks and aar files for which we have native shared libraries. for (String target : supportedTargets) { @@ -192,4 +183,6 @@ task cleanGodotTemplates(type: Delete) { delete("$binDir/android_source.zip") delete("$binDir/godot-lib.debug.aar") delete("$binDir/godot-lib.release.aar") + + finalizedBy getTasksByName("clean", true) } diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties index aac7c9b461..2dc069ad2f 100644 --- a/platform/android/java/gradle.properties +++ b/platform/android/java/gradle.properties @@ -7,6 +7,9 @@ # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html +android.enableJetifier=true +android.useAndroidX=true + # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m @@ -15,3 +18,5 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +org.gradle.warning.mode=all diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index f56b0f6a5e..a7d8a0f310 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.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 19eee5a315..89ce3d15e6 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -18,6 +18,11 @@ android { targetSdkVersion versions.targetSdk } + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + lintOptions { abortOnError false disable 'MissingTranslation', 'UnusedResources' @@ -50,15 +55,6 @@ android { def buildType = variant.buildType.name.capitalize() - def taskPrefix = "" - if (project.path != ":") { - taskPrefix = project.path + ":" - } - - // Disable the externalNativeBuild* task as it would cause build failures since the cmake build - // files is only setup for editing support. - gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType - def releaseTarget = buildType.toLowerCase() if (releaseTarget == null || releaseTarget == "") { throw new GradleException("Invalid build type: " + buildType) @@ -68,20 +64,46 @@ android { throw new GradleException("Invalid default abi: " + defaultAbi) } + // Find scons' executable path + File sconsExecutableFile = null + def sconsName = "scons" + def sconsExts = (org.gradle.internal.os.OperatingSystem.current().isWindows() + ? [".bat", ".exe"] + : [""]) + logger.lifecycle("Looking for $sconsName executable path") + for (ext in sconsExts) { + String sconsNameExt = sconsName + ext + logger.lifecycle("Checking $sconsNameExt") + + sconsExecutableFile = org.gradle.internal.os.OperatingSystem.current().findInPath(sconsNameExt) + if (sconsExecutableFile != null) { + // We're done! + break + } + + // Check all the options in path + List<File> allOptions = org.gradle.internal.os.OperatingSystem.current().findAllInPath(sconsNameExt) + if (!allOptions.isEmpty()) { + // Pick the first option and we're done! + sconsExecutableFile = allOptions.get(0) + break + } + } + + if (sconsExecutableFile == null) { + throw new GradleException("Unable to find executable path for the '$sconsName' command.") + } else { + logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}") + } + // Creating gradle task to generate the native libraries for the default abi. def taskName = getSconsTaskName(buildType) tasks.create(name: taskName, type: Exec) { - executable "scons" + sconsExt + executable sconsExecutableFile.absolutePath args "--directory=${pathToRootDir}", "platform=android", "target=${releaseTarget}", "android_arch=${defaultAbi}", "-j" + Runtime.runtime.availableProcessors() } // Schedule the tasks so the generated libs are present before the aar file is packaged. tasks["merge${buildType}JniLibFolders"].dependsOn taskName } - - externalNativeBuild { - cmake { - path "CMakeLists.txt" - } - } } diff --git a/platform/android/java/lib/res/layout/downloading_expansion.xml b/platform/android/java/lib/res/layout/downloading_expansion.xml index 4a9700965f..34c2757598 100644 --- a/platform/android/java/lib/res/layout/downloading_expansion.xml +++ b/platform/android/java/lib/res/layout/downloading_expansion.xml @@ -162,4 +162,4 @@ </LinearLayout> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/platform/android/java/lib/res/layout/godot_app_layout.xml b/platform/android/java/lib/res/layout/godot_app_layout.xml new file mode 100644 index 0000000000..386ded1c5d --- /dev/null +++ b/platform/android/java/lib/res/layout/godot_app_layout.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/godot_fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml b/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml index fae1faeb60..426e1bd841 100644 --- a/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml +++ b/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml @@ -105,4 +105,4 @@ </RelativeLayout> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml b/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml index 1ed4037035..cfdcca2ab5 100644 --- a/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml +++ b/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml @@ -2,4 +2,4 @@ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@mipmap/icon_background"/> <foreground android:drawable="@mipmap/icon_foreground"/> -</adaptive-icon>
\ No newline at end of file +</adaptive-icon> diff --git a/platform/android/java/lib/res/values-fa/strings.xml b/platform/android/java/lib/res/values-fa/strings.xml index f1e29013c4..60b01accf1 100644 --- a/platform/android/java/lib/res/values-fa/strings.xml +++ b/platform/android/java/lib/res/values-fa/strings.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="godot_project_name_string">godot-project-name-fa</string> <string name="text_paused_cellular">آیا می خواهید بر روی اتصال داده همراه دانلود را شروع کنید؟ بر اساس نوع سطح داده شما این ممکن است برای شما هزینه مالی داشته باشد.</string> <string name="text_paused_cellular_2">اگر نمی خواهید بر روی اتصال داده همراه دانلود را شروع کنید ، دانلود به صورت خودکار در زمان دسترسی به وای-فای شروع می شود.</string> <string name="text_button_resume_cellular">ادامه دانلود</string> diff --git a/platform/android/java/lib/res/values-in/strings.xml b/platform/android/java/lib/res/values-in/strings.xml deleted file mode 100644 index 9e9a8b0c03..0000000000 --- a/platform/android/java/lib/res/values-in/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="godot_project_name_string">godot-project-name-id</string> -</resources>
\ No newline at end of file diff --git a/platform/android/java/lib/res/values-iw/strings.xml b/platform/android/java/lib/res/values-iw/strings.xml deleted file mode 100644 index f52ede2085..0000000000 --- a/platform/android/java/lib/res/values-iw/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="godot_project_name_string">godot-project-name-he</string> -</resources>
\ No newline at end of file diff --git a/platform/android/java/lib/res/values-ko/strings.xml b/platform/android/java/lib/res/values-ko/strings.xml index fab0bdd753..7b62345977 100644 --- a/platform/android/java/lib/res/values-ko/strings.xml +++ b/platform/android/java/lib/res/values-ko/strings.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="godot_project_name_string">godot-project-name-ko</string> <string name="text_paused_cellular">모바일 네트워크를 사용하여 다운로드 하시겠습니까? 남은 데이터 사용량에 따라, 요금이 부과될 수 있습니다.</string> <string name="text_paused_cellular_2">모바일 네트워크를 사용하여 다운로드 하지 않을 경우, 와이파이 연결이 가능할 때 자동적으로 다운로드가 이루어집니다.</string> <string name="text_button_resume_cellular">다운로드 계속하기</string> @@ -52,4 +51,4 @@ <string name="kilobytes_per_second">%1$s KB/s</string> <string name="time_remaining">남은 시간: %1$s</string> <string name="time_remaining_notification">%1$s 남음</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values/dimens.xml b/platform/android/java/lib/res/values/dimens.xml new file mode 100644 index 0000000000..9034dbbcc1 --- /dev/null +++ b/platform/android/java/lib/res/values/dimens.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <dimen name="text_edit_height">48dp</dimen> +</resources> diff --git a/platform/android/java/lib/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml index a1b81a6186..590b066d8a 100644 --- a/platform/android/java/lib/res/values/strings.xml +++ b/platform/android/java/lib/res/values/strings.xml @@ -52,4 +52,4 @@ <string name="kilobytes_per_second">%1$s KB/s</string> <string name="time_remaining">Time remaining: %1$s</string> <string name="time_remaining_notification">%1$s left</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values/styles.xml b/platform/android/java/lib/res/values/styles.xml index a442f61e7e..b798373bc6 100644 --- a/platform/android/java/lib/res/values/styles.xml +++ b/platform/android/java/lib/res/values/styles.xml @@ -22,4 +22,4 @@ <item name="android:background">@android:color/background_dark</item> </style> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java index 0abaf2e052..d481c22204 100644 --- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java @@ -29,9 +29,9 @@ import com.google.android.vending.expansion.downloader.IDownloaderClient; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; -import android.os.Build; import android.os.Messenger; -import android.support.v4.app.NotificationCompat; + +import androidx.core.app.NotificationCompat; /** * This class handles displaying the notification associated with the download diff --git a/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java b/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java index 594cae774b..8b7a9c6c74 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java @@ -34,16 +34,13 @@ import java.util.HashMap; import java.util.Set; public class Dictionary extends HashMap<String, Object> { - protected String[] keys_cache; public String[] get_keys() { - String[] ret = new String[size()]; int i = 0; Set<String> keys = keySet(); for (String key : keys) { - ret[i] = key; i++; }; @@ -52,12 +49,10 @@ public class Dictionary extends HashMap<String, Object> { }; public Object[] get_values() { - Object[] ret = new Object[size()]; int i = 0; Set<String> keys = keySet(); for (String key : keys) { - ret[i] = get(key); i++; }; @@ -70,7 +65,6 @@ public class Dictionary extends HashMap<String, Object> { }; public void set_values(Object[] vals) { - int i = 0; for (String key : keys_cache) { put(key, vals[i]); diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/CustomSSLSocketFactory.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java index 9571769cd3..5aa48d87da 100644 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/CustomSSLSocketFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -1,5 +1,5 @@ /*************************************************************************/ -/* CustomSSLSocketFactory.java */ +/* FullScreenGodotApp.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,43 +28,72 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.plugin.payment.utils; +package org.godotengine.godot; -import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import org.apache.http.conn.ssl.SSLSocketFactory; +import android.content.Intent; +import android.os.Bundle; +import android.view.KeyEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; /** + * Base activity for Android apps intending to use Godot as the primary and only screen. * - * @author Luis Linietsky <luis.linietsky@gmail.com> + * It's also a reference implementation for how to setup and use the {@link Godot} fragment + * within an Android app. */ -public class CustomSSLSocketFactory extends SSLSocketFactory { - SSLContext sslContext = SSLContext.getInstance("TLS"); +public abstract class FullScreenGodotApp extends FragmentActivity { + @Nullable + private Godot godotFragment; - public CustomSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - super(truststore); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.godot_app_layout); + godotFragment = initGodotInstance(); + if (godotFragment == null) { + throw new IllegalStateException("Godot instance must be non-null."); + } - TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); - tmf.init(truststore); + getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss(); + } - sslContext.init(null, tmf.getTrustManagers(), null); + @Override + public void onNewIntent(Intent intent) { + if (godotFragment != null) { + godotFragment.onNewIntent(intent); + } } @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { - return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); + public void onBackPressed() { + if (godotFragment != null) { + godotFragment.onBackPressed(); + } else { + super.onBackPressed(); + } } @Override - public Socket createSocket() throws IOException { - return sslContext.getSocketFactory().createSocket(); + public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) { + if (godotFragment != null && godotFragment.onKeyMultiple(inKeyCode, repeatCount, event)) { + return true; + } + return super.onKeyMultiple(inKeyCode, repeatCount, event); + } + + /** + * Used to initialize the Godot fragment instance in {@link FullScreenGodotApp#onCreate(Bundle)}. + */ + @NonNull + protected Godot initGodotInstance() { + return new Godot(); + } + + @Nullable + protected final Godot getGodotFragment() { + return godotFragment; } } 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 bf0d1c6273..3bbe35091c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -30,6 +30,16 @@ package org.godotengine.godot; +import static android.content.Context.MODE_PRIVATE; +import static android.content.Context.WINDOW_SERVICE; + +import org.godotengine.godot.input.GodotEditText; +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; + import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; @@ -59,12 +69,10 @@ 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.v4.app.FragmentActivity; import android.view.Display; +import android.view.InputDevice; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.View; @@ -77,6 +85,12 @@ import android.widget.Button; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.TextView; + +import androidx.annotation.CallSuper; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + import com.google.android.vending.expansion.downloader.DownloadProgressInfo; import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; @@ -84,6 +98,7 @@ import com.google.android.vending.expansion.downloader.Helpers; import com.google.android.vending.expansion.downloader.IDownloaderClient; import com.google.android.vending.expansion.downloader.IDownloaderService; import com.google.android.vending.expansion.downloader.IStub; + import java.io.File; import java.io.FileInputStream; import java.io.InputStream; @@ -91,15 +106,8 @@ import java.security.MessageDigest; import java.util.LinkedList; import java.util.List; import java.util.Locale; -import org.godotengine.godot.input.GodotEditText; -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 FragmentActivity implements SensorEventListener, IDownloaderClient { +public class Godot extends Fragment implements SensorEventListener, IDownloaderClient { private IStub mDownloaderClientStub; private TextView mStatusText; private TextView mProgressFraction; @@ -127,7 +135,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe static private Intent mCurrentIntent; - @Override public void onNewIntent(Intent intent) { mCurrentIntent = intent; } @@ -145,14 +152,14 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe private void setButtonPausedState(boolean paused) { mStatePaused = paused; - int stringResourceID = paused ? R.string.text_button_resume : - R.string.text_button_pause; + int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause; mPauseButton.setText(stringResourceID); } private String[] command_line; private boolean use_apk_expansion; + private ViewGroup containerLayout; public GodotRenderView mRenderView; private boolean godot_initialized = false; @@ -171,7 +178,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe public ResultCallback result_callback; @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(int requestCode, int resultCode, Intent data) { if (result_callback != null) { result_callback.callback(requestCode, resultCode, data); result_callback = null; @@ -208,27 +215,29 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe */ @Keep private void onVideoInit() { - final FrameLayout layout = new FrameLayout(this); - layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - setContentView(layout); + final Activity activity = getActivity(); + containerLayout = new FrameLayout(activity); + containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); // GodotEditText layout - GodotEditText editText = new GodotEditText(this); - editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + GodotEditText editText = new GodotEditText(activity); + editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, + (int)getResources().getDimension(R.dimen.text_edit_height))); // ...add to FrameLayout - layout.addView(editText); + containerLayout.addView(editText); GodotLib.setup(command_line); final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name"); if (videoDriver.equals("Vulkan")) { - mRenderView = new GodotVulkanRenderView(this); + mRenderView = new GodotVulkanRenderView(activity, this); } else { - mRenderView = new GodotGLRenderView(this, xrMode, use_32_bits, use_debug_opengl); + mRenderView = new GodotGLRenderView(activity, this, xrMode, use_32_bits, + use_debug_opengl); } View view = mRenderView.getView(); - layout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + containerLayout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); editText.setView(mRenderView); io.setEdit(editText); @@ -236,7 +245,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override public void onGlobalLayout() { Point fullSize = new Point(); - getWindowManager().getDefaultDisplay().getSize(fullSize); + activity.getWindowManager().getDefaultDisplay().getSize(fullSize); Rect gameSize = new Rect(); mRenderView.getView().getWindowVisibleDisplayFrame(gameSize); @@ -248,7 +257,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe mRenderView.queueOnRenderThread(new Runnable() { @Override public void run() { - // Must occur after GodotLib.setup has completed. for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onRegisterPluginWithGodotNative(); @@ -260,9 +268,9 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe // Include the returned non-null views in the Godot view hierarchy. for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - View pluginView = plugin.onMainCreateView(this); + View pluginView = plugin.onMainCreate(activity); if (pluginView != null) { - layout.addView(pluginView); + containerLayout.addView(pluginView); } } } @@ -272,9 +280,9 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override public void run() { if (p_enabled) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } }); @@ -288,7 +296,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Keep private void vibrate(int durationMs) { if (requestPermission("VIBRATE")) { - Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE); + Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)); @@ -312,13 +320,16 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe // Using instrumentation is a way of making the whole app process restart, because Android // will kill any process of the same package which was already running. // - Bundle args = new Bundle(); - args.putParcelable("intent", mCurrentIntent); - startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args); + final Activity activity = getActivity(); + if (activity != null) { + Bundle args = new Bundle(); + args.putParcelable("intent", mCurrentIntent); + activity.startInstrumentation(new ComponentName(activity, GodotInstrumentation.class), null, args); + } } public void alert(final String message, final String title) { - final Activity activity = this; + final Activity activity = getActivity(); runOnUiThread(new Runnable() { @Override public void run() { @@ -338,15 +349,16 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public int getGLESVersionCode() { - ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo(); return deviceInfo.reqGlEsVersion; } - private String[] getCommandLine() { + @CallSuper + protected String[] getCommandLine() { InputStream is; try { - is = getAssets().open("_cl_"); + is = getActivity().getAssets().open("_cl_"); byte[] len = new byte[4]; int r = is.read(len); if (r < 4) { @@ -358,7 +370,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe for (int i = 0; i < argc; i++) { r = is.read(len); if (r < 4) { - return new String[0]; } int strlen = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF)); @@ -406,9 +417,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe String expansion_pack_path; private void initializeGodot() { - if (expansion_pack_path != null) { - String[] new_cmdline; int cll = 0; if (command_line != null) { @@ -426,11 +435,12 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe command_line = new_cmdline; } - io = new GodotIO(this); - io.unique_id = Secure.getString(getContentResolver(), Secure.ANDROID_ID); + final Activity activity = getActivity(); + io = new GodotIO(activity); + io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID); GodotLib.io = io; - netUtils = new GodotNetUtils(this); - mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); + netUtils = new GodotNetUtils(activity); + mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); @@ -440,7 +450,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); - GodotLib.initialize(this, getAssets(), use_apk_expansion); + GodotLib.initialize(activity, this, activity.getAssets(), use_apk_expansion); result_callback = null; @@ -454,157 +464,152 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } @Override - protected void onCreate(Bundle icicle) { - - super.onCreate(icicle); - Window window = getWindow(); + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) { + final Activity activity = getActivity(); + Window window = activity.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); - mClipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); + mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE); pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); //check for apk expansion API - if (true) { - boolean md5mismatch = false; - command_line = getCommandLine(); - String main_pack_md5 = null; - String main_pack_key = null; - - List<String> new_args = new LinkedList<String>(); - - for (int i = 0; i < command_line.length; i++) { - - boolean has_extra = i < command_line.length - 1; - if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) { - xrMode = XRMode.REGULAR; - } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) { - xrMode = XRMode.OVR; - } else if (command_line[i].equals("--use_depth_32")) { - use_32_bits = true; - } else if (command_line[i].equals("--debug_opengl")) { - use_debug_opengl = true; - } else if (command_line[i].equals("--use_immersive")) { - use_immersive = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ - window.getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - - UiChangeListener(); - } - } else if (command_line[i].equals("--use_apk_expansion")) { - use_apk_expansion = true; - } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) { - main_pack_md5 = command_line[i + 1]; - i++; - } else if (has_extra && command_line[i].equals("--apk_expansion_key")) { - main_pack_key = command_line[i + 1]; - SharedPreferences prefs = getSharedPreferences("app_data_keys", MODE_PRIVATE); - Editor editor = prefs.edit(); - editor.putString("store_public_key", main_pack_key); - - editor.apply(); - i++; - } else if (command_line[i].trim().length() != 0) { - new_args.add(command_line[i]); + boolean md5mismatch = false; + command_line = getCommandLine(); + String main_pack_md5 = null; + String main_pack_key = null; + + List<String> new_args = new LinkedList<String>(); + + for (int i = 0; i < command_line.length; i++) { + boolean has_extra = i < command_line.length - 1; + if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) { + xrMode = XRMode.REGULAR; + } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) { + xrMode = XRMode.OVR; + } else if (command_line[i].equals("--use_depth_32")) { + use_32_bits = true; + } else if (command_line[i].equals("--debug_opengl")) { + use_debug_opengl = true; + } else if (command_line[i].equals("--use_immersive")) { + use_immersive = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar + View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + UiChangeListener(); } + } else if (command_line[i].equals("--use_apk_expansion")) { + use_apk_expansion = true; + } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) { + main_pack_md5 = command_line[i + 1]; + i++; + } else if (has_extra && command_line[i].equals("--apk_expansion_key")) { + main_pack_key = command_line[i + 1]; + SharedPreferences prefs = activity.getSharedPreferences("app_data_keys", + MODE_PRIVATE); + Editor editor = prefs.edit(); + editor.putString("store_public_key", main_pack_key); + + editor.apply(); + i++; + } else if (command_line[i].trim().length() != 0) { + new_args.add(command_line[i]); } + } - if (new_args.isEmpty()) { - command_line = null; - } else { - - command_line = new_args.toArray(new String[new_args.size()]); + if (new_args.isEmpty()) { + command_line = null; + } else { + command_line = new_args.toArray(new String[new_args.size()]); + } + if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { + //check that environment is ok! + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + //show popup and die } - if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { - //check that environment is ok! - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - //show popup and die - } - - // Build the full path to the app's expansion files - try { - expansion_pack_path = Helpers.getSaveFilePath(getApplicationContext()); - expansion_pack_path += "/main." + getPackageManager().getPackageInfo(getPackageName(), 0).versionCode + "." + this.getPackageName() + ".obb"; - } catch (Exception e) { - e.printStackTrace(); - } - File f = new File(expansion_pack_path); + // Build the full path to the app's expansion files + try { + expansion_pack_path = Helpers.getSaveFilePath(getContext()); + expansion_pack_path += "/main." + activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode + "." + activity.getPackageName() + ".obb"; + } catch (Exception e) { + e.printStackTrace(); + } - boolean pack_valid = true; + File f = new File(expansion_pack_path); - if (!f.exists()) { + boolean pack_valid = true; - pack_valid = false; + if (!f.exists()) { + pack_valid = false; - } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) { - pack_valid = false; - try { - f.delete(); - } catch (Exception e) { - } + } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) { + pack_valid = false; + try { + f.delete(); + } catch (Exception e) { } + } - if (!pack_valid) { - - Intent notifierIntent = new Intent(this, this.getClass()); - notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_CLEAR_TOP); + if (!pack_valid) { + Intent notifierIntent = new Intent(activity, activity.getClass()); + notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, - notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, + notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); - int startResult; - try { - startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( - getApplicationContext(), - pendingIntent, + int startResult; + try { + startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( + getContext(), + pendingIntent, + GodotDownloaderService.class); + + if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { + // This is where you do set up to display the download + // progress (next step) + mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, GodotDownloaderService.class); - if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { - // This is where you do set up to display the download - // progress (next step) - mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, - GodotDownloaderService.class); - - setContentView(R.layout.downloading_expansion); - mPB = (ProgressBar)findViewById(R.id.progressBar); - mStatusText = (TextView)findViewById(R.id.statusText); - mProgressFraction = (TextView)findViewById(R.id.progressAsFraction); - mProgressPercent = (TextView)findViewById(R.id.progressAsPercentage); - mAverageSpeed = (TextView)findViewById(R.id.progressAverageSpeed); - mTimeRemaining = (TextView)findViewById(R.id.progressTimeRemaining); - mDashboard = findViewById(R.id.downloaderDashboard); - mCellMessage = findViewById(R.id.approveCellular); - mPauseButton = (Button)findViewById(R.id.pauseButton); - mWiFiSettingsButton = (Button)findViewById(R.id.wifiSettingsButton); - - return; - } - } catch (NameNotFoundException e) { - // TODO Auto-generated catch block + View downloadingExpansionView = + inflater.inflate(R.layout.downloading_expansion, container, false); + mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar); + mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText); + mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction); + mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage); + mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed); + mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining); + mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard); + mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular); + mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton); + mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton); + + return downloadingExpansionView; } + } catch (NameNotFoundException e) { + // TODO Auto-generated catch block } } } - mCurrentIntent = getIntent(); + mCurrentIntent = activity.getIntent(); initializeGodot(); + return containerLayout; } @Override - protected void onDestroy() { - + public void onDestroy() { for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onMainDestroy(); } - GodotLib.ondestroy(this); + GodotLib.ondestroy(); super.onDestroy(); @@ -614,13 +619,13 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } @Override - protected void onPause() { + public void onPause() { super.onPause(); activityResumed = false; if (!godot_initialized) { if (null != mDownloaderClientStub) { - mDownloaderClientStub.disconnect(this); + mDownloaderClientStub.disconnect(getActivity()); } return; } @@ -634,7 +639,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public String getClipboard() { - String copiedText = ""; if (mClipboard.getPrimaryClip() != null) { @@ -646,18 +650,17 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public void setClipboard(String p_text) { - ClipData clip = ClipData.newPlainText("myLabel", p_text); mClipboard.setPrimaryClip(clip); } @Override - protected void onResume() { + public void onResume() { super.onResume(); activityResumed = true; if (!godot_initialized) { if (null != mDownloaderClientStub) { - mDownloaderClientStub.connect(this); + mDownloaderClientStub.connect(getActivity()); } return; } @@ -670,7 +673,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); if (use_immersive && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ - Window window = getWindow(); + Window window = getActivity().getWindow(); window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | @@ -686,7 +689,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public void UiChangeListener() { - final View decorView = getWindow().getDecorView(); + final View decorView = getActivity().getWindow().getDecorView(); decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { @@ -707,7 +710,8 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override public void onSensorChanged(SensorEvent event) { - Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); + Display display = + ((WindowManager)getActivity().getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); int displayRotation = display.getRotation(); float[] adjustedValues = new float[3]; @@ -756,9 +760,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe /* @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) { - System.out.printf("** BACK REQUEST!\n"); GodotLib.quit(); @@ -770,7 +772,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } */ - @Override public void onBackPressed() { boolean shouldQuit = true; @@ -801,14 +802,18 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } } + public final void runOnUiThread(@NonNull Runnable action) { + if (getActivity() != null) { + getActivity().runOnUiThread(action); + } + } + private void forceQuit() { System.exit(0); } private boolean obbIsCorrupted(String f, String main_pack_md5) { - try { - InputStream fis = new FileInputStream(f); // Create MD5 Hash @@ -848,76 +853,17 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } } - public boolean gotTouchEvent(final MotionEvent event) { - - final int evcount = event.getPointerCount(); - if (evcount == 0) - return true; - - if (mRenderView != null) { - final int[] arr = new int[event.getPointerCount() * 3]; - - for (int i = 0; i < event.getPointerCount(); i++) { - - arr[i * 3 + 0] = (int)event.getPointerId(i); - arr[i * 3 + 1] = (int)event.getX(i); - arr[i * 3 + 2] = (int)event.getY(i); - } - final int pointer_idx = event.getPointerId(event.getActionIndex()); - - //System.out.printf("gaction: %d\n",event.getAction()); - final int action = event.getAction() & MotionEvent.ACTION_MASK; - mRenderView.queueOnRenderThread(new Runnable() { - @Override - public void run() { - switch (action) { - case MotionEvent.ACTION_DOWN: { - GodotLib.touch(0, 0, evcount, arr); - //System.out.printf("action down at: %f,%f\n", event.getX(),event.getY()); - } break; - case MotionEvent.ACTION_MOVE: { - GodotLib.touch(1, 0, evcount, arr); - /* - for(int i=0;i<event.getPointerCount();i++) { - System.out.printf("%d - moved to: %f,%f\n",i, event.getX(i),event.getY(i)); - } - */ - } break; - case MotionEvent.ACTION_POINTER_UP: { - GodotLib.touch(4, pointer_idx, evcount, arr); - //System.out.printf("%d - s.up at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); - } break; - case MotionEvent.ACTION_POINTER_DOWN: { - GodotLib.touch(3, pointer_idx, evcount, arr); - //System.out.printf("%d - s.down at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); - } break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - GodotLib.touch(2, 0, evcount, arr); - /* - for(int i=0;i<event.getPointerCount();i++) { - System.out.printf("%d - up! %f,%f\n",i, event.getX(i),event.getY(i)); - } - */ - } break; - } - } - }); - } - return true; - } - - @Override public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) { String s = event.getCharacters(); if (s == null || s.length() == 0) - return super.onKeyMultiple(inKeyCode, repeatCount, event); + return false; final char[] cc = s.toCharArray(); int cnt = 0; for (int i = cc.length; --i >= 0; cnt += cc[i] != 0 ? 1 : 0) ; - if (cnt == 0) return super.onKeyMultiple(inKeyCode, repeatCount, event); + if (cnt == 0) + return false; mRenderView.queueOnRenderThread(new Runnable() { // This method will be called on the rendering thread: public void run() { @@ -935,15 +881,15 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public boolean requestPermission(String p_name) { - return PermissionsUtil.requestPermission(p_name, this); + return PermissionsUtil.requestPermission(p_name, getActivity()); } public boolean requestPermissions() { - return PermissionsUtil.requestManifestPermissions(this); + return PermissionsUtil.requestManifestPermissions(getActivity()); } public String[] getGrantedPermissions() { - return PermissionsUtil.getGrantedPermissions(this); + return PermissionsUtil.getGrantedPermissions(getActivity()); } /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java index 1fb242d0bc..a3dae15980 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java @@ -35,6 +35,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; import android.util.Log; + import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; /** @@ -45,7 +46,6 @@ import com.google.android.vending.expansion.downloader.DownloaderClientMarshalle * <receiver android:name=".GodotDownloaderAlarmReceiver"/> */ public class GodotDownloaderAlarmReceiver extends BroadcastReceiver { - @Override public void onReceive(Context context, Intent intent) { Log.d("GODOT", "Alarma recivida"); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java index 7e74e8a80d..434da95bc0 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java @@ -33,6 +33,7 @@ package org.godotengine.godot; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; + import com.google.android.vending.expansion.downloader.impl.DownloaderService; /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 9be93243b8..d731e080c4 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -29,13 +29,6 @@ /*************************************************************************/ package org.godotengine.godot; -import android.annotation.SuppressLint; -import android.graphics.PixelFormat; -import android.opengl.GLSurfaceView; -import android.view.GestureDetector; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.SurfaceView; import org.godotengine.godot.input.GodotGestureHandler; import org.godotengine.godot.input.GodotInputHandler; import org.godotengine.godot.utils.GLUtils; @@ -47,6 +40,15 @@ import org.godotengine.godot.xr.regular.RegularConfigChooser; import org.godotengine.godot.xr.regular.RegularContextFactory; import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.PixelFormat; +import android.opengl.GLSurfaceView; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SurfaceView; + /** * A simple GLSurfaceView sub-class that demonstrate how to perform * OpenGL ES 2.0 rendering into a GL Surface. Note the following important @@ -66,20 +68,20 @@ import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. */ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { - - private final Godot activity; + private final Godot godot; private final GodotInputHandler inputHandler; private final GestureDetector detector; private final GodotRenderer godotRenderer; - public GodotGLRenderView(Godot activity, XRMode xrMode, boolean p_use_32_bits, boolean p_use_debug_opengl) { - super(activity); + public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_32_bits, + boolean p_use_debug_opengl) { + super(context); GLUtils.use_32 = p_use_32_bits; GLUtils.use_debug_opengl = p_use_debug_opengl; - this.activity = activity; + this.godot = godot; this.inputHandler = new GodotInputHandler(this); - this.detector = new GestureDetector(activity, new GodotGestureHandler(this)); + this.detector = new GestureDetector(context, new GodotGestureHandler(this)); this.godotRenderer = new GodotRenderer(); init(xrMode, false, 16, 0); } @@ -111,7 +113,12 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView @Override public void onBackPressed() { - activity.onBackPressed(); + godot.onBackPressed(); + } + + @Override + public GodotInputHandler getInputHandler() { + return inputHandler; } @SuppressLint("ClickableViewAccessibility") @@ -119,7 +126,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); this.detector.onTouchEvent(event); - return activity.gotTouchEvent(event); + return inputHandler.onTouchEvent(event); } @Override @@ -138,11 +145,9 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView } private void init(XRMode xrMode, boolean translucent, int depth, int stencil) { - setPreserveEGLContextOnPause(true); setFocusableInTouchMode(true); switch (xrMode) { - case OVR: // Replace the default egl config chooser. setEGLConfigChooser(new OvrConfigChooser()); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 016a3a8d18..894009e30f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -29,28 +29,34 @@ /*************************************************************************/ package org.godotengine.godot; + +import org.godotengine.godot.input.*; + +import android.app.Activity; import android.content.*; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.AssetManager; +import android.graphics.Point; import android.media.*; import android.net.Uri; import android.os.*; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.WindowInsets; + import java.io.IOException; import java.io.InputStream; import java.util.Locale; -import org.godotengine.godot.input.*; -//android.os.Build // Wrapper for native library public class GodotIO { - AssetManager am; - Godot activity; + final Activity activity; GodotEditText edit; final int SCREEN_LANDSCAPE = 0; @@ -68,7 +74,6 @@ public class GodotIO { public int last_file_id = 1; class AssetData { - public boolean eof = false; public String path; public InputStream is; @@ -79,7 +84,6 @@ public class GodotIO { SparseArray<AssetData> streams; public int file_open(String path, boolean write) { - //System.out.printf("file_open: Attempt to Open %s\n",path); //Log.v("MyApp", "TRYING TO OPEN FILE: " + path); @@ -92,7 +96,6 @@ public class GodotIO { ad.is = am.open(path); } catch (Exception e) { - //System.out.printf("Exception on file_open: %s\n",path); return -1; } @@ -100,7 +103,6 @@ public class GodotIO { try { ad.len = ad.is.available(); } catch (Exception e) { - System.out.printf("Exception availabling on file_open: %s\n", path); return -1; } @@ -113,7 +115,6 @@ public class GodotIO { return last_file_id; } public int file_get_size(int id) { - if (streams.get(id) == null) { System.out.printf("file_get_size: Invalid file id: %d\n", id); return -1; @@ -122,7 +123,6 @@ public class GodotIO { return streams.get(id).len; } public void file_seek(int id, int bytes) { - if (streams.get(id) == null) { System.out.printf("file_get_size: Invalid file id: %d\n", id); return; @@ -135,7 +135,6 @@ public class GodotIO { bytes = 0; try { - if (bytes > (int)ad.pos) { int todo = bytes - (int)ad.pos; while (todo > 0) { @@ -143,7 +142,6 @@ public class GodotIO { } ad.pos = bytes; } else if (bytes < (int)ad.pos) { - ad.is = am.open(ad.path); ad.pos = bytes; @@ -155,14 +153,12 @@ public class GodotIO { ad.eof = false; } catch (IOException e) { - System.out.printf("Exception on file_seek: %s\n", e); return; } } public int file_tell(int id) { - if (streams.get(id) == null) { System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id); return 0; @@ -172,7 +168,6 @@ public class GodotIO { return ad.pos; } public boolean file_eof(int id) { - if (streams.get(id) == null) { System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id); return false; @@ -183,7 +178,6 @@ public class GodotIO { } public byte[] file_read(int id, int bytes) { - if (streams.get(id) == null) { System.out.printf("file_read: Can't read invalid file id: %d\n", id); return new byte[0]; @@ -192,13 +186,11 @@ public class GodotIO { AssetData ad = streams.get(id); if (ad.pos + bytes > ad.len) { - bytes = ad.len - ad.pos; ad.eof = true; } if (bytes == 0) { - return new byte[0]; } @@ -207,7 +199,6 @@ public class GodotIO { try { r = ad.is.read(buf1); } catch (IOException e) { - System.out.printf("Exception on file_read: %s\n", e); return new byte[bytes]; } @@ -219,19 +210,16 @@ public class GodotIO { ad.pos += r; if (r < bytes) { - byte[] buf2 = new byte[r]; for (int i = 0; i < r; i++) buf2[i] = buf1[i]; return buf2; } else { - return buf1; } } public void file_close(int id) { - if (streams.get(id) == null) { System.out.printf("file_close: Can't close invalid file id: %d\n", id); return; @@ -245,7 +233,6 @@ public class GodotIO { ///////////////////////// class AssetDir { - public String[] files; public int current; public String path; @@ -256,7 +243,6 @@ public class GodotIO { SparseArray<AssetDir> dirs; public int dir_open(String path) { - AssetDir ad = new AssetDir(); ad.current = 0; ad.path = path; @@ -269,7 +255,6 @@ public class GodotIO { return -1; } } catch (IOException e) { - System.out.printf("Exception on dir_open: %s\n", e); return -1; } @@ -308,7 +293,6 @@ public class GodotIO { } public String dir_next(int id) { - if (dirs.get(id) == null) { System.out.printf("dir_next: invalid dir id: %d\n", id); return ""; @@ -327,7 +311,6 @@ public class GodotIO { } public void dir_close(int id) { - if (dirs.get(id) == null) { System.out.printf("dir_close: invalid dir id: %d\n", id); return; @@ -336,8 +319,7 @@ public class GodotIO { dirs.remove(id); } - GodotIO(Godot p_activity) { - + GodotIO(Activity p_activity) { am = p_activity.getAssets(); activity = p_activity; //streams = new HashMap<Integer, AssetData>(); @@ -428,7 +410,6 @@ public class GodotIO { } public void audioPause(boolean p_pause) { - if (p_pause) mAudioTrack.pause(); else @@ -440,7 +421,6 @@ public class GodotIO { ///////////////////////// public int openURI(String p_uri) { - try { Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri); String path = p_uri; @@ -449,7 +429,6 @@ public class GodotIO { //absolute path to filesystem, prepend file:// path = "file://" + path; if (p_uri.endsWith(".png") || p_uri.endsWith(".jpg") || p_uri.endsWith(".gif") || p_uri.endsWith(".webp")) { - type = "image/*"; } } @@ -465,18 +444,15 @@ public class GodotIO { activity.startActivity(intent); return 0; } catch (ActivityNotFoundException e) { - return 1; } } public String getDataDir() { - return activity.getFilesDir().getAbsolutePath(); } public String getLocale() { - return Locale.getDefault().toString(); } @@ -489,9 +465,31 @@ public class GodotIO { return (int)(metrics.density * 160f); } - public void showKeyboard(String p_existing_text, int p_max_input_length) { + public int[] screenGetUsableRect() { + DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); + Display display = activity.getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getRealSize(size); + + int result[] = { 0, 0, size.x, size.y }; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + int insetLeft = cutout.getSafeInsetLeft(); + int insetTop = cutout.getSafeInsetTop(); + result[0] = insetLeft; + result[1] = insetTop; + result[2] -= insetLeft + cutout.getSafeInsetRight(); + result[3] -= insetTop + cutout.getSafeInsetBottom(); + } + } + return result; + } + + public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { if (edit != null) - edit.showKeyboard(p_existing_text, p_max_input_length); + edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end); //InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE); //inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); @@ -503,9 +501,7 @@ public class GodotIO { }; public void setScreenOrientation(int p_orientation) { - switch (p_orientation) { - case SCREEN_LANDSCAPE: { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } break; @@ -519,13 +515,13 @@ public class GodotIO { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT); } break; case SCREEN_SENSOR_LANDSCAPE: { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE); } break; case SCREEN_SENSOR_PORTRAIT: { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT); } break; case SCREEN_SENSOR: { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER); } break; } } @@ -548,7 +544,6 @@ public class GodotIO { public static final int SYSTEM_DIR_RINGTONES = 7; public String getSystemDir(int idx) { - String what = ""; switch (idx) { case SYSTEM_DIR_DESKTOP: { @@ -593,7 +588,6 @@ public class GodotIO { public static String unique_id = ""; public String getUniqueID() { - return unique_id; } } 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 71fe822233..6ccbe91e60 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -33,6 +33,7 @@ package org.godotengine.godot; import android.app.Activity; import android.hardware.SensorEvent; import android.view.Surface; + import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; @@ -40,7 +41,6 @@ import javax.microedition.khronos.opengles.GL10; * Wrapper for native library */ public class GodotLib { - public static GodotIO io; static { @@ -50,13 +50,13 @@ public class GodotLib { /** * Invoked on the main thread to initialize Godot native layer. */ - public static native void initialize(Godot p_instance, Object p_asset_manager, boolean use_apk_expansion); + public static native void initialize(Activity activity, Godot p_instance, Object p_asset_manager, boolean use_apk_expansion); /** * Invoked on the main thread to clean up Godot native layer. - * @see Activity#onDestroy() + * @see androidx.fragment.app.Fragment#onDestroy() */ - public static native void ondestroy(Godot p_instance); + public static native void ondestroy(); /** * Invoked on the GL thread to complete setup for the Godot native layer logic. @@ -66,11 +66,12 @@ public class GodotLib { /** * Invoked on the GL thread when the underlying Android surface has changed size. - * @param width - * @param height + * @param p_surface + * @param p_width + * @param p_height * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int) */ - public static native void resize(int width, int height); + public static native void resize(Surface p_surface, int p_width, int p_height); /** * Invoked on the render thread when the underlying Android surface is created or recreated. @@ -93,17 +94,19 @@ public class GodotLib { /** * Forward touch events from the main thread to the GL thread. */ - public static native void touch(int what, int pointer, int howmany, int[] arr); + public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions); + public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions, int buttonsMask); + public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions, int buttonsMask, float verticalFactor, float horizontalFactor); /** * Forward hover events from the main thread to the GL thread. */ - public static native void hover(int type, int x, int y); + public static native void hover(int type, float x, float y); /** * Forward double_tap events from the main thread to the GL thread. */ - public static native void doubletap(int x, int y); + public static native void doubleTap(int buttonMask, int x, int y); /** * Forward scroll events from the main thread to the GL thread. @@ -160,14 +163,14 @@ public class GodotLib { public static native void joyconnectionchanged(int p_device, boolean p_connected, String p_name); /** - * Invoked when the Android activity resumes. - * @see Activity#onResume() + * Invoked when the Android app resumes. + * @see androidx.fragment.app.Fragment#onResume() */ public static native void focusin(); /** - * Invoked when the Android activity pauses. - * @see Activity#onPause() + * Invoked when the Android app pauses. + * @see androidx.fragment.app.Fragment#onPause() */ public static native void focusout(); @@ -189,7 +192,7 @@ public class GodotLib { * @param p_method Name of the method to invoke * @param p_params Parameters to use for method invocation */ - public static native void callobject(int p_id, String p_method, Object[] p_params); + public static native void callobject(long p_id, String p_method, Object[] p_params); /** * Invoke method |p_method| on the Godot object specified by |p_id| during idle time. @@ -197,7 +200,7 @@ public class GodotLib { * @param p_method Name of the method to invoke * @param p_params Parameters to use for method invocation */ - public static native void calldeferred(int p_id, String p_method, Object[] p_params); + public static native void calldeferred(long p_id, String p_method, Object[] p_params); /** * Forward the results from a permission request. diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index 170c433c9c..68b8a16641 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -30,10 +30,11 @@ package org.godotengine.godot; +import org.godotengine.godot.input.GodotInputHandler; + import android.view.SurfaceView; public interface GodotRenderView { - abstract public SurfaceView getView(); abstract public void initInputDevices(); @@ -44,4 +45,6 @@ public interface GodotRenderView { abstract public void onActivityResumed(); abstract public void onBackPressed(); + + abstract public GodotInputHandler getInputHandler(); } 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 3e5bb4a4c9..64395f7d1e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java @@ -30,19 +30,20 @@ package org.godotengine.godot; +import org.godotengine.godot.plugin.GodotPlugin; +import org.godotengine.godot.plugin.GodotPluginRegistry; +import org.godotengine.godot.utils.GLUtils; + 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; /** * Godot's renderer implementation. */ class GodotRenderer implements GLSurfaceView.Renderer { - private final GodotPluginRegistry pluginRegistry; private boolean activityJustResumed = false; @@ -63,7 +64,7 @@ class GodotRenderer implements GLSurfaceView.Renderer { } public void onSurfaceChanged(GL10 gl, int width, int height) { - GodotLib.resize(width, height); + GodotLib.resize(null, width, height); for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onGLSurfaceChanged(gl, width, height); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index 30197d5729..6cd5ca7b4e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -30,29 +30,31 @@ package org.godotengine.godot; +import org.godotengine.godot.input.GodotGestureHandler; +import org.godotengine.godot.input.GodotInputHandler; +import org.godotengine.godot.vulkan.VkRenderer; +import org.godotengine.godot.vulkan.VkSurfaceView; + import android.annotation.SuppressLint; +import android.content.Context; import android.view.GestureDetector; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceView; -import org.godotengine.godot.input.GodotGestureHandler; -import org.godotengine.godot.input.GodotInputHandler; -import org.godotengine.godot.vulkan.VkRenderer; -import org.godotengine.godot.vulkan.VkSurfaceView; public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { - - private final Godot mActivity; + private final Godot godot; private final GodotInputHandler mInputHandler; private final GestureDetector mGestureDetector; private final VkRenderer mRenderer; - public GodotVulkanRenderView(Godot activity) { - super(activity); + public GodotVulkanRenderView(Context context, Godot godot) { + super(context); - mActivity = activity; + this.godot = godot; mInputHandler = new GodotInputHandler(this); - mGestureDetector = new GestureDetector(mActivity, new GodotGestureHandler(this)); + mGestureDetector = new GestureDetector(context, new GodotGestureHandler(this)); mRenderer = new VkRenderer(); setFocusableInTouchMode(true); @@ -86,7 +88,12 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV @Override public void onBackPressed() { - mActivity.onBackPressed(); + godot.onBackPressed(); + } + + @Override + public GodotInputHandler getInputHandler() { + return mInputHandler; } @SuppressLint("ClickableViewAccessibility") @@ -94,22 +101,22 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); mGestureDetector.onTouchEvent(event); - return mActivity.gotTouchEvent(event); + return mInputHandler.onTouchEvent(event); } @Override public boolean onKeyUp(final int keyCode, KeyEvent event) { - return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); + return mInputHandler.onKeyUp(keyCode, event); } @Override public boolean onKeyDown(final int keyCode, KeyEvent event) { - return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); + return mInputHandler.onKeyDown(keyCode, event); } @Override public boolean onGenericMotionEvent(MotionEvent event) { - return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); + return mInputHandler.onGenericMotionEvent(event); } @Override diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java index 92bb118e44..c95339c583 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java @@ -29,17 +29,21 @@ /*************************************************************************/ package org.godotengine.godot.input; + +import org.godotengine.godot.*; + import android.content.Context; import android.os.Handler; import android.os.Message; import android.text.InputFilter; +import android.text.InputType; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; + import java.lang.ref.WeakReference; -import org.godotengine.godot.*; public class GodotEditText extends EditText { // =========================================================== @@ -55,6 +59,8 @@ public class GodotEditText extends EditText { private GodotTextInputWrapper mInputWrapper; private EditHandler sHandler = new EditHandler(this); private String mOriginText; + private int mMaxInputLength = Integer.MAX_VALUE; + private boolean mMultiline = false; private static class EditHandler extends Handler { private final WeakReference<GodotEditText> mEdit; @@ -91,7 +97,11 @@ public class GodotEditText extends EditText { protected void initView() { setPadding(0, 0, 0, 0); - setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE); + } + + public boolean isMultiline() { + return mMultiline; } private void handleMessage(final Message msg) { @@ -101,11 +111,24 @@ public class GodotEditText extends EditText { String text = edit.mOriginText; if (edit.requestFocus()) { edit.removeTextChangedListener(edit.mInputWrapper); + setMaxInputLength(edit); edit.setText(""); edit.append(text); + if (msg.arg2 != -1) { + edit.setSelection(msg.arg1, msg.arg2); + edit.mInputWrapper.setSelection(true); + } else { + edit.mInputWrapper.setSelection(false); + } + + int inputType = InputType.TYPE_CLASS_TEXT; + if (edit.isMultiline()) { + inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; + } + edit.setInputType(inputType); + edit.mInputWrapper.setOriginText(text); edit.addTextChangedListener(edit.mInputWrapper); - setMaxInputLength(edit, msg.arg1); final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(edit, 0); } @@ -122,14 +145,10 @@ public class GodotEditText extends EditText { } } - private void setMaxInputLength(EditText p_edit_text, int p_max_input_length) { - if (p_max_input_length > 0) { - InputFilter[] filters = new InputFilter[1]; - filters[0] = new InputFilter.LengthFilter(p_max_input_length); - p_edit_text.setFilters(filters); - } else { - p_edit_text.setFilters(new InputFilter[] {}); - } + private void setMaxInputLength(EditText p_edit_text) { + InputFilter[] filters = new InputFilter[1]; + filters[0] = new InputFilter.LengthFilter(this.mMaxInputLength); + p_edit_text.setFilters(filters); } // =========================================================== @@ -148,26 +167,60 @@ public class GodotEditText extends EditText { // =========================================================== @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { - super.onKeyDown(keyCode, keyEvent); - - /* Let GlSurfaceView get focus if back key is input. */ + /* Let SurfaceView get focus if back key is input. */ if (keyCode == KeyEvent.KEYCODE_BACK) { mRenderView.getView().requestFocus(); } - return true; + // pass event to godot in special cases + if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent)) { + return true; + } else { + return super.onKeyDown(keyCode, keyEvent); + } + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { + if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent)) { + return true; + } else { + return super.onKeyUp(keyCode, keyEvent); + } + } + + private boolean needHandlingInGodot(int keyCode, KeyEvent keyEvent) { + boolean isArrowKey = keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || + keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT; + boolean isModifiedKey = keyEvent.isAltPressed() || keyEvent.isCtrlPressed() || keyEvent.isSymPressed() || + keyEvent.isFunctionPressed() || keyEvent.isMetaPressed(); + return isArrowKey || keyCode == KeyEvent.KEYCODE_TAB || KeyEvent.isModifierKey(keyCode) || + isModifiedKey; } // =========================================================== // Methods // =========================================================== - public void showKeyboard(String p_existing_text, int p_max_input_length) { - mOriginText = p_existing_text; + public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { + int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length; + if (p_cursor_start == -1) { // cursor position not given + this.mOriginText = p_existing_text; + this.mMaxInputLength = maxInputLength; + } else if (p_cursor_end == -1) { // not text selection + this.mOriginText = p_existing_text.substring(0, p_cursor_start); + this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_start); + } else { + this.mOriginText = p_existing_text.substring(0, p_cursor_end); + this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end); + } + + this.mMultiline = p_multiline; final Message msg = new Message(); msg.what = HANDLER_OPEN_IME_KEYBOARD; msg.obj = this; - msg.arg1 = p_max_input_length; + msg.arg1 = p_cursor_start; + msg.arg2 = p_cursor_end; sHandler.sendMessage(msg); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java index b1e0f66373..fb151fa504 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java @@ -30,18 +30,17 @@ package org.godotengine.godot.input; -import android.util.Log; -import android.view.GestureDetector; -import android.view.MotionEvent; import org.godotengine.godot.GodotLib; import org.godotengine.godot.GodotRenderView; +import android.view.GestureDetector; +import android.view.MotionEvent; + /** * Handles gesture input related events for the {@link GodotRenderView} view. * https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener */ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener { - private final GodotRenderView mRenderView; public GodotGestureHandler(GodotRenderView godotView) { @@ -75,10 +74,11 @@ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener //Log.i("GodotGesture", "onDoubleTap"); final int x = Math.round(event.getX()); final int y = Math.round(event.getY()); + final int buttonMask = event.getButtonState(); queueEvent(new Runnable() { @Override public void run() { - GodotLib.doubletap(x, y); + GodotLib.doubleTap(buttonMask, x, y); } }); return true; 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 0e4fc65119..f3e985f944 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 @@ -32,24 +32,26 @@ package org.godotengine.godot.input; import static org.godotengine.godot.utils.GLUtils.DEBUG; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.GodotRenderView; +import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; + +import android.os.Build; import android.util.Log; import android.view.InputDevice; import android.view.InputDevice.MotionRange; import android.view.KeyEvent; import android.view.MotionEvent; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.GodotRenderView; -import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; /** * Handles input related events for the {@link GodotRenderView} view. */ public class GodotInputHandler implements InputDeviceListener { - private final ArrayList<Joystick> mJoysticksDevices = new ArrayList<Joystick>(); private final GodotRenderView mRenderView; @@ -84,7 +86,6 @@ public class GodotInputHandler implements InputDeviceListener { int source = event.getSource(); if (isKeyEvent_GameDevice(source)) { - final int button = getGodotButton(keyCode); final int device_id = findJoystickDevice(event.getDeviceId()); @@ -127,7 +128,6 @@ public class GodotInputHandler implements InputDeviceListener { //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD))); if (isKeyEvent_GameDevice(source)) { - if (event.getRepeatCount() > 0) // ignore key echo return true; @@ -157,9 +157,55 @@ public class GodotInputHandler implements InputDeviceListener { return true; } + public boolean onTouchEvent(final MotionEvent event) { + // Mouse drag (mouse pressed and move) doesn't fire onGenericMotionEvent so this is needed + if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (event.getAction() != MotionEvent.ACTION_MOVE) { + // we return true because every time a mouse event is fired, the event is already handled + // in onGenericMotionEvent, so by touch event we can say that the event is also handled + return true; + } + return handleMouseEvent(event); + } + + final int evcount = event.getPointerCount(); + if (evcount == 0) + return true; + + if (mRenderView != null) { + final float[] arr = new float[event.getPointerCount() * 3]; // pointerId1, x1, y1, pointerId2, etc... + + for (int i = 0; i < event.getPointerCount(); i++) { + arr[i * 3 + 0] = event.getPointerId(i); + arr[i * 3 + 1] = event.getX(i); + arr[i * 3 + 2] = event.getY(i); + } + final int action = event.getActionMasked(); + + mRenderView.queueOnRenderThread(new Runnable() { + @Override + public void run() { + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_MOVE: { + GodotLib.touch(event.getSource(), action, 0, evcount, arr); + } break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: { + int pointer_idx = event.getPointerId(event.getActionIndex()); + GodotLib.touch(event.getSource(), action, pointer_idx, evcount, arr); + } break; + } + } + }); + } + return true; + } + public boolean onGenericMotionEvent(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) { - final int device_id = findJoystickDevice(event.getDeviceId()); // Check if the device exists @@ -191,8 +237,8 @@ public class GodotInputHandler implements InputDeviceListener { return true; } } else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) { - final int x = Math.round(event.getX()); - final int y = Math.round(event.getY()); + final float x = event.getX(); + final float y = event.getY(); final int type = event.getAction(); queueEvent(new Runnable() { @Override @@ -201,6 +247,10 @@ public class GodotInputHandler implements InputDeviceListener { } }); return true; + } else if ((event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return handleMouseEvent(event); + } } return false; @@ -368,4 +418,53 @@ public class GodotInputHandler implements InputDeviceListener { return -1; } + + private boolean handleMouseEvent(final MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_HOVER_EXIT: { + final float x = event.getX(); + final float y = event.getY(); + final int type = event.getAction(); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.hover(type, x, y); + } + }); + return true; + } + case MotionEvent.ACTION_BUTTON_PRESS: + case MotionEvent.ACTION_BUTTON_RELEASE: + case MotionEvent.ACTION_MOVE: { + final float x = event.getX(); + final float y = event.getY(); + final int buttonsMask = event.getButtonState(); + final int action = event.getAction(); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask); + } + }); + return true; + } + case MotionEvent.ACTION_SCROLL: { + final float x = event.getX(); + final float y = event.getY(); + final int buttonsMask = event.getButtonState(); + final int action = event.getAction(); + final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask, verticalFactor, horizontalFactor); + } + }); + } + } + return false; + } } 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 e12ff266bf..4dd1054738 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 @@ -29,6 +29,9 @@ /*************************************************************************/ package org.godotengine.godot.input; + +import org.godotengine.godot.*; + import android.content.Context; import android.text.Editable; import android.text.TextWatcher; @@ -37,7 +40,6 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import org.godotengine.godot.*; public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListener { // =========================================================== @@ -51,6 +53,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene private final GodotRenderView mRenderView; private final GodotEditText mEdit; private String mOriginText; + private boolean mHasSelection; // =========================================================== // Constructors @@ -75,6 +78,10 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene mOriginText = originText; } + public void setSelection(boolean selection) { + mHasSelection = selection; + } + // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @@ -93,6 +100,11 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene for (int i = 0; i < count; ++i) { GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true); GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false); + + if (mHasSelection) { + mHasSelection = false; + break; + } } } }); @@ -111,7 +123,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene public void run() { for (int i = 0; i < count; ++i) { int key = newChars[i]; - if (key == '\n') { + if ((key == '\n') && !mEdit.isMultiline()) { // Return keys are handled through action events continue; } @@ -139,7 +151,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene }); } - if (pActionID == EditorInfo.IME_NULL) { + if (pActionID == EditorInfo.IME_ACTION_DONE) { // Enter key has been pressed GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true); GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false); diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java index 4042c42e9d..62810ad3a4 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java @@ -120,7 +120,6 @@ public interface InputManagerCompat { * Use this to construct a compatible InputManager. */ public static class Factory { - /** * Constructs and returns a compatible InputManger * diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java index e4bafa7ff9..61828dccae 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java @@ -23,12 +23,12 @@ import android.os.Build; import android.os.Handler; import android.view.InputDevice; import android.view.MotionEvent; + import java.util.HashMap; import java.util.Map; @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class InputManagerV16 implements InputManagerCompat { - private final InputManager mInputManager; private final Map<InputManagerCompat.InputDeviceListener, V16InputDeviceListener> mListeners; diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java index 0c1bdb32aa..1f3fe1e527 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java @@ -31,6 +31,7 @@ package org.godotengine.godot.input; import android.view.InputDevice.MotionRange; + import java.util.ArrayList; /** 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 index a051164d15..93c204935c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -30,24 +30,28 @@ package org.godotengine.godot.plugin; +import org.godotengine.godot.BuildConfig; +import org.godotengine.godot.Godot; + import android.app.Activity; import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; +import android.os.Bundle; import android.util.Log; import android.view.Surface; import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; + import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; -import org.godotengine.godot.BuildConfig; -import org.godotengine.godot.Godot; /** * Base class for the Godot Android plugins. @@ -73,7 +77,6 @@ import org.godotengine.godot.Godot; * 'godot/plugin/v1/[PluginName]/' */ public abstract class GodotPlugin { - private static final String TAG = GodotPlugin.class.getSimpleName(); private final Godot godot; @@ -91,6 +94,14 @@ public abstract class GodotPlugin { } /** + * Provides access to the underlying {@link Activity}. + */ + @Nullable + protected Activity getActivity() { + return godot.getActivity(); + } + + /** * Register the plugin with Godot native code. * * This method is invoked on the render thread. @@ -143,13 +154,14 @@ public abstract class GodotPlugin { * 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. + * The plugin can return a non-null {@link View} layout in order to add it to the Godot view + * hierarchy. * - * @return the view to be included; null if no views should be included. + * @see Activity#onCreate(Bundle) + * @return the plugin's view to be included; null if no views should be included. */ @Nullable - public View onMainCreateView(Activity activity) { + public View onMainCreate(Activity activity) { return null; } 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 index e13a9c15d8..1c2d1a6563 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -30,26 +30,28 @@ package org.godotengine.godot.plugin; +import org.godotengine.godot.Godot; + +import android.app.Activity; 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 androidx.annotation.Nullable; + 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."; @@ -57,7 +59,9 @@ public final class GodotPluginRegistry { /** * 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 final String GODOT_ENABLED_PLUGINS_LABEL = "plugins"; + + private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"; private static GodotPluginRegistry instance; private final ConcurrentHashMap<String, GodotPlugin> registry; @@ -118,22 +122,24 @@ public final class GodotPluginRegistry { private void loadPlugins(Godot godot) { try { - ApplicationInfo appInfo = godot + final Activity activity = godot.getActivity(); + ApplicationInfo appInfo = activity .getPackageManager() - .getApplicationInfo(godot.getPackageName(), PackageManager.GET_META_DATA); + .getApplicationInfo(activity.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. + // which plugins to enable. // 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(","); + String[] enabledPluginsList = enabledPlugins.split(PLUGIN_VALUE_SEPARATOR_REGEX); if (enabledPluginsList.length == 0) { // No plugins to enable. Aborting early. return; @@ -157,6 +163,8 @@ public final class GodotPluginRegistry { continue; } + Log.i(TAG, "Initializing Godot plugin " + pluginName); + // Retrieve the plugin class full name. String pluginHandleClassFullName = metaData.getString(metaDataName); if (!TextUtils.isEmpty(pluginHandleClassFullName)) { @@ -176,6 +184,7 @@ public final class GodotPluginRegistry { "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName()); } registry.put(pluginName, pluginHandle); + Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName()); } catch (ClassNotFoundException e) { Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); } catch (IllegalAccessException e) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java index f907706889..f82c4d3fa0 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* 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 */ @@ -30,15 +30,16 @@ package org.godotengine.godot.plugin; -import android.support.annotation.NonNull; import android.text.TextUtils; + +import androidx.annotation.NonNull; + import java.util.Arrays; /** * Store information about a {@link GodotPlugin}'s signal. */ public final class SignalInfo { - private final String name; private final Class<?>[] paramTypes; private final String[] paramTypesNames; diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java index bc0e565774..acc9c4981b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java @@ -34,7 +34,6 @@ import java.security.MessageDigest; import java.util.Random; public class Crypt { - public static String md5(String input) { try { // Create MD5 Hash diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java index 9d29551f89..82420eda79 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java @@ -31,6 +31,7 @@ package org.godotengine.godot.utils; import android.util.Log; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; @@ -39,7 +40,6 @@ import javax.microedition.khronos.egl.EGLDisplay; * Contains GL utilities methods. */ public class GLUtils { - private static final String TAG = GLUtils.class.getSimpleName(); public static final boolean DEBUG = false; diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java index 011d426c7e..c89118ad55 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java @@ -30,10 +30,10 @@ package org.godotengine.godot.utils; +import android.app.Activity; import android.content.Context; import android.net.wifi.WifiManager; import android.util.Log; -import org.godotengine.godot.Godot; /** * This class handles Android-specific networking functions. @@ -41,11 +41,10 @@ import org.godotengine.godot.Godot; * to receive broadcast and multicast packets. */ public class GodotNetUtils { - /* A single, reference counted, multicast lock, or null if permission CHANGE_WIFI_MULTICAST_STATE is missing */ private WifiManager.MulticastLock multicastLock; - public GodotNetUtils(Godot p_activity) { + public GodotNetUtils(Activity p_activity) { if (PermissionsUtil.hasManifestPermission(p_activity, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) { WifiManager wifi = (WifiManager)p_activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE); multicastLock = wifi.createMulticastLock("GodotMulticastLock"); diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 7cf32b00fe..7104baf86e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -31,20 +31,24 @@ package org.godotengine.godot.utils; import android.Manifest; +import android.app.Activity; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; import android.os.Build; -import android.support.v4.content.ContextCompat; +import android.util.Log; + +import androidx.core.content.ContextCompat; + import java.util.ArrayList; import java.util.List; -import org.godotengine.godot.Godot; /** * This class includes utility functions for Android permissions related operations. * @author Cagdas Caglak <cagdascaglak@gmail.com> */ public final class PermissionsUtil { + private static final String TAG = PermissionsUtil.class.getSimpleName(); static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; static final int REQUEST_CAMERA_PERMISSION = 2; @@ -60,7 +64,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return true/false. "true" if permission was granted otherwise returns "false". */ - public static boolean requestPermission(String name, Godot activity) { + public static boolean requestPermission(String name, Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // Not necessary, asked on install already return true; @@ -88,7 +92,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return true/false. "true" if all permissions were granted otherwise returns "false". */ - public static boolean requestManifestPermissions(Godot activity) { + public static boolean requestManifestPermissions(Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } @@ -113,8 +117,8 @@ public final class PermissionsUtil { dangerousPermissions.add(manifestPermission); } } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return false; + // Skip this permission and continue. + Log.w(TAG, "Unable to identify permission " + manifestPermission, e); } } @@ -133,7 +137,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return granted permissions list */ - public static String[] getGrantedPermissions(Godot activity) { + public static String[] getGrantedPermissions(Activity activity) { String[] manifestPermissions; try { manifestPermissions = getManifestPermissions(activity); @@ -153,8 +157,8 @@ public final class PermissionsUtil { dangerousPermissions.add(manifestPermission); } } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return new String[0]; + // Skip this permission and continue. + Log.w(TAG, "Unable to identify permission " + manifestPermission, e); } } @@ -167,7 +171,7 @@ public final class PermissionsUtil { * @param permission the permession to look for in the manifest file. * @return "true" if the permission is in the manifest file of the activity, "false" otherwise. */ - public static boolean hasManifestPermission(Godot activity, String permission) { + public static boolean hasManifestPermission(Activity activity, String permission) { try { for (String p : getManifestPermissions(activity)) { if (permission.equals(p)) @@ -185,7 +189,7 @@ public final class PermissionsUtil { * @return manifest permissions list * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - private static String[] getManifestPermissions(Godot activity) throws PackageManager.NameNotFoundException { + private static String[] getManifestPermissions(Activity activity) throws PackageManager.NameNotFoundException { PackageManager packageManager = activity.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS); if (packageInfo.requestedPermissions == null) @@ -200,7 +204,7 @@ public final class PermissionsUtil { * @return permission info object * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - private static PermissionInfo getPermissionInfo(Godot activity, String permission) throws PackageManager.NameNotFoundException { + private static PermissionInfo getPermissionInfo(Activity activity, String permission) throws PackageManager.NameNotFoundException { PackageManager packageManager = activity.getPackageManager(); return packageManager.getPermissionInfo(permission, 0); } 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 index 608ad48df9..f93cf0fa38 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt @@ -52,16 +52,13 @@ import org.godotengine.godot.plugin.GodotPluginRegistry * @see [VkSurfaceView.startRenderer] */ internal class VkRenderer { - private val pluginRegistry: GodotPluginRegistry = GodotPluginRegistry.getPluginRegistry() /** * Called when the surface is created and signals the beginning of rendering. */ fun onVkSurfaceCreated(surface: Surface) { - // TODO: properly implement surface re-creation: - // GodotLib.newcontext should be called here once it's done. - //GodotLib.newcontext(surface, false) + GodotLib.newcontext(surface, false) for (plugin in pluginRegistry.getAllPlugins()) { plugin.onVkSurfaceCreated(surface) @@ -72,13 +69,8 @@ internal class VkRenderer { * Called after the surface is created and whenever its size changes. */ fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) { - GodotLib.resize(width, height) - - // TODO: properly implement surface re-creation: - // Update the native renderer instead of restarting the app. - // GodotLib.newcontext should not be called here once it's done. - GodotLib.newcontext(surface, false) - + GodotLib.resize(surface, width, height) + for (plugin in pluginRegistry.getAllPlugins()) { plugin.onVkSurfaceChanged(surface, width, height) } 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 index 6b0e12b21a..e5c7a39bfb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt @@ -50,7 +50,6 @@ import android.view.SurfaceView * </ul> */ open internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback { - companion object { fun checkState(expression: Boolean, errorMessage: Any) { check(expression) { errorMessage.toString() } 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 index 7557c8aa22..fb02e3a69f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt @@ -41,7 +41,6 @@ import kotlin.concurrent.withLock * 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 } @@ -226,5 +225,4 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk threadExiting() } } - } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java index 9209d6ccf2..819bcccdf1 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java @@ -32,6 +32,7 @@ package org.godotengine.godot.xr.ovr; import android.opengl.EGLExt; import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; @@ -40,7 +41,6 @@ import javax.microedition.khronos.egl.EGLDisplay; * EGL config chooser for the Oculus Mobile VR SDK. */ public class OvrConfigChooser implements GLSurfaceView.EGLConfigChooser { - private static final int[] CONFIG_ATTRIBS = { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java index 36f4416df2..2d9b921466 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java @@ -32,6 +32,7 @@ package org.godotengine.godot.xr.ovr; import android.opengl.EGL14; import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; @@ -41,7 +42,6 @@ import javax.microedition.khronos.egl.EGLDisplay; * EGL Context factory for the Oculus mobile VR SDK. */ public class OvrContextFactory implements GLSurfaceView.EGLContextFactory { - private static final int[] CONTEXT_ATTRIBS = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java index b2aa130f37..43c7f0f966 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java @@ -31,6 +31,7 @@ package org.godotengine.godot.xr.ovr; import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; @@ -40,7 +41,6 @@ import javax.microedition.khronos.egl.EGLSurface; * EGL window surface factory for the Oculus mobile VR SDK. */ public class OvrWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory { - private final static int[] SURFACE_ATTRIBS = { EGL10.EGL_WIDTH, 16, EGL10.EGL_HEIGHT, 16, diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java index 8409e37f8f..54672db282 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java @@ -30,17 +30,18 @@ package org.godotengine.godot.xr.regular; +import org.godotengine.godot.utils.GLUtils; + import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; -import org.godotengine.godot.utils.GLUtils; /** * Used to select the egl config for pancake games. */ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { - private static final String TAG = RegularConfigChooser.class.getSimpleName(); private int[] mValue = new int[1]; @@ -72,7 +73,6 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { } public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - /* Get the number of minimally matching EGL configurations */ int[] num_config = new int[1]; @@ -127,7 +127,6 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) { - if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { return mValue[0]; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java index 31cf696195..126f3ad5f5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java @@ -30,14 +30,16 @@ package org.godotengine.godot.xr.regular; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.utils.GLUtils; + import android.opengl.GLSurfaceView; import android.util.Log; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.utils.GLUtils; /** * Factory used to setup the opengl context for pancake games. diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java index 71fcf06020..c83c47bed7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java @@ -30,15 +30,16 @@ package org.godotengine.godot.xr.regular; +import org.godotengine.godot.utils.GLUtils; + import android.util.Log; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; -import org.godotengine.godot.utils.GLUtils; /* Fallback if 32bit View is not supported*/ public class RegularFallbackConfigChooser extends RegularConfigChooser { - private static final String TAG = RegularFallbackConfigChooser.class.getSimpleName(); private RegularConfigChooser fallback; diff --git a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml new file mode 100644 index 0000000000..dc180375d5 --- /dev/null +++ b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest package="org.godotengine.godot" /> diff --git a/platform/android/java/lib/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt index d3bdf6a5f2..34925684da 100644 --- a/platform/android/java/lib/CMakeLists.txt +++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt @@ -1,3 +1,4 @@ +# Non functional cmake build file used to provide Android Studio editor support to the project. cmake_minimum_required(VERSION 3.6) project(godot) diff --git a/platform/android/java/nativeSrcsConfigs/README.md b/platform/android/java/nativeSrcsConfigs/README.md new file mode 100644 index 0000000000..e48505ccda --- /dev/null +++ b/platform/android/java/nativeSrcsConfigs/README.md @@ -0,0 +1,4 @@ +## Native sources configs + +This is a non functional Android library used to provide Android Studio editor support to the Godot project native files. +Nothing else should be added to this library. diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle new file mode 100644 index 0000000000..65b7bb9dc9 --- /dev/null +++ b/platform/android/java/nativeSrcsConfigs/build.gradle @@ -0,0 +1,54 @@ +// Non functional android library used to provide Android Studio editor support to the project. +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion versions.compileSdk + buildToolsVersion versions.buildTools + + defaultConfig { + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk + } + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + + // Should be uncommented for development purpose within Android Studio + // doNotStrip '**/*.so' + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + libraryVariants.all { variant -> + def buildType = variant.buildType.name.capitalize() + + def taskPrefix = "" + if (project.path != ":") { + taskPrefix = project.path + ":" + } + + // Disable the externalNativeBuild* task as it would cause build failures since the cmake build + // files is only setup for editing support. + gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType + } +} + +dependencies {} diff --git a/platform/android/java/plugins/godotpayment/build.gradle b/platform/android/java/plugins/godotpayment/build.gradle deleted file mode 100644 index ffab86e26e..0000000000 --- a/platform/android/java/plugins/godotpayment/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion versions.compileSdk - buildToolsVersion versions.buildTools - useLibrary 'org.apache.http.legacy' - - 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 deleted file mode 100644 index 61afa03799..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml +++ /dev/null @@ -1,11 +0,0 @@ -<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/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl b/platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl deleted file mode 100644 index 0f2bcae338..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.vending.billing; - -import android.os.Bundle; - -/** - * InAppBillingService is the service that provides in-app billing version 3 and beyond. - * This service provides the following features: - * 1. Provides a new API to get details of in-app items published for the app including - * price, type, title and description. - * 2. The purchase flow is synchronous and purchase information is available immediately - * after it completes. - * 3. Purchase information of in-app purchases is maintained within the Google Play system - * till the purchase is consumed. - * 4. An API to consume a purchase of an inapp item. All purchases of one-time - * in-app items are consumable and thereafter can be purchased again. - * 5. An API to get current purchases of the user immediately. This will not contain any - * consumed purchases. - * - * All calls will give a response code with the following possible values - * RESULT_OK = 0 - success - * RESULT_USER_CANCELED = 1 - User pressed back or canceled a dialog - * RESULT_SERVICE_UNAVAILABLE = 2 - The network connection is down - * RESULT_BILLING_UNAVAILABLE = 3 - This billing API version is not supported for the type requested - * RESULT_ITEM_UNAVAILABLE = 4 - Requested SKU is not available for purchase - * RESULT_DEVELOPER_ERROR = 5 - Invalid arguments provided to the API - * RESULT_ERROR = 6 - Fatal error during the API action - * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned - * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned - */ -interface IInAppBillingService { - /** - * Checks support for the requested billing API version, package and in-app type. - * Minimum API version supported by this interface is 3. - * @param apiVersion billing API version that the app is using - * @param packageName the package name of the calling app - * @param type type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @return RESULT_OK(0) on success and appropriate response code on failures. - */ - int isBillingSupported(int apiVersion, String packageName, String type); - - /** - * Provides details of a list of SKUs - * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle - * with a list JSON strings containing the productId, price, title and description. - * This API can be called with a maximum of 20 SKUs. - * @param apiVersion billing API version that the app is using - * @param packageName the package name of the calling app - * @param type of the in-app items ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "DETAILS_LIST" with a StringArrayList containing purchase information - * in JSON format similar to: - * '{ "productId" : "exampleSku", - * "type" : "inapp", - * "price" : "$5.00", - * "price_currency": "USD", - * "price_amount_micros": 5000000, - * "title : "Example Title", - * "description" : "This is an example description" }' - */ - Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); - - /** - * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, - * the type, a unique purchase token and an optional developer payload. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, - String developerPayload); - - /** - * Returns the current SKUs owned by the user of the type and package name specified along with - * purchase information and a signature of the data to be validated. - * This will return all SKUs that have been purchased in V3 and managed items purchased using - * V1 and V2 that have not been consumed. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus are too many, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - on failures. - * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); - - /** - * Consume the last purchase of the given SKU. This will result in this item being removed - * from all subsequent responses to getPurchases() and allow re-purchase of this item. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param purchaseToken token in the purchase information JSON that identifies the purchase - * to be consumed - * @return RESULT_OK(0) if consumption succeeded, appropriate response codes on failures. - */ - int consumePurchase(int apiVersion, String packageName, String purchaseToken); - - /** - * This API is currently under development. - */ - int stub(int apiVersion, String packageName, String type); - - /** - * Returns a pending intent to launch the purchase flow for upgrading or downgrading a - * subscription. The existing owned SKU(s) should be provided along with the new SKU that - * the user is upgrading or downgrading to. - * @param apiVersion billing API version that the app is using, must be 5 or later - * @param packageName package name of the calling app - * @param oldSkus the SKU(s) that the user is upgrading or downgrading from, - * if null or empty this method will behave like {@link #getBuyIntent} - * @param newSku the SKU that the user is upgrading or downgrading to - * @param type of the item being purchased, currently must be "subs" - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - Bundle getBuyIntentToReplaceSkus(int apiVersion, String packageName, - in List<String> oldSkus, String newSku, String type, String developerPayload); - - /** - * Returns a pending intent to launch the purchase flow for an in-app item. This method is - * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams} - * parameter. This parameter is a Bundle of optional keys and values that affect the - * operation of the method. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @extraParams a Bundle with the following optional keys: - * "skusToReplace" - List<String> - an optional list of SKUs that the user is - * upgrading or downgrading from. - * Pass this field if the purchase is upgrading or downgrading - * existing subscriptions. - * The specified SKUs are replaced with the SKUs that the user is - * purchasing. Google Play replaces the specified SKUs at the start of - * the next billing cycle. - * "replaceSkusProration" - Boolean - whether the user should be credited for any unused - * subscription time on the SKUs they are upgrading or downgrading. - * If you set this field to true, Google Play swaps out the old SKUs - * and credits the user with the unused value of their subscription - * time on a pro-rated basis. - * Google Play applies this credit to the new subscription, and does - * not begin billing the user for the new subscription until after - * the credit is used up. - * If you set this field to false, the user does not receive credit for - * any unused subscription time and the recurrence date does not - * change. - * Default value is true. Ignored if you do not pass skusToReplace. - * "accountId" - String - an optional obfuscated string that is uniquely - * associated with the user's account in your app. - * If you pass this value, Google Play can use it to detect irregular - * activity, such as many devices making purchases on the same - * account in a short period of time. - * Do not use the developer ID or the user's Google ID for this field. - * In addition, this field should not contain the user's ID in - * cleartext. - * We recommend that you use a one-way hash to generate a string from - * the user's ID, and store the hashed string in this field. - * "vr" - Boolean - an optional flag indicating whether the returned intent - * should start a VR purchase flow. The apiVersion must also be 7 or - * later to use this flag. - */ - Bundle getBuyIntentExtraParams(int apiVersion, String packageName, String sku, - String type, String developerPayload, in Bundle extraParams); - - /** - * Returns the most recent purchase made by the user for each SKU, even if that purchase is - * expired, canceled, or consumed. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus is too large, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @param extraParams a Bundle with extra params that would be appended into http request - * query string. Not used at this moment. Reserved for future functionality. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value: RESULT_OK(0) if success, - * {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures. - * - * "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - Bundle getPurchaseHistory(int apiVersion, String packageName, String type, - String continuationToken, in Bundle extraParams); - - /** - * This method is a variant of {@link #isBillingSupported}} that takes an additional - * {@code extraParams} parameter. - * @param apiVersion billing API version that the app is using, must be 7 or later - * @param packageName package name of the calling app - * @param type of the in-app item being purchased ("inapp" for one-time purchases and "subs" - * for subscriptions) - * @param extraParams a Bundle with the following optional keys: - * "vr" - Boolean - an optional flag to indicate whether {link #getBuyIntentExtraParams} - * supports returning a VR purchase flow. - * @return RESULT_OK(0) on success and appropriate response code on failures. - */ - int isBillingSupportedExtraParams(int apiVersion, String packageName, String type, - in Bundle extraParams); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java deleted file mode 100644 index c15bc232ce..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java +++ /dev/null @@ -1,116 +0,0 @@ -/*************************************************************************/ -/* ConsumeTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.RemoteException; -import com.android.vending.billing.IInAppBillingService; -import java.lang.ref.WeakReference; - -abstract public class ConsumeTask { - - private Context context; - private IInAppBillingService mService; - - private String mSku; - private String mToken; - - private static class ConsumeAsyncTask extends AsyncTask<String, String, String> { - - private WeakReference<ConsumeTask> mTask; - - ConsumeAsyncTask(ConsumeTask consume) { - mTask = new WeakReference<>(consume); - } - - @Override - protected String doInBackground(String... strings) { - ConsumeTask consume = mTask.get(); - if (consume != null) { - return consume.doInBackground(strings); - } - return null; - } - - @Override - protected void onPostExecute(String param) { - ConsumeTask consume = mTask.get(); - if (consume != null) { - consume.onPostExecute(param); - } - } - } - - public ConsumeTask(IInAppBillingService mService, Context context) { - this.context = context; - this.mService = mService; - } - - public void consume(final String sku) { - mSku = sku; - PaymentsCache pc = new PaymentsCache(context); - Boolean isBlocked = pc.getConsumableFlag("block", sku); - mToken = pc.getConsumableValue("token", sku); - if (!isBlocked && mToken == null) { - // Consuming task is processing - } else if (!isBlocked) { - return; - } else if (mToken == null) { - this.error("No token for sku:" + sku); - return; - } - new ConsumeAsyncTask(this).execute(); - } - - private String doInBackground(String... params) { - try { - int response = mService.consumePurchase(3, context.getPackageName(), mToken); - if (response == 0 || response == 8) { - return null; - } - } catch (RemoteException e) { - return e.getMessage(); - } - return "Some error"; - } - - private void onPostExecute(String param) { - if (param == null) { - success(new PaymentsCache(context).getConsumableValue("ticket", mSku)); - } else { - error(param); - } - } - - abstract protected void success(String ticket); - abstract protected void error(String message); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java deleted file mode 100644 index c7d0a5de65..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java +++ /dev/null @@ -1,255 +0,0 @@ -/*************************************************************************/ -/* GodotPayment.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.content.Intent; -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.plugin.GodotPlugin; -import org.json.JSONException; -import org.json.JSONObject; - -public class GodotPayment extends GodotPlugin { - - private Integer purchaseCallbackId = 0; - private String accessToken; - private String purchaseValidationUrlPrefix; - private String transactionId; - private final PaymentsManager mPaymentManager; - private final Dictionary mSkuDetails = new Dictionary(); - - public GodotPayment(Godot godot) { - super(godot); - mPaymentManager = new PaymentsManager(godot, this); - mPaymentManager.initService(); - } - - @Override - public void onMainActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) { - mPaymentManager.processPurchaseResponse(resultCode, data); - } - } - - @Override - public void onMainDestroy() { - super.onMainDestroy(); - if (mPaymentManager != null) { - mPaymentManager.destroy(); - } - } - - public void purchase(final String sku, final String transactionId) { - runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.requestPurchase(sku, transactionId); - } - }); - } - - public void consumeUnconsumedPurchases() { - runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.consumeUnconsumedPurchases(); - } - }); - } - - private String signature; - - public String getSignature() { - return this.signature; - } - - public void callbackSuccess(String ticket, String signature, String sku) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku }); - } - - 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 }); - } - - public void callbackSuccessNoUnconsumedPurchases() { - GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {}); - } - - public void callbackFailConsume(String message) { - GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message }); - } - - public void callbackFail(String message) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message }); - } - - public void callbackCancel() { - GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {}); - } - - public void callbackAlreadyOwned(String sku) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku }); - } - - public int getPurchaseCallbackId() { - return purchaseCallbackId; - } - - public void setPurchaseCallbackId(int purchaseCallbackId) { - this.purchaseCallbackId = purchaseCallbackId; - } - - public String getPurchaseValidationUrlPrefix() { - return this.purchaseValidationUrlPrefix; - } - - public void setPurchaseValidationUrlPrefix(String url) { - this.purchaseValidationUrlPrefix = url; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public void setTransactionId(String transactionId) { - this.transactionId = transactionId; - } - - public String getTransactionId() { - return this.transactionId; - } - - // request purchased items are not consumed - public void requestPurchased() { - runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.requestPurchased(); - } - }); - } - - // callback for requestPurchased() - public void callbackPurchased(String receipt, String signature, String sku) { - GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku }); - } - - public void callbackDisconnected() { - GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {}); - } - - public void callbackConnected() { - GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {}); - } - - // true if connected, false otherwise - public boolean isConnected() { - return mPaymentManager.isConnected(); - } - - // consume item automatically after purchase. default is true. - public void setAutoConsume(boolean autoConsume) { - mPaymentManager.setAutoConsume(autoConsume); - } - - // consume a specific item - public void consume(String sku) { - mPaymentManager.consume(sku); - } - - // query in app item detail info - public void querySkuDetails(String[] list) { - List<String> nKeys = Arrays.asList(list); - List<String> cKeys = Arrays.asList(mSkuDetails.get_keys()); - ArrayList<String> fKeys = new ArrayList<String>(); - for (String key : nKeys) { - if (!cKeys.contains(key)) { - fKeys.add(key); - } - } - if (fKeys.size() > 0) { - mPaymentManager.querySkuDetails(fKeys.toArray(new String[0])); - } else { - completeSkuDetail(); - } - } - - public void addSkuDetail(String itemJson) { - JSONObject o = null; - try { - o = new JSONObject(itemJson); - Dictionary item = new Dictionary(); - item.put("type", o.optString("type")); - item.put("product_id", o.optString("productId")); - item.put("title", o.optString("title")); - item.put("description", o.optString("description")); - item.put("price", o.optString("price")); - item.put("price_currency_code", o.optString("price_currency_code")); - item.put("price_amount", 0.000001d * o.optLong("price_amount_micros")); - mSkuDetails.put(item.get("product_id").toString(), item); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - public void completeSkuDetail() { - GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails }); - } - - 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/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java deleted file mode 100644 index fe5685288b..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java +++ /dev/null @@ -1,93 +0,0 @@ -/*************************************************************************/ -/* HandlePurchaseTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.app.Activity; -import android.content.Intent; -import org.json.JSONException; -import org.json.JSONObject; - -abstract public class HandlePurchaseTask { - - private Activity context; - - public HandlePurchaseTask(Activity context) { - this.context = context; - } - - public void handlePurchaseRequest(int resultCode, Intent data) { - //Log.d("XXX", "Handling purchase response"); - if (resultCode == Activity.RESULT_OK) { - try { - //int responseCode = data.getIntExtra("RESPONSE_CODE", 0); - PaymentsCache pc = new PaymentsCache(context); - - String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); - //Log.d("XXX", "Purchase data:" + purchaseData); - String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); - //Log.d("XXX", "Purchase signature:" + dataSignature); - //Log.d("SARLANGA", purchaseData); - - JSONObject jo = new JSONObject(purchaseData); - //String sku = jo.getString("productId"); - //alert("You have bought the " + sku + ". Excellent choice, aventurer!"); - //String orderId = jo.getString("orderId"); - //String packageName = jo.getString("packageName"); - String productId = jo.getString("productId"); - //Long purchaseTime = jo.getLong("purchaseTime"); - //Integer state = jo.getInt("purchaseState"); - String developerPayload = jo.getString("developerPayload"); - String purchaseToken = jo.getString("purchaseToken"); - - if (!pc.getConsumableValue("validation_hash", productId).equals(developerPayload)) { - error("Untrusted callback"); - return; - } - //Log.d("XXX", "Este es el product ID:" + productId); - pc.setConsumableValue("ticket_signautre", productId, dataSignature); - pc.setConsumableValue("ticket", productId, purchaseData); - pc.setConsumableFlag("block", productId, true); - pc.setConsumableValue("token", productId, purchaseToken); - - success(productId, dataSignature, purchaseData); - return; - } catch (JSONException e) { - error(e.getMessage()); - } - } else if (resultCode == Activity.RESULT_CANCELED) { - canceled(); - } - } - - abstract protected void success(String sku, String signature, String ticket); - abstract protected void error(String message); - abstract protected void canceled(); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java deleted file mode 100644 index bded1f452f..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java +++ /dev/null @@ -1,405 +0,0 @@ -/*************************************************************************/ -/* PaymentsManager.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.text.TextUtils; -import android.util.Log; -import com.android.vending.billing.IInAppBillingService; -import java.util.ArrayList; -import java.util.Arrays; -import org.json.JSONException; -import org.json.JSONObject; - -public class PaymentsManager { - - public static final int BILLING_RESPONSE_RESULT_OK = 0; - public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001; - private static boolean auto_consume = true; - - private final Activity activity; - private final GodotPayment godotPayment; - IInAppBillingService mService; - - PaymentsManager(Activity activity, GodotPayment godotPayment) { - this.activity = activity; - this.godotPayment = godotPayment; - } - - public PaymentsManager initService() { - Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); - intent.setPackage("com.android.vending"); - activity.bindService( - intent, - mServiceConn, - Context.BIND_AUTO_CREATE); - return this; - } - - public void destroy() { - if (mService != null) { - activity.unbindService(mServiceConn); - } - } - - ServiceConnection mServiceConn = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - - // At this stage, godotPayment might not have been initialized yet. - if (godotPayment != null) { - godotPayment.callbackDisconnected(); - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - mService = IInAppBillingService.Stub.asInterface(service); - - // At this stage, godotPayment might not have been initialized yet. - if (godotPayment != null) { - godotPayment.callbackConnected(); - } - } - }; - - public void requestPurchase(final String sku, String transactionId) { - new PurchaseTask(mService, activity) { - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - - @Override - protected void canceled() { - godotPayment.callbackCancel(); - } - - @Override - protected void alreadyOwned() { - godotPayment.callbackAlreadyOwned(sku); - } - } - .purchase(sku, transactionId); - } - - public boolean isConnected() { - return mService != null; - } - - public void consumeUnconsumedPurchases() { - new ReleaseAllConsumablesTask(mService, activity) { - @Override - protected void success(String sku, String receipt, String signature, String token) { - godotPayment.callbackSuccessProductMassConsumed(receipt, signature, sku); - } - - @Override - protected void error(String message) { - Log.d("godot", "consumeUnconsumedPurchases :" + message); - godotPayment.callbackFailConsume(message); - } - - @Override - protected void notRequired() { - Log.d("godot", "callbackSuccessNoUnconsumedPurchases :"); - godotPayment.callbackSuccessNoUnconsumedPurchases(); - } - } - .consumeItAll(); - } - - public void requestPurchased() { - try { - PaymentsCache pc = new PaymentsCache(activity); - - String continueToken = null; - - do { - Bundle bundle = mService.getPurchases(3, activity.getPackageName(), "inapp", continueToken); - - if (bundle.getInt("RESPONSE_CODE") == 0) { - - final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); - final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); - - if (myPurchases == null || myPurchases.size() == 0) { - godotPayment.callbackPurchased("", "", ""); - return; - } - - for (int i = 0; i < myPurchases.size(); i++) { - - try { - String receipt = myPurchases.get(i); - JSONObject inappPurchaseData = new JSONObject(receipt); - String sku = inappPurchaseData.getString("productId"); - String token = inappPurchaseData.getString("purchaseToken"); - String signature = mySignatures.get(i); - - pc.setConsumableValue("ticket_signautre", sku, signature); - pc.setConsumableValue("ticket", sku, receipt); - pc.setConsumableFlag("block", sku, true); - pc.setConsumableValue("token", sku, token); - - godotPayment.callbackPurchased(receipt, signature, sku); - } catch (JSONException e) { - } - } - } - continueToken = bundle.getString("INAPP_CONTINUATION_TOKEN"); - Log.d("godot", "continue token = " + continueToken); - } while (!TextUtils.isEmpty(continueToken)); - } catch (Exception e) { - Log.d("godot", "Error requesting purchased products:" + e.getClass().getName() + ":" + e.getMessage()); - } - } - - public void processPurchaseResponse(int resultCode, Intent data) { - new HandlePurchaseTask(activity) { - @Override - protected void success(final String sku, final String signature, final String ticket) { - godotPayment.callbackSuccess(ticket, signature, sku); - - if (auto_consume) { - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - } - - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - } - .consume(sku); - } - } - - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - - @Override - protected void canceled() { - godotPayment.callbackCancel(); - } - } - .handlePurchaseRequest(resultCode, data); - } - - public void validatePurchase(String purchaseToken, final String sku) { - - new ValidateTask(activity, godotPayment) { - @Override - protected void success() { - - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - godotPayment.callbackSuccess(ticket, null, sku); - } - - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - } - .consume(sku); - } - - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - - @Override - protected void canceled() { - godotPayment.callbackCancel(); - } - } - .validatePurchase(sku); - } - - public void setAutoConsume(boolean autoConsume) { - auto_consume = autoConsume; - } - - public void consume(final String sku) { - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - godotPayment.callbackSuccessProductMassConsumed(ticket, "", sku); - } - - @Override - protected void error(String message) { - godotPayment.callbackFailConsume(message); - } - } - .consume(sku); - } - - // Workaround to bug where sometimes response codes come as Long instead of Integer - int getResponseCodeFromBundle(Bundle b) { - Object o = b.get("RESPONSE_CODE"); - if (o == null) { - //logDebug("Bundle with null response code, assuming OK (known issue)"); - return BILLING_RESPONSE_RESULT_OK; - } else if (o instanceof Integer) - return ((Integer)o).intValue(); - else if (o instanceof Long) - return (int)((Long)o).longValue(); - else { - //logError("Unexpected type for bundle response code."); - //logError(o.getClass().getName()); - throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName()); - } - } - - /** - * Returns a human-readable description for the given response code. - * - * @param code The response code - * @return A human-readable string explaining the result code. - * It also includes the result code numerically. - */ - public static String getResponseDesc(int code) { - String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" - + - "3:Billing Unavailable/4:Item unavailable/" - + - "5:Developer Error/6:Error/7:Item Already Owned/" - + - "8:Item not owned") - .split("/"); - String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" - + - "-1002:Bad response received/" - + - "-1003:Purchase signature verification failed/" - + - "-1004:Send intent failed/" - + - "-1005:User cancelled/" - + - "-1006:Unknown purchase response/" - + - "-1007:Missing token/" - + - "-1008:Unknown error/" - + - "-1009:Subscriptions not available/" - + - "-1010:Invalid consumption attempt") - .split("/"); - - if (code <= -1000) { - int index = -1000 - code; - if (index >= 0 && index < iabhelper_msgs.length) - return iabhelper_msgs[index]; - else - return String.valueOf(code) + ":Unknown IAB Helper Error"; - } else if (code < 0 || code >= iab_msgs.length) - return String.valueOf(code) + ":Unknown"; - else - return iab_msgs[code]; - } - - public void querySkuDetails(final String[] list) { - (new Thread(new Runnable() { - @Override - public void run() { - ArrayList<String> skuList = new ArrayList<String>(Arrays.asList(list)); - if (skuList.size() == 0) { - return; - } - // Split the sku list in blocks of no more than 20 elements. - ArrayList<ArrayList<String>> packs = new ArrayList<ArrayList<String>>(); - ArrayList<String> tempList; - int n = skuList.size() / 20; - int mod = skuList.size() % 20; - for (int i = 0; i < n; i++) { - tempList = new ArrayList<String>(); - for (String s : skuList.subList(i * 20, i * 20 + 20)) { - tempList.add(s); - } - packs.add(tempList); - } - if (mod != 0) { - tempList = new ArrayList<String>(); - for (String s : skuList.subList(n * 20, n * 20 + mod)) { - tempList.add(s); - } - packs.add(tempList); - } - for (ArrayList<String> skuPartList : packs) { - Bundle querySkus = new Bundle(); - querySkus.putStringArrayList("ITEM_ID_LIST", skuPartList); - Bundle skuDetails = null; - try { - skuDetails = mService.getSkuDetails(3, activity.getPackageName(), "inapp", querySkus); - if (!skuDetails.containsKey("DETAILS_LIST")) { - int response = getResponseCodeFromBundle(skuDetails); - if (response != BILLING_RESPONSE_RESULT_OK) { - godotPayment.errorSkuDetail(getResponseDesc(response)); - } else { - godotPayment.errorSkuDetail("No error but no detail list."); - } - return; - } - - ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST"); - - for (String thisResponse : responseList) { - Log.d("godot", "response = " + thisResponse); - godotPayment.addSkuDetail(thisResponse); - } - } catch (RemoteException e) { - e.printStackTrace(); - godotPayment.errorSkuDetail("RemoteException error!"); - } - } - godotPayment.completeSkuDetail(); - } - })) - .start(); - } -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java deleted file mode 100644 index eecd1d2151..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java +++ /dev/null @@ -1,118 +0,0 @@ -/*************************************************************************/ -/* PurchaseTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Intent; -import android.content.IntentSender.SendIntentException; -import android.os.Bundle; -import android.os.RemoteException; -import android.util.Log; -import com.android.vending.billing.IInAppBillingService; - -abstract public class PurchaseTask { - - private Activity context; - - private IInAppBillingService mService; - public PurchaseTask(IInAppBillingService mService, Activity context) { - this.context = context; - this.mService = mService; - } - - private boolean isLooping = false; - - public void purchase(final String sku, final String transactionId) { - Log.d("XXX", "Starting purchase for: " + sku); - PaymentsCache pc = new PaymentsCache(context); - Boolean isBlocked = pc.getConsumableFlag("block", sku); - /* - if(isBlocked){ - Log.d("XXX", "Is awaiting payment confirmation"); - error("Awaiting payment confirmation"); - return; - } - */ - final String hash = transactionId; - - Bundle buyIntentBundle; - try { - buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash); - } catch (RemoteException e) { - //Log.d("XXX", "Error: " + e.getMessage()); - error(e.getMessage()); - return; - } - Object rc = buyIntentBundle.get("RESPONSE_CODE"); - int responseCode = 0; - if (rc == null) { - responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK; - } else if (rc instanceof Integer) { - responseCode = ((Integer)rc).intValue(); - } else if (rc instanceof Long) { - responseCode = (int)((Long)rc).longValue(); - } - //Log.d("XXX", "Buy intent response code: " + responseCode); - if (responseCode == 1 || responseCode == 3 || responseCode == 4) { - canceled(); - return; - } - if (responseCode == 7) { - alreadyOwned(); - return; - } - - PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); - pc.setConsumableValue("validation_hash", sku, hash); - try { - if (context == null) { - //Log.d("XXX", "No context!"); - } - if (pendingIntent == null) { - //Log.d("XXX", "No pending intent"); - } - //Log.d("XXX", "Starting activity for purchase!"); - context.startIntentSenderForResult( - pendingIntent.getIntentSender(), - PaymentsManager.REQUEST_CODE_FOR_PURCHASE, - new Intent(), - Integer.valueOf(0), Integer.valueOf(0), - Integer.valueOf(0)); - } catch (SendIntentException e) { - error(e.getMessage()); - } - } - - abstract protected void error(String message); - abstract protected void canceled(); - abstract protected void alreadyOwned(); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java deleted file mode 100644 index b7bd638feb..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java +++ /dev/null @@ -1,141 +0,0 @@ -/*************************************************************************/ -/* ReleaseAllConsumablesTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.Log; -import com.android.vending.billing.IInAppBillingService; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import org.json.JSONException; -import org.json.JSONObject; - -abstract public class ReleaseAllConsumablesTask { - - private Context context; - private IInAppBillingService mService; - - private static class ReleaseAllConsumablesAsyncTask extends AsyncTask<String, String, String> { - - private WeakReference<ReleaseAllConsumablesTask> mTask; - private String mSku; - private String mReceipt; - private String mSignature; - private String mToken; - - ReleaseAllConsumablesAsyncTask(ReleaseAllConsumablesTask task, String sku, String receipt, String signature, String token) { - mTask = new WeakReference<ReleaseAllConsumablesTask>(task); - - mSku = sku; - mReceipt = receipt; - mSignature = signature; - mToken = token; - } - - @Override - protected String doInBackground(String... params) { - ReleaseAllConsumablesTask consume = mTask.get(); - if (consume != null) { - return consume.doInBackground(mToken); - } - return null; - } - - @Override - protected void onPostExecute(String param) { - ReleaseAllConsumablesTask consume = mTask.get(); - if (consume != null) { - consume.success(mSku, mReceipt, mSignature, mToken); - } - } - } - - public ReleaseAllConsumablesTask(IInAppBillingService mService, Context context) { - this.context = context; - this.mService = mService; - } - - public void consumeItAll() { - try { - //Log.d("godot", "consumeItall for " + context.getPackageName()); - Bundle bundle = mService.getPurchases(3, context.getPackageName(), "inapp", null); - - if (bundle.getInt("RESPONSE_CODE") == 0) { - - final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); - final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); - - if (myPurchases == null || myPurchases.size() == 0) { - //Log.d("godot", "No purchases!"); - notRequired(); - return; - } - - //Log.d("godot", "# products to be consumed:" + myPurchases.size()); - for (int i = 0; i < myPurchases.size(); i++) { - - try { - String receipt = myPurchases.get(i); - JSONObject inappPurchaseData = new JSONObject(receipt); - String sku = inappPurchaseData.getString("productId"); - String token = inappPurchaseData.getString("purchaseToken"); - String signature = mySignatures.get(i); - //Log.d("godot", "A punto de consumir un item con token:" + token + "\n" + receipt); - new ReleaseAllConsumablesAsyncTask(this, sku, receipt, signature, token).execute(); - } catch (JSONException e) { - } - } - } - } catch (Exception e) { - Log.d("godot", "Error releasing products:" + e.getClass().getName() + ":" + e.getMessage()); - } - } - - private String doInBackground(String token) { - try { - //Log.d("godot", "Requesting to consume an item with token ." + token); - int response = mService.consumePurchase(3, context.getPackageName(), token); - //Log.d("godot", "consumePurchase response: " + response); - if (response == 0 || response == 8) { - return null; - } - } catch (Exception e) { - Log.d("godot", "Error " + e.getClass().getName() + ":" + e.getMessage()); - } - return null; - } - - abstract protected void success(String sku, String receipt, String signature, String token); - abstract protected void error(String message); - abstract protected void notRequired(); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java deleted file mode 100644 index d42ded0c9b..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java +++ /dev/null @@ -1,141 +0,0 @@ -/*************************************************************************/ -/* ValidateTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.os.AsyncTask; -import java.lang.ref.WeakReference; -import org.godotengine.godot.plugin.payment.utils.HttpRequester; -import org.godotengine.godot.plugin.payment.utils.RequestParams; -import org.json.JSONException; -import org.json.JSONObject; - -abstract public class ValidateTask { - - private Activity context; - private GodotPayment godotPayments; - private ProgressDialog dialog; - private String mSku; - - private static class ValidateAsyncTask extends AsyncTask<String, String, String> { - private WeakReference<ValidateTask> mTask; - - ValidateAsyncTask(ValidateTask task) { - mTask = new WeakReference<>(task); - } - - @Override - protected void onPreExecute() { - ValidateTask task = mTask.get(); - if (task != null) { - task.onPreExecute(); - } - } - - @Override - protected String doInBackground(String... params) { - ValidateTask task = mTask.get(); - if (task != null) { - return task.doInBackground(params); - } - return null; - } - - @Override - protected void onPostExecute(String response) { - ValidateTask task = mTask.get(); - if (task != null) { - task.onPostExecute(response); - } - } - } - - public ValidateTask(Activity context, GodotPayment godotPayments) { - this.context = context; - this.godotPayments = godotPayments; - } - - public void validatePurchase(final String sku) { - mSku = sku; - new ValidateAsyncTask(this).execute(); - } - - private void onPreExecute() { - dialog = ProgressDialog.show(context, null, "Please wait..."); - } - - private String doInBackground(String... params) { - PaymentsCache pc = new PaymentsCache(context); - String url = godotPayments.getPurchaseValidationUrlPrefix(); - RequestParams param = new RequestParams(); - param.setUrl(url); - param.put("ticket", pc.getConsumableValue("ticket", mSku)); - param.put("purchaseToken", pc.getConsumableValue("token", mSku)); - param.put("sku", mSku); - //Log.d("XXX", "Haciendo request a " + url); - //Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku)); - //Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku)); - //Log.d("XXX", "sku: " + sku); - param.put("package", context.getApplicationContext().getPackageName()); - HttpRequester requester = new HttpRequester(); - String jsonResponse = requester.post(param); - //Log.d("XXX", "Validation response:\n"+jsonResponse); - return jsonResponse; - } - - private void onPostExecute(String response) { - if (dialog != null) { - dialog.dismiss(); - dialog = null; - } - JSONObject j; - try { - j = new JSONObject(response); - if (j.getString("status").equals("OK")) { - success(); - return; - } else if (j.getString("status") != null) { - error(j.getString("message")); - } else { - error("Connection error"); - } - } catch (JSONException e) { - error(e.getMessage()); - } catch (Exception e) { - error(e.getMessage()); - } - } - - abstract protected void success(); - abstract protected void error(String message); - abstract protected void canceled(); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java deleted file mode 100644 index dcb983201e..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java +++ /dev/null @@ -1,228 +0,0 @@ -/*************************************************************************/ -/* HttpRequester.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment.utils; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.security.KeyStore; -import java.util.Date; -import org.apache.http.HttpResponse; -import org.apache.http.HttpVersion; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; -import org.apache.http.protocol.HTTP; -import org.apache.http.util.EntityUtils; -import org.godotengine.godot.utils.Crypt; - -/** - * - * @author Luis Linietsky <luis.linietsky@gmail.com> - */ -public class HttpRequester { - - private Context context; - private static final int TTL = 600000; // 10 minutos - private long cttl = 0; - - public HttpRequester() { - //Log.d("XXX", "Creando http request sin contexto"); - } - - public HttpRequester(Context context) { - this.context = context; - //Log.d("XXX", "Creando http request con contexto"); - } - - public String post(RequestParams params) { - HttpPost httppost = new HttpPost(params.getUrl()); - try { - httppost.setEntity(new UrlEncodedFormEntity(params.toPairsList())); - return request(httppost); - } catch (UnsupportedEncodingException e) { - return null; - } - } - - public String get(RequestParams params) { - String response = getResponseFromCache(params.getUrl()); - if (response == null) { - //Log.d("XXX", "Cache miss!"); - HttpGet httpget = new HttpGet(params.getUrl()); - long timeInit = new Date().getTime(); - response = request(httpget); - long delay = new Date().getTime() - timeInit; - Log.d("HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay / 1000.0f) + " seconds"); - if (response == null || response.length() == 0) { - response = ""; - } else { - saveResponseIntoCache(params.getUrl(), response); - } - } - Log.d("XXX", "Req: " + params.getUrl()); - Log.d("XXX", "Resp: " + response); - return response; - } - - private String request(HttpUriRequest request) { - //Log.d("XXX", "Haciendo request a: " + request.getURI() ); - Log.d("PPP", "Haciendo request a: " + request.getURI()); - long init = new Date().getTime(); - HttpClient httpclient = getNewHttpClient(); - HttpParams httpParameters = httpclient.getParams(); - HttpConnectionParams.setConnectionTimeout(httpParameters, 0); - HttpConnectionParams.setSoTimeout(httpParameters, 0); - HttpConnectionParams.setTcpNoDelay(httpParameters, true); - try { - HttpResponse response = httpclient.execute(request); - Log.d("PPP", "Fin de request (" + (new Date().getTime() - init) + ") a: " + request.getURI()); - //Log.d("XXX1", "Status:" + response.getStatusLine().toString()); - if (response.getStatusLine().getStatusCode() == 200) { - String strResponse = EntityUtils.toString(response.getEntity()); - //Log.d("XXX2", strResponse); - return strResponse; - } else { - Log.d("XXX3", "Response status code:" + response.getStatusLine().getStatusCode() + "\n" + EntityUtils.toString(response.getEntity())); - return null; - } - - } catch (ClientProtocolException e) { - Log.d("XXX3", e.getMessage()); - } catch (IOException e) { - Log.d("XXX4", e.getMessage()); - } - return null; - } - - private HttpClient getNewHttpClient() { - try { - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(null, null); - - SSLSocketFactory sf = new CustomSSLSocketFactory(trustStore); - sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - - HttpParams params = new BasicHttpParams(); - HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); - HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); - - SchemeRegistry registry = new SchemeRegistry(); - registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - registry.register(new Scheme("https", sf, 443)); - - ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); - - return new DefaultHttpClient(ccm, params); - } catch (Exception e) { - return new DefaultHttpClient(); - } - } - - private static String convertStreamToString(InputStream is) { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line = null; - try { - while ((line = reader.readLine()) != null) { - sb.append((line + "\n")); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - is.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - return sb.toString(); - } - - public void saveResponseIntoCache(String request, String response) { - if (context == null) { - //Log.d("XXX", "No context, cache failed!"); - return; - } - SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putString("request_" + Crypt.md5(request), response); - editor.putLong("request_" + Crypt.md5(request) + "_ttl", new Date().getTime() + getTtl()); - editor.apply(); - } - - public String getResponseFromCache(String request) { - if (context == null) { - Log.d("XXX", "No context, cache miss"); - return null; - } - SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE); - long ttl = getResponseTtl(request); - if (ttl == 0l || (new Date().getTime() - ttl) > 0l) { - Log.d("XXX", "Cache invalid ttl:" + ttl + " vs now:" + new Date().getTime()); - return null; - } - return sharedPref.getString("request_" + Crypt.md5(request), null); - } - - public long getResponseTtl(String request) { - SharedPreferences sharedPref = context.getSharedPreferences( - "http_get_cache", Context.MODE_PRIVATE); - return sharedPref.getLong("request_" + Crypt.md5(request) + "_ttl", 0l); - } - - public long getTtl() { - return cttl > 0 ? cttl : TTL; - } - - public void setTtl(long ttl) { - this.cttl = (ttl * 1000) + new Date().getTime(); - } -} diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle index 9536d3de6d..524031d93f 100644 --- a/platform/android/java/settings.gradle +++ b/platform/android/java/settings.gradle @@ -3,4 +3,4 @@ rootProject.name = "Godot" include ':app' include ':lib' -include ':plugins:godotpayment' +include ':nativeSrcsConfigs' diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 6b9105b8e5..9b44ac4b41 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -33,7 +33,6 @@ #include "thread_jandroid.h" bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret) { - Map<StringName, List<MethodInfo>>::Element *M = methods.find(p_method); if (!M) return false; @@ -42,7 +41,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, MethodInfo *method = nullptr; for (List<MethodInfo>::Element *E = M->get().front(); E; E = E->next()) { - if (!p_instance && !E->get()._static) { r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; continue; @@ -50,13 +48,11 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, int pc = E->get().param_types.size(); if (pc > p_argcount) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = pc; continue; } if (pc < p_argcount) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument = pc; continue; @@ -65,10 +61,8 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, bool valid = true; for (int i = 0; i < pc; i++) { - Variant::Type arg_expected = Variant::NIL; switch (ptypes[i]) { - case ARG_TYPE_VOID: { //bug? } break; @@ -86,7 +80,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, case ARG_TYPE_SHORT: case ARG_TYPE_INT: case ARG_TYPE_LONG: { - if (!p_args[i]->is_num()) arg_expected = Variant::INT; @@ -95,32 +88,26 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, case ARG_NUMBER_CLASS_BIT | ARG_TYPE_DOUBLE: case ARG_TYPE_FLOAT: case ARG_TYPE_DOUBLE: { - if (!p_args[i]->is_num()) arg_expected = Variant::FLOAT; } break; case ARG_TYPE_STRING: { - if (p_args[i]->get_type() != Variant::STRING) arg_expected = Variant::STRING; } break; case ARG_TYPE_CLASS: { - if (p_args[i]->get_type() != Variant::OBJECT) arg_expected = Variant::OBJECT; else { - Ref<Reference> ref = *p_args[i]; if (!ref.is_null()) { if (Object::cast_to<JavaObject>(ref.ptr())) { - Ref<JavaObject> jo = ref; //could be faster jclass c = env->FindClass(E->get().param_sigs[i].operator String().utf8().get_data()); if (!c || !env->IsInstanceOf(jo->instance, c)) { - arg_expected = Variant::OBJECT; } else { //ok @@ -133,7 +120,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; default: { - if (p_args[i]->get_type() != Variant::ARRAY) arg_expected = Variant::ARRAY; @@ -163,13 +149,11 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, jvalue *argv = nullptr; if (method->param_types.size()) { - argv = (jvalue *)alloca(sizeof(jvalue) * method->param_types.size()); } List<jobject> to_free; for (int i = 0; i < method->param_types.size(); i++) { - switch (method->param_types[i]) { case ARG_TYPE_VOID: { //can't happen @@ -279,10 +263,8 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, to_free.push_back(jStr); } break; case ARG_TYPE_CLASS: { - Ref<JavaObject> jo = *p_args[i]; if (jo.is_valid()) { - argv[i].l = jo->instance; } else { argv[i].l = nullptr; //I hope this works @@ -290,7 +272,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: { - Array arr = *p_args[i]; jbooleanArray a = env->NewBooleanArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -302,7 +283,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_BYTE: { - Array arr = *p_args[i]; jbyteArray a = env->NewByteArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -314,7 +294,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_CHAR: { - Array arr = *p_args[i]; jcharArray a = env->NewCharArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -326,7 +305,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_SHORT: { - Array arr = *p_args[i]; jshortArray a = env->NewShortArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -338,7 +316,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_INT: { - Array arr = *p_args[i]; jintArray a = env->NewIntArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -360,7 +337,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_FLOAT: { - Array arr = *p_args[i]; jfloatArray a = env->NewFloatArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -372,7 +348,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_DOUBLE: { - Array arr = *p_args[i]; jdoubleArray a = env->NewDoubleArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -384,11 +359,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_STRING: { - Array arr = *p_args[i]; jobjectArray a = env->NewObjectArray(arr.size(), env->FindClass("java/lang/String"), nullptr); for (int j = 0; j < arr.size(); j++) { - String s = arr[j]; jstring jStr = env->NewStringUTF(s.utf8().get_data()); env->SetObjectArrayElement(a, j, jStr); @@ -399,7 +372,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, to_free.push_back(a); } break; case ARG_ARRAY_BIT | ARG_TYPE_CLASS: { - argv[i].l = nullptr; } break; } @@ -409,7 +381,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, bool success = true; switch (method->return_type) { - case ARG_TYPE_VOID: { if (method->_static) { env->CallStaticVoidMethodA(_class, method->method, argv); @@ -434,7 +405,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } } break; case ARG_TYPE_CHAR: { - if (method->_static) { ret = env->CallStaticCharMethodA(_class, method->method, argv); } else { @@ -442,7 +412,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } } break; case ARG_TYPE_SHORT: { - if (method->_static) { ret = env->CallStaticShortMethodA(_class, method->method, argv); } else { @@ -451,7 +420,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_TYPE_INT: { - if (method->_static) { ret = env->CallStaticIntMethodA(_class, method->method, argv); } else { @@ -460,7 +428,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_TYPE_LONG: { - if (method->_static) { ret = (int64_t)env->CallStaticLongMethodA(_class, method->method, argv); } else { @@ -469,7 +436,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_TYPE_FLOAT: { - if (method->_static) { ret = env->CallStaticFloatMethodA(_class, method->method, argv); } else { @@ -478,7 +444,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_TYPE_DOUBLE: { - if (method->_static) { ret = env->CallStaticDoubleMethodA(_class, method->method, argv); } else { @@ -487,7 +452,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; default: { - jobject obj; if (method->_static) { obj = env->CallStaticObjectMethodA(_class, method->method, argv); @@ -498,7 +462,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, if (!obj) { ret = Variant(); } else { - if (!_convert_object_to_variant(env, obj, ret, method->return_type)) { ret = Variant(); r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; @@ -518,7 +481,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } Variant JavaClass::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - Variant ret; bool found = _call_method(nullptr, p_method, p_args, p_argcount, r_error, ret); if (found) { @@ -534,7 +496,6 @@ JavaClass::JavaClass() { ///////////////////// Variant JavaObject::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - return Variant(); } @@ -547,14 +508,12 @@ JavaObject::~JavaObject() { //////////////////// bool JavaClassWrapper::_get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig) { - jstring name2 = (jstring)env->CallObjectMethod(obj, Class_getName); String str_type = jstring_to_string(name2, env); env->DeleteLocalRef(name2); uint32_t t = 0; if (str_type.begins_with("[")) { - t = JavaClass::ARG_ARRAY_BIT; strsig = "["; str_type = str_type.substr(1, str_type.length() - 1); @@ -633,87 +592,71 @@ bool JavaClassWrapper::_get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, St } bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &var, uint32_t p_sig) { - if (!obj) { var = Variant(); //seems null is just null... return true; } switch (p_sig) { - case ARG_TYPE_VOID: { - return Variant(); } break; case ARG_TYPE_BOOLEAN | ARG_NUMBER_CLASS_BIT: { - var = env->CallBooleanMethod(obj, JavaClassWrapper::singleton->Boolean_booleanValue); return true; } break; case ARG_TYPE_BYTE | ARG_NUMBER_CLASS_BIT: { - var = env->CallByteMethod(obj, JavaClassWrapper::singleton->Byte_byteValue); return true; } break; case ARG_TYPE_CHAR | ARG_NUMBER_CLASS_BIT: { - var = env->CallCharMethod(obj, JavaClassWrapper::singleton->Character_characterValue); return true; } break; case ARG_TYPE_SHORT | ARG_NUMBER_CLASS_BIT: { - var = env->CallShortMethod(obj, JavaClassWrapper::singleton->Short_shortValue); return true; } break; case ARG_TYPE_INT | ARG_NUMBER_CLASS_BIT: { - var = env->CallIntMethod(obj, JavaClassWrapper::singleton->Integer_integerValue); return true; } break; case ARG_TYPE_LONG | ARG_NUMBER_CLASS_BIT: { - var = (int64_t)env->CallLongMethod(obj, JavaClassWrapper::singleton->Long_longValue); return true; } break; case ARG_TYPE_FLOAT | ARG_NUMBER_CLASS_BIT: { - var = env->CallFloatMethod(obj, JavaClassWrapper::singleton->Float_floatValue); return true; } break; case ARG_TYPE_DOUBLE | ARG_NUMBER_CLASS_BIT: { - var = env->CallDoubleMethod(obj, JavaClassWrapper::singleton->Double_doubleValue); return true; } break; case ARG_TYPE_STRING: { - var = jstring_to_string((jstring)obj, env); return true; } break; case ARG_TYPE_CLASS: { - return false; } break; case ARG_ARRAY_BIT | ARG_TYPE_VOID: { - var = Array(); // ? return true; } break; case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jboolean val; env->GetBooleanArrayRegion((jbooleanArray)arr, 0, 1, &val); ret.push_back(val); @@ -724,14 +667,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } break; case ARG_ARRAY_BIT | ARG_TYPE_BYTE: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jbyte val; env->GetByteArrayRegion((jbyteArray)arr, 0, 1, &val); ret.push_back(val); @@ -747,7 +688,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jchar val; env->GetCharArrayRegion((jcharArray)arr, 0, 1, &val); ret.push_back(val); @@ -763,7 +703,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jshort val; env->GetShortArrayRegion((jshortArray)arr, 0, 1, &val); ret.push_back(val); @@ -779,7 +718,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jint val; env->GetIntArrayRegion((jintArray)arr, 0, 1, &val); ret.push_back(val); @@ -795,7 +733,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jlong val; env->GetLongArrayRegion((jlongArray)arr, 0, 1, &val); ret.push_back((int64_t)val); @@ -811,7 +748,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jfloat val; env->GetFloatArrayRegion((jfloatArray)arr, 0, 1, &val); ret.push_back(val); @@ -827,7 +763,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jdouble val; env->GetDoubleArrayRegion((jdoubleArray)arr, 0, 1, &val); ret.push_back(val); @@ -837,14 +772,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -860,14 +793,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_BYTE: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -882,14 +813,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_CHAR: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -904,14 +833,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_SHORT: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -926,14 +853,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_INT: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -948,14 +873,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_LONG: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -970,14 +893,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_FLOAT: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -998,7 +919,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -1014,14 +934,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } break; case ARG_ARRAY_BIT | ARG_TYPE_STRING: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -1036,7 +954,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_ARRAY_BIT | ARG_TYPE_CLASS: { - } break; } @@ -1044,7 +961,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { - if (class_cache.has(p_class)) return class_cache[p_class]; @@ -1066,7 +982,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { int count = env->GetArrayLength(methods); for (int i = 0; i < count; i++) { - jobject obj = env->GetObjectArrayElement(methods, i); ERR_CONTINUE(!obj); @@ -1096,7 +1011,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { String signature = "("; for (int j = 0; j < count2; j++) { - jobject obj2 = env->GetObjectArrayElement(param_types, j); String strsig; uint32_t sig = 0; @@ -1138,7 +1052,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { bool discard = false; for (List<JavaClass::MethodInfo>::Element *E = java_class->methods[str_method].front(); E; E = E->next()) { - float new_likeliness = 0; float existing_likeliness = 0; @@ -1146,7 +1059,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { continue; bool valid = true; for (int j = 0; j < E->get().param_types.size(); j++) { - Variant::Type _new; float new_l; Variant::Type existing; @@ -1195,7 +1107,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { count = env->GetArrayLength(fields); for (int i = 0; i < count; i++) { - jobject obj = env->GetObjectArrayElement(fields, i); ERR_CONTINUE(!obj); @@ -1207,17 +1118,13 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { jobject objc = env->CallObjectMethod(obj, Field_get, nullptr); if (objc) { - uint32_t sig; String strsig; jclass cl = env->GetObjectClass(objc); if (JavaClassWrapper::_get_type_sig(env, cl, sig, strsig)) { - if ((sig & JavaClass::ARG_TYPE_MASK) <= JavaClass::ARG_TYPE_STRING) { - Variant value; if (JavaClass::_convert_object_to_variant(env, objc, value, sig)) { - java_class->constant_map[str_field] = value; } } @@ -1239,12 +1146,11 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { JavaClassWrapper *JavaClassWrapper::singleton = nullptr; JavaClassWrapper::JavaClassWrapper(jobject p_activity) { - singleton = this; JNIEnv *env = ThreadAndroid::get_env(); - jclass activityClass = env->FindClass("org/godotengine/godot/Godot"); + jclass activityClass = env->FindClass("android/app/Activity"); jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); classLoader = env->CallObjectMethod(p_activity, getClassLoader); classLoader = (jclass)env->NewGlobalRef(classLoader); diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 0da0bd6387..8d1db395ab 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "java_godot_io_wrapper.h" -#include "core/error_list.h" +#include "core/error/error_list.h" // JNIEnv is only valid within the thread it belongs to, in a multi threading environment // we can't cache it. @@ -52,8 +52,9 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc _get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;"); _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); + _screen_get_usable_rect = p_env->GetMethodID(cls, "screenGetUsableRect", "()[I"), _get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;"); - _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;I)V"); + _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V"); _hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V"); _set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V"); _get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I"); @@ -118,6 +119,19 @@ int GodotIOJavaWrapper::get_screen_dpi() { } } +void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { + if (_screen_get_usable_rect) { + JNIEnv *env = ThreadAndroid::get_env(); + jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _screen_get_usable_rect); + ERR_FAIL_COND(env->GetArrayLength(returnArray) != 4); + jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE); + for (int i = 0; i < 4; i++) { + p_rect_xywh[i] = arrayBody[i]; + } + env->ReleaseIntArrayElements(returnArray, arrayBody, 0); + } +} + String GodotIOJavaWrapper::get_unique_id() { if (_get_unique_id) { JNIEnv *env = ThreadAndroid::get_env(); @@ -132,11 +146,11 @@ bool GodotIOJavaWrapper::has_vk() { return (_show_keyboard != 0) && (_hide_keyboard != 0); } -void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_max_input_length) { +void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { if (_show_keyboard) { JNIEnv *env = ThreadAndroid::get_env(); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); - env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_max_input_length); + env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end); } } diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index dbb3b564f6..13270c794b 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -50,6 +50,7 @@ private: jmethodID _get_locale = 0; jmethodID _get_model = 0; jmethodID _get_screen_DPI = 0; + jmethodID _screen_get_usable_rect = 0; jmethodID _get_unique_id = 0; jmethodID _show_keyboard = 0; jmethodID _hide_keyboard = 0; @@ -68,9 +69,10 @@ public: String get_locale(); String get_model(); int get_screen_dpi(); + void screen_get_usable_rect(int (&p_rect_xywh)[4]); String get_unique_id(); bool has_vk(); - void show_vk(const String &p_existing, int p_max_input_length); + void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end); void hide_vk(); int get_vk_height(); void set_vk_height(int p_height); diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 8b38113e09..5dc773fae2 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -37,9 +37,9 @@ #include "api/java_class_wrapper.h" #include "api/jni_singleton.h" #include "audio_driver_jandroid.h" -#include "core/engine.h" -#include "core/input/input_filter.h" -#include "core/project_settings.h" +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "core/input/input.h" #include "dir_access_jandroid.h" #include "display_server_android.h" #include "file_access_android.h" @@ -51,6 +51,7 @@ #include "string_android.h" #include "thread_jandroid.h" +#include <android/input.h> #include <unistd.h> #include <android/native_window_jni.h> @@ -77,15 +78,14 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHei } } -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_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject godot_instance, jobject p_asset_manager, jboolean p_use_apk_expansion) { initialized = true; JavaVM *jvm; env->GetJavaVM(&jvm); // create our wrapper classes - godot_java = new GodotJavaWrapper(env, activity); // our activity is our godot instance is our activity.. + godot_java = new GodotJavaWrapper(env, activity, godot_instance); godot_io_java = new GodotIOJavaWrapper(env, godot_java->get_member_object("io", "Lorg/godotengine/godot/GodotIO;", env)); ThreadAndroid::make_default(jvm); @@ -110,7 +110,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, jclass clazz, jobject activity) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz) { // lets cleanup if (godot_io_java) { delete godot_io_java; @@ -137,7 +137,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc j_cmdline = (jstring *)malloc(cmdlen * sizeof(jstring)); for (int i = 0; i < cmdlen; i++) { - jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i); const char *rawString = env->GetStringUTFChars(string, 0); @@ -166,10 +165,20 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc ClassDB::register_class<JNISingleton>(); } -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_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height) { + if (os_android) { + os_android->set_display_size(Size2i(p_width, p_height)); + + // No need to reset the surface during startup + if (step > 0) { + if (p_surface) { + ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface); + os_android->set_native_window(native_window); - if (os_android) - os_android->set_display_size(Size2i(width, height)); + DisplayServerAndroid::get_singleton()->reset_window(); + } + } + } } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits) { @@ -225,47 +234,55 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl DisplayServerAndroid::get_singleton()->process_gyroscope(gyroscope); if (os_android->main_loop_iterate()) { - godot_java->force_quit(env); } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint count, jintArray positions) { - +void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) { if (step == 0) return; Vector<DisplayServerAndroid::TouchPos> points; - for (int i = 0; i < count; i++) { - - jint p[3]; - env->GetIntArrayRegion(positions, i * 3, 3, p); + for (int i = 0; i < pointer_count; i++) { + jfloat p[3]; + env->GetFloatArrayRegion(positions, i * 3, 3, p); DisplayServerAndroid::TouchPos tp; tp.pos = Point2(p[1], p[2]); - tp.id = p[0]; + tp.id = (int)p[0]; points.push_back(tp); } - DisplayServerAndroid::get_singleton()->process_touch(ev, pointer, points); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + DisplayServerAndroid::get_singleton()->process_mouse_event(ev, buttons_mask, points[0].pos, vertical_factor, horizontal_factor); + } else { + DisplayServerAndroid::get_singleton()->process_touch(ev, pointer, points); + } +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3F(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position) { + touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position); +} - /* - if (os_android) - os_android->process_touch(ev,pointer,points); - */ +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FI(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position, jint buttons_mask) { + touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position, buttons_mask); } -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_touch__IIII_3FIFF(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) { + touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position, buttons_mask, vertical_factor, horizontal_factor); +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y) { if (step == 0) return; DisplayServerAndroid::get_singleton()->process_hover(p_type, Point2(p_x, 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_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, jint p_x, jint p_y) { if (step == 0) return; - DisplayServerAndroid::get_singleton()->process_double_tap(Point2(p_x, p_y)); + DisplayServerAndroid::get_singleton()->process_double_tap(p_button_mask, Point2(p_x, p_y)); } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y) { @@ -311,15 +328,15 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, j int hat = 0; if (p_hat_x != 0) { if (p_hat_x < 0) - hat |= InputFilter::HAT_MASK_LEFT; + hat |= Input::HAT_MASK_LEFT; else - hat |= InputFilter::HAT_MASK_RIGHT; + hat |= Input::HAT_MASK_RIGHT; } if (p_hat_y != 0) { if (p_hat_y < 0) - hat |= InputFilter::HAT_MASK_UP; + hat |= Input::HAT_MASK_UP; else - hat |= InputFilter::HAT_MASK_DOWN; + hat |= Input::HAT_MASK_DOWN; } jevent.hat = hat; @@ -329,7 +346,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, j 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); - InputFilter::get_singleton()->joy_connection_changed(p_device, p_connected, name); + Input::get_singleton()->joy_connection_changed(p_device, p_connected, name); } } @@ -357,7 +374,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) { - if (step == 0) return; @@ -365,7 +381,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) { - if (step == 0) return; @@ -373,21 +388,18 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz) { - ThreadAndroid::setup_thread(); AudioDriverAndroid::thread_func(env); } 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_callobject(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params) { - - Object *obj = ObjectDB::get_instance(ObjectID((uint64_t)ID)); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) { + Object *obj = ObjectDB::get_instance(ObjectID(ID)); ERR_FAIL_COND(!obj); int res = env->PushLocalFrame(16); @@ -399,7 +411,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en Variant *vlist = (Variant *)alloca(sizeof(Variant) * count); Variant **vptr = (Variant **)alloca(sizeof(Variant *) * count); for (int i = 0; i < count; i++) { - jobject obj = env->GetObjectArrayElement(params, i); Variant v; if (obj) @@ -417,9 +428,8 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en env->PopLocalFrame(nullptr); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params) { - - Object *obj = ObjectDB::get_instance(ObjectID((uint64_t)ID)); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) { + Object *obj = ObjectDB::get_instance(ObjectID(ID)); ERR_FAIL_COND(!obj); int res = env->PushLocalFrame(16); @@ -431,13 +441,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * Variant args[VARIANT_ARG_MAX]; for (int i = 0; i < MIN(count, VARIANT_ARG_MAX); i++) { - jobject obj = env->GetObjectArrayElement(params, i); if (obj) args[i] = _jobject_to_variant(env, obj); env->DeleteLocalRef(obj); }; + static_assert(VARIANT_ARG_MAX == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5"); obj->call_deferred(str_method, args[0], args[1], args[2], args[3], args[4]); // something env->PopLocalFrame(nullptr); @@ -459,7 +469,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI return; if (os_android->get_main_loop()) { - os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_RESUMED); + os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED); } } @@ -468,7 +478,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE return; if (os_android->get_main_loop()) { - os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_PAUSED); + os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED); } } } diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 221d701e2b..b499f6dfa1 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -37,16 +37,19 @@ // 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, 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_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject godot_instance, jobject p_asset_manager, jboolean p_use_apk_expansion); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz); 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_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, 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); +void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask = 0, jfloat vertical_factor = 0, jfloat horizontal_factor = 0); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3F(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FI(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FIFF(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, 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); @@ -61,8 +64,8 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env 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_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong 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); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 8ef99dfab0..cff591d903 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -37,36 +37,47 @@ // TODO we could probably create a base class for this... -GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { +GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance) { godot_instance = p_env->NewGlobalRef(p_godot_instance); + activity = p_env->NewGlobalRef(p_activity); // get info about our Godot class so we can get pointers and stuff... - cls = p_env->FindClass("org/godotengine/godot/Godot"); - if (cls) { - cls = (jclass)p_env->NewGlobalRef(cls); + godot_class = p_env->FindClass("org/godotengine/godot/Godot"); + if (godot_class) { + godot_class = (jclass)p_env->NewGlobalRef(godot_class); + } else { + // this is a pretty serious fail.. bail... pointers will stay 0 + return; + } + activity_class = p_env->FindClass("android/app/Activity"); + if (activity_class) { + activity_class = (jclass)p_env->NewGlobalRef(activity_class); } else { // this is a pretty serious fail.. bail... pointers will stay 0 return; } - // get some method pointers... - _on_video_init = p_env->GetMethodID(cls, "onVideoInit", "()V"); - _restart = p_env->GetMethodID(cls, "restart", "()V"); - _finish = p_env->GetMethodID(cls, "forceQuit", "()V"); - _set_keep_screen_on = p_env->GetMethodID(cls, "setKeepScreenOn", "(Z)V"); - _alert = p_env->GetMethodID(cls, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); - _get_GLES_version_code = p_env->GetMethodID(cls, "getGLESVersionCode", "()I"); - _get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;"); - _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); - _request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z"); - _request_permissions = p_env->GetMethodID(cls, "requestPermissions", "()Z"); - _get_granted_permissions = p_env->GetMethodID(cls, "getGrantedPermissions", "()[Ljava/lang/String;"); - _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V"); - _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); - _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_godot_main_loop_started = p_env->GetMethodID(cls, "onGodotMainLoopStarted", "()V"); + // get some Godot method pointers... + _on_video_init = p_env->GetMethodID(godot_class, "onVideoInit", "()V"); + _restart = p_env->GetMethodID(godot_class, "restart", "()V"); + _finish = p_env->GetMethodID(godot_class, "forceQuit", "()V"); + _set_keep_screen_on = p_env->GetMethodID(godot_class, "setKeepScreenOn", "(Z)V"); + _alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); + _get_GLES_version_code = p_env->GetMethodID(godot_class, "getGLESVersionCode", "()I"); + _get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;"); + _set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V"); + _request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z"); + _request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z"); + _get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;"); + _init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V"); + _get_surface = p_env->GetMethodID(godot_class, "getSurface", "()Landroid/view/Surface;"); + _is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z"); + _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V"); + _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;"); + _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); + + // get some Activity method pointers... + _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -74,27 +85,25 @@ GodotJavaWrapper::~GodotJavaWrapper() { } jobject GodotJavaWrapper::get_activity() { - // our godot instance is our activity - return godot_instance; + return activity; } jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) { - if (cls) { + if (godot_class) { if (p_env == nullptr) p_env = ThreadAndroid::get_env(); - jfieldID fid = p_env->GetStaticFieldID(cls, p_name, p_class); - return p_env->GetStaticObjectField(cls, fid); + jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class); + return p_env->GetStaticObjectField(godot_class, fid); } else { return nullptr; } } jobject GodotJavaWrapper::get_class_loader() { - if (cls) { + if (_get_class_loader) { JNIEnv *env = ThreadAndroid::get_env(); - jmethodID getClassLoader = env->GetMethodID(cls, "getClassLoader", "()Ljava/lang/ClassLoader;"); - return env->CallObjectMethod(godot_instance, getClassLoader); + return env->CallObjectMethod(godot_instance, _get_class_loader); } else { return nullptr; } diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 89d6b6db46..e0c3809a64 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -43,7 +43,9 @@ class GodotJavaWrapper { private: jobject godot_instance; - jclass cls; + jobject activity; + jclass godot_class; + jclass activity_class; jmethodID _on_video_init = 0; jmethodID _restart = 0; @@ -62,9 +64,10 @@ private: jmethodID _vibrate = 0; jmethodID _get_input_fallback_mapping = 0; jmethodID _on_godot_main_loop_started = 0; + jmethodID _get_class_loader = 0; public: - GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); + GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); ~GodotJavaWrapper(); jobject get_activity(); diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp index ded79a668f..8e1ae53b78 100644 --- a/platform/android/jni_utils.cpp +++ b/platform/android/jni_utils.cpp @@ -31,13 +31,10 @@ #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"); @@ -52,9 +49,7 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a }; } break; case Variant::INT: { - if (force_jobject) { - jclass bclass = env->FindClass("java/lang/Integer"); jmethodID ctor = env->GetMethodID(bclass, "<init>", "(I)V"); jvalue val; @@ -69,9 +64,7 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a }; } break; case Variant::FLOAT: { - if (force_jobject) { - jclass bclass = env->FindClass("java/lang/Double"); jmethodID ctor = env->GetMethodID(bclass, "<init>", "(D)V"); jvalue val; @@ -86,19 +79,16 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a }; } 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); @@ -109,7 +99,6 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a } break; case Variant::DICTIONARY: { - Dictionary dict = *p_arg; jclass dclass = env->FindClass("org/godotengine/godot/Dictionary"); jmethodID ctor = env->GetMethodID(dclass, "<init>", "()V"); @@ -152,7 +141,6 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a } break; case Variant::PACKED_INT32_ARRAY: { - Vector<int> array = *p_arg; jintArray arr = env->NewIntArray(array.size()); const int *r = array.ptr(); @@ -171,7 +159,6 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a } break; case Variant::PACKED_FLOAT32_ARRAY: { - Vector<float> array = *p_arg; jfloatArray arr = env->NewFloatArray(array.size()); const float *r = array.ptr(); @@ -185,7 +172,6 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a #endif default: { - v.val.i = 0; } break; } @@ -193,7 +179,6 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a } 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); @@ -210,7 +195,6 @@ String _get_class_name(JNIEnv *env, jclass cls, bool *array) { } Variant _jobject_to_variant(JNIEnv *env, jobject obj) { - if (obj == nullptr) { return Variant(); } @@ -220,12 +204,10 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { 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; @@ -240,14 +222,12 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { }; 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); @@ -255,7 +235,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { }; if (name == "[I") { - jintArray arr = (jintArray)obj; int fCount = env->GetArrayLength(arr); Vector<int> sarr; @@ -267,7 +246,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { }; if (name == "[B") { - jbyteArray arr = (jbyteArray)obj; int fCount = env->GetArrayLength(arr); Vector<uint8_t> sarr; @@ -279,7 +257,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { }; 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); @@ -287,7 +264,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { }; if (name == "[D") { - jdoubleArray arr = (jdoubleArray)obj; int fCount = env->GetArrayLength(arr); PackedFloat32Array sarr; @@ -296,7 +272,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { real_t *w = sarr.ptrw(); for (int i = 0; i < fCount; i++) { - double n; env->GetDoubleArrayRegion(arr, i, 1, &n); w[i] = n; @@ -305,7 +280,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { }; if (name == "[F") { - jfloatArray arr = (jfloatArray)obj; int fCount = env->GetArrayLength(arr); PackedFloat32Array sarr; @@ -314,7 +288,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { real_t *w = sarr.ptrw(); for (int i = 0; i < fCount; i++) { - float n; env->GetFloatArrayRegion(arr, i, 1, &n); w[i] = n; @@ -323,7 +296,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { }; if (name == "[Ljava.lang.Object;") { - jobjectArray arr = (jobjectArray)obj; int objCount = env->GetArrayLength(arr); Array varr; @@ -339,7 +311,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { }; 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;"); @@ -355,7 +326,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { env->DeleteLocalRef(arr); for (int i = 0; i < keys.size(); i++) { - ret[keys[i]] = vals[i]; }; @@ -368,7 +338,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { } Variant::Type get_jni_type(const String &p_type) { - static struct { const char *name; Variant::Type type; @@ -390,7 +359,6 @@ Variant::Type get_jni_type(const String &p_type) { int idx = 0; while (_type_to_vtype[idx].name) { - if (p_type == _type_to_vtype[idx].name) return _type_to_vtype[idx].type; @@ -401,7 +369,6 @@ Variant::Type get_jni_type(const String &p_type) { } const char *get_jni_sig(const String &p_type) { - static struct { const char *name; const char *sig; @@ -423,7 +390,6 @@ const char *get_jni_sig(const String &p_type) { int idx = 0; while (_type_to_vtype[idx].name) { - if (p_type == _type_to_vtype[idx].name) return _type_to_vtype[idx].sig; diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h index c2baa51b4a..bda056604f 100644 --- a/platform/android/jni_utils.h +++ b/platform/android/jni_utils.h @@ -32,12 +32,11 @@ #define JNI_UTILS_H #include "string_android.h" -#include <core/engine.h> -#include <core/variant.h> +#include <core/config/engine.h> +#include <core/variant/variant.h> #include <jni.h> struct jvalret { - jobject obj; jvalue val; jvalret() { obj = nullptr; } diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differindex df445f6a9c..f44d360a25 100644 --- a/platform/android/logo.png +++ b/platform/android/logo.png diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index 320bdd3817..0341ef3ec6 100644 --- a/platform/android/net_socket_android.cpp +++ b/platform/android/net_socket_android.cpp @@ -38,7 +38,6 @@ jmethodID NetSocketAndroid::_multicast_lock_acquire = 0; jmethodID NetSocketAndroid::_multicast_lock_release = 0; void NetSocketAndroid::setup(jobject p_net_utils) { - JNIEnv *env = ThreadAndroid::get_env(); net_utils = env->NewGlobalRef(p_net_utils); @@ -72,11 +71,6 @@ void NetSocketAndroid::make_default() { _create = _create_func; } -NetSocketAndroid::NetSocketAndroid() : - wants_broadcast(false), - multicast_groups(0) { -} - NetSocketAndroid::~NetSocketAndroid() { close(); } diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h index 4fc80d2de1..955d906535 100644 --- a/platform/android/net_socket_android.h +++ b/platform/android/net_socket_android.h @@ -45,15 +45,14 @@ * joins/leaves a multicast group. */ class NetSocketAndroid : public NetSocketPosix { - private: static jobject net_utils; static jclass cls; static jmethodID _multicast_lock_acquire; static jmethodID _multicast_lock_release; - bool wants_broadcast; - int multicast_groups; + bool wants_broadcast = false; + int multicast_groups = 0; static void multicast_lock_acquire(); static void multicast_lock_release(); @@ -71,7 +70,7 @@ public: virtual Error join_multicast_group(const IP_Address &p_multi_address, String p_if_name); virtual Error leave_multicast_group(const IP_Address &p_multi_address, String p_if_name); - NetSocketAndroid(); + NetSocketAndroid() {} ~NetSocketAndroid(); }; diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 760157595c..00733f6dbb 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -30,8 +30,8 @@ #include "os_android.h" +#include "core/config/project_settings.h" #include "core/io/file_access_buffered_fa.h" -#include "core/project_settings.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" #include "file_access_android.h" @@ -57,7 +57,6 @@ public: }; void OS_Android::initialize_core() { - OS_Unix::initialize_core(); if (use_apk_expansion) @@ -88,7 +87,7 @@ void OS_Android::initialize() { } void OS_Android::initialize_joypads() { - InputFilter::get_singleton()->set_fallback_mapping(godot_java->get_input_fallback_mapping()); + Input::get_singleton()->set_fallback_mapping(godot_java->get_input_fallback_mapping()); // This queries/updates the currently connected devices/joypads. godot_java->init_input_devices(); @@ -121,17 +120,14 @@ GodotIOJavaWrapper *OS_Android::get_godot_io_java() { } bool OS_Android::request_permission(const String &p_name) { - return godot_java->request_permission(p_name); } bool OS_Android::request_permissions() { - return godot_java->request_permissions(); } Vector<String> OS_Android::get_granted_permissions() const { - return godot_java->get_granted_permissions(); } @@ -142,30 +138,26 @@ Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_han } String OS_Android::get_name() const { - return "Android"; } MainLoop *OS_Android::get_main_loop() const { - return main_loop; } void OS_Android::main_loop_begin() { - if (main_loop) main_loop->init(); } bool OS_Android::main_loop_iterate() { - if (!main_loop) return false; + DisplayServerAndroid::get_singleton()->process_events(); return Main::iteration(); } void OS_Android::main_loop_end() { - if (main_loop) main_loop->finish(); } @@ -185,17 +177,14 @@ void OS_Android::main_loop_request_go_back() { } Error OS_Android::shell_open(String p_uri) { - return godot_io_java->open_uri(p_uri); } String OS_Android::get_resource_dir() const { - return "/"; //android has its own filesystem for resources inside the APK } String OS_Android::get_locale() const { - String locale = godot_io_java->get_locale(); if (locale != "") { return locale; @@ -205,7 +194,6 @@ String OS_Android::get_locale() const { } String OS_Android::get_model_name() const { - String model = godot_io_java->get_model(); if (model != "") return model; @@ -214,13 +202,11 @@ String OS_Android::get_model_name() const { } String OS_Android::get_user_data_dir() const { - if (data_dir_cache != String()) return data_dir_cache; String data_dir = godot_io_java->get_user_data_dir(); if (data_dir != "") { - //store current dir char real_current_dir_name[2048]; getcwd(real_current_dir_name, 2048); @@ -245,7 +231,6 @@ String OS_Android::get_user_data_dir() const { } String OS_Android::get_unique_id() const { - String unique_id = godot_io_java->get_unique_id(); if (unique_id != "") return unique_id; @@ -254,7 +239,6 @@ String OS_Android::get_unique_id() const { } String OS_Android::get_system_dir(SystemDir p_dir) const { - return godot_io_java->get_system_dir(p_dir); } diff --git a/platform/android/plugin/godot_plugin_config.h b/platform/android/plugin/godot_plugin_config.h new file mode 100644 index 0000000000..ecb9c0c7f5 --- /dev/null +++ b/platform/android/plugin/godot_plugin_config.h @@ -0,0 +1,268 @@ +/*************************************************************************/ +/* godot_plugin_config.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 GODOT_PLUGIN_CONFIG_H +#define GODOT_PLUGIN_CONFIG_H + +#include "core/error/error_list.h" +#include "core/io/config_file.h" +#include "core/string/ustring.h" + +static const char *PLUGIN_CONFIG_EXT = ".gdap"; + +static const char *CONFIG_SECTION = "config"; +static const char *CONFIG_NAME_KEY = "name"; +static const char *CONFIG_BINARY_TYPE_KEY = "binary_type"; +static const char *CONFIG_BINARY_KEY = "binary"; + +static const char *DEPENDENCIES_SECTION = "dependencies"; +static const char *DEPENDENCIES_LOCAL_KEY = "local"; +static const char *DEPENDENCIES_REMOTE_KEY = "remote"; +static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos"; + +static const char *BINARY_TYPE_LOCAL = "local"; +static const char *BINARY_TYPE_REMOTE = "remote"; + +static const char *PLUGIN_VALUE_SEPARATOR = "|"; + +/* + The `config` section and fields are required and defined as follow: +- **name**: name of the plugin +- **binary_type**: can be either `local` or `remote`. The type affects the **binary** field +- **binary**: + - if **binary_type** is `local`, then this should be the filename of the plugin `aar` file in the `res://android/plugins` directory (e.g: `MyPlugin.aar`). + - if **binary_type** is `remote`, then this should be a declaration for a remote gradle binary (e.g: "org.godot.example:my-plugin:0.0.0"). + +The `dependencies` section and fields are optional and defined as follow: +- **local**: contains a list of local `.aar` binary files the plugin depends on. The local binary dependencies must also be located in the `res://android/plugins` directory. +- **remote**: contains a list of remote binary gradle dependencies for the plugin. +- **custom_maven_repos**: contains a list of urls specifying custom maven repos required for the plugin's dependencies. + + See https://github.com/godotengine/godot/issues/38157#issuecomment-618773871 + */ +struct PluginConfig { + // Set to true when the config file is properly loaded. + bool valid_config = false; + // Unix timestamp of last change to this plugin. + uint64_t last_updated = 0; + + // Required config section + String name; + String binary_type; + String binary; + + // Optional dependencies section + Vector<String> local_dependencies; + Vector<String> remote_dependencies; + Vector<String> custom_maven_repos; +}; + +/* + * Set of prebuilt plugins. + * Currently unused, this is just for future reference: + */ +// static const PluginConfig MY_PREBUILT_PLUGIN = { +// /*.valid_config =*/true, +// /*.last_updated =*/0, +// /*.name =*/"GodotPayment", +// /*.binary_type =*/"local", +// /*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar", +// /*.local_dependencies =*/{}, +// /*.remote_dependencies =*/String("com.android.billingclient:billing:2.2.1").split("|"), +// /*.custom_maven_repos =*/{} +// }; + +static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { + String absolute_path; + if (!dependency_path.empty()) { + if (dependency_path.is_abs_path()) { + absolute_path = ProjectSettings::get_singleton()->globalize_path(dependency_path); + } else { + absolute_path = plugin_config_dir.plus_file(dependency_path); + } + } + + return absolute_path; +} + +static inline PluginConfig resolve_prebuilt_plugin(PluginConfig prebuilt_plugin, String plugin_config_dir) { + PluginConfig resolved = prebuilt_plugin; + resolved.binary = resolved.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.binary) : prebuilt_plugin.binary; + if (!prebuilt_plugin.local_dependencies.empty()) { + resolved.local_dependencies.clear(); + for (int i = 0; i < prebuilt_plugin.local_dependencies.size(); i++) { + resolved.local_dependencies.push_back(resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.local_dependencies[i])); + } + } + return resolved; +} + +static inline Vector<PluginConfig> get_prebuilt_plugins(String plugins_base_dir) { + Vector<PluginConfig> prebuilt_plugins; + // prebuilt_plugins.push_back(resolve_prebuilt_plugin(MY_PREBUILT_PLUGIN, plugins_base_dir)); + return prebuilt_plugins; +} + +static inline bool is_plugin_config_valid(PluginConfig plugin_config) { + bool valid_name = !plugin_config.name.empty(); + bool valid_binary_type = plugin_config.binary_type == BINARY_TYPE_LOCAL || + plugin_config.binary_type == BINARY_TYPE_REMOTE; + + bool valid_binary = false; + if (valid_binary_type) { + valid_binary = !plugin_config.binary.empty() && + (plugin_config.binary_type == BINARY_TYPE_REMOTE || + FileAccess::exists(plugin_config.binary)); + } + + bool valid_local_dependencies = true; + if (!plugin_config.local_dependencies.empty()) { + for (int i = 0; i < plugin_config.local_dependencies.size(); i++) { + if (!FileAccess::exists(plugin_config.local_dependencies[i])) { + valid_local_dependencies = false; + break; + } + } + } + return valid_name && valid_binary && valid_binary_type && valid_local_dependencies; +} + +static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_config, const String &config_path) { + uint64_t last_updated = FileAccess::get_modified_time(config_path); + last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary)); + + for (int i = 0; i < plugin_config.local_dependencies.size(); i++) { + String binary = plugin_config.local_dependencies.get(i); + last_updated = MAX(last_updated, FileAccess::get_modified_time(binary)); + } + + return last_updated; +} + +static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) { + PluginConfig plugin_config = {}; + + if (config_file.is_valid()) { + Error err = config_file->load(path); + if (err == OK) { + String config_base_dir = path.get_base_dir(); + + plugin_config.name = config_file->get_value(CONFIG_SECTION, CONFIG_NAME_KEY, String()); + plugin_config.binary_type = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_TYPE_KEY, String()); + + String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String()); + plugin_config.binary = plugin_config.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(config_base_dir, binary_path) : binary_path; + + if (config_file->has_section(DEPENDENCIES_SECTION)) { + Vector<String> local_dependencies_paths = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LOCAL_KEY, Vector<String>()); + if (!local_dependencies_paths.empty()) { + for (int i = 0; i < local_dependencies_paths.size(); i++) { + plugin_config.local_dependencies.push_back(resolve_local_dependency_path(config_base_dir, local_dependencies_paths[i])); + } + } + + plugin_config.remote_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_REMOTE_KEY, Vector<String>()); + plugin_config.custom_maven_repos = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY, Vector<String>()); + } + + plugin_config.valid_config = is_plugin_config_valid(plugin_config); + plugin_config.last_updated = get_plugin_modification_time(plugin_config, path); + } + } + + return plugin_config; +} + +static inline String get_plugins_binaries(String binary_type, Vector<PluginConfig> plugins_configs) { + String plugins_binaries; + if (!plugins_configs.empty()) { + Vector<String> binaries; + for (int i = 0; i < plugins_configs.size(); i++) { + PluginConfig config = plugins_configs[i]; + if (!config.valid_config) { + continue; + } + + if (config.binary_type == binary_type) { + binaries.push_back(config.binary); + } + + if (binary_type == BINARY_TYPE_LOCAL) { + binaries.append_array(config.local_dependencies); + } + + if (binary_type == BINARY_TYPE_REMOTE) { + binaries.append_array(config.remote_dependencies); + } + } + + plugins_binaries = String(PLUGIN_VALUE_SEPARATOR).join(binaries); + } + + return plugins_binaries; +} + +static inline String get_plugins_custom_maven_repos(Vector<PluginConfig> plugins_configs) { + String custom_maven_repos; + if (!plugins_configs.empty()) { + Vector<String> repos_urls; + for (int i = 0; i < plugins_configs.size(); i++) { + PluginConfig config = plugins_configs[i]; + if (!config.valid_config) { + continue; + } + + repos_urls.append_array(config.custom_maven_repos); + } + + custom_maven_repos = String(PLUGIN_VALUE_SEPARATOR).join(repos_urls); + } + return custom_maven_repos; +} + +static inline String get_plugins_names(Vector<PluginConfig> plugins_configs) { + String plugins_names; + if (!plugins_configs.empty()) { + Vector<String> names; + for (int i = 0; i < plugins_configs.size(); i++) { + PluginConfig config = plugins_configs[i]; + if (!config.valid_config) { + continue; + } + + names.push_back(config.name); + } + plugins_names = String(PLUGIN_VALUE_SEPARATOR).join(names); + } + + return plugins_names; +} + +#endif // GODOT_PLUGIN_CONFIG_H diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index c3bfb2f2ed..b8e5345b85 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -30,9 +30,9 @@ #include "godot_plugin_jni.h" -#include <core/engine.h> -#include <core/error_macros.h> -#include <core/project_settings.h> +#include <core/config/engine.h> +#include <core/config/project_settings.h> +#include <core/error/error_macros.h> #include <platform/android/api/jni_singleton.h> #include <platform/android/jni_utils.h> #include <platform/android/string_android.h> @@ -42,7 +42,6 @@ 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 = (JNISingleton *)ClassDB::instance("JNISingleton"); s->set_instance(env->NewGlobalRef(obj)); @@ -53,7 +52,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis } 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)); @@ -68,7 +66,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis 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)); @@ -80,7 +77,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis 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); } @@ -100,7 +96,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis int stringCount = env->GetArrayLength(j_signal_param_types); for (int i = 0; i < stringCount; i++) { - jstring j_signal_param_type = (jstring)env->GetObjectArrayElement(j_signal_param_types, i); const String signal_param_type = jstring_to_string(j_signal_param_type, env); types.push_back(get_jni_type(signal_param_type)); @@ -119,13 +114,15 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS String signal_name = jstring_to_string(j_signal_name, env); int count = env->GetArrayLength(j_signal_params); - const Variant *args[count]; + ERR_FAIL_COND_MSG(count > VARIANT_ARG_MAX, "Maximum argument count exceeded!"); - for (int i = 0; i < count; i++) { + Variant variant_params[VARIANT_ARG_MAX]; + const Variant *args[VARIANT_ARG_MAX]; + for (int i = 0; i < count; i++) { jobject j_param = env->GetObjectArrayElement(j_signal_params, i); - Variant variant = _jobject_to_variant(env, j_param); - args[i] = &variant; + variant_params[i] = _jobject_to_variant(env, j_param); + args[i] = &variant_params[i]; env->DeleteLocalRef(j_param); }; diff --git a/platform/android/string_android.h b/platform/android/string_android.h index 88ccd3b652..0a7dbf475d 100644 --- a/platform/android/string_android.h +++ b/platform/android/string_android.h @@ -30,7 +30,7 @@ #ifndef STRING_ANDROID_H #define STRING_ANDROID_H -#include "core/ustring.h" +#include "core/string/ustring.h" #include "thread_jandroid.h" #include <jni.h> diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index 729327f6f0..442e4d781b 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -30,9 +30,9 @@ #include "thread_jandroid.h" +#include "core/object/script_language.h" #include "core/os/memory.h" -#include "core/safe_refcount.h" -#include "core/script_language.h" +#include "core/templates/safe_refcount.h" static void _thread_id_key_destr_callback(void *p_value) { memdelete(static_cast<Thread::ID *>(p_value)); @@ -48,17 +48,14 @@ pthread_key_t ThreadAndroid::thread_id_key = _create_thread_id_key(); Thread::ID ThreadAndroid::next_thread_id = 0; Thread::ID ThreadAndroid::get_id() const { - return id; } Thread *ThreadAndroid::create_thread_jandroid() { - return memnew(ThreadAndroid); } void *ThreadAndroid::thread_callback(void *userdata) { - ThreadAndroid *t = reinterpret_cast<ThreadAndroid *>(userdata); setup_thread(); ScriptServer::thread_enter(); //scripts may need to attach a stack @@ -70,7 +67,6 @@ void *ThreadAndroid::thread_callback(void *userdata) { } Thread *ThreadAndroid::create_func_jandroid(ThreadCreateCallback p_callback, void *p_user, const Settings &) { - ThreadAndroid *tr = memnew(ThreadAndroid); tr->callback = p_callback; tr->user = p_user; @@ -83,7 +79,6 @@ Thread *ThreadAndroid::create_func_jandroid(ThreadCreateCallback p_callback, voi } Thread::ID ThreadAndroid::get_thread_id_func_jandroid() { - void *value = pthread_getspecific(thread_id_key); if (value) @@ -95,7 +90,6 @@ Thread::ID ThreadAndroid::get_thread_id_func_jandroid() { } void ThreadAndroid::wait_to_finish_func_jandroid(Thread *p_thread) { - ThreadAndroid *tp = static_cast<ThreadAndroid *>(p_thread); ERR_FAIL_COND(!tp); ERR_FAIL_COND(tp->pthread == 0); @@ -105,7 +99,6 @@ void ThreadAndroid::wait_to_finish_func_jandroid(Thread *p_thread) { } void ThreadAndroid::_thread_destroyed(void *value) { - /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ JNIEnv *env = (JNIEnv *)value; if (env != nullptr) { @@ -118,7 +111,6 @@ pthread_key_t ThreadAndroid::jvm_key; JavaVM *ThreadAndroid::java_vm = nullptr; void ThreadAndroid::setup_thread() { - if (pthread_getspecific(jvm_key)) return; //already setup JNIEnv *env; @@ -127,7 +119,6 @@ void ThreadAndroid::setup_thread() { } void ThreadAndroid::make_default(JavaVM *p_java_vm) { - java_vm = p_java_vm; create_func = create_func_jandroid; get_thread_id_func = get_thread_id_func_jandroid; @@ -137,7 +128,6 @@ void ThreadAndroid::make_default(JavaVM *p_java_vm) { } JNIEnv *ThreadAndroid::get_env() { - if (!pthread_getspecific(jvm_key)) { setup_thread(); } @@ -148,7 +138,6 @@ JNIEnv *ThreadAndroid::get_env() { } ThreadAndroid::ThreadAndroid() { - pthread = 0; } diff --git a/platform/android/thread_jandroid.h b/platform/android/thread_jandroid.h index eb4725ae68..9cfcc64813 100644 --- a/platform/android/thread_jandroid.h +++ b/platform/android/thread_jandroid.h @@ -37,7 +37,6 @@ #include <sys/types.h> class ThreadAndroid : public Thread { - static pthread_key_t thread_id_key; static ID next_thread_id; diff --git a/platform/android/vulkan/vulkan_context_android.cpp b/platform/android/vulkan/vulkan_context_android.cpp index 5fb7a83da4..56ef99dfc7 100644 --- a/platform/android/vulkan/vulkan_context_android.cpp +++ b/platform/android/vulkan/vulkan_context_android.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "vulkan_context_android.h" + #include <vulkan/vulkan_android.h> const char *VulkanContextAndroid::_get_platform_surface_extension() const { diff --git a/platform/android/vulkan/vulkan_context_android.h b/platform/android/vulkan/vulkan_context_android.h index 7e698ada4f..6bd3cbee36 100644 --- a/platform/android/vulkan/vulkan_context_android.h +++ b/platform/android/vulkan/vulkan_context_android.h @@ -36,7 +36,6 @@ struct ANativeWindow; class VulkanContextAndroid : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; public: diff --git a/platform/haiku/SCsub b/platform/haiku/SCsub deleted file mode 100644 index dbff6c5ae9..0000000000 --- a/platform/haiku/SCsub +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python - -Import("env") - -common_haiku = [ - "os_haiku.cpp", - "context_gl_haiku.cpp", - "haiku_application.cpp", - "haiku_direct_window.cpp", - "haiku_gl_view.cpp", - "key_mapping_haiku.cpp", - "audio_driver_media_kit.cpp", -] - -target = env.add_program("#bin/godot", ["godot_haiku.cpp"] + common_haiku) - -command = env.Command("#bin/godot.rsrc", "#platform/haiku/godot.rdef", ["rc -o $TARGET $SOURCE"]) - - -def addResourcesAction(target=None, source=None, env=None): - return env.Execute("xres -o " + File(target)[0].path + " bin/godot.rsrc") - - -env.AddPostAction(target, addResourcesAction) -env.Depends(target, command) diff --git a/platform/haiku/audio_driver_media_kit.cpp b/platform/haiku/audio_driver_media_kit.cpp deleted file mode 100644 index 94c9e83368..0000000000 --- a/platform/haiku/audio_driver_media_kit.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/*************************************************************************/ -/* audio_driver_media_kit.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 "audio_driver_media_kit.h" - -#ifdef MEDIA_KIT_ENABLED - -#include "core/project_settings.h" - -int32_t *AudioDriverMediaKit::samples_in = nullptr; - -Error AudioDriverMediaKit::init() { - active = false; - - mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); - speaker_mode = SPEAKER_MODE_STEREO; - channels = 2; - - int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); - buffer_size = next_power_of_2(latency * mix_rate / 1000); - samples_in = memnew_arr(int32_t, buffer_size * channels); - - media_raw_audio_format format; - format = media_raw_audio_format::wildcard; - format.frame_rate = mix_rate; - format.channel_count = channels; - format.format = media_raw_audio_format::B_AUDIO_INT; - format.byte_order = B_MEDIA_LITTLE_ENDIAN; - format.buffer_size = buffer_size * sizeof(int32_t) * channels; - - player = new BSoundPlayer( - &format, - "godot_sound_server", - AudioDriverMediaKit::PlayBuffer, - nullptr, - this); - - if (player->InitCheck() != B_OK) { - fprintf(stderr, "MediaKit ERR: can not create a BSoundPlayer instance\n"); - ERR_FAIL_COND_V(player == nullptr, ERR_CANT_OPEN); - } - - player->Start(); - - return OK; -} - -void AudioDriverMediaKit::PlayBuffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format &format) { - AudioDriverMediaKit *ad = (AudioDriverMediaKit *)cookie; - int32_t *buf = (int32_t *)buffer; - - if (!ad->active) { - for (unsigned int i = 0; i < ad->buffer_size * ad->channels; i++) { - AudioDriverMediaKit::samples_in[i] = 0; - } - } else { - ad->lock(); - ad->audio_server_process(ad->buffer_size, AudioDriverMediaKit::samples_in); - ad->unlock(); - } - - for (unsigned int i = 0; i < ad->buffer_size * ad->channels; i++) { - buf[i] = AudioDriverMediaKit::samples_in[i]; - } -} - -void AudioDriverMediaKit::start() { - active = true; -} - -int AudioDriverMediaKit::get_mix_rate() const { - return mix_rate; -} - -AudioDriverMediaKit::SpeakerMode AudioDriverMediaKit::get_speaker_mode() const { - return speaker_mode; -} - -void AudioDriverMediaKit::lock() { - if (!mutex) - return; - - mutex.lock(); -} - -void AudioDriverMediaKit::unlock() { - if (!mutex) - return; - - mutex.unlock(); -} - -void AudioDriverMediaKit::finish() { - delete player; - - if (samples_in) { - memdelete_arr(samples_in); - }; -} - -AudioDriverMediaKit::AudioDriverMediaKit() { - player = nullptr; -} - -AudioDriverMediaKit::~AudioDriverMediaKit() { -} - -#endif diff --git a/platform/haiku/context_gl_haiku.cpp b/platform/haiku/context_gl_haiku.cpp deleted file mode 100644 index 3c4d43ff71..0000000000 --- a/platform/haiku/context_gl_haiku.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/*************************************************************************/ -/* context_gl_haiku.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 "context_gl_haiku.h" - -#if defined(OPENGL_ENABLED) - -ContextGL_Haiku::ContextGL_Haiku(HaikuDirectWindow *p_window) { - window = p_window; - - uint32 type = BGL_RGB | BGL_DOUBLE | BGL_DEPTH; - view = new HaikuGLView(window->Bounds(), type); - - use_vsync = false; -} - -ContextGL_Haiku::~ContextGL_Haiku() { - delete view; -} - -Error ContextGL_Haiku::initialize() { - window->AddChild(view); - window->SetHaikuGLView(view); - - return OK; -} - -void ContextGL_Haiku::release_current() { - view->UnlockGL(); -} - -void ContextGL_Haiku::make_current() { - view->LockGL(); -} - -void ContextGL_Haiku::swap_buffers() { - view->SwapBuffers(use_vsync); -} - -int ContextGL_Haiku::get_window_width() { - return window->Bounds().IntegerWidth(); -} - -int ContextGL_Haiku::get_window_height() { - return window->Bounds().IntegerHeight(); -} - -void ContextGL_Haiku::set_use_vsync(bool p_use) { - use_vsync = p_use; -} - -bool ContextGL_Haiku::is_using_vsync() const { - return use_vsync; -} - -#endif diff --git a/platform/haiku/detect.py b/platform/haiku/detect.py deleted file mode 100644 index 0b84df8f9b..0000000000 --- a/platform/haiku/detect.py +++ /dev/null @@ -1,158 +0,0 @@ -import os -import sys - - -def is_active(): - return True - - -def get_name(): - return "Haiku" - - -def can_build(): - - if os.name != "posix" or sys.platform == "darwin": - return False - - return True - - -def get_opts(): - from SCons.Variables import EnumVariable - - return [ - EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), - ] - - -def get_flags(): - - return [] - - -def configure(env): - - ## Build type - - if env["target"] == "release": - env.Prepend(CCFLAGS=["-O3"]) - if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": - env.Prepend(CCFLAGS=["-g2"]) - - elif env["target"] == "release_debug": - env.Prepend(CCFLAGS=["-O2", "-DDEBUG_ENABLED"]) - if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": - env.Prepend(CCFLAGS=["-g2"]) - - elif env["target"] == "debug": - env.Prepend(CCFLAGS=["-g3", "-DDEBUG_ENABLED", "-DDEBUG_MEMORY_ENABLED"]) - - ## Architecture - - is64 = sys.maxsize > 2 ** 32 - if env["bits"] == "default": - env["bits"] = "64" if is64 else "32" - - ## Compiler configuration - - env["CC"] = "gcc-x86" - env["CXX"] = "g++-x86" - - ## Dependencies - - if not env["builtin_libwebp"]: - env.ParseConfig("pkg-config libwebp --cflags --libs") - - # freetype depends on libpng and zlib, so bundling one of them while keeping others - # as shared libraries leads to weird issues - if env["builtin_freetype"] or env["builtin_libpng"] or env["builtin_zlib"]: - env["builtin_freetype"] = True - env["builtin_libpng"] = True - env["builtin_zlib"] = True - - if not env["builtin_freetype"]: - env.ParseConfig("pkg-config freetype2 --cflags --libs") - - if not env["builtin_libpng"]: - env.ParseConfig("pkg-config libpng16 --cflags --libs") - - if not env["builtin_bullet"]: - # We need at least version 2.88 - import subprocess - - bullet_version = subprocess.check_output(["pkg-config", "bullet", "--modversion"]).strip() - if bullet_version < "2.88": - # Abort as system bullet was requested but too old - print( - "Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format( - bullet_version, "2.88" - ) - ) - sys.exit(255) - env.ParseConfig("pkg-config bullet --cflags --libs") - - if not env["builtin_enet"]: - env.ParseConfig("pkg-config libenet --cflags --libs") - - if not env["builtin_squish"]: - env.ParseConfig("pkg-config libsquish --cflags --libs") - - if not env["builtin_zstd"]: - env.ParseConfig("pkg-config libzstd --cflags --libs") - - # Sound and video libraries - # Keep the order as it triggers chained dependencies (ogg needed by others, etc.) - - if not env["builtin_libtheora"]: - env["builtin_libogg"] = False # Needed to link against system libtheora - env["builtin_libvorbis"] = False # Needed to link against system libtheora - env.ParseConfig("pkg-config theora theoradec --cflags --libs") - - if not env["builtin_libvpx"]: - env.ParseConfig("pkg-config vpx --cflags --libs") - - if not env["builtin_libvorbis"]: - env["builtin_libogg"] = False # Needed to link against system libvorbis - env.ParseConfig("pkg-config vorbis vorbisfile --cflags --libs") - - if not env["builtin_opus"]: - env["builtin_libogg"] = False # Needed to link against system opus - env.ParseConfig("pkg-config opus opusfile --cflags --libs") - - if not env["builtin_libogg"]: - env.ParseConfig("pkg-config ogg --cflags --libs") - - if env["builtin_libtheora"]: - list_of_x86 = ["x86_64", "x86", "i386", "i586"] - if any(platform.machine() in s for s in list_of_x86): - env["x86_libtheora_opt_gcc"] = True - - if not env["builtin_wslay"]: - env.ParseConfig("pkg-config libwslay --cflags --libs") - - if not env["builtin_mbedtls"]: - # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228 - env.Append(LIBS=["mbedtls", "mbedcrypto", "mbedx509"]) - - if not env["builtin_miniupnpc"]: - # No pkgconfig file so far, hardcode default paths. - env.Prepend(CPPPATH=["/system/develop/headers/x86/miniupnpc"]) - env.Append(LIBS=["miniupnpc"]) - - # On Linux wchar_t should be 32-bits - # 16-bit library shouldn't be required due to compiler optimisations - if not env["builtin_pcre2"]: - env.ParseConfig("pkg-config libpcre2-32 --cflags --libs") - - ## Flags - - env.Prepend(CPPPATH=["#platform/haiku"]) - env.Append(CPPDEFINES=["UNIX_ENABLED", "OPENGL_ENABLED", "GLES_ENABLED"]) - env.Append(CPPDEFINES=["MEDIA_KIT_ENABLED"]) - env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) # TODO: enable when we have pthread_setname_np - env.Append(LIBS=["be", "game", "media", "network", "bnetapi", "z", "GL"]) diff --git a/platform/haiku/godot.rdef b/platform/haiku/godot.rdef deleted file mode 100644 index a55cddbf0d..0000000000 --- a/platform/haiku/godot.rdef +++ /dev/null @@ -1,60 +0,0 @@ -resource app_version { - major = 2, - middle = 0, - minor = 0, - - variety = B_APPV_FINAL, - internal = 0, - - short_info = "Godot Game Engine", - long_info = "An advanced, feature packed, multi-platform 2D and 3D game engine." -}; - -resource app_signature "application/x-vnd.godot"; - -resource vector_icon { - $"6E6369660403A39F9F05FF03478CBF03414042090A04B37FB379CC26B379CC26" - $"CC20B37FCC200A09B5E9C41B2AC240B8E1BDFBBFA1BDA4C6A7BDFFCA1AC45CC9" - $"7AC607C01CC75BB6F4C65A062AFE9FFF9F69FE7FFEDFCF0FC95FC3D7C95FC51E" - $"C95FC51EC95FC53EC92BC565C94AC55BC92BC565C728C60BC728C60BC712C612" - $"C6E6C600C6F9C60EC6D3C5F2C6C7C5C4C6C7C5DCC6C7C5C4C460C4E5C4BCC4E5" - $"C626C4E5C626C4E5C64BC4A5C670C4CAC66BC4A5C670C1EDC6CFC1EDC6CFC1E9" - $"C6CFC1E2C6D0C1E6C6D0C1D1C6D0C1B2C6BEC1BFC6C9C1A2C6AFC19851C198C6" - $"9BC19851C505C031C507C507C016C507BFFCC507C507BE94C505BE9451BE9451" - $"BE94C69BBE7BC6BEBE8BC6AFBE6DC6C9BE4AC6D0BE5CC6D0BE47C6D0BE40C6CF" - $"BE44C6CFBE40C6CFBB87C670BB87C670BB63C66BBB47C626BB47C64BBB47C626" - $"C4BCB965C460B965C5C4B965C5C4B965C5DCB947C600B95AC5F2B934C60EB904" - $"C60BB91BC612B904C60BB701C565B701C565B6E3C55BB6CEC51EB6CEC53EB6CE" - $"C51EC3D7B590C36CB590C36CB581C3B0B578C43AB578C3F5B578C78FBFF8CA27" - $"BA2ACA22BFF8CA27BFFABFFCCA27BFFCCA27C5CACA22CA7CC43ACA7CC78FCA7C" - $"C3FBCA67C37ECA754ACA67C37E0639F6F97FFEF8E7FFF9F6FFFFFFFFFF03B67D" - $"BDEEC31FB730C35CB730C35CB74EC3662BC3A22BC3822BC3A2C4E8B8D1C55EB8" - $"D1C406B8D1C406B8D1C3F0B8ECC3CDB8DBC3DBB8FDC3BFB929C3BDB913C3B9B9" - $"29C3BDBB9FC436BB9FC436BBC2C43CBBDCC47EBBDCC45BBBDCC47EC5E6BE00C6" - $"31BE00C4BBBE00C4BBBE00C4A7BE16C486BE08C494BE24C479BE4AC471BE37C4" - $"71BE4AC471BE4BC016C473C1E2C471C1E2C471C1F6C471C217C486C209C479C2" - $"25C494C22DC4BBC22DC4A7C22DC4BBC631C451C5E6C451C47EC451C47EC451C4" - $"5BC48DC436C46AC43CC48DC436C704C3BDC704C3BDC719C3B9C741C3CDC730C3" - $"BF53C3DBC75CC406C75CC3F0C75CC406C55EC8CAC4E8C8CAC3A2C8CAC3A2C8CA" - $"C382C8FDC35CC8DFC366C8FDC35CC977C333BDEEC97ABDEEC97ABDEEC9F1BD56" - $"CAC9BC0BCA60BCB6CA3DBB1CC8D9B981C991BA47C82FB9D7C6EDBAA0C789BA38" - $"C69FBA52C5F0B9D0C647BA12C59BB98BC4E0B91FC53BB959C4FBB855C50EB6C0" - $"C509B78FC424B64AC22DB5C4C32AB5FCC1C8B66DC11BB7D9C16BB725C0BCB7C9" - $"BFFCB7C2C05CB7C3BFFCB7C2BFFCB7C2BFFCB7C2BFFBB7C2BFFAB7C2BFFAB7C2" - $"BFF9B7C2BFF8B7C2BFF9B7C2BFF8B7C2BFF8B7C2BFF8B7C2BF98B7C3BED9B7D9" - $"BF38B7C9BE88B725BDC7B5C4BE2CB66DBCCAB5FCBAE6B6C0BBD0B64ABAEBB78F" - $"BB13B91F34B855BAB8B959BA04B9D0BA59B98BB9ADBA12B907BAA0B955BA52B8" - $"6ABA38B71AB981B7C5B9D7B663BA47B52BBC0BB5B7BB1CB594BCB6B679BDEEB6" - $"02BD56B679BDEE0005BD3EC06CBD3EC06CBD3EC197BB2147BC4C47B9F647B904" - $"C06CB904C197B904BF41BB21BE4FB9F6BE4FBC4CBE4FBD3EC06CBD3EBF41BD3E" - $"C06C0005BCBC42BCBC42BCBCC153BB55C1F3BC1BC1F3BA8EC1F3B9ED42B9EDC1" - $"53B9EDBFC6BB55BF25BA8EBF25BC1BBF25BCBC42BCBCBFC6BCBC420007C01BC2" - $"BBC01BC2BBBFBAC2BBBF6CC21CBF6CC274BF6CC21CBF6CC02ABF6CC02ABF6CBF" - $"D3C01BBF8CBFBABF8CC07BBF8CC0C9C02AC0C9BFD3C0C9C02AC0C9C21CC0C9C2" - $"1CC0C9C274C01BC2BBC07BC2BBC01BC2BB0005C2F7C06CC2F7C06CC2F7C197C5" - $"1547C3E947C64047C732C06CC732C197C732BF41C515BE4FC640BE4FC3E9BE4F" - $"C2F7C06CC2F7BF41C2F7C06C0005C37942C37942C379C153C4E1C1F3C41AC1F3" - $"C5A7C1F3C64842C648C153C648BFC6C4E1BF25C5A7BF25C41ABF25C37942C379" - $"BFC6C37942090A0000000A010101000A020102000A020103000A010104000A03" - $"0105000A010106000A010107000A03010800" -}; diff --git a/platform/haiku/haiku_application.cpp b/platform/haiku/haiku_application.cpp deleted file mode 100644 index 82d9c093e1..0000000000 --- a/platform/haiku/haiku_application.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/*************************************************************************/ -/* haiku_application.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 "haiku_application.h" - -HaikuApplication::HaikuApplication() : - BApplication("application/x-vnd.godot") { -} diff --git a/platform/haiku/haiku_direct_window.cpp b/platform/haiku/haiku_direct_window.cpp deleted file mode 100644 index 0a40f847f4..0000000000 --- a/platform/haiku/haiku_direct_window.cpp +++ /dev/null @@ -1,363 +0,0 @@ -/*************************************************************************/ -/* haiku_direct_window.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 <UnicodeChar.h> - -#include "core/os/keyboard.h" -#include "haiku_direct_window.h" -#include "key_mapping_haiku.h" -#include "main/main.h" - -HaikuDirectWindow::HaikuDirectWindow(BRect p_frame) : - BDirectWindow(p_frame, "Godot", B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE) { - last_mouse_pos_valid = false; - last_buttons_state = 0; - last_button_mask = 0; - last_key_modifier_state = 0; - - view = nullptr; - update_runner = nullptr; - input = nullptr; - main_loop = nullptr; -} - -HaikuDirectWindow::~HaikuDirectWindow() { -} - -void HaikuDirectWindow::SetHaikuGLView(HaikuGLView *p_view) { - view = p_view; -} - -void HaikuDirectWindow::StartMessageRunner() { - update_runner = new BMessageRunner(BMessenger(this), - new BMessage(REDRAW_MSG), 1000000 / 60 /* 60 fps */); -} - -void HaikuDirectWindow::StopMessageRunner() { - delete update_runner; -} - -void HaikuDirectWindow::SetInput(InputDefault *p_input) { - input = p_input; -} - -void HaikuDirectWindow::SetMainLoop(MainLoop *p_main_loop) { - main_loop = p_main_loop; -} - -bool HaikuDirectWindow::QuitRequested() { - StopMessageRunner(); - main_loop->notification(NOTIFICATION_WM_CLOSE_REQUEST); - return false; -} - -void HaikuDirectWindow::DirectConnected(direct_buffer_info *info) { - view->DirectConnected(info); - view->EnableDirectMode(true); -} - -void HaikuDirectWindow::MessageReceived(BMessage *message) { - switch (message->what) { - case REDRAW_MSG: - if (Main::iteration()) { - view->EnableDirectMode(false); - Quit(); - } - break; - - default: - BDirectWindow::MessageReceived(message); - } -} - -void HaikuDirectWindow::DispatchMessage(BMessage *message, BHandler *handler) { - switch (message->what) { - case B_MOUSE_DOWN: - case B_MOUSE_UP: - HandleMouseButton(message); - break; - - case B_MOUSE_MOVED: - HandleMouseMoved(message); - break; - - case B_MOUSE_WHEEL_CHANGED: - HandleMouseWheelChanged(message); - break; - - case B_KEY_DOWN: - case B_KEY_UP: - HandleKeyboardEvent(message); - break; - - case B_MODIFIERS_CHANGED: - HandleKeyboardModifierEvent(message); - break; - - case B_WINDOW_RESIZED: - HandleWindowResized(message); - break; - - case LOCKGL_MSG: - view->LockGL(); - break; - - case UNLOCKGL_MSG: - view->UnlockGL(); - break; - - default: - BDirectWindow::DispatchMessage(message, handler); - } -} - -void HaikuDirectWindow::HandleMouseButton(BMessage *message) { - BPoint where; - if (message->FindPoint("where", &where) != B_OK) { - return; - } - - uint32 modifiers = message->FindInt32("modifiers"); - uint32 buttons = message->FindInt32("buttons"); - uint32 button = buttons ^ last_buttons_state; - last_buttons_state = buttons; - - // TODO: implement the mouse_mode checks - /* - if (mouse_mode == MOUSE_MODE_CAPTURED) { - event.xbutton.x=last_mouse_pos.x; - event.xbutton.y=last_mouse_pos.y; - } - */ - - Ref<InputEventMouseButton> mouse_event; - mouse_event.instance(); - - mouse_event->set_button_mask(GetMouseButtonState(buttons)); - mouse_event->set_position({ where.x, where.y }); - mouse_event->set_global_position({ where.x, where.y }); - GetKeyModifierState(mouse_event, modifiers); - - switch (button) { - default: - case B_PRIMARY_MOUSE_BUTTON: - mouse_event->set_button_index(1); - break; - - case B_SECONDARY_MOUSE_BUTTON: - mouse_event->set_button_index(2); - break; - - case B_TERTIARY_MOUSE_BUTTON: - mouse_event->set_button_index(3); - break; - } - - mouse_event->set_pressed(message->what == B_MOUSE_DOWN); - - if (message->what == B_MOUSE_DOWN && mouse_event->get_button_index() == 1) { - int32 clicks = message->FindInt32("clicks"); - - if (clicks > 1) { - mouse_event->set_doubleclick(true); - } - } - - input->parse_input_event(mouse_event); -} - -void HaikuDirectWindow::HandleMouseMoved(BMessage *message) { - BPoint where; - if (message->FindPoint("where", &where) != B_OK) { - return; - } - - Point2i pos(where.x, where.y); - uint32 modifiers = message->FindInt32("modifiers"); - uint32 buttons = message->FindInt32("buttons"); - - if (!last_mouse_pos_valid) { - last_mouse_position = pos; - last_mouse_pos_valid = true; - } - - Point2i rel = pos - last_mouse_position; - - Ref<InputEventMouseMotion> motion_event; - motion_event.instance(); - GetKeyModifierState(motion_event, modifiers); - - motion_event->set_button_mask(GetMouseButtonState(buttons)); - motion_event->set_position({ pos.x, pos.y }); - input->set_mouse_position(pos); - motion_event->set_global_position({ pos.x, pos.y }); - motion_event->set_speed({ input->get_last_mouse_speed().x, - input->get_last_mouse_speed().y }); - - motion_event->set_relative({ rel.x, rel.y }); - - last_mouse_position = pos; - - input->parse_input_event(motion_event); -} - -void HaikuDirectWindow::HandleMouseWheelChanged(BMessage *message) { - float wheel_delta_y = 0; - if (message->FindFloat("be:wheel_delta_y", &wheel_delta_y) != B_OK) { - return; - } - - Ref<InputEventMouseButton> mouse_event; - mouse_event.instance(); - //GetKeyModifierState(mouse_event, modifiers); - - mouse_event->set_button_index(wheel_delta_y < 0 ? 4 : 5); - mouse_event->set_button_mask(last_button_mask); - mouse_event->set_position({ last_mouse_position.x, - last_mouse_position.y }); - mouse_event->set_global_position({ last_mouse_position.x, - last_mouse_position.y }); - - mouse_event->set_pressed(true); - input->parse_input_event(mouse_event); - - mouse_event->set_pressed(false); - input->parse_input_event(mouse_event); -} - -void HaikuDirectWindow::HandleKeyboardEvent(BMessage *message) { - int32 raw_char = 0; - int32 key = 0; - int32 modifiers = 0; - - if (message->FindInt32("raw_char", &raw_char) != B_OK) { - return; - } - - if (message->FindInt32("key", &key) != B_OK) { - return; - } - - if (message->FindInt32("modifiers", &modifiers) != B_OK) { - return; - } - - Ref<InputEventKey> event; - event.instance(); - GetKeyModifierState(event, modifiers); - event->set_pressed(message->what == B_KEY_DOWN); - 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); - - const char *bytes = nullptr; - if (message->FindString("bytes", &bytes) == B_OK) { - event->set_unicode(BUnicodeChar::FromUTF8(&bytes)); - } - - //make it consistent across platforms. - if (event->get_keycode() == KEY_BACKTAB) { - event->set_keycode(KEY_TAB); - event->set_physical_keycode(KEY_TAB); - event->set_shift(true); - } - - input->parse_input_event(event); -} - -void HaikuDirectWindow::HandleKeyboardModifierEvent(BMessage *message) { - int32 old_modifiers = 0; - int32 modifiers = 0; - - if (message->FindInt32("be:old_modifiers", &old_modifiers) != B_OK) { - return; - } - - if (message->FindInt32("modifiers", &modifiers) != B_OK) { - return; - } - - int32 key = old_modifiers ^ modifiers; - - Ref<InputEventWithModifiers> event; - event.instance(); - GetKeyModifierState(event, modifiers); - - event->set_shift(key & B_SHIFT_KEY); - event->set_alt(key & B_OPTION_KEY); - event->set_control(key & B_CONTROL_KEY); - event->set_command(key & B_COMMAND_KEY); - - input->parse_input_event(event); -} - -void HaikuDirectWindow::HandleWindowResized(BMessage *message) { - int32 width = 0; - int32 height = 0; - - if ((message->FindInt32("width", &width) != B_OK) || (message->FindInt32("height", &height) != B_OK)) { - return; - } - - current_video_mode->width = width; - current_video_mode->height = height; -} - -inline void HaikuDirectWindow::GetKeyModifierState(Ref<InputEventWithModifiers> event, uint32 p_state) { - last_key_modifier_state = p_state; - - event->set_shift(p_state & B_SHIFT_KEY); - event->set_control(p_state & B_CONTROL_KEY); - event->set_alt(p_state & B_OPTION_KEY); - event->set_metakey(p_state & B_COMMAND_KEY); - - return state; -} - -inline int HaikuDirectWindow::GetMouseButtonState(uint32 p_state) { - int state = 0; - - if (p_state & B_PRIMARY_MOUSE_BUTTON) { - state |= 1 << 0; - } - - if (p_state & B_SECONDARY_MOUSE_BUTTON) { - state |= 1 << 1; - } - - if (p_state & B_TERTIARY_MOUSE_BUTTON) { - state |= 1 << 2; - } - - last_button_mask = state; - - return state; -} diff --git a/platform/haiku/haiku_direct_window.h b/platform/haiku/haiku_direct_window.h deleted file mode 100644 index 925bb9ac5d..0000000000 --- a/platform/haiku/haiku_direct_window.h +++ /dev/null @@ -1,89 +0,0 @@ -/*************************************************************************/ -/* haiku_direct_window.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 HAIKU_DIRECT_WINDOW_H -#define HAIKU_DIRECT_WINDOW_H - -#include <kernel/image.h> // needed for image_id - -#include <DirectWindow.h> - -#include "core/input/input_filter.h" -#include "core/os/os.h" - -#include "haiku_gl_view.h" - -#define REDRAW_MSG 'rdrw' -#define LOCKGL_MSG 'glck' -#define UNLOCKGL_MSG 'ulck' - -class HaikuDirectWindow : public BDirectWindow { -private: - Point2i last_mouse_position; - bool last_mouse_pos_valid; - uint32 last_buttons_state; - uint32 last_key_modifier_state; - int last_button_mask; - OS::VideoMode *current_video_mode; - - MainLoop *main_loop; - InputDefault *input; - HaikuGLView *view; - BMessageRunner *update_runner; - - void HandleMouseButton(BMessage *message); - void HandleMouseMoved(BMessage *message); - void HandleMouseWheelChanged(BMessage *message); - void HandleWindowResized(BMessage *message); - void HandleKeyboardEvent(BMessage *message); - void HandleKeyboardModifierEvent(BMessage *message); - inline void GetKeyModifierState(Ref<InputEventWithModifiers> event, uint32 p_state); - inline int GetMouseButtonState(uint32 p_state); - -public: - HaikuDirectWindow(BRect p_frame); - ~HaikuDirectWindow(); - - void SetHaikuGLView(HaikuGLView *p_view); - void StartMessageRunner(); - void StopMessageRunner(); - void SetInput(InputDefault *p_input); - void SetMainLoop(MainLoop *p_main_loop); - inline void SetVideoMode(OS::VideoMode *video_mode) { current_video_mode = video_mode; }; - virtual bool QuitRequested(); - virtual void DirectConnected(direct_buffer_info *info); - virtual void MessageReceived(BMessage *message); - virtual void DispatchMessage(BMessage *message, BHandler *handler); - - inline Point2i GetLastMousePosition() { return last_mouse_position; }; - inline int GetLastButtonMask() { return last_button_mask; }; -}; - -#endif diff --git a/platform/haiku/key_mapping_haiku.cpp b/platform/haiku/key_mapping_haiku.cpp deleted file mode 100644 index 692a1e5a78..0000000000 --- a/platform/haiku/key_mapping_haiku.cpp +++ /dev/null @@ -1,249 +0,0 @@ -/*************************************************************************/ -/* key_mapping_haiku.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 <InterfaceDefs.h> - -#include "core/os/keyboard.h" -#include "key_mapping_haiku.h" - -struct _HaikuTranslatePair { - unsigned int keysym; - int32 keycode; -}; - -static _HaikuTranslatePair _mod_to_keycode[] = { - { KEY_SHIFT, B_SHIFT_KEY }, - { KEY_ALT, B_COMMAND_KEY }, - { KEY_CONTROL, B_CONTROL_KEY }, - { KEY_CAPSLOCK, B_CAPS_LOCK }, - { KEY_SCROLLLOCK, B_SCROLL_LOCK }, - { KEY_NUMLOCK, B_NUM_LOCK }, - { KEY_SUPER_L, B_OPTION_KEY }, - { KEY_MENU, B_MENU_KEY }, - { KEY_SHIFT, B_LEFT_SHIFT_KEY }, - { KEY_SHIFT, B_RIGHT_SHIFT_KEY }, - { KEY_ALT, B_LEFT_COMMAND_KEY }, - { KEY_ALT, B_RIGHT_COMMAND_KEY }, - { KEY_CONTROL, B_LEFT_CONTROL_KEY }, - { KEY_CONTROL, B_RIGHT_CONTROL_KEY }, - { KEY_SUPER_L, B_LEFT_OPTION_KEY }, - { KEY_SUPER_R, B_RIGHT_OPTION_KEY }, - { KEY_UNKNOWN, 0 } -}; - -static _HaikuTranslatePair _fn_to_keycode[] = { - { KEY_F1, B_F1_KEY }, - { KEY_F2, B_F2_KEY }, - { KEY_F3, B_F3_KEY }, - { KEY_F4, B_F4_KEY }, - { KEY_F5, B_F5_KEY }, - { KEY_F6, B_F6_KEY }, - { KEY_F7, B_F7_KEY }, - { KEY_F8, B_F8_KEY }, - { KEY_F9, B_F9_KEY }, - { KEY_F10, B_F10_KEY }, - { KEY_F11, B_F11_KEY }, - { KEY_F12, B_F12_KEY }, - //{ KEY_F13, ? }, - //{ KEY_F14, ? }, - //{ KEY_F15, ? }, - //{ KEY_F16, ? }, - { KEY_PRINT, B_PRINT_KEY }, - { KEY_SCROLLLOCK, B_SCROLL_KEY }, - { KEY_PAUSE, B_PAUSE_KEY }, - { KEY_UNKNOWN, 0 } -}; - -static _HaikuTranslatePair _hb_to_keycode[] = { - { KEY_BACKSPACE, B_BACKSPACE }, - { KEY_TAB, B_TAB }, - { KEY_ENTER, B_RETURN }, - { KEY_CAPSLOCK, B_CAPS_LOCK }, - { KEY_ESCAPE, B_ESCAPE }, - { KEY_SPACE, B_SPACE }, - { KEY_PAGEUP, B_PAGE_UP }, - { KEY_PAGEDOWN, B_PAGE_DOWN }, - { KEY_END, B_END }, - { KEY_HOME, B_HOME }, - { KEY_LEFT, B_LEFT_ARROW }, - { KEY_UP, B_UP_ARROW }, - { KEY_RIGHT, B_RIGHT_ARROW }, - { KEY_DOWN, B_DOWN_ARROW }, - { KEY_PRINT, B_PRINT_KEY }, - { KEY_INSERT, B_INSERT }, - { KEY_DELETE, B_DELETE }, - // { KEY_HELP, ??? }, - - { KEY_0, (0x30) }, - { KEY_1, (0x31) }, - { KEY_2, (0x32) }, - { KEY_3, (0x33) }, - { KEY_4, (0x34) }, - { KEY_5, (0x35) }, - { KEY_6, (0x36) }, - { KEY_7, (0x37) }, - { KEY_8, (0x38) }, - { KEY_9, (0x39) }, - { KEY_A, (0x61) }, - { KEY_B, (0x62) }, - { KEY_C, (0x63) }, - { KEY_D, (0x64) }, - { KEY_E, (0x65) }, - { KEY_F, (0x66) }, - { KEY_G, (0x67) }, - { KEY_H, (0x68) }, - { KEY_I, (0x69) }, - { KEY_J, (0x6A) }, - { KEY_K, (0x6B) }, - { KEY_L, (0x6C) }, - { KEY_M, (0x6D) }, - { KEY_N, (0x6E) }, - { KEY_O, (0x6F) }, - { KEY_P, (0x70) }, - { KEY_Q, (0x71) }, - { KEY_R, (0x72) }, - { KEY_S, (0x73) }, - { KEY_T, (0x74) }, - { KEY_U, (0x75) }, - { KEY_V, (0x76) }, - { KEY_W, (0x77) }, - { KEY_X, (0x78) }, - { KEY_Y, (0x79) }, - { KEY_Z, (0x7A) }, - - /* -{ KEY_PLAY, VK_PLAY},// (0xFA) -{ KEY_STANDBY,VK_SLEEP },//(0x5F) -{ KEY_BACK,VK_BROWSER_BACK},// (0xA6) -{ KEY_FORWARD,VK_BROWSER_FORWARD},// (0xA7) -{ KEY_REFRESH,VK_BROWSER_REFRESH},// (0xA8) -{ KEY_STOP,VK_BROWSER_STOP},// (0xA9) -{ KEY_SEARCH,VK_BROWSER_SEARCH},// (0xAA) -{ KEY_FAVORITES, VK_BROWSER_FAVORITES},// (0xAB) -{ KEY_HOMEPAGE,VK_BROWSER_HOME},// (0xAC) -{ KEY_VOLUMEMUTE,VK_VOLUME_MUTE},// (0xAD) -{ KEY_VOLUMEDOWN,VK_VOLUME_DOWN},// (0xAE) -{ KEY_VOLUMEUP,VK_VOLUME_UP},// (0xAF) -{ KEY_MEDIANEXT,VK_MEDIA_NEXT_TRACK},// (0xB0) -{ KEY_MEDIAPREVIOUS,VK_MEDIA_PREV_TRACK},// (0xB1) -{ KEY_MEDIASTOP,VK_MEDIA_STOP},// (0xB2) -{ KEY_LAUNCHMAIL, VK_LAUNCH_MAIL},// (0xB4) -{ KEY_LAUNCHMEDIA,VK_LAUNCH_MEDIA_SELECT},// (0xB5) -{ KEY_LAUNCH0,VK_LAUNCH_APP1},// (0xB6) -{ KEY_LAUNCH1,VK_LAUNCH_APP2},// (0xB7) -*/ - - { KEY_SEMICOLON, 0x3B }, - { KEY_EQUAL, 0x3D }, - { KEY_COLON, 0x2C }, - { KEY_MINUS, 0x2D }, - { KEY_PERIOD, 0x2E }, - { KEY_SLASH, 0x2F }, - { KEY_KP_MULTIPLY, 0x2A }, - { KEY_KP_ADD, 0x2B }, - - { KEY_QUOTELEFT, 0x60 }, - { KEY_BRACKETLEFT, 0x5B }, - { KEY_BACKSLASH, 0x5C }, - { KEY_BRACKETRIGHT, 0x5D }, - { KEY_APOSTROPHE, 0x27 }, - - { KEY_UNKNOWN, 0 } -}; - -unsigned int KeyMappingHaiku::get_keysym(int32 raw_char, int32 key) { - if (raw_char == B_INSERT && key == 0x64) { - return KEY_KP_0; - } - if (raw_char == B_END && key == 0x58) { - return KEY_KP_1; - } - if (raw_char == B_DOWN_ARROW && key == 0x59) { - return KEY_KP_2; - } - if (raw_char == B_PAGE_DOWN && key == 0x5A) { - return KEY_KP_3; - } - if (raw_char == B_LEFT_ARROW && key == 0x48) { - return KEY_KP_4; - } - if (raw_char == 0x35 && key == 0x49) { - return KEY_KP_5; - } - if (raw_char == B_RIGHT_ARROW && key == 0x4A) { - return KEY_KP_6; - } - if (raw_char == B_HOME && key == 0x37) { - return KEY_KP_7; - } - if (raw_char == B_UP_ARROW && key == 0x38) { - return KEY_KP_8; - } - if (raw_char == B_PAGE_UP && key == 0x39) { - return KEY_KP_9; - } - if (raw_char == 0x2F && key == 0x23) { - return KEY_KP_DIVIDE; - } - if (raw_char == 0x2D && key == 0x25) { - return KEY_KP_SUBTRACT; - } - if (raw_char == B_DELETE && key == 0x65) { - return KEY_KP_PERIOD; - } - - if (raw_char == 0x10) { - for (int i = 0; _fn_to_keycode[i].keysym != KEY_UNKNOWN; i++) { - if (_fn_to_keycode[i].keycode == key) { - return _fn_to_keycode[i].keysym; - } - } - - return KEY_UNKNOWN; - } - - for (int i = 0; _hb_to_keycode[i].keysym != KEY_UNKNOWN; i++) { - if (_hb_to_keycode[i].keycode == raw_char) { - return _hb_to_keycode[i].keysym; - } - } - - return KEY_UNKNOWN; -} - -unsigned int KeyMappingHaiku::get_modifier_keysym(int32 key) { - for (int i = 0; _mod_to_keycode[i].keysym != KEY_UNKNOWN; i++) { - if ((_mod_to_keycode[i].keycode & key) != 0) { - return _mod_to_keycode[i].keysym; - } - } - - return KEY_UNKNOWN; -} diff --git a/platform/haiku/logo.png b/platform/haiku/logo.png Binary files differdeleted file mode 100644 index a2d8e242a6..0000000000 --- a/platform/haiku/logo.png +++ /dev/null diff --git a/platform/haiku/os_haiku.cpp b/platform/haiku/os_haiku.cpp deleted file mode 100644 index 07cb18d7cd..0000000000 --- a/platform/haiku/os_haiku.cpp +++ /dev/null @@ -1,362 +0,0 @@ -/*************************************************************************/ -/* os_haiku.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 "os_haiku.h" - -#include "drivers/gles2/rasterizer_gles2.h" -#include "main/main.h" -#include "servers/physics_3d/physics_server_3d_sw.h" -#include "servers/rendering/rendering_server_raster.h" -#include "servers/rendering/rendering_server_wrap_mt.h" - -#include <Screen.h> - -OS_Haiku::OS_Haiku() { -#ifdef MEDIA_KIT_ENABLED - AudioDriverManager::add_driver(&driver_media_kit); -#endif -}; - -void OS_Haiku::run() { - if (!main_loop) { - return; - } - - main_loop->init(); - context_gl->release_current(); - - // TODO: clean up - BMessenger *bms = new BMessenger(window); - BMessage *msg = new BMessage(); - bms->SendMessage(LOCKGL_MSG, msg); - - window->StartMessageRunner(); - app->Run(); - window->StopMessageRunner(); - - delete app; - - delete bms; - delete msg; - main_loop->finish(); -} - -String OS_Haiku::get_name() const { - return "Haiku"; -} - -int OS_Haiku::get_video_driver_count() const { - return 1; -} - -const char *OS_Haiku::get_video_driver_name(int p_driver) const { - return "GLES2"; -} - -int OS_Haiku::get_current_video_driver() const { - return video_driver_index; -} - -Error OS_Haiku::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - main_loop = nullptr; - current_video_mode = p_desired; - - app = new HaikuApplication(); - - BRect frame; - frame.Set(50, 50, 50 + current_video_mode.width - 1, 50 + current_video_mode.height - 1); - - window = new HaikuDirectWindow(frame); - window->SetVideoMode(¤t_video_mode); - - if (current_video_mode.fullscreen) { - window->SetFullScreen(true); - } - - if (!current_video_mode.resizable) { - uint32 flags = window->Flags(); - flags |= B_NOT_RESIZABLE; - window->SetFlags(flags); - } - -#if defined(OPENGL_ENABLED) - context_gl = memnew(ContextGL_Haiku(window)); - context_gl->initialize(); - context_gl->make_current(); - context_gl->set_use_vsync(current_video_mode.use_vsync); - // FIXME: That's not how the rasterizer setup should happen. - RasterizerGLES2::register_config(); - RasterizerGLES2::make_current(); -#endif - - rendering_server = memnew(RenderingServerRaster); - // FIXME: Reimplement threaded rendering - if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { - rendering_server = memnew(RenderingServerWrapMT(rendering_server, false)); - } - - ERR_FAIL_COND_V(!rendering_server, ERR_UNAVAILABLE); - - video_driver_index = p_video_driver; - - input = memnew(InputDefault); - window->SetInput(input); - - window->Show(); - rendering_server->init(); - - AudioDriverManager::initialize(p_audio_driver); - - return OK; -} - -void OS_Haiku::finalize() { - if (main_loop) { - memdelete(main_loop); - } - - main_loop = nullptr; - - rendering_server->finish(); - memdelete(rendering_server); - - memdelete(input); - -#if defined(OPENGL_ENABLED) - memdelete(context_gl); -#endif -} - -void OS_Haiku::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; - input->set_main_loop(p_main_loop); - window->SetMainLoop(p_main_loop); -} - -MainLoop *OS_Haiku::get_main_loop() const { - return main_loop; -} - -void OS_Haiku::delete_main_loop() { - if (main_loop) { - memdelete(main_loop); - } - - main_loop = nullptr; - window->SetMainLoop(nullptr); -} - -void OS_Haiku::release_rendering_thread() { - context_gl->release_current(); -} - -void OS_Haiku::make_rendering_thread() { - context_gl->make_current(); -} - -bool OS_Haiku::can_draw() const { - // TODO: implement - return true; -} - -void OS_Haiku::swap_buffers() { - context_gl->swap_buffers(); -} - -Point2 OS_Haiku::get_mouse_position() const { - return window->GetLastMousePosition(); -} - -int OS_Haiku::get_mouse_button_state() const { - return window->GetLastButtonMask(); -} - -void OS_Haiku::set_cursor_shape(CursorShape p_shape) { - //ERR_PRINT("set_cursor_shape() NOT IMPLEMENTED"); -} - -OS::CursorShape OS_Haiku::get_cursor_shape() const { - // TODO: implement get_cursor_shape -} - -void OS_Haiku::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { - // TODO -} - -int OS_Haiku::get_screen_count() const { - // TODO: implement get_screen_count() - return 1; -} - -int OS_Haiku::get_current_screen() const { - // TODO: implement get_current_screen() - return 0; -} - -void OS_Haiku::set_current_screen(int p_screen) { - // TODO: implement set_current_screen() -} - -Point2 OS_Haiku::get_screen_position(int p_screen) const { - // TODO: make this work with the p_screen parameter - BScreen *screen = new BScreen(window); - BRect frame = screen->Frame(); - delete screen; - return Point2i(frame.left, frame.top); -} - -Size2 OS_Haiku::get_screen_size(int p_screen) const { - // TODO: make this work with the p_screen parameter - BScreen *screen = new BScreen(window); - BRect frame = screen->Frame(); - delete screen; - return Size2i(frame.IntegerWidth() + 1, frame.IntegerHeight() + 1); -} - -void OS_Haiku::set_window_title(const String &p_title) { - window->SetTitle(p_title.utf8().get_data()); -} - -Size2 OS_Haiku::get_window_size() const { - BSize size = window->Size(); - return Size2i(size.IntegerWidth() + 1, size.IntegerHeight() + 1); -} - -void OS_Haiku::set_window_size(const Size2 p_size) { - // TODO: why does it stop redrawing after this is called? - window->ResizeTo(p_size.x, p_size.y); -} - -Point2 OS_Haiku::get_window_position() const { - BPoint point(0, 0); - window->ConvertToScreen(&point); - return Point2i(point.x, point.y); -} - -void OS_Haiku::set_window_position(const Point2 &p_position) { - window->MoveTo(p_position.x, p_position.y); -} - -void OS_Haiku::set_window_fullscreen(bool p_enabled) { - window->SetFullScreen(p_enabled); - current_video_mode.fullscreen = p_enabled; - rendering_server->init(); -} - -bool OS_Haiku::is_window_fullscreen() const { - return current_video_mode.fullscreen; -} - -void OS_Haiku::set_window_resizable(bool p_enabled) { - uint32 flags = window->Flags(); - - if (p_enabled) { - flags &= ~(B_NOT_RESIZABLE); - } else { - flags |= B_NOT_RESIZABLE; - } - - window->SetFlags(flags); - current_video_mode.resizable = p_enabled; -} - -bool OS_Haiku::is_window_resizable() const { - return current_video_mode.resizable; -} - -void OS_Haiku::set_window_minimized(bool p_enabled) { - window->Minimize(p_enabled); -} - -bool OS_Haiku::is_window_minimized() const { - return window->IsMinimized(); -} - -void OS_Haiku::set_window_maximized(bool p_enabled) { - window->Minimize(!p_enabled); -} - -bool OS_Haiku::is_window_maximized() const { - return !window->IsMinimized(); -} - -void OS_Haiku::set_video_mode(const VideoMode &p_video_mode, int p_screen) { - ERR_PRINT("set_video_mode() NOT IMPLEMENTED"); -} - -OS::VideoMode OS_Haiku::get_video_mode(int p_screen) const { - return current_video_mode; -} - -void OS_Haiku::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { - ERR_PRINT("get_fullscreen_mode_list() NOT IMPLEMENTED"); -} - -String OS_Haiku::get_executable_path() const { - return OS::get_executable_path(); -} - -bool OS_Haiku::_check_internal_feature_support(const String &p_feature) { - - return p_feature == "pc"; -} - -String OS_Haiku::get_config_path() const { - - if (has_environment("XDG_CONFIG_HOME")) { - return get_environment("XDG_CONFIG_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file("config/settings"); - } else { - return "."; - } -} - -String OS_Haiku::get_data_path() const { - - if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file("config/data"); - } else { - return get_config_path(); - } -} - -String OS_Haiku::get_cache_path() const { - - if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file("config/cache"); - } else { - return get_config_path(); - } -} diff --git a/platform/haiku/os_haiku.h b/platform/haiku/os_haiku.h deleted file mode 100644 index 64f5690dd1..0000000000 --- a/platform/haiku/os_haiku.h +++ /dev/null @@ -1,123 +0,0 @@ -/*************************************************************************/ -/* os_haiku.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 OS_HAIKU_H -#define OS_HAIKU_H - -#include "audio_driver_media_kit.h" -#include "context_gl_haiku.h" -#include "core/input/input_filter.h" -#include "drivers/unix/os_unix.h" -#include "haiku_application.h" -#include "haiku_direct_window.h" -#include "servers/audio_server.h" -#include "servers/rendering_server.h" - -class OS_Haiku : public OS_Unix { -private: - HaikuApplication *app; - HaikuDirectWindow *window; - MainLoop *main_loop; - InputDefault *input; - RenderingServer *rendering_server; - VideoMode current_video_mode; - int video_driver_index; - -#ifdef MEDIA_KIT_ENABLED - AudioDriverMediaKit driver_media_kit; -#endif - -#if defined(OPENGL_ENABLED) - ContextGL_Haiku *context_gl; -#endif - - virtual void delete_main_loop(); - -protected: - virtual int get_video_driver_count() const; - virtual const char *get_video_driver_name(int p_driver) const; - virtual int get_current_video_driver() const; - - virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); - virtual void finalize(); - - virtual void set_main_loop(MainLoop *p_main_loop); - -public: - OS_Haiku(); - void run(); - - virtual String get_name() const; - - virtual MainLoop *get_main_loop() const; - - virtual bool can_draw() const; - virtual void release_rendering_thread(); - virtual void make_rendering_thread(); - virtual void swap_buffers(); - - virtual Point2 get_mouse_position() const; - virtual int get_mouse_button_state() const; - virtual void set_cursor_shape(CursorShape p_shape); - virtual CursorShape get_cursor_shape() const; - virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); - - virtual int get_screen_count() const; - virtual int get_current_screen() const; - virtual void set_current_screen(int p_screen); - virtual Point2 get_screen_position(int p_screen = -1) const; - virtual Size2 get_screen_size(int p_screen = -1) const; - virtual void set_window_title(const String &p_title); - virtual Size2 get_window_size() const; - virtual void set_window_size(const Size2 p_size); - virtual Point2 get_window_position() const; - virtual void set_window_position(const Point2 &p_position); - virtual void set_window_fullscreen(bool p_enabled); - virtual bool is_window_fullscreen() const; - virtual void set_window_resizable(bool p_enabled); - virtual bool is_window_resizable() const; - virtual void set_window_minimized(bool p_enabled); - virtual bool is_window_minimized() const; - virtual void set_window_maximized(bool p_enabled); - virtual bool is_window_maximized() const; - - virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); - virtual VideoMode get_video_mode(int p_screen = 0) const; - virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const; - virtual String get_executable_path() const; - - virtual bool _check_internal_feature_support(const String &p_feature); - - virtual String get_config_path() const; - virtual String get_data_path() const; - virtual String get_cache_path() const; -}; - -#endif diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index a48629f720..ee7b2f4ab5 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -3,23 +3,31 @@ Import("env") iphone_lib = [ - "godot_iphone.cpp", - "os_iphone.cpp", - "semaphore_iphone.cpp", - "gl_view.mm", + "godot_iphone.mm", + "os_iphone.mm", "main.m", "app_delegate.mm", "view_controller.mm", - "game_center.mm", - "in_app_store.mm", - "icloud.mm", "ios.mm", "vulkan_context_iphone.mm", + "display_server_iphone.mm", + "joypad_iphone.mm", + "godot_view.mm", + "display_layer.mm", + "godot_app_delegate.m", + "godot_view_renderer.mm", + "godot_view_gesture_recognizer.mm", + "device_metrics.m", + "keyboard_input_view.mm", + "native_video_view.m", ] env_ios = env.Clone() ios_lib = env_ios.add_library("iphone", iphone_lib) +# (iOS) Enable module support +env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"]) + def combine_libs(target=None, source=None, env=None): lib_path = target[0].srcnode().abspath diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h index 27552d781a..2f082f1e07 100644 --- a/platform/iphone/app_delegate.h +++ b/platform/iphone/app_delegate.h @@ -28,29 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#if defined(OPENGL_ENABLED) -#import "gl_view.h" -#endif -#import "view_controller.h" #import <UIKit/UIKit.h> -#import <CoreMotion/CoreMotion.h> +@class ViewController; // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again, // so it can't be done with compilation time branching. //#if defined(OPENGL_ENABLED) //@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> { //#endif -#if defined(VULKAN_ENABLED) -@interface AppDelegate : NSObject <UIApplicationDelegate> { -#endif - //@property (strong, nonatomic) UIWindow *window; - ViewController *view_controller; - bool is_focus_out; -}; +//#if defined(VULKAN_ENABLED) +@interface AppDelegate : NSObject <UIApplicationDelegate> +//#endif @property(strong, nonatomic) UIWindow *window; - -+ (ViewController *)getViewController; +@property(strong, class, readonly, nonatomic) ViewController *viewController; @end diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index 0ac8bb7a56..c1942e77dd 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -29,654 +29,61 @@ /*************************************************************************/ #import "app_delegate.h" - -#include "core/project_settings.h" +#include "core/config/project_settings.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" -#if defined(OPENGL_ENABLED) -#import "gl_view.h" -#endif +#import "godot_view.h" #include "main/main.h" #include "os_iphone.h" +#import "view_controller.h" -#import "GameController/GameController.h" +#import <AVFoundation/AVFoundation.h> #import <AudioToolbox/AudioServices.h> -#define kFilteringFactor 0.1 #define kRenderingFrequency 60 -#define kAccelerometerFrequency 100.0 // Hz - -Error _shell_open(String); -void _set_keep_screen_on(bool p_enabled); - -Error _shell_open(String p_uri) { - NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; - - if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]]) - return ERR_CANT_OPEN; - - printf("opening url %ls\n", p_uri.c_str()); - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; - [url release]; - return OK; -}; - -void _set_keep_screen_on(bool p_enabled) { - [[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled]; -}; - -void _vibrate() { - AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); -}; - -@implementation AppDelegate - -@synthesize window; extern int gargc; extern char **gargv; -extern int iphone_main(int, int, int, char **, String); + +extern int iphone_main(int, char **, String); extern void iphone_finish(); -CMMotionManager *motionManager; -bool motionInitialised; +@implementation AppDelegate static ViewController *mainViewController = nil; -+ (ViewController *)getViewController { - return mainViewController; -} - -NSMutableDictionary *ios_joysticks = nil; -NSMutableArray *pending_ios_joysticks = nil; - -- (GCControllerPlayerIndex)getFreePlayerIndex { - bool have_player_1 = false; - bool have_player_2 = false; - bool have_player_3 = false; - bool have_player_4 = false; - - if (ios_joysticks == nil) { - NSArray *keys = [ios_joysticks allKeys]; - for (NSNumber *key in keys) { - GCController *controller = [ios_joysticks objectForKey:key]; - if (controller.playerIndex == GCControllerPlayerIndex1) { - have_player_1 = true; - } else if (controller.playerIndex == GCControllerPlayerIndex2) { - have_player_2 = true; - } else if (controller.playerIndex == GCControllerPlayerIndex3) { - have_player_3 = true; - } else if (controller.playerIndex == GCControllerPlayerIndex4) { - have_player_4 = true; - }; - }; - }; - - if (!have_player_1) { - return GCControllerPlayerIndex1; - } else if (!have_player_2) { - return GCControllerPlayerIndex2; - } else if (!have_player_3) { - return GCControllerPlayerIndex3; - } else if (!have_player_4) { - return GCControllerPlayerIndex4; - } else { - return GCControllerPlayerIndexUnset; - }; -}; - -void _ios_add_joystick(GCController *controller, AppDelegate *delegate) { - // get a new id for our controller - int joy_id = OSIPhone::get_singleton()->get_unused_joy_id(); - if (joy_id != -1) { - // assign our player index - if (controller.playerIndex == GCControllerPlayerIndexUnset) { - controller.playerIndex = [delegate getFreePlayerIndex]; - }; - - // tell Godot about our new controller - OSIPhone::get_singleton()->joy_connection_changed( - joy_id, true, [controller.vendorName UTF8String]); - - // add it to our dictionary, this will retain our controllers - [ios_joysticks setObject:controller - forKey:[NSNumber numberWithInt:joy_id]]; - - // set our input handler - [delegate setControllerInputHandler:controller]; - } else { - printf("Couldn't retrieve new joy id\n"); - }; -} - -static void on_focus_out(ViewController *view_controller, bool *is_focus_out) { - if (!*is_focus_out) { - *is_focus_out = true; - if (OS::get_singleton()->get_main_loop()) - OS::get_singleton()->get_main_loop()->notification( - MainLoop::NOTIFICATION_WM_FOCUS_OUT); - - [view_controller.view stopAnimation]; - if (OS::get_singleton()->native_video_is_playing()) { - OSIPhone::get_singleton()->native_video_focus_out(); - } - - AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton()); - if (audio) - audio->stop(); - } -} - -static void on_focus_in(ViewController *view_controller, bool *is_focus_out) { - if (*is_focus_out) { - *is_focus_out = false; - if (OS::get_singleton()->get_main_loop()) - OS::get_singleton()->get_main_loop()->notification( - MainLoop::NOTIFICATION_WM_FOCUS_IN); - - [view_controller.view startAnimation]; - if (OSIPhone::get_singleton()->native_video_is_playing()) { - OSIPhone::get_singleton()->native_video_unpause(); - } - AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton()); - if (audio) - audio->start(); - } ++ (ViewController *)viewController { + return mainViewController; } -- (void)controllerWasConnected:(NSNotification *)notification { - // create our dictionary if we don't have one yet - if (ios_joysticks == nil) { - ios_joysticks = [[NSMutableDictionary alloc] init]; - }; - - // get our controller - GCController *controller = (GCController *)notification.object; - if (controller == nil) { - printf("Couldn't retrieve new controller\n"); - } else if ([[ios_joysticks allKeysForObject:controller] count] != 0) { - printf("Controller is already registered\n"); - } else if (frame_count > 1) { - _ios_add_joystick(controller, self); - } else { - if (pending_ios_joysticks == nil) - pending_ios_joysticks = [[NSMutableArray alloc] init]; - [pending_ios_joysticks addObject:controller]; - }; -}; - -- (void)controllerWasDisconnected:(NSNotification *)notification { - if (ios_joysticks != nil) { - // find our joystick, there should be only one in our dictionary - GCController *controller = (GCController *)notification.object; - NSArray *keys = [ios_joysticks allKeysForObject:controller]; - for (NSNumber *key in keys) { - // tell Godot this joystick is no longer there - int joy_id = [key intValue]; - OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, ""); - - // and remove it from our dictionary - [ios_joysticks removeObjectForKey:key]; - }; - }; -}; - -- (int)getJoyIdForController:(GCController *)controller { - if (ios_joysticks != nil) { - // find our joystick, there should be only one in our dictionary - NSArray *keys = [ios_joysticks allKeysForObject:controller]; - for (NSNumber *key in keys) { - int joy_id = [key intValue]; - return joy_id; - }; - }; - - return -1; -}; - -- (void)setControllerInputHandler:(GCController *)controller { - // Hook in the callback handler for the correct gamepad profile. - // This is a bit of a weird design choice on Apples part. - // You need to select the most capable gamepad profile for the - // gamepad attached. - if (controller.extendedGamepad != nil) { - // The extended gamepad profile has all the input you could possibly find on - // a gamepad but will only be active if your gamepad actually has all of - // these... - controller.extendedGamepad.valueChangedHandler = ^( - GCExtendedGamepad *gamepad, GCControllerElement *element) { - int joy_id = [self getJoyIdForController:controller]; - - if (element == gamepad.buttonA) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0, - gamepad.buttonA.isPressed); - } else if (element == gamepad.buttonB) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1, - gamepad.buttonB.isPressed); - } else if (element == gamepad.buttonX) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2, - gamepad.buttonX.isPressed); - } else if (element == gamepad.buttonY) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3, - gamepad.buttonY.isPressed); - } else if (element == gamepad.leftShoulder) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_L, - gamepad.leftShoulder.isPressed); - } else if (element == gamepad.rightShoulder) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_R, - gamepad.rightShoulder.isPressed); - } else if (element == gamepad.leftTrigger) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_L2, - gamepad.leftTrigger.isPressed); - } else if (element == gamepad.rightTrigger) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_R2, - gamepad.rightTrigger.isPressed); - } else if (element == gamepad.dpad) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP, - gamepad.dpad.up.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN, - gamepad.dpad.down.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT, - gamepad.dpad.left.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT, - gamepad.dpad.right.isPressed); - }; - - InputDefault::JoyAxis jx; - jx.min = -1; - if (element == gamepad.leftThumbstick) { - jx.value = gamepad.leftThumbstick.xAxis.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LX, jx); - jx.value = -gamepad.leftThumbstick.yAxis.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LY, jx); - } else if (element == gamepad.rightThumbstick) { - jx.value = gamepad.rightThumbstick.xAxis.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RX, jx); - jx.value = -gamepad.rightThumbstick.yAxis.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RY, jx); - } else if (element == gamepad.leftTrigger) { - jx.value = gamepad.leftTrigger.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx); - } else if (element == gamepad.rightTrigger) { - jx.value = gamepad.rightTrigger.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx); - }; - }; - } else if (controller.gamepad != nil) { - // gamepad is the standard profile with 4 buttons, shoulder buttons and a - // D-pad - controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, - GCControllerElement *element) { - int joy_id = [self getJoyIdForController:controller]; - - if (element == gamepad.buttonA) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0, - gamepad.buttonA.isPressed); - } else if (element == gamepad.buttonB) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1, - gamepad.buttonB.isPressed); - } else if (element == gamepad.buttonX) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2, - gamepad.buttonX.isPressed); - } else if (element == gamepad.buttonY) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3, - gamepad.buttonY.isPressed); - } else if (element == gamepad.leftShoulder) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_L, - gamepad.leftShoulder.isPressed); - } else if (element == gamepad.rightShoulder) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_R, - gamepad.rightShoulder.isPressed); - } else if (element == gamepad.dpad) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP, - gamepad.dpad.up.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN, - gamepad.dpad.down.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT, - gamepad.dpad.left.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT, - gamepad.dpad.right.isPressed); - }; - }; -#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+, - // while we are setting that as the minimum, seems our - // build environment doesn't like it - } else if (controller.microGamepad != nil) { - // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad - controller.microGamepad.valueChangedHandler = - ^(GCMicroGamepad *gamepad, GCControllerElement *element) { - int joy_id = [self getJoyIdForController:controller]; - - if (element == gamepad.buttonA) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0, - gamepad.buttonA.isPressed); - } else if (element == gamepad.buttonX) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2, - gamepad.buttonX.isPressed); - } else if (element == gamepad.dpad) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP, - gamepad.dpad.up.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN, - gamepad.dpad.down.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT, - gamepad.dpad.left.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT, - gamepad.dpad.right.isPressed); - }; - }; -#endif - }; - - ///@TODO need to add support for controller.motion which gives us access to - /// the orientation of the device (if supported) - - ///@TODO need to add support for controllerPausedHandler which should be a - /// toggle -}; - -- (void)initGameControllers { - // get told when controllers connect, this will be called right away for - // already connected controllers - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(controllerWasConnected:) - name:GCControllerDidConnectNotification - object:nil]; - - // get told when controllers disconnect - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(controllerWasDisconnected:) - name:GCControllerDidDisconnectNotification - object:nil]; -}; - -- (void)deinitGameControllers { - [[NSNotificationCenter defaultCenter] - removeObserver:self - name:GCControllerDidConnectNotification - object:nil]; - [[NSNotificationCenter defaultCenter] - removeObserver:self - name:GCControllerDidDisconnectNotification - object:nil]; - - if (ios_joysticks != nil) { - [ios_joysticks dealloc]; - ios_joysticks = nil; - }; - - if (pending_ios_joysticks != nil) { - [pending_ios_joysticks dealloc]; - pending_ios_joysticks = nil; - }; -}; - -OS::VideoMode _get_video_mode() { - int backingWidth; - int backingHeight; -#if defined(OPENGL_ENABLED) - glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, - GL_RENDERBUFFER_WIDTH_OES, &backingWidth); - glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, - GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); -#endif - - OS::VideoMode vm; - vm.fullscreen = true; - vm.width = backingWidth; - vm.height = backingHeight; - vm.resizable = false; - return vm; -}; - -static int frame_count = 0; -- (void)drawView:(UIView *)view; -{ - - switch (frame_count) { - case 0: { - OS::get_singleton()->set_video_mode(_get_video_mode()); - - if (!OS::get_singleton()) { - exit(0); - }; - ++frame_count; - - NSString *locale_code = [[NSLocale currentLocale] localeIdentifier]; - OSIPhone::get_singleton()->set_locale( - String::utf8([locale_code UTF8String])); - - NSString *uuid; - if ([[UIDevice currentDevice] - respondsToSelector:@selector(identifierForVendor)]) { - uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; - } else { - // before iOS 6, so just generate an identifier and store it - uuid = [[NSUserDefaults standardUserDefaults] - objectForKey:@"identiferForVendor"]; - if (!uuid) { - CFUUIDRef cfuuid = CFUUIDCreate(NULL); - uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid); - CFRelease(cfuuid); - [[NSUserDefaults standardUserDefaults] - setObject:uuid - forKey:@"identifierForVendor"]; - } - } - - OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String])); - - }; break; - - case 1: { - - Main::setup2(); - ++frame_count; - - if (pending_ios_joysticks != nil) { - for (GCController *controller in pending_ios_joysticks) { - _ios_add_joystick(controller, self); - } - [pending_ios_joysticks dealloc]; - pending_ios_joysticks = nil; - } - - // this might be necessary before here - NSDictionary *dict = [[NSBundle mainBundle] infoDictionary]; - for (NSString *key in dict) { - NSObject *value = [dict objectForKey:key]; - String ukey = String::utf8([key UTF8String]); - - // we need a NSObject to Variant conversor - - if ([value isKindOfClass:[NSString class]]) { - NSString *str = (NSString *)value; - String uval = String::utf8([str UTF8String]); - - ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval); - - } else if ([value isKindOfClass:[NSNumber class]]) { - - NSNumber *n = (NSNumber *)value; - double dval = [n doubleValue]; - - ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval); - }; - // do stuff - } - - }; break; - - case 2: { - - Main::start(); - ++frame_count; - - }; break; // no fallthrough - - default: { - if (OSIPhone::get_singleton()) { - // OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1], - // accel[2]); - if (motionInitialised) { - // Just using polling approach for now, we can set this up so it sends - // data to us in intervals, might be better. See Apple reference pages - // for more details: - // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc - - // Apple splits our accelerometer date into a gravity and user movement - // component. We add them back together - CMAcceleration gravity = motionManager.deviceMotion.gravity; - CMAcceleration acceleration = - motionManager.deviceMotion.userAcceleration; - - ///@TODO We don't seem to be getting data here, is my device broken or - /// is this code incorrect? - CMMagneticField magnetic = - motionManager.deviceMotion.magneticField.field; - - ///@TODO we can access rotationRate as a CMRotationRate variable - ///(processed date) or CMGyroData (raw data), have to see what works - /// best - CMRotationRate rotation = motionManager.deviceMotion.rotationRate; - - // Adjust for screen orientation. - // [[UIDevice currentDevice] orientation] changes even if we've fixed - // our orientation which is not a good thing when you're trying to get - // your user to move the screen in all directions and want consistent - // output - - ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation] - /// is a bit of a hack. Godot obviously knows the orientation so maybe - /// we - // can use that instead? (note that left and right seem swapped) - - switch ([[UIApplication sharedApplication] statusBarOrientation]) { - case UIDeviceOrientationLandscapeLeft: { - OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, - gravity.z); - OSIPhone::get_singleton()->update_accelerometer( - -(acceleration.y + gravity.y), (acceleration.x + gravity.x), - acceleration.z + gravity.z); - OSIPhone::get_singleton()->update_magnetometer( - -magnetic.y, magnetic.x, magnetic.z); - OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, - rotation.z); - }; break; - case UIDeviceOrientationLandscapeRight: { - OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, - gravity.z); - OSIPhone::get_singleton()->update_accelerometer( - (acceleration.y + gravity.y), -(acceleration.x + gravity.x), - acceleration.z + gravity.z); - OSIPhone::get_singleton()->update_magnetometer( - magnetic.y, -magnetic.x, magnetic.z); - OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, - rotation.z); - }; break; - case UIDeviceOrientationPortraitUpsideDown: { - OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, - gravity.z); - OSIPhone::get_singleton()->update_accelerometer( - -(acceleration.x + gravity.x), (acceleration.y + gravity.y), - acceleration.z + gravity.z); - OSIPhone::get_singleton()->update_magnetometer( - -magnetic.x, magnetic.y, magnetic.z); - OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, - rotation.z); - }; break; - default: { // assume portrait - OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, - gravity.z); - OSIPhone::get_singleton()->update_accelerometer( - acceleration.x + gravity.x, acceleration.y + gravity.y, - acceleration.z + gravity.z); - OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, - magnetic.z); - OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, - rotation.z); - }; break; - }; - } - - bool quit_request = OSIPhone::get_singleton()->iterate(); - }; - - }; break; - }; -}; - -- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification( - MainLoop::NOTIFICATION_OS_MEMORY_WARNING); - } -}; - - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - CGRect rect = [[UIScreen mainScreen] bounds]; - - is_focus_out = false; + // TODO: might be required to make an early return, so app wouldn't crash because of timeout. + // TODO: logo screen is not displayed while shaders are compiling + // DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController - [application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; - // disable idle timer - // application.idleTimerDisabled = YES; + CGRect windowBounds = [[UIScreen mainScreen] bounds]; // Create a full-screen window - window = [[UIWindow alloc] initWithFrame:rect]; + self.window = [[UIWindow alloc] initWithFrame:windowBounds]; - OS::VideoMode vm = _get_video_mode(); - - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, - NSUserDomainMask, YES); + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; - int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String])); + int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String])); + if (err != 0) { // bail, things did not go very well for us, should probably output a message on screen with our error code... exit(0); - return FALSE; + return NO; }; -#if defined(OPENGL_ENABLED) - // WARNING: We must *always* create the GLView after we have constructed the - // OS with iphone_main. This allows the GLView to access project settings so - // it can properly initialize the OpenGL context - GLView *glView = [[GLView alloc] initWithFrame:rect]; - glView.delegate = self; - - view_controller = [[ViewController alloc] init]; - view_controller.view = glView; - - _set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO); - glView.useCADisplayLink = - bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; - printf("cadisaplylink: %d", glView.useCADisplayLink); - glView.animationInterval = 1.0 / kRenderingFrequency; - [glView startAnimation]; -#endif - -#if defined(VULKAN_ENABLED) - view_controller = [[ViewController alloc] init]; -#endif + ViewController *viewController = [[ViewController alloc] init]; + viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; + viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency; - window.rootViewController = view_controller; + self.window.rootViewController = viewController; // Show the window - [window makeKeyAndVisible]; - - // Configure and start accelerometer - if (!motionInitialised) { - motionManager = [[CMMotionManager alloc] init]; - if (motionManager.deviceMotionAvailable) { - motionManager.deviceMotionUpdateInterval = 1.0 / 70.0; - [motionManager startDeviceMotionUpdatesUsingReferenceFrame: - CMAttitudeReferenceFrameXMagneticNorthZVertical]; - motionInitialised = YES; - }; - }; - - [self initGameControllers]; + [self.window makeKeyAndVisible]; [[NSNotificationCenter defaultCenter] addObserver:self @@ -684,40 +91,33 @@ static int frame_count = 0; name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; - // OSIPhone::screen_width = rect.size.width - rect.origin.x; - // OSIPhone::screen_height = rect.size.height - rect.origin.y; - - mainViewController = view_controller; + mainViewController = viewController; // prevent to stop music in another background app [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; - return TRUE; + return YES; }; - (void)onAudioInterruption:(NSNotification *)notification { if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) { if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) { NSLog(@"Audio interruption began"); - on_focus_out(view_controller, &is_focus_out); + OSIPhone::get_singleton()->on_focus_out(); } else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) { NSLog(@"Audio interruption ended"); - on_focus_in(view_controller, &is_focus_out); + OSIPhone::get_singleton()->on_focus_in(); } } }; -- (void)applicationWillTerminate:(UIApplication *)application { - [self deinitGameControllers]; - - if (motionInitialised) { - ///@TODO is this the right place to clean this up? - [motionManager stopDeviceMotionUpdates]; - [motionManager release]; - motionManager = nil; - motionInitialised = NO; - }; +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING); + } +}; +- (void)applicationWillTerminate:(UIApplication *)application { iphone_finish(); }; @@ -732,16 +132,15 @@ static int frame_count = 0; // notification panel by swiping from the upper part of the screen. - (void)applicationWillResignActive:(UIApplication *)application { - on_focus_out(view_controller, &is_focus_out); + OSIPhone::get_singleton()->on_focus_out(); } - (void)applicationDidBecomeActive:(UIApplication *)application { - on_focus_in(view_controller, &is_focus_out); + OSIPhone::get_singleton()->on_focus_in(); } - (void)dealloc { - [window release]; - [super dealloc]; + self.window = nil; } @end diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 3e6c2f0ecf..0456458326 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -31,12 +31,10 @@ def get_opts(): ("IPHONESDK", "Path to the iPhone SDK", ""), BoolVariable( "use_static_mvk", - "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables validation layers)", + "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables" + " validation layers)", False, ), - BoolVariable("game_center", "Support for game center", True), - BoolVariable("store_kit", "Support for in-app store", True), - BoolVariable("icloud", "Support for iCloud", True), BoolVariable("ios_exceptions", "Enable exceptions", False), ("ios_triple", "Triple for ios toolchain", ""), ] @@ -67,7 +65,7 @@ def configure(env): elif env["target"] == "debug": env.Append(CCFLAGS=["-gdwarf-2", "-O0"]) - env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED"]) if env["use_lto"]: env.Append(CCFLAGS=["-flto"]) @@ -118,20 +116,33 @@ def configure(env): arch_flag = "i386" if env["arch"] == "x86" else env["arch"] env.Append( CCFLAGS=( - "-arch " + "-fobjc-arc -arch " + arch_flag - + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0" + + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks" + " -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=13.0" ).split() ) elif env["arch"] == "arm": detect_darwin_sdk_path("iphone", env) env.Append( - CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split() + CCFLAGS=( + "-fobjc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing" + " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" + " -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb" + ' "-DIBOutlet=__attribute__((iboutlet))"' + ' "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))"' + ' "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=11.0 -MMD -MT dependencies'.split() + ) ) elif env["arch"] == "arm64": detect_darwin_sdk_path("iphone", env) env.Append( - CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split() + CCFLAGS=( + "-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing" + " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" + " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0" + " -isysroot $IPHONESDK".split() + ) ) env.Append(CPPDEFINES=["NEED_LONG_INT"]) env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"]) @@ -143,6 +154,9 @@ def configure(env): else: env.Append(CCFLAGS=["-fno-exceptions"]) + # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation + env.Append(CCFLAGS=["-Wno-ambiguous-macro"]) + ## Link flags if env["arch"] == "x86" or env["arch"] == "x86_64": @@ -151,7 +165,7 @@ def configure(env): LINKFLAGS=[ "-arch", arch_flag, - "-mios-simulator-version-min=10.0", + "-mios-simulator-version-min=13.0", "-isysroot", "$IPHONESDK", "-Xlinker", @@ -162,9 +176,9 @@ def configure(env): ] ) elif env["arch"] == "arm": - env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"]) + env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"]) if env["arch"] == "arm64": - env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"]) + env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"]) env.Append( LINKFLAGS=[ @@ -205,20 +219,11 @@ def configure(env): ] ) - # Feature options - if env["game_center"]: - env.Append(CPPDEFINES=["GAME_CENTER_ENABLED"]) - env.Append(LINKFLAGS=["-framework", "GameKit"]) - - if env["store_kit"]: - env.Append(CPPDEFINES=["STOREKIT_ENABLED"]) - env.Append(LINKFLAGS=["-framework", "StoreKit"]) - - if env["icloud"]: - env.Append(CPPDEFINES=["ICLOUD_ENABLED"]) - env.Prepend( - CPPPATH=["$IPHONESDK/usr/include", "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers",] + CPPPATH=[ + "$IPHONESDK/usr/include", + "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers", + ] ) env["ENV"]["CODESIGN_ALLOCATE"] = "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate" @@ -228,8 +233,7 @@ def configure(env): env.Append(CPPDEFINES=["VULKAN_ENABLED"]) env.Append(LINKFLAGS=["-framework", "IOSurface"]) - if env["use_static_mvk"]: - env.Append(LINKFLAGS=["-framework", "MoltenVK"]) - env["builtin_vulkan"] = False - elif not env["builtin_vulkan"]: - env.Append(LIBS=["vulkan"]) + + # Use Static Vulkan for iOS. Dynamic Framework works fine too. + env.Append(LINKFLAGS=["-framework", "MoltenVK"]) + env["builtin_vulkan"] = False diff --git a/platform/haiku/platform_config.h b/platform/iphone/device_metrics.h index f2d5418adf..6d0ff49077 100644 --- a/platform/haiku/platform_config.h +++ b/platform/iphone/device_metrics.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* platform_config.h */ +/* device_metrics.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,9 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include <alloca.h> +#import <Foundation/Foundation.h> -// for ifaddrs.h needed in drivers/unix/ip_unix.cpp -#define _BSD_SOURCE 1 +@interface GodotDeviceMetrics : NSObject -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h" +@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList; + +@end diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java b/platform/iphone/device_metrics.m index d5919e3d9d..747872bc49 100644 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java +++ b/platform/iphone/device_metrics.m @@ -1,5 +1,5 @@ /*************************************************************************/ -/* PaymentsCache.java */ +/* device_metrics.m */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,44 +28,125 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.plugin.payment; +#import "device_metrics.h" -import android.content.Context; -import android.content.SharedPreferences; +@implementation GodotDeviceMetrics -public class PaymentsCache { - - public Context context; - - public PaymentsCache(Context context) { - this.context = context; - } - - public void setConsumableFlag(String set, String sku, Boolean flag) { - SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putBoolean(sku, flag); - editor.apply(); - } - - public boolean getConsumableFlag(String set, String sku) { - SharedPreferences sharedPref = context.getSharedPreferences( - "consumables_" + set, Context.MODE_PRIVATE); - return sharedPref.getBoolean(sku, false); - } - - public void setConsumableValue(String set, String sku, String value) { - SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putString(sku, value); - //Log.d("XXX", "Setting asset: consumables_" + set + ":" + sku); - editor.apply(); - } - - public String getConsumableValue(String set, String sku) { - SharedPreferences sharedPref = context.getSharedPreferences( - "consumables_" + set, Context.MODE_PRIVATE); - //Log.d("XXX", "Getting asset: consumables_" + set + ":" + sku); - return sharedPref.getString(sku, null); - } ++ (NSDictionary *)dpiList { + return @{ + @[ + @"iPad1,1", + @"iPad2,1", + @"iPad2,2", + @"iPad2,3", + @"iPad2,4", + ] : @132, + @[ + @"iPhone1,1", + @"iPhone1,2", + @"iPhone2,1", + @"iPad2,5", + @"iPad2,6", + @"iPad2,7", + @"iPod1,1", + @"iPod2,1", + @"iPod3,1", + ] : @163, + @[ + @"iPad3,1", + @"iPad3,2", + @"iPad3,3", + @"iPad3,4", + @"iPad3,5", + @"iPad3,6", + @"iPad4,1", + @"iPad4,2", + @"iPad4,3", + @"iPad5,3", + @"iPad5,4", + @"iPad6,3", + @"iPad6,4", + @"iPad6,7", + @"iPad6,8", + @"iPad6,11", + @"iPad6,12", + @"iPad7,1", + @"iPad7,2", + @"iPad7,3", + @"iPad7,4", + @"iPad7,5", + @"iPad7,6", + @"iPad7,11", + @"iPad7,12", + @"iPad8,1", + @"iPad8,2", + @"iPad8,3", + @"iPad8,4", + @"iPad8,5", + @"iPad8,6", + @"iPad8,7", + @"iPad8,8", + @"iPad8,9", + @"iPad8,10", + @"iPad8,11", + @"iPad8,12", + @"iPad11,3", + @"iPad11,4", + ] : @264, + @[ + @"iPhone3,1", + @"iPhone3,2", + @"iPhone3,3", + @"iPhone4,1", + @"iPhone5,1", + @"iPhone5,2", + @"iPhone5,3", + @"iPhone5,4", + @"iPhone6,1", + @"iPhone6,2", + @"iPhone7,2", + @"iPhone8,1", + @"iPhone8,4", + @"iPhone9,1", + @"iPhone9,3", + @"iPhone10,1", + @"iPhone10,4", + @"iPhone11,8", + @"iPhone12,1", + @"iPhone12,8", + @"iPad4,4", + @"iPad4,5", + @"iPad4,6", + @"iPad4,7", + @"iPad4,8", + @"iPad4,9", + @"iPad5,1", + @"iPad5,2", + @"iPad11,1", + @"iPad11,2", + @"iPod4,1", + @"iPod5,1", + @"iPod7,1", + @"iPod9,1", + ] : @326, + @[ + @"iPhone7,1", + @"iPhone8,2", + @"iPhone9,2", + @"iPhone9,4", + @"iPhone10,2", + @"iPhone10,5", + ] : @401, + @[ + @"iPhone10,3", + @"iPhone10,6", + @"iPhone11,2", + @"iPhone11,4", + @"iPhone11,6", + @"iPhone12,3", + @"iPhone12,5", + ] : @458, + }; } + +@end diff --git a/platform/haiku/context_gl_haiku.h b/platform/iphone/display_layer.h index c5d258915d..bfde8f96a3 100644 --- a/platform/haiku/context_gl_haiku.h +++ b/platform/iphone/display_layer.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* context_gl_haiku.h */ +/* display_layer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,35 +28,31 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CONTEXT_GL_HAIKU_H -#define CONTEXT_GL_HAIKU_H +#import <OpenGLES/EAGLDrawable.h> +#import <QuartzCore/QuartzCore.h> -#if defined(OPENGL_ENABLED) +@protocol DisplayLayer <NSObject> -#include "haiku_direct_window.h" -#include "haiku_gl_view.h" +- (void)renderDisplayLayer; +- (void)initializeDisplayLayer; +- (void)layoutDisplayLayer; -class ContextGL_Haiku { -private: - HaikuGLView *view; - HaikuDirectWindow *window; - - bool use_vsync; - -public: - Error initialize(); - void release_current(); - void make_current(); - void swap_buffers(); - int get_window_width(); - int get_window_height(); - - void set_use_vsync(bool p_use); - bool is_using_vsync() const; - - ContextGL_Haiku(HaikuDirectWindow *p_window); - ~ContextGL_Haiku(); -}; +@end +// An ugly workaround for iOS simulator +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR +#if defined(__IPHONE_13_0) +API_AVAILABLE(ios(13.0)) +@interface GodotMetalLayer : CAMetalLayer <DisplayLayer> +#else +@interface GodotMetalLayer : CALayer <DisplayLayer> #endif +#else +@interface GodotMetalLayer : CAMetalLayer <DisplayLayer> #endif +@end + +API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0)) +@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer> + +@end diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm new file mode 100644 index 0000000000..7420a5ebe6 --- /dev/null +++ b/platform/iphone/display_layer.mm @@ -0,0 +1,183 @@ +/*************************************************************************/ +/* display_layer.mm */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#import "display_layer.h" +#include "core/config/project_settings.h" +#include "core/os/keyboard.h" +#include "display_server_iphone.h" +#include "main/main.h" +#include "os_iphone.h" +#include "servers/audio_server.h" + +#import <AudioToolbox/AudioServices.h> +#import <GameController/GameController.h> +#import <OpenGLES/EAGL.h> +#import <OpenGLES/ES1/gl.h> +#import <OpenGLES/ES1/glext.h> +#import <QuartzCore/QuartzCore.h> +#import <UIKit/UIKit.h> + +@implementation GodotMetalLayer + +- (void)initializeDisplayLayer { +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR + if (@available(iOS 13, *)) { + // Simulator supports Metal since iOS 13 + } else { + NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering."); + } +#endif +} + +- (void)layoutDisplayLayer { +} + +- (void)renderDisplayLayer { +} + +@end + +@implementation GodotOpenGLLayer { + // The pixel dimensions of the backbuffer + GLint backingWidth; + GLint backingHeight; + + EAGLContext *context; + GLuint viewRenderbuffer, viewFramebuffer; + GLuint depthRenderbuffer; +} + +- (void)initializeDisplayLayer { + // Get our backing layer + + // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color. + self.opaque = YES; + self.drawableProperties = [NSDictionary + dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], + kEAGLDrawablePropertyRetainedBacking, + kEAGLColorFormatRGBA8, + kEAGLDrawablePropertyColorFormat, + nil]; + + // FIXME: Add Vulkan support via MoltenVK. Add fallback code back? + + // Create GL ES 2 context + if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") { + context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + NSLog(@"Setting up an OpenGL ES 2.0 context."); + if (!context) { + NSLog(@"Failed to create OpenGL ES 2.0 context!"); + return; + } + } + + if (![EAGLContext setCurrentContext:context]) { + NSLog(@"Failed to set EAGLContext!"); + return; + } + if (![self createFramebuffer]) { + NSLog(@"Failed to create frame buffer!"); + return; + } +} + +- (void)layoutDisplayLayer { + [EAGLContext setCurrentContext:context]; + [self destroyFramebuffer]; + [self createFramebuffer]; +} + +- (void)renderDisplayLayer { + [EAGLContext setCurrentContext:context]; +} + +- (void)dealloc { + if ([EAGLContext currentContext] == context) { + [EAGLContext setCurrentContext:nil]; + } + + if (context) { + context = nil; + } +} + +- (BOOL)createFramebuffer { + glGenFramebuffersOES(1, &viewFramebuffer); + glGenRenderbuffersOES(1, &viewRenderbuffer); + + glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); + glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); + // This call associates the storage for the current render buffer with the EAGLDrawable (our CAself) + // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view). + [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self]; + glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); + + glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); + glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); + + // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer. + glGenRenderbuffersOES(1, &depthRenderbuffer); + glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); + glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); + glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); + + if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { + NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); + return NO; + } + + // if (OS::get_singleton()) { + // OS::VideoMode vm; + // vm.fullscreen = true; + // vm.width = backingWidth; + // vm.height = backingHeight; + // vm.resizable = false; + // OS::get_singleton()->set_video_mode(vm); + // OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer); + // }; + // gl_view_base_fb = viewFramebuffer; + + return YES; +} + +// Clean up any buffers we have allocated. +- (void)destroyFramebuffer { + glDeleteFramebuffersOES(1, &viewFramebuffer); + viewFramebuffer = 0; + glDeleteRenderbuffersOES(1, &viewRenderbuffer); + viewRenderbuffer = 0; + + if (depthRenderbuffer) { + glDeleteRenderbuffersOES(1, &depthRenderbuffer); + depthRenderbuffer = 0; + } +} + +@end diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h new file mode 100644 index 0000000000..229b1e80db --- /dev/null +++ b/platform/iphone/display_server_iphone.h @@ -0,0 +1,202 @@ +/*************************************************************************/ +/* display_server_iphone.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 display_server_iphone_h +#define display_server_iphone_h + +#include "core/input/input.h" +#include "servers/display_server.h" + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" + +#include "vulkan_context_iphone.h" + +#import <QuartzCore/CAMetalLayer.h> +#include <vulkan/vulkan_metal.h> +#endif + +class DisplayServerIPhone : public DisplayServer { + GDCLASS(DisplayServerIPhone, DisplayServer) + + _THREAD_SAFE_CLASS_ + +#if defined(VULKAN_ENABLED) + VulkanContextIPhone *context_vulkan; + RenderingDeviceVulkan *rendering_device_vulkan; +#endif + + DisplayServer::ScreenOrientation screen_orientation; + + ObjectID window_attached_instance_id; + + Callable window_event_callback; + Callable window_resize_callback; + Callable input_event_callback; + Callable input_text_callback; + + int virtual_keyboard_height = 0; + + void perform_event(const Ref<InputEvent> &p_event); + + DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerIPhone(); + +public: + String rendering_driver; + + static DisplayServerIPhone *get_singleton(); + + static void register_iphone_driver(); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + static Vector<String> get_rendering_drivers_func(); + + // MARK: - Events + + virtual void process_events() override; + + virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + + static void _dispatch_input_events(const Ref<InputEvent> &p_event); + void send_input_event(const Ref<InputEvent> &p_event) const; + void send_input_text(const String &p_text) const; + void send_window_event(DisplayServer::WindowEvent p_event) const; + void _window_callback(const Callable &p_callable, const Variant &p_arg) const; + + // MARK: - Input + + // MARK: Touches + + void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); + void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y); + void touches_cancelled(int p_idx); + + // MARK: Keyboard + + void key(uint32_t p_key, bool p_pressed); + + // MARK: Motion + + void update_gravity(float p_x, float p_y, float p_z); + void update_accelerometer(float p_x, float p_y, float p_z); + void update_magnetometer(float p_x, float p_y, float p_z); + void update_gyroscope(float p_x, float p_y, float p_z); + + // MARK: - + + virtual bool has_feature(Feature p_feature) const override; + virtual String get_name() const override; + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + + virtual int get_screen_count() const override; + virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + virtual Vector<DisplayServer::WindowID> get_window_list() const override; + + virtual WindowID + get_window_at_screen_position(const Point2i &p_position) const override; + + virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; + virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; + + virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; + + virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; + virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; + + virtual float screen_get_max_scale() const override; + + virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override; + virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override; + + virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; + + virtual bool can_any_window_draw() const override; + + virtual bool screen_is_touchscreen(int p_screen) const override; + + virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override; + virtual void virtual_keyboard_hide() override; + + void virtual_keyboard_set_height(int height); + virtual int virtual_keyboard_get_height() const override; + + virtual void clipboard_set(const String &p_text) override; + virtual String clipboard_get() const override; + + virtual void screen_set_keep_on(bool p_enable) override; + virtual bool screen_is_kept_on() const override; + + virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override; + virtual bool native_video_is_playing() const override; + virtual void native_video_pause() override; + virtual void native_video_unpause() override; + virtual void native_video_stop() override; + + void resize_window(CGSize size); +}; + +#endif /* display_server_iphone_h */ diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm new file mode 100644 index 0000000000..d47d131719 --- /dev/null +++ b/platform/iphone/display_server_iphone.mm @@ -0,0 +1,648 @@ +/*************************************************************************/ +/* display_server_iphone.mm */ +/*************************************************************************/ +/* 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 "display_server_iphone.h" +#import "app_delegate.h" +#include "core/config/project_settings.h" +#include "core/io/file_access_pack.h" +#import "device_metrics.h" +#import "godot_view.h" +#include "ios.h" +#import "keyboard_input_view.h" +#import "native_video_view.h" +#include "os_iphone.h" +#import "view_controller.h" + +#import <Foundation/Foundation.h> +#import <sys/utsname.h> + +static const float kDisplayServerIPhoneAcceleration = 1; + +DisplayServerIPhone *DisplayServerIPhone::get_singleton() { + return (DisplayServerIPhone *)DisplayServer::get_singleton(); +} + +DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + rendering_driver = p_rendering_driver; + +#if defined(OPENGL_ENABLED) + // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented + // again, + + if (rendering_driver == "opengl_es") { + bool gl_initialization_error = false; + + // FIXME: Add Vulkan support via MoltenVK. Add fallback code back? + + 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.", "Unable to initialize video driver"); + // return ERR_UNAVAILABLE; + } + + // rendering_server = memnew(RenderingServerRaster); + // // FIXME: Reimplement threaded rendering + // if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { + // rendering_server = memnew(RenderingServerWrapMT(rendering_server, + // false)); + // } + // rendering_server->init(); + // rendering_server->cursor_set_visible(false, 0); + + // reset this to what it should be, it will have been set to 0 after + // rendering_server->init() is called + // RasterizerStorageGLES2::system_fbo = gl_view_base_fb; + } +#endif + +#if defined(VULKAN_ENABLED) + rendering_driver = "vulkan"; + + context_vulkan = nullptr; + rendering_device_vulkan = nullptr; + + if (rendering_driver == "vulkan") { + context_vulkan = memnew(VulkanContextIPhone); + if (context_vulkan->initialize() != OK) { + memdelete(context_vulkan); + context_vulkan = nullptr; + ERR_FAIL_MSG("Failed to initialize Vulkan context"); + } + + CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"]; + + if (!layer) { + ERR_FAIL_MSG("Failed to create iOS rendering layer."); + } + + Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale(); + if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) { + memdelete(context_vulkan); + context_vulkan = nullptr; + ERR_FAIL_MSG("Failed to create Vulkan window."); + } + + rendering_device_vulkan = memnew(RenderingDeviceVulkan); + rendering_device_vulkan->initialize(context_vulkan); + + RasterizerRD::make_current(); + } +#endif + + bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)); + screen_set_keep_on(keep_screen_on); + + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + + r_error = OK; +} + +DisplayServerIPhone::~DisplayServerIPhone() { +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); + rendering_device_vulkan = NULL; + } + + if (context_vulkan) { + context_vulkan->window_destroy(MAIN_WINDOW_ID); + memdelete(context_vulkan); + context_vulkan = NULL; + } + } +#endif +} + +DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); +} + +Vector<String> DisplayServerIPhone::get_rendering_drivers_func() { + Vector<String> drivers; + +#if defined(VULKAN_ENABLED) + drivers.push_back("vulkan"); +#endif +#if defined(OPENGL_ENABLED) + drivers.push_back("opengl_es"); +#endif + + return drivers; +} + +void DisplayServerIPhone::register_iphone_driver() { + register_create_function("iphone", create_func, get_rendering_drivers_func); +} + +// MARK: Events + +void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { + window_resize_callback = p_callable; +} + +void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { + window_event_callback = p_callable; +} +void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { + input_event_callback = p_callable; +} + +void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { + input_text_callback = p_callable; +} + +void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { + // Probably not supported for iOS +} + +void DisplayServerIPhone::process_events() { +} + +void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) { + DisplayServerIPhone::get_singleton()->send_input_event(p_event); +} + +void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const { + _window_callback(input_event_callback, p_event); +} + +void DisplayServerIPhone::send_input_text(const String &p_text) const { + _window_callback(input_text_callback, p_text); +} + +void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const { + _window_callback(window_event_callback, int(p_event)); +} + +void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const { + if (!p_callable.is_null()) { + const Variant *argp = &p_arg; + Variant ret; + Callable::CallError ce; + p_callable.call((const Variant **)&argp, 1, ret, ce); + } +} + +// MARK: - Input + +// MARK: Touches + +void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) { + if (!GLOBAL_DEF("debug/disable_touch", false)) { + Ref<InputEventScreenTouch> ev; + ev.instance(); + + ev->set_index(p_idx); + ev->set_pressed(p_pressed); + ev->set_position(Vector2(p_x, p_y)); + perform_event(ev); + } +}; + +void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) { + if (!GLOBAL_DEF("debug/disable_touch", false)) { + Ref<InputEventScreenDrag> ev; + ev.instance(); + ev->set_index(p_idx); + ev->set_position(Vector2(p_x, p_y)); + ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); + perform_event(ev); + }; +}; + +void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) { + Input::get_singleton()->parse_input_event(p_event); +}; + +void DisplayServerIPhone::touches_cancelled(int p_idx) { + touch_press(p_idx, -1, -1, false, false); +}; + +// MARK: Keyboard + +void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) { + Ref<InputEventKey> ev; + ev.instance(); + ev->set_echo(false); + ev->set_pressed(p_pressed); + ev->set_keycode(p_key); + ev->set_physical_keycode(p_key); + ev->set_unicode(p_key); + perform_event(ev); +}; + +// MARK: Motion + +void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) { + Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); +}; + +void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) { + // Found out the Z should not be negated! Pass as is! + Vector3 v_accelerometer = Vector3( + p_x / kDisplayServerIPhoneAcceleration, + p_y / kDisplayServerIPhoneAcceleration, + p_z / kDisplayServerIPhoneAcceleration); + + Input::get_singleton()->set_accelerometer(v_accelerometer); +}; + +void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) { + Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z)); +}; + +void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) { + Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z)); +}; + +// MARK: - + +bool DisplayServerIPhone::has_feature(Feature p_feature) const { + switch (p_feature) { + // case FEATURE_CONSOLE_WINDOW: + // case FEATURE_CURSOR_SHAPE: + // case FEATURE_CUSTOM_CURSOR_SHAPE: + // case FEATURE_GLOBAL_MENU: + // case FEATURE_HIDPI: + // case FEATURE_ICON: + // case FEATURE_IME: + // case FEATURE_MOUSE: + // case FEATURE_MOUSE_WARP: + // case FEATURE_NATIVE_DIALOG: + // case FEATURE_NATIVE_ICON: + // case FEATURE_NATIVE_VIDEO: + // case FEATURE_WINDOW_TRANSPARENCY: + case FEATURE_CLIPBOARD: + case FEATURE_KEEP_SCREEN_ON: + case FEATURE_ORIENTATION: + case FEATURE_TOUCHSCREEN: + case FEATURE_VIRTUAL_KEYBOARD: + return true; + default: + return false; + } +} + +String DisplayServerIPhone::get_name() const { + return "iPhone"; +} + +void DisplayServerIPhone::alert(const String &p_alert, const String &p_title) { + const CharString utf8_alert = p_alert.utf8(); + const CharString utf8_title = p_title.utf8(); + iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + +int DisplayServerIPhone::get_screen_count() const { + return 1; +} + +Point2i DisplayServerIPhone::screen_get_position(int p_screen) const { + return Size2i(); +} + +Size2i DisplayServerIPhone::screen_get_size(int p_screen) const { + CALayer *layer = AppDelegate.viewController.godotView.renderingLayer; + + if (!layer) { + return Size2i(); + } + + return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen); +} + +Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const { + if (@available(iOS 11, *)) { + UIEdgeInsets insets = UIEdgeInsetsZero; + UIView *view = AppDelegate.viewController.godotView; + + if ([view respondsToSelector:@selector(safeAreaInsets)]) { + insets = [view safeAreaInsets]; + } + + float scale = screen_get_scale(p_screen); + Size2i insets_position = Size2i(insets.left, insets.top) * scale; + Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale; + + return Rect2i(screen_get_position(p_screen) + insets_position, screen_get_size(p_screen) - insets_size); + } else { + return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); + } +} + +int DisplayServerIPhone::screen_get_dpi(int p_screen) const { + struct utsname systemInfo; + uname(&systemInfo); + + NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + + NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList]; + + for (NSArray *keyArray in iOSModelToDPI) { + if ([keyArray containsObject:string]) { + NSNumber *value = iOSModelToDPI[keyArray]; + return [value intValue]; + } + } + + // If device wasn't found in dictionary + // make a best guess from device metrics. + CGFloat scale = [UIScreen mainScreen].scale; + + UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom; + + switch (idiom) { + case UIUserInterfaceIdiomPad: + return scale == 2 ? 264 : 132; + case UIUserInterfaceIdiomPhone: { + if (scale == 3) { + CGFloat nativeScale = [UIScreen mainScreen].nativeScale; + return nativeScale == 3 ? 458 : 401; + } + + return 326; + } + default: + return 72; + } +} + +float DisplayServerIPhone::screen_get_scale(int p_screen) const { + return [UIScreen mainScreen].nativeScale; +} + +Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const { + Vector<DisplayServer::WindowID> list; + list.push_back(MAIN_WINDOW_ID); + return list; +} + +DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const { + return MAIN_WINDOW_ID; +} + +void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { + window_attached_instance_id = p_instance; +} + +ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const { + return window_attached_instance_id; +} + +void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) { + // Probably not supported for iOS +} + +int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const { + return SCREEN_OF_MAIN_WINDOW; +} + +void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) { + // Probably not supported for iOS +} + +Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const { + return Point2i(); +} + +void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) { + // Probably not supported for single window iOS app +} + +void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) { + // Probably not supported for iOS +} + +void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) { + // Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const { + CGRect screenBounds = [UIScreen mainScreen].bounds; + return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale(); +} + +Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const { + return window_get_size(p_window); +} + +void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) { + // Probably not supported for iOS +} + +DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const { + return WindowMode::WINDOW_MODE_FULLSCREEN; +} + +bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const { + return false; +} + +void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { + // Probably not supported for iOS +} + +bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const { + return false; +} + +void DisplayServerIPhone::window_request_attention(WindowID p_window) { + // Probably not supported for iOS +} + +void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) { + // Probably not supported for iOS +} + +float DisplayServerIPhone::screen_get_max_scale() const { + return screen_get_scale(SCREEN_OF_MAIN_WINDOW); +}; + +void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { + screen_orientation = p_orientation; +} + +DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const { + return screen_orientation; +} + +bool DisplayServerIPhone::window_can_draw(WindowID p_window) const { + return true; +} + +bool DisplayServerIPhone::can_any_window_draw() const { + return true; +} + +bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const { + return true; +} + +void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) { + NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()]; + + [AppDelegate.viewController.keyboardView + becomeFirstResponderWithString:existingString + multiline:p_multiline + cursorStart:p_cursor_start + cursorEnd:p_cursor_end]; +} + +void DisplayServerIPhone::virtual_keyboard_hide() { + [AppDelegate.viewController.keyboardView resignFirstResponder]; +} + +void DisplayServerIPhone::virtual_keyboard_set_height(int height) { + virtual_keyboard_height = height * screen_get_max_scale(); +} + +int DisplayServerIPhone::virtual_keyboard_get_height() const { + return virtual_keyboard_height; +} + +void DisplayServerIPhone::clipboard_set(const String &p_text) { + [UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()]; +} + +String DisplayServerIPhone::clipboard_get() const { + NSString *text = [UIPasteboard generalPasteboard].string; + + return String::utf8([text UTF8String]); +} + +void DisplayServerIPhone::screen_set_keep_on(bool p_enable) { + [UIApplication sharedApplication].idleTimerDisabled = p_enable; +} + +bool DisplayServerIPhone::screen_is_kept_on() const { + return [UIApplication sharedApplication].idleTimerDisabled; +} + +Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) { + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + bool exists = f && f->is_open(); + + String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir(); + + if (!exists) { + return FAILED; + } + + String tempFile = OSIPhone::get_singleton()->get_user_data_dir(); + + if (p_path.begins_with("res://")) { + if (PackedData::get_singleton()->has_path(p_path)) { + printf("Unable to play %s using the native player as it resides in a .pck file\n", p_path.utf8().get_data()); + return ERR_INVALID_PARAMETER; + } else { + p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path()); + } + } else if (p_path.begins_with("user://")) { + p_path = p_path.replace("user:/", user_data_dir); + } + + memdelete(f); + + printf("Playing video: %s\n", p_path.utf8().get_data()); + + String file_path = ProjectSettings::get_singleton()->globalize_path(p_path); + + NSString *filePath = [[NSString alloc] initWithUTF8String:file_path.utf8().get_data()]; + NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()]; + NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()]; + + if (![AppDelegate.viewController playVideoAtPath:filePath + volume:p_volume + audio:audioTrack + subtitle:subtitleTrack]) { + return OK; + } + + return FAILED; +} + +bool DisplayServerIPhone::native_video_is_playing() const { + return [AppDelegate.viewController.videoView isVideoPlaying]; +} + +void DisplayServerIPhone::native_video_pause() { + if (native_video_is_playing()) { + [AppDelegate.viewController.videoView pauseVideo]; + } +} + +void DisplayServerIPhone::native_video_unpause() { + [AppDelegate.viewController.videoView unpauseVideo]; +}; + +void DisplayServerIPhone::native_video_stop() { + if (native_video_is_playing()) { + [AppDelegate.viewController.videoView stopVideo]; + } +} + +void DisplayServerIPhone::resize_window(CGSize viewSize) { + Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); + +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + if (context_vulkan) { + context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y); + } + } +#endif + + Variant resize_rect = Rect2i(Point2i(), size); + _window_callback(window_resize_callback, resize_rect); +} diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 3efe338ac7..62fcfffbb7 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -29,31 +29,39 @@ /*************************************************************************/ #include "export.h" + +#include "core/config/project_settings.h" #include "core/io/image_loader.h" #include "core/io/marshalls.h" #include "core/io/resource_saver.h" #include "core/io/zip_io.h" #include "core/os/file_access.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "core/version.h" #include "editor/editor_export.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "main/splash.gen.h" #include "platform/iphone/logo.gen.h" +#include "platform/iphone/plugin/godot_plugin_config.h" #include "string.h" #include <sys/stat.h> class EditorExportPlatformIOS : public EditorExportPlatform { - GDCLASS(EditorExportPlatformIOS, EditorExportPlatform); int version_code; Ref<ImageTexture> logo; + // Plugins + volatile bool plugins_changed; + Thread *check_for_changes_thread; + volatile bool quit_request; + Mutex plugins_lock; + Vector<PluginConfig> plugins; + typedef Error (*FileHandler)(String p_file, void *p_userdata); static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata); static Error _codesign(String p_file, void *p_userdata); @@ -70,16 +78,13 @@ class EditorExportPlatformIOS : public EditorExportPlatform { String modules_fileref; String modules_buildphase; String modules_buildgrp; + Vector<String> capabilities; }; struct ExportArchitecture { - String name; - bool is_default; + bool is_default = false; - ExportArchitecture() : - name(""), - is_default(false) { - } + ExportArchitecture() {} ExportArchitecture(String p_name, bool p_is_default) { name = p_name; @@ -90,24 +95,27 @@ class EditorExportPlatformIOS : public EditorExportPlatform { struct IOSExportAsset { String exported_path; bool is_framework; // framework is anything linked to the binary, otherwise it's a resource + bool should_embed; }; String _get_additional_plist_content(); String _get_linker_flags(); String _get_cpp_code(); void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug); - Error _export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir); + Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir); + Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir); Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir); Vector<ExportArchitecture> _get_supported_architectures(); Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset); void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets); - Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets); + Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets); + Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets); Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets); + Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug); bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { - String pname = p_package; if (pname.length() == 0) { @@ -118,7 +126,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { } for (int i = 0; i < pname.length(); i++) { - CharType c = pname[i]; + char32_t c = pname[i]; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) { if (r_error) { *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); @@ -130,44 +138,162 @@ class EditorExportPlatformIOS : public EditorExportPlatform { return true; } + static void _check_for_changes_poll_thread(void *ud) { + EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud; + + while (!ea->quit_request) { + // Nothing to do if we already know the plugins have changed. + if (!ea->plugins_changed) { + MutexLock lock(ea->plugins_lock); + + Vector<PluginConfig> loaded_plugins = get_plugins(); + + if (ea->plugins.size() != loaded_plugins.size()) { + ea->plugins_changed = true; + } else { + for (int i = 0; i < ea->plugins.size(); i++) { + if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) { + ea->plugins_changed = true; + break; + } + } + } + } + + uint64_t wait = 3000000; + uint64_t time = OS::get_singleton()->get_ticks_usec(); + while (OS::get_singleton()->get_ticks_usec() - time < wait) { + OS::get_singleton()->delay_usec(300000); + + if (ea->quit_request) { + break; + } + } + } + } + protected: - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features); - virtual void get_export_options(List<ExportOption> *r_options); + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override; + virtual void get_export_options(List<ExportOption> *r_options) override; public: - virtual String get_name() const { return "iOS"; } - virtual String get_os_name() const { return "iOS"; } - virtual Ref<Texture2D> get_logo() const { return logo; } + virtual String get_name() const override { return "iOS"; } + virtual String get_os_name() const override { return "iOS"; } + virtual Ref<Texture2D> get_logo() const override { return logo; } + + virtual bool should_update_export_options() override { + bool export_options_changed = plugins_changed; + if (export_options_changed) { + // don't clear unless we're reporting true, to avoid race + plugins_changed = false; + } + return export_options_changed; + } - virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { List<String> list; list.push_back("ipa"); return list; } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); - virtual void add_module_code(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &p_name, const String &p_fid, const String &p_gid); + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const; - - virtual void get_platform_features(List<String> *r_features) { + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; + virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("mobile"); r_features->push_back("iOS"); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { } EditorExportPlatformIOS(); ~EditorExportPlatformIOS(); + + /// List the gdip files in the directory specified by the p_path parameter. + static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) { + Vector<String> dir_files; + DirAccessRef da = DirAccess::open(p_path); + if (da) { + da->list_dir_begin(); + while (true) { + String file = da->get_next(); + if (file.empty()) { + break; + } + + if (file == "." || file == "..") { + continue; + } + + if (da->current_is_hidden()) { + continue; + } + + if (da->current_is_dir()) { + if (p_check_directories) { + Vector<String> directory_files = list_plugin_config_files(p_path.plus_file(file), false); + for (int i = 0; i < directory_files.size(); ++i) { + dir_files.push_back(file.plus_file(directory_files[i])); + } + } + + continue; + } + + if (file.ends_with(PLUGIN_CONFIG_EXT)) { + dir_files.push_back(file); + } + } + da->list_dir_end(); + } + + return dir_files; + } + + static Vector<PluginConfig> get_plugins() { + Vector<PluginConfig> loaded_plugins; + + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins"); + + if (DirAccess::exists(plugins_dir)) { + Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true); + + if (!plugins_filenames.empty()) { + Ref<ConfigFile> config_file = memnew(ConfigFile); + for (int i = 0; i < plugins_filenames.size(); i++) { + PluginConfig config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i])); + if (config.valid_config) { + loaded_plugins.push_back(config); + } else { + print_error("Invalid plugin config file " + plugins_filenames[i]); + } + } + } + } + + return loaded_plugins; + } + + static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) { + Vector<PluginConfig> enabled_plugins; + Vector<PluginConfig> all_plugins = get_plugins(); + for (int i = 0; i < all_plugins.size(); i++) { + PluginConfig plugin = all_plugins[i]; + bool enabled = p_presets->get("plugins/" + plugin.name); + if (enabled) { + enabled_plugins.push_back(plugin); + } + } + + return enabled_plugins; + } }; void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { - String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); - if (driver == "GLES2") { - r_features->push_back("etc"); - } else if (driver == "Vulkan") { + r_features->push_back("pvrtc"); + if (driver == "Vulkan") { // FIXME: Review if this is correct. r_features->push_back("etc2"); } @@ -209,7 +335,6 @@ static const LoadingScreenInfo loading_screen_infos[] = { }; void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); @@ -225,18 +350,22 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/arkit"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/camera"), false)); + Vector<PluginConfig> found_plugins = get_plugins(); + + for (int i = 0; i < found_plugins.size(); i++) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false)); + } + + plugins_changed = false; + plugins = found_plugins; r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/game_center"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/in_app_purchases"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false)); @@ -263,6 +392,13 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with retina display + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale To Fit,Scale To Fill,Scale"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color())); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "launch_screens/generate_missing"), false)); for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { @@ -282,6 +418,12 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ "ad-hoc", "enterprise" }; + static const String storyboard_image_scale_mode[] = { + "center", + "scaleAspectFit", + "scaleAspectFill", + "scaleToFill" + }; String str; String strnew; str.parse_utf8((const char *)pfile.ptr(), pfile.size()); @@ -301,8 +443,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$name", p_config.pkg_name) + "\n"; } else if (lines[i].find("$info") != -1) { strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n"; - } else if (lines[i].find("$identifier") != -1) { - strnew += lines[i].replace("$identifier", p_preset->get("application/identifier")) + "\n"; + } else if (lines[i].find("$bundle_identifier") != -1) { + strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n"; } else if (lines[i].find("$version") != -1) { @@ -339,33 +481,23 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "<true/>" : "<false/>") + "\n"; } else if (lines[i].find("$docs_sharing") != -1) { strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "<true/>" : "<false/>") + "\n"; - } else if (lines[i].find("$access_wifi") != -1) { - bool is_on = p_preset->get("capabilities/access_wifi"); - strnew += lines[i].replace("$access_wifi", is_on ? "1" : "0") + "\n"; - } else if (lines[i].find("$game_center") != -1) { - bool is_on = p_preset->get("capabilities/game_center"); - strnew += lines[i].replace("$game_center", is_on ? "1" : "0") + "\n"; - } else if (lines[i].find("$in_app_purchases") != -1) { - bool is_on = p_preset->get("capabilities/in_app_purchases"); - strnew += lines[i].replace("$in_app_purchases", is_on ? "1" : "0") + "\n"; - } else if (lines[i].find("$push_notifications") != -1) { + } else if (lines[i].find("$entitlements_push_notifications") != -1) { bool is_on = p_preset->get("capabilities/push_notifications"); - strnew += lines[i].replace("$push_notifications", is_on ? "1" : "0") + "\n"; + strnew += lines[i].replace("$entitlements_push_notifications", is_on ? "<key>aps-environment</key><string>development</string>" : "") + "\n"; } else if (lines[i].find("$required_device_capabilities") != -1) { String capabilities; // I've removed armv7 as we can run on 64bit only devices // Note that capabilities listed here are requirements for the app to be installed. // They don't enable anything. + Vector<String> capabilities_list = p_config.capabilities; - if ((bool)p_preset->get("capabilities/arkit")) { - capabilities += "<string>arkit</string>\n"; - } - if ((bool)p_preset->get("capabilities/game_center")) { - capabilities += "<string>gamekit</string>\n"; + if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) { + capabilities_list.push_back("wifi"); } - if ((bool)p_preset->get("capabilities/access_wifi")) { - capabilities += "<string>wifi</string>\n"; + + for (int idx = 0; idx < capabilities_list.size(); idx++) { + capabilities += "<string>" + capabilities_list[idx] + "</string>\n"; } strnew += lines[i].replace("$required_device_capabilities", capabilities); @@ -395,6 +527,60 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ } else if (lines[i].find("$photolibrary_usage_description") != -1) { String description = p_preset->get("privacy/photolibrary_usage_description"); strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n"; + } else if (lines[i].find("$plist_launch_screen_name") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>" : ""; + strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n"; + } else if (lines[i].find("$pbx_launch_screen_file_reference") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };" : ""; + strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n"; + } else if (lines[i].find("$pbx_launch_screen_copy_files") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */," : ""; + strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n"; + } else if (lines[i].find("$pbx_launch_screen_build_phase") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */," : ""; + strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n"; + } else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };" : ""; + strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n"; + } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "" : "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;"; + strnew += lines[i].replace("$pbx_launch_image_usage_setting", value) + "\n"; + } else if (lines[i].find("$launch_screen_image_mode") != -1) { + int image_scale_mode = p_preset->get("storyboard/image_scale_mode"); + String value; + + switch (image_scale_mode) { + case 0: { + String logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + bool is_on = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize"); + // If custom logo is not specified, Godot does not scale default one, so we should do the same. + value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center"; + } break; + default: { + value = storyboard_image_scale_mode[image_scale_mode - 1]; + } + } + + strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n"; + } else if (lines[i].find("$launch_screen_background_color") != -1) { + bool use_custom = p_preset->get("storyboard/use_custom_bg_color"); + Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : ProjectSettings::get_singleton()->get("application/boot_splash/bg_color"); + const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\""; + + Dictionary value_dictionary; + value_dictionary["red"] = color.r; + value_dictionary["green"] = color.g; + value_dictionary["blue"] = color.b; + value_dictionary["alpha"] = color.a; + String value = value_format.format(value_dictionary, "$_"); + + strnew += lines[i].replace("$launch_screen_background_color", value) + "\n"; } else { strnew += lines[i] + "\n"; } @@ -423,7 +609,9 @@ String EditorExportPlatformIOS::_get_linker_flags() { String result; for (int i = 0; i < export_plugins.size(); ++i) { String flags = export_plugins[i]->get_ios_linker_flags(); - if (flags.length() == 0) continue; + if (flags.length() == 0) { + continue; + } if (result.length() > 0) { result += ' '; } @@ -443,7 +631,6 @@ String EditorExportPlatformIOS::_get_cpp_code() { } void EditorExportPlatformIOS::_blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot) { - ERR_FAIL_COND(p_dst.is_null()); ERR_FAIL_COND(p_src.is_null()); @@ -456,8 +643,12 @@ void EditorExportPlatformIOS::_blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p int xs = (x_pos >= 0) ? 0 : -x_pos; int ys = (y_pos >= 0) ? 0 : -y_pos; - if (sw + x_pos > p_dst->get_width()) sw = p_dst->get_width() - x_pos; - if (sh + y_pos > p_dst->get_height()) sh = p_dst->get_height() - y_pos; + if (sw + x_pos > p_dst->get_width()) { + sw = p_dst->get_width() - x_pos; + } + if (sh + y_pos > p_dst->get_height()) { + sh = p_dst->get_height() - y_pos; + } for (int y = ys; y < sh; y++) { for (int x = xs; x < sw; x++) { @@ -499,7 +690,6 @@ static const IconInfo icon_infos[] = { { "optional_icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40", false }, { "optional_icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40", false } - }; Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) { @@ -591,7 +781,75 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr return OK; } -Error EditorExportPlatformIOS::_export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { +Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { + const String custom_launch_image_2x = p_preset->get("storyboard/custom_image@2x"); + const String custom_launch_image_3x = p_preset->get("storyboard/custom_image@3x"); + + if (custom_launch_image_2x.length() > 0 && custom_launch_image_3x.length() > 0) { + Ref<Image> image; + String image_path = p_dest_dir.plus_file("splash@2x.png"); + image.instance(); + Error err = image->load(custom_launch_image_2x); + + if (err) { + image.unref(); + return err; + } + + if (image->save_png(image_path) != OK) { + return ERR_FILE_CANT_WRITE; + } + + image.unref(); + image_path = p_dest_dir.plus_file("splash@3x.png"); + image.instance(); + err = image->load(custom_launch_image_3x); + + if (err) { + image.unref(); + return err; + } + + if (image->save_png(image_path) != OK) { + return ERR_FILE_CANT_WRITE; + } + } else { + Ref<Image> splash; + + const String splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + + if (!splash_path.empty()) { + splash.instance(); + const Error err = splash->load(splash_path); + if (err) { + splash.unref(); + } + } + + if (splash.is_null()) { + splash = Ref<Image>(memnew(Image(boot_splash_png))); + } + + // Using same image for both @2x and @3x + // because Godot's own boot logo uses single image for all resolutions. + // Also not using @1x image, because devices using this image variant + // are not supported by iOS 9, which is minimal target. + const String splash_png_path_2x = p_dest_dir.plus_file("splash@2x.png"); + const String splash_png_path_3x = p_dest_dir.plus_file("splash@3x.png"); + + if (splash->save_png(splash_png_path_2x) != OK) { + return ERR_FILE_CANT_WRITE; + } + + if (splash->save_png(splash_png_path_3x) != OK) { + return ERR_FILE_CANT_WRITE; + } + } + + return OK; +} + +Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { DirAccess *da = DirAccess::open(p_dest_dir); ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); @@ -776,15 +1034,6 @@ struct ExportLibsData { }; void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) { - Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); - Vector<String> frameworks; - for (int i = 0; i < export_plugins.size(); ++i) { - Vector<String> plugin_frameworks = export_plugins[i]->get_ios_frameworks(); - for (int j = 0; j < plugin_frameworks.size(); ++j) { - frameworks.push_back(plugin_frameworks[j]); - } - } - // that is just a random number, we just need Godot IDs not to clash with // existing IDs in the project. PbxId current_id = { 0x58938401, 0, 0 }; @@ -793,17 +1042,37 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese String pbx_frameworks_refs; String pbx_resources_build; String pbx_resources_refs; + String pbx_embeded_frameworks; const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") + "$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"<group>\"; };\n"; + for (int i = 0; i < p_additional_assets.size(); ++i) { + String additional_asset_info_format = file_info_format; + String build_id = (++current_id).str(); String ref_id = (++current_id).str(); + String framework_id = ""; + const IOSExportAsset &asset = p_additional_assets[i]; String type; if (asset.exported_path.ends_with(".framework")) { + if (asset.should_embed) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + } + type = "wrapper.framework"; + } else if (asset.exported_path.ends_with(".xcframework")) { + if (asset.should_embed) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + } + + type = "wrapper.xcframework"; } else if (asset.exported_path.ends_with(".dylib")) { type = "compiled.mach-o.dylib"; } else if (asset.exported_path.ends_with(".a")) { @@ -828,40 +1097,22 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese format_dict["name"] = asset.exported_path.get_file(); format_dict["file_path"] = asset.exported_path; format_dict["file_type"] = type; - pbx_files += file_info_format.format(format_dict, "$_"); + if (framework_id.length() > 0) { + format_dict["framework_id"] = framework_id; + } + pbx_files += additional_asset_info_format.format(format_dict, "$_"); } // Note, frameworks like gamekit are always included in our project.pbxprof file // even if turned off in capabilities. - // We do need our ARKit framework - if ((bool)p_preset->get("capabilities/arkit")) { - String build_id = (++current_id).str(); - String ref_id = (++current_id).str(); - - if (pbx_frameworks_build.length() > 0) { - pbx_frameworks_build += ",\n"; - pbx_frameworks_refs += ",\n"; - } - - pbx_frameworks_build += build_id; - pbx_frameworks_refs += ref_id; - - Dictionary format_dict; - format_dict["build_id"] = build_id; - format_dict["ref_id"] = ref_id; - format_dict["name"] = "ARKit.framework"; - format_dict["file_path"] = "System/Library/Frameworks/ARKit.framework"; - format_dict["file_type"] = "wrapper.framework"; - pbx_files += file_info_format.format(format_dict, "$_"); - } - String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size()); str = str.replace("$additional_pbx_files", pbx_files); str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build); str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs); str = str.replace("$additional_pbx_resources_build", pbx_resources_build); str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs); + str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks); CharString cs = str.utf8(); p_project_data.resize(cs.size() - 1); @@ -870,51 +1121,175 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese } } -Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets) { +Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); + + String binary_name = p_out_dir.get_file().get_basename(); + + DirAccess *da = DirAccess::create_for_path(p_asset); + if (!da) { + memdelete(filesystem_da); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + "."); + } + bool file_exists = da->file_exists(p_asset); + bool dir_exists = da->dir_exists(p_asset); + if (!file_exists && !dir_exists) { + memdelete(da); + memdelete(filesystem_da); + return ERR_FILE_NOT_FOUND; + } + + String base_dir = p_asset.get_base_dir().replace("res://", ""); + String destination_dir; + String destination; + String asset_path; + + bool create_framework = false; + + if (p_is_framework && p_asset.ends_with(".dylib")) { + // For iOS we need to turn .dylib into .framework + // to be able to send application to AppStore + asset_path = String("dylibs").plus_file(base_dir); + + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_basename().get_file(); + } else { + file_name = *p_custom_file_name; + } + + String framework_name = file_name + ".framework"; + + asset_path = asset_path.plus_file(framework_name); + destination_dir = p_out_dir.plus_file(asset_path); + destination = destination_dir.plus_file(file_name); + create_framework = true; + } else if (p_is_framework && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) { + asset_path = String("dylibs").plus_file(base_dir); + + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_file(); + } else { + file_name = *p_custom_file_name; + } + + asset_path = asset_path.plus_file(file_name); + destination_dir = p_out_dir.plus_file(asset_path); + destination = destination_dir; + } else { + asset_path = base_dir; + + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_file(); + } else { + file_name = *p_custom_file_name; + } + + destination_dir = p_out_dir.plus_file(asset_path); + asset_path = asset_path.plus_file(file_name); + destination = p_out_dir.plus_file(asset_path); + } + + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + memdelete(da); + memdelete(filesystem_da); + return make_dir_err; + } + } + + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + memdelete(da); + if (err) { + memdelete(filesystem_da); + return err; + } + IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed }; + r_exported_assets.push_back(exported_asset); + + if (create_framework) { + String file_name; + + if (!p_custom_file_name) { + file_name = p_asset.get_basename().get_file(); + } else { + file_name = *p_custom_file_name; + } + + String framework_name = file_name + ".framework"; + + // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib + { + List<String> install_name_args; + install_name_args.push_back("-id"); + install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name)); + install_name_args.push_back(destination); + + OS::get_singleton()->execute("install_name_tool", install_name_args, true); + } + + // Creating Info.plist + { + String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">\n" + "<dict>\n" + "<key>CFBundleShortVersionString</key>\n" + "<string>1.0</string>\n" + "<key>CFBundleIdentifier</key>\n" + "<string>com.gdnative.framework.$name</string>\n" + "<key>CFBundleName</key>\n" + "<string>$name</string>\n" + "<key>CFBundleExecutable</key>\n" + "<string>$name</string>\n" + "<key>DTPlatformName</key>\n" + "<string>iphoneos</string>\n" + "<key>CFBundleInfoDictionaryVersion</key>\n" + "<string>6.0</string>\n" + "<key>CFBundleVersion</key>\n" + "<string>1</string>\n" + "<key>CFBundlePackageType</key>\n" + "<string>FMWK</string>\n" + "<key>MinimumOSVersion</key>\n" + "<string>10.0</string>\n" + "</dict>\n" + "</plist>"; + + String info_plist = info_plist_format.replace("$name", file_name); + + FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE); + if (f) { + f->store_string(info_plist); + f->close(); + memdelete(f); + } + } + } + + memdelete(filesystem_da); + + return OK; +} + +Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) { String asset = p_assets[f_idx]; if (!asset.begins_with("res://")) { // either SDK-builtin or already a part of the export template - IOSExportAsset exported_asset = { asset, p_is_framework }; + IOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed }; r_exported_assets.push_back(exported_asset); } else { - DirAccess *da = DirAccess::create_for_path(asset); - if (!da) { - memdelete(filesystem_da); - ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + asset + "."); - } - bool file_exists = da->file_exists(asset); - bool dir_exists = da->dir_exists(asset); - if (!file_exists && !dir_exists) { - memdelete(da); - memdelete(filesystem_da); - return ERR_FILE_NOT_FOUND; - } - String additional_dir = p_is_framework && asset.ends_with(".dylib") ? "/dylibs/" : "/"; - String destination_dir = p_out_dir + additional_dir + asset.get_base_dir().replace("res://", ""); - if (!filesystem_da->dir_exists(destination_dir)) { - Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); - if (make_dir_err) { - memdelete(da); - memdelete(filesystem_da); - return make_dir_err; - } - } - - String destination = destination_dir.plus_file(asset.get_file()); - Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination); - memdelete(da); - if (err) { - memdelete(filesystem_da); - return err; - } - IOSExportAsset exported_asset = { destination, p_is_framework }; - r_exported_assets.push_back(exported_asset); + Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); + ERR_FAIL_COND_V(err, err); } } - memdelete(filesystem_da); return OK; } @@ -922,18 +1297,23 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) { Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); for (int i = 0; i < export_plugins.size(); i++) { - Vector<String> frameworks = export_plugins[i]->get_ios_frameworks(); - Error err = _export_additional_assets(p_out_dir, frameworks, true, r_exported_assets); + Vector<String> linked_frameworks = export_plugins[i]->get_ios_frameworks(); + Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + Vector<String> embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks(); + err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets); ERR_FAIL_COND_V(err, err); Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs(); - for (int j = 0; j < project_static_libs.size(); j++) + for (int j = 0; j < project_static_libs.size(); j++) { project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project - err = _export_additional_assets(p_out_dir, project_static_libs, true, r_exported_assets); + } + err = _export_additional_assets(p_out_dir, project_static_libs, true, true, r_exported_assets); ERR_FAIL_COND_V(err, err); Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files(); - err = _export_additional_assets(p_out_dir, ios_bundle_files, false, r_exported_assets); + err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets); ERR_FAIL_COND_V(err, err); } @@ -941,7 +1321,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir for (int i = 0; i < p_libraries.size(); ++i) { library_paths.push_back(p_libraries[i].path); } - Error err = _export_additional_assets(p_out_dir, library_paths, true, r_exported_assets); + Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets); ERR_FAIL_COND_V(err, err); return OK; @@ -959,20 +1339,173 @@ Vector<String> EditorExportPlatformIOS::_get_preset_architectures(const Ref<Edit return enabled_archs; } -void EditorExportPlatformIOS::add_module_code(const Ref<EditorExportPreset> &p_preset, EditorExportPlatformIOS::IOSConfigData &p_config_data, const String &p_name, const String &p_fid, const String &p_gid) { - if ((bool)p_preset->get("capabilities/" + p_name)) { - //add module static library - print_line("ADDING MODULE: " + p_name); +Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug) { + String plugin_definition_cpp_code; + String plugin_initialization_cpp_code; + String plugin_deinitialization_cpp_code; - p_config_data.modules_buildfile += p_gid + " /* libgodot_" + p_name + "_module.a in Frameworks */ = {isa = PBXBuildFile; fileRef = " + p_fid + " /* libgodot_" + p_name + "_module.a */; };\n\t\t"; - p_config_data.modules_fileref += p_fid + " /* libgodot_" + p_name + "_module.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = godot_" + p_name + "_module ; path = \"libgodot_" + p_name + "_module.a\"; sourceTree = \"<group>\"; };\n\t\t"; - p_config_data.modules_buildphase += p_gid + " /* libgodot_" + p_name + "_module.a */,\n\t\t\t\t"; - p_config_data.modules_buildgrp += p_fid + " /* libgodot_" + p_name + "_module.a */,\n\t\t\t\t"; - } else { - //add stub function for disabled module - p_config_data.cpp_code += "void register_" + p_name + "_types() { /*stub*/ };\n"; - p_config_data.cpp_code += "void unregister_" + p_name + "_types() { /*stub*/ };\n"; + Vector<String> plugin_linked_dependencies; + Vector<String> plugin_embedded_dependencies; + Vector<String> plugin_files; + + Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset); + + Vector<String> added_linked_dependenciy_names; + Vector<String> added_embedded_dependenciy_names; + HashMap<String, String> plist_values; + + Error err; + + for (int i = 0; i < enabled_plugins.size(); i++) { + PluginConfig plugin = enabled_plugins[i]; + + // Export plugin binary. + if (!plugin.supports_targets) { + err = _copy_asset(dest_dir, plugin.binary, nullptr, true, true, r_exported_assets); + } else { + String plugin_binary_dir = plugin.binary.get_base_dir(); + String plugin_name_prefix = plugin.binary.get_basename().get_file(); + String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + ".a"; + String result_file_name = plugin.binary.get_file(); + + err = _copy_asset(dest_dir, plugin_binary_dir.plus_file(plugin_file), &result_file_name, true, true, r_exported_assets); + } + + ERR_FAIL_COND_V(err, err); + + // Adding dependencies. + // Use separate container for names to check for duplicates. + for (int j = 0; j < plugin.linked_dependencies.size(); j++) { + String dependency = plugin.linked_dependencies[j]; + String name = dependency.get_file(); + + if (added_linked_dependenciy_names.has(name)) { + continue; + } + + added_linked_dependenciy_names.push_back(name); + plugin_linked_dependencies.push_back(dependency); + } + + for (int j = 0; j < plugin.system_dependencies.size(); j++) { + String dependency = plugin.system_dependencies[j]; + String name = dependency.get_file(); + + if (added_linked_dependenciy_names.has(name)) { + continue; + } + + added_linked_dependenciy_names.push_back(name); + plugin_linked_dependencies.push_back(dependency); + } + + for (int j = 0; j < plugin.embedded_dependencies.size(); j++) { + String dependency = plugin.embedded_dependencies[j]; + String name = dependency.get_file(); + + if (added_embedded_dependenciy_names.has(name)) { + continue; + } + + added_embedded_dependenciy_names.push_back(name); + plugin_embedded_dependencies.push_back(dependency); + } + + plugin_files.append_array(plugin.files_to_copy); + + // Capabilities + // Also checking for duplicates. + for (int j = 0; j < plugin.capabilities.size(); j++) { + String capability = plugin.capabilities[j]; + + if (p_config_data.capabilities.has(capability)) { + continue; + } + + p_config_data.capabilities.push_back(capability); + } + + // Plist + // Using hash map container to remove duplicates + const String *K = nullptr; + + while ((K = plugin.plist.next(K))) { + String key = *K; + String value = plugin.plist[key]; + + if (key.empty() || value.empty()) { + continue; + } + + plist_values[key] = value; + } + + // CPP Code + String definition_comment = "// Plugin: " + plugin.name + "\n"; + String initialization_method = plugin.initialization_method + "();\n"; + String deinitialization_method = plugin.deinitialization_method + "();\n"; + + plugin_definition_cpp_code += definition_comment + + "extern void " + initialization_method + + "extern void " + deinitialization_method + "\n"; + + plugin_initialization_cpp_code += "\t" + initialization_method; + plugin_deinitialization_cpp_code += "\t" + deinitialization_method; } + + // Updating `Info.plist` + { + const String *K = nullptr; + while ((K = plist_values.next(K))) { + String key = *K; + String value = plist_values[key]; + + if (key.empty() || value.empty()) { + continue; + } + + p_config_data.plist_content += "<key>" + key + "</key><string>" + value + "</string>\n"; + } + } + + // Export files + { + // Export linked plugin dependency + err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + // Export embedded plugin dependency + err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets); + ERR_FAIL_COND_V(err, err); + + // Export plugin files + err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets); + ERR_FAIL_COND_V(err, err); + } + + // Update CPP + { + Dictionary plugin_format; + plugin_format["definition"] = plugin_definition_cpp_code; + plugin_format["initialization"] = plugin_initialization_cpp_code; + plugin_format["deinitialization"] = plugin_deinitialization_cpp_code; + + String plugin_cpp_code = "\n// Godot Plugins\n" + "void godot_ios_plugins_initialize();\n" + "void godot_ios_plugins_deinitialize();\n" + "// Exported Plugins\n\n" + "$definition" + "// Use Plugins\n" + "void godot_ios_plugins_initialize() {\n" + "$initialization" + "}\n\n" + "void godot_ios_plugins_deinitialize() {\n" + "$deinitialization" + "}\n"; + + p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_"); + } + return OK; } Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { @@ -987,10 +1520,11 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p String team_id = p_preset->get("application/app_store_team_id"); ERR_FAIL_COND_V_MSG(team_id.length() == 0, ERR_CANT_OPEN, "App Store Team ID not specified - cannot configure the project."); - if (p_debug) + if (p_debug) { src_pkg_name = p_preset->get("custom_template/debug"); - else + } else { src_pkg_name = p_preset->get("custom_template/release"); + } if (src_pkg_name == "") { String err; @@ -1036,8 +1570,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p String pack_path = dest_dir + binary_name + ".pck"; Vector<SharedObject> libraries; Error err = save_pack(p_preset, pack_path, &libraries); - if (err) + if (err) { return err; + } if (ep.step("Extracting and configuring Xcode project", 1)) { return ERR_SKIP; @@ -1047,12 +1582,13 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p print_line("Static library: " + library_to_use); String pkg_name; - if (p_preset->get("application/name") != "") + if (p_preset->get("application/name") != "") { pkg_name = p_preset->get("application/name"); // app_name - else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") + } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); - else + } else { pkg_name = "Unnamed"; + } bool found_library = false; int total_size = 0; @@ -1065,6 +1601,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p files_to_parse.insert("godot_ios/dummy.cpp"); files_to_parse.insert("godot_ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata"); files_to_parse.insert("godot_ios.xcodeproj/xcshareddata/xcschemes/godot_ios.xcscheme"); + files_to_parse.insert("godot_ios/godot_ios.entitlements"); + files_to_parse.insert("godot_ios/Launch Screen.storyboard"); IOSConfigData config_data = { pkg_name, @@ -1076,9 +1614,12 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p "", "", "", - "" + "", + Vector<String>() }; + Vector<IOSExportAsset> assets; + DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir); ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE); @@ -1091,8 +1632,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p return ERR_CANT_OPEN; } - add_module_code(p_preset, config_data, "arkit", "F9B95E6E2391205500AF0000", "F9C95E812391205C00BF0000"); - add_module_code(p_preset, config_data, "camera", "F9B95E6E2391205500AF0001", "F9C95E812391205C00BF0001"); + err = _export_ios_plugins(p_preset, config_data, dest_dir + binary_name, assets, p_debug); + ERR_FAIL_COND_V(err, err); //export rest of the files int ret = unzGoToFirstFile(src_pkg_zip); @@ -1134,21 +1675,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p is_execute = true; #endif file = "godot_ios.a"; - } else if (file.begins_with("libgodot_arkit")) { - if ((bool)p_preset->get("capabilities/arkit") && file.ends_with(String(p_debug ? "debug" : "release") + ".fat.a")) { - file = "libgodot_arkit_module.a"; - } else { - ret = unzGoToNextFile(src_pkg_zip); - continue; //ignore! - } - } else if (file.begins_with("libgodot_camera")) { - if ((bool)p_preset->get("capabilities/camera") && file.ends_with(String(p_debug ? "debug" : "release") + ".fat.a")) { - file = "libgodot_camera_module.a"; - } else { - ret = unzGoToNextFile(src_pkg_zip); - continue; //ignore! - } } + if (file == project_file) { project_file_data = data; } @@ -1231,19 +1759,57 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p err = tmp_app_path->make_dir_recursive(iconset_dir); } memdelete(tmp_app_path); - if (err) + if (err) { return err; + } err = _export_icons(p_preset, iconset_dir); - if (err) + if (err) { return err; + } + + bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard"); + + String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"; + String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/"; + + DirAccess *launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + if (!launch_screen_da) { + return ERR_CANT_CREATE; + } + + if (use_storyboard) { + print_line("Using Launch Storyboard"); - err = _export_loading_screens(p_preset, dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"); - if (err) + if (launch_screen_da->change_dir(launch_image_path) == OK) { + launch_screen_da->erase_contents_recursive(); + launch_screen_da->remove(launch_image_path); + } + + err = _export_loading_screen_file(p_preset, splash_image_path); + } else { + print_line("Using Launch Images"); + + const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard"; + + launch_screen_da->remove(launch_screen_path); + + if (launch_screen_da->change_dir(splash_image_path) == OK) { + launch_screen_da->erase_contents_recursive(); + launch_screen_da->remove(splash_image_path); + } + + err = _export_loading_screen_images(p_preset, launch_image_path); + } + + memdelete(launch_screen_da); + + if (err) { return err; + } print_line("Exporting additional assets"); - Vector<IOSExportAsset> assets; _export_additional_assets(dest_dir + binary_name, libraries, assets); _add_assets_to_project(p_preset, project_file_data, assets); String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj"; @@ -1310,7 +1876,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p } bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - String err; bool valid = false; @@ -1343,7 +1908,7 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset valid = false; } - String identifier = p_preset->get("application/identifier"); + String identifier = p_preset->get("application/bundle_identifier"); String pn_err; if (!is_package_name_valid(identifier, &pn_err)) { err += TTR("Invalid Identifier:") + " " + pn_err + "\n"; @@ -1362,30 +1927,37 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset } } - String etc_error = test_etc2(); + String etc_error = test_etc2_or_pvrtc(); if (etc_error != String()) { valid = false; err += etc_error; } - if (!err.empty()) + if (!err.empty()) { r_error = err; + } return valid; } EditorExportPlatformIOS::EditorExportPlatformIOS() { - Ref<Image> img = memnew(Image(_iphone_logo)); logo.instance(); logo->create_from_image(img); + + plugins_changed = true; + quit_request = false; + + check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this); } EditorExportPlatformIOS::~EditorExportPlatformIOS() { + quit_request = true; + Thread::wait_to_finish(check_for_changes_thread); + memdelete(check_for_changes_thread); } void register_iphone_exporter() { - Ref<EditorExportPlatformIOS> platform; platform.instance(); diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h deleted file mode 100644 index d35cc4b87c..0000000000 --- a/platform/iphone/game_center.h +++ /dev/null @@ -1,76 +0,0 @@ -/*************************************************************************/ -/* game_center.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. */ -/*************************************************************************/ - -#ifdef GAME_CENTER_ENABLED - -#ifndef GAME_CENTER_H -#define GAME_CENTER_H - -#include "core/object.h" - -class GameCenter : public Object { - - GDCLASS(GameCenter, Object); - - static GameCenter *instance; - static void _bind_methods(); - - List<Variant> pending_events; - - bool authenticated; - - void return_connect_error(const char *p_error_description); - -public: - void connect(); - bool is_authenticated(); - - Error post_score(Variant p_score); - Error award_achievement(Variant p_params); - void reset_achievements(); - void request_achievements(); - void request_achievement_descriptions(); - Error show_game_center(Variant p_params); - Error request_identity_verification_signature(); - - void game_center_closed(); - - int get_pending_event_count(); - Variant pop_pending_event(); - - static GameCenter *get_singleton(); - - GameCenter(); - ~GameCenter(); -}; - -#endif - -#endif diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm deleted file mode 100644 index 99d539d4ff..0000000000 --- a/platform/iphone/game_center.mm +++ /dev/null @@ -1,400 +0,0 @@ -/*************************************************************************/ -/* game_center.mm */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -#ifdef GAME_CENTER_ENABLED - -#include "game_center.h" - -#ifdef __IPHONE_9_0 - -#import <GameKit/GameKit.h> -extern "C" { - -#else - -extern "C" { -#import <GameKit/GameKit.h> - -#endif - -#import "app_delegate.h" -}; - -GameCenter *GameCenter::instance = NULL; - -void GameCenter::_bind_methods() { - ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated); - - ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score); - ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement); - ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements); - ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements); - ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions); - ClassDB::bind_method(D_METHOD("show_game_center"), &GameCenter::show_game_center); - ClassDB::bind_method(D_METHOD("request_identity_verification_signature"), &GameCenter::request_identity_verification_signature); - - ClassDB::bind_method(D_METHOD("get_pending_event_count"), &GameCenter::get_pending_event_count); - ClassDB::bind_method(D_METHOD("pop_pending_event"), &GameCenter::pop_pending_event); -}; - -void GameCenter::return_connect_error(const char *p_error_description) { - authenticated = false; - Dictionary ret; - ret["type"] = "authentication"; - ret["result"] = "error"; - ret["error_code"] = 0; - ret["error_description"] = p_error_description; - pending_events.push_back(ret); -} - -void GameCenter::connect() { - - //if this class isn't available, game center isn't implemented - if ((NSClassFromString(@"GKLocalPlayer")) == nil) { - return_connect_error("GameCenter not available"); - return; - } - - GKLocalPlayer *player = [GKLocalPlayer localPlayer]; - if (![player respondsToSelector:@selector(authenticateHandler)]) { - return_connect_error("GameCenter doesn't respond to 'authenticateHandler'"); - return; - } - - ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; - if (!root_controller) { - return_connect_error("Window doesn't have root ViewController"); - return; - } - - // This handler is called several times. First when the view needs to be shown, then again - // after the view is cancelled or the user logs in. Or if the user's already logged in, it's - // called just once to confirm they're authenticated. This is why no result needs to be specified - // in the presentViewController phase. In this case, more calls to this function will follow. - player.authenticateHandler = (^(UIViewController *controller, NSError *error) { - if (controller) { - [root_controller presentViewController:controller animated:YES completion:nil]; - } else { - Dictionary ret; - ret["type"] = "authentication"; - if (player.isAuthenticated) { - ret["result"] = "ok"; - ret["player_id"] = [player.playerID UTF8String]; - GameCenter::get_singleton()->authenticated = true; - } else { - ret["result"] = "error"; - ret["error_code"] = (int64_t)error.code; - ret["error_description"] = [error.localizedDescription UTF8String]; - GameCenter::get_singleton()->authenticated = false; - }; - - pending_events.push_back(ret); - }; - }); -}; - -bool GameCenter::is_authenticated() { - return authenticated; -}; - -Error GameCenter::post_score(Variant p_score) { - - Dictionary params = p_score; - ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER); - float score = params["score"]; - String category = params["category"]; - - NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease]; - GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease]; - reporter.value = score; - - ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE); - - [GKScore reportScores:@[ reporter ] - withCompletionHandler:^(NSError *error) { - Dictionary ret; - ret["type"] = "post_score"; - if (error == nil) { - ret["result"] = "ok"; - } else { - ret["result"] = "error"; - ret["error_code"] = (int64_t)error.code; - ret["error_description"] = [error.localizedDescription UTF8String]; - }; - - pending_events.push_back(ret); - }]; - - return OK; -}; - -Error GameCenter::award_achievement(Variant p_params) { - - Dictionary params = p_params; - ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER); - String name = params["name"]; - float progress = params["progress"]; - - NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease]; - GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease]; - ERR_FAIL_COND_V(!achievement, FAILED); - - ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE); - - achievement.percentComplete = progress; - achievement.showsCompletionBanner = NO; - if (params.has("show_completion_banner")) { - achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO; - } - - [GKAchievement reportAchievements:@[ achievement ] - withCompletionHandler:^(NSError *error) { - Dictionary ret; - ret["type"] = "award_achievement"; - if (error == nil) { - ret["result"] = "ok"; - } else { - ret["result"] = "error"; - ret["error_code"] = (int64_t)error.code; - }; - - pending_events.push_back(ret); - }]; - - return OK; -}; - -void GameCenter::request_achievement_descriptions() { - - [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) { - Dictionary ret; - ret["type"] = "achievement_descriptions"; - if (error == nil) { - ret["result"] = "ok"; - PackedStringArray names; - PackedStringArray titles; - PackedStringArray unachieved_descriptions; - PackedStringArray achieved_descriptions; - PackedInt32Array maximum_points; - Array hidden; - Array replayable; - - for (int i = 0; i < [descriptions count]; i++) { - - GKAchievementDescription *description = [descriptions objectAtIndex:i]; - - const char *str = [description.identifier UTF8String]; - names.push_back(String::utf8(str != NULL ? str : "")); - - str = [description.title UTF8String]; - titles.push_back(String::utf8(str != NULL ? str : "")); - - str = [description.unachievedDescription UTF8String]; - unachieved_descriptions.push_back(String::utf8(str != NULL ? str : "")); - - str = [description.achievedDescription UTF8String]; - achieved_descriptions.push_back(String::utf8(str != NULL ? str : "")); - - maximum_points.push_back(description.maximumPoints); - - hidden.push_back(description.hidden == YES); - - replayable.push_back(description.replayable == YES); - } - - ret["names"] = names; - ret["titles"] = titles; - ret["unachieved_descriptions"] = unachieved_descriptions; - ret["achieved_descriptions"] = achieved_descriptions; - ret["maximum_points"] = maximum_points; - ret["hidden"] = hidden; - ret["replayable"] = replayable; - - } else { - ret["result"] = "error"; - ret["error_code"] = (int64_t)error.code; - }; - - pending_events.push_back(ret); - }]; -}; - -void GameCenter::request_achievements() { - - [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) { - Dictionary ret; - ret["type"] = "achievements"; - if (error == nil) { - ret["result"] = "ok"; - PackedStringArray names; - PackedFloat32Array percentages; - - for (int i = 0; i < [achievements count]; i++) { - - GKAchievement *achievement = [achievements objectAtIndex:i]; - const char *str = [achievement.identifier UTF8String]; - names.push_back(String::utf8(str != NULL ? str : "")); - - percentages.push_back(achievement.percentComplete); - } - - ret["names"] = names; - ret["progress"] = percentages; - - } else { - ret["result"] = "error"; - ret["error_code"] = (int64_t)error.code; - }; - - pending_events.push_back(ret); - }]; -}; - -void GameCenter::reset_achievements() { - - [GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error) { - Dictionary ret; - ret["type"] = "reset_achievements"; - if (error == nil) { - ret["result"] = "ok"; - } else { - ret["result"] = "error"; - ret["error_code"] = (int64_t)error.code; - }; - - pending_events.push_back(ret); - }]; -}; - -Error GameCenter::show_game_center(Variant p_params) { - - ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED); - - Dictionary params = p_params; - - GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault; - if (params.has("view")) { - String view_name = params["view"]; - if (view_name == "default") { - view_state = GKGameCenterViewControllerStateDefault; - } else if (view_name == "leaderboards") { - view_state = GKGameCenterViewControllerStateLeaderboards; - } else if (view_name == "achievements") { - view_state = GKGameCenterViewControllerStateAchievements; - } else if (view_name == "challenges") { - view_state = GKGameCenterViewControllerStateChallenges; - } else { - return ERR_INVALID_PARAMETER; - } - } - - GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init]; - ERR_FAIL_COND_V(!controller, FAILED); - - ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; - ERR_FAIL_COND_V(!root_controller, FAILED); - - controller.gameCenterDelegate = root_controller; - controller.viewState = view_state; - if (view_state == GKGameCenterViewControllerStateLeaderboards) { - controller.leaderboardIdentifier = nil; - if (params.has("leaderboard_name")) { - String name = params["leaderboard_name"]; - NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease]; - controller.leaderboardIdentifier = name_str; - } - } - - [root_controller presentViewController:controller animated:YES completion:nil]; - - return OK; -}; - -Error GameCenter::request_identity_verification_signature() { - - ERR_FAIL_COND_V(!is_authenticated(), ERR_UNAUTHORIZED); - - GKLocalPlayer *player = [GKLocalPlayer localPlayer]; - [player generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) { - Dictionary ret; - ret["type"] = "identity_verification_signature"; - if (error == nil) { - ret["result"] = "ok"; - ret["public_key_url"] = [publicKeyUrl.absoluteString UTF8String]; - ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String]; - ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String]; - ret["timestamp"] = timestamp; - ret["player_id"] = [player.playerID UTF8String]; - } else { - ret["result"] = "error"; - ret["error_code"] = (int64_t)error.code; - ret["error_description"] = [error.localizedDescription UTF8String]; - }; - - pending_events.push_back(ret); - }]; - - return OK; -}; - -void GameCenter::game_center_closed() { - - Dictionary ret; - ret["type"] = "show_game_center"; - ret["result"] = "ok"; - pending_events.push_back(ret); -} - -int GameCenter::get_pending_event_count() { - - return pending_events.size(); -}; - -Variant GameCenter::pop_pending_event() { - - Variant front = pending_events.front()->get(); - pending_events.pop_front(); - - return front; -}; - -GameCenter *GameCenter::get_singleton() { - return instance; -}; - -GameCenter::GameCenter() { - ERR_FAIL_COND(instance != NULL); - instance = this; - authenticated = false; -}; - -GameCenter::~GameCenter(){}; - -#endif diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h deleted file mode 100644 index 975aa4b70a..0000000000 --- a/platform/iphone/gl_view.h +++ /dev/null @@ -1,123 +0,0 @@ -/*************************************************************************/ -/* gl_view.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. */ -/*************************************************************************/ - -#import <AVFoundation/AVFoundation.h> -#import <MediaPlayer/MediaPlayer.h> -#import <OpenGLES/EAGL.h> -#import <OpenGLES/ES1/gl.h> -#import <OpenGLES/ES1/glext.h> -#import <UIKit/UIKit.h> - -@protocol GLViewDelegate; - -@interface GLView : UIView <UIKeyInput> { -@private - // The pixel dimensions of the backbuffer - GLint backingWidth; - GLint backingHeight; - - EAGLContext *context; - - // OpenGL names for the renderbuffer and framebuffers used to render to this view - GLuint viewRenderbuffer, viewFramebuffer; - - // OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) - GLuint depthRenderbuffer; - - BOOL useCADisplayLink; - // CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15 - CADisplayLink *displayLink; - - // An animation timer that, when animation is started, will periodically call -drawView at the given rate. - // Only used if CADisplayLink is not - NSTimer *animationTimer; - - NSTimeInterval animationInterval; - - // Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer. - id<GLViewDelegate> delegate; - - // Flag to denote that the -setupView method of a delegate has been called. - // Resets to NO whenever the delegate changes. - BOOL delegateSetup; - BOOL active; - float screen_scale; -} - -@property(nonatomic, assign) id<GLViewDelegate> delegate; - -// AVPlayer-related properties -@property(strong, nonatomic) AVAsset *avAsset; -@property(strong, nonatomic) AVPlayerItem *avPlayerItem; -@property(strong, nonatomic) AVPlayer *avPlayer; -@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; - -@property(strong, nonatomic) UIWindow *backgroundWindow; - -@property(nonatomic) UITextAutocorrectionType autocorrectionType; - -- (void)startAnimation; -- (void)stopAnimation; -- (void)drawView; - -- (BOOL)canBecomeFirstResponder; - -- (void)open_keyboard; -- (void)hide_keyboard; -- (void)deleteBackward; -- (BOOL)hasText; -- (void)insertText:(NSString *)p_text; - -- (id)initGLES; -- (BOOL)createFramebuffer; -- (void)destroyFramebuffer; - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification; -- (void)keyboardOnScreen:(NSNotification *)notification; -- (void)keyboardHidden:(NSNotification *)notification; - -@property NSTimeInterval animationInterval; -@property(nonatomic, assign) BOOL useCADisplayLink; - -@end - -@protocol GLViewDelegate <NSObject> - -@required - -// Draw with OpenGL ES -- (void)drawView:(GLView *)view; - -@optional - -// Called whenever you need to do some initialization before rendering. -- (void)setupView:(GLView *)view; - -@end diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm deleted file mode 100644 index ede60a502d..0000000000 --- a/platform/iphone/gl_view.mm +++ /dev/null @@ -1,723 +0,0 @@ -/*************************************************************************/ -/* gl_view.mm */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -#import "gl_view.h" - -#include "core/os/keyboard.h" -#include "core/project_settings.h" -#include "os_iphone.h" -#include "servers/audio_server.h" - -#import <OpenGLES/EAGLDrawable.h> -#import <QuartzCore/QuartzCore.h> - -/* -@interface GLView (private) - -- (id)initGLES; -- (BOOL)createFramebuffer; -- (void)destroyFramebuffer; -@end -*/ - -int gl_view_base_fb; -static String keyboard_text; -static GLView *_instance = NULL; - -static bool video_found_error = false; -static bool video_playing = false; -static CMTime video_current_time; - -void _show_keyboard(String); -void _hide_keyboard(); -bool _play_video(String, float, String, String); -bool _is_video_playing(); -void _pause_video(); -void _focus_out_video(); -void _unpause_video(); -void _stop_video(); -CGFloat _points_to_pixels(CGFloat); - -void _show_keyboard(String p_existing) { - keyboard_text = p_existing; - printf("instance on show is %p\n", _instance); - [_instance open_keyboard]; -}; - -void _hide_keyboard() { - printf("instance on hide is %p\n", _instance); - [_instance hide_keyboard]; - keyboard_text = ""; -}; - -Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) { - UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0); - if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) { - insets = [_instance safeAreaInsets]; - } - ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0, - Rect2(0, 0, p_window_width, p_window_height)); - UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right)); - return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top); -} - -bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) { - p_path = ProjectSettings::get_singleton()->globalize_path(p_path); - - NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease]; - - _instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]]; - - _instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset]; - [_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil]; - - _instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem]; - _instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer]; - - [_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil]; - [[NSNotificationCenter defaultCenter] - addObserver:_instance - selector:@selector(playerItemDidReachEnd:) - name:AVPlayerItemDidPlayToEndTimeNotification - object:[_instance.avPlayer currentItem]]; - - [_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; - - [_instance.avPlayerLayer setFrame:_instance.bounds]; - [_instance.layer addSublayer:_instance.avPlayerLayer]; - [_instance.avPlayer play]; - - AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - - NSMutableArray *allAudioParams = [NSMutableArray array]; - for (id track in audioGroup.options) { - NSString *language = [[track locale] localeIdentifier]; - NSLog(@"subtitle lang: %@", language); - - if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) { - AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; - [audioInputParams setVolume:p_volume atTime:kCMTimeZero]; - [audioInputParams setTrackID:[track trackID]]; - [allAudioParams addObject:audioInputParams]; - - AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; - [audioMix setInputParameters:allAudioParams]; - - [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; - [_instance.avPlayer.currentItem setAudioMix:audioMix]; - - break; - } - } - - AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; - NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; - - for (id track in useableTracks) { - NSString *language = [[track locale] localeIdentifier]; - NSLog(@"subtitle lang: %@", language); - - if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) { - [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; - break; - } - } - - video_playing = true; - - return true; -} - -bool _is_video_playing() { - if (_instance.avPlayer.error) { - printf("Error during playback\n"); - } - return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error); -} - -void _pause_video() { - video_current_time = _instance.avPlayer.currentTime; - [_instance.avPlayer pause]; - video_playing = false; -} - -void _focus_out_video() { - printf("focus out pausing video\n"); - [_instance.avPlayer pause]; -}; - -void _unpause_video() { - - [_instance.avPlayer play]; - video_playing = true; -}; - -void _stop_video() { - [_instance.avPlayer pause]; - [_instance.avPlayerLayer removeFromSuperlayer]; - _instance.avPlayer = nil; - video_playing = false; -} - -CGFloat _points_to_pixels(CGFloat points) { - float pixelPerInch; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - pixelPerInch = 132; - } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { - pixelPerInch = 163; - } else { - pixelPerInch = 160; - } - CGFloat pointsPerInch = 72.0; - return (points / pointsPerInch * pixelPerInch); -} - -@implementation GLView - -@synthesize animationInterval; - -static const int max_touches = 8; -static UITouch *touches[max_touches]; - -static void init_touches() { - - for (int i = 0; i < max_touches; i++) { - touches[i] = NULL; - }; -}; - -static int get_touch_id(UITouch *p_touch) { - - int first = -1; - for (int i = 0; i < max_touches; i++) { - if (first == -1 && touches[i] == NULL) { - first = i; - continue; - }; - if (touches[i] == p_touch) - return i; - }; - - if (first != -1) { - touches[first] = p_touch; - return first; - }; - - return -1; -}; - -static int remove_touch(UITouch *p_touch) { - - int remaining = 0; - for (int i = 0; i < max_touches; i++) { - - if (touches[i] == NULL) - continue; - if (touches[i] == p_touch) - touches[i] = NULL; - else - ++remaining; - }; - return remaining; -}; - -static void clear_touches() { - - for (int i = 0; i < max_touches; i++) { - - touches[i] = NULL; - }; -}; - -// Implement this to override the default layer class (which is [CALayer class]). -// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering. -+ (Class)layerClass { - return [CAEAGLLayer class]; -} - -//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder: -- (id)initWithCoder:(NSCoder *)coder { - active = FALSE; - if ((self = [super initWithCoder:coder])) { - self = [self initGLES]; - } - return self; -} - -- (id)initGLES { - // Get our backing layer - CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; - - // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color. - eaglLayer.opaque = YES; - eaglLayer.drawableProperties = [NSDictionary - dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], - kEAGLDrawablePropertyRetainedBacking, - kEAGLColorFormatRGBA8, - kEAGLDrawablePropertyColorFormat, - nil]; - - // FIXME: Add Vulkan support via MoltenVK. Add fallback code back? - - // Create GL ES 2 context - if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") { - context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - NSLog(@"Setting up an OpenGL ES 2.0 context."); - if (!context) { - NSLog(@"Failed to create OpenGL ES 2.0 context!"); - return nil; - } - } - - if (![EAGLContext setCurrentContext:context]) { - NSLog(@"Failed to set EAGLContext!"); - return nil; - } - if (![self createFramebuffer]) { - NSLog(@"Failed to create frame buffer!"); - return nil; - } - - // Default the animation interval to 1/60th of a second. - animationInterval = 1.0 / 60.0; - return self; -} - -- (id<GLViewDelegate>)delegate { - return delegate; -} - -// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called. -- (void)setDelegate:(id<GLViewDelegate>)d { - delegate = d; - delegateSetup = ![delegate respondsToSelector:@selector(setupView:)]; -} - -@synthesize useCADisplayLink; - -// If our view is resized, we'll be asked to layout subviews. -// This is the perfect opportunity to also update the framebuffer so that it is -// the same size as our display area. - -- (void)layoutSubviews { - [EAGLContext setCurrentContext:context]; - [self destroyFramebuffer]; - [self createFramebuffer]; - [self drawView]; -} - -- (BOOL)createFramebuffer { - // Generate IDs for a framebuffer object and a color renderbuffer - UIScreen *mainscr = [UIScreen mainScreen]; - printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height); - self.contentScaleFactor = mainscr.nativeScale; - - glGenFramebuffersOES(1, &viewFramebuffer); - glGenRenderbuffersOES(1, &viewRenderbuffer); - - glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); - glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); - // This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer) - // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view). - [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer]; - glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); - - glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); - glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); - - // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer. - glGenRenderbuffersOES(1, &depthRenderbuffer); - glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); - glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); - glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); - - if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { - NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); - return NO; - } - - if (OS::get_singleton()) { - OS::VideoMode vm; - vm.fullscreen = true; - vm.width = backingWidth; - vm.height = backingHeight; - vm.resizable = false; - OS::get_singleton()->set_video_mode(vm); - OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer); - }; - gl_view_base_fb = viewFramebuffer; - - return YES; -} - -// Clean up any buffers we have allocated. -- (void)destroyFramebuffer { - glDeleteFramebuffersOES(1, &viewFramebuffer); - viewFramebuffer = 0; - glDeleteRenderbuffersOES(1, &viewRenderbuffer); - viewRenderbuffer = 0; - - if (depthRenderbuffer) { - glDeleteRenderbuffersOES(1, &depthRenderbuffer); - depthRenderbuffer = 0; - } -} - -- (void)startAnimation { - if (active) - return; - active = TRUE; - printf("start animation!\n"); - if (useCADisplayLink) { - - // Approximate frame rate - // assumes device refreshes at 60 fps - int frameInterval = (int)floor(animationInterval * 60.0f); - - displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; - [displayLink setFrameInterval:frameInterval]; - - // Setup DisplayLink in main thread - [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - } else { - animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES]; - } - - if (video_playing) { - _unpause_video(); - } -} - -- (void)stopAnimation { - if (!active) - return; - active = FALSE; - printf("******** stop animation!\n"); - - if (useCADisplayLink) { - [displayLink invalidate]; - displayLink = nil; - } else { - [animationTimer invalidate]; - animationTimer = nil; - } - - clear_touches(); - - if (video_playing) { - // save position - } -} - -- (void)setAnimationInterval:(NSTimeInterval)interval { - animationInterval = interval; - if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) { - [self stopAnimation]; - [self startAnimation]; - } -} - -// Updates the OpenGL view when the timer fires -- (void)drawView { - - if (!active) { - printf("draw view not active!\n"); - return; - }; - if (useCADisplayLink) { - // Pause the CADisplayLink to avoid recursion - [displayLink setPaused:YES]; - - // Process all input events - while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) - ; - - // We are good to go, resume the CADisplayLink - [displayLink setPaused:NO]; - } - - // Make sure that you are drawing to the current context - [EAGLContext setCurrentContext:context]; - - // If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again. - if (!delegateSetup) { - [delegate setupView:self]; - delegateSetup = YES; - } - - glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); - - [delegate drawView:self]; - - glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); - [context presentRenderbuffer:GL_RENDERBUFFER_OES]; - -#ifdef DEBUG_ENABLED - GLenum err = glGetError(); - if (err) - NSLog(@"DrawView: %x error", err); -#endif -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - NSArray *tlist = [[event allTouches] allObjects]; - for (unsigned int i = 0; i < [tlist count]; i++) { - - if ([touches containsObject:[tlist objectAtIndex:i]]) { - - UITouch *touch = [tlist objectAtIndex:i]; - if (touch.phase != UITouchPhaseBegan) - continue; - int tid = get_touch_id(touch); - ERR_FAIL_COND(tid == -1); - CGPoint touchPoint = [touch locationInView:self]; - OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); - }; - }; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - - NSArray *tlist = [[event allTouches] allObjects]; - for (unsigned int i = 0; i < [tlist count]; i++) { - - if ([touches containsObject:[tlist objectAtIndex:i]]) { - - UITouch *touch = [tlist objectAtIndex:i]; - if (touch.phase != UITouchPhaseMoved) - continue; - int tid = get_touch_id(touch); - ERR_FAIL_COND(tid == -1); - CGPoint touchPoint = [touch locationInView:self]; - CGPoint prev_point = [touch previousLocationInView:self]; - OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor); - }; - }; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - NSArray *tlist = [[event allTouches] allObjects]; - for (unsigned int i = 0; i < [tlist count]; i++) { - - if ([touches containsObject:[tlist objectAtIndex:i]]) { - - UITouch *touch = [tlist objectAtIndex:i]; - if (touch.phase != UITouchPhaseEnded) - continue; - int tid = get_touch_id(touch); - ERR_FAIL_COND(tid == -1); - remove_touch(touch); - CGPoint touchPoint = [touch locationInView:self]; - OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); - }; - }; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - - OSIPhone::get_singleton()->touches_cancelled(); - clear_touches(); -}; - -- (BOOL)canBecomeFirstResponder { - return YES; -}; - -- (void)open_keyboard { - //keyboard_text = p_existing; - [self becomeFirstResponder]; -}; - -- (void)hide_keyboard { - //keyboard_text = p_existing; - [self resignFirstResponder]; -}; - -- (void)keyboardOnScreen:(NSNotification *)notification { - NSDictionary *info = notification.userInfo; - NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; - - CGRect rawFrame = [value CGRectValue]; - CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil]; - - OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height)); -} - -- (void)keyboardHidden:(NSNotification *)notification { - OSIPhone::get_singleton()->set_virtual_keyboard_height(0); -} - -- (void)deleteBackward { - if (keyboard_text.length()) - keyboard_text.erase(keyboard_text.length() - 1, 1); - OSIPhone::get_singleton()->key(KEY_BACKSPACE, true); -}; - -- (BOOL)hasText { - return keyboard_text.length() ? YES : NO; -}; - -- (void)insertText:(NSString *)p_text { - String character; - character.parse_utf8([p_text UTF8String]); - keyboard_text = keyboard_text + character; - OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true); - printf("inserting text with character %lc\n", (CharType)character[0]); -}; - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { - printf("*********** route changed!\n"); - NSDictionary *interuptionDict = notification.userInfo; - - NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; - - switch (routeChangeReason) { - - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { - NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); - NSLog(@"Headphone/Line plugged in"); - }; break; - - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { - NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); - NSLog(@"Headphone/Line was pulled. Resuming video play...."); - if (_is_video_playing()) { - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [_instance.avPlayer play]; // NOTE: change this line according your current player implementation - NSLog(@"resumed play"); - }); - }; - }; break; - - case AVAudioSessionRouteChangeReasonCategoryChange: { - // called at start - also when other audio wants to play - NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); - }; break; - } -} - -// When created via code however, we get initWithFrame -- (id)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - _instance = self; - printf("after init super %p\n", self); - if (self != nil) { - self = [self initGLES]; - printf("after init gles %p\n", self); - } - init_touches(); - self.multipleTouchEnabled = YES; - self.autocorrectionType = UITextAutocorrectionTypeNo; - - printf("******** adding observer for sound routing changes\n"); - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(audioRouteChangeListenerCallback:) - name:AVAudioSessionRouteChangeNotification - object:nil]; - - printf("******** adding observer for keyboard show/hide\n"); - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(keyboardOnScreen:) - name:UIKeyboardDidShowNotification - object:nil]; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(keyboardHidden:) - name:UIKeyboardDidHideNotification - object:nil]; - - //self.autoresizesSubviews = YES; - //[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth]; - - return self; -} - -//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers { -// return YES; -//} - -//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{ -// return YES; -//} - -// Stop animating and release resources when they are no longer needed. -- (void)dealloc { - [self stopAnimation]; - - if ([EAGLContext currentContext] == context) { - [EAGLContext setCurrentContext:nil]; - } - - [context release]; - context = nil; - - [super dealloc]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - - if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) { - if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) { - _stop_video(); - video_found_error = true; - } - - if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay && - _instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && - CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) { - - //NSLog(@"time: %@", video_current_time); - - [_instance.avPlayer seekToTime:video_current_time]; - video_current_time = kCMTimeZero; - } - } - - if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) { - NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate); - if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [_instance.avPlayer play]; // NOTE: change this line according your current player implementation - NSLog(@"resumed play"); - }); - - NSLog(@" . . . PAUSED (or just started)"); - } - } -} - -- (void)playerItemDidReachEnd:(NSNotification *)notification { - _stop_video(); -} - -@end diff --git a/platform/iphone/godot_app_delegate.h b/platform/iphone/godot_app_delegate.h new file mode 100644 index 0000000000..ebb21c499b --- /dev/null +++ b/platform/iphone/godot_app_delegate.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* godot_app_delegate.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. */ +/*************************************************************************/ + +#import <UIKit/UIKit.h> + +typedef NSObject<UIApplicationDelegate> ApplicationDelegateService; + +@interface GodotApplicalitionDelegate : NSObject <UIApplicationDelegate> + +@property(class, readonly, strong) NSArray<ApplicationDelegateService *> *services; + ++ (void)addService:(ApplicationDelegateService *)service; + +@end diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m new file mode 100644 index 0000000000..a5aad26bd5 --- /dev/null +++ b/platform/iphone/godot_app_delegate.m @@ -0,0 +1,497 @@ +/*************************************************************************/ +/* godot_app_delegate.m */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#import "godot_app_delegate.h" + +#import "app_delegate.h" + +@interface GodotApplicalitionDelegate () + +@end + +@implementation GodotApplicalitionDelegate + +static NSMutableArray<ApplicationDelegateService *> *services = nil; + ++ (NSArray<ApplicationDelegateService *> *)services { + return services; +} + ++ (void)load { + services = [NSMutableArray new]; + [services addObject:[AppDelegate new]]; +} + ++ (void)addService:(ApplicationDelegateService *)service { + if (!services || !service) { + return; + } + [services addObject:service]; +} + +// UIApplicationDelegate documantation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate + +// MARK: Window + +- (UIWindow *)window { + UIWindow *result = nil; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + UIWindow *value = [service window]; + + if (value) { + result = value; + } + } + + return result; +} + +// MARK: Initializing + +- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application willFinishLaunchingWithOptions:launchOptions]) { + result = YES; + } + } + + return result; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application didFinishLaunchingWithOptions:launchOptions]) { + result = YES; + } + } + + return result; +} + +/* Can be handled by Info.plist. Not yet supported by Godot. + +// MARK: Scene + +- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {} + +- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {} + +*/ + +// MARK: Life-Cycle + +- (void)applicationDidBecomeActive:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidBecomeActive:application]; + } +} + +- (void)applicationWillResignActive:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillResignActive:application]; + } +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidEnterBackground:application]; + } +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillEnterForeground:application]; + } +} + +- (void)applicationWillTerminate:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationWillTerminate:application]; + } +} + +// MARK: Environment Changes + +- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationProtectedDataDidBecomeAvailable:application]; + } +} + +- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationProtectedDataWillBecomeUnavailable:application]; + } +} + +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationDidReceiveMemoryWarning:application]; + } +} + +- (void)applicationSignificantTimeChange:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationSignificantTimeChange:application]; + } +} + +// MARK: App State Restoration + +- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application shouldSaveSecureApplicationState:coder]) { + result = YES; + } + } + + return result; +} + +- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application shouldRestoreSecureApplicationState:coder]) { + result = YES; + } + } + + return result; +} + +- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder]; + + if (controller) { + return controller; + } + } + + return nil; +} + +- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application willEncodeRestorableStateWithCoder:coder]; + } +} + +- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didDecodeRestorableStateWithCoder:coder]; + } +} + +// MARK: Download Data in Background + +- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler]; + } + + completionHandler(); +} + +// MARK: Remote Notification + +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + } +} + +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didFailToRegisterForRemoteNotificationsWithError:error]; + } +} + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; + } + + completionHandler(UIBackgroundFetchResultNoData); +} + +// MARK: User Activity and Handling Quick Actions + +- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application willContinueUserActivityWithType:userActivityType]) { + result = YES; + } + } + + return result; +} + +- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) { + result = YES; + } + } + + return result; +} + +- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didUpdateUserActivity:userActivity]; + } +} + +- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application didFailToContinueUserActivityWithType:userActivityType error:error]; + } +} + +- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; + } +} + +// MARK: WatchKit + +- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application handleWatchKitExtensionRequest:userInfo reply:reply]; + } +} + +// MARK: HealthKit + +- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service applicationShouldRequestHealthAuthorization:application]; + } +} + +// MARK: Opening an URL + +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:app openURL:url options:options]) { + return YES; + } + } + + return NO; +} + +// MARK: Disallowing Specified App Extension Types + +- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier { + BOOL result = NO; + + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) { + result = YES; + } + } + + return result; +} + +// MARK: SiriKit + +- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + id result = [service application:application handlerForIntent:intent]; + + if (result) { + return result; + } + } + + return nil; +} + +// MARK: CloudKit + +- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata { + for (ApplicationDelegateService *service in services) { + if (![service respondsToSelector:_cmd]) { + continue; + } + + [service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata]; + } +} + +/* Handled By Info.plist file for now + +// MARK: Interface Geometry + +- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {} + +*/ + +@end diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.mm index 3e67362e16..a4119bcafd 100644 --- a/platform/iphone/godot_iphone.cpp +++ b/platform/iphone/godot_iphone.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* godot_iphone.cpp */ +/* godot_iphone.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/ustring.h" +#include "core/string/ustring.h" #include "main/main.h" #include "os_iphone.h" @@ -38,19 +38,49 @@ static OSIPhone *os = nullptr; -extern "C" { -int add_path(int p_argc, char **p_args); -int add_cmdline(int p_argc, char **p_args); +int add_path(int, char **); +int add_cmdline(int, char **); +int iphone_main(int, char **, String); + +int add_path(int p_argc, char **p_args) { + NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; + if (!str) { + return p_argc; + } + + p_args[p_argc++] = (char *)"--path"; + p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; + p_args[p_argc] = NULL; + + return p_argc; }; -int iphone_main(int, int, int, char **, String); +int add_cmdline(int p_argc, char **p_args) { + NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; + if (!arr) { + return p_argc; + } + + for (NSUInteger i = 0; i < [arr count]; i++) { + NSString *str = [arr objectAtIndex:i]; + if (!str) { + continue; + } + p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; + }; + + p_args[p_argc] = NULL; -int iphone_main(int width, int height, int argc, char **argv, String data_dir) { + return p_argc; +}; +int iphone_main(int argc, char **argv, String data_dir) { size_t len = strlen(argv[0]); while (len--) { - if (argv[0][len] == '/') break; + if (argv[0][len] == '/') { + break; + } } if (len >= 0) { @@ -65,7 +95,10 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) { char cwd[512]; getcwd(cwd, sizeof(cwd)); printf("cwd %s\n", cwd); - os = new OSIPhone(width, height, data_dir); + os = new OSIPhone(data_dir); + + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE char *fargv[64]; for (int i = 0; i < argc; i++) { @@ -76,16 +109,19 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) { argc = add_cmdline(argc, fargv); printf("os created\n"); + Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false); printf("setup %i\n", err); - if (err != OK) + if (err != OK) { return 255; + } + + os->initialize_modules(); return 0; }; void iphone_finish() { - printf("iphone_finish\n"); Main::cleanup(); delete os; diff --git a/platform/javascript/id_handler.js b/platform/iphone/godot_view.h index 67d29075b8..a8f4cb38d9 100644 --- a/platform/javascript/id_handler.js +++ b/platform/iphone/godot_view.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* id_handler.js */ +/* godot_view.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,36 +28,27 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -var IDHandler = /** @constructor */ function() { +#import <UIKit/UIKit.h> - var ids = {}; - var size = 0; +class String; - this.has = function(id) { - return ids.hasOwnProperty(id); - } +@protocol DisplayLayer; +@protocol GodotViewRendererProtocol; - this.add = function(obj) { - size += 1; - var id = crypto.getRandomValues(new Int32Array(32))[0]; - ids[id] = obj; - return id; - } +@interface GodotView : UIView - this.get = function(id) { - return ids[id]; - } +@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer; - this.remove = function(id) { - size -= 1; - delete ids[id]; - } +@property(assign, readonly, nonatomic) BOOL isActive; - this.size = function() { - return size; - } +@property(assign, nonatomic) BOOL useCADisplayLink; +@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer; +@property(assign, readonly, nonatomic) BOOL canRender; - this.ids = ids; -}; +@property(assign, nonatomic) NSTimeInterval renderingInterval; -Module.IDHandler = new IDHandler; +- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName; +- (void)stopRendering; +- (void)startRendering; + +@end diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm new file mode 100644 index 0000000000..0c50842cfa --- /dev/null +++ b/platform/iphone/godot_view.mm @@ -0,0 +1,458 @@ +/*************************************************************************/ +/* godot_view.mm */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#import "godot_view.h" +#include "core/os/keyboard.h" +#include "core/string/ustring.h" +#import "display_layer.h" +#include "display_server_iphone.h" +#import "godot_view_gesture_recognizer.h" +#import "godot_view_renderer.h" + +#import <CoreMotion/CoreMotion.h> + +static const int max_touches = 8; + +@interface GodotView () { + UITouch *godot_touches[max_touches]; +} + +@property(assign, nonatomic) BOOL isActive; + +// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15 +@property(strong, nonatomic) CADisplayLink *displayLink; + +// An animation timer that, when animation is started, will periodically call -drawView at the given rate. +// Only used if CADisplayLink is not +@property(strong, nonatomic) NSTimer *animationTimer; + +@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer; + +@property(strong, nonatomic) CMMotionManager *motionManager; + +@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer; + +@end + +@implementation GodotView + +- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName { + if (self.renderingLayer) { + return self.renderingLayer; + } + + CALayer<DisplayLayer> *layer; + + if ([driverName isEqualToString:@"vulkan"]) { + layer = [GodotMetalLayer layer]; + } else if ([driverName isEqualToString:@"opengl_es"]) { + if (@available(iOS 13, *)) { + NSLog(@"OpenGL ES is deprecated on iOS 13"); + } +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR + return nil; +#else + layer = [GodotOpenGLLayer layer]; +#endif + } else { + return nil; + } + + layer.frame = self.bounds; + layer.contentsScale = self.contentScaleFactor; + + [self.layer addSublayer:layer]; + self.renderingLayer = layer; + + [layer initializeDisplayLayer]; + + return self.renderingLayer; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)dealloc { + [self stopRendering]; + + self.renderer = nil; + + if (self.renderingLayer) { + [self.renderingLayer removeFromSuperlayer]; + self.renderingLayer = nil; + } + + if (self.motionManager) { + [self.motionManager stopDeviceMotionUpdates]; + self.motionManager = nil; + } + + if (self.displayLink) { + [self.displayLink invalidate]; + self.displayLink = nil; + } + + if (self.animationTimer) { + [self.animationTimer invalidate]; + self.animationTimer = nil; + } + + if (self.delayGestureRecognizer) { + self.delayGestureRecognizer = nil; + } +} + +- (void)godot_commonInit { + self.contentScaleFactor = [UIScreen mainScreen].nativeScale; + + [self initTouches]; + + // Configure and start accelerometer + if (!self.motionManager) { + self.motionManager = [[CMMotionManager alloc] init]; + if (self.motionManager.deviceMotionAvailable) { + self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0; + [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical]; + } else { + self.motionManager = nil; + } + } + + // Initialize delay gesture recognizer + GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init]; + self.delayGestureRecognizer = gestureRecognizer; + [self addGestureRecognizer:self.delayGestureRecognizer]; +} + +- (void)stopRendering { + if (!self.isActive) { + return; + } + + self.isActive = NO; + + printf("******** stop animation!\n"); + + if (self.useCADisplayLink) { + [self.displayLink invalidate]; + self.displayLink = nil; + } else { + [self.animationTimer invalidate]; + self.animationTimer = nil; + } + + [self clearTouches]; +} + +- (void)startRendering { + if (self.isActive) { + return; + } + + self.isActive = YES; + + printf("start animation!\n"); + + if (self.useCADisplayLink) { + self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; + + // Approximate frame rate + // assumes device refreshes at 60 fps + int displayFPS = (NSInteger)(1.0 / self.renderingInterval); + + self.displayLink.preferredFramesPerSecond = displayFPS; + + // Setup DisplayLink in main thread + [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + } else { + self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES]; + } +} + +- (void)drawView { + if (!self.isActive) { + printf("draw view not active!\n"); + return; + } + + if (self.useCADisplayLink) { + // Pause the CADisplayLink to avoid recursion + [self.displayLink setPaused:YES]; + + // Process all input events + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) + ; + + // We are good to go, resume the CADisplayLink + [self.displayLink setPaused:NO]; + } + + [self.renderingLayer renderDisplayLayer]; + + if (!self.renderer) { + return; + } + + if ([self.renderer setupView:self]) { + return; + } + + [self handleMotion]; + [self.renderer renderOnView:self]; +} + +- (BOOL)canRender { + if (self.useCADisplayLink) { + return self.displayLink != nil; + } else { + return self.animationTimer != nil; + } +} + +- (void)setRenderingInterval:(NSTimeInterval)renderingInterval { + _renderingInterval = renderingInterval; + + if (self.canRender) { + [self stopRendering]; + [self startRendering]; + } +} + +- (void)layoutSubviews { + if (self.renderingLayer) { + self.renderingLayer.frame = self.bounds; + [self.renderingLayer layoutDisplayLayer]; + + if (DisplayServerIPhone::get_singleton()) { + DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size); + } + } + + [super layoutSubviews]; +} + +// MARK: - Input + +// MARK: Touches + +- (void)initTouches { + for (int i = 0; i < max_touches; i++) { + godot_touches[i] = NULL; + } +} + +- (int)getTouchIDForTouch:(UITouch *)p_touch { + int first = -1; + for (int i = 0; i < max_touches; i++) { + if (first == -1 && godot_touches[i] == NULL) { + first = i; + continue; + } + if (godot_touches[i] == p_touch) { + return i; + } + } + + if (first != -1) { + godot_touches[first] = p_touch; + return first; + } + + return -1; +} + +- (int)removeTouch:(UITouch *)p_touch { + int remaining = 0; + for (int i = 0; i < max_touches; i++) { + if (godot_touches[i] == NULL) { + continue; + } + if (godot_touches[i] == p_touch) { + godot_touches[i] = NULL; + } else { + ++remaining; + } + } + return remaining; +} + +- (void)clearTouches { + for (int i = 0; i < max_touches; i++) { + godot_touches[i] = NULL; + } +} + +- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event { + NSArray *tlist = [event.allTouches allObjects]; + for (unsigned int i = 0; i < [tlist count]; i++) { + if ([touchesSet containsObject:[tlist objectAtIndex:i]]) { + UITouch *touch = [tlist objectAtIndex:i]; + int tid = [self getTouchIDForTouch:touch]; + ERR_FAIL_COND(tid == -1); + CGPoint touchPoint = [touch locationInView:self]; + DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); + } + } +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + NSArray *tlist = [event.allTouches allObjects]; + for (unsigned int i = 0; i < [tlist count]; i++) { + if ([touches containsObject:[tlist objectAtIndex:i]]) { + UITouch *touch = [tlist objectAtIndex:i]; + int tid = [self getTouchIDForTouch:touch]; + ERR_FAIL_COND(tid == -1); + CGPoint touchPoint = [touch locationInView:self]; + CGPoint prev_point = [touch previousLocationInView:self]; + DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor); + } + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + NSArray *tlist = [event.allTouches allObjects]; + for (unsigned int i = 0; i < [tlist count]; i++) { + if ([touches containsObject:[tlist objectAtIndex:i]]) { + UITouch *touch = [tlist objectAtIndex:i]; + int tid = [self getTouchIDForTouch:touch]; + ERR_FAIL_COND(tid == -1); + [self removeTouch:touch]; + CGPoint touchPoint = [touch locationInView:self]; + DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); + } + } +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + NSArray *tlist = [event.allTouches allObjects]; + for (unsigned int i = 0; i < [tlist count]; i++) { + if ([touches containsObject:[tlist objectAtIndex:i]]) { + UITouch *touch = [tlist objectAtIndex:i]; + int tid = [self getTouchIDForTouch:touch]; + ERR_FAIL_COND(tid == -1); + DisplayServerIPhone::get_singleton()->touches_cancelled(tid); + } + } + [self clearTouches]; +} + +// MARK: Motion + +- (void)handleMotion { + if (!self.motionManager) { + return; + } + + // Just using polling approach for now, we can set this up so it sends + // data to us in intervals, might be better. See Apple reference pages + // for more details: + // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc + + // Apple splits our accelerometer date into a gravity and user movement + // component. We add them back together + CMAcceleration gravity = self.motionManager.deviceMotion.gravity; + CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration; + + ///@TODO We don't seem to be getting data here, is my device broken or + /// is this code incorrect? + CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field; + + ///@TODO we can access rotationRate as a CMRotationRate variable + ///(processed date) or CMGyroData (raw data), have to see what works + /// best + CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate; + + // Adjust for screen orientation. + // [[UIDevice currentDevice] orientation] changes even if we've fixed + // our orientation which is not a good thing when you're trying to get + // your user to move the screen in all directions and want consistent + // output + + ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation] + /// is a bit of a hack. Godot obviously knows the orientation so maybe + /// we + // can use that instead? (note that left and right seem swapped) + + UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown; + + if (@available(iOS 13, *)) { + interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR + } else { + interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; +#endif + } + + switch (interfaceOrientation) { + case UIInterfaceOrientationLandscapeLeft: { + DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z); + DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z); + DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z); + DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z); + } break; + case UIInterfaceOrientationLandscapeRight: { + DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z); + DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z); + DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z); + DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z); + } break; + case UIInterfaceOrientationPortraitUpsideDown: { + DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z); + DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z); + DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z); + DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z); + } break; + default: { // assume portrait + DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z); + DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z); + DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z); + DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z); + } break; + } +} + +@end diff --git a/platform/iphone/icloud.h b/platform/iphone/godot_view_gesture_recognizer.h index 401a6cbeb8..1431a9fb89 100644 --- a/platform/iphone/icloud.h +++ b/platform/iphone/godot_view_gesture_recognizer.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* icloud.h */ +/* godot_view_gesture_recognizer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,38 +28,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef ICLOUD_ENABLED +// GLViewGestureRecognizer allows iOS gestures to work currectly by +// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer. +// It catches all gestures incoming to UIView and delays them for 150ms +// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer) +// If touch cancellation or end message is fired it fires delayed +// begin touch immediately as well as last touch signal -#ifndef ICLOUD_H -#define ICLOUD_H +#import <UIKit/UIKit.h> -#include "core/object.h" +@interface GodotViewGestureRecognizer : UIGestureRecognizer -class ICloud : public Object { +@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval; - GDCLASS(ICloud, Object); +- (instancetype)init; - static ICloud *instance; - static void _bind_methods(); - - List<Variant> pending_events; - -public: - Error remove_key(Variant p_param); - Variant set_key_values(Variant p_param); - Variant get_key_value(Variant p_param); - Error synchronize_key_values(); - Variant get_all_key_values(); - - int get_pending_event_count(); - Variant pop_pending_event(); - - static ICloud *get_singleton(); - - ICloud(); - ~ICloud(); -}; - -#endif - -#endif +@end diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm new file mode 100644 index 0000000000..91cc07fcf6 --- /dev/null +++ b/platform/iphone/godot_view_gesture_recognizer.mm @@ -0,0 +1,171 @@ +/*************************************************************************/ +/* godot_view_gesture_recognizer.mm */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#import "godot_view_gesture_recognizer.h" + +#include "core/config/project_settings.h" + +// Minimum distance for touches to move to fire +// a delay timer before scheduled time. +// Should be the low enough to not cause issues with dragging +// but big enough to allow click to work. +const CGFloat kGLGestureMovementDistance = 0.5; + +@interface GodotViewGestureRecognizer () + +@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval; + +@end + +@interface GodotViewGestureRecognizer () + +// Timer used to delay begin touch message. +// Should work as simple emulation of UIDelayedAction +@property(strong, nonatomic) NSTimer *delayTimer; + +// Delayed touch parameters +@property(strong, nonatomic) NSSet *delayedTouches; +@property(strong, nonatomic) UIEvent *delayedEvent; + +@end + +@implementation GodotViewGestureRecognizer + +- (instancetype)init { + self = [super init]; + + self.cancelsTouchesInView = YES; + self.delaysTouchesBegan = YES; + self.delaysTouchesEnded = YES; + + self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay"); + + return self; +} + +- (void)dealloc { + if (self.delayTimer) { + [self.delayTimer invalidate]; + self.delayTimer = nil; + } + + if (self.delayedTouches) { + self.delayedTouches = nil; + } + + if (self.delayedEvent) { + self.delayedEvent = nil; + } +} + +- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event { + [self.delayTimer fire]; + + self.delayedTouches = touches; + self.delayedEvent = event; + + self.delayTimer = [NSTimer + scheduledTimerWithTimeInterval:self.delayTimeInterval + target:self + selector:@selector(fireDelayedTouches:) + userInfo:nil + repeats:NO]; +} + +- (void)fireDelayedTouches:(id)timer { + [self.delayTimer invalidate]; + self.delayTimer = nil; + + if (self.delayedTouches) { + [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent]; + } + + self.delayedTouches = nil; + self.delayedEvent = nil; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan]; + [self delayTouches:cleared andEvent:event]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved]; + + if (self.delayTimer) { + // We should check if movement was significant enough to fire an event + // for dragging to work correctly. + for (UITouch *touch in cleared) { + CGPoint from = [touch locationInView:self.view]; + CGPoint to = [touch previousLocationInView:self.view]; + CGFloat xDistance = from.x - to.x; + CGFloat yDistance = from.y - to.y; + + CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance); + + // Early exit, since one of touches has moved enough to fire a drag event. + if (distance > kGLGestureMovementDistance) { + [self.delayTimer fire]; + [self.view touchesMoved:cleared withEvent:event]; + return; + } + } + + return; + } + + [self.view touchesMoved:cleared withEvent:event]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [self.delayTimer fire]; + + NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; + [self.view touchesEnded:cleared withEvent:event]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + [self.delayTimer fire]; + [self.view touchesCancelled:touches withEvent:event]; +}; + +- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave { + NSMutableSet *cleared = [touches mutableCopy]; + + for (UITouch *touch in touches) { + if (touch.phase != phaseToSave) { + [cleared removeObject:touch]; + } + } + + return cleared; +} + +@end diff --git a/platform/haiku/godot_haiku.cpp b/platform/iphone/godot_view_renderer.h index 0657f4c052..ea8998c808 100644 --- a/platform/haiku/godot_haiku.cpp +++ b/platform/iphone/godot_view_renderer.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* godot_haiku.cpp */ +/* godot_view_renderer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,22 +28,17 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "main/main.h" -#include "os_haiku.h" +#import <UIKit/UIKit.h> -int main(int argc, char *argv[]) { - OS_Haiku os; +@protocol GodotViewRendererProtocol <NSObject> - Error error = Main::setup(argv[0], argc - 1, &argv[1]); - if (error != OK) { - return 255; - } +@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; - if (Main::start()) { - os.run(); - } +- (BOOL)setupView:(UIView *)view; +- (void)renderOnView:(UIView *)view; - Main::cleanup(); +@end - return os.get_exit_code(); -} +@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol> + +@end diff --git a/platform/haiku/haiku_gl_view.cpp b/platform/iphone/godot_view_renderer.mm index 970a1276fd..f2ff417e6f 100644 --- a/platform/haiku/haiku_gl_view.cpp +++ b/platform/iphone/godot_view_renderer.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* haiku_gl_view.cpp */ +/* godot_view_renderer.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,20 +28,90 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "haiku_gl_view.h" +#import "godot_view_renderer.h" +#include "core/config/project_settings.h" +#include "core/os/keyboard.h" +#import "display_server_iphone.h" #include "main/main.h" +#include "os_iphone.h" +#include "servers/audio_server.h" -HaikuGLView::HaikuGLView(BRect frame, uint32 type) : - BGLView(frame, "GodotGLView", B_FOLLOW_ALL_SIDES, 0, type) { +#import <AudioToolbox/AudioServices.h> +#import <CoreMotion/CoreMotion.h> +#import <GameController/GameController.h> +#import <QuartzCore/QuartzCore.h> +#import <UIKit/UIKit.h> + +@interface GodotViewRenderer () + +@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup; +@property(assign, nonatomic) BOOL hasStartedMain; +@property(assign, nonatomic) BOOL hasFinishedSetup; + +@end + +@implementation GodotViewRenderer + +- (BOOL)setupView:(UIView *)view { + if (self.hasFinishedSetup) { + return NO; + } + + if (!OS::get_singleton()) { + exit(0); + } + + if (!self.hasFinishedProjectDataSetup) { + [self setupProjectData]; + return YES; + } + + if (!self.hasStartedMain) { + self.hasStartedMain = YES; + OSIPhone::get_singleton()->start(); + return YES; + } + + self.hasFinishedSetup = YES; + + return NO; } -void HaikuGLView::AttachedToWindow(void) { - LockGL(); - BGLView::AttachedToWindow(); - UnlockGL(); - MakeFocus(); +- (void)setupProjectData { + self.hasFinishedProjectDataSetup = YES; + + Main::setup2(); + + // this might be necessary before here + NSDictionary *dict = [[NSBundle mainBundle] infoDictionary]; + for (NSString *key in dict) { + NSObject *value = [dict objectForKey:key]; + String ukey = String::utf8([key UTF8String]); + + // we need a NSObject to Variant conversor + + if ([value isKindOfClass:[NSString class]]) { + NSString *str = (NSString *)value; + String uval = String::utf8([str UTF8String]); + + ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval); + + } else if ([value isKindOfClass:[NSNumber class]]) { + NSNumber *n = (NSNumber *)value; + double dval = [n doubleValue]; + + ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval); + }; + // do stuff + } } -void HaikuGLView::Draw(BRect updateRect) { - Main::force_redraw(); +- (void)renderOnView:(UIView *)view { + if (!OSIPhone::get_singleton()) { + return; + } + + OSIPhone::get_singleton()->iterate(); } + +@end diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm deleted file mode 100644 index 251f78f2da..0000000000 --- a/platform/iphone/icloud.mm +++ /dev/null @@ -1,359 +0,0 @@ -/*************************************************************************/ -/* icloud.mm */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -#ifdef ICLOUD_ENABLED - -#include "icloud.h" - -#ifndef __IPHONE_9_0 -extern "C" { -#endif - -#import "app_delegate.h" - -#import <Foundation/Foundation.h> - -#ifndef __IPHONE_9_0 -}; -#endif - -ICloud *ICloud::instance = NULL; - -void ICloud::_bind_methods() { - ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key); - ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values); - ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value); - ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values); - ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values); - - ClassDB::bind_method(D_METHOD("get_pending_event_count"), &ICloud::get_pending_event_count); - ClassDB::bind_method(D_METHOD("pop_pending_event"), &ICloud::pop_pending_event); -}; - -int ICloud::get_pending_event_count() { - - return pending_events.size(); -}; - -Variant ICloud::pop_pending_event() { - - Variant front = pending_events.front()->get(); - pending_events.pop_front(); - - return front; -}; - -ICloud *ICloud::get_singleton() { - return instance; -}; - -//convert from apple's abstract type to godot's abstract type.... -Variant nsobject_to_variant(NSObject *object) { - if ([object isKindOfClass:[NSString class]]) { - const char *str = [(NSString *)object UTF8String]; - return String::utf8(str != NULL ? str : ""); - } else if ([object isKindOfClass:[NSData class]]) { - PackedByteArray ret; - NSData *data = (NSData *)object; - if ([data length] > 0) { - ret.resize([data length]); - { - // PackedByteArray::Write w = ret.write(); - copymem((void *)ret.ptr(), [data bytes], [data length]); - } - } - return ret; - } else if ([object isKindOfClass:[NSArray class]]) { - Array result; - NSArray *array = (NSArray *)object; - for (unsigned int i = 0; i < [array count]; ++i) { - NSObject *value = [array objectAtIndex:i]; - result.push_back(nsobject_to_variant(value)); - } - return result; - } else if ([object isKindOfClass:[NSDictionary class]]) { - Dictionary result; - NSDictionary *dic = (NSDictionary *)object; - - NSArray *keys = [dic allKeys]; - int count = [keys count]; - for (int i = 0; i < count; ++i) { - NSObject *k = [keys objectAtIndex:i]; - NSObject *v = [dic objectForKey:k]; - - result[nsobject_to_variant(k)] = nsobject_to_variant(v); - } - return result; - } else if ([object isKindOfClass:[NSNumber class]]) { - //Every type except numbers can reliably identify its type. The following is comparing to the *internal* representation, which isn't guaranteed to match the type that was used to create it, and is not advised, particularly when dealing with potential platform differences (ie, 32/64 bit) - //To avoid errors, we'll cast as broadly as possible, and only return int or float. - //bool, char, int, uint, longlong -> int - //float, double -> float - NSNumber *num = (NSNumber *)object; - if (strcmp([num objCType], @encode(BOOL)) == 0) { - return Variant((int)[num boolValue]); - } else if (strcmp([num objCType], @encode(char)) == 0) { - return Variant((int)[num charValue]); - } else if (strcmp([num objCType], @encode(int)) == 0) { - return Variant([num intValue]); - } else if (strcmp([num objCType], @encode(unsigned int)) == 0) { - return Variant((int)[num unsignedIntValue]); - } else if (strcmp([num objCType], @encode(long long)) == 0) { - return Variant((int)[num longValue]); - } else if (strcmp([num objCType], @encode(float)) == 0) { - return Variant([num floatValue]); - } else if (strcmp([num objCType], @encode(double)) == 0) { - return Variant((float)[num doubleValue]); - } else { - return Variant(); - } - } else if ([object isKindOfClass:[NSDate class]]) { - //this is a type that icloud supports...but how did you submit it in the first place? - //I guess this is a type that *might* show up, if you were, say, trying to make your game - //compatible with existing cloud data written by another engine's version of your game - WARN_PRINT("NSDate unsupported, returning null Variant"); - return Variant(); - } else if ([object isKindOfClass:[NSNull class]] or object == nil) { - return Variant(); - } else { - WARN_PRINT("Trying to convert unknown NSObject type to Variant"); - return Variant(); - } -} - -NSObject *variant_to_nsobject(Variant v) { - if (v.get_type() == Variant::STRING) { - return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease]; - } else if (v.get_type() == Variant::REAL) { - return [NSNumber numberWithDouble:(double)v]; - } else if (v.get_type() == Variant::INT) { - return [NSNumber numberWithLongLong:(long)(int)v]; - } else if (v.get_type() == Variant::BOOL) { - return [NSNumber numberWithBool:BOOL((bool)v)]; - } else if (v.get_type() == Variant::DICTIONARY) { - NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease]; - Dictionary dic = v; - Array keys = dic.keys(); - for (unsigned int i = 0; i < keys.size(); ++i) { - NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease]; - NSObject *value = variant_to_nsobject(dic[keys[i]]); - - if (key == NULL || value == NULL) { - return NULL; - } - - [result setObject:value forKey:key]; - } - return result; - } else if (v.get_type() == Variant::ARRAY) { - NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease]; - Array arr = v; - for (unsigned int i = 0; i < arr.size(); ++i) { - NSObject *value = variant_to_nsobject(arr[i]); - if (value == NULL) { - //trying to add something unsupported to the array. cancel the whole array - return NULL; - } - [result addObject:value]; - } - return result; - } else if (v.get_type() == Variant::PACKED_BYTE_ARRAY) { - PackedByteArray arr = v; - // PackedByteArray::Read r = arr.read(); - NSData *result = [NSData dataWithBytes:arr.ptr() length:arr.size()]; - return result; - } - WARN_PRINT(String("Could not add unsupported type to iCloud: '" + Variant::get_type_name(v.get_type()) + "'").utf8().get_data()); - return NULL; -} - -Error ICloud::remove_key(Variant p_param) { - String param = p_param; - NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease]; - - NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; - - if (![[store dictionaryRepresentation] objectForKey:key]) { - return ERR_INVALID_PARAMETER; - } - - [store removeObjectForKey:key]; - return OK; -} - -//return an array of the keys that could not be set -Variant ICloud::set_key_values(Variant p_params) { - Dictionary params = p_params; - Array keys = params.keys(); - - Array error_keys; - - for (unsigned int i = 0; i < keys.size(); ++i) { - String variant_key = keys[i]; - Variant variant_value = params[variant_key]; - - NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease]; - if (key == NULL) { - error_keys.push_back(variant_key); - continue; - } - - NSObject *value = variant_to_nsobject(variant_value); - - if (value == NULL) { - error_keys.push_back(variant_key); - continue; - } - - NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; - [store setObject:value forKey:key]; - } - - return error_keys; -} - -Variant ICloud::get_key_value(Variant p_param) { - String param = p_param; - - NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease]; - NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; - - if (![[store dictionaryRepresentation] objectForKey:key]) { - return Variant(); - } - - Variant result = nsobject_to_variant([[store dictionaryRepresentation] objectForKey:key]); - - return result; -} - -Variant ICloud::get_all_key_values() { - Dictionary result; - - NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; - NSDictionary *store_dictionary = [store dictionaryRepresentation]; - - NSArray *keys = [store_dictionary allKeys]; - int count = [keys count]; - for (int i = 0; i < count; ++i) { - NSString *k = [keys objectAtIndex:i]; - NSObject *v = [store_dictionary objectForKey:k]; - - const char *str = [k UTF8String]; - if (str != NULL) { - result[String::utf8(str)] = nsobject_to_variant(v); - } - } - - return result; -} - -Error ICloud::synchronize_key_values() { - NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; - BOOL result = [store synchronize]; - if (result == YES) { - return OK; - } else { - return FAILED; - } -} -/* -Error ICloud::initial_sync() { - //you sometimes have to write something to the store to get it to download new data. go apple! - NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; - if ([store boolForKey:@"isb"]) - { - [store setBool:NO forKey:@"isb"]; - } - else - { - [store setBool:YES forKey:@"isb"]; - } - return synchronize(); -} -*/ -ICloud::ICloud() { - ERR_FAIL_COND(instance != NULL); - instance = this; - //connected = false; - - [[NSNotificationCenter defaultCenter] - addObserverForName:NSUbiquitousKeyValueStoreDidChangeExternallyNotification - object:[NSUbiquitousKeyValueStore defaultStore] - queue:nil - usingBlock:^(NSNotification *notification) { - NSDictionary *userInfo = [notification userInfo]; - NSInteger change = [[userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey] integerValue]; - - Dictionary ret; - ret["type"] = "key_value_changed"; - - //PackedStringArray result_keys; - //Array result_values; - Dictionary keyValues; - String reason = ""; - - if (change == NSUbiquitousKeyValueStoreServerChange) { - reason = "server"; - } else if (change == NSUbiquitousKeyValueStoreInitialSyncChange) { - reason = "initial_sync"; - } else if (change == NSUbiquitousKeyValueStoreQuotaViolationChange) { - reason = "quota_violation"; - } else if (change == NSUbiquitousKeyValueStoreAccountChange) { - reason = "account"; - } - - ret["reason"] = reason; - - NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; - - NSArray *keys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]; - for (NSString *key in keys) { - const char *str = [key UTF8String]; - if (str == NULL) { - continue; - } - - NSObject *object = [store objectForKey:key]; - - //figure out what kind of object it is - Variant value = nsobject_to_variant(object); - - keyValues[String::utf8(str)] = value; - } - - ret["changed_values"] = keyValues; - pending_events.push_back(ret); - }]; -} - -ICloud::~ICloud(){}; - -#endif diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm deleted file mode 100644 index a8a887824f..0000000000 --- a/platform/iphone/in_app_store.mm +++ /dev/null @@ -1,339 +0,0 @@ -/*************************************************************************/ -/* in_app_store.mm */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -#ifdef STOREKIT_ENABLED - -#include "in_app_store.h" - -extern "C" { -#import <Foundation/Foundation.h> -#import <StoreKit/StoreKit.h> -}; - -bool auto_finish_transactions = true; -NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary]; - -@interface SKProduct (LocalizedPrice) -@property(nonatomic, readonly) NSString *localizedPrice; -@end - -//----------------------------------// -// SKProduct extension -//----------------------------------// -@implementation SKProduct (LocalizedPrice) -- (NSString *)localizedPrice { - NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; - [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; - [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; - [numberFormatter setLocale:self.priceLocale]; - NSString *formattedString = [numberFormatter stringFromNumber:self.price]; - [numberFormatter release]; - return formattedString; -} -@end - -InAppStore *InAppStore::instance = NULL; - -void InAppStore::_bind_methods() { - ClassDB::bind_method(D_METHOD("request_product_info"), &InAppStore::request_product_info); - ClassDB::bind_method(D_METHOD("restore_purchases"), &InAppStore::restore_purchases); - ClassDB::bind_method(D_METHOD("purchase"), &InAppStore::purchase); - - ClassDB::bind_method(D_METHOD("get_pending_event_count"), &InAppStore::get_pending_event_count); - ClassDB::bind_method(D_METHOD("pop_pending_event"), &InAppStore::pop_pending_event); - ClassDB::bind_method(D_METHOD("finish_transaction"), &InAppStore::finish_transaction); - ClassDB::bind_method(D_METHOD("set_auto_finish_transaction"), &InAppStore::set_auto_finish_transaction); -}; - -@interface ProductsDelegate : NSObject <SKProductsRequestDelegate> { -}; - -@end - -@implementation ProductsDelegate - -- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { - - NSArray *products = response.products; - Dictionary ret; - ret["type"] = "product_info"; - ret["result"] = "ok"; - PackedStringArray titles; - PackedStringArray descriptions; - PackedFloat32Array prices; - PackedStringArray ids; - PackedStringArray localized_prices; - PackedStringArray currency_codes; - - for (NSUInteger i = 0; i < [products count]; i++) { - - SKProduct *product = [products objectAtIndex:i]; - - const char *str = [product.localizedTitle UTF8String]; - titles.push_back(String::utf8(str != NULL ? str : "")); - - str = [product.localizedDescription UTF8String]; - descriptions.push_back(String::utf8(str != NULL ? str : "")); - prices.push_back([product.price doubleValue]); - ids.push_back(String::utf8([product.productIdentifier UTF8String])); - localized_prices.push_back(String::utf8([product.localizedPrice UTF8String])); - currency_codes.push_back(String::utf8([[[product priceLocale] objectForKey:NSLocaleCurrencyCode] UTF8String])); - }; - ret["titles"] = titles; - ret["descriptions"] = descriptions; - ret["prices"] = prices; - ret["ids"] = ids; - ret["localized_prices"] = localized_prices; - ret["currency_codes"] = currency_codes; - - PackedStringArray invalid_ids; - - for (NSString *ipid in response.invalidProductIdentifiers) { - - invalid_ids.push_back(String::utf8([ipid UTF8String])); - }; - ret["invalid_ids"] = invalid_ids; - - InAppStore::get_singleton()->_post_event(ret); - - [request release]; -}; - -@end - -Error InAppStore::request_product_info(Variant p_params) { - - Dictionary params = p_params; - ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER); - - PackedStringArray pids = params["product_ids"]; - printf("************ request product info! %i\n", pids.size()); - - NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease]; - for (int i = 0; i < pids.size(); i++) { - printf("******** adding %ls to product list\n", pids[i].c_str()); - NSString *pid = [[[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()] autorelease]; - [array addObject:pid]; - }; - - NSSet *products = [[[NSSet alloc] initWithArray:array] autorelease]; - SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products]; - - ProductsDelegate *delegate = [[ProductsDelegate alloc] init]; - - request.delegate = delegate; - [request start]; - - return OK; -}; - -Error InAppStore::restore_purchases() { - - printf("restoring purchases!\n"); - [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; - - return OK; -}; - -@interface TransObserver : NSObject <SKPaymentTransactionObserver> { -}; -@end - -@implementation TransObserver - -- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { - - printf("transactions updated!\n"); - for (SKPaymentTransaction *transaction in transactions) { - - switch (transaction.transactionState) { - case SKPaymentTransactionStatePurchased: { - printf("status purchased!\n"); - String pid = String::utf8([transaction.payment.productIdentifier UTF8String]); - String transactionId = String::utf8([transaction.transactionIdentifier UTF8String]); - InAppStore::get_singleton()->_record_purchase(pid); - Dictionary ret; - ret["type"] = "purchase"; - ret["result"] = "ok"; - ret["product_id"] = pid; - ret["transaction_id"] = transactionId; - - NSData *receipt = nil; - int sdk_version = 6; - - if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) { - - NSURL *receiptFileURL = nil; - NSBundle *bundle = [NSBundle mainBundle]; - if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) { - - // Get the transaction receipt file path location in the app bundle. - receiptFileURL = [bundle appStoreReceiptURL]; - - // Read in the contents of the transaction file. - receipt = [NSData dataWithContentsOfURL:receiptFileURL]; - sdk_version = 7; - - } else { - // Fall back to deprecated transaction receipt, - // which is still available in iOS 7. - - // Use SKPaymentTransaction's transactionReceipt. - receipt = transaction.transactionReceipt; - } - - } else { - receipt = transaction.transactionReceipt; - } - - NSString *receipt_to_send = nil; - if (receipt != nil) { - receipt_to_send = [receipt description]; - } - Dictionary receipt_ret; - receipt_ret["receipt"] = String::utf8(receipt_to_send != nil ? [receipt_to_send UTF8String] : ""); - receipt_ret["sdk"] = sdk_version; - ret["receipt"] = receipt_ret; - - InAppStore::get_singleton()->_post_event(ret); - - if (auto_finish_transactions) { - [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; - } else { - [pending_transactions setObject:transaction forKey:transaction.payment.productIdentifier]; - } - - }; break; - case SKPaymentTransactionStateFailed: { - printf("status transaction failed!\n"); - String pid = String::utf8([transaction.payment.productIdentifier UTF8String]); - Dictionary ret; - ret["type"] = "purchase"; - ret["result"] = "error"; - ret["product_id"] = pid; - ret["error"] = String::utf8([transaction.error.localizedDescription UTF8String]); - InAppStore::get_singleton()->_post_event(ret); - [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; - } break; - case SKPaymentTransactionStateRestored: { - printf("status transaction restored!\n"); - String pid = String::utf8([transaction.originalTransaction.payment.productIdentifier UTF8String]); - InAppStore::get_singleton()->_record_purchase(pid); - Dictionary ret; - ret["type"] = "restore"; - ret["result"] = "ok"; - ret["product_id"] = pid; - InAppStore::get_singleton()->_post_event(ret); - [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; - } break; - default: { - printf("status default %i!\n", (int)transaction.transactionState); - }; break; - }; - }; -}; - -@end - -Error InAppStore::purchase(Variant p_params) { - - ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE); - if (![SKPaymentQueue canMakePayments]) - return ERR_UNAVAILABLE; - - printf("purchasing!\n"); - Dictionary params = p_params; - ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER); - - NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease]; - SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid]; - SKPaymentQueue *defq = [SKPaymentQueue defaultQueue]; - [defq addPayment:payment]; - printf("purchase sent!\n"); - - return OK; -}; - -int InAppStore::get_pending_event_count() { - return pending_events.size(); -}; - -Variant InAppStore::pop_pending_event() { - - Variant front = pending_events.front()->get(); - pending_events.pop_front(); - - return front; -}; - -void InAppStore::_post_event(Variant p_event) { - - pending_events.push_back(p_event); -}; - -void InAppStore::_record_purchase(String product_id) { - - String skey = "purchased/" + product_id; - NSString *key = [[[NSString alloc] initWithUTF8String:skey.utf8().get_data()] autorelease]; - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:key]; - [[NSUserDefaults standardUserDefaults] synchronize]; -}; - -InAppStore *InAppStore::get_singleton() { - - return instance; -}; - -InAppStore::InAppStore() { - ERR_FAIL_COND(instance != NULL); - instance = this; - auto_finish_transactions = false; - - TransObserver *observer = [[TransObserver alloc] init]; - [[SKPaymentQueue defaultQueue] addTransactionObserver:observer]; - //pending_transactions = [NSMutableDictionary dictionary]; -}; - -void InAppStore::finish_transaction(String product_id) { - NSString *prod_id = [NSString stringWithCString:product_id.utf8().get_data() encoding:NSUTF8StringEncoding]; - - if ([pending_transactions objectForKey:prod_id]) { - [[SKPaymentQueue defaultQueue] finishTransaction:[pending_transactions objectForKey:prod_id]]; - [pending_transactions removeObjectForKey:prod_id]; - } -}; - -void InAppStore::set_auto_finish_transaction(bool b) { - auto_finish_transactions = b; -} - -InAppStore::~InAppStore(){}; - -#endif diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h index 1378fdbbc5..5415c36cd9 100644 --- a/platform/iphone/ios.h +++ b/platform/iphone/ios.h @@ -31,10 +31,9 @@ #ifndef IOS_H #define IOS_H -#include "core/object.h" +#include "core/object/class_db.h" class iOS : public Object { - GDCLASS(iOS, Object); static void _bind_methods(); diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 2656f125b9..d4e099063f 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -29,18 +29,28 @@ /*************************************************************************/ #include "ios.h" -#include <sys/sysctl.h> - +#import "app_delegate.h" +#import "view_controller.h" #import <UIKit/UIKit.h> +#include <sys/sysctl.h> void iOS::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url); }; void iOS::alert(const char *p_alert, const char *p_title) { - UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease]; - [alert show]; + NSString *title = [NSString stringWithUTF8String:p_title]; + NSString *message = [NSString stringWithUTF8String:p_alert]; + + UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleCancel + handler:^(id){ + }]; + + [alert addAction:button]; + + [AppDelegate.viewController presentViewController:alert animated:YES completion:nil]; } String iOS::get_model() const { @@ -59,26 +69,12 @@ String iOS::get_model() const { } String iOS::get_rate_url(int p_app_id) const { - String templ = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID"; - String templ_iOS7 = "itms-apps://itunes.apple.com/app/idAPP_ID"; - String templ_iOS8 = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software"; - - //ios7 before - String ret = templ; - - if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 7.1) { - // iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131 - ret = templ_iOS7; - } else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { - // iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182 - ret = templ_iOS8; - } + String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID"; - // ios7 for everything? - ret = templ_iOS7.replace("APP_ID", String::num(p_app_id)); + String ret = app_url_path.replace("APP_ID", String::num(p_app_id)); - printf("returning rate url %ls\n", ret.c_str()); + printf("returning rate url %s\n", ret.utf8().get_data()); return ret; }; -iOS::iOS(){}; +iOS::iOS() {} diff --git a/platform/haiku/haiku_gl_view.h b/platform/iphone/joypad_iphone.h index 59e02d2367..85e26e1dc8 100644 --- a/platform/haiku/haiku_gl_view.h +++ b/platform/iphone/joypad_iphone.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* haiku_gl_view.h */ +/* joypad_iphone.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,18 +28,23 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef HAIKU_GL_VIEW_H -#define HAIKU_GL_VIEW_H +#import <GameController/GameController.h> -#include <kernel/image.h> // needed for image_id +@interface JoypadIPhoneObserver : NSObject -#include <GLView.h> +- (void)startObserving; +- (void)startProcessing; +- (void)finishObserving; + +@end + +class JoypadIPhone { +private: + JoypadIPhoneObserver *observer; -class HaikuGLView : public BGLView { public: - HaikuGLView(BRect frame, uint32 type); - virtual void AttachedToWindow(void); - virtual void Draw(BRect updateRect); -}; + JoypadIPhone(); + ~JoypadIPhone(); -#endif + void start_processing(); +}; diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm new file mode 100644 index 0000000000..22d7dd4bd2 --- /dev/null +++ b/platform/iphone/joypad_iphone.mm @@ -0,0 +1,345 @@ +/*************************************************************************/ +/* joypad_iphone.mm */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#import "joypad_iphone.h" +#include "core/config/project_settings.h" +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "main/main.h" + +#import "godot_view.h" + +#include "os_iphone.h" + +JoypadIPhone::JoypadIPhone() { + observer = [[JoypadIPhoneObserver alloc] init]; + [observer startObserving]; +} + +JoypadIPhone::~JoypadIPhone() { + if (observer) { + [observer finishObserving]; + observer = nil; + } +} + +void JoypadIPhone::start_processing() { + if (observer) { + [observer startProcessing]; + } +} + +@interface JoypadIPhoneObserver () + +@property(assign, nonatomic) BOOL isObserving; +@property(assign, nonatomic) BOOL isProcessing; +@property(strong, nonatomic) NSMutableDictionary *connectedJoypads; +@property(strong, nonatomic) NSMutableArray *joypadsQueue; + +@end + +@implementation JoypadIPhoneObserver + +- (instancetype)init { + self = [super init]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + self.isObserving = NO; + self.isProcessing = NO; +} + +- (void)startProcessing { + self.isProcessing = YES; + + for (GCController *controller in self.joypadsQueue) { + [self addiOSJoypad:controller]; + } + + [self.joypadsQueue removeAllObjects]; +} + +- (void)startObserving { + if (self.isObserving) { + return; + } + + self.isObserving = YES; + + self.connectedJoypads = [NSMutableDictionary dictionary]; + self.joypadsQueue = [NSMutableArray array]; + + // get told when controllers connect, this will be called right away for + // already connected controllers + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(controllerWasConnected:) + name:GCControllerDidConnectNotification + object:nil]; + + // get told when controllers disconnect + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(controllerWasDisconnected:) + name:GCControllerDidDisconnectNotification + object:nil]; +} + +- (void)finishObserving { + if (self.isObserving) { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } + + self.isObserving = NO; + self.isProcessing = NO; + + self.connectedJoypads = nil; + self.joypadsQueue = nil; +} + +- (void)dealloc { + [self finishObserving]; +} + +- (int)getJoyIdForController:(GCController *)controller { + NSArray *keys = [self.connectedJoypads allKeysForObject:controller]; + + for (NSNumber *key in keys) { + int joy_id = [key intValue]; + return joy_id; + }; + + return -1; +}; + +- (void)addiOSJoypad:(GCController *)controller { + // get a new id for our controller + int joy_id = Input::get_singleton()->get_unused_joy_id(); + + if (joy_id == -1) { + printf("Couldn't retrieve new joy id\n"); + return; + } + + // assign our player index + if (controller.playerIndex == GCControllerPlayerIndexUnset) { + controller.playerIndex = [self getFreePlayerIndex]; + }; + + // tell Godot about our new controller + Input::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]); + + // add it to our dictionary, this will retain our controllers + [self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]]; + + // set our input handler + [self setControllerInputHandler:controller]; +} + +- (void)controllerWasConnected:(NSNotification *)notification { + // get our controller + GCController *controller = (GCController *)notification.object; + + if (!controller) { + printf("Couldn't retrieve new controller\n"); + return; + } + + if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) { + printf("Controller is already registered\n"); + } else if (!self.isProcessing) { + [self.joypadsQueue addObject:controller]; + } else { + [self addiOSJoypad:controller]; + } +} + +- (void)controllerWasDisconnected:(NSNotification *)notification { + // find our joystick, there should be only one in our dictionary + GCController *controller = (GCController *)notification.object; + + if (!controller) { + return; + } + + NSArray *keys = [self.connectedJoypads allKeysForObject:controller]; + for (NSNumber *key in keys) { + // tell Godot this joystick is no longer there + int joy_id = [key intValue]; + Input::get_singleton()->joy_connection_changed(joy_id, false, ""); + + // and remove it from our dictionary + [self.connectedJoypads removeObjectForKey:key]; + }; +}; + +- (GCControllerPlayerIndex)getFreePlayerIndex { + bool have_player_1 = false; + bool have_player_2 = false; + bool have_player_3 = false; + bool have_player_4 = false; + + if (self.connectedJoypads == nil) { + NSArray *keys = [self.connectedJoypads allKeys]; + for (NSNumber *key in keys) { + GCController *controller = [self.connectedJoypads objectForKey:key]; + if (controller.playerIndex == GCControllerPlayerIndex1) { + have_player_1 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex2) { + have_player_2 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex3) { + have_player_3 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex4) { + have_player_4 = true; + }; + }; + }; + + if (!have_player_1) { + return GCControllerPlayerIndex1; + } else if (!have_player_2) { + return GCControllerPlayerIndex2; + } else if (!have_player_3) { + return GCControllerPlayerIndex3; + } else if (!have_player_4) { + return GCControllerPlayerIndex4; + } else { + return GCControllerPlayerIndexUnset; + }; +} + +- (void)setControllerInputHandler:(GCController *)controller { + // Hook in the callback handler for the correct gamepad profile. + // This is a bit of a weird design choice on Apples part. + // You need to select the most capable gamepad profile for the + // gamepad attached. + if (controller.extendedGamepad != nil) { + // The extended gamepad profile has all the input you could possibly find on + // a gamepad but will only be active if your gamepad actually has all of + // these... + _weakify(self); + _weakify(controller); + + controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonB) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, + gamepad.buttonB.isPressed); + } else if (element == gamepad.buttonX) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.buttonY) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, + gamepad.buttonY.isPressed); + } else if (element == gamepad.leftShoulder) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, + gamepad.leftShoulder.isPressed); + } else if (element == gamepad.rightShoulder) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, + gamepad.rightShoulder.isPressed); + } else if (element == gamepad.dpad) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, + gamepad.dpad.up.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, + gamepad.dpad.down.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, + gamepad.dpad.left.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, + gamepad.dpad.right.isPressed); + }; + + Input::JoyAxis jx; + jx.min = -1; + if (element == gamepad.leftThumbstick) { + jx.value = gamepad.leftThumbstick.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx); + jx.value = -gamepad.leftThumbstick.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx); + } else if (element == gamepad.rightThumbstick) { + jx.value = gamepad.rightThumbstick.xAxis.value; + Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx); + jx.value = -gamepad.rightThumbstick.yAxis.value; + Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx); + } else if (element == gamepad.leftTrigger) { + jx.value = gamepad.leftTrigger.value; + Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx); + } else if (element == gamepad.rightTrigger) { + jx.value = gamepad.rightTrigger.value; + Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx); + }; + }; + } else if (controller.microGamepad != nil) { + // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad + _weakify(self); + _weakify(controller); + + controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) { + _strongify(self); + _strongify(controller); + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonX) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.dpad) { + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, + gamepad.dpad.up.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, + gamepad.dpad.down.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, gamepad.dpad.left.isPressed); + Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, gamepad.dpad.right.isPressed); + }; + }; + } + + ///@TODO need to add support for controller.motion which gives us access to + /// the orientation of the device (if supported) + + ///@TODO need to add support for controllerPausedHandler which should be a + /// toggle +}; + +@end diff --git a/platform/haiku/haiku_application.h b/platform/iphone/keyboard_input_view.h index 2e04d921bf..5382a2e9a9 100644 --- a/platform/haiku/haiku_application.h +++ b/platform/iphone/keyboard_input_view.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* haiku_application.h */ +/* keyboard_input_view.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,16 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef HAIKU_APPLICATION_H -#define HAIKU_APPLICATION_H +#import <UIKit/UIKit.h> -#include <kernel/image.h> // needed for image_id +@interface GodotKeyboardInputView : UITextView -#include <Application.h> +- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end; -class HaikuApplication : public BApplication { -public: - HaikuApplication(); -}; - -#endif +@end diff --git a/platform/iphone/keyboard_input_view.mm b/platform/iphone/keyboard_input_view.mm new file mode 100644 index 0000000000..1a37403de7 --- /dev/null +++ b/platform/iphone/keyboard_input_view.mm @@ -0,0 +1,195 @@ +/*************************************************************************/ +/* keyboard_input_view.mm */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#import "keyboard_input_view.h" + +#include "core/os/keyboard.h" +#include "display_server_iphone.h" +#include "os_iphone.h" + +@interface GodotKeyboardInputView () <UITextViewDelegate> + +@property(nonatomic, copy) NSString *previousText; +@property(nonatomic, assign) NSRange previousSelectedRange; + +@end + +@implementation GodotKeyboardInputView + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer { + self = [super initWithFrame:frame textContainer:textContainer]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + self.hidden = YES; + self.delegate = self; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(observeTextChange:) + name:UITextViewTextDidChangeNotification + object:self]; +} + +- (void)dealloc { + self.delegate = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +// MARK: Keyboard + +- (BOOL)canBecomeFirstResponder { + return YES; +} + +- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end { + self.text = existingString; + self.previousText = existingString; + + NSRange textRange; + + // Either a simple cursor or a selection. + if (end > 0) { + textRange = NSMakeRange(start, end - start); + } else { + textRange = NSMakeRange(start, 0); + } + + self.selectedRange = textRange; + self.previousSelectedRange = textRange; + + return [self becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder { + self.text = nil; + self.previousText = nil; + return [super resignFirstResponder]; +} + +// MARK: OS Messages + +- (void)deleteText:(NSInteger)charactersToDelete { + for (int i = 0; i < charactersToDelete; i++) { + DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true); + DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, false); + } +} + +- (void)enterText:(NSString *)substring { + String characters; + characters.parse_utf8([substring UTF8String]); + + for (int i = 0; i < characters.size(); i++) { + int character = characters[i]; + + switch (character) { + case 10: + character = KEY_ENTER; + break; + case 8198: + character = KEY_SPACE; + break; + default: + break; + } + + DisplayServerIPhone::get_singleton()->key(character, true); + DisplayServerIPhone::get_singleton()->key(character, false); + } +} + +// MARK: Observer + +- (void)observeTextChange:(NSNotification *)notification { + if (notification.object != self) { + return; + } + + if (self.previousSelectedRange.length == 0) { + // We are deleting all text before cursor if no range was selected. + // This way any inserted or changed text will be updated. + NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location]; + [self deleteText:substringToDelete.length]; + } else { + // If text was previously selected + // we are sending only one `backspace`. + // It will remove all text from text input. + [self deleteText:1]; + } + + NSString *substringToEnter; + + if (self.selectedRange.length == 0) { + // If previous cursor had a selection + // we have to calculate an inserted text. + if (self.previousSelectedRange.length != 0) { + NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length; + NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location); + NSInteger rangeLength = MAX(0, rangeEnd - rangeStart); + + NSRange calculatedRange; + + if (rangeLength >= 0) { + calculatedRange = NSMakeRange(rangeStart, rangeLength); + } else { + calculatedRange = NSMakeRange(rangeStart, 0); + } + + substringToEnter = [self.text substringWithRange:calculatedRange]; + } else { + substringToEnter = [self.text substringToIndex:self.selectedRange.location]; + } + } else { + substringToEnter = [self.text substringWithRange:self.selectedRange]; + } + + [self enterText:substringToEnter]; + + self.previousText = self.text; + self.previousSelectedRange = self.selectedRange; +} + +@end diff --git a/platform/iphone/logo.png b/platform/iphone/logo.png Binary files differindex 405b6f93ca..966d8aa70a 100644 --- a/platform/iphone/logo.png +++ b/platform/iphone/logo.png diff --git a/platform/iphone/main.m b/platform/iphone/main.m index 164db2a74b..351b40a881 100644 --- a/platform/iphone/main.m +++ b/platform/iphone/main.m @@ -28,24 +28,30 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#import "app_delegate.h" +#import "godot_app_delegate.h" #import <UIKit/UIKit.h> #include <stdio.h> +#include <vulkan/vulkan.h> int gargc; char **gargv; int main(int argc, char *argv[]) { +#if defined(VULKAN_ENABLED) + //MoltenVK - enable full component swizzling support + setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); +#endif + printf("*********** main.m\n"); gargc = argc; gargv = argv; - NSAutoreleasePool *pool = [NSAutoreleasePool new]; - AppDelegate *app = [AppDelegate alloc]; printf("running app main\n"); - UIApplicationMain(argc, argv, nil, @"AppDelegate"); - printf("main done, pool release\n"); - [pool release]; + @autoreleasepool { + NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]); + UIApplicationMain(argc, argv, nil, className); + } + printf("main done\n"); return 0; } diff --git a/platform/haiku/key_mapping_haiku.h b/platform/iphone/native_video_view.h index a0e85e3390..d8687b3538 100644 --- a/platform/haiku/key_mapping_haiku.h +++ b/platform/iphone/native_video_view.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* key_mapping_haiku.h */ +/* native_video_view.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,15 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef KEY_MAPPING_HAIKU_H -#define KEY_MAPPING_HAIKU_H +#import <UIKit/UIKit.h> -class KeyMappingHaiku { - KeyMappingHaiku(){}; +@interface GodotNativeVideoView : UIView -public: - static unsigned int get_keysym(int32 raw_char, int32 key); - static unsigned int get_modifier_keysym(int32 key); -}; +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; +- (BOOL)isVideoPlaying; +- (void)pauseVideo; +- (void)unpauseVideo; +- (void)stopVideo; -#endif +@end diff --git a/platform/iphone/native_video_view.m b/platform/iphone/native_video_view.m new file mode 100644 index 0000000000..a4e9f209f0 --- /dev/null +++ b/platform/iphone/native_video_view.m @@ -0,0 +1,260 @@ +/*************************************************************************/ +/* native_video_view.m */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#import "native_video_view.h" +#import <AVFoundation/AVFoundation.h> + +@interface GodotNativeVideoView () + +@property(strong, nonatomic) AVAsset *avAsset; +@property(strong, nonatomic) AVPlayerItem *avPlayerItem; +@property(strong, nonatomic) AVPlayer *avPlayer; +@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; +@property(assign, nonatomic) CMTime videoCurrentTime; +@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying; + +@end + +@implementation GodotNativeVideoView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + [self godot_commonInit]; + } + + return self; +} + +- (void)godot_commonInit { + self.isVideoCurrentlyPlaying = NO; + self.videoCurrentTime = kCMTimeZero; + + [self observeVideoAudio]; +} + +- (void)observeVideoAudio { + printf("******** adding observer for sound routing changes\n"); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(audioRouteChangeListenerCallback:) + name:AVAudioSessionRouteChangeNotification + object:nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) { + [self handleVideoOrPlayerStatus]; + } + + if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) { + [self handleVideoPlayRate]; + } +} + +// MARK: Video Audio + +- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { + printf("*********** route changed!\n"); + NSDictionary *interuptionDict = notification.userInfo; + + NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; + + switch (routeChangeReason) { + case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { + NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); + NSLog(@"Headphone/Line plugged in"); + } break; + case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { + NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); + NSLog(@"Headphone/Line was pulled. Resuming video play...."); + if ([self isVideoPlaying]) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self.avPlayer play]; // NOTE: change this line according your current player implementation + NSLog(@"resumed play"); + }); + } + } break; + case AVAudioSessionRouteChangeReasonCategoryChange: { + // called at start - also when other audio wants to play + NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); + } break; + } +} + +// MARK: Native Video Player + +- (void)handleVideoOrPlayerStatus { + if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) { + [self stopVideo]; + } + + if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) { + // NSLog(@"time: %@", self.video_current_time); + [self.avPlayer seekToTime:self.videoCurrentTime]; + self.videoCurrentTime = kCMTimeZero; + } +} + +- (void)handleVideoPlayRate { + NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate); + if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self.avPlayer play]; // NOTE: change this line according your current player implementation + NSLog(@"resumed play"); + }); + + NSLog(@" . . . PAUSED (or just started)"); + } +} + +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { + self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]; + + self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset]; + [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; + + self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; + self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; + + [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(playerItemDidReachEnd:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:[self.avPlayer currentItem]]; + + [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; + + [self.avPlayerLayer setFrame:self.bounds]; + [self.layer addSublayer:self.avPlayerLayer]; + [self.avPlayer play]; + + AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + + NSMutableArray *allAudioParams = [NSMutableArray array]; + for (id track in audioGroup.options) { + NSString *language = [[track locale] localeIdentifier]; + NSLog(@"subtitle lang: %@", language); + + if ([language isEqualToString:audioTrack]) { + AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; + [audioInputParams setVolume:videoVolume atTime:kCMTimeZero]; + [audioInputParams setTrackID:[track trackID]]; + [allAudioParams addObject:audioInputParams]; + + AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; + [audioMix setInputParameters:allAudioParams]; + + [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; + [self.avPlayer.currentItem setAudioMix:audioMix]; + + break; + } + } + + AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; + NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; + + for (id track in useableTracks) { + NSString *language = [[track locale] localeIdentifier]; + NSLog(@"subtitle lang: %@", language); + + if ([language isEqualToString:subtitleTrack]) { + [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; + break; + } + } + + self.isVideoCurrentlyPlaying = YES; + + return true; +} + +- (BOOL)isVideoPlaying { + if (self.avPlayer.error) { + printf("Error during playback\n"); + } + return (self.avPlayer.rate > 0 && !self.avPlayer.error); +} + +- (void)pauseVideo { + self.videoCurrentTime = self.avPlayer.currentTime; + [self.avPlayer pause]; + self.isVideoCurrentlyPlaying = NO; +} + +- (void)unpauseVideo { + [self.avPlayer play]; + self.isVideoCurrentlyPlaying = YES; +} + +- (void)playerItemDidReachEnd:(NSNotification *)notification { + [self stopVideo]; +} + +- (void)finishPlayingVideo { + [self.avPlayer pause]; + [self.avPlayerLayer removeFromSuperlayer]; + self.avPlayerLayer = nil; + + if (self.avPlayerItem) { + [self.avPlayerItem removeObserver:self forKeyPath:@"status"]; + self.avPlayerItem = nil; + } + + if (self.avPlayer) { + [self.avPlayer removeObserver:self forKeyPath:@"status"]; + self.avPlayer = nil; + } + + self.avAsset = nil; + + self.isVideoCurrentlyPlaying = NO; +} + +- (void)stopVideo { + [self finishPlayingVideo]; + + [self removeFromSuperview]; +} + +@end diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp deleted file mode 100644 index 3ef521a61a..0000000000 --- a/platform/iphone/os_iphone.cpp +++ /dev/null @@ -1,668 +0,0 @@ -/*************************************************************************/ -/* os_iphone.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. */ -/*************************************************************************/ - -#ifdef IPHONE_ENABLED - -#include "os_iphone.h" - -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif - -#if defined(VULKAN_ENABLED) -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" -// #import <QuartzCore/CAMetalLayer.h> -#include <vulkan/vulkan_metal.h> -#endif - -#include "servers/rendering/rendering_server_raster.h" -#include "servers/rendering/rendering_server_wrap_mt.h" - -#include "main/main.h" - -#include "core/io/file_access_pack.h" -#include "core/os/dir_access.h" -#include "core/os/file_access.h" -#include "core/project_settings.h" -#include "drivers/unix/syslog_logger.h" - -#include "semaphore_iphone.h" - -#include <dlfcn.h> - -int OSIPhone::get_video_driver_count() const { - - return 2; -}; - -const char *OSIPhone::get_video_driver_name(int p_driver) const { - - switch (p_driver) { - case VIDEO_DRIVER_GLES2: - return "GLES2"; - } - ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + "."); -}; - -OSIPhone *OSIPhone::get_singleton() { - - return (OSIPhone *)OS::get_singleton(); -}; - -extern int gl_view_base_fb; // from gl_view.mm - -void OSIPhone::set_data_dir(String p_dir) { - - DirAccess *da = DirAccess::open(p_dir); - - data_dir = da->get_current_dir(); - printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str()); - memdelete(da); -}; - -void OSIPhone::set_unique_id(String p_id) { - - unique_id = p_id; -}; - -String OSIPhone::get_unique_id() const { - - return unique_id; -}; - -void OSIPhone::initialize_core() { - - OS_Unix::initialize_core(); - - set_data_dir(data_dir); -}; - -int OSIPhone::get_current_video_driver() const { - return video_driver_index; -} - -Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - video_driver_index = p_video_driver; - -#if defined(OPENGL_ENABLED) - bool gl_initialization_error = false; - - // FIXME: Add Vulkan support via MoltenVK. Add fallback code back? - - 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.", - "Unable to initialize video driver"); - return ERR_UNAVAILABLE; - } -#endif - -#if defined(VULKAN_ENABLED) - RasterizerRD::make_current(); -#endif - - rendering_server = memnew(RenderingServerRaster); - // FIXME: Reimplement threaded rendering - if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { - rendering_server = memnew(RenderingServerWrapMT(rendering_server, false)); - } - rendering_server->init(); - //rendering_server->cursor_set_visible(false, 0); - -#if defined(OPENGL_ENABLED) - // reset this to what it should be, it will have been set to 0 after rendering_server->init() is called - RasterizerStorageGLES2::system_fbo = gl_view_base_fb; -#endif - - AudioDriverManager::initialize(p_audio_driver); - - input = memnew(InputDefault); - -#ifdef GAME_CENTER_ENABLED - game_center = memnew(GameCenter); - Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center)); - game_center->connect(); -#endif - -#ifdef STOREKIT_ENABLED - store_kit = memnew(InAppStore); - Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit)); -#endif - -#ifdef ICLOUD_ENABLED - icloud = memnew(ICloud); - Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud)); - //icloud->connect(); -#endif - ios = memnew(iOS); - Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); - - return OK; -}; - -MainLoop *OSIPhone::get_main_loop() const { - - return main_loop; -}; - -void OSIPhone::set_main_loop(MainLoop *p_main_loop) { - - main_loop = p_main_loop; - - if (main_loop) { - input->set_main_loop(p_main_loop); - main_loop->init(); - } -}; - -bool OSIPhone::iterate() { - - if (!main_loop) - return true; - - if (main_loop) { - for (int i = 0; i < event_count; i++) { - - input->parse_input_event(event_queue[i]); - }; - }; - event_count = 0; - - return Main::iteration(); -}; - -void OSIPhone::key(uint32_t p_key, bool p_pressed) { - - Ref<InputEventKey> ev; - ev.instance(); - ev->set_echo(false); - ev->set_pressed(p_pressed); - ev->set_keycode(p_key); - ev->set_physical_keycode(p_key); - ev->set_unicode(p_key); - queue_event(ev); -}; - -void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) { - - if (!GLOBAL_DEF("debug/disable_touch", false)) { - Ref<InputEventScreenTouch> ev; - ev.instance(); - - ev->set_index(p_idx); - ev->set_pressed(p_pressed); - ev->set_position(Vector2(p_x, p_y)); - queue_event(ev); - }; - - touch_list.pressed[p_idx] = p_pressed; -}; - -void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) { - - if (!GLOBAL_DEF("debug/disable_touch", false)) { - - Ref<InputEventScreenDrag> ev; - ev.instance(); - ev->set_index(p_idx); - ev->set_position(Vector2(p_x, p_y)); - ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); - queue_event(ev); - }; -}; - -void OSIPhone::queue_event(const Ref<InputEvent> &p_event) { - - ERR_FAIL_INDEX(event_count, MAX_EVENTS); - - event_queue[event_count++] = p_event; -}; - -void OSIPhone::touches_cancelled() { - - for (int i = 0; i < MAX_MOUSE_COUNT; i++) { - - if (touch_list.pressed[i]) { - - // send a mouse_up outside the screen - touch_press(i, -1, -1, false, false); - }; - }; -}; - -static const float ACCEL_RANGE = 1; - -void OSIPhone::update_gravity(float p_x, float p_y, float p_z) { - input->set_gravity(Vector3(p_x, p_y, p_z)); -}; - -void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) { - - // Found out the Z should not be negated! Pass as is! - input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE)); - - /* - if (p_x != last_accel.x) { - //printf("updating accel x %f\n", p_x); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_0; - ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE); - last_accel.x = p_x; - queue_event(ev); - }; - if (p_y != last_accel.y) { - //printf("updating accel y %f\n", p_y); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_1; - ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE); - last_accel.y = p_y; - queue_event(ev); - }; - if (p_z != last_accel.z) { - //printf("updating accel z %f\n", p_z); - InputEvent ev; - ev.type = InputEvent::JOYPAD_MOTION; - ev.device = 0; - ev.joy_motion.axis = JOY_ANALOG_2; - ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE); - last_accel.z = p_z; - queue_event(ev); - }; - */ -}; - -void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) { - input->set_magnetometer(Vector3(p_x, p_y, p_z)); -}; - -void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) { - input->set_gyroscope(Vector3(p_x, p_y, p_z)); -}; - -int OSIPhone::get_unused_joy_id() { - return input->get_unused_joy_id(); -}; - -void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) { - input->joy_connection_changed(p_idx, p_connected, p_name); -}; - -void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) { - input->joy_button(p_device, p_button, p_pressed); -}; - -void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) { - input->joy_axis(p_device, p_axis, p_value); -}; - -void OSIPhone::delete_main_loop() { - - if (main_loop) { - main_loop->finish(); - memdelete(main_loop); - }; - - main_loop = nullptr; -}; - -void OSIPhone::finalize() { - - delete_main_loop(); - - memdelete(input); - memdelete(ios); - -#ifdef GAME_CENTER_ENABLED - memdelete(game_center); -#endif - -#ifdef STOREKIT_ENABLED - memdelete(store_kit); -#endif - -#ifdef ICLOUD_ENABLED - memdelete(icloud); -#endif - - rendering_server->finish(); - memdelete(rendering_server); - // memdelete(rasterizer); - - // Free unhandled events before close - for (int i = 0; i < MAX_EVENTS; i++) { - event_queue[i].unref(); - }; - event_count = 0; -}; - -void OSIPhone::set_mouse_show(bool p_show){}; -void OSIPhone::set_mouse_grab(bool p_grab){}; - -bool OSIPhone::is_mouse_grab_enabled() const { - - return true; -}; - -Point2 OSIPhone::get_mouse_position() const { - - return Point2(); -}; - -int OSIPhone::get_mouse_button_state() const { - - return 0; -}; - -void OSIPhone::set_window_title(const String &p_title){}; - -void OSIPhone::alert(const String &p_alert, const String &p_title) { - - const CharString utf8_alert = p_alert.utf8(); - const CharString utf8_title = p_title.utf8(); - iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); -} - -Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { - if (p_path.length() == 0) { - p_library_handle = RTLD_SELF; - return OK; - } - return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); -} - -Error OSIPhone::close_dynamic_library(void *p_library_handle) { - if (p_library_handle == RTLD_SELF) { - return OK; - } - return OS_Unix::close_dynamic_library(p_library_handle); -} - -HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table; -void register_dynamic_symbol(char *name, void *address) { - OSIPhone::dynamic_symbol_lookup_table[String(name)] = address; -} - -Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { - if (p_library_handle == RTLD_SELF) { - void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name); - if (ptr) { - p_symbol_handle = *ptr; - return OK; - } - } - return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); -} - -void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) { - - video_mode = p_video_mode; -}; - -OS::VideoMode OSIPhone::get_video_mode(int p_screen) const { - - return video_mode; -}; - -void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { - - p_list->push_back(video_mode); -}; - -bool OSIPhone::can_draw() const { - - if (native_video_is_playing()) - return false; - return true; -}; - -int OSIPhone::set_base_framebuffer(int p_fb) { -#if defined(OPENGL_ENABLED) - // gl_view_base_fb has not been updated yet - RasterizerStorageGLES2::system_fbo = p_fb; -#endif - - return 0; -}; - -bool OSIPhone::has_virtual_keyboard() const { - return true; -}; - -extern void _show_keyboard(String p_existing); -extern void _hide_keyboard(); -extern Error _shell_open(String p_uri); -extern void _set_keep_screen_on(bool p_enabled); -extern void _vibrate(); - -void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length) { - _show_keyboard(p_existing_text); -}; - -void OSIPhone::hide_virtual_keyboard() { - _hide_keyboard(); -}; - -void OSIPhone::set_virtual_keyboard_height(int p_height) { - virtual_keyboard_height = p_height; -} - -int OSIPhone::get_virtual_keyboard_height() const { - return virtual_keyboard_height; -} - -Error OSIPhone::shell_open(String p_uri) { - return _shell_open(p_uri); -}; - -void OSIPhone::set_keep_screen_on(bool p_enabled) { - OS::set_keep_screen_on(p_enabled); - _set_keep_screen_on(p_enabled); -}; - -String OSIPhone::get_user_data_dir() const { - - return data_dir; -}; - -String OSIPhone::get_name() const { - - return "iOS"; -}; - -String OSIPhone::get_model_name() const { - - String model = ios->get_model(); - if (model != "") - return model; - - return OS_Unix::get_model_name(); -} - -Size2 OSIPhone::get_window_size() const { - - return Vector2(video_mode.width, video_mode.height); -} - -extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height); - -Rect2 OSIPhone::get_window_safe_area() const { - return _get_ios_window_safe_area(video_mode.width, video_mode.height); -} - -bool OSIPhone::has_touchscreen_ui_hint() const { - - return true; -} - -void OSIPhone::set_locale(String p_locale) { - locale_code = p_locale; -} - -String OSIPhone::get_locale() const { - return locale_code; -} - -extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); -extern bool _is_video_playing(); -extern void _pause_video(); -extern void _unpause_video(); -extern void _stop_video(); -extern void _focus_out_video(); - -Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) { - FileAccess *f = FileAccess::open(p_path, FileAccess::READ); - bool exists = f && f->is_open(); - - String tempFile = get_user_data_dir(); - if (!exists) - return FAILED; - - if (p_path.begins_with("res://")) { - if (PackedData::get_singleton()->has_path(p_path)) { - print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str()); - return ERR_INVALID_PARAMETER; - } else { - p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path()); - } - } else if (p_path.begins_with("user://")) - p_path = p_path.replace("user:/", get_user_data_dir()); - - memdelete(f); - - print("Playing video: %S\n", p_path.c_str()); - if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track)) - return OK; - return FAILED; -} - -bool OSIPhone::native_video_is_playing() const { - return _is_video_playing(); -} - -void OSIPhone::native_video_pause() { - if (native_video_is_playing()) - _pause_video(); -} - -void OSIPhone::native_video_unpause() { - _unpause_video(); -}; - -void OSIPhone::native_video_focus_out() { - _focus_out_video(); -}; - -void OSIPhone::native_video_stop() { - if (native_video_is_playing()) - _stop_video(); -} - -void OSIPhone::vibrate_handheld(int p_duration_ms) { - // iOS does not support duration for vibration - _vibrate(); -} - -bool OSIPhone::_check_internal_feature_support(const String &p_feature) { - - return p_feature == "mobile"; -} - -// Initialization order between compilation units is not guaranteed, -// so we use this as a hack to ensure certain code is called before -// everything else, but after all units are initialized. -typedef void (*init_callback)(); -static init_callback *ios_init_callbacks = nullptr; -static int ios_init_callbacks_count = 0; -static int ios_init_callbacks_capacity = 0; - -void add_ios_init_callback(init_callback cb) { - if (ios_init_callbacks_count == ios_init_callbacks_capacity) { - void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32); - if (new_ptr) { - ios_init_callbacks = (init_callback *)(new_ptr); - ios_init_callbacks_capacity += 32; - } - } - if (ios_init_callbacks_capacity > ios_init_callbacks_count) { - ios_init_callbacks[ios_init_callbacks_count] = cb; - ++ios_init_callbacks_count; - } -} - -OSIPhone::OSIPhone(int width, int height, String p_data_dir) { - for (int i = 0; i < ios_init_callbacks_count; ++i) { - ios_init_callbacks[i](); - } - free(ios_init_callbacks); - ios_init_callbacks = nullptr; - ios_init_callbacks_count = 0; - ios_init_callbacks_capacity = 0; - - main_loop = nullptr; - rendering_server = nullptr; - - VideoMode vm; - vm.fullscreen = true; - vm.width = width; - vm.height = height; - vm.resizable = false; - set_video_mode(vm); - event_count = 0; - virtual_keyboard_height = 0; - - // can't call set_data_dir from here, since it requires DirAccess - // which is initialized in initialize_core - data_dir = p_data_dir; - - Vector<Logger *> loggers; - loggers.push_back(memnew(SyslogLogger)); -#ifdef DEBUG_ENABLED - // it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode - loggers.push_back(memnew(StdLogger)); -#endif - _set_logger(memnew(CompositeLogger(loggers))); - - AudioDriverManager::add_driver(&audio_driver); -}; - -OSIPhone::~OSIPhone() { -} - -#endif diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 96cf041c37..04a0a478d5 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -33,182 +33,92 @@ #ifndef OS_IPHONE_H #define OS_IPHONE_H -#include "core/input/input_filter.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "drivers/unix/os_unix.h" -#include "game_center.h" -#include "icloud.h" -#include "in_app_store.h" #include "ios.h" +#include "joypad_iphone.h" #include "servers/audio_server.h" #include "servers/rendering/rasterizer.h" -#include "servers/rendering_server.h" #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_device_vulkan.h" #include "platform/iphone/vulkan_context_iphone.h" #endif -class OSIPhone : public OS_Unix { +extern void godot_ios_plugins_initialize(); +extern void godot_ios_plugins_deinitialize(); +class OSIPhone : public OS_Unix { private: - enum { - MAX_MOUSE_COUNT = 8, - MAX_EVENTS = 64, - }; - static HashMap<String, void *> dynamic_symbol_lookup_table; friend void register_dynamic_symbol(char *name, void *address); - RenderingServer *rendering_server; - AudioDriverCoreAudio audio_driver; -#ifdef GAME_CENTER_ENABLED - GameCenter *game_center; -#endif -#ifdef STOREKIT_ENABLED - InAppStore *store_kit; -#endif -#ifdef ICLOUD_ENABLED - ICloud *icloud; -#endif iOS *ios; - MainLoop *main_loop; - -#if defined(VULKAN_ENABLED) - VulkanContextIPhone *context_vulkan; - RenderingDeviceVulkan *rendering_device_vulkan; -#endif - VideoMode video_mode; - - virtual int get_video_driver_count() const; - virtual const char *get_video_driver_name(int p_driver) const; - - virtual int get_current_video_driver() const; - - virtual void initialize_core(); - virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); - - virtual void set_main_loop(MainLoop *p_main_loop); - virtual MainLoop *get_main_loop() const; - - virtual void delete_main_loop(); + JoypadIPhone *joypad_iphone; - virtual void finalize(); - - struct MouseList { + MainLoop *main_loop; - bool pressed[MAX_MOUSE_COUNT]; - MouseList() { - for (int i = 0; i < MAX_MOUSE_COUNT; i++) - pressed[i] = false; - }; - }; + virtual void initialize_core() override; + virtual void initialize() override; - MouseList touch_list; + virtual void initialize_joypads() override { + } - Vector3 last_accel; + virtual void set_main_loop(MainLoop *p_main_loop) override; + virtual MainLoop *get_main_loop() const override; - Ref<InputEvent> event_queue[MAX_EVENTS]; - int event_count; - void queue_event(const Ref<InputEvent> &p_event); + virtual void delete_main_loop() override; - String data_dir; - String unique_id; - String locale_code; + virtual void finalize() override; - InputDefault *input; + String user_data_dir; - int virtual_keyboard_height; + bool is_focused = false; - int video_driver_index; + void deinitialize_modules(); public: - bool iterate(); - - uint8_t get_orientations() const; - - void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); - void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y); - void touches_cancelled(); - void key(uint32_t p_key, bool p_pressed); - void set_virtual_keyboard_height(int p_height); - - int set_base_framebuffer(int p_fb); - - void update_gravity(float p_x, float p_y, float p_z); - void update_accelerometer(float p_x, float p_y, float p_z); - void update_magnetometer(float p_x, float p_y, float p_z); - void update_gyroscope(float p_x, float p_y, float p_z); - - int get_unused_joy_id(); - void joy_connection_changed(int p_idx, bool p_connected, String p_name); - void joy_button(int p_device, int p_button, bool p_pressed); - void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value); - static OSIPhone *get_singleton(); - virtual void set_mouse_show(bool p_show); - virtual void set_mouse_grab(bool p_grab); - virtual bool is_mouse_grab_enabled() const; - virtual Point2 get_mouse_position() const; - virtual int get_mouse_button_state() const; - virtual void set_window_title(const String &p_title); - - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); - virtual Error close_dynamic_library(void *p_library_handle); - virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false); - - virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); - virtual VideoMode get_video_mode(int p_screen = 0) const; - virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const; + OSIPhone(String p_data_dir); + ~OSIPhone(); - virtual void set_keep_screen_on(bool p_enabled); + void initialize_modules(); - virtual bool can_draw() const; + bool iterate(); - virtual bool has_virtual_keyboard() const; - virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1); - virtual void hide_virtual_keyboard(); - virtual int get_virtual_keyboard_height() const; + void start(); - virtual Size2 get_window_size() const; - virtual Rect2 get_window_safe_area() const; + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; + virtual Error close_dynamic_library(void *p_library_handle) override; + virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override; - virtual bool has_touchscreen_ui_hint() const; + virtual void alert(const String &p_alert, + const String &p_title = "ALERT!") override; - void set_data_dir(String p_dir); + virtual String get_name() const override; + virtual String get_model_name() const override; - virtual String get_name() const; - virtual String get_model_name() const; + virtual Error shell_open(String p_uri) override; - Error shell_open(String p_uri); + void set_user_data_dir(String p_dir); + virtual String get_user_data_dir() const override; - String get_user_data_dir() const; + virtual String get_locale() const override; - void set_locale(String p_locale); - String get_locale() const; + virtual String get_unique_id() const override; - void set_unique_id(String p_id); - String get_unique_id() const; + virtual void vibrate_handheld(int p_duration_ms = 500) override; - virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); - virtual bool native_video_is_playing() const; - virtual void native_video_pause(); - virtual void native_video_unpause(); - virtual void native_video_focus_out(); - virtual void native_video_stop(); - virtual void vibrate_handheld(int p_duration_ms = 500); + virtual bool _check_internal_feature_support(const String &p_feature) override; - virtual bool _check_internal_feature_support(const String &p_feature); - OSIPhone(int width, int height, String p_data_dir); - ~OSIPhone(); + void on_focus_out(); + void on_focus_in(); }; #endif // OS_IPHONE_H -#endif +#endif // IPHONE_ENABLED diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm new file mode 100644 index 0000000000..b87e6f37a0 --- /dev/null +++ b/platform/iphone/os_iphone.mm @@ -0,0 +1,330 @@ +/*************************************************************************/ +/* os_iphone.mm */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifdef IPHONE_ENABLED + +#include "os_iphone.h" +#import "app_delegate.h" +#include "core/config/project_settings.h" +#include "core/io/file_access_pack.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "display_server_iphone.h" +#include "drivers/unix/syslog_logger.h" +#import "godot_view.h" +#include "main/main.h" +#import "view_controller.h" + +#import <AudioToolbox/AudioServices.h> +#import <UIKit/UIKit.h> +#import <dlfcn.h> + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#import <QuartzCore/CAMetalLayer.h> +#include <vulkan/vulkan_metal.h> +#endif + +// Initialization order between compilation units is not guaranteed, +// so we use this as a hack to ensure certain code is called before +// everything else, but after all units are initialized. +typedef void (*init_callback)(); +static init_callback *ios_init_callbacks = nullptr; +static int ios_init_callbacks_count = 0; +static int ios_init_callbacks_capacity = 0; +HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table; + +void add_ios_init_callback(init_callback cb) { + if (ios_init_callbacks_count == ios_init_callbacks_capacity) { + void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32); + if (new_ptr) { + ios_init_callbacks = (init_callback *)(new_ptr); + ios_init_callbacks_capacity += 32; + } + } + if (ios_init_callbacks_capacity > ios_init_callbacks_count) { + ios_init_callbacks[ios_init_callbacks_count] = cb; + ++ios_init_callbacks_count; + } +} + +void register_dynamic_symbol(char *name, void *address) { + OSIPhone::dynamic_symbol_lookup_table[String(name)] = address; +} + +OSIPhone *OSIPhone::get_singleton() { + return (OSIPhone *)OS::get_singleton(); +} + +OSIPhone::OSIPhone(String p_data_dir) { + for (int i = 0; i < ios_init_callbacks_count; ++i) { + ios_init_callbacks[i](); + } + free(ios_init_callbacks); + ios_init_callbacks = nullptr; + ios_init_callbacks_count = 0; + ios_init_callbacks_capacity = 0; + + main_loop = nullptr; + + // can't call set_data_dir from here, since it requires DirAccess + // which is initialized in initialize_core + user_data_dir = p_data_dir; + + Vector<Logger *> loggers; + loggers.push_back(memnew(SyslogLogger)); +#ifdef DEBUG_ENABLED + // it seems iOS app's stdout/stderr is only obtainable if you launch it from + // Xcode + loggers.push_back(memnew(StdLogger)); +#endif + _set_logger(memnew(CompositeLogger(loggers))); + + AudioDriverManager::add_driver(&audio_driver); + + DisplayServerIPhone::register_iphone_driver(); +} + +OSIPhone::~OSIPhone() {} + +void OSIPhone::initialize_core() { + OS_Unix::initialize_core(); + + set_user_data_dir(user_data_dir); +} + +void OSIPhone::initialize() { + initialize_core(); +} + +void OSIPhone::initialize_modules() { + ios = memnew(iOS); + Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); + + joypad_iphone = memnew(JoypadIPhone); +} + +void OSIPhone::deinitialize_modules() { + if (joypad_iphone) { + memdelete(joypad_iphone); + } + + if (ios) { + memdelete(ios); + } + + godot_ios_plugins_deinitialize(); +} + +void OSIPhone::set_main_loop(MainLoop *p_main_loop) { + godot_ios_plugins_initialize(); + + main_loop = p_main_loop; + + if (main_loop) { + main_loop->init(); + } +} + +MainLoop *OSIPhone::get_main_loop() const { + return main_loop; +} + +void OSIPhone::delete_main_loop() { + if (main_loop) { + main_loop->finish(); + memdelete(main_loop); + }; + + main_loop = nullptr; +} + +bool OSIPhone::iterate() { + if (!main_loop) { + return true; + } + + if (DisplayServer::get_singleton()) { + DisplayServer::get_singleton()->process_events(); + } + + return Main::iteration(); +} + +void OSIPhone::start() { + Main::start(); + + if (joypad_iphone) { + joypad_iphone->start_processing(); + } +} + +void OSIPhone::finalize() { + deinitialize_modules(); + + // Already gets called + // delete_main_loop(); +} + +// MARK: Dynamic Libraries + +Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { + if (p_path.length() == 0) { + p_library_handle = RTLD_SELF; + return OK; + } + return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); +} + +Error OSIPhone::close_dynamic_library(void *p_library_handle) { + if (p_library_handle == RTLD_SELF) { + return OK; + } + return OS_Unix::close_dynamic_library(p_library_handle); +} + +Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { + if (p_library_handle == RTLD_SELF) { + void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name); + if (ptr) { + p_symbol_handle = *ptr; + return OK; + } + } + return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); +} + +void OSIPhone::alert(const String &p_alert, const String &p_title) { + const CharString utf8_alert = p_alert.utf8(); + const CharString utf8_title = p_title.utf8(); + iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + +String OSIPhone::get_name() const { + return "iOS"; +}; + +String OSIPhone::get_model_name() const { + String model = ios->get_model(); + if (model != "") + return model; + + return OS_Unix::get_model_name(); +} + +Error OSIPhone::shell_open(String p_uri) { + NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; + NSURL *url = [NSURL URLWithString:urlPath]; + + if (![[UIApplication sharedApplication] canOpenURL:url]) { + return ERR_CANT_OPEN; + } + + printf("opening url %s\n", p_uri.utf8().get_data()); + + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + + return OK; +}; + +void OSIPhone::set_user_data_dir(String p_dir) { + DirAccess *da = DirAccess::open(p_dir); + + user_data_dir = da->get_current_dir(); + printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data()); + memdelete(da); +} + +String OSIPhone::get_user_data_dir() const { + return user_data_dir; +} + +String OSIPhone::get_locale() const { + NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject; + + if (preferedLanguage) { + return String::utf8([preferedLanguage UTF8String]).replace("-", "_"); + } + + NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier]; + return String::utf8([localeIdentifier UTF8String]).replace("-", "_"); +} + +String OSIPhone::get_unique_id() const { + NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; + return String::utf8([uuid UTF8String]); +} + +void OSIPhone::vibrate_handheld(int p_duration_ms) { + // iOS does not support duration for vibration + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); +} + +bool OSIPhone::_check_internal_feature_support(const String &p_feature) { + return p_feature == "mobile"; +} + +void OSIPhone::on_focus_out() { + if (is_focused) { + is_focused = false; + + if (DisplayServerIPhone::get_singleton()) { + DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT); + } + + [AppDelegate.viewController.godotView stopRendering]; + + if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) { + DisplayServerIPhone::get_singleton()->native_video_pause(); + } + + audio_driver.stop(); + } +} + +void OSIPhone::on_focus_in() { + if (!is_focused) { + is_focused = true; + + if (DisplayServerIPhone::get_singleton()) { + DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN); + } + + [AppDelegate.viewController.godotView startRendering]; + + if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) { + DisplayServerIPhone::get_singleton()->native_video_unpause(); + } + + audio_driver.start(); + } +} + +#endif // IPHONE_ENABLED diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h index bc190ba956..ec39ad0ba4 100644 --- a/platform/iphone/platform_config.h +++ b/platform/iphone/platform_config.h @@ -30,8 +30,13 @@ #include <alloca.h> -#define GLES2_INCLUDE_H <ES2/gl.h> - #define PLATFORM_REFCOUNT #define PTHREAD_RENAME_SELF + +#define _weakify(var) __weak typeof(var) GDWeak_##var = var; +#define _strongify(var) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + __strong typeof(var) var = GDWeak_##var; \ + _Pragma("clang diagnostic pop") diff --git a/platform/iphone/plugin/godot_plugin_config.h b/platform/iphone/plugin/godot_plugin_config.h new file mode 100644 index 0000000000..5323f94989 --- /dev/null +++ b/platform/iphone/plugin/godot_plugin_config.h @@ -0,0 +1,265 @@ +/*************************************************************************/ +/* godot_plugin_config.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 GODOT_PLUGIN_CONFIG_H +#define GODOT_PLUGIN_CONFIG_H + +#include "core/error/error_list.h" +#include "core/io/config_file.h" +#include "core/string/ustring.h" + +static const char *PLUGIN_CONFIG_EXT = ".gdip"; + +static const char *CONFIG_SECTION = "config"; +static const char *CONFIG_NAME_KEY = "name"; +static const char *CONFIG_BINARY_KEY = "binary"; +static const char *CONFIG_INITIALIZE_KEY = "initialization"; +static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization"; + +static const char *DEPENDENCIES_SECTION = "dependencies"; +static const char *DEPENDENCIES_LINKED_KEY = "linked"; +static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded"; +static const char *DEPENDENCIES_SYSTEM_KEY = "system"; +static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities"; +static const char *DEPENDENCIES_FILES_KEY = "files"; + +static const char *PLIST_SECTION = "plist"; + +/* + The `config` section and fields are required and defined as follow: +- **name**: name of the plugin +- **binary**: path to static `.a` library + +The `dependencies` and fields are optional. +- **linked**: dependencies that should only be linked. +- **embedded**: dependencies that should be linked and embedded into application. +- **system**: system dependencies that should be linked. +- **capabilities**: capabilities that would be used for `UIRequiredDeviceCapabilities` options in Info.plist file. +- **files**: files that would be copied into application + +The `plist` section are optional. +- **key**: key and value that would be added in Info.plist file. + */ + +struct PluginConfig { + // Set to true when the config file is properly loaded. + bool valid_config = false; + bool supports_targets = false; + // Unix timestamp of last change to this plugin. + uint64_t last_updated = 0; + + // Required config section + String name; + String binary; + String initialization_method; + String deinitialization_method; + + // Optional dependencies section + Vector<String> linked_dependencies; + Vector<String> embedded_dependencies; + Vector<String> system_dependencies; + + Vector<String> files_to_copy; + Vector<String> capabilities; + + // Optional plist section + // Supports only string types for now + HashMap<String, String> plist; +}; + +static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { + String absolute_path; + + if (dependency_path.empty()) { + return absolute_path; + } + + if (dependency_path.is_abs_path()) { + return dependency_path; + } + + String res_path = ProjectSettings::get_singleton()->globalize_path("res://"); + absolute_path = plugin_config_dir.plus_file(dependency_path); + + return absolute_path.replace(res_path, "res://"); +} + +static inline String resolve_system_dependency_path(String dependency_path) { + String absolute_path; + + if (dependency_path.empty()) { + return absolute_path; + } + + if (dependency_path.is_abs_path()) { + return dependency_path; + } + + String system_path = "/System/Library/Frameworks"; + + return system_path.plus_file(dependency_path); +} + +static inline Vector<String> resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths) { + Vector<String> paths; + + for (int i = 0; i < p_paths.size(); i++) { + String path = resolve_local_dependency_path(plugin_config_dir, p_paths[i]); + + if (path.empty()) { + continue; + } + + paths.push_back(path); + } + + return paths; +} + +static inline Vector<String> resolve_system_dependencies(Vector<String> p_paths) { + Vector<String> paths; + + for (int i = 0; i < p_paths.size(); i++) { + String path = resolve_system_dependency_path(p_paths[i]); + + if (path.empty()) { + continue; + } + + paths.push_back(path); + } + + return paths; +} + +static inline bool validate_plugin(PluginConfig &plugin_config) { + bool valid_name = !plugin_config.name.empty(); + bool valid_binary_name = !plugin_config.binary.empty(); + bool valid_initialize = !plugin_config.initialization_method.empty(); + bool valid_deinitialize = !plugin_config.deinitialization_method.empty(); + + bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize; + + if (fields_value && FileAccess::exists(plugin_config.binary)) { + plugin_config.valid_config = true; + plugin_config.supports_targets = false; + } else if (fields_value) { + String file_path = plugin_config.binary.get_base_dir(); + String file_name = plugin_config.binary.get_basename().get_file(); + String release_file_name = file_path.plus_file(file_name + ".release.a"); + String debug_file_name = file_path.plus_file(file_name + ".debug.a"); + + if (FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) { + plugin_config.valid_config = true; + plugin_config.supports_targets = true; + } + } + + return plugin_config.valid_config; +} + +static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_config, const String &config_path) { + uint64_t last_updated = FileAccess::get_modified_time(config_path); + + if (!plugin_config.supports_targets) { + last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary)); + } else { + String file_path = plugin_config.binary.get_base_dir(); + String file_name = plugin_config.binary.get_basename().get_file(); + String release_file_name = file_path.plus_file(file_name + ".release.a"); + String debug_file_name = file_path.plus_file(file_name + ".debug.a"); + + last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name)); + last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name)); + } + + return last_updated; +} + +static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) { + PluginConfig plugin_config = {}; + + if (!config_file.is_valid()) { + return plugin_config; + } + + Error err = config_file->load(path); + + if (err != OK) { + return plugin_config; + } + + String config_base_dir = path.get_base_dir(); + + plugin_config.name = config_file->get_value(CONFIG_SECTION, CONFIG_NAME_KEY, String()); + plugin_config.initialization_method = config_file->get_value(CONFIG_SECTION, CONFIG_INITIALIZE_KEY, String()); + plugin_config.deinitialization_method = config_file->get_value(CONFIG_SECTION, CONFIG_DEINITIALIZE_KEY, String()); + + String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String()); + plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path); + + if (config_file->has_section(DEPENDENCIES_SECTION)) { + Vector<String> linked_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LINKED_KEY, Vector<String>()); + Vector<String> embedded_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_EMBEDDED_KEY, Vector<String>()); + Vector<String> system_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_SYSTEM_KEY, Vector<String>()); + Vector<String> files = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_FILES_KEY, Vector<String>()); + + plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies); + plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies); + plugin_config.system_dependencies = resolve_system_dependencies(system_dependencies); + + plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files); + + plugin_config.capabilities = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_CAPABILITIES_KEY, Vector<String>()); + } + + if (config_file->has_section(PLIST_SECTION)) { + List<String> keys; + config_file->get_section_keys(PLIST_SECTION, &keys); + + for (int i = 0; i < keys.size(); i++) { + String value = config_file->get_value(PLIST_SECTION, keys[i], String()); + + if (value.empty()) { + continue; + } + + plugin_config.plist[keys[i]] = value; + } + } + + if (validate_plugin(plugin_config)) { + plugin_config.last_updated = get_plugin_modification_time(plugin_config, path); + } + + return plugin_config; +} + +#endif // GODOT_PLUGIN_CONFIG_H diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index f6bbe11d97..ff76359842 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -28,23 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#import <GameKit/GameKit.h> #import <UIKit/UIKit.h> -@interface ViewController : UIViewController <GKGameCenterControllerDelegate> { -}; +@class GodotView; +@class GodotNativeVideoView; +@class GodotKeyboardInputView; -- (BOOL)shouldAutorotateToInterfaceOrientation: - (UIInterfaceOrientation)p_orientation; +@interface ViewController : UIViewController -- (void)didReceiveMemoryWarning; +@property(nonatomic, readonly, strong) GodotView *godotView; +@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView; +@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView; -- (void)viewDidLoad; +// MARK: Native Video Player -- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures; - -- (BOOL)prefersStatusBarHidden; - -- (BOOL)prefersHomeIndicatorAutoHidden; +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; @end diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 465e38e45e..d3969e6b02 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -29,100 +29,150 @@ /*************************************************************************/ #import "view_controller.h" - +#include "core/config/project_settings.h" +#include "display_server_iphone.h" +#import "godot_view.h" +#import "godot_view_renderer.h" +#import "keyboard_input_view.h" +#import "native_video_view.h" #include "os_iphone.h" -#include "core/project_settings.h" +#import <AVFoundation/AVFoundation.h> +#import <GameController/GameController.h> -extern "C" { +@interface ViewController () -int add_path(int, char **); -int add_cmdline(int, char **); +@property(strong, nonatomic) GodotViewRenderer *renderer; +@property(strong, nonatomic) GodotNativeVideoView *videoView; +@property(strong, nonatomic) GodotKeyboardInputView *keyboardView; -int add_path(int p_argc, char **p_args) { +@end - NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; - if (!str) - return p_argc; +@implementation ViewController - p_args[p_argc++] = "--path"; - [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo - p_args[p_argc++] = (char *)[str cString]; - p_args[p_argc] = NULL; +- (GodotView *)godotView { + return (GodotView *)self.view; +} - return p_argc; -}; +- (void)loadView { + GodotView *view = [[GodotView alloc] init]; + GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init]; -int add_cmdline(int p_argc, char **p_args) { + self.renderer = renderer; + self.view = view; - NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; - if (!arr) - return p_argc; + view.renderer = self.renderer; +} - for (int i = 0; i < [arr count]; i++) { +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - NSString *str = [arr objectAtIndex:i]; - if (!str) - continue; - [str retain]; // @todo delete these at some point - p_args[p_argc++] = (char *)[str cString]; - }; + if (self) { + [self godot_commonInit]; + } - p_args[p_argc] = NULL; + return self; +} - return p_argc; -}; -}; // extern "C" +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; -@interface ViewController () + if (self) { + [self godot_commonInit]; + } -@end + return self; +} -@implementation ViewController +- (void)godot_commonInit { + // Initialize view controller values. +} - (void)didReceiveMemoryWarning { - + [super didReceiveMemoryWarning]; printf("*********** did receive memory warning!\n"); -}; +} - (void)viewDidLoad { [super viewDidLoad]; + [self observeKeyboard]; + if (@available(iOS 11.0, *)) { [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; } } +- (void)observeKeyboard { + printf("******** setting up keyboard input view\n"); + self.keyboardView = [GodotKeyboardInputView new]; + [self.view addSubview:self.keyboardView]; + + printf("******** adding observer for keyboard show/hide\n"); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(keyboardOnScreen:) + name:UIKeyboardDidShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(keyboardHidden:) + name:UIKeyboardDidHideNotification + object:nil]; +} + +- (void)dealloc { + [self.videoView stopVideo]; + + self.videoView = nil; + + self.keyboardView = nil; + + self.renderer = nil; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +// MARK: Orientation + - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { return UIRectEdgeAll; } - (BOOL)shouldAutorotate { - switch (OS::get_singleton()->get_screen_orientation()) { - case OS::SCREEN_SENSOR: - case OS::SCREEN_SENSOR_LANDSCAPE: - case OS::SCREEN_SENSOR_PORTRAIT: + if (!DisplayServerIPhone::get_singleton()) { + return NO; + } + + switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { + case DisplayServer::SCREEN_SENSOR: + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + case DisplayServer::SCREEN_SENSOR_PORTRAIT: return YES; default: return NO; } -}; +} - (UIInterfaceOrientationMask)supportedInterfaceOrientations { - switch (OS::get_singleton()->get_screen_orientation()) { - case OS::SCREEN_PORTRAIT: + if (!DisplayServerIPhone::get_singleton()) { + return UIInterfaceOrientationMaskAll; + } + + switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { + case DisplayServer::SCREEN_PORTRAIT: return UIInterfaceOrientationMaskPortrait; - case OS::SCREEN_REVERSE_LANDSCAPE: + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: return UIInterfaceOrientationMaskLandscapeRight; - case OS::SCREEN_REVERSE_PORTRAIT: + case DisplayServer::SCREEN_REVERSE_PORTRAIT: return UIInterfaceOrientationMaskPortraitUpsideDown; - case OS::SCREEN_SENSOR_LANDSCAPE: + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: return UIInterfaceOrientationMaskLandscape; - case OS::SCREEN_SENSOR_PORTRAIT: + case DisplayServer::SCREEN_SENSOR_PORTRAIT: return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; - case OS::SCREEN_SENSOR: + case DisplayServer::SCREEN_SENSOR: return UIInterfaceOrientationMaskAll; - case OS::SCREEN_LANDSCAPE: + case DisplayServer::SCREEN_LANDSCAPE: return UIInterfaceOrientationMaskLandscapeLeft; } }; @@ -139,12 +189,39 @@ int add_cmdline(int p_argc, char **p_args) { } } -#ifdef GAME_CENTER_ENABLED -- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController { - //[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone - GameCenter::get_singleton()->game_center_closed(); - [gameCenterViewController dismissViewControllerAnimated:YES completion:nil]; +// MARK: Keyboard + +- (void)keyboardOnScreen:(NSNotification *)notification { + NSDictionary *info = notification.userInfo; + NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; + + CGRect rawFrame = [value CGRectValue]; + CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil]; + + if (DisplayServerIPhone::get_singleton()) { + DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height); + } +} + +- (void)keyboardHidden:(NSNotification *)notification { + if (DisplayServerIPhone::get_singleton()) { + DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0); + } +} + +// MARK: Native Video Player + +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { + // If we are showing some video already, reuse existing view for new video. + if (self.videoView) { + return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack]; + } else { + // Create autoresizing view for video playback. + GodotNativeVideoView *videoView = [[GodotNativeVideoView alloc] initWithFrame:self.view.bounds]; + videoView.autoresizingMask = UIViewAutoresizingFlexibleWidth & UIViewAutoresizingFlexibleHeight; + [self.view addSubview:videoView]; + return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack]; + } } -#endif @end diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h index 625e41f4b9..5c3d5fe33e 100644 --- a/platform/iphone/vulkan_context_iphone.h +++ b/platform/iphone/vulkan_context_iphone.h @@ -32,14 +32,14 @@ #define VULKAN_CONTEXT_IPHONE_H #include "drivers/vulkan/vulkan_context.h" -// #import <UIKit/UIKit.h> -class VulkanContextIPhone : public VulkanContext { +#import <UIKit/UIKit.h> +class VulkanContextIPhone : public VulkanContext { virtual const char *_get_platform_surface_extension() const; public: - int window_create(void *p_window, int p_width, int p_height); + Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height); VulkanContextIPhone(); ~VulkanContextIPhone(); diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm index 701ac0d9bb..d62e826957 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/iphone/vulkan_context_iphone.mm @@ -35,22 +35,21 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const { return VK_MVK_IOS_SURFACE_EXTENSION_NAME; } -int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) { - +Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height) { VkIOSSurfaceCreateInfoMVK createInfo; - createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; createInfo.pNext = NULL; createInfo.flags = 0; - createInfo.pView = p_window; + createInfo.pView = (__bridge const void *)p_metal_layer; VkSurfaceKHR surface; - VkResult err = vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); - ERR_FAIL_COND_V(err, -1); - return _window_create(surface, p_width, p_height); -} + VkResult err = + vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); + ERR_FAIL_COND_V(err, ERR_CANT_CREATE); -VulkanContextIPhone::VulkanContextIPhone() { + return _window_create(p_window_id, surface, p_width, p_height); } -VulkanContextIPhone::~VulkanContextIPhone() { -} +VulkanContextIPhone::VulkanContextIPhone() {} + +VulkanContextIPhone::~VulkanContextIPhone() {} diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 7239648937..a0e6fa0e18 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -4,10 +4,12 @@ Import("env") javascript_files = [ "audio_driver_javascript.cpp", + "display_server_javascript.cpp", "http_client_javascript.cpp", "javascript_eval.cpp", "javascript_main.cpp", "os_javascript.cpp", + "api/javascript_tools_editor_plugin.cpp", ] build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] @@ -16,23 +18,25 @@ if env["threads_enabled"]: build = env.add_program(build_targets, javascript_files) -js_libraries = [ - "http_request.js", -] -for lib in js_libraries: - env.Append(LINKFLAGS=["--js-library", env.File(lib).path]) -env.Depends(build, js_libraries) +env.AddJSLibraries( + [ + "native/http_request.js", + "native/library_godot_audio.js", + "native/library_godot_display.js", + "native/library_godot_os.js", + ] +) -js_modules = [ - "id_handler.js", -] -for module in js_modules: - env.Append(LINKFLAGS=["--pre-js", env.File(module).path]) -env.Depends(build, js_modules) +if env["tools"]: + env.AddJSLibraries(["native/library_godot_editor_tools.js"]) +if env["javascript_eval"]: + env.AddJSLibraries(["native/library_godot_eval.js"]) +for lib in env["JS_LIBS"]: + env.Append(LINKFLAGS=["--js-library", lib]) +env.Depends(build, env["JS_LIBS"]) engine = [ "engine/preloader.js", - "engine/loader.js", "engine/utils.js", "engine/engine.js", ] @@ -47,11 +51,18 @@ wrap_list = [ 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") -out_files = [zip_dir.File("godot.js"), zip_dir.File("godot.wasm"), zip_dir.File("godot.html")] -in_files = [js_wrapped, build[1], "#misc/dist/html/full-size.html"] +binary_name = "godot.tools" if env["tools"] else "godot" +out_files = [ + zip_dir.File(binary_name + ".js"), + zip_dir.File(binary_name + ".wasm"), + zip_dir.File(binary_name + ".html"), + zip_dir.File(binary_name + ".audio.worklet.js"), +] +html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html" +in_files = [js_wrapped, build[1], html_file, "#platform/javascript/native/audio.worklet.js"] if env["threads_enabled"]: in_files.append(build[2]) - out_files.append(zip_dir.File("godot.worker.js")) + out_files.append(zip_dir.File(binary_name + ".worker.js")) zip_files = env.InstallAs(out_files, in_files) env.Zip( @@ -59,5 +70,5 @@ env.Zip( zip_files, ZIPROOT=zip_dir, ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}", - ZIPCOMSTR="Archving $SOURCES as $TARGET", + ZIPCOMSTR="Archiving $SOURCES as $TARGET", ) diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 45cb82b351..6fd6c0ddf1 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -29,32 +29,30 @@ /*************************************************************************/ #include "api.h" -#include "core/engine.h" +#include "core/config/engine.h" #include "javascript_eval.h" +#include "javascript_tools_editor_plugin.h" static JavaScript *javascript_eval; void register_javascript_api() { - + JavaScriptToolsEditorPlugin::initialize(); ClassDB::register_virtual_class<JavaScript>(); javascript_eval = memnew(JavaScript); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval)); } void unregister_javascript_api() { - memdelete(javascript_eval); } JavaScript *JavaScript::singleton = nullptr; JavaScript *JavaScript::get_singleton() { - return singleton; } JavaScript::JavaScript() { - ERR_FAIL_COND_MSG(singleton != nullptr, "JavaScript singleton already exist."); singleton = this; } @@ -62,13 +60,11 @@ JavaScript::JavaScript() { JavaScript::~JavaScript() {} void JavaScript::_bind_methods() { - ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScript::eval, DEFVAL(false)); } #if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED) Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { - return Variant(); } #endif diff --git a/platform/javascript/api/javascript_eval.h b/platform/javascript/api/javascript_eval.h index 29229de8e3..389983077e 100644 --- a/platform/javascript/api/javascript_eval.h +++ b/platform/javascript/api/javascript_eval.h @@ -31,7 +31,7 @@ #ifndef JAVASCRIPT_EVAL_H #define JAVASCRIPT_EVAL_H -#include "core/object.h" +#include "core/object/class_db.h" class JavaScript : public Object { private: diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp new file mode 100644 index 0000000000..8d781703ed --- /dev/null +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -0,0 +1,140 @@ +/*************************************************************************/ +/* javascript_tools_editor_plugin.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. */ +/*************************************************************************/ + +#if defined(TOOLS_ENABLED) && defined(JAVASCRIPT_ENABLED) +#include "javascript_tools_editor_plugin.h" + +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "editor/editor_node.h" + +#include <emscripten/emscripten.h> + +// JavaScript functions defined in library_godot_editor_tools.js +extern "C" { +extern void godot_js_editor_download_file(const char *p_path, const char *p_name, const char *p_mime); +} + +static void _javascript_editor_init_callback() { + EditorNode::get_singleton()->add_editor_plugin(memnew(JavaScriptToolsEditorPlugin(EditorNode::get_singleton()))); +} + +void JavaScriptToolsEditorPlugin::initialize() { + EditorNode::add_init_callback(_javascript_editor_init_callback); +} + +JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) { + Variant v; + add_tool_menu_item("Download Project Source", this, "_download_zip", v); +} + +void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { + if (!Engine::get_singleton() || !Engine::get_singleton()->is_editor_hint()) { + WARN_PRINT("Project download is only available in Editor mode"); + return; + } + String resource_path = ProjectSettings::get_singleton()->get_resource_path(); + + FileAccess *src_f; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, NULL, &io); + String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; + _zip_recursive(resource_path, base_path, zip); + zipClose(zip, NULL); + godot_js_editor_download_file("/tmp/project.zip", "project.zip", "application/zip"); +} + +void JavaScriptToolsEditorPlugin::_bind_methods() { + ClassDB::bind_method("_download_zip", &JavaScriptToolsEditorPlugin::_download_zip); +} + +void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + if (!f) { + WARN_PRINT("Unable to open file for zipping: " + p_path); + return; + } + Vector<uint8_t> data; + int len = f->get_len(); + data.resize(len); + f->get_buffer(data.ptrw(), len); + f->close(); + memdelete(f); + + String path = p_path.replace_first(p_base_path, ""); + zipOpenNewFileInZip(p_zip, + path.utf8().get_data(), + NULL, + NULL, + 0, + NULL, + 0, + NULL, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + zipWriteInFileInZip(p_zip, data.ptr(), data.size()); + zipCloseFileInZip(p_zip); +} + +void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) { + DirAccess *dir = DirAccess::open(p_path); + if (!dir) { + WARN_PRINT("Unable to open dir for zipping: " + p_path); + return; + } + dir->list_dir_begin(); + String cur = dir->get_next(); + while (!cur.empty()) { + String cs = p_path.plus_file(cur); + if (cur == "." || cur == ".." || cur == ".import") { + // Skip + } else if (dir->current_is_dir()) { + String path = cs.replace_first(p_base_path, "") + "/"; + zipOpenNewFileInZip(p_zip, + path.utf8().get_data(), + NULL, + NULL, + 0, + NULL, + 0, + NULL, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + zipCloseFileInZip(p_zip); + _zip_recursive(cs, p_base_path, p_zip); + } else { + _zip_file(cs, p_base_path, p_zip); + } + cur = dir->get_next(); + } +} +#endif diff --git a/platform/iphone/in_app_store.h b/platform/javascript/api/javascript_tools_editor_plugin.h index 493877a5a7..cc09fa4cd3 100644 --- a/platform/iphone/in_app_store.h +++ b/platform/javascript/api/javascript_tools_editor_plugin.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* in_app_store.h */ +/* javascript_tools_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,41 +28,35 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef STOREKIT_ENABLED +#ifndef JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H +#define JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H -#ifndef IN_APP_STORE_H -#define IN_APP_STORE_H +#if defined(TOOLS_ENABLED) && defined(JAVASCRIPT_ENABLED) +#include "core/io/zip_io.h" +#include "editor/editor_plugin.h" -#include "core/object.h" +class JavaScriptToolsEditorPlugin : public EditorPlugin { + GDCLASS(JavaScriptToolsEditorPlugin, EditorPlugin); -class InAppStore : public Object { +private: + void _zip_file(String p_path, String p_base_path, zipFile p_zip); + void _zip_recursive(String p_path, String p_base_path, zipFile p_zip); - GDCLASS(InAppStore, Object); - - static InAppStore *instance; +protected: static void _bind_methods(); - List<Variant> pending_events; + void _download_zip(Variant p_v); public: - Error request_product_info(Variant p_params); - Error restore_purchases(); - Error purchase(Variant p_params); - - int get_pending_event_count(); - Variant pop_pending_event(); - void finish_transaction(String product_id); - void set_auto_finish_transaction(bool b); - - void _post_event(Variant p_event); - void _record_purchase(String product_id); - - static InAppStore *get_singleton(); + static void initialize(); - InAppStore(); - ~InAppStore(); + JavaScriptToolsEditorPlugin(EditorNode *p_editor); +}; +#else +class JavaScriptToolsEditorPlugin { +public: + static void initialize() {} }; - #endif -#endif +#endif // JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index 8f857478e5..dd982bc3a8 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -30,242 +30,256 @@ #include "audio_driver_javascript.h" +#include "core/config/project_settings.h" + #include <emscripten.h> AudioDriverJavaScript *AudioDriverJavaScript::singleton = nullptr; -const char *AudioDriverJavaScript::get_name() const { +bool AudioDriverJavaScript::is_available() { + return godot_audio_is_available() != 0; +} +const char *AudioDriverJavaScript::get_name() const { return "JavaScript"; } -extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_js_mix() { - - AudioDriverJavaScript::singleton->mix_to_js(); +void AudioDriverJavaScript::_state_change_callback(int p_state) { + singleton->state = p_state; } -extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_process_capture(float sample) { - - AudioDriverJavaScript::singleton->process_capture(sample); +void AudioDriverJavaScript::_latency_update_callback(float p_latency) { + singleton->output_latency = p_latency; } -void AudioDriverJavaScript::mix_to_js() { +void AudioDriverJavaScript::_audio_driver_process(int p_from, int p_samples) { + int32_t *stream_buffer = reinterpret_cast<int32_t *>(output_rb); + const int max_samples = memarr_len(output_rb); - int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode()); - int sample_count = memarr_len(internal_buffer) / channel_count; - int32_t *stream_buffer = reinterpret_cast<int32_t *>(internal_buffer); - audio_server_process(sample_count, stream_buffer); - for (int i = 0; i < sample_count * channel_count; i++) { - internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.f; + int write_pos = p_from; + int to_write = p_samples; + if (to_write == 0) { + to_write = max_samples; + } + // High part + if (write_pos + to_write > max_samples) { + const int samples_high = max_samples - write_pos; + audio_server_process(samples_high / channel_count, &stream_buffer[write_pos]); + for (int i = write_pos; i < max_samples; i++) { + output_rb[i] = float(stream_buffer[i] >> 16) / 32768.f; + } + to_write -= samples_high; + write_pos = 0; + } + // Leftover + audio_server_process(to_write / channel_count, &stream_buffer[write_pos]); + for (int i = write_pos; i < write_pos + to_write; i++) { + output_rb[i] = float(stream_buffer[i] >> 16) / 32768.f; } } -void AudioDriverJavaScript::process_capture(float sample) { +void AudioDriverJavaScript::_audio_driver_capture(int p_from, int p_samples) { + if (get_input_buffer().size() == 0) { + return; // Input capture stopped. + } + const int max_samples = memarr_len(input_rb); - int32_t sample32 = int32_t(sample * 32768.f) * (1U << 16); - input_buffer_write(sample32); + int read_pos = p_from; + int to_read = p_samples; + if (to_read == 0) { + to_read = max_samples; + } + // High part + if (read_pos + to_read > max_samples) { + const int samples_high = max_samples - read_pos; + for (int i = read_pos; i < max_samples; i++) { + input_buffer_write(int32_t(input_rb[i] * 32768.f) * (1U << 16)); + } + to_read -= samples_high; + read_pos = 0; + } + // Leftover + for (int i = read_pos; i < read_pos + to_read; i++) { + input_buffer_write(int32_t(input_rb[i] * 32768.f) * (1U << 16)); + } } Error AudioDriverJavaScript::init() { - - /* clang-format off */ - _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 ref = Module.IDHandler.get($0); - var ctx = ref['context']; - var CHANNEL_COUNT = $1; - - var channelCount = ctx.destination.channelCount; - var script = null; - try { - // Try letting the browser recommend a buffer length. - script = ctx.createScriptProcessor(0, 2, channelCount); - } catch (e) { - // ...otherwise, default to 4096. - script = ctx.createScriptProcessor(4096, 2, channelCount); - } - script.connect(ctx.destination); - ref['script'] = script; - - return script.bufferSize; - }, _driver_id, channel_count); - /* clang-format on */ - if (!buffer_length) { - return FAILED; + mix_rate = GLOBAL_GET("audio/mix_rate"); + int latency = GLOBAL_GET("audio/output_latency"); + + channel_count = godot_audio_init(mix_rate, latency, &_state_change_callback, &_latency_update_callback); + buffer_length = closest_power_of_2((latency * mix_rate / 1000)); +#ifndef NO_THREADS + node = memnew(WorkletNode); +#else + node = memnew(ScriptProcessorNode); +#endif + buffer_length = node->create(buffer_length, channel_count); + if (output_rb) { + memdelete_arr(output_rb); } - - if (!internal_buffer || (int)memarr_len(internal_buffer) != buffer_length * channel_count) { - if (internal_buffer) - memdelete_arr(internal_buffer); - internal_buffer = memnew_arr(float, buffer_length *channel_count); + output_rb = memnew_arr(float, buffer_length *channel_count); + if (!output_rb) { + return ERR_OUT_OF_MEMORY; } - - return internal_buffer ? OK : ERR_OUT_OF_MEMORY; + if (input_rb) { + memdelete_arr(input_rb); + } + input_rb = memnew_arr(float, buffer_length *channel_count); + if (!input_rb) { + return ERR_OUT_OF_MEMORY; + } + return OK; } void AudioDriverJavaScript::start() { - - /* clang-format off */ - EM_ASM({ - 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']); - ref['script'].onaudioprocess = function(audioProcessingEvent) { - audioDriverMixFunction(); - - var input = audioProcessingEvent.inputBuffer; - var output = audioProcessingEvent.outputBuffer; - var internalBuffer = HEAPF32.subarray( - INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT, - INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT + output.length * output.numberOfChannels); - - for (var channel = 0; channel < output.numberOfChannels; channel++) { - var outputData = output.getChannelData(channel); - // Loop through samples. - for (var sample = 0; sample < outputData.length; sample++) { - outputData[sample] = internalBuffer[sample * output.numberOfChannels + channel]; - } - } - - if (ref['input']) { - var inputDataL = input.getChannelData(0); - var inputDataR = input.getChannelData(1); - for (var i = 0; i < inputDataL.length; i++) { - audioDriverProcessCapture(inputDataL[i]); - audioDriverProcessCapture(inputDataR[i]); - } - } - }; - }, _driver_id, internal_buffer); - /* clang-format on */ + if (node) { + node->start(output_rb, memarr_len(output_rb), input_rb, memarr_len(input_rb)); + } } void AudioDriverJavaScript::resume() { - /* clang-format off */ - EM_ASM({ - const ref = Module.IDHandler.get($0); - if (ref && ref['context'] && ref['context'].resume) - ref['context'].resume(); - }, _driver_id); - /* clang-format on */ + if (state == 0) { // 'suspended' + godot_audio_resume(); + } } -int AudioDriverJavaScript::get_mix_rate() const { +float AudioDriverJavaScript::get_latency() { + return output_latency + (float(buffer_length) / mix_rate); +} - /* clang-format off */ - return EM_ASM_INT({ - const ref = Module.IDHandler.get($0); - return ref && ref['context'] ? ref['context'].sampleRate : 0; - }, _driver_id); - /* clang-format on */ +int AudioDriverJavaScript::get_mix_rate() const { + return mix_rate; } AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const { - - /* clang-format off */ - 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 */ + return get_speaker_mode_by_total_channels(channel_count); } -// No locking, as threads are not supported. void AudioDriverJavaScript::lock() { + if (node) { + node->unlock(); + } } void AudioDriverJavaScript::unlock() { + if (node) { + node->unlock(); + } } void AudioDriverJavaScript::finish() { - - /* clang-format off */ - EM_ASM({ - Module.IDHandler.remove($0); - }, _driver_id); - /* clang-format on */ - - if (internal_buffer) { - memdelete_arr(internal_buffer); - internal_buffer = nullptr; + if (node) { + node->finish(); + memdelete(node); + node = nullptr; + } + if (output_rb) { + memdelete_arr(output_rb); + output_rb = nullptr; + } + if (input_rb) { + memdelete_arr(input_rb); + input_rb = nullptr; } - _driver_id = 0; } Error AudioDriverJavaScript::capture_start() { - + lock(); input_buffer_init(buffer_length); + unlock(); + godot_audio_capture_start(); + return OK; +} - /* clang-format off */ - EM_ASM({ - function gotMediaInput(stream) { - var ref = Module.IDHandler.get($0); - ref['stream'] = stream; - ref['input'] = ref['context'].createMediaStreamSource(stream); - ref['input'].connect(ref['script']); - } - - function gotMediaInputError(e) { - out(e); - } +Error AudioDriverJavaScript::capture_stop() { + godot_audio_capture_stop(); + lock(); + input_buffer.clear(); + unlock(); + return OK; +} - if (navigator.mediaDevices.getUserMedia) { - navigator.mediaDevices.getUserMedia({"audio": true}).then(gotMediaInput, gotMediaInputError); - } else { - if (!navigator.getUserMedia) - navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; - navigator.getUserMedia({"audio": true}, gotMediaInput, gotMediaInputError); - } - }, _driver_id); - /* clang-format on */ +AudioDriverJavaScript::AudioDriverJavaScript() { + singleton = this; +} - return OK; +#ifdef NO_THREADS +/// ScriptProcessorNode implementation +void AudioDriverJavaScript::ScriptProcessorNode::_process_callback() { + AudioDriverJavaScript::singleton->_audio_driver_capture(); + AudioDriverJavaScript::singleton->_audio_driver_process(); } -Error AudioDriverJavaScript::capture_stop() { +int AudioDriverJavaScript::ScriptProcessorNode::create(int p_buffer_samples, int p_channels) { + return godot_audio_script_create(p_buffer_samples, p_channels); +} - /* clang-format off */ - EM_ASM({ - 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(); +void AudioDriverJavaScript::ScriptProcessorNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { + godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback); +} +#else +/// AudioWorkletNode implementation +void AudioDriverJavaScript::WorkletNode::_audio_thread_func(void *p_data) { + AudioDriverJavaScript::WorkletNode *obj = static_cast<AudioDriverJavaScript::WorkletNode *>(p_data); + AudioDriverJavaScript *driver = AudioDriverJavaScript::singleton; + const int out_samples = memarr_len(driver->output_rb); + const int in_samples = memarr_len(driver->input_rb); + int wpos = 0; + int to_write = out_samples; + int rpos = 0; + int to_read = 0; + int32_t step = 0; + while (!obj->quit) { + if (to_read) { + driver->lock(); + driver->_audio_driver_capture(rpos, to_read); + godot_audio_worklet_state_add(obj->state, STATE_SAMPLES_IN, -to_read); + driver->unlock(); + rpos += to_read; + if (rpos >= in_samples) { + rpos -= in_samples; } - ref['stream'] = null; } - - if (ref['input']) { - ref['input'].disconnect(); - ref['input'] = null; + if (to_write) { + driver->lock(); + driver->_audio_driver_process(wpos, to_write); + godot_audio_worklet_state_add(obj->state, STATE_SAMPLES_OUT, to_write); + driver->unlock(); + wpos += to_write; + if (wpos >= out_samples) { + wpos -= out_samples; + } } + step = godot_audio_worklet_state_wait(obj->state, STATE_PROCESS, step, 1); + to_write = out_samples - godot_audio_worklet_state_get(obj->state, STATE_SAMPLES_OUT); + to_read = godot_audio_worklet_state_get(obj->state, STATE_SAMPLES_IN); + } +} - }, _driver_id); - /* clang-format on */ - - input_buffer.clear(); +int AudioDriverJavaScript::WorkletNode::create(int p_buffer_size, int p_channels) { + godot_audio_worklet_create(p_channels); + return p_buffer_size; +} - return OK; +void AudioDriverJavaScript::WorkletNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { + godot_audio_worklet_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, state); + thread = Thread::create(_audio_thread_func, this); } -AudioDriverJavaScript::AudioDriverJavaScript() { +void AudioDriverJavaScript::WorkletNode::lock() { + mutex.lock(); +} - _driver_id = 0; - internal_buffer = nullptr; - buffer_length = 0; +void AudioDriverJavaScript::WorkletNode::unlock() { + mutex.unlock(); +} - singleton = this; +void AudioDriverJavaScript::WorkletNode::finish() { + quit = true; // Ask thread to quit. + Thread::wait_to_finish(thread); + memdelete(thread); + thread = nullptr; } +#endif diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index f6f2dacd4e..f112a1ede4 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -31,18 +31,78 @@ #ifndef AUDIO_DRIVER_JAVASCRIPT_H #define AUDIO_DRIVER_JAVASCRIPT_H +#include "core/os/mutex.h" +#include "core/os/thread.h" #include "servers/audio_server.h" +#include "godot_audio.h" + class AudioDriverJavaScript : public AudioDriver { +public: + class AudioNode { + public: + virtual int create(int p_buffer_size, int p_output_channels) = 0; + virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) = 0; + virtual void finish() {} + virtual void lock() {} + virtual void unlock() {} + virtual ~AudioNode() {} + }; + + class WorkletNode : public AudioNode { + private: + enum { + STATE_LOCK, + STATE_PROCESS, + STATE_SAMPLES_IN, + STATE_SAMPLES_OUT, + STATE_MAX, + }; + Mutex mutex; + Thread *thread = nullptr; + bool quit = false; + int32_t state[STATE_MAX] = { 0 }; + + static void _audio_thread_func(void *p_data); + + public: + int create(int p_buffer_size, int p_output_channels) override; + void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; + void finish() override; + void lock() override; + void unlock() override; + }; + + class ScriptProcessorNode : public AudioNode { + private: + static void _process_callback(); - float *internal_buffer; + public: + int create(int p_buffer_samples, int p_channels) override; + void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; + }; - int _driver_id; - int buffer_length; +private: + AudioNode *node = nullptr; + + float *output_rb = nullptr; + float *input_rb = nullptr; + + int buffer_length = 0; + int mix_rate = 0; + int channel_count = 0; + int state = 0; + float output_latency = 0.0; + + static void _state_change_callback(int p_state); + static void _latency_update_callback(float p_latency); + +protected: + void _audio_driver_process(int p_from = 0, int p_samples = 0); + void _audio_driver_capture(int p_from = 0, int p_samples = 0); public: - void mix_to_js(); - void process_capture(float sample); + static bool is_available(); static AudioDriverJavaScript *singleton; @@ -51,6 +111,7 @@ public: virtual Error init(); virtual void start(); void resume(); + virtual float get_latency(); virtual int get_mix_rate() const; virtual SpeakerMode get_speaker_mode() const; virtual void lock(); @@ -62,5 +123,4 @@ public: AudioDriverJavaScript(); }; - #endif diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 9486e10717..e6e35f6aa9 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -1,6 +1,7 @@ import os -from emscripten_helpers import parse_config, run_closure_compiler, create_engine_file +from emscripten_helpers import run_closure_compiler, create_engine_file, add_js_libraries +from SCons.Util import WhereIs def is_active(): @@ -12,7 +13,7 @@ def get_name(): def can_build(): - return "EM_CONFIG" in os.environ or os.path.exists(os.path.expanduser("~/.emscripten")) + return WhereIs("emcc") is not None def get_opts(): @@ -22,7 +23,7 @@ def get_opts(): # 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), + BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False), ] @@ -57,7 +58,7 @@ def configure(env): env.Append(CPPDEFINES=["DEBUG_ENABLED"]) # Retain function names for backtraces at the cost of file size. env.Append(LINKFLAGS=["--profiling-funcs"]) - else: # 'debug' + else: # "debug" env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(CCFLAGS=["-O1", "-g"]) env.Append(LINKFLAGS=["-O1", "-g"]) @@ -84,7 +85,8 @@ def configure(env): 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"]) + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) # Closure compiler if env["use_closure_compiler"]: @@ -94,15 +96,15 @@ def configure(env): jscc = env.Builder(generator=run_closure_compiler, suffix=".cc.js", src_suffix=".js") env.Append(BUILDERS={"BuildJS": jscc}) + # Add helper method for adding libraries. + env.AddMethod(add_js_libraries, "AddJSLibraries") + # 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++" env["LINK"] = "emcc" @@ -137,8 +139,9 @@ def configure(env): 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", "PTHREAD_POOL_SIZE=8"]) env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) + env.extra_suffix = ".threads" + env.extra_suffix else: env.Append(CPPDEFINES=["NO_THREADS"]) @@ -150,7 +153,7 @@ def configure(env): env.Append(LIBS=["idbfs.js"]) env.Append(LINKFLAGS=["-s", "BINARYEN=1"]) - env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", 'EXPORT_NAME="Godot"']) + 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 @@ -162,5 +165,10 @@ def configure(env): env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"]) - # callMain for manual start, FS for preloading. - env.Append(LINKFLAGS=["-s", 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain", "FS"]']) + # Allow use to take control of swapping WebGL buffers. + env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"]) + + # callMain for manual start, FS for preloading, PATH and ERRNO_CODES for BrowserFS. + env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"]) + # Add code that allow exiting runtime. + env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"]) diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp new file mode 100644 index 0000000000..af8800d565 --- /dev/null +++ b/platform/javascript/display_server_javascript.cpp @@ -0,0 +1,1043 @@ +/*************************************************************************/ +/* display_server_javascript.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 "platform/javascript/display_server_javascript.h" + +#include "drivers/dummy/rasterizer_dummy.h" +#include "platform/javascript/os_javascript.h" + +#include <emscripten.h> +#include <png.h> + +#include "dom_keys.inc" +#include "godot_js.h" + +#define DOM_BUTTON_LEFT 0 +#define DOM_BUTTON_MIDDLE 1 +#define DOM_BUTTON_RIGHT 2 +#define DOM_BUTTON_XBUTTON1 3 +#define DOM_BUTTON_XBUTTON2 4 + +DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() { + return static_cast<DisplayServerJavaScript *>(DisplayServer::get_singleton()); +} + +// Window (canvas) +void DisplayServerJavaScript::focus_canvas() { + godot_js_display_canvas_focus(); +} + +bool DisplayServerJavaScript::is_canvas_focused() { + return godot_js_display_canvas_is_focused() != 0; +} + +bool DisplayServerJavaScript::check_size_force_redraw() { + int canvas_width; + int canvas_height; + emscripten_get_canvas_element_size(DisplayServerJavaScript::canvas_id, &canvas_width, &canvas_height); + if (last_width != canvas_width || last_height != canvas_height) { + last_width = canvas_width; + last_height = canvas_height; + // Update the framebuffer size for redraw. + emscripten_set_canvas_element_size(DisplayServerJavaScript::canvas_id, canvas_width, canvas_height); + return true; + } + return false; +} + +Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) { + DisplayServerJavaScript *display = get_singleton(); + int canvas_x; + int canvas_y; + godot_js_display_canvas_bounding_rect_position_get(&canvas_x, &canvas_y); + int canvas_width; + int canvas_height; + emscripten_get_canvas_element_size(display->canvas_id, &canvas_width, &canvas_height); + + double element_width; + double element_height; + emscripten_get_element_css_size(display->canvas_id, &element_width, &element_height); + + return Point2((int)(canvas_width / element_width * (p_x - canvas_x)), + (int)(canvas_height / element_height * (p_y - canvas_y))); +} + +EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { + DisplayServerJavaScript *display = get_singleton(); + // Empty ID is canvas. + String target_id = String::utf8(p_event->id); + if (target_id.empty() || target_id == String::utf8(display->canvas_id)) { + // This event property is the only reliable data on + // browser fullscreen state. + if (p_event->isFullscreen) { + display->window_mode = WINDOW_MODE_FULLSCREEN; + } else { + display->window_mode = WINDOW_MODE_WINDOWED; + } + } + return false; +} + +// Drag and drop callback. +void DisplayServerJavaScript::drop_files_js_callback(char **p_filev, int p_filec) { + DisplayServerJavaScript *ds = get_singleton(); + if (!ds) { + ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active"); + } + if (ds->drop_files_callback.is_null()) { + return; + } + Vector<String> files; + for (int i = 0; i < p_filec; i++) { + files.push_back(String::utf8(p_filev[i])); + } + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + ds->drop_files_callback.call((const Variant **)&vp, 1, ret, ce); +} + +// JavaScript quit request callback. +void DisplayServerJavaScript::request_quit_callback() { + DisplayServerJavaScript *ds = get_singleton(); + if (ds && !ds->window_event_callback.is_null()) { + Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce); + } +} + +// Keys + +template <typename T> +void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { + godot_event->set_shift(emscripten_event_ptr->shiftKey); + godot_event->set_alt(emscripten_event_ptr->altKey); + godot_event->set_control(emscripten_event_ptr->ctrlKey); + godot_event->set_metakey(emscripten_event_ptr->metaKey); +} + +Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { + Ref<InputEventKey> ev; + ev.instance(); + ev->set_echo(emscripten_event->repeat); + dom2godot_mod(emscripten_event, ev); + ev->set_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, false)); + ev->set_physical_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, true)); + + String unicode = String::utf8(emscripten_event->key); + // Check if empty or multi-character (e.g. `CapsLock`). + if (unicode.length() != 1) { + // Might be empty as well, but better than nonsense. + unicode = String::utf8(emscripten_event->charValue); + } + if (unicode.length() == 1) { + ev->set_unicode(unicode[0]); + } + + return ev; +} + +EM_BOOL DisplayServerJavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { + DisplayServerJavaScript *display = get_singleton(); + Ref<InputEventKey> ev = setup_key_event(p_event); + ev->set_pressed(true); + if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_keycode())) { + // Defer to keypress event for legacy unicode retrieval. + display->deferred_key_event = ev; + // Do not suppress keypress event. + return false; + } + Input::get_singleton()->parse_input_event(ev); + return true; +} + +EM_BOOL DisplayServerJavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { + DisplayServerJavaScript *display = get_singleton(); + display->deferred_key_event->set_unicode(p_event->charCode); + Input::get_singleton()->parse_input_event(display->deferred_key_event); + return true; +} + +EM_BOOL DisplayServerJavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { + Ref<InputEventKey> ev = setup_key_event(p_event); + ev->set_pressed(false); + Input::get_singleton()->parse_input_event(ev); + return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != 0; +} + +// Mouse + +EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { + DisplayServerJavaScript *display = get_singleton(); + + Ref<InputEventMouseButton> ev; + ev.instance(); + ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN); + ev->set_position(compute_position_in_canvas(p_event->clientX, p_event->clientY)); + ev->set_global_position(ev->get_position()); + dom2godot_mod(p_event, ev); + + switch (p_event->button) { + case DOM_BUTTON_LEFT: + ev->set_button_index(BUTTON_LEFT); + break; + case DOM_BUTTON_MIDDLE: + ev->set_button_index(BUTTON_MIDDLE); + break; + case DOM_BUTTON_RIGHT: + ev->set_button_index(BUTTON_RIGHT); + break; + case DOM_BUTTON_XBUTTON1: + ev->set_button_index(BUTTON_XBUTTON1); + break; + case DOM_BUTTON_XBUTTON2: + ev->set_button_index(BUTTON_XBUTTON2); + break; + default: + return false; + } + + if (ev->is_pressed()) { + double diff = emscripten_get_now() - display->last_click_ms; + + if (ev->get_button_index() == display->last_click_button_index) { + if (diff < 400 && Point2(display->last_click_pos).distance_to(ev->get_position()) < 5) { + display->last_click_ms = 0; + display->last_click_pos = Point2(-100, -100); + display->last_click_button_index = -1; + ev->set_doubleclick(true); + } + + } else { + display->last_click_button_index = ev->get_button_index(); + } + + if (!ev->is_doubleclick()) { + display->last_click_ms += diff; + display->last_click_pos = ev->get_position(); + } + } + + Input *input = Input::get_singleton(); + int mask = input->get_mouse_button_mask(); + int button_flag = 1 << (ev->get_button_index() - 1); + if (ev->is_pressed()) { + // Since the event is consumed, focus manually. The containing iframe, + // if exists, may not have focus yet, so focus even if already focused. + focus_canvas(); + mask |= button_flag; + } else if (mask & button_flag) { + mask &= ~button_flag; + } else { + // Received release event, but press was outside the canvas, so ignore. + return false; + } + ev->set_button_mask(mask); + + input->parse_input_event(ev); + // Prevent multi-click text selection and wheel-click scrolling anchor. + // Context menu is prevented through contextmenu event. + return true; +} + +EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { + DisplayServerJavaScript *ds = get_singleton(); + Input *input = Input::get_singleton(); + int input_mask = input->get_mouse_button_mask(); + Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY); + // For motion outside the canvas, only read mouse movement if dragging + // started inside the canvas; imitating desktop app behaviour. + if (!ds->cursor_inside_canvas && !input_mask) + return false; + + Ref<InputEventMouseMotion> ev; + ev.instance(); + dom2godot_mod(p_event, ev); + ev->set_button_mask(input_mask); + + ev->set_position(pos); + ev->set_global_position(ev->get_position()); + + ev->set_relative(Vector2(p_event->movementX, p_event->movementY)); + input->set_mouse_position(ev->get_position()); + ev->set_speed(input->get_last_mouse_speed()); + + input->parse_input_event(ev); + // Don't suppress mouseover/-leave events. + return false; +} + +// Cursor +const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape p_shape) { + switch (p_shape) { + case DisplayServer::CURSOR_ARROW: + return "auto"; + case DisplayServer::CURSOR_IBEAM: + return "text"; + case DisplayServer::CURSOR_POINTING_HAND: + return "pointer"; + case DisplayServer::CURSOR_CROSS: + return "crosshair"; + case DisplayServer::CURSOR_WAIT: + return "progress"; + case DisplayServer::CURSOR_BUSY: + return "wait"; + case DisplayServer::CURSOR_DRAG: + return "grab"; + case DisplayServer::CURSOR_CAN_DROP: + return "grabbing"; + case DisplayServer::CURSOR_FORBIDDEN: + return "no-drop"; + case DisplayServer::CURSOR_VSIZE: + return "ns-resize"; + case DisplayServer::CURSOR_HSIZE: + return "ew-resize"; + case DisplayServer::CURSOR_BDIAGSIZE: + return "nesw-resize"; + case DisplayServer::CURSOR_FDIAGSIZE: + return "nwse-resize"; + case DisplayServer::CURSOR_MOVE: + return "move"; + case DisplayServer::CURSOR_VSPLIT: + return "row-resize"; + case DisplayServer::CURSOR_HSPLIT: + return "col-resize"; + case DisplayServer::CURSOR_HELP: + return "help"; + default: + return "auto"; + } +} + +void DisplayServerJavaScript::cursor_set_shape(CursorShape p_shape) { + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + if (cursor_shape == p_shape) { + return; + } + cursor_shape = p_shape; + godot_js_display_cursor_set_shape(godot2dom_cursor(cursor_shape)); +} + +DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const { + return cursor_shape; +} + +void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { + if (p_cursor.is_valid()) { + Ref<Texture2D> texture = p_cursor; + Ref<AtlasTexture> atlas_texture = p_cursor; + Ref<Image> image; + Size2 texture_size; + Rect2 atlas_rect; + + if (texture.is_valid()) { + image = texture->get_data(); + } + + if (!image.is_valid() && atlas_texture.is_valid()) { + texture = atlas_texture->get_atlas(); + + atlas_rect.size.width = texture->get_width(); + atlas_rect.size.height = texture->get_height(); + atlas_rect.position.x = atlas_texture->get_region().position.x; + atlas_rect.position.y = atlas_texture->get_region().position.y; + + texture_size.width = atlas_texture->get_region().size.x; + texture_size.height = atlas_texture->get_region().size.y; + } else if (image.is_valid()) { + texture_size.width = texture->get_width(); + texture_size.height = texture->get_height(); + } + + ERR_FAIL_COND(!texture.is_valid()); + ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); + ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); + ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); + + image = texture->get_data(); + + ERR_FAIL_COND(!image.is_valid()); + + image = image->duplicate(); + + if (atlas_texture.is_valid()) + image->crop_from_point( + atlas_rect.position.x, + atlas_rect.position.y, + texture_size.width, + texture_size.height); + + if (image->get_format() != Image::FORMAT_RGBA8) { + image->convert(Image::FORMAT_RGBA8); + } + + png_image png_meta; + memset(&png_meta, 0, sizeof png_meta); + png_meta.version = PNG_IMAGE_VERSION; + png_meta.width = texture_size.width; + png_meta.height = texture_size.height; + png_meta.format = PNG_FORMAT_RGBA; + + PackedByteArray png; + size_t len; + PackedByteArray data = image->get_data(); + ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr)); + + png.resize(len); + ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr)); + + godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), png.ptr(), len, p_hotspot.x, p_hotspot.y); + + } else { + godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), NULL, 0, 0, 0); + } + + cursor_set_shape(cursor_shape); +} + +// Mouse mode +void DisplayServerJavaScript::mouse_set_mode(MouseMode p_mode) { + ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED, "MOUSE_MODE_CONFINED is not supported for the HTML5 platform."); + if (p_mode == mouse_get_mode()) + return; + + if (p_mode == MOUSE_MODE_VISIBLE) { + godot_js_display_cursor_set_visible(1); + emscripten_exit_pointerlock(); + + } else if (p_mode == MOUSE_MODE_HIDDEN) { + godot_js_display_cursor_set_visible(0); + emscripten_exit_pointerlock(); + + } else if (p_mode == MOUSE_MODE_CAPTURED) { + godot_js_display_cursor_set_visible(1); + EMSCRIPTEN_RESULT result = emscripten_request_pointerlock(canvas_id, false); + ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback."); + ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback."); + } +} + +DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_mode() const { + if (godot_js_display_cursor_is_hidden()) { + return MOUSE_MODE_HIDDEN; + } + + EmscriptenPointerlockChangeEvent ev; + emscripten_get_pointerlock_status(&ev); + return (ev.isActive && String::utf8(ev.id) == String::utf8(canvas_id)) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE; +} + +// Wheel +EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) { + ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false); + DisplayServerJavaScript *ds = get_singleton(); + if (!is_canvas_focused()) { + if (ds->cursor_inside_canvas) { + focus_canvas(); + } else { + return false; + } + } + + Input *input = Input::get_singleton(); + Ref<InputEventMouseButton> ev; + ev.instance(); + ev->set_position(input->get_mouse_position()); + ev->set_global_position(ev->get_position()); + + ev->set_shift(input->is_key_pressed(KEY_SHIFT)); + ev->set_alt(input->is_key_pressed(KEY_ALT)); + ev->set_control(input->is_key_pressed(KEY_CONTROL)); + ev->set_metakey(input->is_key_pressed(KEY_META)); + + if (p_event->deltaY < 0) + ev->set_button_index(BUTTON_WHEEL_UP); + else if (p_event->deltaY > 0) + ev->set_button_index(BUTTON_WHEEL_DOWN); + else if (p_event->deltaX > 0) + ev->set_button_index(BUTTON_WHEEL_LEFT); + else if (p_event->deltaX < 0) + ev->set_button_index(BUTTON_WHEEL_RIGHT); + else + return false; + + // Different browsers give wildly different delta values, and we can't + // interpret deltaMode, so use default value for wheel events' factor. + + int button_flag = 1 << (ev->get_button_index() - 1); + + ev->set_pressed(true); + ev->set_button_mask(input->get_mouse_button_mask() | button_flag); + input->parse_input_event(ev); + + ev->set_pressed(false); + ev->set_button_mask(input->get_mouse_button_mask() & ~button_flag); + input->parse_input_event(ev); + + return true; +} + +// Touch +EM_BOOL DisplayServerJavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) { + DisplayServerJavaScript *display = get_singleton(); + Ref<InputEventScreenTouch> ev; + ev.instance(); + int lowest_id_index = -1; + for (int i = 0; i < p_event->numTouches; ++i) { + const EmscriptenTouchPoint &touch = p_event->touches[i]; + if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier) + lowest_id_index = i; + if (!touch.isChanged) + continue; + ev->set_index(touch.identifier); + ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY)); + display->touches[i] = ev->get_position(); + ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART); + + Input::get_singleton()->parse_input_event(ev); + } + // Resume audio context after input in case autoplay was denied. + return true; +} + +EM_BOOL DisplayServerJavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) { + DisplayServerJavaScript *display = get_singleton(); + Ref<InputEventScreenDrag> ev; + ev.instance(); + int lowest_id_index = -1; + for (int i = 0; i < p_event->numTouches; ++i) { + const EmscriptenTouchPoint &touch = p_event->touches[i]; + if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier) + lowest_id_index = i; + if (!touch.isChanged) + continue; + ev->set_index(touch.identifier); + ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY)); + Point2 &prev = display->touches[i]; + ev->set_relative(ev->get_position() - prev); + prev = ev->get_position(); + + Input::get_singleton()->parse_input_event(ev); + } + return true; +} + +bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { + return godot_js_display_touchscreen_is_available(); +} + +// Gamepad + +EM_BOOL DisplayServerJavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) { + Input *input = Input::get_singleton(); + if (p_event_type == EMSCRIPTEN_EVENT_GAMEPADCONNECTED) { + String guid = ""; + if (String::utf8(p_event->mapping) == "standard") + guid = "Default HTML5 Gamepad"; + input->joy_connection_changed(p_event->index, true, String::utf8(p_event->id), guid); + } else { + input->joy_connection_changed(p_event->index, false, ""); + } + return true; +} + +void DisplayServerJavaScript::process_joypads() { + int joypad_count = emscripten_get_num_gamepads(); + Input *input = Input::get_singleton(); + for (int joypad = 0; joypad < joypad_count; joypad++) { + EmscriptenGamepadEvent state; + EMSCRIPTEN_RESULT query_result = emscripten_get_gamepad_status(joypad, &state); + // Chromium reserves gamepads slots, so NO_DATA is an expected result. + ERR_CONTINUE(query_result != EMSCRIPTEN_RESULT_SUCCESS && + query_result != EMSCRIPTEN_RESULT_NO_DATA); + if (query_result == EMSCRIPTEN_RESULT_SUCCESS && state.connected) { + int button_count = MIN(state.numButtons, 18); + int axis_count = MIN(state.numAxes, 8); + for (int button = 0; button < button_count; button++) { + float value = state.analogButton[button]; + input->joy_button(joypad, button, value); + } + for (int axis = 0; axis < axis_count; axis++) { + Input::JoyAxis joy_axis; + joy_axis.min = -1; + joy_axis.value = state.axis[axis]; + input->joy_axis(joypad, axis, joy_axis); + } + } + } +} + +#if 0 +bool DisplayServerJavaScript::is_joy_known(int p_device) { + return Input::get_singleton()->is_joy_mapped(p_device); +} + + +String DisplayServerJavaScript::get_joy_guid(int p_device) const { + return Input::get_singleton()->get_joy_guid_remapped(p_device); +} +#endif + +Vector<String> DisplayServerJavaScript::get_rendering_drivers_func() { + Vector<String> drivers; + drivers.push_back("dummy"); + return drivers; +} + +// Clipboard +void DisplayServerJavaScript::update_clipboard_callback(const char *p_text) { + get_singleton()->clipboard = p_text; +} + +void DisplayServerJavaScript::clipboard_set(const String &p_text) { + clipboard = p_text; + int err = godot_js_display_clipboard_set(p_text.utf8().get_data()); + ERR_FAIL_COND_MSG(err, "Clipboard API is not supported."); +} + +String DisplayServerJavaScript::clipboard_get() const { + godot_js_display_clipboard_get(update_clipboard_callback); + return clipboard; +} + +void DisplayServerJavaScript::send_window_event_callback(int p_notification) { + DisplayServerJavaScript *ds = get_singleton(); + if (!ds) { + return; + } + if (p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER || p_notification == DisplayServer::WINDOW_EVENT_MOUSE_EXIT) { + ds->cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER; + } + if (!ds->window_event_callback.is_null()) { + Variant event = int(p_notification); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce); + } +} + +void DisplayServerJavaScript::alert(const String &p_alert, const String &p_title) { + godot_js_display_alert(p_alert.utf8().get_data()); +} + +void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) { + ERR_FAIL_COND(p_icon.is_null()); + Ref<Image> icon = p_icon; + if (icon->is_compressed()) { + icon = icon->duplicate(); + ERR_FAIL_COND(icon->decompress() != OK); + } + if (icon->get_format() != Image::FORMAT_RGBA8) { + if (icon == p_icon) + icon = icon->duplicate(); + icon->convert(Image::FORMAT_RGBA8); + } + + png_image png_meta; + memset(&png_meta, 0, sizeof png_meta); + png_meta.version = PNG_IMAGE_VERSION; + png_meta.width = icon->get_width(); + png_meta.height = icon->get_height(); + png_meta.format = PNG_FORMAT_RGBA; + + PackedByteArray png; + size_t len; + PackedByteArray data = icon->get_data(); + ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr)); + + png.resize(len); + ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr)); + + godot_js_display_window_icon_set(png.ptr(), len); +} + +void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_event) { + OS_JavaScript *os = OS_JavaScript::get_singleton(); + + // Resume audio context after input in case autoplay was denied. + os->resume_audio(); + + Callable cb = get_singleton()->input_event_callback; + if (!cb.is_null()) { + Variant ev = p_event; + Variant *evp = &ev; + Variant ret; + Callable::CallError ce; + cb.call((const Variant **)&evp, 1, ret, ce); + } +} + +DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + return memnew(DisplayServerJavaScript(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); +} + +DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + r_error = OK; // Always succeeds for now. + + // Ensure the canvas ID. + godot_js_config_canvas_id_get(canvas_id, 256); + + // Check if it's windows. + swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1; + + // Expose method for requesting quit. + godot_js_os_request_quit_cb(request_quit_callback); + + RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu? +#if 0 + EmscriptenWebGLContextAttributes attributes; + emscripten_webgl_init_context_attributes(&attributes); + attributes.alpha = GLOBAL_GET("display/window/per_pixel_transparency/allowed"); + attributes.antialias = false; + ERR_FAIL_INDEX_V(p_video_driver, VIDEO_DRIVER_MAX, ERR_INVALID_PARAMETER); + + if (p_desired.layered) { + set_window_per_pixel_transparency_enabled(true); + } + + bool gl_initialization_error = false; + + if (RasterizerGLES2::is_viable() == OK) { + attributes.majorVersion = 1; + RasterizerGLES2::register_config(); + RasterizerGLES2::make_current(); + } else { + gl_initialization_error = true; + } + + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id, &attributes); + if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) { + gl_initialization_error = true; + } + + if (gl_initialization_error) { + OS::get_singleton()->alert("Your browser does not seem to support WebGL. Please update your browser version.", + "Unable to initialize video driver"); + return ERR_UNAVAILABLE; + } + + video_driver_index = p_video_driver; +#endif + + window_set_mode(p_mode); + if (godot_js_config_is_resize_on_start()) { + window_set_size(p_resolution); + } + + EMSCRIPTEN_RESULT result; +#define EM_CHECK(ev) \ + if (result != EMSCRIPTEN_RESULT_SUCCESS) \ + ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result)); +#define SET_EM_CALLBACK(target, ev, cb) \ + result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \ + EM_CHECK(ev) +#define SET_EM_WINDOW_CALLBACK(ev, cb) \ + result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, &cb); \ + EM_CHECK(ev) +#define SET_EM_CALLBACK_NOTARGET(ev, cb) \ + result = emscripten_set_##ev##_callback(nullptr, true, &cb); \ + EM_CHECK(ev) + // These callbacks from Emscripten's html5.h suffice to access most + // JavaScript APIs. + SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback) + SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback) + SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback) + SET_EM_CALLBACK(canvas_id, wheel, wheel_callback) + SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback) + SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback) + SET_EM_CALLBACK(canvas_id, touchend, touch_press_callback) + SET_EM_CALLBACK(canvas_id, touchcancel, touch_press_callback) + SET_EM_CALLBACK(canvas_id, keydown, keydown_callback) + SET_EM_CALLBACK(canvas_id, keypress, keypress_callback) + SET_EM_CALLBACK(canvas_id, keyup, keyup_callback) + SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback) + SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback) + SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback) +#undef SET_EM_CALLBACK_NOTARGET +#undef SET_EM_CALLBACK +#undef EM_CHECK + + // For APIs that are not (sufficiently) exposed, a + // library is used below (implemented in library_godot_display.js). + godot_js_display_notification_cb(&send_window_event_callback, + WINDOW_EVENT_MOUSE_ENTER, + WINDOW_EVENT_MOUSE_EXIT, + WINDOW_EVENT_FOCUS_IN, + WINDOW_EVENT_FOCUS_OUT); + godot_js_display_paste_cb(update_clipboard_callback); + godot_js_display_drop_files_cb(drop_files_js_callback); + + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event); +} + +DisplayServerJavaScript::~DisplayServerJavaScript() { + //emscripten_webgl_commit_frame(); + //emscripten_webgl_destroy_context(webgl_ctx); +} + +bool DisplayServerJavaScript::has_feature(Feature p_feature) const { + switch (p_feature) { + //case FEATURE_CONSOLE_WINDOW: + //case FEATURE_GLOBAL_MENU: + //case FEATURE_HIDPI: + //case FEATURE_IME: + case FEATURE_ICON: + case FEATURE_CLIPBOARD: + case FEATURE_CURSOR_SHAPE: + case FEATURE_CUSTOM_CURSOR_SHAPE: + case FEATURE_MOUSE: + case FEATURE_TOUCHSCREEN: + return true; + //case FEATURE_MOUSE_WARP: + //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_ICON: + //case FEATURE_NATIVE_VIDEO: + //case FEATURE_WINDOW_TRANSPARENCY: + //case FEATURE_KEEP_SCREEN_ON: + //case FEATURE_ORIENTATION: + //case FEATURE_VIRTUAL_KEYBOARD: + default: + return false; + } +} + +void DisplayServerJavaScript::register_javascript_driver() { + register_create_function("javascript", create_func, get_rendering_drivers_func); +} + +String DisplayServerJavaScript::get_name() const { + return "javascript"; +} + +int DisplayServerJavaScript::get_screen_count() const { + return 1; +} + +Point2i DisplayServerJavaScript::screen_get_position(int p_screen) const { + return Point2i(); // TODO offsetX/Y? +} + +Size2i DisplayServerJavaScript::screen_get_size(int p_screen) const { + EmscriptenFullscreenChangeEvent ev; + EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status(&ev); + ERR_FAIL_COND_V(result != EMSCRIPTEN_RESULT_SUCCESS, Size2i()); + return Size2i(ev.screenWidth, ev.screenHeight); +} + +Rect2i DisplayServerJavaScript::screen_get_usable_rect(int p_screen) const { + int canvas[2]; + emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); + return Rect2i(0, 0, canvas[0], canvas[1]); +} + +int DisplayServerJavaScript::screen_get_dpi(int p_screen) const { + return 96; // TODO maybe check pixel ratio via window.devicePixelRatio * 96? Inexact. +} + +Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const { + Vector<WindowID> ret; + ret.push_back(MAIN_WINDOW_ID); + return ret; +} + +DisplayServerJavaScript::WindowID DisplayServerJavaScript::get_window_at_screen_position(const Point2i &p_position) const { + return MAIN_WINDOW_ID; +} + +void DisplayServerJavaScript::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { + window_attached_instance_id = p_instance; +} + +ObjectID DisplayServerJavaScript::window_get_attached_instance_id(WindowID p_window) const { + return window_attached_instance_id; +} + +void DisplayServerJavaScript::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { + // Not supported. +} + +void DisplayServerJavaScript::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { + window_event_callback = p_callable; +} + +void DisplayServerJavaScript::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { + input_event_callback = p_callable; +} + +void DisplayServerJavaScript::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { + input_text_callback = p_callable; // TODO unused... do I need this? +} + +void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { + drop_files_callback = p_callable; +} + +void DisplayServerJavaScript::window_set_title(const String &p_title, WindowID p_window) { + godot_js_display_window_title_set(p_title.utf8().get_data()); +} + +int DisplayServerJavaScript::window_get_current_screen(WindowID p_window) const { + return 1; +} + +void DisplayServerJavaScript::window_set_current_screen(int p_screen, WindowID p_window) { + // Not implemented. +} + +Point2i DisplayServerJavaScript::window_get_position(WindowID p_window) const { + return Point2i(); // TODO Does this need implementation? +} + +void DisplayServerJavaScript::window_set_position(const Point2i &p_position, WindowID p_window) { + // Not supported. +} + +void DisplayServerJavaScript::window_set_transient(WindowID p_window, WindowID p_parent) { + // Not supported. +} + +void DisplayServerJavaScript::window_set_max_size(const Size2i p_size, WindowID p_window) { + // Not supported. +} + +Size2i DisplayServerJavaScript::window_get_max_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerJavaScript::window_set_min_size(const Size2i p_size, WindowID p_window) { + // Not supported. +} + +Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const { + return Size2i(); +} + +void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) { + last_width = p_size.x; + last_height = p_size.y; + double scale = godot_js_display_pixel_ratio_get(); + emscripten_set_canvas_element_size(canvas_id, p_size.x * scale, p_size.y * scale); + emscripten_set_element_css_size(canvas_id, p_size.x, p_size.y); +} + +Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { + int canvas[2]; + emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); + return Size2(canvas[0], canvas[1]); +} + +Size2i DisplayServerJavaScript::window_get_real_size(WindowID p_window) const { + return window_get_size(p_window); +} + +void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_window) { + if (window_mode == p_mode) + return; + + switch (p_mode) { + case WINDOW_MODE_WINDOWED: { + if (window_mode == WINDOW_MODE_FULLSCREEN) { + emscripten_exit_fullscreen(); + } + window_mode = WINDOW_MODE_WINDOWED; + window_set_size(Size2i(last_width, last_height)); + } break; + case WINDOW_MODE_FULLSCREEN: { + EmscriptenFullscreenStrategy strategy; + strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; + strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; + strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; + strategy.canvasResizedCallback = nullptr; + EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id, false, &strategy); + ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); + ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); + } break; + case WINDOW_MODE_MAXIMIZED: + case WINDOW_MODE_MINIMIZED: + WARN_PRINT("WindowMode MAXIMIZED and MINIMIZED are not supported in HTML5 platform."); + break; + default: + break; + } +} + +DisplayServerJavaScript::WindowMode DisplayServerJavaScript::window_get_mode(WindowID p_window) const { + return window_mode; +} + +bool DisplayServerJavaScript::window_is_maximize_allowed(WindowID p_window) const { + return false; +} + +void DisplayServerJavaScript::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { + // Not supported. +} + +bool DisplayServerJavaScript::window_get_flag(WindowFlags p_flag, WindowID p_window) const { + return false; +} + +void DisplayServerJavaScript::window_request_attention(WindowID p_window) { + // Not supported. +} + +void DisplayServerJavaScript::window_move_to_foreground(WindowID p_window) { + // Not supported. +} + +bool DisplayServerJavaScript::window_can_draw(WindowID p_window) const { + return true; +} + +bool DisplayServerJavaScript::can_any_window_draw() const { + return true; +} + +void DisplayServerJavaScript::process_events() { + if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) + process_joypads(); +} + +int DisplayServerJavaScript::get_current_video_driver() const { + return 1; +} + +bool DisplayServerJavaScript::get_swap_cancel_ok() { + return swap_cancel_ok; +} + +void DisplayServerJavaScript::swap_buffers() { + //emscripten_webgl_commit_frame(); +} diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h new file mode 100644 index 0000000000..1f00295d48 --- /dev/null +++ b/platform/javascript/display_server_javascript.h @@ -0,0 +1,205 @@ +/*************************************************************************/ +/* display_server_javascript.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 DISPLAY_SERVER_JAVASCRIPT_H +#define DISPLAY_SERVER_JAVASCRIPT_H + +#include "servers/display_server.h" + +#include <emscripten.h> +#include <emscripten/html5.h> + +class DisplayServerJavaScript : public DisplayServer { +private: + WindowMode window_mode = WINDOW_MODE_WINDOWED; + ObjectID window_attached_instance_id = {}; + + Callable window_event_callback; + Callable input_event_callback; + Callable input_text_callback; + Callable drop_files_callback; + + String clipboard; + Ref<InputEventKey> deferred_key_event; + Point2 touches[32]; + + char canvas_id[256] = { 0 }; + bool cursor_inside_canvas = true; + CursorShape cursor_shape = CURSOR_ARROW; + Point2i last_click_pos = Point2(-100, -100); // TODO check this again. + double last_click_ms = 0; + int last_click_button_index = -1; + + int last_width = 0; + int last_height = 0; + + bool swap_cancel_ok = false; + + // utilities + static Point2 compute_position_in_canvas(int p_x, int p_y); + static void focus_canvas(); + static bool is_canvas_focused(); + template <typename T> + static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event); + static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event); + static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape); + + // events + static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data); + + static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); + static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); + static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); + + static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data); + static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data); + + static EM_BOOL wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data); + + static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data); + static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data); + + static EM_BOOL gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data); + void process_joypads(); + + static Vector<String> get_rendering_drivers_func(); + static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + + static void _dispatch_input_event(const Ref<InputEvent> &p_event); + + static void request_quit_callback(); + static void update_clipboard_callback(const char *p_text); + static void send_window_event_callback(int p_notification); + static void drop_files_js_callback(char **p_filev, int p_filec); + +protected: + int get_current_video_driver() const; + +public: + // Override return type to make writing static callbacks less tedious. + static DisplayServerJavaScript *get_singleton(); + + // utilities + bool check_size_force_redraw(); + + // from DisplayServer + void alert(const String &p_alert, const String &p_title = "ALERT!") override; + bool has_feature(Feature p_feature) const override; + String get_name() const override; + + // cursor + void cursor_set_shape(CursorShape p_shape) override; + CursorShape cursor_get_shape() const override; + void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; + + // mouse + void mouse_set_mode(MouseMode p_mode) override; + MouseMode mouse_get_mode() const override; + + // touch + bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + // clipboard + void clipboard_set(const String &p_text) override; + String clipboard_get() const override; + + // screen + int get_screen_count() const override; + Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + // windows + Vector<DisplayServer::WindowID> get_window_list() const override; + WindowID get_window_at_screen_position(const Point2i &p_position) const override; + + void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; + ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; + + void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + + void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + + void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + + void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + + int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; + void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; + + Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; + + void window_set_transient(WindowID p_window, WindowID p_parent) override; + + void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; + Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + + void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; + WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; + + bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; + + void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; + bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; + + void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; + void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; + + bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; + + bool can_any_window_draw() const override; + + // events + void process_events() override; + + // icon + void set_icon(const Ref<Image> &p_icon) override; + + // others + bool get_swap_cancel_ok() override; + void swap_buffers() override; + + static void register_javascript_driver(); + DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerJavaScript(); +}; + +#endif // DISPLAY_SERVER_JAVASCRIPT_H diff --git a/platform/javascript/dom_keys.inc b/platform/javascript/dom_keys.inc index fd9df765d2..e3f2ce42b4 100644 --- a/platform/javascript/dom_keys.inc +++ b/platform/javascript/dom_keys.inc @@ -30,351 +30,203 @@ #include "core/os/keyboard.h" -// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Constants_for_keyCode_value -#define DOM_VK_CANCEL 0x03 -#define DOM_VK_HELP 0x06 -#define DOM_VK_BACK_SPACE 0x08 -#define DOM_VK_TAB 0x09 -#define DOM_VK_CLEAR 0x0C -#define DOM_VK_RETURN 0x0D -#define DOM_VK_ENTER 0x0E // "Reserved, but not used." -#define DOM_VK_SHIFT 0x10 -#define DOM_VK_CONTROL 0x11 -#define DOM_VK_ALT 0x12 -#define DOM_VK_PAUSE 0x13 -#define DOM_VK_CAPS_LOCK 0x14 -#define DOM_VK_KANA 0x15 -#define DOM_VK_HANGUL 0x15 -#define DOM_VK_EISU 0x16 -#define DOM_VK_JUNJA 0x17 -#define DOM_VK_FINAL 0x18 -#define DOM_VK_HANJA 0x19 -#define DOM_VK_KANJI 0x19 -#define DOM_VK_ESCAPE 0x1B -#define DOM_VK_CONVERT 0x1C -#define DOM_VK_NONCONVERT 0x1D -#define DOM_VK_ACCEPT 0x1E -#define DOM_VK_MODECHANGE 0x1F -#define DOM_VK_SPACE 0x20 -#define DOM_VK_PAGE_UP 0x21 -#define DOM_VK_PAGE_DOWN 0x22 -#define DOM_VK_END 0x23 -#define DOM_VK_HOME 0x24 -#define DOM_VK_LEFT 0x25 -#define DOM_VK_UP 0x26 -#define DOM_VK_RIGHT 0x27 -#define DOM_VK_DOWN 0x28 -#define DOM_VK_SELECT 0x29 -#define DOM_VK_PRINT 0x2A -#define DOM_VK_EXECUTE 0x2B -#define DOM_VK_PRINTSCREEN 0x2C -#define DOM_VK_INSERT 0x2D -#define DOM_VK_DELETE 0x2E -#define DOM_VK_0 0x30 -#define DOM_VK_1 0x31 -#define DOM_VK_2 0x32 -#define DOM_VK_3 0x33 -#define DOM_VK_4 0x34 -#define DOM_VK_5 0x35 -#define DOM_VK_6 0x36 -#define DOM_VK_7 0x37 -#define DOM_VK_8 0x38 -#define DOM_VK_9 0x39 -#define DOM_VK_COLON 0x3A -#define DOM_VK_SEMICOLON 0x3B -#define DOM_VK_LESS_THAN 0x3C -#define DOM_VK_EQUALS 0x3D -#define DOM_VK_GREATER_THAN 0x3E -#define DOM_VK_QUESTION_MARK 0x3F -#define DOM_VK_AT 0x40 -#define DOM_VK_A 0x41 -#define DOM_VK_B 0x42 -#define DOM_VK_C 0x43 -#define DOM_VK_D 0x44 -#define DOM_VK_E 0x45 -#define DOM_VK_F 0x46 -#define DOM_VK_G 0x47 -#define DOM_VK_H 0x48 -#define DOM_VK_I 0x49 -#define DOM_VK_J 0x4A -#define DOM_VK_K 0x4B -#define DOM_VK_L 0x4C -#define DOM_VK_M 0x4D -#define DOM_VK_N 0x4E -#define DOM_VK_O 0x4F -#define DOM_VK_P 0x50 -#define DOM_VK_Q 0x51 -#define DOM_VK_R 0x52 -#define DOM_VK_S 0x53 -#define DOM_VK_T 0x54 -#define DOM_VK_U 0x55 -#define DOM_VK_V 0x56 -#define DOM_VK_W 0x57 -#define DOM_VK_X 0x58 -#define DOM_VK_Y 0x59 -#define DOM_VK_Z 0x5A -#define DOM_VK_WIN 0x5B -#define DOM_VK_CONTEXT_MENU 0x5D -#define DOM_VK_SLEEP 0x5F -#define DOM_VK_NUMPAD0 0x60 -#define DOM_VK_NUMPAD1 0x61 -#define DOM_VK_NUMPAD2 0x62 -#define DOM_VK_NUMPAD3 0x63 -#define DOM_VK_NUMPAD4 0x64 -#define DOM_VK_NUMPAD5 0x65 -#define DOM_VK_NUMPAD6 0x66 -#define DOM_VK_NUMPAD7 0x67 -#define DOM_VK_NUMPAD8 0x68 -#define DOM_VK_NUMPAD9 0x69 -#define DOM_VK_MULTIPLY 0x6A -#define DOM_VK_ADD 0x6B -#define DOM_VK_SEPARATOR 0x6C -#define DOM_VK_SUBTRACT 0x6D -#define DOM_VK_DECIMAL 0x6E -#define DOM_VK_DIVIDE 0x6F -#define DOM_VK_F1 0x70 -#define DOM_VK_F2 0x71 -#define DOM_VK_F3 0x72 -#define DOM_VK_F4 0x73 -#define DOM_VK_F5 0x74 -#define DOM_VK_F6 0x75 -#define DOM_VK_F7 0x76 -#define DOM_VK_F8 0x77 -#define DOM_VK_F9 0x78 -#define DOM_VK_F10 0x79 -#define DOM_VK_F11 0x7A -#define DOM_VK_F12 0x7B -#define DOM_VK_F13 0x7C -#define DOM_VK_F14 0x7D -#define DOM_VK_F15 0x7E -#define DOM_VK_F16 0x7F -#define DOM_VK_F17 0x80 -#define DOM_VK_F18 0x81 -#define DOM_VK_F19 0x82 -#define DOM_VK_F20 0x83 -#define DOM_VK_F21 0x84 -#define DOM_VK_F22 0x85 -#define DOM_VK_F23 0x86 -#define DOM_VK_F24 0x87 -#define DOM_VK_NUM_LOCK 0x90 -#define DOM_VK_SCROLL_LOCK 0x91 -#define DOM_VK_WIN_OEM_FJ_JISHO 0x92 -#define DOM_VK_WIN_OEM_FJ_MASSHOU 0x93 -#define DOM_VK_WIN_OEM_FJ_TOUROKU 0x94 -#define DOM_VK_WIN_OEM_FJ_LOYA 0x95 -#define DOM_VK_WIN_OEM_FJ_ROYA 0x96 -#define DOM_VK_CIRCUMFLEX 0xA0 -#define DOM_VK_EXCLAMATION 0xA1 -#define DOM_VK_DOUBLE_QUOTE 0xA2 -#define DOM_VK_HASH 0xA3 -#define DOM_VK_DOLLAR 0xA4 -#define DOM_VK_PERCENT 0xA5 -#define DOM_VK_AMPERSAND 0xA6 -#define DOM_VK_UNDERSCORE 0xA7 -#define DOM_VK_OPEN_PAREN 0xA8 -#define DOM_VK_CLOSE_PAREN 0xA9 -#define DOM_VK_ASTERISK 0xAA -#define DOM_VK_PLUS 0xAB -#define DOM_VK_PIPE 0xAC -#define DOM_VK_HYPHEN_MINUS 0xAD -#define DOM_VK_OPEN_CURLY_BRACKET 0xAE -#define DOM_VK_CLOSE_CURLY_BRACKET 0xAF -#define DOM_VK_TILDE 0xB0 -#define DOM_VK_VOLUME_MUTE 0xB5 -#define DOM_VK_VOLUME_DOWN 0xB6 -#define DOM_VK_VOLUME_UP 0xB7 -#define DOM_VK_COMMA 0xBC -#define DOM_VK_PERIOD 0xBE -#define DOM_VK_SLASH 0xBF -#define DOM_VK_BACK_QUOTE 0xC0 -#define DOM_VK_OPEN_BRACKET 0xDB -#define DOM_VK_BACK_SLASH 0xDC -#define DOM_VK_CLOSE_BRACKET 0xDD -#define DOM_VK_QUOTE 0xDE -#define DOM_VK_META 0xE0 -#define DOM_VK_ALTGR 0xE1 -#define DOM_VK_WIN_ICO_HELP 0xE3 -#define DOM_VK_WIN_ICO_00 0xE4 -#define DOM_VK_WIN_ICO_CLEAR 0xE6 -#define DOM_VK_WIN_OEM_RESET 0xE9 -#define DOM_VK_WIN_OEM_JUMP 0xEA -#define DOM_VK_WIN_OEM_PA1 0xEB -#define DOM_VK_WIN_OEM_PA2 0xEC -#define DOM_VK_WIN_OEM_PA3 0xED -#define DOM_VK_WIN_OEM_WSCTRL 0xEE -#define DOM_VK_WIN_OEM_CUSEL 0xEF -#define DOM_VK_WIN_OEM_ATTN 0xF0 -#define DOM_VK_WIN_OEM_FINISH 0xF1 -#define DOM_VK_WIN_OEM_COPY 0xF2 -#define DOM_VK_WIN_OEM_AUTO 0xF3 -#define DOM_VK_WIN_OEM_ENLW 0xF4 -#define DOM_VK_WIN_OEM_BACKTAB 0xF5 -#define DOM_VK_ATTN 0xF6 -#define DOM_VK_CRSEL 0xF7 -#define DOM_VK_EXSEL 0xF8 -#define DOM_VK_EREOF 0xF9 -#define DOM_VK_PLAY 0xFA -#define DOM_VK_ZOOM 0xFB -#define DOM_VK_PA1 0xFD -#define DOM_VK_WIN_OEM_CLEAR 0xFE - -int dom2godot_keycode(int dom_keycode) { - - if (DOM_VK_0 <= dom_keycode && dom_keycode <= DOM_VK_Z) { - // ASCII intersection - return dom_keycode; - } - - if (DOM_VK_NUMPAD0 <= dom_keycode && dom_keycode <= DOM_VK_NUMPAD9) { - // Numpad numbers - return KEY_KP_0 + (dom_keycode - DOM_VK_NUMPAD0); +// See https://w3c.github.io/uievents-code/#code-value-tables +int dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], bool p_physical) { +#define DOM2GODOT(p_str, p_godot_code) \ + if (memcmp((const void *)p_str, (void *)p_code, strlen(p_str) + 1) == 0) { \ + return KEY_##p_godot_code; \ } - if (DOM_VK_F1 <= dom_keycode && dom_keycode <= DOM_VK_F16) { - // F1-F16 - return KEY_F1 + (dom_keycode - DOM_VK_F1); + // Numpad section. + DOM2GODOT("NumLock", NUMLOCK); + DOM2GODOT("Numpad0", KP_0); + DOM2GODOT("Numpad1", KP_1); + DOM2GODOT("Numpad2", KP_2); + DOM2GODOT("Numpad3", KP_3); + DOM2GODOT("Numpad4", KP_4); + DOM2GODOT("Numpad5", KP_5); + DOM2GODOT("Numpad6", KP_6); + DOM2GODOT("Numpad7", KP_7); + DOM2GODOT("Numpad8", KP_8); + DOM2GODOT("Numpad9", KP_9); + DOM2GODOT("NumpadAdd", KP_ADD); + DOM2GODOT("NumpadBackspace", BACKSPACE); + DOM2GODOT("NumpadClear", CLEAR); + DOM2GODOT("NumpadClearEntry", CLEAR); + //DOM2GODOT("NumpadComma", UNKNOWN); + DOM2GODOT("NumpadDecimal", KP_PERIOD); + DOM2GODOT("NumpadDivide", KP_DIVIDE); + DOM2GODOT("NumpadEnter", KP_ENTER); + DOM2GODOT("NumpadEqual", EQUAL); + //DOM2GODOT("NumpadHash", UNKNOWN); + //DOM2GODOT("NumpadMemoryAdd", UNKNOWN); + //DOM2GODOT("NumpadMemoryClear", UNKNOWN); + //DOM2GODOT("NumpadMemoryRecall", UNKNOWN); + //DOM2GODOT("NumpadMemoryStore", UNKNOWN); + //DOM2GODOT("NumpadMemorySubtract", UNKNOWN); + DOM2GODOT("NumpadMultiply", KP_MULTIPLY); + DOM2GODOT("NumpadParenLeft", PARENLEFT); + DOM2GODOT("NumpadParenRight", PARENRIGHT); + DOM2GODOT("NumpadStar", KP_MULTIPLY); // or ASTERISK ? + DOM2GODOT("NumpadSubtract", KP_SUBTRACT); + + // Printable ASCII. + if (!p_physical) { + uint8_t b0 = (uint8_t)p_key[0]; + uint8_t b1 = (uint8_t)p_key[1]; + uint8_t b2 = (uint8_t)p_key[2]; + if (b1 == 0 && b0 > 0x1F && b0 < 0x7F) { // ASCII. + if (b0 > 0x60 && b0 < 0x7B) { // Lowercase ASCII. + b0 -= 32; + } + return b0; + } + +#define _U_2BYTES_MASK 0xE0 +#define _U_2BYTES 0xC0 + // Latin-1 codes. + if (b2 == 0 && (b0 & _U_2BYTES_MASK) == _U_2BYTES) { // 2-bytes utf8, only known latin. + uint32_t key = ((b0 & ~_U_2BYTES_MASK) << 6) | (b1 & 0x3F); + if (key >= 0xA0 && key <= 0xDF) { + return key; + } + if (key >= 0xE0 && key <= 0xFF) { // Lowercase known latin. + key -= 0x20; + return key; + } + } +#undef _U_2BYTES_MASK +#undef _U_2BYTES } - switch (dom_keycode) { - //case DOM_VK_CANCEL: return KEY_UNKNOWN; - case DOM_VK_HELP: return KEY_HELP; - case DOM_VK_BACK_SPACE: return KEY_BACKSPACE; - case DOM_VK_TAB: return KEY_TAB; - - case DOM_VK_CLEAR: - case DOM_VK_WIN_OEM_CLEAR: // OEM duplicate - return KEY_CLEAR; - - case DOM_VK_RETURN: - case DOM_VK_ENTER: // unused according to MDN - return KEY_ENTER; - - case DOM_VK_SHIFT: return KEY_SHIFT; - case DOM_VK_CONTROL: return KEY_CONTROL; - - case DOM_VK_ALT: - case DOM_VK_ALTGR: - return KEY_ALT; - - case DOM_VK_PAUSE: return KEY_PAUSE; - case DOM_VK_CAPS_LOCK: - return KEY_CAPSLOCK; - - /* - case DOM_VK_KANA: return KEY_UNKNOWN; - case DOM_VK_HANGUL: return KEY_UNKNOWN; - case DOM_VK_EISU: return KEY_UNKNOWN; - case DOM_VK_JUNJA: return KEY_UNKNOWN; - case DOM_VK_FINAL: return KEY_UNKNOWN; - case DOM_VK_HANJA: return KEY_UNKNOWN; - case DOM_VK_KANJI: return KEY_UNKNOWN; - */ - - case DOM_VK_ESCAPE: - return KEY_ESCAPE; - /* - case DOM_VK_CONVERT: return KEY_UNKNOWN; - case DOM_VK_NONCONVERT: return KEY_UNKNOWN; - case DOM_VK_ACCEPT: return KEY_UNKNOWN; - case DOM_VK_MODECHANGE: return KEY_UNKNOWN; - */ - - case DOM_VK_SPACE: return KEY_SPACE; - case DOM_VK_PAGE_UP: return KEY_PAGEUP; - case DOM_VK_PAGE_DOWN: return KEY_PAGEDOWN; - case DOM_VK_END: return KEY_END; - case DOM_VK_HOME: return KEY_HOME; - case DOM_VK_LEFT: return KEY_LEFT; - case DOM_VK_UP: return KEY_UP; - case DOM_VK_RIGHT: return KEY_RIGHT; - case DOM_VK_DOWN: - return KEY_DOWN; - - //case DOM_VK_SELECT: return KEY_UNKNOWN; - - case DOM_VK_PRINTSCREEN: - case DOM_VK_PRINT: - return KEY_PRINT; - - //case DOM_VK_EXECUTE: return KEY_UNKNOWN; - case DOM_VK_INSERT: return KEY_INSERT; - case DOM_VK_DELETE: return KEY_DELETE; - - case DOM_VK_META: - case DOM_VK_WIN: - return KEY_META; - - case DOM_VK_CONTEXT_MENU: return KEY_MENU; - case DOM_VK_SLEEP: - return KEY_STANDBY; - - // Numpad keys - case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY; - case DOM_VK_ADD: return KEY_KP_ADD; - case DOM_VK_SEPARATOR: - return KEY_KP_PERIOD; // Good enough? - case DOM_VK_SUBTRACT: return KEY_KP_SUBTRACT; - case DOM_VK_DECIMAL: return KEY_KP_PERIOD; - case DOM_VK_DIVIDE: - return KEY_KP_DIVIDE; - - /* - case DOM_VK_F17: return KEY_UNKNOWN; - case DOM_VK_F18: return KEY_UNKNOWN; - case DOM_VK_F19: return KEY_UNKNOWN; - case DOM_VK_F20: return KEY_UNKNOWN; - case DOM_VK_F21: return KEY_UNKNOWN; - case DOM_VK_F22: return KEY_UNKNOWN; - case DOM_VK_F23: return KEY_UNKNOWN; - case DOM_VK_F24: return KEY_UNKNOWN; - */ - - case DOM_VK_NUM_LOCK: return KEY_NUMLOCK; - case DOM_VK_SCROLL_LOCK: - return KEY_SCROLLLOCK; - - /* - case DOM_VK_WIN_OEM_FJ_JISHO: return KEY_UNKNOWN; - case DOM_VK_WIN_OEM_FJ_MASSHOU: return KEY_UNKNOWN; - case DOM_VK_WIN_OEM_FJ_TOUROKU: return KEY_UNKNOWN; - case DOM_VK_WIN_OEM_FJ_LOYA: return KEY_UNKNOWN; - case DOM_VK_WIN_OEM_FJ_ROYA: return KEY_UNKNOWN; - */ - - case DOM_VK_CIRCUMFLEX: return KEY_ASCIICIRCUM; - case DOM_VK_EXCLAMATION: return KEY_EXCLAM; - case DOM_VK_DOUBLE_QUOTE: return KEY_QUOTEDBL; - case DOM_VK_HASH: return KEY_NUMBERSIGN; - case DOM_VK_DOLLAR: return KEY_DOLLAR; - case DOM_VK_PERCENT: return KEY_PERCENT; - case DOM_VK_AMPERSAND: return KEY_AMPERSAND; - case DOM_VK_UNDERSCORE: return KEY_UNDERSCORE; - case DOM_VK_OPEN_PAREN: return KEY_PARENLEFT; - case DOM_VK_CLOSE_PAREN: return KEY_PARENRIGHT; - case DOM_VK_ASTERISK: return KEY_ASTERISK; - case DOM_VK_PLUS: return KEY_PLUS; - case DOM_VK_PIPE: return KEY_BAR; - case DOM_VK_HYPHEN_MINUS: return KEY_MINUS; - case DOM_VK_OPEN_CURLY_BRACKET: return KEY_BRACELEFT; - case DOM_VK_CLOSE_CURLY_BRACKET: return KEY_BRACERIGHT; - case DOM_VK_TILDE: return KEY_ASCIITILDE; - - case DOM_VK_VOLUME_MUTE: return KEY_VOLUMEMUTE; - case DOM_VK_VOLUME_DOWN: return KEY_VOLUMEDOWN; - case DOM_VK_VOLUME_UP: return KEY_VOLUMEUP; - - case DOM_VK_COMMA: return KEY_COMMA; - case DOM_VK_PERIOD: return KEY_PERIOD; - case DOM_VK_SLASH: return KEY_SLASH; - case DOM_VK_BACK_QUOTE: return KEY_QUOTELEFT; - case DOM_VK_OPEN_BRACKET: return KEY_BRACKETLEFT; - case DOM_VK_BACK_SLASH: return KEY_BACKSLASH; - case DOM_VK_CLOSE_BRACKET: return KEY_BRACKETRIGHT; - case DOM_VK_QUOTE: - return KEY_APOSTROPHE; - - // The rest is OEM/unusual. - - default: return KEY_UNKNOWN; - }; + // Alphanumeric section. + DOM2GODOT("Backquote", QUOTELEFT); + DOM2GODOT("Backslash", BACKSLASH); + DOM2GODOT("BracketLeft", BRACKETLEFT); + DOM2GODOT("BracketRight", BRACKETRIGHT); + DOM2GODOT("Comma", COMMA); + DOM2GODOT("Digit0", 0); + DOM2GODOT("Digit1", 1); + DOM2GODOT("Digit2", 2); + DOM2GODOT("Digit3", 3); + DOM2GODOT("Digit4", 4); + DOM2GODOT("Digit5", 5); + DOM2GODOT("Digit6", 6); + DOM2GODOT("Digit7", 7); + DOM2GODOT("Digit8", 8); + DOM2GODOT("Digit9", 9); + DOM2GODOT("Equal", EQUAL); + DOM2GODOT("IntlBackslash", BACKSLASH); + //DOM2GODOT("IntlRo", UNKNOWN); + DOM2GODOT("IntlYen", YEN); + + DOM2GODOT("KeyA", A); + DOM2GODOT("KeyB", B); + DOM2GODOT("KeyC", C); + DOM2GODOT("KeyD", D); + DOM2GODOT("KeyE", E); + DOM2GODOT("KeyF", F); + DOM2GODOT("KeyG", G); + DOM2GODOT("KeyH", H); + DOM2GODOT("KeyI", I); + DOM2GODOT("KeyJ", J); + DOM2GODOT("KeyK", K); + DOM2GODOT("KeyL", L); + DOM2GODOT("KeyM", M); + DOM2GODOT("KeyN", N); + DOM2GODOT("KeyO", O); + DOM2GODOT("KeyP", P); + DOM2GODOT("KeyQ", Q); + DOM2GODOT("KeyR", R); + DOM2GODOT("KeyS", S); + DOM2GODOT("KeyT", T); + DOM2GODOT("KeyU", U); + DOM2GODOT("KeyV", V); + DOM2GODOT("KeyW", W); + DOM2GODOT("KeyX", X); + DOM2GODOT("KeyY", Y); + DOM2GODOT("KeyZ", Z); + + DOM2GODOT("Minus", MINUS); + DOM2GODOT("Period", PERIOD); + DOM2GODOT("Quote", APOSTROPHE); + DOM2GODOT("Semicolon", SEMICOLON); + DOM2GODOT("Slash", SLASH); + + // Functional keys in the Alphanumeric section. + DOM2GODOT("AltLeft", ALT); + DOM2GODOT("AltRight", ALT); + DOM2GODOT("Backspace", BACKSPACE); + DOM2GODOT("CapsLock", CAPSLOCK); + DOM2GODOT("ContextMenu", MENU); + DOM2GODOT("ControlLeft", CONTROL); + DOM2GODOT("ControlRight", CONTROL); + DOM2GODOT("Enter", ENTER); + DOM2GODOT("MetaLeft", SUPER_L); + DOM2GODOT("MetaRight", SUPER_R); + DOM2GODOT("ShiftLeft", SHIFT); + DOM2GODOT("ShiftRight", SHIFT); + DOM2GODOT("Space", SPACE); + DOM2GODOT("Tab", TAB); + + // ControlPad section. + DOM2GODOT("Delete", DELETE); + DOM2GODOT("End", END); + DOM2GODOT("Help", HELP); + DOM2GODOT("Home", HOME); + DOM2GODOT("Insert", INSERT); + DOM2GODOT("PageDown", PAGEDOWN); + DOM2GODOT("PageUp", PAGEUP); + + // ArrowPad section. + DOM2GODOT("ArrowDown", DOWN); + DOM2GODOT("ArrowLeft", LEFT); + DOM2GODOT("ArrowRight", RIGHT); + DOM2GODOT("ArrowUp", UP); + + // Function section. + DOM2GODOT("Escape", ESCAPE); + DOM2GODOT("F1", F1); + DOM2GODOT("F2", F2); + DOM2GODOT("F3", F3); + DOM2GODOT("F4", F4); + DOM2GODOT("F5", F5); + DOM2GODOT("F6", F6); + DOM2GODOT("F7", F7); + DOM2GODOT("F8", F8); + DOM2GODOT("F9", F9); + DOM2GODOT("F10", F10); + DOM2GODOT("F11", F11); + DOM2GODOT("F12", F12); + //DOM2GODOT("Fn", UNKNOWN); // never actually fired, but included in the standard draft. + //DOM2GODOT("FnLock", UNKNOWN); + DOM2GODOT("PrintScreen", PRINT); + DOM2GODOT("ScrollLock", SCROLLLOCK); + DOM2GODOT("Pause", PAUSE); + + // Media keys section. + DOM2GODOT("BrowserBack", BACK); + DOM2GODOT("BrowserFavorites", FAVORITES); + DOM2GODOT("BrowserForward", FORWARD); + DOM2GODOT("BrowserHome", OPENURL); + DOM2GODOT("BrowserRefresh", REFRESH); + DOM2GODOT("BrowserSearch", SEARCH); + DOM2GODOT("BrowserStop", STOP); + //DOM2GODOT("Eject", UNKNOWN); + DOM2GODOT("LaunchApp1", LAUNCH0); + DOM2GODOT("LaunchApp2", LAUNCH1); + DOM2GODOT("LaunchMail", LAUNCHMAIL); + DOM2GODOT("MediaPlayPause", MEDIAPLAY); + DOM2GODOT("MediaSelect", LAUNCHMEDIA); + DOM2GODOT("MediaStop", MEDIASTOP); + DOM2GODOT("MediaTrackNext", MEDIANEXT); + DOM2GODOT("MediaTrackPrevious", MEDIAPREVIOUS); + //DOM2GODOT("Power", UNKNOWN); + //DOM2GODOT("Sleep", UNKNOWN); + DOM2GODOT("AudioVolumeDown", VOLUMEDOWN); + DOM2GODOT("AudioVolumeMute", VOLUMEMUTE); + DOM2GODOT("AudioVolumeUp", VOLUMEUP); + //DOM2GODOT("WakeUp", UNKNOWN); + return KEY_UNKNOWN; +#undef DOM2GODOT } diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py index a55c9d3f48..cc874c432e 100644 --- a/platform/javascript/emscripten_helpers.py +++ b/platform/javascript/emscripten_helpers.py @@ -1,28 +1,11 @@ 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 +from SCons.Util import WhereIs def run_closure_compiler(target, source, env, for_signature): - cfg = parse_config() - cmd = [cfg["NODE_JS"], cfg["CLOSURE_BIN"]] + closure_bin = os.path.join(os.path.dirname(WhereIs("emcc")), "node_modules", ".bin", "google-closure-compiler") + cmd = [WhereIs("node"), closure_bin] cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"]) for f in env["JSEXTERNS"]: cmd.extend(["--externs", f.get_abspath()]) @@ -36,3 +19,9 @@ 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]) + + +def add_js_libraries(env, libraries): + if "JS_LIBS" not in env: + env["JS_LIBS"] = [] + env.Append(JS_LIBS=env.File(libraries)) diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js index 6d7509377f..3745e04479 100644 --- a/platform/javascript/engine/engine.js +++ b/platform/javascript/engine/engine.js @@ -1,16 +1,8 @@ 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 wasmExt = '.wasm'; + var unloadAfterInit = true; var loadPath = ''; var loadPromise = null; var initPromise = null; @@ -33,31 +25,46 @@ Function('return this')()['Engine'] = (function() { }; /** @constructor */ - function Engine() {}; + function Engine() { + this.canvas = null; + this.executableName = ''; + this.rtenv = null; + this.customLocale = null; + this.resizeCanvasOnStart = false; + this.onExecute = null; + this.onExit = null; + this.persistentPaths = ['/userfs']; + }; Engine.prototype.init = /** @param {string=} basePath */ function(basePath) { if (initPromise) { return initPromise; } - if (!loadPromise) { + if (loadPromise == null) { 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 = {} + 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(); + var me = this; + initPromise = new Promise(function(resolve, reject) { + config['locateFile'] = Utils.createLocateRewrite(loadPath); + config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise); + Godot(config).then(function(module) { + module['initFS'](me.persistentPaths).then(function(fs_err) { + me.rtenv = module; + if (unloadAfterInit) { + unload(); + } + resolve(); + config = null; + }); }); }); return initPromise; @@ -76,33 +83,85 @@ Function('return this')()['Engine'] = (function() { 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(); + return me.init().then(function() { + if (!me.rtenv) { + return Promise.reject(new Error('The engine must be initialized before it can be started')); + } + + if (!(me.canvas instanceof HTMLCanvasElement)) { + me.canvas = Utils.findCanvas(); + } + + // Canvas can grab focus on click, or key events won't work. + if (me.canvas.tabIndex < 0) { + me.canvas.tabIndex = 0; + } + + // Disable right-click context menu. + me.canvas.addEventListener('contextmenu', function(ev) { + ev.preventDefault(); + }, false); + + // Until context restoration is implemented warn the user of context loss. + me.canvas.addEventListener('webglcontextlost', function(ev) { + alert("WebGL context lost, please reload the page"); + ev.preventDefault(); + }, false); + + // Browser locale, or custom one if defined. + var locale = me.customLocale; + if (!locale) { + locale = navigator.languages ? navigator.languages[0] : navigator.language; + locale = locale.split('.')[0]; + } + // Emscripten configuration. + me.rtenv['thisProgram'] = me.executableName; + me.rtenv['noExitRuntime'] = true; + // Godot configuration. + me.rtenv['initConfig']({ + 'resizeCanvasOnStart': me.resizeCanvasOnStart, + 'canvas': me.canvas, + 'locale': locale, + 'onExecute': function(p_args) { + if (me.onExecute) { + me.onExecute(p_args); + return 0; + } + return 1; + }, + 'onExit': function(p_code) { + me.rtenv['deinitFS'](); + if (me.onExit) { + me.onExit(p_code); + } + me.rtenv = null; + }, + }); + + return new Promise(function(resolve, reject) { + preloader.preloadedFiles.forEach(function(file) { + me.rtenv['copyToFS'](file.path, file.buffer); }); + preloader.preloadedFiles.length = 0; // Clear memory + me.rtenv['callMain'](args); + initPromise = null; + resolve(); }); }); }; - Engine.prototype.startGame = function(execName, mainPack) { + Engine.prototype.startGame = function(execName, mainPack, extraArgs) { // Start and init with execName as loadPath if not inited. - executableName = execName; + this.executableName = execName; var me = this; return Promise.all([ this.init(execName), this.preloadFile(mainPack, mainPack) ]).then(function() { - return me.start('--main-pack', mainPack); + var args = ['--main-pack', mainPack]; + if (extraArgs) + args = args.concat(extraArgs); + return me.start.apply(me, args); }); }; @@ -118,67 +177,95 @@ Function('return this')()['Engine'] = (function() { }; Engine.prototype.setCanvas = function(canvasElem) { - canvas = canvasElem; + this.canvas = canvasElem; }; Engine.prototype.setCanvasResizedOnStart = function(enabled) { - resizeCanvasOnStart = enabled; + this.resizeCanvasOnStart = enabled; }; Engine.prototype.setLocale = function(locale) { - customLocale = locale; + this.customLocale = locale; }; Engine.prototype.setExecutableName = function(newName) { - executableName = newName; + this.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; + if (this.rtenv) + this.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; + if (this.rtenv) + this.rtenv.printErr = printErr; stderr = printErr; }; + Engine.prototype.setOnExecute = function(onExecute) { + this.onExecute = onExecute; + }; + + Engine.prototype.setOnExit = function(onExit) { + this.onExit = onExit; + }; + + Engine.prototype.copyToFS = function(path, buffer) { + if (this.rtenv == null) { + throw new Error("Engine must be inited before copying files"); + } + this.rtenv['copyToFS'](path, buffer); + }; + + Engine.prototype.setPersistentPaths = function(persistentPaths) { + this.persistentPaths = persistentPaths; + }; + + Engine.prototype.requestQuit = function() { + if (this.rtenv) { + this.rtenv['request_quit'](); + } + }; + // 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 + 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; + Engine.prototype['setOnExecute'] = Engine.prototype.setOnExecute; + Engine.prototype['setOnExit'] = Engine.prototype.setOnExit; + Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; + Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths; + Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; return Engine; })(); diff --git a/platform/javascript/engine/loader.js b/platform/javascript/engine/loader.js deleted file mode 100644 index d27fbf612e..0000000000 --- a/platform/javascript/engine/loader.js +++ /dev/null @@ -1,33 +0,0 @@ -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 index 17918eae38..b3467d009f 100644 --- a/platform/javascript/engine/preloader.js +++ b/platform/javascript/engine/preloader.js @@ -1,5 +1,4 @@ var Preloader = /** @constructor */ function() { - var DOWNLOAD_ATTEMPTS_MAX = 4; var progressFunc = null; var lastProgress = { loaded: 0, total: 0 }; @@ -20,9 +19,7 @@ var Preloader = /** @constructor */ function() { } 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(); @@ -103,7 +100,6 @@ var Preloader = /** @constructor */ function() { }; var animateProgress = function() { - var loaded = 0; var total = 0; var totalIsValid = true; diff --git a/platform/javascript/engine/utils.js b/platform/javascript/engine/utils.js index fdff90a923..8455739a25 100644 --- a/platform/javascript/engine/utils.js +++ b/platform/javascript/engine/utils.js @@ -1,9 +1,10 @@ var Utils = { - createLocateRewrite: function(execName) { function rw(path) { if (path.endsWith('.worker.js')) { return execName + '.worker.js'; + } else if (path.endsWith('.audio.worklet.js')) { + return execName + '.audio.worklet.js'; } else if (path.endsWith('.js')) { return execName + '.js'; } else if (path.endsWith('.wasm')) { @@ -27,24 +28,6 @@ var Utils = { 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) { @@ -54,7 +37,6 @@ var Utils = { }, isWebGLAvailable: function(majorVersion = 1) { - var testContext = false; try { var testCanvas = document.createElement('canvas'); diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 39faae2d17..d520931067 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -28,6 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#include "core/io/json.h" #include "core/io/tcp_server.h" #include "core/io/zip_io.h" #include "editor/editor_export.h" @@ -40,7 +41,6 @@ #define EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG "webassembly_debug.zip" class EditorHTTPServer : public Reference { - private: Ref<TCP_Server> server; Ref<StreamPeerTCP> connection; @@ -94,6 +94,9 @@ public: } else if (req[1] == basereq + ".js") { filepath += ".js"; ctype = "application/javascript"; + } else if (req[1] == basereq + ".audio.worklet.js") { + filepath += ".audio.worklet.js"; + ctype = "application/javascript"; } else if (req[1] == basereq + ".worker.js") { filepath += ".worker.js"; ctype = "application/javascript"; @@ -124,6 +127,9 @@ public: String s = "HTTP/1.1 200 OK\r\n"; s += "Connection: Close\r\n"; s += "Content-Type: " + ctype + "\r\n"; + s += "Access-Control-Allow-Origin: *\r\n"; + s += "Cross-Origin-Opener-Policy: same-origin\r\n"; + s += "Cross-Origin-Embedder-Policy: require-corp\r\n"; s += "\r\n"; CharString cs = s.utf8(); Error err = connection->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); @@ -148,11 +154,13 @@ public: } void poll() { - if (!server->is_listening()) + if (!server->is_listening()) { return; + } if (connection.is_null()) { - if (!server->is_connection_available()) + if (!server->is_connection_available()) { return; + } connection = server->take_connection(); time = OS::get_singleton()->get_ticks_usec(); } @@ -160,11 +168,11 @@ public: _clear_client(); return; } - if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) + if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) { return; + } while (true) { - char *r = (char *)req_buf; int l = req_pos - 1; if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { @@ -190,7 +198,6 @@ public: }; class EditorExportPlatformJavaScript : public EditorExportPlatform { - GDCLASS(EditorExportPlatformJavaScript, EditorExportPlatform); Ref<ImageTexture> logo; @@ -198,7 +205,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { Ref<ImageTexture> stop_icon; int menu_options; - void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug); + void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags); private: Ref<EditorHTTPServer> server; @@ -209,51 +216,57 @@ private: static void _server_thread_poll(void *data); public: - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features); - - virtual void get_export_options(List<ExportOption> *r_options); + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override; - virtual String get_name() const; - virtual String get_os_name() const; - virtual Ref<Texture2D> get_logo() const; + virtual void get_export_options(List<ExportOption> *r_options) override; - virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const; - virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); + virtual String get_name() const override; + virtual String get_os_name() const override; + virtual Ref<Texture2D> get_logo() const override; - virtual bool poll_export(); - virtual int get_options_count() const; - virtual String get_option_label(int p_index) const { return p_index ? TTR("Stop HTTP Server") : TTR("Run in Browser"); } - virtual String get_option_tooltip(int p_index) const { return p_index ? TTR("Stop HTTP Server") : TTR("Run exported HTML in the system's default browser."); } - virtual Ref<ImageTexture> get_option_icon(int p_index) const; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags); - virtual Ref<Texture2D> get_run_icon() const; + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - virtual void get_platform_features(List<String> *r_features) { + virtual bool poll_export() override; + virtual int get_options_count() const override; + virtual String get_option_label(int p_index) const override { return p_index ? TTR("Stop HTTP Server") : TTR("Run in Browser"); } + virtual String get_option_tooltip(int p_index) const override { return p_index ? TTR("Stop HTTP Server") : TTR("Run exported HTML in the system's default browser."); } + virtual Ref<ImageTexture> get_option_icon(int p_index) const override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override; + virtual Ref<Texture2D> get_run_icon() const override; + virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("web"); r_features->push_back(get_os_name()); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { } + String get_debug_protocol() const override { return "ws://"; } + EditorExportPlatformJavaScript(); ~EditorExportPlatformJavaScript(); }; -void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug) { - +void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags) { String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size()); String str_export; Vector<String> lines = str_template.split("\n"); + Vector<String> flags; + String flags_json; + gen_export_flags(flags, p_flags); + flags_json = JSON::print(flags); for (int i = 0; i < lines.size(); i++) { - String current_line = lines[i]; current_line = current_line.replace("$GODOT_BASENAME", p_name); + current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name")); current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); + current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false"); current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false"); + current_line = current_line.replace("$GODOT_ARGS", flags_json); str_export += current_line + "\n"; } @@ -265,7 +278,6 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re } void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { - if (p_preset->get("vram_texture_compression/for_desktop")) { r_features->push_back("s3tc"); } @@ -282,32 +294,28 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP } void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_options) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/full_window_size"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); } String EditorExportPlatformJavaScript::get_name() const { - return "HTML5"; } String EditorExportPlatformJavaScript::get_os_name() const { - return "HTML5"; } Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const { - return logo; } bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - String err; bool valid = false; @@ -342,14 +350,14 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p } } - if (!err.empty()) + if (!err.empty()) { r_error = err; + } return valid; } List<String> EditorExportPlatformJavaScript::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { - List<String> list; list.push_back("html"); return list; @@ -367,11 +375,11 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese template_path = template_path.strip_edges(); if (template_path == String()) { - - if (p_debug) + if (p_debug) { template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG); - else + } else { template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE); + } } if (!DirAccess::exists(p_path.get_base_dir())) { @@ -395,7 +403,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese unzFile pkg = unzOpen2(template_path.utf8().get_data(), &io); if (!pkg) { - EditorNode::get_singleton()->show_warning(TTR("Could not open template for export:") + "\n" + template_path); return ERR_FILE_NOT_FOUND; } @@ -425,22 +432,21 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese //write if (file == "godot.html") { - if (!custom_html.empty()) { continue; } - _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug); + _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags); file = p_path.get_file(); } 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") { + } else if (file == "godot.audio.worklet.js") { + file = p_path.get_file().get_basename() + ".audio.worklet.js"; + } else if (file == "godot.wasm") { file = p_path.get_file().get_basename() + ".wasm"; } @@ -458,7 +464,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese unzClose(pkg); if (!custom_html.empty()) { - FileAccess *f = FileAccess::open(custom_html, FileAccess::READ); if (!f) { EditorNode::get_singleton()->show_warning(TTR("Could not read custom HTML shell:") + "\n" + custom_html); @@ -468,7 +473,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese buf.resize(f->get_len()); f->get_buffer(buf.ptrw(), buf.size()); memdelete(f); - _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug); + _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags); f = FileAccess::open(p_path, FileAccess::WRITE); if (!f) { @@ -522,11 +527,9 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese } bool EditorExportPlatformJavaScript::poll_export() { - Ref<EditorExportPreset> preset; for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { - Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i); if (ep->is_runnable() && ep->get_platform() == this) { preset = ep; @@ -552,12 +555,10 @@ Ref<ImageTexture> EditorExportPlatformJavaScript::get_option_icon(int p_index) c } int EditorExportPlatformJavaScript::get_options_count() const { - return menu_options; } Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) { - if (p_option == 1) { MutexLock lock(server_lock); server->stop(); @@ -571,6 +572,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese 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 + ".audio.worklet.js"); DirAccess::remove_file_or_error(basepath + ".pck"); DirAccess::remove_file_or_error(basepath + ".png"); DirAccess::remove_file_or_error(basepath + ".wasm"); @@ -605,7 +607,6 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese } Ref<Texture2D> EditorExportPlatformJavaScript::get_run_icon() const { - return run_icon; } @@ -621,7 +622,6 @@ void EditorExportPlatformJavaScript::_server_thread_poll(void *data) { } EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { - server.instance(); server_quit = false; server_thread = Thread::create(_server_thread_poll, this); @@ -635,10 +635,11 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { run_icon->create_from_image(img); Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); - if (theme.is_valid()) + if (theme.is_valid()) { stop_icon = theme->get_icon("Stop", "EditorIcons"); - else + } else { stop_icon.instance(); + } menu_options = 0; } @@ -651,7 +652,6 @@ EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() { } void register_javascript_exporter() { - EDITOR_DEF("export/web/http_host", "localhost"); EDITOR_DEF("export/web/http_port", 8060); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/web/http_port", PROPERTY_HINT_RANGE, "1,65535,1")); diff --git a/platform/haiku/audio_driver_media_kit.h b/platform/javascript/godot_audio.h index 8272780fa7..7ebda3ad39 100644 --- a/platform/haiku/audio_driver_media_kit.h +++ b/platform/javascript/godot_audio.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* audio_driver_media_kit.h */ +/* godot_audio.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,47 +28,36 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "servers/audio_server.h" +#ifndef GODOT_AUDIO_H +#define GODOT_AUDIO_H -#ifdef MEDIA_KIT_ENABLED - -#include "core/os/mutex.h" -#include "core/os/thread.h" - -#include <kernel/image.h> // needed for image_id - -#include <SoundPlayer.h> - -class AudioDriverMediaKit : public AudioDriver { - Mutex mutex; - - BSoundPlayer *player; - static int32_t *samples_in; - - static void PlayBuffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format &format); +#ifdef __cplusplus +extern "C" { +#endif - unsigned int mix_rate; - SpeakerMode speaker_mode; - unsigned int buffer_size; - int channels; +#include "stddef.h" - bool active; +extern int godot_audio_is_available(); +extern int godot_audio_init(int p_mix_rate, int p_latency, void (*_state_cb)(int), void (*_latency_cb)(float)); +extern void godot_audio_resume(); -public: - const char *get_name() const { - return "MediaKit"; - }; +extern void godot_audio_capture_start(); +extern void godot_audio_capture_stop(); - virtual Error init(); - virtual void start(); - virtual int get_mix_rate() const; - virtual SpeakerMode get_speaker_mode() const; - virtual void lock(); - virtual void unlock(); - virtual void finish(); +// Worklet +typedef int32_t GodotAudioState[4]; +extern void godot_audio_worklet_create(int p_channels); +extern void godot_audio_worklet_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, GodotAudioState p_state); +extern void godot_audio_worklet_state_add(GodotAudioState p_state, int p_idx, int p_value); +extern int godot_audio_worklet_state_get(GodotAudioState p_state, int p_idx); +extern int godot_audio_worklet_state_wait(int32_t *p_state, int p_idx, int32_t p_expected, int p_timeout); - AudioDriverMediaKit(); - ~AudioDriverMediaKit(); -}; +// Script +extern int godot_audio_script_create(int p_buffer_size, int p_channels); +extern void godot_audio_script_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, void (*p_cb)()); +#ifdef __cplusplus +} #endif + +#endif /* GODOT_AUDIO_H */ diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h new file mode 100644 index 0000000000..23596a0897 --- /dev/null +++ b/platform/javascript/godot_js.h @@ -0,0 +1,87 @@ +/*************************************************************************/ +/* godot_js.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 GODOT_JS_H +#define GODOT_JS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stddef.h" + +// Config +extern void godot_js_config_locale_get(char *p_ptr, int p_ptr_max); +extern void godot_js_config_canvas_id_get(char *p_ptr, int p_ptr_max); +extern int godot_js_config_is_resize_on_start(); + +// OS +extern void godot_js_os_finish_async(void (*p_callback)()); +extern void godot_js_os_request_quit_cb(void (*p_callback)()); +extern int godot_js_os_fs_is_persistent(); +extern void godot_js_os_fs_sync(void (*p_callback)()); +extern int godot_js_os_execute(const char *p_json); +extern void godot_js_os_shell_open(const char *p_uri); + +// Display +extern double godot_js_display_pixel_ratio_get(); +extern void godot_js_display_alert(const char *p_text); +extern int godot_js_display_touchscreen_is_available(); +extern int godot_js_display_is_swap_ok_cancel(); + +// Display canvas +extern void godot_js_display_canvas_focus(); +extern int godot_js_display_canvas_is_focused(); +extern void godot_js_display_canvas_bounding_rect_position_get(int32_t *p_x, int32_t *p_y); + +// Display window +extern void godot_js_display_window_request_fullscreen(); +extern void godot_js_display_window_title_set(const char *p_text); +extern void godot_js_display_window_icon_set(const uint8_t *p_ptr, int p_len); + +// Display clipboard +extern int godot_js_display_clipboard_set(const char *p_text); +extern int godot_js_display_clipboard_get(void (*p_callback)(const char *p_text)); + +// Display cursor +extern void godot_js_display_cursor_set_shape(const char *p_cursor); +extern int godot_js_display_cursor_is_hidden(); +extern void godot_js_display_cursor_set_custom_shape(const char *p_shape, const uint8_t *p_ptr, int p_len, int p_hotspot_x, int p_hotspot_y); +extern void godot_js_display_cursor_set_visible(int p_visible); + +// Display listeners +extern void godot_js_display_notification_cb(void (*p_callback)(int p_notification), int p_enter, int p_exit, int p_in, int p_out); +extern void godot_js_display_paste_cb(void (*p_callback)(const char *p_text)); +extern void godot_js_display_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec)); +#ifdef __cplusplus +} +#endif + +#endif /* GODOT_JS_H */ diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc index ac275aadbc..4d5ff88bdd 100644 --- a/platform/javascript/http_client.h.inc +++ b/platform/javascript/http_client.h.inc @@ -33,21 +33,21 @@ Error prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers); int xhr_id; -int read_limit; -int response_read_offset; -Status status; +int read_limit = 4096; +int response_read_offset = 0; +Status status = STATUS_DISCONNECTED; String host; -int port; -bool use_tls; +int port = -1; +bool use_tls = false; String username; String password; -int polled_response_code; +int polled_response_code = 0; String polled_response_header; PackedByteArray polled_response; #ifdef DEBUG_ENABLED -bool has_polled; -uint64_t last_polling_frame; +bool has_polled = false; +uint64_t last_polling_frame = 0; #endif diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index 863c207896..cb0e48b8a9 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -29,10 +29,10 @@ /*************************************************************************/ #include "core/io/http_client.h" + #include "http_request.h" Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { - close(); if (p_ssl && !p_verify_host) { WARN_PRINT("Disabling HTTPClient's host verification is not supported for the HTML5 platform, host will be verified"); @@ -67,17 +67,14 @@ Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, } void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) { - ERR_FAIL_MSG("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform."); } Ref<StreamPeer> HTTPClient::get_connection() const { - ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform."); } Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers) { - ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the HTML5 platform."); ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); @@ -104,7 +101,6 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve } Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) { - Error err = prepare_request(p_method, p_url, p_headers); if (err != OK) return err; @@ -113,7 +109,6 @@ Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector } Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) { - Error err = prepare_request(p_method, p_url, p_headers); if (err != OK) return err; @@ -122,7 +117,6 @@ Error HTTPClient::request(Method p_method, const String &p_url, const Vector<Str } void HTTPClient::close() { - host = ""; port = -1; use_tls = false; @@ -134,28 +128,23 @@ void HTTPClient::close() { } HTTPClient::Status HTTPClient::get_status() const { - return status; } bool HTTPClient::has_response() const { - return !polled_response_header.empty(); } bool HTTPClient::is_response_chunked() const { - // TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream return false; } int HTTPClient::get_response_code() const { - return polled_response_code; } Error HTTPClient::get_response_headers(List<String> *r_response) { - if (polled_response_header.empty()) return ERR_INVALID_PARAMETER; @@ -168,12 +157,10 @@ Error HTTPClient::get_response_headers(List<String> *r_response) { } int HTTPClient::get_response_body_length() const { - return polled_response.size(); } PackedByteArray HTTPClient::read_response_body_chunk() { - ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); int to_read = MIN(read_limit, polled_response.size() - response_read_offset); @@ -192,17 +179,14 @@ PackedByteArray HTTPClient::read_response_body_chunk() { } void HTTPClient::set_blocking_mode(bool p_enable) { - ERR_FAIL_COND_MSG(p_enable, "HTTPClient blocking mode is not supported for the HTML5 platform."); } bool HTTPClient::is_blocking_mode_enabled() const { - return false; } void HTTPClient::set_read_chunk_size(int p_size) { - read_limit = p_size; } @@ -211,9 +195,7 @@ int HTTPClient::get_read_chunk_size() const { } Error HTTPClient::poll() { - switch (status) { - case STATUS_DISCONNECTED: return ERR_UNCONFIGURED; @@ -233,7 +215,6 @@ Error HTTPClient::poll() { return ERR_CONNECTION_ERROR; case STATUS_REQUESTING: { - #ifdef DEBUG_ENABLED if (!has_polled) { has_polled = true; @@ -279,20 +260,9 @@ Error HTTPClient::poll() { } HTTPClient::HTTPClient() { - xhr_id = godot_xhr_new(); - read_limit = 4096; - status = STATUS_DISCONNECTED; - port = -1; - use_tls = false; - polled_response_code = 0; -#ifdef DEBUG_ENABLED - has_polled = false; - last_polling_frame = 0; -#endif } HTTPClient::~HTTPClient() { - godot_xhr_free(xhr_id); } diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp index db8050b90e..b203253a39 100644 --- a/platform/javascript/javascript_eval.cpp +++ b/platform/javascript/javascript_eval.cpp @@ -33,97 +33,30 @@ #include "api/javascript_eval.h" #include "emscripten.h" -extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_PackedByteArray_and_open_write(PackedByteArray *p_arr, VectorWriteProxy<uint8_t> *r_write, int p_len) { +extern "C" { +union js_eval_ret { + uint32_t b; + double d; + char *s; +}; + +extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len)); +} - p_arr->resize(p_len); - *r_write = p_arr->write; - return p_arr->ptrw(); +void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) { + PackedByteArray *arr = (PackedByteArray *)p_arr; + VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write; + arr->resize(p_len); + *write = arr->write; + return arr->ptrw(); } Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { - - union { - bool b; - double d; - char *s; - } js_data; - + union js_eval_ret js_data; PackedByteArray arr; VectorWriteProxy<uint8_t> arr_write; - /* clang-format off */ - Variant::Type return_type = static_cast<Variant::Type>(EM_ASM_INT({ - - const CODE = $0; - const USE_GLOBAL_EXEC_CONTEXT = $1; - const PTR = $2; - const BYTEARRAY_PTR = $3; - const BYTEARRAY_WRITE_PTR = $4; - var eval_ret; - try { - if (USE_GLOBAL_EXEC_CONTEXT) { - // indirect eval call grants global execution context - var global_eval = eval; - eval_ret = global_eval(UTF8ToString(CODE)); - } else { - eval_ret = eval(UTF8ToString(CODE)); - } - } catch (e) { - err(e); - eval_ret = null; - } - - switch (typeof eval_ret) { - - case 'boolean': - setValue(PTR, eval_ret, 'i32'); - return 1; // BOOL - - case 'number': - setValue(PTR, eval_ret, 'double'); - return 3; // FLOAT - - case 'string': - var array_len = lengthBytesUTF8(eval_ret)+1; - var array_ptr = _malloc(array_len); - try { - if (array_ptr===0) { - throw new Error('String allocation failed (probably out of memory)'); - } - setValue(PTR, array_ptr , '*'); - stringToUTF8(eval_ret, array_ptr, array_len); - return 4; // STRING - } catch (e) { - if (array_ptr!==0) { - _free(array_ptr) - } - err(e); - // fall through - } - break; - - case 'object': - if (eval_ret === null) { - break; - } - - if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { - eval_ret = new Uint8Array(eval_ret.buffer); - } - else if (eval_ret instanceof ArrayBuffer) { - eval_ret = new Uint8Array(eval_ret); - } - if (eval_ret instanceof Uint8Array) { - var bytes_ptr = ccall('resize_PackedByteArray_and_open_write', 'number', ['number', 'number' ,'number'], [BYTEARRAY_PTR, BYTEARRAY_WRITE_PTR, eval_ret.length]); - HEAPU8.set(eval_ret, bytes_ptr); - return 20; // PACKED_BYTE_ARRAY - } - break; - } - return 0; // NIL - - }, p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write)); - /* clang-format on */ + Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write)); switch (return_type) { case Variant::BOOL: @@ -132,9 +65,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { return js_data.d; case Variant::STRING: { String str = String::utf8(js_data.s); - /* clang-format off */ - EM_ASM_({ _free($0); }, js_data.s); - /* clang-format on */ + free(js_data.s); // Must free the string allocated in JS. return str; } case Variant::PACKED_BYTE_ARRAY: diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 815bc7e456..2d28a63566 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -30,42 +30,68 @@ #include "core/io/resource_loader.h" #include "main/main.h" -#include "os_javascript.h" +#include "platform/javascript/display_server_javascript.h" +#include "platform/javascript/os_javascript.h" #include <emscripten/emscripten.h> +#include <stdlib.h> -extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) { +#include "godot_js.h" - String idbfs_err = String::utf8(p_idbfs_err); - if (!idbfs_err.empty()) { - print_line("IndexedDB not available: " + idbfs_err); +static OS_JavaScript *os = nullptr; +static uint64_t target_ticks = 0; + +void exit_callback() { + emscripten_cancel_main_loop(); // After this, we can exit! + Main::cleanup(); + int exit_code = OS_JavaScript::get_singleton()->get_exit_code(); + memdelete(os); + os = nullptr; + emscripten_force_exit(exit_code); // No matter that we call cancel_main_loop, regular "exit" will not work, forcing. +} + +void cleanup_after_sync() { + emscripten_set_main_loop(exit_callback, -1, false); +} + +void main_loop_callback() { + uint64_t current_ticks = os->get_ticks_usec(); + + bool force_draw = DisplayServerJavaScript::get_singleton()->check_size_force_redraw(); + if (force_draw) { + Main::force_redraw(); + } else if (current_ticks < target_ticks) { + return; // Skip frame. + } + + int target_fps = Engine::get_singleton()->get_target_fps(); + if (target_fps > 0) { + target_ticks += (uint64_t)(1000000 / target_fps); + } + if (os->main_loop_iterate()) { + emscripten_cancel_main_loop(); // Cancel current loop and wait for cleanup_after_sync. + godot_js_os_finish_async(cleanup_after_sync); } - OS_JavaScript *os = OS_JavaScript::get_singleton(); - os->set_idb_available(idbfs_err.empty()); - // Ease up compatibility. - ResourceLoader::set_abort_on_missing_resources(false); - Main::start(); - os->run_async(); } +/// When calling main, it is assumed FS is setup and synced. int main(int argc, char *argv[]) { + os = new OS_JavaScript(); - // Sync from persistent state into memory and then - // run the 'main_after_fs_sync' function. - /* clang-format off */ - EM_ASM( - FS.mkdir('/userfs'); - FS.mount(IDBFS, {}, '/userfs'); - FS.syncfs(true, function(err) { - ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]) - }); - ); - /* clang-format on */ + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE - new OS_JavaScript(argc, argv); - // TODO: Check error return value. Main::setup(argv[0], argc - 1, &argv[1]); + // Ease up compatibility. + ResourceLoader::set_abort_on_missing_resources(false); + + Main::start(); + os->get_main_loop()->init(); + emscripten_set_main_loop(main_loop_callback, -1, false); + // Immediately run the first iteration. + // We are inside an animation frame, we want to immediately draw on the newly setup canvas. + main_loop_callback(); + return 0; - // Continued async in main_after_fs_sync() from the syncfs() callback. } diff --git a/platform/javascript/native/audio.worklet.js b/platform/javascript/native/audio.worklet.js new file mode 100644 index 0000000000..320af6189c --- /dev/null +++ b/platform/javascript/native/audio.worklet.js @@ -0,0 +1,185 @@ +/*************************************************************************/ +/* audio.worklet.js */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ +class RingBuffer { + constructor(p_buffer, p_state) { + this.buffer = p_buffer; + this.avail = p_state; + this.rpos = 0; + this.wpos = 0; + } + + data_left() { + return Atomics.load(this.avail, 0); + } + + space_left() { + return this.buffer.length - this.data_left(); + } + + read(output) { + const size = this.buffer.length; + let from = 0 + let to_write = output.length; + if (this.rpos + to_write > size) { + const high = size - this.rpos; + output.set(this.buffer.subarray(this.rpos, size)); + from = high; + to_write -= high; + this.rpos = 0; + } + output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); + this.rpos += to_write; + Atomics.add(this.avail, 0, -output.length); + Atomics.notify(this.avail, 0); + } + + write(p_buffer) { + const to_write = p_buffer.length; + const mw = this.buffer.length - this.wpos; + if (mw >= to_write) { + this.buffer.set(p_buffer, this.wpos); + } else { + const high = p_buffer.subarray(0, to_write - mw); + const low = p_buffer.subarray(to_write - mw); + this.buffer.set(high, this.wpos); + this.buffer.set(low); + } + let diff = to_write; + if (this.wpos + diff >= this.buffer.length) { + diff -= this.buffer.length; + } + this.wpos += diff; + Atomics.add(this.avail, 0, to_write); + Atomics.notify(this.avail, 0); + } +} + +class GodotProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.running = true; + this.lock = null; + this.notifier = null; + this.output = null; + this.output_buffer = new Float32Array(); + this.input = null; + this.input_buffer = new Float32Array(); + this.port.onmessage = (event) => { + const cmd = event.data['cmd']; + const data = event.data['data']; + this.parse_message(cmd, data); + }; + } + + process_notify() { + Atomics.add(this.notifier, 0, 1); + Atomics.notify(this.notifier, 0); + } + + parse_message(p_cmd, p_data) { + if (p_cmd == "start" && p_data) { + const state = p_data[0]; + let idx = 0; + this.lock = state.subarray(idx, ++idx); + this.notifier = state.subarray(idx, ++idx); + const avail_in = state.subarray(idx, ++idx); + const avail_out = state.subarray(idx, ++idx); + this.input = new RingBuffer(p_data[1], avail_in); + this.output = new RingBuffer(p_data[2], avail_out); + } else if (p_cmd == "stop") { + this.runing = false; + this.output = null; + this.input = null; + } + } + + array_has_data(arr) { + return arr.length && arr[0].length && arr[0][0].length; + } + + process(inputs, outputs, parameters) { + if (!this.running) { + return false; // Stop processing. + } + if (this.output === null) { + return true; // Not ready yet, keep processing. + } + const process_input = this.array_has_data(inputs); + if (process_input) { + const input = inputs[0]; + const chunk = input[0].length * input.length; + if (this.input_buffer.length != chunk) { + this.input_buffer = new Float32Array(chunk); + } + if (this.input.space_left() >= chunk) { + this.write_input(this.input_buffer, input); + this.input.write(this.input_buffer); + } else { + this.port.postMessage("Input buffer is full! Skipping input frame."); + } + } + const process_output = this.array_has_data(outputs); + if (process_output) { + const output = outputs[0]; + const chunk = output[0].length * output.length; + if (this.output_buffer.length != chunk) { + this.output_buffer = new Float32Array(chunk) + } + if (this.output.data_left() >= chunk) { + this.output.read(this.output_buffer); + this.write_output(output, this.output_buffer); + } else { + this.port.postMessage("Output buffer has not enough frames! Skipping output frame."); + } + } + this.process_notify(); + return true; + } + + write_output(dest, source) { + const channels = dest.length; + for (let ch = 0; ch < channels; ch++) { + for (let sample = 0; sample < dest[ch].length; sample++) { + dest[ch][sample] = source[sample * channels + ch]; + } + } + } + + write_input(dest, source) { + const channels = source.length; + for (let ch = 0; ch < channels; ch++) { + for (let sample = 0; sample < source[ch].length; sample++) { + dest[sample * channels + ch] = source[ch][sample]; + } + } + } +} + +registerProcessor('godot-processor', GodotProcessor); diff --git a/platform/javascript/http_request.js b/platform/javascript/native/http_request.js index f621689f9d..272154aee3 100644 --- a/platform/javascript/http_request.js +++ b/platform/javascript/native/http_request.js @@ -28,9 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ var GodotHTTPRequest = { - $GodotHTTPRequest: { - requests: [], getUnusedRequestId: function() { diff --git a/platform/javascript/native/library_godot_audio.js b/platform/javascript/native/library_godot_audio.js new file mode 100644 index 0000000000..3a0c8f297a --- /dev/null +++ b/platform/javascript/native/library_godot_audio.js @@ -0,0 +1,338 @@ +/*************************************************************************/ +/* library_godot_audio.js */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +const GodotAudio = { + $GodotAudio__deps: ['$GodotOS'], + $GodotAudio: { + ctx: null, + input: null, + driver: null, + interval: 0, + + init: function(mix_rate, latency, onstatechange, onlatencyupdate) { + const ctx = new (window.AudioContext || window.webkitAudioContext)({ + sampleRate: mix_rate, + // latencyHint: latency / 1000 // Do not specify, leave 'interactive' for good performance. + }); + GodotAudio.ctx = ctx; + onstatechange(ctx.state); // Immeditately notify state. + ctx.onstatechange = function() { + let state = 0; + switch (ctx.state) { + case 'suspended': + state = 0; + break; + case 'running': + state = 1; + break; + case 'closed': + state = 2; + break; + } + onstatechange(state); + } + // Update computed latency + GodotAudio.interval = setInterval(function() { + let latency = 0; + if (ctx.baseLatency) { + latency += GodotAudio.ctx.baseLatency; + } + if (ctx.outputLatency) { + latency += GodotAudio.ctx.outputLatency; + } + onlatencyupdate(latency); + }, 1000); + GodotOS.atexit(GodotAudio.close_async); + return ctx.destination.channelCount; + }, + + create_input: function(callback) { + if (GodotAudio.input) { + return; // Already started. + } + function gotMediaInput(stream) { + GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream); + callback(GodotAudio.input) + } + if (navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia({ + "audio": true + }).then(gotMediaInput, function(e) { out(e) }); + } else { + if (!navigator.getUserMedia) { + navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + } + navigator.getUserMedia({ + "audio": true + }, gotMediaInput, function(e) { out(e) }); + } + }, + + close_async: function(resolve, reject) { + const ctx = GodotAudio.ctx; + GodotAudio.ctx = null; + // Audio was not initialized. + if (!ctx) { + resolve(); + return; + } + // Remove latency callback + if (GodotAudio.interval) { + clearInterval(GodotAudio.interval); + GodotAudio.interval = 0; + } + // Disconnect input, if it was started. + if (GodotAudio.input) { + GodotAudio.input.disconnect(); + GodotAudio.input = null; + } + // Disconnect output + let closed = Promise.resolve(); + if (GodotAudio.driver) { + closed = GodotAudio.driver.close(); + } + closed.then(function() { + return ctx.close(); + }).then(function() { + ctx.onstatechange = null; + resolve(); + }).catch(function(e) { + ctx.onstatechange = null; + console.error("Error closing AudioContext", e); + resolve(); + }); + }, + }, + + godot_audio_is_available__proxy: 'sync', + godot_audio_is_available: function () { + if (!(window.AudioContext || window.webkitAudioContext)) { + return 0; + } + return 1; + }, + + godot_audio_init: function(p_mix_rate, p_latency, p_state_change, p_latency_update) { + const statechange = GodotOS.get_func(p_state_change); + const latencyupdate = GodotOS.get_func(p_latency_update); + return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate); + }, + + godot_audio_resume: function() { + if (GodotAudio.ctx && GodotAudio.ctx.state != 'running') { + GodotAudio.ctx.resume(); + } + }, + + godot_audio_capture_start__proxy: 'sync', + godot_audio_capture_start: function() { + if (GodotAudio.input) { + return; // Already started. + } + GodotAudio.create_input(function(input) { + input.connect(GodotAudio.driver.get_node()); + }); + }, + + godot_audio_capture_stop__proxy: 'sync', + godot_audio_capture_stop: function() { + if (GodotAudio.input) { + const tracks = GodotAudio.input['mediaStream']['getTracks'](); + for (let i = 0; i < tracks.length; i++) { + tracks[i]['stop'](); + } + GodotAudio.input.disconnect(); + GodotAudio.input = null; + } + }, +}; + +autoAddDeps(GodotAudio, "$GodotAudio"); +mergeInto(LibraryManager.library, GodotAudio); + +/** + * The AudioWorklet API driver, used when threads are available. + */ +const GodotAudioWorklet = { + $GodotAudioWorklet__deps: ['$GodotAudio'], + $GodotAudioWorklet: { + promise: null, + worklet: null, + + create: function(channels) { + const path = Module['locateFile']('godot.audio.worklet.js'); + GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet.addModule(path).then(function() { + GodotAudioWorklet.worklet = new AudioWorkletNode( + GodotAudio.ctx, + 'godot-processor', + { + 'outputChannelCount': [channels] + } + ); + return Promise.resolve(); + }); + GodotAudio.driver = GodotAudioWorklet; + }, + + start: function(in_buf, out_buf, state) { + GodotAudioWorklet.promise.then(function() { + const node = GodotAudioWorklet.worklet; + node.connect(GodotAudio.ctx.destination); + node.port.postMessage({ + 'cmd': 'start', + 'data': [state, in_buf, out_buf], + }); + node.port.onmessage = function(event) { + console.error(event.data); + }; + }); + }, + + get_node: function() { + return GodotAudioWorklet.worklet; + }, + + close: function() { + return new Promise(function(resolve, reject) { + GodotAudioWorklet.promise.then(function() { + GodotAudioWorklet.worklet.port.postMessage({ + 'cmd': 'stop', + 'data': null, + }); + GodotAudioWorklet.worklet.disconnect(); + GodotAudioWorklet.worklet = null; + GodotAudioWorklet.promise = null; + resolve(); + }); + }); + }, + }, + + godot_audio_worklet_create: function(channels) { + GodotAudioWorklet.create(channels); + }, + + godot_audio_worklet_start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) { + const out_buffer = GodotOS.heapSub(HEAPF32, p_out_buf, p_out_size); + const in_buffer = GodotOS.heapSub(HEAPF32, p_in_buf, p_in_size); + const state = GodotOS.heapSub(HEAP32, p_state, 4); + GodotAudioWorklet.start(in_buffer, out_buffer, state); + }, + + godot_audio_worklet_state_wait: function(p_state, p_idx, p_expected, p_timeout) { + Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout); + return Atomics.load(HEAP32, (p_state >> 2) + p_idx); + }, + + godot_audio_worklet_state_add: function(p_state, p_idx, p_value) { + return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value); + }, + + godot_audio_worklet_state_get: function(p_state, p_idx) { + return Atomics.load(HEAP32, (p_state >> 2) + p_idx); + }, +}; + +autoAddDeps(GodotAudioWorklet, "$GodotAudioWorklet"); +mergeInto(LibraryManager.library, GodotAudioWorklet); + +/* + * The deprecated ScriptProcessorNode API, used when threads are disabled. + */ +const GodotAudioScript = { + $GodotAudioScript__deps: ['$GodotAudio'], + $GodotAudioScript: { + script: null, + + create: function(buffer_length, channel_count) { + GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count); + GodotAudio.driver = GodotAudioScript; + return GodotAudioScript.script.bufferSize; + }, + + start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) { + GodotAudioScript.script.onaudioprocess = function(event) { + // Read input + const inb = GodotOS.heapSub(HEAPF32, p_in_buf, p_in_size); + const input = event.inputBuffer; + if (GodotAudio.input) { + const inlen = input.getChannelData(0).length; + for (let ch = 0; ch < 2; ch++) { + const data = input.getChannelData(ch); + for (let s = 0; s < inlen; s++) { + inb[s * 2 + ch] = data[s]; + } + } + } + + // Let Godot process the input/output. + onprocess(); + + // Write the output. + const outb = GodotOS.heapSub(HEAPF32, p_out_buf, p_out_size); + const output = event.outputBuffer; + const channels = output.numberOfChannels; + for (let ch = 0; ch < channels; ch++) { + const data = output.getChannelData(ch); + // Loop through samples and assign computed values. + for (let sample = 0; sample < data.length; sample++) { + data[sample] = outb[sample * channels + ch]; + } + } + }; + GodotAudioScript.script.connect(GodotAudio.ctx.destination); + }, + + get_node: function() { + return GodotAudioScript.script; + }, + + close: function() { + return new Promise(function(resolve, reject) { + GodotAudioScript.script.disconnect(); + GodotAudioScript.script.onaudioprocess = null; + GodotAudioScript.script = null; + resolve(); + }); + }, + }, + + godot_audio_script_create: function(buffer_length, channel_count) { + return GodotAudioScript.create(buffer_length, channel_count); + }, + + godot_audio_script_start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) { + const onprocess = GodotOS.get_func(p_cb); + GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess); + }, +}; + +autoAddDeps(GodotAudioScript, "$GodotAudioScript"); +mergeInto(LibraryManager.library, GodotAudioScript); diff --git a/platform/javascript/native/library_godot_display.js b/platform/javascript/native/library_godot_display.js new file mode 100644 index 0000000000..11bbfbc60d --- /dev/null +++ b/platform/javascript/native/library_godot_display.js @@ -0,0 +1,477 @@ +/*************************************************************************/ +/* library_godot_display.js */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +/* + * Display Server listeners. + * Keeps track of registered event listeners so it can remove them on shutdown. + */ +const GodotDisplayListeners = { + $GodotDisplayListeners__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayListeners.clear(); resolve(); });', + $GodotDisplayListeners: { + handlers: [], + + has: function(target, event, method, capture) { + return GodotDisplayListeners.handlers.findIndex(function(e) { + return e.target === target && e.event === event && e.method === method && e.capture == capture; + }) !== -1; + }, + + add: function(target, event, method, capture) { + if (GodotDisplayListeners.has(target, event, method, capture)) { + return; + } + function Handler(target, event, method, capture) { + this.target = target; + this.event = event; + this.method = method; + this.capture = capture; + }; + GodotDisplayListeners.handlers.push(new Handler(target, event, method, capture)); + target.addEventListener(event, method, capture); + }, + + clear: function() { + GodotDisplayListeners.handlers.forEach(function(h) { + h.target.removeEventListener(h.event, h.method, h.capture); + }); + GodotDisplayListeners.handlers.length = 0; + }, + }, +}; +mergeInto(LibraryManager.library, GodotDisplayListeners); + +/* + * Drag and drop handler. + * This is pretty big, but basically detect dropped files on GodotConfig.canvas, + * process them one by one (recursively for directories), and copies them to + * the temporary FS path '/tmp/drop-[random]/' so it can be emitted as a godot + * event (that requires a string array of paths). + * + * NOTE: The temporary files are removed after the callback. This means that + * deferred callbacks won't be able to access the files. + */ +const GodotDisplayDragDrop = { + $GodotDisplayDragDrop__deps: ['$FS', '$GodotFS'], + $GodotDisplayDragDrop: { + promises: [], + pending_files: [], + + add_entry: function(entry) { + if (entry.isDirectory) { + GodotDisplayDragDrop.add_dir(entry); + } else if (entry.isFile) { + GodotDisplayDragDrop.add_file(entry); + } else { + console.error("Unrecognized entry...", entry); + } + }, + + add_dir: function(entry) { + GodotDisplayDragDrop.promises.push(new Promise(function(resolve, reject) { + const reader = entry.createReader(); + reader.readEntries(function(entries) { + for (let i = 0; i < entries.length; i++) { + GodotDisplayDragDrop.add_entry(entries[i]); + } + resolve(); + }); + })); + }, + + add_file: function(entry) { + GodotDisplayDragDrop.promises.push(new Promise(function(resolve, reject) { + entry.file(function(file) { + const reader = new FileReader(); + reader.onload = function() { + const f = { + "path": file.relativePath || file.webkitRelativePath, + "name": file.name, + "type": file.type, + "size": file.size, + "data": reader.result + }; + if (!f['path']) { + f['path'] = f['name']; + } + GodotDisplayDragDrop.pending_files.push(f); + resolve() + }; + reader.onerror = function() { + console.log("Error reading file"); + reject(); + } + reader.readAsArrayBuffer(file); + }, function(err) { + console.log("Error!"); + reject(); + }); + })); + }, + + process: function(resolve, reject) { + if (GodotDisplayDragDrop.promises.length == 0) { + resolve(); + return; + } + GodotDisplayDragDrop.promises.pop().then(function() { + setTimeout(function() { + GodotDisplayDragDrop.process(resolve, reject); + }, 0); + }); + }, + + _process_event: function(ev, callback) { + ev.preventDefault(); + if (ev.dataTransfer.items) { + // Use DataTransferItemList interface to access the file(s) + for (let i = 0; i < ev.dataTransfer.items.length; i++) { + const item = ev.dataTransfer.items[i]; + let entry = null; + if ("getAsEntry" in item) { + entry = item.getAsEntry(); + } else if ("webkitGetAsEntry" in item) { + entry = item.webkitGetAsEntry(); + } + if (entry) { + GodotDisplayDragDrop.add_entry(entry); + } + } + } else { + console.error("File upload not supported"); + } + new Promise(GodotDisplayDragDrop.process).then(function() { + const DROP = "/tmp/drop-" + parseInt(Math.random() * Math.pow(2, 31)) + "/"; + const drops = []; + const files = []; + FS.mkdir(DROP); + GodotDisplayDragDrop.pending_files.forEach((elem) => { + const path = elem['path']; + GodotFS.copy_to_fs(DROP + path, elem['data']); + let idx = path.indexOf("/"); + if (idx == -1) { + // Root file + drops.push(DROP + path); + } else { + // Subdir + const sub = path.substr(0, idx); + idx = sub.indexOf("/"); + if (idx < 0 && drops.indexOf(DROP + sub) == -1) { + drops.push(DROP + sub); + } + } + files.push(DROP + path); + }); + GodotDisplayDragDrop.promises = []; + GodotDisplayDragDrop.pending_files = []; + callback(drops); + const dirs = [DROP.substr(0, DROP.length -1)]; + // Remove temporary files + files.forEach(function (file) { + FS.unlink(file); + let dir = file.replace(DROP, ""); + let idx = dir.lastIndexOf("/"); + while (idx > 0) { + dir = dir.substr(0, idx); + if (dirs.indexOf(DROP + dir) == -1) { + dirs.push(DROP + dir); + } + idx = dir.lastIndexOf("/"); + } + }); + // Remove dirs. + dirs.sort(function(a, b) { + const al = (a.match(/\//g) || []).length; + const bl = (b.match(/\//g) || []).length; + if (al > bl) + return -1; + else if (al < bl) + return 1; + return 0; + }).forEach(function(dir) { + FS.rmdir(dir); + }); + }); + }, + + handler: function(callback) { + return function(ev) { + GodotDisplayDragDrop._process_event(ev, callback); + }; + }, + }, +}; +mergeInto(LibraryManager.library, GodotDisplayDragDrop); + +/* + * Display server cursor helper. + * Keeps track of cursor status and custom shapes. + */ +const GodotDisplayCursor = { + $GodotDisplayCursor__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayCursor.clear(); resolve(); });', + $GodotDisplayCursor__deps: ['$GodotConfig', '$GodotOS'], + $GodotDisplayCursor: { + shape: 'auto', + visible: true, + cursors: {}, + set_style: function(style) { + GodotConfig.canvas.style.cursor = style; + }, + set_shape: function(shape) { + GodotDisplayCursor.shape = shape; + let css = shape; + if (shape in GodotDisplayCursor.cursors) { + const c = GodotDisplayCursor.cursors[shape]; + css = 'url("' + c.url + '") ' + c.x + ' ' + c.y + ', auto'; + } + if (GodotDisplayCursor.visible) { + GodotDisplayCursor.set_style(css); + } + }, + clear: function() { + GodotDisplayCursor.set_style(''); + GodotDisplayCursor.shape = 'auto'; + GodotDisplayCursor.visible = true; + Object.keys(GodotDisplayCursor.cursors).forEach(function(key) { + URL.revokeObjectURL(GodotDisplayCursor.cursors[key]); + delete GodotDisplayCursor.cursors[key]; + }); + }, + }, +}; +mergeInto(LibraryManager.library, GodotDisplayCursor); + +/** + * Display server interface. + * + * Exposes all the functions needed by DisplayServer implementation. + */ +const GodotDisplay = { + $GodotDisplay__deps: ['$GodotConfig', '$GodotOS', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop'], + $GodotDisplay: { + window_icon: '', + }, + + godot_js_display_is_swap_ok_cancel: function() { + const win = (['Windows', 'Win64', 'Win32', 'WinCE']); + const plat = navigator.platform || ""; + if (win.indexOf(plat) !== -1) { + return 1; + } + return 0; + }, + + godot_js_display_alert: function(p_text) { + window.alert(UTF8ToString(p_text)); + }, + + godot_js_display_pixel_ratio_get: function() { + return window.devicePixelRatio || 1; + }, + + /* + * Canvas + */ + godot_js_display_canvas_focus: function() { + GodotConfig.canvas.focus(); + }, + + godot_js_display_canvas_is_focused: function() { + return document.activeElement == GodotConfig.canvas; + }, + + godot_js_display_canvas_bounding_rect_position_get: function(r_x, r_y) { + const brect = GodotConfig.canvas.getBoundingClientRect(); + setValue(r_x, brect.x, 'i32'); + setValue(r_y, brect.y, 'i32'); + }, + + /* + * Touchscreen + */ + godot_js_display_touchscreen_is_available: function() { + return 'ontouchstart' in window; + }, + + /* + * Clipboard + */ + godot_js_display_clipboard_set: function(p_text) { + const text = UTF8ToString(p_text); + if (!navigator.clipboard || !navigator.clipboard.writeText) { + return 1; + } + navigator.clipboard.writeText(text).catch(function(e) { + // Setting OS clipboard is only possible from an input callback. + console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e); + }); + return 0; + }, + + godot_js_display_clipboard_get_deps: ['$GodotOS'], + godot_js_display_clipboard_get: function(callback) { + const func = GodotOS.get_func(callback); + try { + navigator.clipboard.readText().then(function (result) { + const ptr = allocate(intArrayFromString(result), ALLOC_NORMAL); + func(ptr); + _free(ptr); + }).catch(function (e) { + // Fail graciously. + }); + } catch (e) { + // Fail graciously. + } + }, + + /* + * Window + */ + godot_js_display_window_request_fullscreen: function() { + const canvas = GodotConfig.canvas; + (canvas.requestFullscreen || canvas.msRequestFullscreen || + canvas.mozRequestFullScreen || canvas.mozRequestFullscreen || + canvas.webkitRequestFullscreen + ).call(canvas); + }, + + godot_js_display_window_title_set: function(p_data) { + document.title = UTF8ToString(p_data); + }, + + godot_js_display_window_icon_set: function(p_ptr, p_len) { + let link = document.getElementById('-gd-engine-icon'); + if (link === null) { + link = document.createElement('link'); + link.rel = 'icon'; + link.id = '-gd-engine-icon'; + document.head.appendChild(link); + } + const old_icon = GodotDisplay.window_icon; + const png = new Blob([GodotOS.heapCopy(HEAPU8, p_ptr, p_len)], { type: "image/png" }); + GodotDisplay.window_icon = URL.createObjectURL(png); + link.href = GodotDisplay.window_icon; + if (old_icon) { + URL.revokeObjectURL(old_icon); + } + }, + + /* + * Cursor + */ + godot_js_display_cursor_set_visible: function(p_visible) { + const visible = p_visible != 0; + if (visible == GodotDisplayCursor.visible) { + return; + } + GodotDisplayCursor.visible = visible; + if (visible) { + GodotDisplayCursor.set_shape(GodotDisplayCursor.shape); + } else { + GodotDisplayCursor.set_style('none'); + } + }, + + godot_js_display_cursor_is_hidden: function() { + return !GodotDisplayCursor.visible; + }, + + godot_js_display_cursor_set_shape: function(p_string) { + GodotDisplayCursor.set_shape(UTF8ToString(p_string)); + }, + + godot_js_display_cursor_set_custom_shape: function(p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) { + const shape = UTF8ToString(p_shape); + const old_shape = GodotDisplayCursor.cursors[shape]; + if (p_len > 0) { + const png = new Blob([GodotOS.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); + const url = URL.createObjectURL(png); + GodotDisplayCursor.cursors[shape] = { + url: url, + x: p_hotspot_x, + y: p_hotspot_y, + }; + } else { + delete GodotDisplayCursor.cursors[shape]; + } + if (shape == GodotDisplayCursor.shape) { + GodotDisplayCursor.set_shape(GodotDisplayCursor.shape); + } + if (old_shape) { + URL.revokeObjectURL(old_shape.url); + } + }, + + /* + * Listeners + */ + godot_js_display_notification_cb: function(callback, p_enter, p_exit, p_in, p_out) { + const canvas = GodotConfig.canvas; + const func = GodotOS.get_func(callback); + const notif = [p_enter, p_exit, p_in, p_out]; + ['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function(evt_name, idx) { + GodotDisplayListeners.add(canvas, evt_name, function() { + func.bind(null, notif[idx]); + }, true); + }); + }, + + godot_js_display_paste_cb: function(callback) { + const func = GodotOS.get_func(callback); + GodotDisplayListeners.add(window, 'paste', function(evt) { + const text = evt.clipboardData.getData('text'); + const ptr = allocate(intArrayFromString(text), ALLOC_NORMAL); + func(ptr); + _free(ptr); + }, false); + }, + + godot_js_display_drop_files_cb: function(callback) { + const func = GodotOS.get_func(callback) + const dropFiles = function(files) { + const args = files || []; + if (!args.length) { + return; + } + const argc = args.length; + const argv = GodotOS.allocStringArray(args); + func(argv, argc); + GodotOS.freeStringArray(argv, argc); + }; + const canvas = GodotConfig.canvas; + GodotDisplayListeners.add(canvas, 'dragover', function(ev) { + // Prevent default behavior (which would try to open the file(s)) + ev.preventDefault(); + }, false); + GodotDisplayListeners.add(canvas, 'drop', GodotDisplayDragDrop.handler(dropFiles)); + }, +}; + +autoAddDeps(GodotDisplay, '$GodotDisplay'); +mergeInto(LibraryManager.library, GodotDisplay); diff --git a/platform/javascript/native/library_godot_editor_tools.js b/platform/javascript/native/library_godot_editor_tools.js new file mode 100644 index 0000000000..202a198adb --- /dev/null +++ b/platform/javascript/native/library_godot_editor_tools.js @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* library_godot_editor_tools.js */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +const GodotEditorTools = { + godot_js_editor_download_file__deps: ['$FS'], + godot_js_editor_download_file: function(p_path, p_name, p_mime) { + const path = UTF8ToString(p_path); + const name = UTF8ToString(p_name); + const mime = UTF8ToString(p_mime); + const size = FS.stat(path)['size']; + const buf = new Uint8Array(size); + const fd = FS.open(path, 'r'); + FS.read(fd, buf, 0, size); + FS.close(fd); + FS.unlink(path); + const blob = new Blob([buf], { type: mime }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = name; + a.style.display = 'none'; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + }, +}; + +mergeInto(LibraryManager.library, GodotEditorTools); diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/RequestParams.java b/platform/javascript/native/library_godot_eval.js index 4be8b37473..44d356a4fb 100644 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/RequestParams.java +++ b/platform/javascript/native/library_godot_eval.js @@ -1,5 +1,5 @@ /*************************************************************************/ -/* RequestParams.java */ +/* library_godot_eval.js */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,57 +28,58 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.plugin.payment.utils; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicNameValuePair; - -/** - * - * @author Luis Linietsky <luis.linietsky@gmail.com> - */ -public class RequestParams { - - private HashMap<String, String> params; - private String url; - - public RequestParams() { - params = new HashMap<String, String>(); - } - - public void put(String key, String value) { - params.put(key, value); - } +const GodotEval = { + godot_js_eval__deps: ['$GodotOS'], + godot_js_eval: function(p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { + const js_code = UTF8ToString(p_js); + let eval_ret = null; + try { + if (p_use_global_ctx) { + // indirect eval call grants global execution context + const global_eval = eval; + eval_ret = global_eval(js_code); + } else { + eval_ret = eval(js_code); + } + } catch (e) { + err(e); + } - public String get(String key) { - return params.get(key); - } + switch (typeof eval_ret) { + case 'boolean': + setValue(p_union_ptr, eval_ret, 'i32'); + return 1; // BOOL - public void remove(Object key) { - params.remove(key); - } + case 'number': + setValue(p_union_ptr, eval_ret, 'double'); + return 3; // REAL - public boolean has(String key) { - return params.containsKey(key); - } + case 'string': + let array_ptr = GodotOS.allocString(eval_ret); + setValue(p_union_ptr, array_ptr , '*'); + return 4; // STRING - public List<NameValuePair> toPairsList() { - List<NameValuePair> fields = new ArrayList<NameValuePair>(); + case 'object': + if (eval_ret === null) { + break; + } - for (String key : params.keySet()) { - fields.add(new BasicNameValuePair(key, this.get(key))); + if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { + eval_ret = new Uint8Array(eval_ret.buffer); + } + else if (eval_ret instanceof ArrayBuffer) { + eval_ret = new Uint8Array(eval_ret); + } + if (eval_ret instanceof Uint8Array) { + const func = GodotOS.get_func(p_callback); + const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length); + HEAPU8.set(eval_ret, bytes_ptr); + return 20; // POOL_BYTE_ARRAY + } + break; } - return fields; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } + return 0; // NIL + }, } + +mergeInto(LibraryManager.library, GodotEval); diff --git a/platform/javascript/native/library_godot_os.js b/platform/javascript/native/library_godot_os.js new file mode 100644 index 0000000000..a1424a691a --- /dev/null +++ b/platform/javascript/native/library_godot_os.js @@ -0,0 +1,311 @@ +/*************************************************************************/ +/* library_godot_os.js */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +const IDHandler = { + $IDHandler: { + _last_id: 0, + _references: {}, + + get: function(p_id) { + return IDHandler._references[p_id]; + }, + + add: function(p_data) { + const id = ++IDHandler._last_id; + IDHandler._references[id] = p_data; + return id; + }, + + remove: function(p_id) { + delete IDHandler._references[p_id]; + }, + }, +}; + +autoAddDeps(IDHandler, "$IDHandler"); +mergeInto(LibraryManager.library, IDHandler); + +const GodotConfig = { + $GodotConfig__postset: 'Module["initConfig"] = GodotConfig.init_config;', + $GodotConfig: { + canvas: null, + locale: "en", + resize_on_start: false, + on_execute: null, + + init_config: function(p_opts) { + GodotConfig.resize_on_start = p_opts['resizeCanvasOnStart'] ? true : false; + GodotConfig.canvas = p_opts['canvas']; + GodotConfig.locale = p_opts['locale'] || GodotConfig.locale; + GodotConfig.on_execute = p_opts['onExecute']; + // This is called by emscripten, even if undocumented. + Module['onExit'] = p_opts['onExit']; + }, + }, + + godot_js_config_canvas_id_get: function(p_ptr, p_ptr_max) { + stringToUTF8('#' + GodotConfig.canvas.id, p_ptr, p_ptr_max); + }, + + godot_js_config_locale_get: function(p_ptr, p_ptr_max) { + stringToUTF8(GodotConfig.locale, p_ptr, p_ptr_max); + }, + + godot_js_config_is_resize_on_start: function() { + return GodotConfig.resize_on_start ? 1 : 0; + }, +}; + +autoAddDeps(GodotConfig, '$GodotConfig'); +mergeInto(LibraryManager.library, GodotConfig); + +const GodotFS = { + $GodotFS__deps: ['$FS', '$IDBFS'], + $GodotFS__postset: [ + 'Module["initFS"] = GodotFS.init;', + 'Module["deinitFS"] = GodotFS.deinit;', + 'Module["copyToFS"] = GodotFS.copy_to_fs;', + ].join(''), + $GodotFS: { + _idbfs: false, + _syncing: false, + _mount_points: [], + + is_persistent: function() { + return GodotFS._idbfs ? 1 : 0; + }, + + // Initialize godot file system, setting up persistent paths. + // Returns a promise that resolves when the FS is ready. + // We keep track of mount_points, so that we can properly close the IDBFS + // since emscripten is not doing it by itself. (emscripten GH#12516). + init: function(persistentPaths) { + GodotFS._idbfs = false; + if (!Array.isArray(persistentPaths)) { + return Promise.reject(new Error('Persistent paths must be an array')); + } + if (!persistentPaths.length) { + return Promise.resolve(); + } + GodotFS._mount_points = persistentPaths.slice(); + + function createRecursive(dir) { + try { + FS.stat(dir); + } catch (e) { + if (e.errno !== ERRNO_CODES.ENOENT) { + throw e; + } + FS.mkdirTree(dir); + } + } + + GodotFS._mount_points.forEach(function(path) { + createRecursive(path); + FS.mount(IDBFS, {}, path); + }); + return new Promise(function(resolve, reject) { + FS.syncfs(true, function(err) { + if (err) { + GodotFS._mount_points = []; + GodotFS._idbfs = false; + console.log("IndexedDB not available: " + err.message); + } else { + GodotFS._idbfs = true; + } + resolve(err); + }); + }); + }, + + // Deinit godot file system, making sure to unmount file systems, and close IDBFS(s). + deinit: function() { + GodotFS._mount_points.forEach(function(path) { + try { + FS.unmount(path); + } catch (e) { + console.log("Already unmounted", e); + } + if (GodotFS._idbfs && IDBFS.dbs[path]) { + IDBFS.dbs[path].close(); + delete IDBFS.dbs[path]; + } + }); + GodotFS._mount_points = []; + GodotFS._idbfs = false; + GodotFS._syncing = false; + }, + + sync: function() { + if (GodotFS._syncing) { + err('Already syncing!'); + return Promise.resolve(); + } + GodotFS._syncing = true; + return new Promise(function (resolve, reject) { + FS.syncfs(false, function(error) { + if (error) { + err('Failed to save IDB file system: ' + error.message); + } + GodotFS._syncing = false; + resolve(error); + }); + }); + }, + + // Copies a buffer to the internal file system. Creating directories recursively. + copy_to_fs: function(path, buffer) { + const idx = path.lastIndexOf("/"); + let dir = "/"; + if (idx > 0) { + dir = path.slice(0, idx); + } + try { + FS.stat(dir); + } catch (e) { + if (e.errno !== ERRNO_CODES.ENOENT) { + throw e; + } + FS.mkdirTree(dir); + } + FS.writeFile(path, new Uint8Array(buffer), {'flags': 'wx+'}); + }, + }, +}; +mergeInto(LibraryManager.library, GodotFS); + +const GodotOS = { + $GodotOS__deps: ['$GodotFS'], + $GodotOS__postset: [ + 'Module["request_quit"] = function() { GodotOS.request_quit() };', + 'GodotOS._fs_sync_promise = Promise.resolve();', + ].join(''), + $GodotOS: { + request_quit: function() {}, + _async_cbs: [], + _fs_sync_promise: null, + + get_func: function(ptr) { + return wasmTable.get(ptr); + }, + + atexit: function(p_promise_cb) { + GodotOS._async_cbs.push(p_promise_cb); + }, + + finish_async: function(callback) { + GodotOS._fs_sync_promise.then(function(err) { + const promises = []; + GodotOS._async_cbs.forEach(function(cb) { + promises.push(new Promise(cb)); + }); + return Promise.all(promises); + }).then(function() { + return GodotFS.sync(); // Final FS sync. + }).then(function(err) { + // Always deferred. + setTimeout(function() { + callback(); + }, 0); + }); + }, + + allocString: function(p_str) { + const length = lengthBytesUTF8(p_str)+1; + const c_str = _malloc(length); + stringToUTF8(p_str, c_str, length); + return c_str; + }, + + allocStringArray: function(strings) { + const size = strings.length; + const c_ptr = _malloc(size * 4); + for (let i = 0; i < size; i++) { + HEAP32[(c_ptr >> 2) + i] = GodotOS.allocString(strings[i]); + } + return c_ptr; + }, + + freeStringArray: function(c_ptr, size) { + for (let i = 0; i < size; i++) { + _free(HEAP32[(c_ptr >> 2) + i]); + } + _free(c_ptr); + }, + + heapSub: function(heap, ptr, size) { + const bytes = heap.BYTES_PER_ELEMENT; + return heap.subarray(ptr / bytes, ptr / bytes + size); + }, + + heapCopy: function(heap, ptr, size) { + const bytes = heap.BYTES_PER_ELEMENT; + return heap.slice(ptr / bytes, ptr / bytes + size); + }, + }, + + godot_js_os_finish_async: function(p_callback) { + const func = GodotOS.get_func(p_callback); + GodotOS.finish_async(func); + }, + + godot_js_os_request_quit_cb: function(p_callback) { + GodotOS.request_quit = GodotOS.get_func(p_callback); + }, + + godot_js_os_fs_is_persistent: function() { + return GodotFS.is_persistent(); + }, + + godot_js_os_fs_sync: function(callback) { + const func = GodotOS.get_func(callback); + GodotOS._fs_sync_promise = GodotFS.sync(); + GodotOS._fs_sync_promise.then(function(err) { + func(); + }); + }, + + godot_js_os_execute: function(p_json) { + const json_args = UTF8ToString(p_json); + const args = JSON.parse(json_args); + if (GodotConfig.on_execute) { + GodotConfig.on_execute(args); + return 0; + } + return 1; + }, + + godot_js_os_shell_open: function(p_uri) { + window.open(UTF8ToString(p_uri), '_blank'); + }, +}; + +autoAddDeps(GodotOS, '$GodotOS'); +mergeInto(LibraryManager.library, GodotOS); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index ad06aef86e..80723d54fc 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -30,1105 +30,103 @@ #include "os_javascript.h" +#include "core/debugger/engine_debugger.h" #include "core/io/file_access_buffered_fa.h" -//#include "drivers/gles2/rasterizer_gles2.h" -#include "drivers/dummy/rasterizer_dummy.h" +#include "core/io/json.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" #include "main/main.h" -#include "servers/rendering/rendering_server_raster.h" +#include "modules/modules_enabled.gen.h" +#include "platform/javascript/display_server_javascript.h" + +#ifdef MODULE_WEBSOCKET_ENABLED +#include "modules/websocket/remote_debugger_peer_websocket.h" +#endif #include <emscripten.h> -#include <png.h> #include <stdlib.h> -#include "dom_keys.inc" - -#define DOM_BUTTON_LEFT 0 -#define DOM_BUTTON_MIDDLE 1 -#define DOM_BUTTON_RIGHT 2 -#define DOM_BUTTON_XBUTTON1 3 -#define DOM_BUTTON_XBUTTON2 4 -#define GODOT_CANVAS_SELECTOR "#canvas" - -// Window (canvas) - -static void focus_canvas() { - - /* clang-format off */ - EM_ASM( - Module.canvas.focus(); - ); - /* clang-format on */ -} - -static bool is_canvas_focused() { - - /* clang-format off */ - return EM_ASM_INT_V( - return document.activeElement == Module.canvas; - ); - /* clang-format on */ -} - -static Point2 compute_position_in_canvas(int x, int y) { - int canvas_x = EM_ASM_INT({ - return document.getElementById('canvas').getBoundingClientRect().x; - }); - int canvas_y = EM_ASM_INT({ - return document.getElementById('canvas').getBoundingClientRect().y; - }); - int canvas_width; - int canvas_height; - emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, &canvas_width, &canvas_height); - - double element_width; - double element_height; - emscripten_get_element_css_size(GODOT_CANVAS_SELECTOR, &element_width, &element_height); - - return Point2((int)(canvas_width / element_width * (x - canvas_x)), - (int)(canvas_height / element_height * (y - canvas_y))); -} - -static bool cursor_inside_canvas = true; - -EM_BOOL OS_JavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { - - OS_JavaScript *os = get_singleton(); - // Empty ID is canvas. - String target_id = String::utf8(p_event->id); - if (target_id.empty() || target_id == "canvas") { - // This event property is the only reliable data on - // browser fullscreen state. - os->video_mode.fullscreen = p_event->isFullscreen; - if (os->video_mode.fullscreen) { - os->entering_fullscreen = false; - } else { - // Restoring maximized window now will cause issues, - // so delay until main_loop_iterate. - os->just_exited_fullscreen = true; - } - } - return false; -} - -void OS_JavaScript::set_video_mode(const VideoMode &p_video_mode, int p_screen) { - - video_mode = p_video_mode; -} - -OS::VideoMode OS_JavaScript::get_video_mode(int p_screen) const { - - return video_mode; -} - -Size2 OS_JavaScript::get_screen_size(int p_screen) const { - - EmscriptenFullscreenChangeEvent ev; - EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status(&ev); - ERR_FAIL_COND_V(result != EMSCRIPTEN_RESULT_SUCCESS, Size2()); - return Size2(ev.screenWidth, ev.screenHeight); -} - -void OS_JavaScript::set_window_size(const Size2 p_size) { - - windowed_size = p_size; - if (video_mode.fullscreen) { - window_maximized = false; - set_window_fullscreen(false); - } else { - if (window_maximized) { - emscripten_exit_soft_fullscreen(); - window_maximized = false; - } - emscripten_set_canvas_element_size(GODOT_CANVAS_SELECTOR, p_size.x, p_size.y); - } -} - -Size2 OS_JavaScript::get_window_size() const { - - int canvas[2]; - emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, canvas, canvas + 1); - return Size2(canvas[0], canvas[1]); -} - -void OS_JavaScript::set_window_maximized(bool p_enabled) { - - if (video_mode.fullscreen) { - window_maximized = p_enabled; - set_window_fullscreen(false); - } else if (!p_enabled) { - emscripten_exit_soft_fullscreen(); - window_maximized = false; - } else if (!window_maximized) { - // Prevent calling emscripten_enter_soft_fullscreen mutltiple times, - // this would hide page elements permanently. - EmscriptenFullscreenStrategy strategy; - strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; - strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; - strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; - strategy.canvasResizedCallback = nullptr; - emscripten_enter_soft_fullscreen(GODOT_CANVAS_SELECTOR, &strategy); - window_maximized = p_enabled; - } -} - -bool OS_JavaScript::is_window_maximized() const { - - return window_maximized; -} - -void OS_JavaScript::set_window_fullscreen(bool p_enabled) { - - if (p_enabled == video_mode.fullscreen) { - return; - } - - // Just request changes here, if successful, logic continues in - // fullscreen_change_callback. - if (p_enabled) { - if (window_maximized) { - // Soft fullsreen during real fullscreen can cause issues, so exit. - // This must be called before requesting full screen. - emscripten_exit_soft_fullscreen(); - } - EmscriptenFullscreenStrategy strategy; - strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; - strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; - strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; - strategy.canvasResizedCallback = nullptr; - EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(GODOT_CANVAS_SELECTOR, false, &strategy); - ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); - ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); - // Not fullscreen yet, so prevent "windowed" canvas dimensions from - // being overwritten. - entering_fullscreen = true; - } else { - // No logic allowed here, since exiting w/ ESC key won't use this function. - ERR_FAIL_COND(emscripten_exit_fullscreen() != EMSCRIPTEN_RESULT_SUCCESS); - } -} - -bool OS_JavaScript::is_window_fullscreen() const { - - return video_mode.fullscreen; -} - -void OS_JavaScript::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { - - Size2 screen = get_screen_size(); - p_list->push_back(OS::VideoMode(screen.width, screen.height, true)); -} - -bool OS_JavaScript::get_window_per_pixel_transparency_enabled() const { - if (!is_layered_allowed()) { - return false; - } - return transparency_enabled; -} - -void OS_JavaScript::set_window_per_pixel_transparency_enabled(bool p_enabled) { - if (!is_layered_allowed()) { - return; - } - transparency_enabled = p_enabled; -} - -// Keys - -template <typename T> -static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { - - godot_event->set_shift(emscripten_event_ptr->shiftKey); - godot_event->set_alt(emscripten_event_ptr->altKey); - godot_event->set_control(emscripten_event_ptr->ctrlKey); - godot_event->set_metakey(emscripten_event_ptr->metaKey); -} - -static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { - - Ref<InputEventKey> ev; - ev.instance(); - ev->set_echo(emscripten_event->repeat); - dom2godot_mod(emscripten_event, ev); - 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`). - if (unicode.length() != 1) { - // Might be empty as well, but better than nonsense. - unicode = String::utf8(emscripten_event->charValue); - } - if (unicode.length() == 1) { - ev->set_unicode(unicode[0]); - } - - return ev; -} - -EM_BOOL OS_JavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - - 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_keycode())) { - // Defer to keypress event for legacy unicode retrieval. - os->deferred_key_event = ev; - // Do not suppress keypress event. - return false; - } - os->input->parse_input_event(ev); - // Resume audio context after input in case autoplay was denied. - os->audio_driver_javascript.resume(); - return true; -} - -EM_BOOL OS_JavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - - OS_JavaScript *os = get_singleton(); - os->deferred_key_event->set_unicode(p_event->charCode); - os->input->parse_input_event(os->deferred_key_event); - return true; -} - -EM_BOOL OS_JavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - - Ref<InputEventKey> ev = setup_key_event(p_event); - ev->set_pressed(false); - get_singleton()->input->parse_input_event(ev); - return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != 0; -} - -// Mouse - -Point2 OS_JavaScript::get_mouse_position() const { - - return input->get_mouse_position(); -} - -int OS_JavaScript::get_mouse_button_state() const { - - return input->get_mouse_button_mask(); -} - -EM_BOOL OS_JavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { - - OS_JavaScript *os = get_singleton(); - - Ref<InputEventMouseButton> ev; - ev.instance(); - ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN); - ev->set_position(compute_position_in_canvas(p_event->clientX, p_event->clientY)); - ev->set_global_position(ev->get_position()); - dom2godot_mod(p_event, ev); - - switch (p_event->button) { - case DOM_BUTTON_LEFT: ev->set_button_index(BUTTON_LEFT); break; - case DOM_BUTTON_MIDDLE: ev->set_button_index(BUTTON_MIDDLE); break; - case DOM_BUTTON_RIGHT: ev->set_button_index(BUTTON_RIGHT); break; - case DOM_BUTTON_XBUTTON1: ev->set_button_index(BUTTON_XBUTTON1); break; - case DOM_BUTTON_XBUTTON2: ev->set_button_index(BUTTON_XBUTTON2); break; - default: return false; - } - - if (ev->is_pressed()) { - - double diff = emscripten_get_now() - os->last_click_ms; - - if (ev->get_button_index() == os->last_click_button_index) { - - if (diff < 400 && Point2(os->last_click_pos).distance_to(ev->get_position()) < 5) { - - os->last_click_ms = 0; - os->last_click_pos = Point2(-100, -100); - os->last_click_button_index = -1; - ev->set_doubleclick(true); - } - - } else { - os->last_click_button_index = ev->get_button_index(); - } - - if (!ev->is_doubleclick()) { - os->last_click_ms += diff; - os->last_click_pos = ev->get_position(); - } - } - - int mask = os->input->get_mouse_button_mask(); - int button_flag = 1 << (ev->get_button_index() - 1); - if (ev->is_pressed()) { - // Since the event is consumed, focus manually. The containing iframe, - // if exists, may not have focus yet, so focus even if already focused. - focus_canvas(); - mask |= button_flag; - } else if (mask & button_flag) { - mask &= ~button_flag; - } else { - // Received release event, but press was outside the canvas, so ignore. - return false; - } - ev->set_button_mask(mask); - - os->input->parse_input_event(ev); - // Resume audio context after input in case autoplay was denied. - os->audio_driver_javascript.resume(); - // Prevent multi-click text selection and wheel-click scrolling anchor. - // Context menu is prevented through contextmenu event. - return true; -} - -EM_BOOL OS_JavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { - - OS_JavaScript *os = get_singleton(); - - int input_mask = os->input->get_mouse_button_mask(); - Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY); - // For motion outside the canvas, only read mouse movement if dragging - // started inside the canvas; imitating desktop app behaviour. - if (!cursor_inside_canvas && !input_mask) - return false; - - Ref<InputEventMouseMotion> ev; - ev.instance(); - dom2godot_mod(p_event, ev); - ev->set_button_mask(input_mask); - - ev->set_position(pos); - ev->set_global_position(ev->get_position()); - - ev->set_relative(Vector2(p_event->movementX, p_event->movementY)); - os->input->set_mouse_position(ev->get_position()); - ev->set_speed(os->input->get_last_mouse_speed()); - - os->input->parse_input_event(ev); - // Don't suppress mouseover/-leave events. - return false; -} - -static const char *godot2dom_cursor(OS::CursorShape p_shape) { - - switch (p_shape) { - case OS::CURSOR_ARROW: - default: - return "auto"; - case OS::CURSOR_IBEAM: return "text"; - case OS::CURSOR_POINTING_HAND: return "pointer"; - case OS::CURSOR_CROSS: return "crosshair"; - case OS::CURSOR_WAIT: return "progress"; - case OS::CURSOR_BUSY: return "wait"; - case OS::CURSOR_DRAG: return "grab"; - case OS::CURSOR_CAN_DROP: return "grabbing"; - case OS::CURSOR_FORBIDDEN: return "no-drop"; - case OS::CURSOR_VSIZE: return "ns-resize"; - case OS::CURSOR_HSIZE: return "ew-resize"; - case OS::CURSOR_BDIAGSIZE: return "nesw-resize"; - case OS::CURSOR_FDIAGSIZE: return "nwse-resize"; - case OS::CURSOR_MOVE: return "move"; - case OS::CURSOR_VSPLIT: return "row-resize"; - case OS::CURSOR_HSPLIT: return "col-resize"; - case OS::CURSOR_HELP: return "help"; - } -} - -static void set_css_cursor(const char *p_cursor) { - - /* clang-format off */ - EM_ASM_({ - Module.canvas.style.cursor = UTF8ToString($0); - }, p_cursor); - /* clang-format on */ -} - -static bool is_css_cursor_hidden() { - - /* clang-format off */ - return EM_ASM_INT({ - return Module.canvas.style.cursor === 'none'; - }); - /* clang-format on */ -} - -void OS_JavaScript::set_cursor_shape(CursorShape p_shape) { - - ERR_FAIL_INDEX(p_shape, CURSOR_MAX); - - if (get_mouse_mode() == MOUSE_MODE_VISIBLE) { - if (cursors[p_shape] != "") { - Vector<String> url = cursors[p_shape].split("?"); - set_css_cursor(("url(\"" + url[0] + "\") " + url[1] + ", auto").utf8()); - } else { - set_css_cursor(godot2dom_cursor(p_shape)); - } - } - - cursor_shape = p_shape; -} - -void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { - - if (p_cursor.is_valid()) { - - Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); - - if (cursor_c) { - if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { - set_cursor_shape(p_shape); - return; - } - - cursors_cache.erase(p_shape); - } - - Ref<Texture2D> texture = p_cursor; - Ref<AtlasTexture> atlas_texture = p_cursor; - Ref<Image> image; - Size2 texture_size; - Rect2 atlas_rect; - - if (texture.is_valid()) { - image = texture->get_data(); - if (image.is_valid()) { - image->duplicate(); - } - } - - if (!image.is_valid() && atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - - atlas_rect.size.width = texture->get_width(); - atlas_rect.size.height = texture->get_height(); - atlas_rect.position.x = atlas_texture->get_region().position.x; - atlas_rect.position.y = atlas_texture->get_region().position.y; - - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else if (image.is_valid()) { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(!texture.is_valid()); - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - - image = texture->get_data(); - - ERR_FAIL_COND(!image.is_valid()); - - image = image->duplicate(); - - if (atlas_texture.is_valid()) - image->crop_from_point( - atlas_rect.position.x, - atlas_rect.position.y, - texture_size.width, - texture_size.height); - - if (image->get_format() != Image::FORMAT_RGBA8) { - image->convert(Image::FORMAT_RGBA8); - } - - png_image png_meta; - memset(&png_meta, 0, sizeof png_meta); - png_meta.version = PNG_IMAGE_VERSION; - png_meta.width = texture_size.width; - png_meta.height = texture_size.height; - png_meta.format = PNG_FORMAT_RGBA; - - PackedByteArray png; - size_t len; - PackedByteArray data = image->get_data(); - ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr)); - - png.resize(len); - ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr)); - - char *object_url; - /* clang-format off */ - EM_ASM({ - var PNG_PTR = $0; - var PNG_LEN = $1; - var PTR = $2; - - var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: 'image/png' }); - var url = URL.createObjectURL(png); - var length_bytes = lengthBytesUTF8(url) + 1; - var string_on_wasm_heap = _malloc(length_bytes); - setValue(PTR, string_on_wasm_heap, '*'); - stringToUTF8(url, string_on_wasm_heap, length_bytes); - }, png.ptr(), len, &object_url); - /* clang-format on */ - - String url = String::utf8(object_url) + "?" + itos(p_hotspot.x) + " " + itos(p_hotspot.y); - - /* clang-format off */ - EM_ASM({ _free($0); }, object_url); - /* clang-format on */ - - if (cursors[p_shape] != "") { - /* clang-format off */ - EM_ASM({ - URL.revokeObjectURL(UTF8ToString($0).split('?')[0]); - }, cursors[p_shape].utf8().get_data()); - /* clang-format on */ - cursors[p_shape] = ""; - } - - cursors[p_shape] = url; - - Vector<Variant> params; - params.push_back(p_cursor); - params.push_back(p_hotspot); - cursors_cache.insert(p_shape, params); - - } else if (cursors[p_shape] != "") { - /* clang-format off */ - EM_ASM({ - URL.revokeObjectURL(UTF8ToString($0).split('?')[0]); - }, cursors[p_shape].utf8().get_data()); - /* clang-format on */ - cursors[p_shape] = ""; - - cursors_cache.erase(p_shape); - } - - set_cursor_shape(cursor_shape); -} - -void OS_JavaScript::set_mouse_mode(OS::MouseMode p_mode) { - - ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED, "MOUSE_MODE_CONFINED is not supported for the HTML5 platform."); - if (p_mode == get_mouse_mode()) - return; - - if (p_mode == MOUSE_MODE_VISIBLE) { - - // set_css_cursor must be called before set_cursor_shape to make the cursor visible - set_css_cursor(godot2dom_cursor(cursor_shape)); - set_cursor_shape(cursor_shape); - emscripten_exit_pointerlock(); - - } else if (p_mode == MOUSE_MODE_HIDDEN) { - - set_css_cursor("none"); - emscripten_exit_pointerlock(); - - } else if (p_mode == MOUSE_MODE_CAPTURED) { - - EMSCRIPTEN_RESULT result = emscripten_request_pointerlock("canvas", false); - ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback."); - ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback."); - // set_css_cursor must be called before set_cursor_shape to make the cursor visible - set_css_cursor(godot2dom_cursor(cursor_shape)); - set_cursor_shape(cursor_shape); - } -} - -OS::MouseMode OS_JavaScript::get_mouse_mode() const { - - if (is_css_cursor_hidden()) - return MOUSE_MODE_HIDDEN; - - EmscriptenPointerlockChangeEvent ev; - emscripten_get_pointerlock_status(&ev); - return (ev.isActive && String::utf8(ev.id) == "canvas") ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE; -} - -// Wheel - -EM_BOOL OS_JavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) { - - ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false); - if (!is_canvas_focused()) { - if (cursor_inside_canvas) { - focus_canvas(); - } else { - return false; - } - } - - InputDefault *input = get_singleton()->input; - Ref<InputEventMouseButton> ev; - ev.instance(); - ev->set_position(input->get_mouse_position()); - ev->set_global_position(ev->get_position()); - - ev->set_shift(input->is_key_pressed(KEY_SHIFT)); - ev->set_alt(input->is_key_pressed(KEY_ALT)); - ev->set_control(input->is_key_pressed(KEY_CONTROL)); - ev->set_metakey(input->is_key_pressed(KEY_META)); - - if (p_event->deltaY < 0) - ev->set_button_index(BUTTON_WHEEL_UP); - else if (p_event->deltaY > 0) - ev->set_button_index(BUTTON_WHEEL_DOWN); - else if (p_event->deltaX > 0) - ev->set_button_index(BUTTON_WHEEL_LEFT); - else if (p_event->deltaX < 0) - ev->set_button_index(BUTTON_WHEEL_RIGHT); - else - return false; - - // Different browsers give wildly different delta values, and we can't - // interpret deltaMode, so use default value for wheel events' factor. - - int button_flag = 1 << (ev->get_button_index() - 1); - - ev->set_pressed(true); - ev->set_button_mask(input->get_mouse_button_mask() | button_flag); - input->parse_input_event(ev); - - ev->set_pressed(false); - ev->set_button_mask(input->get_mouse_button_mask() & ~button_flag); - input->parse_input_event(ev); - - return true; -} - -// Touch - -bool OS_JavaScript::has_touchscreen_ui_hint() const { - - /* clang-format off */ - return EM_ASM_INT_V( - return 'ontouchstart' in window; - ); - /* clang-format on */ -} - -EM_BOOL OS_JavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) { - - OS_JavaScript *os = get_singleton(); - Ref<InputEventScreenTouch> ev; - ev.instance(); - int lowest_id_index = -1; - for (int i = 0; i < p_event->numTouches; ++i) { - - const EmscriptenTouchPoint &touch = p_event->touches[i]; - if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier) - lowest_id_index = i; - if (!touch.isChanged) - continue; - ev->set_index(touch.identifier); - ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY)); - os->touches[i] = ev->get_position(); - ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART); - - os->input->parse_input_event(ev); - } - // Resume audio context after input in case autoplay was denied. - os->audio_driver_javascript.resume(); - return true; -} - -EM_BOOL OS_JavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) { - - OS_JavaScript *os = get_singleton(); - Ref<InputEventScreenDrag> ev; - ev.instance(); - int lowest_id_index = -1; - for (int i = 0; i < p_event->numTouches; ++i) { - - const EmscriptenTouchPoint &touch = p_event->touches[i]; - if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier) - lowest_id_index = i; - if (!touch.isChanged) - continue; - ev->set_index(touch.identifier); - ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY)); - Point2 &prev = os->touches[i]; - ev->set_relative(ev->get_position() - prev); - prev = ev->get_position(); - - os->input->parse_input_event(ev); - } - return true; -} - -// Gamepad - -EM_BOOL OS_JavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) { - - InputDefault *input = get_singleton()->input; - if (p_event_type == EMSCRIPTEN_EVENT_GAMEPADCONNECTED) { - - String guid = ""; - if (String::utf8(p_event->mapping) == "standard") - guid = "Default HTML5 Gamepad"; - input->joy_connection_changed(p_event->index, true, String::utf8(p_event->id), guid); - } else { - input->joy_connection_changed(p_event->index, false, ""); - } - return true; -} - -void OS_JavaScript::process_joypads() { - - int joypad_count = emscripten_get_num_gamepads(); - for (int joypad = 0; joypad < joypad_count; joypad++) { - EmscriptenGamepadEvent state; - EMSCRIPTEN_RESULT query_result = emscripten_get_gamepad_status(joypad, &state); - // Chromium reserves gamepads slots, so NO_DATA is an expected result. - ERR_CONTINUE(query_result != EMSCRIPTEN_RESULT_SUCCESS && - query_result != EMSCRIPTEN_RESULT_NO_DATA); - if (query_result == EMSCRIPTEN_RESULT_SUCCESS && state.connected) { - - int button_count = MIN(state.numButtons, 18); - int axis_count = MIN(state.numAxes, 8); - for (int button = 0; button < button_count; button++) { - - float value = state.analogButton[button]; - if (String::utf8(state.mapping) == "standard" && (button == JOY_ANALOG_L2 || button == JOY_ANALOG_R2)) { - InputDefault::JoyAxis joy_axis; - joy_axis.min = 0; - joy_axis.value = value; - input->joy_axis(joypad, button, joy_axis); - } else { - input->joy_button(joypad, button, value); - } - } - for (int axis = 0; axis < axis_count; axis++) { - - InputDefault::JoyAxis joy_axis; - joy_axis.min = -1; - joy_axis.value = state.axis[axis]; - input->joy_axis(joypad, axis, joy_axis); - } - } - } -} - -bool OS_JavaScript::is_joy_known(int p_device) { - - return input->is_joy_mapped(p_device); -} - -String OS_JavaScript::get_joy_guid(int p_device) const { - - return input->get_joy_guid_remapped(p_device); -} - -// Video - -int OS_JavaScript::get_video_driver_count() const { - - return VIDEO_DRIVER_MAX; -} - -const char *OS_JavaScript::get_video_driver_name(int p_driver) const { - - switch (p_driver) { - case VIDEO_DRIVER_GLES2: - return "GLES2"; - } - ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + "."); -} - -// Audio - -int OS_JavaScript::get_audio_driver_count() const { - - return 1; -} - -const char *OS_JavaScript::get_audio_driver_name(int p_driver) const { - - return "JavaScript"; -} - -// Clipboard -extern "C" EMSCRIPTEN_KEEPALIVE void update_clipboard(const char *p_text) { - // Only call set_clipboard from OS (sets local clipboard) - OS::get_singleton()->OS::set_clipboard(p_text); -} - -void OS_JavaScript::set_clipboard(const String &p_text) { - OS::set_clipboard(p_text); - /* clang-format off */ - int err = EM_ASM_INT({ - var text = UTF8ToString($0); - if (!navigator.clipboard || !navigator.clipboard.writeText) - return 1; - navigator.clipboard.writeText(text).catch(function(e) { - // Setting OS clipboard is only possible from an input callback. - console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e); - }); - return 0; - }, p_text.utf8().get_data()); - /* clang-format on */ - ERR_FAIL_COND_MSG(err, "Clipboard API is not supported."); -} - -String OS_JavaScript::get_clipboard() const { - /* clang-format off */ - EM_ASM({ - try { - navigator.clipboard.readText().then(function (result) { - ccall('update_clipboard', 'void', ['string'], [result]); - }).catch(function (e) { - // Fail graciously. - }); - } catch (e) { - // Fail graciously. - } - }); - /* clang-format on */ - return this->OS::get_clipboard(); -} +#include "godot_js.h" // Lifecycle -int OS_JavaScript::get_current_video_driver() const { - return video_driver_index; -} - -void OS_JavaScript::initialize_core() { - +void OS_JavaScript::initialize() { OS_Unix::initialize_core(); FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix>>(FileAccess::ACCESS_RESOURCES); -} - -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"); - attributes.antialias = false; - ERR_FAIL_INDEX_V(p_video_driver, VIDEO_DRIVER_MAX, ERR_INVALID_PARAMETER); - - if (p_desired.layered) { - set_window_per_pixel_transparency_enabled(true); - } - - bool gl_initialization_error = false; - - if (RasterizerGLES2::is_viable() == OK) { - attributes.majorVersion = 1; - RasterizerGLES2::register_config(); - RasterizerGLES2::make_current(); - } else { - gl_initialization_error = true; - } - - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(GODOT_CANVAS_SELECTOR, &attributes); - if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) { - gl_initialization_error = true; - } + DisplayServerJavaScript::register_javascript_driver(); - if (gl_initialization_error) { - OS::get_singleton()->alert("Your browser does not seem to support WebGL. Please update your browser version.", - "Unable to initialize video driver"); - return ERR_UNAVAILABLE; - } - - video_driver_index = p_video_driver; - - video_mode = p_desired; - // fullscreen_change_callback will correct this if the request is successful. - video_mode.fullscreen = false; - // Emscripten only attempts fullscreen requests if the user input callback - // was registered through one its own functions, so request manually for - // start-up fullscreen. - if (p_desired.fullscreen) { - /* clang-format off */ - EM_ASM({ - const canvas = Module.canvas; - (canvas.requestFullscreen || canvas.msRequestFullscreen || - canvas.mozRequestFullScreen || canvas.mozRequestFullscreen || - canvas.webkitRequestFullscreen - ).call(canvas); - }); - /* clang-format on */ - } - /* clang-format off */ - if (EM_ASM_INT_V({ return Module.resizeCanvasOnStart })) { - /* clang-format on */ - set_window_size(Size2(video_mode.width, video_mode.height)); - } else { - set_window_size(get_window_size()); - } +#ifdef MODULE_WEBSOCKET_ENABLED + EngineDebugger::register_uri_handler("ws://", RemoteDebuggerPeerWebSocket::create); + EngineDebugger::register_uri_handler("wss://", RemoteDebuggerPeerWebSocket::create); #endif - RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu? - - char locale_ptr[16]; - /* clang-format off */ - EM_ASM_ARGS({ - stringToUTF8(Module.locale, $0, 16); - }, locale_ptr); - /* clang-format on */ - setenv("LANG", locale_ptr, true); - - AudioDriverManager::initialize(p_audio_driver); - RenderingServer *rendering_server = memnew(RenderingServerRaster()); - input = memnew(InputDefault); - - EMSCRIPTEN_RESULT result; -#define EM_CHECK(ev) \ - if (result != EMSCRIPTEN_RESULT_SUCCESS) \ - ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result)); -#define SET_EM_CALLBACK(target, ev, cb) \ - result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \ - EM_CHECK(ev) -#define SET_EM_CALLBACK_NOTARGET(ev, cb) \ - result = emscripten_set_##ev##_callback(nullptr, true, &cb); \ - EM_CHECK(ev) - // These callbacks from Emscripten's html5.h suffice to access most - // JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM - // is used below. - SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mousemove, mousemove_callback) - 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(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) - SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback) - SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback) - SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback) -#undef SET_EM_CALLBACK_NOTARGET -#undef SET_EM_CALLBACK -#undef EM_CHECK - - /* clang-format off */ - EM_ASM_ARGS({ - const send_notification = cwrap('send_notification', null, ['number']); - const notifications = arguments; - (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) { - Module.canvas.addEventListener(event, send_notification.bind(null, notifications[index])); - }); - // Clipboard - const update_clipboard = cwrap('update_clipboard', null, ['string']); - window.addEventListener('paste', function(evt) { - update_clipboard(evt.clipboardData.getData('text')); - }, true); - }, - NOTIFICATION_WM_MOUSE_ENTER, - NOTIFICATION_WM_MOUSE_EXIT, - NOTIFICATION_WM_FOCUS_IN, - NOTIFICATION_WM_FOCUS_OUT - ); - /* clang-format on */ - - rendering_server->init(); +} - return OK; +void OS_JavaScript::resume_audio() { + if (audio_driver_javascript) { + audio_driver_javascript->resume(); + } } void OS_JavaScript::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; - input->set_main_loop(p_main_loop); } MainLoop *OS_JavaScript::get_main_loop() const { - return main_loop; } -void OS_JavaScript::run_async() { - - main_loop->init(); - emscripten_set_main_loop(main_loop_callback, -1, false); -} - -void OS_JavaScript::main_loop_callback() { - - get_singleton()->main_loop_iterate(); +void OS_JavaScript::fs_sync_callback() { + get_singleton()->idb_is_syncing = false; } bool OS_JavaScript::main_loop_iterate() { - - if (is_userfs_persistent() && sync_wait_time >= 0) { - int64_t current_time = get_ticks_msec(); - int64_t elapsed_time = current_time - last_sync_check_time; - last_sync_check_time = current_time; - - sync_wait_time -= elapsed_time; - - if (sync_wait_time < 0) { - /* clang-format off */ - EM_ASM( - FS.syncfs(function(error) { - if (error) { err('Failed to save IDB file system: ' + error.message); } - }); - ); - /* clang-format on */ - } - } - - if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) - process_joypads(); - - if (just_exited_fullscreen) { - if (window_maximized) { - EmscriptenFullscreenStrategy strategy; - strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; - strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; - strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; - strategy.canvasResizedCallback = nullptr; - emscripten_enter_soft_fullscreen(GODOT_CANVAS_SELECTOR, &strategy); - } else { - emscripten_set_canvas_element_size(GODOT_CANVAS_SELECTOR, windowed_size.width, windowed_size.height); - } - just_exited_fullscreen = false; + if (is_userfs_persistent() && idb_needs_sync && !idb_is_syncing) { + idb_is_syncing = true; + idb_needs_sync = false; + godot_js_os_fs_sync(&fs_sync_callback); } - int canvas[2]; - emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, canvas, canvas + 1); - video_mode.width = canvas[0]; - video_mode.height = canvas[1]; - if (!window_maximized && !video_mode.fullscreen && !just_exited_fullscreen && !entering_fullscreen) { - windowed_size.width = canvas[0]; - windowed_size.height = canvas[1]; - } + DisplayServer::get_singleton()->process_events(); return Main::iteration(); } void OS_JavaScript::delete_main_loop() { - - memdelete(main_loop); + if (main_loop) { + memdelete(main_loop); + } + main_loop = nullptr; } void OS_JavaScript::finalize() { - - memdelete(input); + delete_main_loop(); + if (audio_driver_javascript) { + memdelete(audio_driver_javascript); + audio_driver_javascript = nullptr; + } } // Miscellaneous Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { - - ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "OS::execute() is not available on the HTML5 platform."); + Array args; + for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) { + args.push_back(E->get()); + } + String json_args = JSON::print(args); + int failed = godot_js_os_execute(json_args.utf8().get_data()); + ERR_FAIL_COND_V_MSG(failed, ERR_UNAVAILABLE, "OS::execute() must be implemented in JavaScript via 'engine.setOnExecute' if required."); + return OK; } Error OS_JavaScript::kill(const ProcessID &p_pid) { - ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "OS::kill() is not available on the HTML5 platform."); } int OS_JavaScript::get_process_id() const { - ERR_FAIL_V_MSG(0, "OS::get_process_id() is not available on the HTML5 platform."); } -extern "C" EMSCRIPTEN_KEEPALIVE void send_notification(int p_notification) { - - if (p_notification == NOTIFICATION_WM_MOUSE_ENTER || p_notification == NOTIFICATION_WM_MOUSE_EXIT) { - cursor_inside_canvas = p_notification == NOTIFICATION_WM_MOUSE_ENTER; - } - OS_JavaScript::get_singleton()->get_main_loop()->notification(p_notification); -} - bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "HTML5" || p_feature == "web") return true; @@ -1140,156 +138,73 @@ bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { return false; } -void OS_JavaScript::alert(const String &p_alert, const String &p_title) { - - /* clang-format off */ - EM_ASM_({ - window.alert(UTF8ToString($0)); - }, p_alert.utf8().get_data()); - /* clang-format on */ -} - -void OS_JavaScript::set_window_title(const String &p_title) { - - /* clang-format off */ - EM_ASM_({ - document.title = UTF8ToString($0); - }, p_title.utf8().get_data()); - /* clang-format on */ -} - -void OS_JavaScript::set_icon(const Ref<Image> &p_icon) { - - ERR_FAIL_COND(p_icon.is_null()); - Ref<Image> icon = p_icon; - if (icon->is_compressed()) { - icon = icon->duplicate(); - ERR_FAIL_COND(icon->decompress() != OK); - } - if (icon->get_format() != Image::FORMAT_RGBA8) { - if (icon == p_icon) - icon = icon->duplicate(); - icon->convert(Image::FORMAT_RGBA8); - } - - png_image png_meta; - memset(&png_meta, 0, sizeof png_meta); - png_meta.version = PNG_IMAGE_VERSION; - png_meta.width = icon->get_width(); - png_meta.height = icon->get_height(); - png_meta.format = PNG_FORMAT_RGBA; - - PackedByteArray png; - size_t len; - PackedByteArray data = icon->get_data(); - ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr)); - - png.resize(len); - ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr)); - - /* clang-format off */ - EM_ASM_ARGS({ - var PNG_PTR = $0; - var PNG_LEN = $1; - - var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: "image/png" }); - var url = URL.createObjectURL(png); - var link = document.getElementById('-gd-engine-icon'); - if (link === null) { - link = document.createElement('link'); - link.rel = 'icon'; - link.id = '-gd-engine-icon'; - document.head.appendChild(link); - } - link.href = url; - }, png.ptr(), len); - /* clang-format on */ -} - String OS_JavaScript::get_executable_path() const { - return OS::get_executable_path(); } Error OS_JavaScript::shell_open(String p_uri) { - // Open URI in a new tab, browser will deal with it by protocol. - /* clang-format off */ - EM_ASM_({ - window.open(UTF8ToString($0), '_blank'); - }, p_uri.utf8().get_data()); - /* clang-format on */ + godot_js_os_shell_open(p_uri.utf8().get_data()); return OK; } String OS_JavaScript::get_name() const { - return "HTML5"; } -bool OS_JavaScript::can_draw() const { - - return true; // Always? -} - String OS_JavaScript::get_user_data_dir() const { - return "/userfs"; }; -String OS_JavaScript::get_resource_dir() const { - - return "/"; +String OS_JavaScript::get_cache_path() const { + return "/home/web_user/.cache"; } -void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags) { - - OS_JavaScript *os = get_singleton(); - if (os->is_userfs_persistent() && p_file.begins_with("/userfs") && p_flags & FileAccess::WRITE) { - os->last_sync_check_time = OS::get_singleton()->get_ticks_msec(); - // Wait five seconds in case more files are about to be closed. - os->sync_wait_time = 5000; - } +String OS_JavaScript::get_config_path() const { + return "/home/web_user/.config"; } -void OS_JavaScript::set_idb_available(bool p_idb_available) { +String OS_JavaScript::get_data_path() const { + return "/home/web_user/.local/share"; +} - idb_available = p_idb_available; +void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags) { + OS_JavaScript *os = OS_JavaScript::get_singleton(); + if (!(os->is_userfs_persistent() && (p_flags & FileAccess::WRITE))) { + return; // FS persistence is not working or we are not writing. + } + bool is_file_persistent = p_file.begins_with("/userfs"); +#ifdef TOOLS_ENABLED + // Hack for editor persistence (can we track). + is_file_persistent = is_file_persistent || p_file.begins_with("/home/web_user/"); +#endif + if (is_file_persistent) { + os->idb_needs_sync = true; + } } bool OS_JavaScript::is_userfs_persistent() const { - return idb_available; } OS_JavaScript *OS_JavaScript::get_singleton() { - return static_cast<OS_JavaScript *>(OS::get_singleton()); } -OS_JavaScript::OS_JavaScript(int p_argc, char *p_argv[]) { - - List<String> arguments; - for (int i = 1; i < p_argc; i++) { - arguments.push_back(String::utf8(p_argv[i])); - } - set_cmdline(p_argv[0], arguments); - - last_click_button_index = -1; - last_click_ms = 0; - last_click_pos = Point2(-100, -100); - - window_maximized = false; - entering_fullscreen = false; - just_exited_fullscreen = false; - transparency_enabled = false; +void OS_JavaScript::initialize_joypads() { +} - main_loop = nullptr; +OS_JavaScript::OS_JavaScript() { + char locale_ptr[16]; + godot_js_config_locale_get(locale_ptr, 16); + setenv("LANG", locale_ptr, true); - idb_available = false; - sync_wait_time = -1; + if (AudioDriverJavaScript::is_available()) { + audio_driver_javascript = memnew(AudioDriverJavaScript); + AudioDriverManager::add_driver(audio_driver_javascript); + } - AudioDriverManager::add_driver(&audio_driver_javascript); + idb_available = godot_js_os_fs_is_persistent(); Vector<Logger *> loggers; loggers.push_back(memnew(StdLogger)); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 81fe4cf0cc..03a3053367 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -32,138 +32,65 @@ #define OS_JAVASCRIPT_H #include "audio_driver_javascript.h" -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "drivers/unix/os_unix.h" #include "servers/audio_server.h" -#include "servers/rendering/rasterizer.h" #include <emscripten/html5.h> class OS_JavaScript : public OS_Unix { + MainLoop *main_loop = nullptr; + AudioDriverJavaScript *audio_driver_javascript = nullptr; - VideoMode video_mode; - Vector2 windowed_size; - bool window_maximized; - bool entering_fullscreen; - bool just_exited_fullscreen; - bool transparency_enabled; - - InputDefault *input; - Ref<InputEventKey> deferred_key_event; - CursorShape cursor_shape; - String cursors[CURSOR_MAX]; - Map<CursorShape, Vector<Variant>> cursors_cache; - Point2 touches[32]; - - Point2i last_click_pos; - double last_click_ms; - int last_click_button_index; - - MainLoop *main_loop; - int video_driver_index; - AudioDriverJavaScript audio_driver_javascript; - - bool idb_available; - int64_t sync_wait_time; - int64_t last_sync_check_time; - - static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data); - - static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); - static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); - static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); - - static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data); - static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data); - - static EM_BOOL wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data); - - static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data); - static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data); - - static EM_BOOL gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data); - void process_joypads(); + bool idb_is_syncing = false; + bool idb_available = false; + bool idb_needs_sync = false; static void main_loop_callback(); static void file_access_close_callback(const String &p_file, int p_flags); + static void fs_sync_callback(); protected: - virtual int get_current_video_driver() const; + void initialize() override; - virtual void initialize_core(); - virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); + void set_main_loop(MainLoop *p_main_loop) override; + void delete_main_loop() override; - virtual void set_main_loop(MainLoop *p_main_loop); - virtual void delete_main_loop(); + void finalize() override; - virtual void finalize(); - - virtual bool _check_internal_feature_support(const String &p_feature); + bool _check_internal_feature_support(const String &p_feature) override; public: // Override return type to make writing static callbacks less tedious. static OS_JavaScript *get_singleton(); - virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); - virtual VideoMode get_video_mode(int p_screen = 0) const; - virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const; - - virtual void set_window_size(const Size2); - virtual Size2 get_window_size() const; - virtual void set_window_maximized(bool p_enabled); - virtual bool is_window_maximized() const; - virtual void set_window_fullscreen(bool p_enabled); - virtual bool is_window_fullscreen() const; - virtual Size2 get_screen_size(int p_screen = -1) const; - - virtual Point2 get_mouse_position() const; - virtual int get_mouse_button_state() const; - virtual void set_cursor_shape(CursorShape p_shape); - virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); - virtual void set_mouse_mode(MouseMode p_mode); - virtual MouseMode get_mouse_mode() const; - - virtual bool get_window_per_pixel_transparency_enabled() const; - virtual void set_window_per_pixel_transparency_enabled(bool p_enabled); - - virtual bool has_touchscreen_ui_hint() const; + void initialize_joypads() override; - virtual bool is_joy_known(int p_device); - virtual String get_joy_guid(int p_device) const; - - virtual int get_video_driver_count() const; - virtual const char *get_video_driver_name(int p_driver) const; - - virtual int get_audio_driver_count() const; - virtual const char *get_audio_driver_name(int p_driver) const; - - virtual void set_clipboard(const String &p_text); - virtual String get_clipboard() const; - - virtual MainLoop *get_main_loop() const; - void run_async(); + MainLoop *get_main_loop() const override; bool main_loop_iterate(); - virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr); - virtual Error kill(const ProcessID &p_pid); - virtual int get_process_id() const; + Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override; + Error kill(const ProcessID &p_pid) override; + int get_process_id() const override; + + String get_executable_path() const override; + Error shell_open(String p_uri) override; + String get_name() const override; + // Override default OS implementation which would block the main thread with delay_usec. + // Implemented in javascript_main.cpp loop callback instead. + void add_frame_delay(bool p_can_draw) override {} - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - virtual void set_window_title(const String &p_title); - virtual void set_icon(const Ref<Image> &p_icon); - String get_executable_path() const; - virtual Error shell_open(String p_uri); - virtual String get_name() const; - virtual bool can_draw() const; + String get_cache_path() const override; + String get_config_path() const override; + String get_data_path() const override; + String get_user_data_dir() const override; - virtual String get_resource_dir() const; - virtual String get_user_data_dir() const; + bool is_userfs_persistent() const override; - void set_idb_available(bool p_idb_available); - virtual bool is_userfs_persistent() const; + void resume_audio(); - OS_JavaScript(int p_argc, char *p_argv[]); + OS_JavaScript(); }; #endif diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub index ae75a75830..6e43ffcedb 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -18,5 +18,5 @@ common_x11 = [ prog = env.add_program("#bin/godot", ["godot_linuxbsd.cpp"] + common_x11) -if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]: +if env["debug_symbols"] == "yes" and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_linuxbsd_builders.make_debug_linuxbsd)) diff --git a/platform/linuxbsd/context_gl_x11.cpp b/platform/linuxbsd/context_gl_x11.cpp index 308d68521a..71dc9602b3 100644 --- a/platform/linuxbsd/context_gl_x11.cpp +++ b/platform/linuxbsd/context_gl_x11.cpp @@ -46,22 +46,18 @@ typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); struct ContextGL_X11_Private { - ::GLXContext glx_context; }; void ContextGL_X11::release_current() { - glXMakeCurrent(x11_display, None, nullptr); } void ContextGL_X11::make_current() { - glXMakeCurrent(x11_display, x11_window, p->glx_context); } void ContextGL_X11::swap_buffers() { - glXSwapBuffers(x11_display, x11_window); } @@ -85,7 +81,6 @@ static void set_class_hint(Display *p_display, Window p_window) { } Error ContextGL_X11::initialize() { - //const char *extensions = glXQueryExtensionsString(x11_display, DefaultScreen(x11_display)); GLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (GLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB"); @@ -167,7 +162,6 @@ Error ContextGL_X11::initialize() { switch (context_type) { case GLES_2_0_COMPATIBLE: { - p->glx_context = glXCreateNewContext(x11_display, fbconfig, GLX_RGBA_TYPE, 0, true); ERR_FAIL_COND_V(!p->glx_context, ERR_UNCONFIGURED); } break; @@ -192,7 +186,6 @@ Error ContextGL_X11::initialize() { } int ContextGL_X11::get_window_width() { - XWindowAttributes xwa; XGetWindowAttributes(x11_display, x11_window, &xwa); @@ -234,14 +227,13 @@ void ContextGL_X11::set_use_vsync(bool p_use) { return; use_vsync = p_use; } -bool ContextGL_X11::is_using_vsync() const { +bool ContextGL_X11::is_using_vsync() const { return use_vsync; } ContextGL_X11::ContextGL_X11(::Display *p_x11_display, ::Window &p_x11_window, const OS::VideoMode &p_default_video_mode, ContextType p_context_type) : x11_window(p_x11_window) { - default_video_mode = p_default_video_mode; x11_display = p_x11_display; diff --git a/platform/linuxbsd/context_gl_x11.h b/platform/linuxbsd/context_gl_x11.h index 2c0643c95a..7aed280c98 100644 --- a/platform/linuxbsd/context_gl_x11.h +++ b/platform/linuxbsd/context_gl_x11.h @@ -42,7 +42,6 @@ struct ContextGL_X11_Private; class ContextGL_X11 { - public: enum ContextType { GLES_2_0_COMPATIBLE, diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index dbdb15918e..36c304b7f9 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -30,8 +30,8 @@ #include "crash_handler_linuxbsd.h" +#include "core/config/project_settings.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "main/main.h" #ifdef DEBUG_ENABLED @@ -63,10 +63,11 @@ static void handle_crash(int sig) { // Dump the backtrace to stderr with a message to the user fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); - if (OS::get_singleton()->get_main_loop()) + if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); + } - fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); + fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { for (size_t i = 1; i < size; i++) { @@ -85,8 +86,9 @@ static void handle_crash(int sig) { snprintf(fname, 1024, "%s", demangled); } - if (demangled) + if (demangled) { free(demangled); + } } } @@ -107,7 +109,7 @@ static void handle_crash(int sig) { output.erase(output.length() - 1, 1); } - fprintf(stderr, "[%ld] %s (%ls)\n", (long int)i, fname, output.c_str()); + fprintf(stderr, "[%ld] %s (%s)\n", (long int)i, fname, output.utf8().get_data()); } free(strings); @@ -128,8 +130,9 @@ CrashHandler::~CrashHandler() { } void CrashHandler::disable() { - if (disabled) + if (disabled) { return; + } #ifdef CRASH_HANDLER_ENABLED signal(SIGSEGV, nullptr); diff --git a/platform/linuxbsd/crash_handler_linuxbsd.h b/platform/linuxbsd/crash_handler_linuxbsd.h index 94b4649690..9bb03579bc 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.h +++ b/platform/linuxbsd/crash_handler_linuxbsd.h @@ -32,7 +32,6 @@ #define CRASH_HANDLER_X11_H class CrashHandler { - bool disabled; public: diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 5d8b4fba48..ea101e24dd 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -35,6 +35,11 @@ def can_build(): print("xinerama not found.. x11 disabled.") return False + x11_error = os.system("pkg-config xext --modversion > /dev/null ") + if x11_error: + print("xext not found.. x11 disabled.") + return False + x11_error = os.system("pkg-config xrandr --modversion > /dev/null ") if x11_error: print("xrandr not found.. x11 disabled.") @@ -68,7 +73,7 @@ def get_opts(): BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), BoolVariable("pulseaudio", "Detect and use PulseAudio", True), BoolVariable("udev", "Use udev for gamepad connection callbacks", False), - EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), + EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("touch", "Enable touch events", True), BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False), @@ -91,8 +96,6 @@ def configure(env): env.Prepend(CCFLAGS=["-Os"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": @@ -103,13 +106,11 @@ def configure(env): env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "debug": env.Prepend(CCFLAGS=["-g3"]) - env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["-rdynamic"]) ## Architecture @@ -194,6 +195,7 @@ def configure(env): env.ParseConfig("pkg-config x11 --cflags --libs") env.ParseConfig("pkg-config xcursor --cflags --libs") env.ParseConfig("pkg-config xinerama --cflags --libs") + env.ParseConfig("pkg-config xext --cflags --libs") env.ParseConfig("pkg-config xrandr --cflags --libs") env.ParseConfig("pkg-config xrender --cflags --libs") env.ParseConfig("pkg-config xi --cflags --libs") @@ -217,15 +219,17 @@ def configure(env): env.ParseConfig("pkg-config libpng16 --cflags --libs") if not env["builtin_bullet"]: - # We need at least version 2.89 + # We need at least version 2.90 + min_bullet_version = "2.90" + import subprocess bullet_version = subprocess.check_output(["pkg-config", "bullet", "--modversion"]).strip() - if str(bullet_version) < "2.89": + if str(bullet_version) < min_bullet_version: # Abort as system bullet was requested but too old print( "Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format( - bullet_version, "2.89" + bullet_version, min_bullet_version ) ) sys.exit(255) diff --git a/platform/linuxbsd/detect_prime_x11.cpp b/platform/linuxbsd/detect_prime_x11.cpp index 1bec65ff04..e5a9bb4737 100644 --- a/platform/linuxbsd/detect_prime_x11.cpp +++ b/platform/linuxbsd/detect_prime_x11.cpp @@ -33,8 +33,8 @@ #include "detect_prime.h" -#include "core/print_string.h" -#include "core/ustring.h" +#include "core/string/print_string.h" +#include "core/string/ustring.h" #include <stdlib.h> @@ -178,7 +178,8 @@ int detect_prime() { close(fdset[0]); - if (i) setenv("DRI_PRIME", "1", 1); + if (i) + setenv("DRI_PRIME", "1", 1); create_context(); const char *vendor = (const char *)glGetString(GL_VENDOR); diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 78ddef5ff6..5fa737af8e 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -32,35 +32,26 @@ #ifdef X11_ENABLED +#include "core/config/project_settings.h" +#include "core/string/print_string.h" #include "detect_prime_x11.h" - -#include "core/os/dir_access.h" -#include "core/print_string.h" -#include "errno.h" #include "key_mapping_x11.h" - -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif +#include "main/main.h" +#include "scene/resources/texture.h" #if defined(VULKAN_ENABLED) #include "servers/rendering/rasterizer_rd/rasterizer_rd.h" #endif -#include "scene/resources/texture.h" - -#ifdef HAVE_MNTENT -#include <mntent.h> -#endif - #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "X11/Xutil.h" +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xinerama.h> +#include <X11/extensions/shape.h> -#include "X11/Xatom.h" -#include "X11/extensions/Xinerama.h" // ICCCM #define WM_NormalState 1L // window normal state #define WM_IconicState 3L // window minimized @@ -69,8 +60,6 @@ #define _NET_WM_STATE_ADD 1L // add/set property #define _NET_WM_STATE_TOGGLE 2L // toggle property -#include "main/main.h" - #include <dlfcn.h> #include <fcntl.h> #include <sys/stat.h> @@ -82,14 +71,9 @@ #undef KEY_TAB #endif -#include <X11/Xatom.h> - #undef CursorShape - #include <X11/XKBlib.h> -#include "core/project_settings.h" - // 2.2 is the first release with multitouch #define XINPUT_CLIENT_VERSION_MAJOR 2 #define XINPUT_CLIENT_VERSION_MINOR 2 @@ -100,6 +84,13 @@ #define VALUATOR_TILTX 3 #define VALUATOR_TILTY 4 +//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +#define DEBUG_LOG_X11(...) printf(__VA_ARGS__) +#else +#define DEBUG_LOG_X11(...) +#endif + static const double abs_resolution_mult = 10000.0; static const double abs_resolution_range_mult = 10.0; @@ -127,6 +118,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { return false; } + String DisplayServerX11::get_name() const { return "X11"; } @@ -148,8 +140,9 @@ void DisplayServerX11::alert(const String &p_alert, const String &p_title) { } } - if (program.length()) + if (program.length()) { break; + } } List<String> args; @@ -204,11 +197,10 @@ void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) { if (xquerypointer_result) { if (win_x > 0 && win_y > 0 && win_x <= wd.size.width && win_y <= wd.size.height) { - last_mouse_pos.x = win_x; last_mouse_pos.y = win_y; last_mouse_pos_valid = true; - InputFilter::get_singleton()->set_mouse_position(last_mouse_pos); + Input::get_singleton()->set_mouse_position(last_mouse_pos); } } } @@ -245,22 +237,27 @@ bool DisplayServerX11::_refresh_device_info() { for (int i = 0; i < dev_count; i++) { XIDeviceInfo *dev = &info[i]; - if (!dev->enabled) + if (!dev->enabled) { continue; - if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave)) + } + if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave)) { continue; + } bool direct_touch = false; bool absolute_mode = false; int resolution_x = 0; int resolution_y = 0; - double range_min_x = 0; - double range_min_y = 0; - double range_max_x = 0; - double range_max_y = 0; - int pressure_resolution = 0; - int tilt_resolution_x = 0; - int tilt_resolution_y = 0; + double abs_x_min = 0; + double abs_x_max = 0; + double abs_y_min = 0; + double abs_y_max = 0; + double pressure_min = 0; + double pressure_max = 0; + double tilt_x_min = 0; + double tilt_x_max = 0; + double tilt_y_min = 0; + double tilt_y_max = 0; for (int j = 0; j < dev->num_classes; j++) { #ifdef TOUCH_ENABLED if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) { @@ -272,23 +269,23 @@ bool DisplayServerX11::_refresh_device_info() { if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) { resolution_x = class_info->resolution; - range_min_x = class_info->min; - range_max_x = class_info->max; + abs_x_min = class_info->min; + abs_y_max = class_info->max; absolute_mode = true; } else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) { resolution_y = class_info->resolution; - range_min_y = class_info->min; - range_max_y = class_info->max; + abs_y_min = class_info->min; + abs_y_max = class_info->max; absolute_mode = true; } else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) { - pressure_resolution = (class_info->max - class_info->min); - if (pressure_resolution == 0) pressure_resolution = 1; + pressure_min = class_info->min; + pressure_max = class_info->max; } else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) { - tilt_resolution_x = (class_info->max - class_info->min); - if (tilt_resolution_x == 0) tilt_resolution_x = 1; + tilt_x_min = class_info->min; + tilt_x_max = class_info->max; } else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) { - tilt_resolution_y = (class_info->max - class_info->min); - if (tilt_resolution_y == 0) tilt_resolution_y = 1; + tilt_x_min = class_info->min; + tilt_x_max = class_info->max; } } } @@ -299,18 +296,19 @@ bool DisplayServerX11::_refresh_device_info() { if (absolute_mode) { // If no resolution was reported, use the min/max ranges. if (resolution_x <= 0) { - resolution_x = (range_max_x - range_min_x) * abs_resolution_range_mult; + resolution_x = (abs_x_max - abs_x_min) * abs_resolution_range_mult; } if (resolution_y <= 0) { - resolution_y = (range_max_y - range_min_y) * abs_resolution_range_mult; + resolution_y = (abs_y_max - abs_y_min) * abs_resolution_range_mult; } - xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y); print_verbose("XInput: Absolute pointing device: " + String(dev->name)); } xi.pressure = 0; - xi.pen_devices[dev->deviceid] = Vector3(pressure_resolution, tilt_resolution_x, tilt_resolution_y); + xi.pen_pressure_range[dev->deviceid] = Vector2(pressure_min, pressure_max); + xi.pen_tilt_x_range[dev->deviceid] = Vector2(tilt_x_min, tilt_x_max); + xi.pen_tilt_y_range[dev->deviceid] = Vector2(tilt_y_min, tilt_y_max); } XIFreeDeviceInfo(info); @@ -324,23 +322,19 @@ bool DisplayServerX11::_refresh_device_info() { } void DisplayServerX11::_flush_mouse_motion() { - while (true) { - if (XPending(x11_display) > 0) { - XEvent event; - XPeekEvent(x11_display, &event); - - if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { - XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; - - if (event_data->evtype == XI_RawMotion) { - XNextEvent(x11_display, &event); - } else { - break; - } - } else { - break; + // Block events polling while flushing motion events. + MutexLock mutex_lock(events_mutex); + + for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) { + XEvent &event = polled_events[event_index]; + if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { + XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; + if (event_data->evtype == XI_RawMotion) { + XFreeEventData(x11_display, &event.xcookie); + polled_events.remove(event_index--); + continue; } - } else { + XFreeEventData(x11_display, &event.xcookie); break; } } @@ -350,20 +344,20 @@ void DisplayServerX11::_flush_mouse_motion() { } void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { - _THREAD_SAFE_METHOD_ - if (p_mode == mouse_mode) + if (p_mode == mouse_mode) { return; + } - if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) + if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) { XUngrabPointer(x11_display, CurrentTime); + } // The only modes that show a cursor are VISIBLE and CONFINED bool showCursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED); for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (showCursor) { XDefineCursor(x11_display, E->get().x11_window, cursors[current_cursor]); // show cursor } else { @@ -373,7 +367,6 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { mouse_mode = p_mode; if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) { - //flush pending motion events _flush_mouse_motion(); WindowData &main_window = windows[MAIN_WINDOW_ID]; @@ -392,7 +385,7 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { XWarpPointer(x11_display, None, main_window.x11_window, 0, 0, 0, 0, (int)center.x, (int)center.y); - InputFilter::get_singleton()->set_mouse_position(center); + Input::get_singleton()->set_mouse_position(center); } } else { do_mouse_warp = false; @@ -400,30 +393,35 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { XFlush(x11_display); } + DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const { return mouse_mode; } void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) { - _THREAD_SAFE_METHOD_ if (mouse_mode == MOUSE_MODE_CAPTURED) { - last_mouse_pos = p_to; } else { - - /*XWindowAttributes xwa; - XGetWindowAttributes(x11_display, x11_window, &xwa); - printf("%d %d\n", xwa.x, xwa.y); needed? */ - XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window, 0, 0, 0, 0, (int)p_to.x, (int)p_to.y); } } Point2i DisplayServerX11::mouse_get_position() const { - return last_mouse_pos; + int root_x, root_y; + int win_x, win_y; + unsigned int mask_return; + Window window_returned; + + Bool result = XQueryPointer(x11_display, RootWindow(x11_display, DefaultScreen(x11_display)), &window_returned, + &window_returned, &root_x, &root_y, &win_x, &win_y, + &mask_return); + if (result == True) { + return Point2i(root_x, root_y); + } + return Point2i(); } Point2i DisplayServerX11::mouse_get_absolute_position() const { @@ -447,16 +445,27 @@ int DisplayServerX11::mouse_get_button_state() const { } void DisplayServerX11::clipboard_set(const String &p_text) { - _THREAD_SAFE_METHOD_ - internal_clipboard = p_text; + { + // The clipboard content can be accessed while polling for events. + MutexLock mutex_lock(events_mutex); + internal_clipboard = p_text; + } + XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime); XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime); } -static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) { +Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) { + if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) { + return True; + } else { + return False; + } +} +String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const { String ret; Atom type; @@ -464,24 +473,27 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x int format, result; unsigned long len, bytes_left, dummy; unsigned char *data; - Window Sown = XGetSelectionOwner(x11_display, p_source); + Window selection_owner = XGetSelectionOwner(x11_display, p_source); - if (Sown == x11_window) { + if (selection_owner == x11_window) { + return internal_clipboard; + } - return p_internal_clipboard; - }; + if (selection_owner != None) { + { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); - if (Sown != None) { - XConvertSelection(x11_display, p_source, target, selection, - x11_window, CurrentTime); - XFlush(x11_display); - while (true) { + XConvertSelection(x11_display, p_source, target, selection, + x11_window, CurrentTime); + + XFlush(x11_display); + + // Blocking wait for predicate to be True + // and remove the event from the queue. XEvent event; - XNextEvent(x11_display, &event); - if (event.type == SelectionNotify && event.xselection.requestor == x11_window) { - break; - }; - }; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + } // // Do not get any data, see how much data is there @@ -503,8 +515,9 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x &len, &dummy, &data); if (result == Success) { ret.parse_utf8((const char *)data); - } else + } else { printf("FAIL\n"); + } if (data) { XFree(data); } @@ -514,48 +527,48 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x return ret; } -static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) { +String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const { String ret; Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True); if (utf8_atom != None) { - ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom); + ret = _clipboard_get_impl(p_source, x11_window, utf8_atom); } - if (ret == "") { - ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING); + if (ret.empty()) { + ret = _clipboard_get_impl(p_source, x11_window, XA_STRING); } return ret; } String DisplayServerX11::clipboard_get() const { - _THREAD_SAFE_METHOD_ String ret; - ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); + ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window); - if (ret == "") { - ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); - }; + if (ret.empty()) { + ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); + } return ret; } int DisplayServerX11::get_screen_count() const { - _THREAD_SAFE_METHOD_ // Using Xinerama Extension int event_base, error_base; const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base); - if (!ext_okay) return 0; + if (!ext_okay) { + return 0; + } int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); XFree(xsi); return count; } -Point2i DisplayServerX11::screen_get_position(int p_screen) const { +Point2i DisplayServerX11::screen_get_position(int p_screen) const { _THREAD_SAFE_METHOD_ if (p_screen == SCREEN_OF_MAIN_WINDOW) { @@ -596,11 +609,15 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { // Using Xinerama Extension int event_base, error_base; const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base); - if (!ext_okay) return Rect2i(0, 0, 0, 0); + if (!ext_okay) { + return Rect2i(0, 0, 0, 0); + } int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - if (p_screen >= count) return Rect2i(0, 0, 0, 0); + if (p_screen >= count) { + return Rect2i(0, 0, 0, 0); + } Rect2i rect = Rect2i(xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height); XFree(xsi); @@ -608,7 +625,6 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { } int DisplayServerX11::screen_get_dpi(int p_screen) const { - _THREAD_SAFE_METHOD_ if (p_screen == SCREEN_OF_MAIN_WINDOW) { @@ -645,14 +661,15 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const { int height_mm = DisplayHeightMM(x11_display, p_screen); double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0); double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0); - if (xdpi || ydpi) + if (xdpi || ydpi) { return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1); + } //could not get dpi return 96; } -bool DisplayServerX11::screen_is_touchscreen(int p_screen) const { +bool DisplayServerX11::screen_is_touchscreen(int p_screen) const { _THREAD_SAFE_METHOD_ #ifndef _MSC_VER @@ -673,7 +690,6 @@ Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const { } DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { - _THREAD_SAFE_METHOD_ WindowID id = _create_window(p_mode, p_flags, p_rect); @@ -686,8 +702,15 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, u return id; } -void DisplayServerX11::delete_sub_window(WindowID p_id) { +void DisplayServerX11::show_window(WindowID p_id) { + _THREAD_SAFE_METHOD_ + + WindowData &wd = windows[p_id]; + + XMapWindow(x11_display, wd.x11_window); +} +void DisplayServerX11::delete_sub_window(WindowID p_id) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_id)); @@ -695,6 +718,8 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { WindowData &wd = windows[p_id]; + DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id); + while (wd.transient_children.size()) { window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID); } @@ -712,13 +737,13 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { XDestroyWindow(x11_display, wd.x11_window); if (wd.xic) { XDestroyIC(wd.xic); + wd.xic = nullptr; } windows.erase(p_id); } void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { - ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -726,19 +751,40 @@ void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p } ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) const { - ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); const WindowData &wd = windows[p_window]; return wd.instance_id; } DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const { + WindowID found_window = INVALID_WINDOW_ID; + WindowID parent_window = INVALID_WINDOW_ID; + unsigned int focus_order = 0; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + const WindowData &wd = E->get(); + + // Discard windows with no focus. + if (wd.focus_order == 0) { + continue; + } + + // Find topmost window which contains the given position. + WindowID window_id = E->key(); + Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id)); + if (win_rect.has_point(p_position)) { + // For siblings, pick the window which was focused last. + if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) { + found_window = window_id; + parent_window = wd.transient_parent; + focus_order = wd.focus_order; + } + } + } - return INVALID_WINDOW_ID; + return found_window; } void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -748,11 +794,44 @@ void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false); Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false); - XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length()); + if (_net_wm_name != None && utf8_string != None) { + XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length()); + } } -void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { +void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + const WindowData &wd = windows[p_window]; + + int event_base, error_base; + const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base); + if (ext_okay) { + Region region; + if (p_region.size() == 0) { + region = XCreateRegion(); + XRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = window_get_real_size(p_window).x; + rect.height = window_get_real_size(p_window).y; + XUnionRectWithRegion(&rect, region, region); + } else { + XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * p_region.size()); + for (int i = 0; i < p_region.size(); i++) { + points[i].x = p_region[i].x; + points[i].y = p_region[i].y; + } + region = XPolygonRegion(points, p_region.size(), EvenOddRule); + memfree(points); + } + XShapeCombineRegion(x11_display, wd.x11_window, ShapeInput, 0, 0, region, ShapeSet); + XDestroyRegion(region); + } +} +void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -761,7 +840,6 @@ void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callab } void DisplayServerX11::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -770,15 +848,14 @@ void DisplayServerX11::window_set_window_event_callback(const Callable &p_callab } void DisplayServerX11::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; wd.input_event_callback = p_callable; } -void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { +void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -787,7 +864,6 @@ void DisplayServerX11::window_set_input_text_callback(const Callable &p_callable } void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -796,7 +872,6 @@ void DisplayServerX11::window_set_drop_files_callback(const Callable &p_callable } int DisplayServerX11::window_get_current_screen(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), -1); @@ -810,20 +885,23 @@ int DisplayServerX11::window_get_current_screen(WindowID p_window) const { for (int i = 0; i < count; i++) { Point2i pos = screen_get_position(i); Size2i size = screen_get_size(i); - if ((x >= pos.x && x < pos.x + size.width) && (y >= pos.y && y < pos.y + size.height)) + if ((x >= pos.x && x < pos.x + size.width) && (y >= pos.y && y < pos.y + size.height)) { return i; + } } return 0; } -void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window) { +void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; int count = get_screen_count(); - if (p_screen >= count) return; + if (p_screen >= count) { + return; + } if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) { Point2i position = screen_get_position(p_screen); @@ -839,7 +917,6 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window } void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(p_window == p_parent); @@ -847,24 +924,34 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd_window = windows[p_window]; - ERR_FAIL_COND(wd_window.transient_parent == p_parent); + WindowID prev_parent = wd_window.transient_parent; + ERR_FAIL_COND(prev_parent == p_parent); ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient."); if (p_parent == INVALID_WINDOW_ID) { //remove transient - ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID); - ERR_FAIL_COND(!windows.has(wd_window.transient_parent)); + ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID); + ERR_FAIL_COND(!windows.has(prev_parent)); - WindowData &wd_parent = windows[wd_window.transient_parent]; + WindowData &wd_parent = windows[prev_parent]; wd_window.transient_parent = INVALID_WINDOW_ID; wd_parent.transient_children.erase(p_window); XSetTransientForHint(x11_display, wd_window.x11_window, None); + + // Set focus to parent sub window to avoid losing all focus with nested menus. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd_window.menu_type && !wd_window.no_focus) { + if (!wd_parent.no_focus) { + XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); + } + } } else { ERR_FAIL_COND(!windows.has(p_parent)); - ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); + ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); WindowData &wd_parent = windows[p_parent]; wd_window.transient_parent = p_parent; @@ -874,8 +961,47 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent } } -Point2i DisplayServerX11::window_get_position(WindowID p_window) const { +// Helper method. Assumes that the window id has already been checked and exists. +void DisplayServerX11::_update_size_hints(WindowID p_window) { + WindowData &wd = windows[p_window]; + WindowMode window_mode = window_get_mode(p_window); + XSizeHints *xsh = XAllocSizeHints(); + + // Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway + xsh->flags |= PPosition | PSize; + xsh->x = wd.position.x; + xsh->y = wd.position.y; + xsh->width = wd.size.width; + xsh->height = wd.size.height; + + if (window_mode == WINDOW_MODE_FULLSCREEN) { + // Do not set any other hints to prevent the window manager from ignoring the fullscreen flags + } else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { + // If resizing is disabled, use the forced size + xsh->flags |= PMinSize | PMaxSize; + xsh->min_width = wd.size.x; + xsh->max_width = wd.size.x; + xsh->min_height = wd.size.y; + xsh->max_height = wd.size.y; + } else { + // Otherwise, just respect min_size and max_size + if (wd.min_size != Size2i()) { + xsh->flags |= PMinSize; + xsh->min_width = wd.min_size.x; + xsh->min_height = wd.min_size.y; + } + if (wd.max_size != Size2i()) { + xsh->flags |= PMaxSize; + xsh->max_width = wd.max_size.x; + xsh->max_height = wd.max_size.y; + } + } + XSetWMNormalHints(x11_display, wd.x11_window, xsh); + XFree(xsh); +} + +Point2i DisplayServerX11::window_get_position(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); @@ -885,7 +1011,6 @@ Point2i DisplayServerX11::window_get_position(WindowID p_window) const { } void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -918,7 +1043,6 @@ void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p } void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -930,28 +1054,11 @@ void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_windo } wd.max_size = p_size; - if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); - - XFlush(x11_display); - } + _update_size_hints(p_window); + XFlush(x11_display); } -Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -961,7 +1068,6 @@ Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { } void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -973,28 +1079,11 @@ void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_windo } wd.min_size = p_size; - if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); - - XFlush(x11_display); - } + _update_size_hints(p_window); + XFlush(x11_display); } -Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -1004,7 +1093,6 @@ Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { } void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1015,8 +1103,9 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { WindowData &wd = windows[p_window]; - if (wd.size.width == size.width && wd.size.height == size.height) + if (wd.size.width == size.width && wd.size.height == size.height) { return; + } XWindowAttributes xwa; XSync(x11_display, False); @@ -1024,57 +1113,36 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { int old_w = xwa.width; int old_h = xwa.height; - // If window resizable is disabled we need to update the attributes first - XSizeHints *xsh; - xsh = XAllocSizeHints(); - if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = size.x; - xsh->max_width = size.x; - xsh->min_height = size.y; - xsh->max_height = size.y; - } else { - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - } - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); + // Update our videomode width and height + wd.size = size; + + // Update the size hints first to make sure the window size can be set + _update_size_hints(p_window); // Resize the window XResizeWindow(x11_display, wd.x11_window, size.x, size.y); - // Update our videomode width and height - wd.size = size; - for (int timeout = 0; timeout < 50; ++timeout) { XSync(x11_display, False); XGetWindowAttributes(x11_display, wd.x11_window, &xwa); - if (old_w != xwa.width || old_h != xwa.height) + if (old_w != xwa.width || old_h != xwa.height) { break; + } usleep(10000); } } -Size2i DisplayServerX11::window_get_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); const WindowData &wd = windows[p_window]; return wd.size; } -Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -1104,19 +1172,23 @@ Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { return Size2i(w, h); } -bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { - - _THREAD_SAFE_METHOD_ - +// Just a helper to reduce code duplication in `window_is_maximize_allowed` +// and `_set_wm_maximized`. +bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_atom_name) const { ERR_FAIL_COND_V(!windows.has(p_window), false); const WindowData &wd = windows[p_window]; - Atom property = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False); + Atom property = XInternAtom(x11_display, p_atom_name, False); Atom type; int format; unsigned long len; unsigned long remaining; unsigned char *data = nullptr; + bool retval = false; + + if (property == None) { + return false; + } int result = XGetWindowProperty( x11_display, @@ -1140,22 +1212,31 @@ bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { bool found_wm_act_max_vert = false; for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_act_max_horz) + if (atoms[i] == wm_act_max_horz) { found_wm_act_max_horz = true; - if (atoms[i] == wm_act_max_vert) + } + if (atoms[i] == wm_act_max_vert) { found_wm_act_max_vert = true; + } - if (found_wm_act_max_horz || found_wm_act_max_vert) - return true; + if (found_wm_act_max_horz || found_wm_act_max_vert) { + retval = true; + break; + } } - XFree(atoms); + + XFree(data); } - return false; + return retval; } -void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { +bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS"); +} +void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -1187,7 +1268,6 @@ void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { } void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { - ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -1198,17 +1278,14 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { hints.flags = 2; hints.decorations = 0; property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + if (property != None) { + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } } - if (p_enabled && window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { + if (p_enabled) { // Set the window as resizable to prevent window managers to ignore the fullscreen state flag. - XSizeHints *xsh; - - xsh = XAllocSizeHints(); - xsh->flags = 0L; - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); + _update_size_hints(p_window); } // Using EWMH -- Extended Window Manager Hints @@ -1230,36 +1307,15 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { // set bypass compositor hint Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False); unsigned long compositing_disable_on = p_enabled ? 1 : 0; - XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1); + if (bypass_compositor != None) { + XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1); + } XFlush(x11_display); if (!p_enabled) { // Reset the non-resizable flags if we un-set these before. - Size2i size = window_get_size(p_window); - XSizeHints *xsh; - xsh = XAllocSizeHints(); - if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = size.x; - xsh->max_width = size.x; - xsh->min_height = size.y; - xsh->max_height = size.y; - } else { - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - } - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); + _update_size_hints(p_window); // put back or remove decorations according to the last set borderless state Hints hints; @@ -1267,12 +1323,13 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { hints.flags = 2; hints.decorations = window_get_flag(WINDOW_FLAG_BORDERLESS, p_window) ? 0 : 1; property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + if (property != None) { + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } } } void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1318,13 +1375,13 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_FULLSCREEN: { //Remove full-screen + wd.fullscreen = false; + _set_wm_fullscreen(p_window, false); //un-maximize required for always on top bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window); - wd.fullscreen = false; - window_set_position(wd.last_position_before_fs, p_window); if (on_top) { @@ -1333,7 +1390,6 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_MAXIMIZED: { - _set_wm_maximized(p_window, false); } break; } @@ -1371,22 +1427,21 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_FULLSCREEN: { wd.last_position_before_fs = wd.position; + if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) { _set_wm_maximized(p_window, true); } - _set_wm_fullscreen(p_window, true); + wd.fullscreen = true; + _set_wm_fullscreen(p_window, true); } break; case WINDOW_MODE_MAXIMIZED: { - _set_wm_maximized(p_window, true); - } break; } } DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); @@ -1395,60 +1450,20 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c if (wd.fullscreen) { //if fullscreen, it's not in another mode return WINDOW_MODE_FULLSCREEN; } - { //test maximized - // Using EWMH -- Extended Window Manager Hints - Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = nullptr; - bool retval = false; - int result = XGetWindowProperty( - x11_display, - wd.x11_window, - property, - 0, - 1024, - False, - XA_ATOM, - &type, - &format, - &len, - &remaining, - &data); - - if (result == Success && data) { - Atom *atoms = (Atom *)data; - Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); - Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False); - bool found_wm_max_horz = false; - bool found_wm_max_vert = false; - - for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_max_horz) - found_wm_max_horz = true; - if (atoms[i] == wm_max_vert) - found_wm_max_vert = true; - - if (found_wm_max_horz && found_wm_max_vert) { - retval = true; - break; - } - } - - XFree(data); - } - - if (retval) { - return WINDOW_MODE_MAXIMIZED; - } + // Test maximized. + // Using EWMH -- Extended Window Manager Hints + if (_window_maximize_check(p_window, "_NET_WM_STATE")) { + return WINDOW_MODE_MAXIMIZED; } - { // test minimzed + { // Test minimized. // Using ICCCM -- Inter-Client Communication Conventions Manual Atom property = XInternAtom(x11_display, "WM_STATE", True); + if (property == None) { + return WINDOW_MODE_WINDOWED; + } + Atom type; int format; unsigned long len; @@ -1479,13 +1494,12 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c } } - // all other discarded, return windowed. + // All other discarded, return windowed. return WINDOW_MODE_WINDOWED; } void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1493,46 +1507,21 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - if (p_enabled) { - Size2i size = window_get_size(p_window); - - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = size.x; - xsh->max_width = size.x; - xsh->min_height = size.y; - xsh->max_height = size.y; - } else { - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - } - - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); - wd.resize_disabled = p_enabled; - XFlush(x11_display); + _update_size_hints(p_window); + XFlush(x11_display); } break; case WINDOW_FLAG_BORDERLESS: { - Hints hints; Atom property; hints.flags = 2; hints.decorations = p_enabled ? 0 : 1; property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); - XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + if (property != None) { + XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } // Preserve window size window_set_size(window_get_size(p_window), p_window); @@ -1540,7 +1529,6 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo wd.borderless = p_enabled; } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { - ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active."); if (p_enabled && wd.fullscreen) { _set_wm_maximized(p_window, true); @@ -1573,8 +1561,8 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } } } -bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const { +bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), false); @@ -1582,15 +1570,12 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { - return wd.resize_disabled; } break; case WINDOW_FLAG_BORDERLESS: { - bool borderless = wd.borderless; Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); if (prop != None) { - Atom type; int format; unsigned long len; @@ -1608,7 +1593,6 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co return borderless; } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { - return wd.on_top; } break; case WINDOW_FLAG_TRANSPARENT: { @@ -1622,7 +1606,6 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co } void DisplayServerX11::window_request_attention(WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1649,7 +1632,6 @@ void DisplayServerX11::window_request_attention(WindowID p_window) { } void DisplayServerX11::window_move_to_foreground(WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1671,12 +1653,11 @@ void DisplayServerX11::window_move_to_foreground(WindowID p_window) { } bool DisplayServerX11::window_can_draw(WindowID p_window) const { - //this seems to be all that is provided by X11 return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED; } -bool DisplayServerX11::can_any_window_draw() const { +bool DisplayServerX11::can_any_window_draw() const { _THREAD_SAFE_METHOD_ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { @@ -1689,7 +1670,6 @@ bool DisplayServerX11::can_any_window_draw() const { } void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1697,18 +1677,25 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win wd.im_active = p_active; - if (!wd.xic) + if (!wd.xic) { return; + } + // Block events polling while changing input focus + // because it triggers some event polling internally. if (p_active) { - XSetICFocus(wd.xic); + { + MutexLock mutex_lock(events_mutex); + XSetICFocus(wd.xic); + } window_set_ime_position(wd.im_position, p_window); } else { + MutexLock mutex_lock(events_mutex); XUnsetICFocus(wd.xic); } } -void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { +void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1716,19 +1703,26 @@ void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_ wd.im_position = p_pos; - if (!wd.xic) + if (!wd.xic) { return; + } ::XPoint spot; spot.x = short(p_pos.x); spot.y = short(p_pos.y); XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, nullptr); - XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + + { + // Block events polling during this call + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + } + XFree(preedit_attr); } void DisplayServerX11::cursor_set_shape(CursorShape p_shape) { - _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX(p_shape, CURSOR_MAX); @@ -1751,15 +1745,15 @@ void DisplayServerX11::cursor_set_shape(CursorShape p_shape) { current_cursor = p_shape; } + DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const { return current_cursor; } -void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { +void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { _THREAD_SAFE_METHOD_ if (p_cursor.is_valid()) { - Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); if (cursor_c) { @@ -1864,62 +1858,132 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape } } -DisplayServerX11::LatinKeyboardVariant DisplayServerX11::get_latin_keyboard_variant() const { - _THREAD_SAFE_METHOD_ - - XkbDescRec *xkbdesc = XkbAllocKeyboard(); - ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY); +int DisplayServerX11::keyboard_get_layout_count() const { + int _group_count = 0; + XkbDescRec *kbd = XkbAllocKeyboard(); + if (kbd) { + kbd->dpy = x11_display; + XkbGetControls(x11_display, XkbAllControlsMask, kbd); + XkbGetNames(x11_display, XkbSymbolsNameMask, kbd); + + const Atom *groups = kbd->names->groups; + if (kbd->ctrls != NULL) { + _group_count = kbd->ctrls->num_groups; + } else { + while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { + _group_count++; + } + } + XkbFreeKeyboard(kbd, 0, true); + } + return _group_count; +} - XkbGetNames(x11_display, XkbSymbolsNameMask, xkbdesc); - ERR_FAIL_COND_V(!xkbdesc->names, LATIN_KEYBOARD_QWERTY); - ERR_FAIL_COND_V(!xkbdesc->names->symbols, LATIN_KEYBOARD_QWERTY); +int DisplayServerX11::keyboard_get_current_layout() const { + XkbStateRec state; + XkbGetState(x11_display, XkbUseCoreKbd, &state); + return state.group; +} - char *layout = XGetAtomName(x11_display, xkbdesc->names->symbols); - ERR_FAIL_COND_V(!layout, LATIN_KEYBOARD_QWERTY); +void DisplayServerX11::keyboard_set_current_layout(int p_index) { + ERR_FAIL_INDEX(p_index, keyboard_get_layout_count()); + XkbLockGroup(x11_display, XkbUseCoreKbd, p_index); +} - Vector<String> info = String(layout).split("+"); - ERR_FAIL_INDEX_V(1, info.size(), LATIN_KEYBOARD_QWERTY); +String DisplayServerX11::keyboard_get_layout_language(int p_index) const { + String ret; + XkbDescRec *kbd = XkbAllocKeyboard(); + if (kbd) { + kbd->dpy = x11_display; + XkbGetControls(x11_display, XkbAllControlsMask, kbd); + XkbGetNames(x11_display, XkbSymbolsNameMask, kbd); + XkbGetNames(x11_display, XkbGroupNamesMask, kbd); + + int _group_count = 0; + const Atom *groups = kbd->names->groups; + if (kbd->ctrls != NULL) { + _group_count = kbd->ctrls->num_groups; + } else { + while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { + _group_count++; + } + } - if (info[1].find("colemak") != -1) { - return LATIN_KEYBOARD_COLEMAK; - } else if (info[1].find("qwertz") != -1) { - return LATIN_KEYBOARD_QWERTZ; - } else if (info[1].find("azerty") != -1) { - return LATIN_KEYBOARD_AZERTY; - } else if (info[1].find("qzerty") != -1) { - return LATIN_KEYBOARD_QZERTY; - } else if (info[1].find("dvorak") != -1) { - return LATIN_KEYBOARD_DVORAK; - } else if (info[1].find("neo") != -1) { - return LATIN_KEYBOARD_NEO; + Atom names = kbd->names->symbols; + if (names != None) { + char *name = XGetAtomName(x11_display, names); + Vector<String> info = String(name).split("+"); + if (p_index >= 0 && p_index < _group_count) { + if (p_index + 1 < info.size()) { + ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols. + } else { + ret = "en"; // No symbol for layout fallback to "en". + } + } else { + ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ")."); + } + XFree(name); + } + XkbFreeKeyboard(kbd, 0, true); } + return ret.substr(0, 2); +} - return LATIN_KEYBOARD_QWERTY; +String DisplayServerX11::keyboard_get_layout_name(int p_index) const { + String ret; + XkbDescRec *kbd = XkbAllocKeyboard(); + if (kbd) { + kbd->dpy = x11_display; + XkbGetControls(x11_display, XkbAllControlsMask, kbd); + XkbGetNames(x11_display, XkbSymbolsNameMask, kbd); + XkbGetNames(x11_display, XkbGroupNamesMask, kbd); + + int _group_count = 0; + const Atom *groups = kbd->names->groups; + if (kbd->ctrls != NULL) { + _group_count = kbd->ctrls->num_groups; + } else { + while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { + _group_count++; + } + } + + if (p_index >= 0 && p_index < _group_count) { + char *full_name = XGetAtomName(x11_display, groups[p_index]); + ret.parse_utf8(full_name); + XFree(full_name); + } else { + ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ")."); + } + XkbFreeKeyboard(kbd, 0, true); + } + return ret; } DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) { - - Atom actual_type; - int actual_format; - unsigned long nitems; - unsigned long bytes_after; + Atom actual_type = None; + int actual_format = 0; + unsigned long nitems = 0; + unsigned long bytes_after = 0; unsigned char *ret = nullptr; int read_bytes = 1024; - //Keep trying to read the property until there are no - //bytes unread. - do { - if (ret != nullptr) - XFree(ret); + // Keep trying to read the property until there are no bytes unread. + if (p_property != None) { + do { + if (ret != nullptr) { + XFree(ret); + } - XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType, - &actual_type, &actual_format, &nitems, &bytes_after, - &ret); + XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, + &ret); - read_bytes *= 2; + read_bytes *= 2; - } while (bytes_after != 0); + } while (bytes_after != 0); + } Property p = { ret, actual_format, (int)nitems, actual_type }; @@ -1927,36 +1991,36 @@ DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, } static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count) { - static const char *target_type = "text/uri-list"; for (int i = 0; i < p_count; i++) { - Atom atom = p_list[i]; - if (atom != None && String(XGetAtomName(p_display, atom)) == target_type) + if (atom != None && String(XGetAtomName(p_display, atom)) == target_type) { return atom; + } } return None; } static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) { - static const char *target_type = "text/uri-list"; - if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type) + if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type) { return p_t1; + } - if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type) + if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type) { return p_t2; + } - if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type) + if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type) { return p_t3; + } return None; } void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) { - state->set_shift((p_x11_state & ShiftMask)); state->set_control((p_x11_state & ControlMask)); state->set_alt((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt @@ -1964,7 +2028,6 @@ void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<Inp } unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) { - unsigned int mask = 1 << (p_x11_button - 1); if (p_x11_type == ButtonPress) { @@ -1976,8 +2039,7 @@ unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button return last_button_state; } -void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) { - +void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) { WindowData wd = windows[p_window]; // X11 functions don't know what const is XKeyEvent *xkeyevent = p_event; @@ -2021,7 +2083,6 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, } if (xkeyevent->type == KeyPress && wd.xic) { - Status status; #ifdef X_HAVE_UTF8_STRING int utf8len = 8; @@ -2041,8 +2102,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode); unsigned int physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); - if (keycode >= 'a' && keycode <= 'z') + if (keycode >= 'a' && keycode <= 'z') { keycode -= 'a' - 'A'; + } String tmp; tmp.parse_utf8(utf8string, utf8bytes); @@ -2077,7 +2139,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, k->set_shift(true); } - InputFilter::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->accumulate_input_event(k); } memfree(utf8string); return; @@ -2085,7 +2147,6 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, memfree(utf8string); #else do { - int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status); xmbstring[mnbytes] = '\0'; @@ -2115,7 +2176,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, /* Phase 4, determine if event must be filtered */ // This seems to be a side-effect of using XIM. - // XEventFilter looks like a core X11 function, + // XFilterEvent looks like a core X11 function, // but it's actually just used to see if we must // ignore a deadkey, or events XIM determines // must not reach the actual gui. @@ -2149,18 +2210,16 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, // Echo characters in X11 are a keyrelease and a keypress // one after the other with the (almot) same timestamp. - // To detect them, i use XPeekEvent and check that their - // difference in time is below a threshold. + // To detect them, i compare to the next event in list and + // check that their difference in time is below a threshold. if (xkeyevent->type != KeyPress) { - p_echo = false; // make sure there are events pending, // so this call won't block. - if (XPending(x11_display) > 0) { - XEvent peek_event; - XPeekEvent(x11_display, &peek_event); + if (p_event_index + 1 < p_events.size()) { + XEvent &peek_event = p_events[p_event_index + 1]; // I'm using a threshold of 5 msecs, // since sometimes there seems to be a little @@ -2175,9 +2234,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, KeySym rk; XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr); if (rk == keysym_keycode) { - XEvent event; - XNextEvent(x11_display, &event); //erase next event - _handle_key_event(p_window, (XKeyEvent *)&event, true); + // Consume to next event. + ++p_event_index; + _handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true); return; //ignore current, echo next } } @@ -2192,8 +2251,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, k->set_pressed(keypress); - if (keycode >= 'a' && keycode <= 'z') + if (keycode >= 'a' && keycode <= 'z') { keycode -= 'a' - 'A'; + } k->set_keycode(keycode); k->set_physical_keycode(physical_keycode); @@ -2210,29 +2270,89 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, //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_keycode() == KEY_SHIFT) + if (k->get_keycode() == KEY_SHIFT) { k->set_shift(false); - else if (k->get_keycode() == KEY_CONTROL) + } else if (k->get_keycode() == KEY_CONTROL) { k->set_control(false); - else if (k->get_keycode() == KEY_ALT) + } else if (k->get_keycode() == KEY_ALT) { k->set_alt(false); - else if (k->get_keycode() == KEY_META) + } else if (k->get_keycode() == KEY_META) { k->set_metakey(false); + } } - bool last_is_pressed = InputFilter::get_singleton()->is_key_pressed(k->get_keycode()); + 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); } } - InputFilter::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->accumulate_input_event(k); +} + +void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) { + XEvent respond; + if (p_event->target == XInternAtom(x11_display, "UTF8_STRING", 0) || + p_event->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || + p_event->target == XInternAtom(x11_display, "TEXT", 0) || + p_event->target == XA_STRING || + p_event->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || + p_event->target == XInternAtom(x11_display, "text/plain", 0)) { + // Directly using internal clipboard because we know our window + // is the owner during a selection request. + CharString clip = internal_clipboard.utf8(); + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + p_event->target, + 8, + PropModeReplace, + (unsigned char *)clip.get_data(), + clip.length()); + respond.xselection.property = p_event->property; + } else if (p_event->target == XInternAtom(x11_display, "TARGETS", 0)) { + Atom data[7]; + data[0] = XInternAtom(x11_display, "TARGETS", 0); + data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); + data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); + data[3] = XInternAtom(x11_display, "TEXT", 0); + data[4] = XA_STRING; + data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); + data[6] = XInternAtom(x11_display, "text/plain", 0); + + XChangeProperty(x11_display, + p_event->requestor, + p_event->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *)&data, + sizeof(data) / sizeof(data[0])); + respond.xselection.property = p_event->property; + + } else { + char *targetname = XGetAtomName(x11_display, p_event->target); + printf("No Target '%s'\n", targetname); + if (targetname) { + XFree(targetname); + } + respond.xselection.property = None; + } + + respond.xselection.type = SelectionNotify; + respond.xselection.display = p_event->display; + respond.xselection.requestor = p_event->requestor; + respond.xselection.selection = p_event->selection; + respond.xselection.target = p_event->target; + respond.xselection.time = p_event->time; + + XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond); + XFlush(x11_display); } void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, ::XPointer call_data) { - WARN_PRINT("Input method stopped"); DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data); ds->xim = nullptr; @@ -2243,7 +2363,6 @@ void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, } void DisplayServerX11::_window_changed(XEvent *event) { - WindowID window_id = MAIN_WINDOW_ID; // Assign the event to the relevant window @@ -2308,7 +2427,6 @@ void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) { } void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) { - Variant ev = p_event; Variant *evp = &ev; Variant ret; @@ -2344,18 +2462,119 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev wd.event_callback.call((const Variant **)&eventp, 1, ret, ce); } } -void DisplayServerX11::process_events() { +void DisplayServerX11::_poll_events_thread(void *ud) { + DisplayServerX11 *display_server = (DisplayServerX11 *)ud; + display_server->_poll_events(); +} + +Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) { + // Just accept all events. + return True; +} + +void DisplayServerX11::_poll_events() { + int x11_fd = ConnectionNumber(x11_display); + fd_set in_fds; + + while (!events_thread_done) { + XFlush(x11_display); + + FD_ZERO(&in_fds); + FD_SET(x11_fd, &in_fds); + + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = 1; + + // Wait for next event or timeout. + int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + if (num_ready_fds < 0) { + ERR_PRINT("_poll_events: select error: " + itos(errno)); + } + + // Process events from the queue. + { + MutexLock mutex_lock(events_mutex); + + // Non-blocking wait for next event + // and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) { + // Check if the input manager wants to process the event. + if (XFilterEvent(&ev, None)) { + // Event has been filtered by the Input Manager, + // it has to be ignored and a new one will be received. + continue; + } + + // Handle selection request events directly in the event thread, because + // communication through the x server takes several events sent back and forth + // and we don't want to block other programs while processing only one each frame. + if (ev.type == SelectionRequest) { + _handle_selection_request_event(&(ev.xselectionrequest)); + continue; + } + + polled_events.push_back(ev); + } + } + } +} + +void DisplayServerX11::process_events() { _THREAD_SAFE_METHOD_ +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED + static int frame = 0; + ++frame; +#endif + + if (app_focused) { + //verify that one of the windows has focus, else send focus out notification + bool focus_found = false; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (E->get().focused) { + focus_found = true; + break; + } + } + + if (!focus_found) { + uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus; + + if (delta > 250) { + //X11 can go between windows and have no focus for a while, when creating them or something else. Use this as safety to avoid unnecessary focus in/outs. + if (OS::get_singleton()->get_main_loop()) { + DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n"); + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } + app_focused = false; + } + } else { + time_since_no_focus = OS::get_singleton()->get_ticks_msec(); + } + } + do_mouse_warp = false; // Is the current mouse mode one where it needs to be grabbed. bool mouse_mode_grab = mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED; - while (XPending(x11_display) > 0) { - XEvent event; - XNextEvent(x11_display, &event); + xi.pressure = 0; + xi.tilt = Vector2(); + xi.pressure_supported = false; + + LocalVector<XEvent> events; + { + // Block events polling while flushing events. + MutexLock mutex_lock(events_mutex); + events = polled_events; + polled_events.clear(); + } + + for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { + XEvent &event = events[event_index]; WindowID window_id = MAIN_WINDOW_ID; @@ -2367,14 +2586,8 @@ void DisplayServerX11::process_events() { } } - if (XFilterEvent(&event, None)) { - continue; - } - if (XGetEventData(x11_display, &event.xcookie)) { - if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { - XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; int index = event_data->detail; Vector2 pos = Vector2(event_data->event_x, event_data->event_y); @@ -2399,9 +2612,6 @@ void DisplayServerX11::process_events() { double rel_x = 0.0; double rel_y = 0.0; - double pressure = 0.0; - double tilt_x = 0.0; - double tilt_y = 0.0; if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) { rel_x = *values; @@ -2414,24 +2624,41 @@ void DisplayServerX11::process_events() { } if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) { - pressure = *values; + Map<int, Vector2>::Element *pen_pressure = xi.pen_pressure_range.find(device_id); + if (pen_pressure) { + Vector2 pen_pressure_range = pen_pressure->value(); + if (pen_pressure_range != Vector2()) { + xi.pressure_supported = true; + xi.pressure = (*values - pen_pressure_range[0]) / + (pen_pressure_range[1] - pen_pressure_range[0]); + } + } + values++; } if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) { - tilt_x = *values; + Map<int, Vector2>::Element *pen_tilt_x = xi.pen_tilt_x_range.find(device_id); + if (pen_tilt_x) { + Vector2 pen_tilt_x_range = pen_tilt_x->value(); + if (pen_tilt_x_range != Vector2()) { + xi.tilt.x = ((*values - pen_tilt_x_range[0]) / (pen_tilt_x_range[1] - pen_tilt_x_range[0])) * 2 - 1; + } + } + values++; } if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) { - tilt_y = *values; - } + Map<int, Vector2>::Element *pen_tilt_y = xi.pen_tilt_y_range.find(device_id); + if (pen_tilt_y) { + Vector2 pen_tilt_y_range = pen_tilt_y->value(); + if (pen_tilt_y_range != Vector2()) { + xi.tilt.y = ((*values - pen_tilt_y_range[0]) / (pen_tilt_y_range[1] - pen_tilt_y_range[0])) * 2 - 1; + } + } - Map<int, Vector3>::Element *pen_info = xi.pen_devices.find(device_id); - if (pen_info) { - Vector3 mult = pen_info->value(); - if (mult.x != 0.0) xi.pressure = pressure / mult.x; - if ((mult.y != 0.0) && (mult.z != 0.0)) xi.tilt = Vector2(tilt_x / mult.y, tilt_y / mult.z); + values++; } // https://bugs.freedesktop.org/show_bug.cgi?id=71609 @@ -2467,7 +2694,6 @@ void DisplayServerX11::process_events() { //XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch); case XI_TouchEnd: { - bool is_begin = event_data->evtype == XI_TouchBegin; Ref<InputEventScreenTouch> st; @@ -2478,39 +2704,39 @@ void DisplayServerX11::process_events() { st->set_pressed(is_begin); if (is_begin) { - if (xi.state.has(index)) // Defensive + if (xi.state.has(index)) { // Defensive break; + } xi.state[index] = pos; if (xi.state.size() == 1) { // X11 may send a motion event when a touch gesture begins, that would result // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out xi.mouse_pos_to_filter = pos; } - InputFilter::get_singleton()->accumulate_input_event(st); + Input::get_singleton()->accumulate_input_event(st); } else { - if (!xi.state.has(index)) // Defensive + if (!xi.state.has(index)) { // Defensive break; + } xi.state.erase(index); - InputFilter::get_singleton()->accumulate_input_event(st); + Input::get_singleton()->accumulate_input_event(st); } } break; case XI_TouchUpdate: { - Map<int, Vector2>::Element *curr_pos_elem = xi.state.find(index); if (!curr_pos_elem) { // Defensive break; } if (curr_pos_elem->value() != pos) { - Ref<InputEventScreenDrag> sd; sd.instance(); sd->set_window_id(window_id); sd->set_index(index); sd->set_position(pos); sd->set_relative(pos - curr_pos_elem->value()); - InputFilter::get_singleton()->accumulate_input_event(sd); + Input::get_singleton()->accumulate_input_event(sd); curr_pos_elem->value() = pos; } @@ -2522,44 +2748,84 @@ void DisplayServerX11::process_events() { XFreeEventData(x11_display, &event.xcookie); switch (event.type) { - case Expose: + case MapNotify: { + DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id); + + const WindowData &wd = windows[window_id]; + + // Set focus when menu window is started. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd.menu_type && !wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); + } + } break; + + case Expose: { + DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count); + Main::force_redraw(); - break; + } break; - case NoExpose: - minimized = true; - break; + case NoExpose: { + DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id); + + windows[window_id].minimized = true; + } break; case VisibilityNotify: { + DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state); + XVisibilityEvent *visibility = (XVisibilityEvent *)&event; - minimized = (visibility->state == VisibilityFullyObscured); + windows[window_id].minimized = (visibility->state == VisibilityFullyObscured); } break; + case LeaveNotify: { + DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); + if (!mouse_mode_grab) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); } } break; + case EnterNotify: { + DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); + if (!mouse_mode_grab) { _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); } } break; - case FocusIn: - minimized = false; - window_has_focus = true; - _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN); - window_focused = true; + + case FocusIn: { + DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); + + WindowData &wd = windows[window_id]; + + wd.focused = true; + + if (wd.xic) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XSetICFocus(wd.xic); + } + + // Keep track of focus order for overlapping windows. + static unsigned int focus_order = 0; + wd.focus_order = ++focus_order; + + _send_window_event(wd, WINDOW_EVENT_FOCUS_IN); if (mouse_mode_grab) { // Show and update the cursor if confined and the window regained focus. for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - - if (mouse_mode == MOUSE_MODE_CONFINED) + if (mouse_mode == MOUSE_MODE_CONFINED) { XUndefineCursor(x11_display, E->get().x11_window); - else if (mouse_mode == MOUSE_MODE_CAPTURED) // or re-hide it in captured mode + } else if (mouse_mode == MOUSE_MODE_CAPTURED) { // or re-hide it in captured mode XDefineCursor(x11_display, E->get().x11_window, null_cursor); + } XGrabPointer( x11_display, E->get().x11_window, True, @@ -2573,20 +2839,34 @@ void DisplayServerX11::process_events() { XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); }*/ #endif - if (windows[window_id].xic) { - XSetICFocus(windows[window_id].xic); + + if (!app_focused) { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } + app_focused = true; } - break; + } break; + + case FocusOut: { + DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); - case FocusOut: - window_has_focus = false; - InputFilter::get_singleton()->release_pressed_events(); - _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); - window_focused = false; + WindowData &wd = windows[window_id]; + + wd.focused = false; + + if (wd.xic) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); + XUnsetICFocus(wd.xic); + } + + Input::get_singleton()->release_pressed_events(); + _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT); if (mouse_mode_grab) { for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - //dear X11, I try, I really try, but you never work, you do whathever you want. if (mouse_mode == MOUSE_MODE_CAPTURED) { // Show the cursor if we're in captured mode so it doesn't look weird. @@ -2603,27 +2883,34 @@ void DisplayServerX11::process_events() { // Release every pointer to avoid sticky points for (Map<int, Vector2>::Element *E = xi.state.front(); E; E = E->next()) { - Ref<InputEventScreenTouch> st; st.instance(); st->set_index(E->key()); st->set_window_id(window_id); st->set_position(E->get()); - InputFilter::get_singleton()->accumulate_input_event(st); + Input::get_singleton()->accumulate_input_event(st); } xi.state.clear(); #endif - if (windows[window_id].xic) { - XSetICFocus(windows[window_id].xic); + } break; + + case ConfigureNotify: { + DEBUG_LOG_X11("[%u] ConfigureNotify window=%lu (%u), event=%lu, above=%lu, override_redirect=%u \n", frame, event.xconfigure.window, window_id, event.xconfigure.event, event.xconfigure.above, event.xconfigure.override_redirect); + + const WindowData &wd = windows[window_id]; + + // Set focus when menu window is re-used. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (wd.menu_type && !wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); } - break; - case ConfigureNotify: _window_changed(&event); - break; + } break; + case ButtonPress: case ButtonRelease: { - /* exit in case of a mouse button press */ last_timestamp = event.xbutton.time; if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -2637,24 +2924,33 @@ void DisplayServerX11::process_events() { mb->set_window_id(window_id); _get_key_modifier_state(event.xbutton.state, mb); mb->set_button_index(event.xbutton.button); - if (mb->get_button_index() == 2) + if (mb->get_button_index() == 2) { mb->set_button_index(3); - else if (mb->get_button_index() == 3) + } else if (mb->get_button_index() == 3) { mb->set_button_index(2); + } mb->set_button_mask(_get_mouse_button_state(mb->get_button_index(), event.xbutton.type)); mb->set_position(Vector2(event.xbutton.x, event.xbutton.y)); mb->set_global_position(mb->get_position()); mb->set_pressed((event.type == ButtonPress)); + const WindowData &wd = windows[window_id]; + if (event.type == ButtonPress) { + DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index()); + + // Ensure window focus on click. + // RevertToPointerRoot is used to make sure we don't lose all focus in case + // a subwindow and its parent are both destroyed. + if (!wd.no_focus) { + XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); + } uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms; if (mb->get_button_index() == last_click_button_index) { - if (diff < 400 && Vector2(last_click_pos).distance_to(Vector2(event.xbutton.x, event.xbutton.y)) < 5) { - last_click_ms = 0; last_click_pos = Point2i(-100, -100); last_click_button_index = -1; @@ -2669,13 +2965,39 @@ void DisplayServerX11::process_events() { last_click_ms += diff; last_click_pos = Point2i(event.xbutton.x, event.xbutton.y); } + } else { + DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index()); + + if (!wd.focused) { + // Propagate the event to the focused window, + // because it's received only on the topmost window. + // Note: This is needed for drag & drop to work between windows, + // because the engine expects events to keep being processed + // on the same window dragging started. + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + const WindowData &wd_other = E->get(); + WindowID window_id_other = E->key(); + if (wd_other.focused) { + if (window_id_other != window_id) { + int x, y; + Window child; + XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child); + + mb->set_window_id(window_id_other); + mb->set_position(Vector2(x, y)); + mb->set_global_position(mb->get_position()); + Input::get_singleton()->accumulate_input_event(mb); + } + break; + } + } + } } - InputFilter::get_singleton()->accumulate_input_event(mb); + Input::get_singleton()->accumulate_input_event(mb); } break; case MotionNotify: { - // The X11 API requires filtering one-by-one through the motion // notify events, in order to figure out which event is the one // generated by warping the mouse pointer. @@ -2687,11 +3009,11 @@ void DisplayServerX11::process_events() { break; } - if (XPending(x11_display) > 0) { - XEvent tevent; - XPeekEvent(x11_display, &tevent); - if (tevent.type == MotionNotify) { - XNextEvent(x11_display, &event); + if (event_index + 1 < events.size()) { + const XEvent &next_event = events[event_index + 1]; + if (next_event.type == MotionNotify) { + ++event_index; + event = next_event; } else { break; } @@ -2719,6 +3041,9 @@ void DisplayServerX11::process_events() { break; } + const WindowData &wd = windows[window_id]; + bool focused = wd.focused; + if (mouse_mode == MOUSE_MODE_CAPTURED) { if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) { break; @@ -2727,11 +3052,10 @@ void DisplayServerX11::process_events() { Point2i new_center = pos; pos = last_mouse_pos + xi.relative_motion; center = new_center; - do_mouse_warp = window_has_focus; // warp the cursor if we're focused in + do_mouse_warp = focused; // warp the cursor if we're focused in } if (!last_mouse_pos_valid) { - last_mouse_pos = pos; last_mouse_pos_valid = true; } @@ -2763,18 +3087,19 @@ void DisplayServerX11::process_events() { mm.instance(); mm->set_window_id(window_id); - mm->set_pressure(xi.pressure); + if (xi.pressure_supported) { + mm->set_pressure(xi.pressure); + } else { + mm->set_pressure((mouse_get_button_state() & (1 << (BUTTON_LEFT - 1))) ? 1.0f : 0.0f); + } mm->set_tilt(xi.tilt); - // Make the absolute position integral so it doesn't look _too_ weird :) - Point2i posi(pos); - _get_key_modifier_state(event.xmotion.state, mm); mm->set_button_mask(mouse_get_button_state()); - mm->set_position(posi); - mm->set_global_position(posi); - InputFilter::get_singleton()->set_mouse_position(posi); - mm->set_speed(InputFilter::get_singleton()->get_last_mouse_speed()); + mm->set_position(pos); + mm->set_global_position(pos); + Input::get_singleton()->set_mouse_position(pos); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); mm->set_relative(rel); @@ -2784,85 +3109,47 @@ void DisplayServerX11::process_events() { // Don't propagate the motion event unless we have focus // this is so that the relative motion doesn't get messed up // after we regain focus. - if (window_has_focus || !mouse_mode_grab) - InputFilter::get_singleton()->accumulate_input_event(mm); + if (focused) { + Input::get_singleton()->accumulate_input_event(mm); + } else { + // Propagate the event to the focused window, + // because it's received only on the topmost window. + // Note: This is needed for drag & drop to work between windows, + // because the engine expects events to keep being processed + // on the same window dragging started. + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + const WindowData &wd_other = E->get(); + if (wd_other.focused) { + int x, y; + Window child; + XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child); + + Point2i pos_focused(x, y); + + mm->set_window_id(E->key()); + mm->set_position(pos_focused); + mm->set_global_position(pos_focused); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + Input::get_singleton()->accumulate_input_event(mm); + + break; + } + } + } } break; case KeyPress: case KeyRelease: { - last_timestamp = event.xkey.time; // key event is a little complex, so // it will be handled in its own function. - _handle_key_event(window_id, (XKeyEvent *)&event); - } break; - case SelectionRequest: { - - XSelectionRequestEvent *req; - XEvent e, respond; - e = event; - - req = &(e.xselectionrequest); - if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) || - req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || - req->target == XInternAtom(x11_display, "TEXT", 0) || - req->target == XA_STRING || - req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || - req->target == XInternAtom(x11_display, "text/plain", 0)) { - CharString clip = clipboard_get().utf8(); - XChangeProperty(x11_display, - req->requestor, - req->property, - req->target, - 8, - PropModeReplace, - (unsigned char *)clip.get_data(), - clip.length()); - respond.xselection.property = req->property; - } else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) { - - Atom data[7]; - data[0] = XInternAtom(x11_display, "TARGETS", 0); - data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); - data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); - data[3] = XInternAtom(x11_display, "TEXT", 0); - data[4] = XA_STRING; - data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); - data[6] = XInternAtom(x11_display, "text/plain", 0); - - XChangeProperty(x11_display, - req->requestor, - req->property, - XA_ATOM, - 32, - PropModeReplace, - (unsigned char *)&data, - sizeof(data) / sizeof(data[0])); - respond.xselection.property = req->property; - - } else { - char *targetname = XGetAtomName(x11_display, req->target); - printf("No Target '%s'\n", targetname); - if (targetname) - XFree(targetname); - respond.xselection.property = None; - } - - respond.xselection.type = SelectionNotify; - respond.xselection.display = req->display; - respond.xselection.requestor = req->requestor; - respond.xselection.selection = req->selection; - respond.xselection.target = req->target; - respond.xselection.time = req->time; - XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond); - XFlush(x11_display); + _handle_key_event(window_id, (XKeyEvent *)&event, events, event_index); } break; case SelectionNotify: if (event.xselection.target == requested) { - Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0)); Vector<String> files = String((char *)p.data).split("\n", false); @@ -2901,7 +3188,6 @@ void DisplayServerX11::process_events() { } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_enter) { - //File(s) have been dragged over the window, check for supported target (text/uri-list) xdnd_version = (event.xclient.data.l[1] >> 24); Window source = event.xclient.data.l[0]; @@ -2909,10 +3195,10 @@ void DisplayServerX11::process_events() { if (more_than_3) { Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False)); requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems); - } else + } else { requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]); + } } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_position) { - //xdnd position event, reply with an XDND status message //just depending on type of data for now XClientMessageEvent m; @@ -2931,13 +3217,13 @@ void DisplayServerX11::process_events() { XSendEvent(x11_display, event.xclient.data.l[0], False, NoEventMask, (XEvent *)&m); XFlush(x11_display); } else if ((unsigned int)event.xclient.message_type == (unsigned int)xdnd_drop) { - if (requested != None) { xdnd_source_window = event.xclient.data.l[0]; - if (xdnd_version >= 1) + if (xdnd_version >= 1) { XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, event.xclient.data.l[2]); - else + } else { XConvertSelection(x11_display, xdnd_selection, requested, XInternAtom(x11_display, "PRIMARY", 0), windows[window_id].x11_window, CurrentTime); + } } else { //Reply that we're not interested. XClientMessageEvent m; @@ -2962,7 +3248,6 @@ void DisplayServerX11::process_events() { XFlush(x11_display); if (do_mouse_warp) { - XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window, 0, 0, 0, 0, (int)windows[MAIN_WINDOW_ID].size.width / 2, (int)windows[MAIN_WINDOW_ID].size.height / 2); @@ -2978,7 +3263,7 @@ void DisplayServerX11::process_events() { */ } - InputFilter::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_accumulated_events(); } void DisplayServerX11::release_rendering_thread() { @@ -2994,7 +3279,6 @@ void DisplayServerX11::_update_context(WindowData &wd) { XClassHint *classHint = XAllocClassHint(); if (classHint) { - CharString name_str; switch (context) { case CONTEXT_EDITOR: @@ -3027,8 +3311,8 @@ void DisplayServerX11::_update_context(WindowData &wd) { XFree(classHint); } } -void DisplayServerX11::set_context(Context p_context) { +void DisplayServerX11::set_context(Context p_context) { _THREAD_SAFE_METHOD_ context = p_context; @@ -3037,6 +3321,7 @@ void DisplayServerX11::set_context(Context p_context) { _update_context(E->get()); } } + void DisplayServerX11::set_native_icon(const String &p_filename) { WARN_PRINT("Native icon not supported by this display server."); } @@ -3110,10 +3395,13 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) { pr += 4; } - XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size()); + if (net_wm_icon != None) { + XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size()); + } - if (!g_set_icon_error) + if (!g_set_icon_error) { break; + } } } else { XDeleteProperty(x11_display, wd.x11_window, net_wm_icon); @@ -3137,12 +3425,16 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() { } DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - - return memnew(DisplayServerX11(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + if (r_error != OK) { + ds->alert("Your video card driver does not support any of the supported Vulkan versions.\n" + "Please update your drivers or if you have a very old or integrated GPU upgrade it.", + "Unable to initialize Video driver"); + } + return ds; } DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { - //Create window long visualMask = VisualScreenMask; @@ -3161,19 +3453,46 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; - WindowID id; + WindowID id = window_id_counter++; + WindowData &wd = windows[id]; + + if ((id != MAIN_WINDOW_ID) && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) { + wd.menu_type = true; + } + + if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { + wd.menu_type = true; + wd.no_focus = true; + } + + // Setup for menu subwindows: + // - override_redirect forces the WM not to interfere with the window, to avoid delays due to + // handling decorations and placement. + // On the other hand, focus changes need to be handled manually when this is set. + // - save_under is a hint for the WM to keep the content of windows behind to avoid repaint. + if (wd.menu_type) { + windowAttributes.override_redirect = True; + windowAttributes.save_under = True; + valuemask |= CWOverrideRedirect | CWSaveUnder; + } + { - WindowData wd; wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes); - XMapWindow(x11_display, wd.x11_window); + // Enable receiving notification when the window is initialized (MapNotify) + // so the focus can be set at the right time. + if (wd.menu_type && !wd.no_focus) { + XSelectInput(x11_display, wd.x11_window, StructureNotifyMask); + } //associate PID // make PID known to X11 { const long pid = OS::get_singleton()->get_process_id(); Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False); - XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + if (net_wm_pid != None) { + XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + } } long im_event_mask = 0; @@ -3221,9 +3540,14 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u /* set the titlebar name */ XStoreName(x11_display, wd.x11_window, "Godot"); XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 1); - XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); + if (xdnd_aware != None) { + XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); + } if (xim && xim_style) { + // Block events polling while changing input focus + // because it triggers some event polling internally. + MutexLock mutex_lock(events_mutex); wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)nullptr); if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) { @@ -3237,96 +3561,40 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u WARN_PRINT("XCreateIC couldn't create wd.xic"); } } else { - wd.xic = nullptr; WARN_PRINT("XCreateIC couldn't create wd.xic"); } _update_context(wd); - id = window_id_counter++; - - windows[id] = wd; - - { - - if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) { - - XSizeHints *xsh; - xsh = XAllocSizeHints(); - - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = p_rect.size.width; - xsh->max_width = p_rect.size.width; - xsh->min_height = p_rect.size.height; - xsh->max_height = p_rect.size.height; - - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); - } - - bool make_utility = false; - - if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { - Hints hints; - Atom property; - hints.flags = 2; - hints.decorations = 0; - property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { + Hints hints; + Atom property; + hints.flags = 2; + hints.decorations = 0; + property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); + if (property != None) { XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - - make_utility = true; - } - if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { - make_utility = true; } + } - if (make_utility) { - //this one seems to disable the fade animations for regular windows - //but has the drawback that will not get focus by default, so - //we need fo force it, unless no focus requested - - Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); - Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - + if (wd.menu_type) { + // Set Utility type to disable fade animations. + Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); + Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); + if (wt_atom != None && type_atom != None) { XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); + } + } else { + Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); + Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) { - //but as utility appears unfocused, it needs to be forcefuly focused, unless no focus requested - XEvent xev; - Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = net_active_window; - xev.xclient.format = 32; - xev.xclient.data.l[0] = 1; - xev.xclient.data.l[1] = CurrentTime; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - } - } else { - Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); - Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - + if (wt_atom != None && type_atom != None) { XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); } } - if (id != MAIN_WINDOW_ID) { - - XSizeHints my_hints = XSizeHints(); - - my_hints.flags = PPosition | PSize; /* I want to specify position and size */ - my_hints.x = p_rect.position.x; /* The origin and size coords I want */ - my_hints.y = p_rect.position.y; - my_hints.width = p_rect.size.width; - my_hints.height = p_rect.size.height; - - XSetNormalHints(x11_display, wd.x11_window, &my_hints); - XMoveWindow(x11_display, wd.x11_window, p_rect.position.x, p_rect.position.y); - } + _update_size_hints(id); #if defined(VULKAN_ENABLED) if (context_vulkan) { @@ -3344,8 +3612,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u XFree(visualInfo); } - WindowData &wd = windows[id]; - window_set_mode(p_mode, id); //sync size @@ -3365,15 +3631,14 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u //set cursor if (cursors[current_cursor] != None) { - XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]); } + return id; } DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - - InputFilter::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); r_error = OK; @@ -3381,7 +3646,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode mouse_mode = MOUSE_MODE_VISIBLE; for (int i = 0; i < CURSOR_MAX; i++) { - cursors[i] = None; img[i] = nullptr; } @@ -3416,7 +3680,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode // Try to support IME if detectable auto-repeat is supported if (xkb_dar == True) { - #ifdef X_HAVE_UTF8_STRING // Xutf8LookupString will be used later instead of XmbLookupString before // the multibyte sequences can be converted to unicode string. @@ -3442,7 +3705,12 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY); if (!xrandr_handle) { err = dlerror(); - fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err); + // For some arcane reason, NetBSD now ships libXrandr.so.3 while the rest of the world has libXrandr.so.2... + // In case this happens for other X11 platforms in the future, let's give it a try too before failing. + xrandr_handle = dlopen("libXrandr.so.3", RTLD_LAZY); + if (!xrandr_handle) { + fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err); + } } else { XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor); if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) { @@ -3493,10 +3761,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode if (xim_styles) { xim_style = 0L; for (int i = 0; i < xim_styles->count_styles; i++) { - if (xim_styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing)) { - xim_style = xim_styles->supported_styles[i]; break; } @@ -3531,7 +3797,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextX11); if (context_vulkan->initialize() != OK) { memdelete(context_vulkan); @@ -3553,7 +3818,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode getenv("PRIMUS_libGL") || getenv("PRIMUS_LOAD_GLOBAL") || getenv("BUMBLEBEE_SOCKET")) { - print_verbose("Optirun/primusrun detected. Skipping GPU detection"); use_prime = 0; } @@ -3565,7 +3829,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode for (int i = 0; i < libraries.size(); ++i) { if (FileAccess::exists(libraries[i] + "/libGL.so.1") || FileAccess::exists(libraries[i] + "/libGL.so")) { - print_verbose("Custom libGL override detected. Skipping GPU detection"); use_prime = 0; } @@ -3606,18 +3869,24 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } #endif - - WindowID main_window = _create_window(p_mode, p_flags, Rect2i(Point2(), p_resolution)); + Point2i window_position( + (screen_get_size(0).width - p_resolution.width) / 2, + (screen_get_size(0).height - p_resolution.height) / 2); + WindowID main_window = _create_window(p_mode, p_flags, Rect2i(window_position, p_resolution)); + if (main_window == INVALID_WINDOW_ID) { + r_error = ERR_CANT_CREATE; + return; + } for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, main_window); } } + show_window(main_window); //create RenderingDevice if used #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - //temporary rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); @@ -3663,7 +3932,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } for (int i = 0; i < CURSOR_MAX; i++) { - static const char *cursor_file[] = { "left_ptr", "xterm", @@ -3780,8 +4048,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode requested = None; - window_has_focus = true; // Set focus to true at init - /*if (p_desired.layered) { set_window_per_pixel_transparency_enabled(true); }*/ @@ -3794,11 +4060,18 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } + events_thread = Thread::create(_poll_events_thread, this); + _update_real_mouse_position(windows[MAIN_WINDOW_ID]); r_error = OK; } + DisplayServerX11::~DisplayServerX11() { + events_thread_done = true; + Thread::wait_to_finish(events_thread); + memdelete(events_thread); + events_thread = nullptr; //destroy all windows for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { @@ -3808,35 +4081,40 @@ DisplayServerX11::~DisplayServerX11() { } #endif - if (E->get().xic) { - XDestroyIC(E->get().xic); + WindowData &wd = E->get(); + if (wd.xic) { + XDestroyIC(wd.xic); + wd.xic = nullptr; } - XUnmapWindow(x11_display, E->get().x11_window); - XDestroyWindow(x11_display, E->get().x11_window); + XUnmapWindow(x11_display, wd.x11_window); + XDestroyWindow(x11_display, wd.x11_window); } //destroy drivers #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - if (rendering_device_vulkan) { rendering_device_vulkan->finalize(); memdelete(rendering_device_vulkan); } - if (context_vulkan) + if (context_vulkan) { memdelete(context_vulkan); + } } #endif - if (xrandr_handle) + if (xrandr_handle) { dlclose(xrandr_handle); + } for (int i = 0; i < CURSOR_MAX; i++) { - if (cursors[i] != None) + if (cursors[i] != None) { XFreeCursor(x11_display, cursors[i]); - if (img[i] != nullptr) + } + if (img[i] != nullptr) { XcursorImageDestroy(img[i]); + } }; if (xim) { @@ -3844,12 +4122,12 @@ DisplayServerX11::~DisplayServerX11() { } XCloseDisplay(x11_display); - if (xmbstring) + if (xmbstring) { memfree(xmbstring); + } } void DisplayServerX11::register_x11_driver() { - register_create_function("x11", create_func, get_rendering_drivers_func); } diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 113e504e9b..9ff28b9ed2 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -35,8 +35,8 @@ #include "servers/display_server.h" -#include "core/input/input_filter.h" - +#include "core/input/input.h" +#include "core/templates/local_vector.h" #include "drivers/alsa/audio_driver_alsa.h" #include "drivers/alsamidi/midi_driver_alsamidi.h" #include "drivers/pulseaudio/audio_driver_pulseaudio.h" @@ -133,6 +133,9 @@ class DisplayServerX11 : public DisplayServer { ObjectID instance_id; + bool menu_type = false; + bool no_focus = false; + //better to guess on the fly, given WM can change it //WindowMode mode; bool fullscreen = false; //OS can't exit from this mode @@ -140,6 +143,10 @@ class DisplayServerX11 : public DisplayServer { bool borderless = false; bool resize_disabled = false; Vector2i last_position_before_fs; + bool focused = false; + bool minimized = false; + + unsigned int focus_order = 0; }; Map<WindowID, WindowData> windows; @@ -165,15 +172,20 @@ class DisplayServerX11 : public DisplayServer { uint64_t last_click_ms; int last_click_button_index; uint32_t last_button_state; + bool app_focused = false; + uint64_t time_since_no_focus = 0; struct { int opcode; Vector<int> touch_devices; Map<int, Vector2> absolute_devices; - Map<int, Vector3> pen_devices; + Map<int, Vector2> pen_pressure_range; + Map<int, Vector2> pen_tilt_x_range; + Map<int, Vector2> pen_tilt_y_range; XIEventMask all_event_mask; Map<int, Vector2> state; double pressure; + bool pressure_supported; Vector2 tilt; Vector2 mouse_pos_to_filter; Vector2 relative_motion; @@ -191,10 +203,14 @@ class DisplayServerX11 : public DisplayServer { MouseMode mouse_mode; Point2i center; - void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false); + void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo = false); + void _handle_selection_request_event(XSelectionRequestEvent *p_event); + + String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; + String _clipboard_get(Atom p_source, Window x11_window) const; - bool minimized; - bool window_has_focus; + //bool minimized; + //bool window_has_focus; bool do_mouse_warp; const char *cursor_theme; @@ -208,7 +224,7 @@ class DisplayServerX11 : public DisplayServer { bool layered_window; String rendering_driver; - bool window_focused; + //bool window_focused; //void set_wm_border(bool p_enabled); void set_wm_fullscreen(bool p_enabled); void set_wm_above(bool p_enabled); @@ -228,6 +244,8 @@ class DisplayServerX11 : public DisplayServer { static Property _read_property(Display *p_display, Window p_window, Atom p_property); void _update_real_mouse_position(const WindowData &wd); + bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; + void _update_size_hints(WindowID p_window); void _set_wm_fullscreen(WindowID p_window, bool p_enabled); void _set_wm_maximized(WindowID p_window, bool p_enabled); @@ -239,6 +257,16 @@ class DisplayServerX11 : public DisplayServer { static void _dispatch_input_events(const Ref<InputEvent> &p_event); void _dispatch_input_event(const Ref<InputEvent> &p_event); + mutable Mutex events_mutex; + Thread *events_thread = nullptr; + bool events_thread_done = false; + LocalVector<XEvent> polled_events; + static void _poll_events_thread(void *ud); + void _poll_events(); + + static Bool _predicate_all_events(Display *display, XEvent *event, XPointer arg); + static Bool _predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg); + protected: void _window_changed(XEvent *event); @@ -269,6 +297,7 @@ public: virtual Vector<DisplayServer::WindowID> get_window_list() const; virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); + virtual void show_window(WindowID p_id); virtual void delete_sub_window(WindowID p_id); virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; @@ -277,6 +306,8 @@ public: virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const; virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); @@ -324,7 +355,11 @@ public: virtual CursorShape cursor_get_shape() const; virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); - virtual LatinKeyboardVariant get_latin_keyboard_variant() const; + virtual int keyboard_get_layout_count() const; + virtual int keyboard_get_current_layout() const; + virtual void keyboard_set_current_layout(int p_index); + virtual String keyboard_get_layout_language(int p_index) const; + virtual String keyboard_get_layout_name(int p_index) const; virtual void process_events(); diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index 53e3ce8f85..86ea95c563 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -38,7 +38,6 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); void register_linuxbsd_exporter() { - Ref<EditorExportPlatformPC> platform; platform.instance(); @@ -62,7 +61,6 @@ void register_linuxbsd_exporter() { } static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { - // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); @@ -139,7 +137,6 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, bool found = false; for (int i = 0; i < num_sections; ++i) { - int64_t section_header_pos = section_table_pos + i * section_header_size; f->seek(section_header_pos); diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index 710ba3ca40..e1796ccefe 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -37,11 +37,13 @@ #include "os_linuxbsd.h" int main(int argc, char *argv[]) { - OS_LinuxBSD os; setlocale(LC_CTYPE, ""); + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE + char *cwd = (char *)malloc(PATH_MAX); ERR_FAIL_COND_V(!cwd, ERR_OUT_OF_MEMORY); char *ret = getcwd(cwd, PATH_MAX); @@ -52,8 +54,9 @@ int main(int argc, char *argv[]) { return 255; } - if (Main::start()) + if (Main::start()) { os.run(); // it is actually the OS that decides how to run + } Main::cleanup(); if (ret) { // Previous getcwd was successful diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 381eb909ba..4a9d0a8181 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -59,7 +59,6 @@ JoypadLinux::Joypad::Joypad() { } JoypadLinux::Joypad::~Joypad() { - for (int i = 0; i < MAX_ABS; i++) { if (abs_info[i]) { memdelete(abs_info[i]); @@ -71,7 +70,7 @@ void JoypadLinux::Joypad::reset() { dpad = 0; fd = -1; - InputFilter::JoyAxis jx; + Input::JoyAxis jx; jx.min = -1; jx.value = 0.0f; for (int i = 0; i < MAX_ABS; i++) { @@ -80,7 +79,7 @@ void JoypadLinux::Joypad::reset() { } } -JoypadLinux::JoypadLinux(InputFilter *in) { +JoypadLinux::JoypadLinux(Input *in) { exit_udev = false; input = in; joy_thread = Thread::create(joy_thread_func, this); @@ -94,7 +93,6 @@ JoypadLinux::~JoypadLinux() { } void JoypadLinux::joy_thread_func(void *p_user) { - if (p_user) { JoypadLinux *joy = (JoypadLinux *)p_user; joy->run_joypad_thread(); @@ -115,7 +113,6 @@ void JoypadLinux::run_joypad_thread() { #ifdef UDEV_ENABLED void JoypadLinux::enumerate_joypads(udev *p_udev) { - udev_enumerate *enumerate; udev_list_entry *devices, *dev_list_entry; udev_device *dev; @@ -126,13 +123,11 @@ void JoypadLinux::enumerate_joypads(udev *p_udev) { udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(dev_list_entry, devices) { - const char *path = udev_list_entry_get_name(dev_list_entry); dev = udev_device_new_from_syspath(p_udev, path); const char *devnode = udev_device_get_devnode(dev); if (devnode) { - String devnode_str = devnode; if (devnode_str.find(ignore_str) == -1) { MutexLock lock(joy_mutex); @@ -145,7 +140,6 @@ void JoypadLinux::enumerate_joypads(udev *p_udev) { } void JoypadLinux::monitor_joypads(udev *p_udev) { - udev_device *dev = nullptr; udev_monitor *mon = udev_monitor_new_from_netlink(p_udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(mon, "input", nullptr); @@ -153,7 +147,6 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { int fd = udev_monitor_get_fd(mon); while (!exit_udev) { - fd_set fds; struct timeval tv; int ret; @@ -172,15 +165,12 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { dev = udev_monitor_receive_device(mon); if (dev && udev_device_get_devnode(dev) != 0) { - MutexLock lock(joy_mutex); String action = udev_device_get_action(dev); const char *devnode = udev_device_get_devnode(dev); if (devnode) { - String devnode_str = devnode; if (devnode_str.find(ignore_str) == -1) { - if (action == "add") open_joypad(devnode); else if (String(action) == "remove") @@ -198,7 +188,6 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { #endif void JoypadLinux::monitor_joypads() { - while (!exit_udev) { { MutexLock lock(joy_mutex); @@ -216,9 +205,7 @@ void JoypadLinux::monitor_joypads() { } int JoypadLinux::get_joy_from_path(String p_path) const { - for (int i = 0; i < JOYPADS_MAX; i++) { - if (joypads[i].devpath == p_path) { return i; } @@ -229,17 +216,16 @@ int JoypadLinux::get_joy_from_path(String p_path) const { void JoypadLinux::close_joypad(int p_id) { if (p_id == -1) { for (int i = 0; i < JOYPADS_MAX; i++) { - close_joypad(i); }; return; - } else if (p_id < 0) + } else if (p_id < 0) { return; + } Joypad &joy = joypads[p_id]; if (joy.fd != -1) { - close(joy.fd); joy.fd = -1; attached_devices.remove(attached_devices.find(joy.devpath)); @@ -248,7 +234,6 @@ void JoypadLinux::close_joypad(int p_id) { } static String _hex_str(uint8_t p_byte) { - static const char *dict = "0123456789abcdef"; char ret[3]; ret[2] = 0; @@ -260,7 +245,6 @@ static String _hex_str(uint8_t p_byte) { } void JoypadLinux::setup_joypad_properties(int p_id) { - Joypad *joy = &joypads[p_id]; unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; @@ -274,16 +258,12 @@ void JoypadLinux::setup_joypad_properties(int p_id) { return; } for (int i = BTN_JOYSTICK; i < KEY_MAX; ++i) { - if (test_bit(i, keybit)) { - joy->key_map[i] = num_buttons++; } } for (int i = BTN_MISC; i < BTN_JOYSTICK; ++i) { - if (test_bit(i, keybit)) { - joy->key_map[i] = num_buttons++; } } @@ -294,7 +274,6 @@ void JoypadLinux::setup_joypad_properties(int p_id) { continue; } if (test_bit(i, absbit)) { - joy->abs_map[i] = num_axes++; joy->abs_info[i] = memnew(input_absinfo); if (ioctl(joy->fd, EVIOCGABS(i), joy->abs_info[i]) < 0) { @@ -315,11 +294,9 @@ void JoypadLinux::setup_joypad_properties(int p_id) { } void JoypadLinux::open_joypad(const char *p_path) { - int joy_num = input->get_unused_joy_id(); int fd = open(p_path, O_RDWR | O_NONBLOCK); if (fd != -1 && joy_num != -1) { - unsigned long evbit[NBITS(EV_MAX)] = { 0 }; unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; @@ -334,16 +311,9 @@ void JoypadLinux::open_joypad(const char *p_path) { return; } - //check if the device supports basic gamepad events, prevents certain keyboards from - //being detected as joypads + // Check if the device supports basic gamepad events if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && - (test_bit(ABS_X, absbit) || test_bit(ABS_Y, absbit) || test_bit(ABS_HAT0X, absbit) || - test_bit(ABS_GAS, absbit) || test_bit(ABS_RUDDER, absbit)) && - (test_bit(BTN_A, keybit) || test_bit(BTN_THUMBL, keybit) || - test_bit(BTN_TRIGGER, keybit) || test_bit(BTN_1, keybit))) && - !(test_bit(EV_ABS, evbit) && - test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) && - test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit))) { + test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) { close(fd); return; } @@ -369,7 +339,6 @@ void JoypadLinux::open_joypad(const char *p_path) { setup_joypad_properties(joy_num); sprintf(uid, "%04x%04x", BSWAP16(inpid.bustype), 0); if (inpid.vendor && inpid.product && inpid.version) { - uint16_t vendor = BSWAP16(inpid.vendor); uint16_t product = BSWAP16(inpid.product); uint16_t version = BSWAP16(inpid.version); @@ -380,7 +349,6 @@ void JoypadLinux::open_joypad(const char *p_path) { String uidname = uid; int uidlen = MIN(name.length(), 11); for (int i = 0; i < uidlen; i++) { - uidname = uidname + _hex_str(name[i]); } uidname += "00"; @@ -436,11 +404,10 @@ void JoypadLinux::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { joy.ff_effect_timestamp = p_timestamp; } -InputFilter::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { - +Input::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { int min = p_abs->minimum; int max = p_abs->maximum; - InputFilter::JoyAxis jx; + Input::JoyAxis jx; if (min < 0) { jx.min = -1; @@ -457,13 +424,13 @@ InputFilter::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p } void JoypadLinux::process_joypads() { - if (joy_mutex.try_lock() != OK) { return; } for (int i = 0; i < JOYPADS_MAX; i++) { - - if (joypads[i].fd == -1) continue; + if (joypads[i].fd == -1) { + continue; + } input_event events[32]; Joypad *joy = &joypads[i]; @@ -473,13 +440,13 @@ void JoypadLinux::process_joypads() { while ((len = read(joy->fd, events, (sizeof events))) > 0) { len /= sizeof(events[0]); for (int j = 0; j < len; j++) { - input_event &ev = events[j]; // ev may be tainted and out of MAX_KEY range, which will cause // joy->key_map[ev.code] to crash - if (ev.code >= MAX_KEY) + if (ev.code >= MAX_KEY) { return; + } switch (ev.type) { case EV_KEY: @@ -491,33 +458,38 @@ void JoypadLinux::process_joypads() { switch (ev.code) { case ABS_HAT0X: if (ev.value != 0) { - if (ev.value < 0) - joy->dpad |= InputFilter::HAT_MASK_LEFT; - else - joy->dpad |= InputFilter::HAT_MASK_RIGHT; - } else - joy->dpad &= ~(InputFilter::HAT_MASK_LEFT | InputFilter::HAT_MASK_RIGHT); + if (ev.value < 0) { + joy->dpad = (joy->dpad | Input::HAT_MASK_LEFT) & ~Input::HAT_MASK_RIGHT; + } else { + joy->dpad = (joy->dpad | Input::HAT_MASK_RIGHT) & ~Input::HAT_MASK_LEFT; + } + } else { + joy->dpad &= ~(Input::HAT_MASK_LEFT | Input::HAT_MASK_RIGHT); + } input->joy_hat(i, joy->dpad); break; case ABS_HAT0Y: if (ev.value != 0) { - if (ev.value < 0) - joy->dpad |= InputFilter::HAT_MASK_UP; - else - joy->dpad |= InputFilter::HAT_MASK_DOWN; - } else - joy->dpad &= ~(InputFilter::HAT_MASK_UP | InputFilter::HAT_MASK_DOWN); + if (ev.value < 0) { + joy->dpad = (joy->dpad | Input::HAT_MASK_UP) & ~Input::HAT_MASK_DOWN; + } else { + joy->dpad = (joy->dpad | Input::HAT_MASK_DOWN) & ~Input::HAT_MASK_UP; + } + } else { + joy->dpad &= ~(Input::HAT_MASK_UP | Input::HAT_MASK_DOWN); + } input->joy_hat(i, joy->dpad); break; default: - if (ev.code >= MAX_ABS) + if (ev.code >= MAX_ABS) { return; + } if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) { - InputFilter::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value); + Input::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value); joy->curr_axis[joy->abs_map[ev.code]] = value; } break; diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index 1d2ed5bbc1..0d175193a5 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -33,7 +33,7 @@ #define JOYPAD_LINUX_H #ifdef JOYDEV_ENABLED -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "core/os/mutex.h" #include "core/os/thread.h" @@ -41,7 +41,7 @@ struct input_absinfo; class JoypadLinux { public: - JoypadLinux(InputFilter *in); + JoypadLinux(Input *in); ~JoypadLinux(); void process_joypads(); @@ -53,7 +53,7 @@ private: }; struct Joypad { - InputFilter::JoyAxis curr_axis[MAX_ABS]; + Input::JoyAxis curr_axis[MAX_ABS]; int key_map[MAX_KEY]; int abs_map[MAX_ABS]; int dpad; @@ -74,7 +74,7 @@ private: bool exit_udev; Mutex joy_mutex; Thread *joy_thread; - InputFilter *input; + Input *input; Joypad joypads[JOYPADS_MAX]; Vector<String> attached_devices; @@ -95,7 +95,7 @@ private: void joypad_vibration_start(int p_id, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop(int p_id, uint64_t p_timestamp); - InputFilter::JoyAxis axis_correct(const input_absinfo *p_abs, int p_value) const; + Input::JoyAxis axis_correct(const input_absinfo *p_abs, int p_value) const; }; #endif diff --git a/platform/linuxbsd/key_mapping_x11.cpp b/platform/linuxbsd/key_mapping_x11.cpp index 78bd2b71a0..78049f2dfc 100644 --- a/platform/linuxbsd/key_mapping_x11.cpp +++ b/platform/linuxbsd/key_mapping_x11.cpp @@ -33,7 +33,6 @@ /***** SCAN CODE CONVERSION ******/ struct _XTranslatePair { - KeySym keysym; unsigned int keycode; }; @@ -181,13 +180,11 @@ static _XTranslatePair _xkeysym_to_keycode[] = { }; struct _TranslatePair { - unsigned int keysym; unsigned int keycode; }; static _TranslatePair _scancode_to_keycode[] = { - { KEY_ESCAPE, 0x09 }, { KEY_1, 0x0A }, { KEY_2, 0x0B }, @@ -301,10 +298,8 @@ static _TranslatePair _scancode_to_keycode[] = { }; 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; @@ -315,34 +310,34 @@ unsigned int KeyMappingX11::get_scancode(unsigned int p_code) { } unsigned int KeyMappingX11::get_keycode(KeySym p_keysym) { - // kinda bruteforce.. could optimize. - if (p_keysym < 0x100) // Latin 1, maps 1-1 + if (p_keysym < 0x100) { // Latin 1, maps 1-1 return p_keysym; + } // look for special key for (int idx = 0; _xkeysym_to_keycode[idx].keysym != 0; idx++) { - - if (_xkeysym_to_keycode[idx].keysym == p_keysym) + if (_xkeysym_to_keycode[idx].keysym == p_keysym) { return _xkeysym_to_keycode[idx].keycode; + } } return 0; } KeySym KeyMappingX11::get_keysym(unsigned int p_code) { - // kinda bruteforce.. could optimize. - if (p_code < 0x100) // Latin 1, maps 1-1 + if (p_code < 0x100) { // Latin 1, maps 1-1 return p_code; + } // look for special key for (int idx = 0; _xkeysym_to_keycode[idx].keysym != 0; idx++) { - - if (_xkeysym_to_keycode[idx].keycode == p_code) + if (_xkeysym_to_keycode[idx].keycode == p_code) { return _xkeysym_to_keycode[idx].keysym; + } } return 0; @@ -353,13 +348,11 @@ KeySym KeyMappingX11::get_keysym(unsigned int p_code) { // Tables taken from FOX toolkit struct _XTranslateUnicodePair { - KeySym keysym; unsigned int unicode; }; enum { - _KEYSYM_MAX = 759 }; @@ -1125,43 +1118,46 @@ static _XTranslateUnicodePair _xkeysym_to_unicode[_KEYSYM_MAX] = { }; unsigned int KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) { - /* Latin-1 */ - if (p_keysym >= 0x20 && p_keysym <= 0x7e) + if (p_keysym >= 0x20 && p_keysym <= 0x7e) { return p_keysym; - if (p_keysym >= 0xa0 && p_keysym <= 0xff) + } + if (p_keysym >= 0xa0 && p_keysym <= 0xff) { return p_keysym; + } // keypad to latin1 is easy - if (p_keysym >= 0xffaa && p_keysym <= 0xffb9) + if (p_keysym >= 0xffaa && p_keysym <= 0xffb9) { return p_keysym - 0xff80; + } /* Unicode (may be present)*/ - if ((p_keysym & 0xff000000) == 0x01000000) + if ((p_keysym & 0xff000000) == 0x01000000) { return p_keysym & 0x00ffffff; + } int middle, low = 0, high = _KEYSYM_MAX - 1; do { middle = (high + low) / 2; - if (_xkeysym_to_unicode[middle].keysym == p_keysym) + if (_xkeysym_to_unicode[middle].keysym == p_keysym) { return _xkeysym_to_unicode[middle].unicode; - if (_xkeysym_to_unicode[middle].keysym <= p_keysym) + } + if (_xkeysym_to_unicode[middle].keysym <= p_keysym) { low = middle + 1; - else + } else { high = middle - 1; + } } while (high >= low); return 0; } struct _XTranslateUnicodePairReverse { - unsigned int unicode; KeySym keysym; }; enum { - _UNICODE_MAX = 750 }; @@ -1919,24 +1915,27 @@ static _XTranslateUnicodePairReverse _unicode_to_xkeysym[_UNICODE_MAX] = { }; KeySym KeyMappingX11::get_keysym_from_unicode(unsigned int p_unicode) { - /* Latin 1 */ - if (p_unicode >= 0x20 && p_unicode <= 0x7e) + if (p_unicode >= 0x20 && p_unicode <= 0x7e) { return p_unicode; + } - if (p_unicode >= 0xa0 && p_unicode <= 0xff) + if (p_unicode >= 0xa0 && p_unicode <= 0xff) { return p_unicode; + } int middle, low = 0, high = _UNICODE_MAX - 1; do { middle = (high + low) / 2; - if (_unicode_to_xkeysym[middle].keysym == p_unicode) + if (_unicode_to_xkeysym[middle].keysym == p_unicode) { return _unicode_to_xkeysym[middle].keysym; - if (_unicode_to_xkeysym[middle].keysym <= p_unicode) + } + if (_unicode_to_xkeysym[middle].keysym <= p_unicode) { low = middle + 1; - else + } else { high = middle - 1; + } } while (high >= low); // if not found, let's hope X understands it as unicode diff --git a/platform/linuxbsd/key_mapping_x11.h b/platform/linuxbsd/key_mapping_x11.h index 10db43bcc4..8f5e01a3c2 100644 --- a/platform/linuxbsd/key_mapping_x11.h +++ b/platform/linuxbsd/key_mapping_x11.h @@ -41,7 +41,7 @@ #include "core/os/keyboard.h" class KeyMappingX11 { - KeyMappingX11(){}; + KeyMappingX11() {} public: static unsigned int get_keycode(KeySym p_keysym); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 5b9a25bd8b..e569aa03d7 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -31,8 +31,11 @@ #include "os_linuxbsd.h" #include "core/os/dir_access.h" -#include "core/print_string.h" -#include "errno.h" +#include "main/main.h" + +#ifdef X11_ENABLED +#include "display_server_x11.h" +#endif #ifdef HAVE_MNTENT #include <mntent.h> @@ -48,28 +51,19 @@ #include <sys/types.h> #include <unistd.h> -#include "main/main.h" - -#ifdef X11_ENABLED -#include "display_server_x11.h" -#endif - void OS_LinuxBSD::initialize() { - crash_handler.initialize(); OS_Unix::initialize_core(); } void OS_LinuxBSD::initialize_joypads() { - #ifdef JOYDEV_ENABLED - joypad = memnew(JoypadLinux(InputFilter::get_singleton())); + joypad = memnew(JoypadLinux(Input::get_singleton())); #endif } String OS_LinuxBSD::get_unique_id() const { - static String machine_id; if (machine_id.empty()) { if (FileAccess *f = FileAccess::open("/etc/machine-id", FileAccess::READ)) { @@ -84,9 +78,9 @@ String OS_LinuxBSD::get_unique_id() const { } void OS_LinuxBSD::finalize() { - - if (main_loop) + if (main_loop) { memdelete(main_loop); + } main_loop = nullptr; #ifdef ALSAMIDI_ENABLED @@ -94,29 +88,28 @@ void OS_LinuxBSD::finalize() { #endif #ifdef JOYDEV_ENABLED - memdelete(joypad); + if (joypad) { + memdelete(joypad); + } #endif } MainLoop *OS_LinuxBSD::get_main_loop() const { - return main_loop; } void OS_LinuxBSD::delete_main_loop() { - - if (main_loop) + if (main_loop) { memdelete(main_loop); + } main_loop = nullptr; } void OS_LinuxBSD::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; } String OS_LinuxBSD::get_name() const { - #ifdef __linux__ return "Linux"; #elif defined(__FreeBSD__) @@ -129,27 +122,47 @@ String OS_LinuxBSD::get_name() const { } Error OS_LinuxBSD::shell_open(String p_uri) { - Error ok; + int err_code; List<String> args; args.push_back(p_uri); - ok = execute("xdg-open", args, false); - if (ok == OK) + + // Agnostic + ok = execute("xdg-open", args, true, nullptr, nullptr, &err_code); + if (ok == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + // GNOME + args.push_front("open"); // The command is `gio open`, so we need to add it to args + ok = execute("gio", args, true, nullptr, nullptr, &err_code); + if (ok == OK && !err_code) { + return OK; + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + args.pop_front(); + ok = execute("gvfs-open", args, true, nullptr, nullptr, &err_code); + if (ok == OK && !err_code) { return OK; - ok = execute("gnome-open", args, false); - if (ok == OK) + } else if (err_code == 2) { + return ERR_FILE_NOT_FOUND; + } + // KDE + ok = execute("kde-open5", args, true, nullptr, nullptr, &err_code); + if (ok == OK && !err_code) { return OK; - ok = execute("kde-open", args, false); - return ok; + } + ok = execute("kde-open", args, true, nullptr, nullptr, &err_code); + return !err_code ? ok : FAILED; } bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; } String OS_LinuxBSD::get_config_path() const { - if (has_environment("XDG_CONFIG_HOME")) { return get_environment("XDG_CONFIG_HOME"); } else if (has_environment("HOME")) { @@ -160,7 +173,6 @@ String OS_LinuxBSD::get_config_path() const { } String OS_LinuxBSD::get_data_path() const { - if (has_environment("XDG_DATA_HOME")) { return get_environment("XDG_DATA_HOME"); } else if (has_environment("HOME")) { @@ -171,7 +183,6 @@ String OS_LinuxBSD::get_data_path() const { } String OS_LinuxBSD::get_cache_path() const { - if (has_environment("XDG_CACHE_HOME")) { return get_environment("XDG_CACHE_HOME"); } else if (has_environment("HOME")) { @@ -182,46 +193,37 @@ String OS_LinuxBSD::get_cache_path() const { } String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const { - String xdgparam; switch (p_dir) { case SYSTEM_DIR_DESKTOP: { - xdgparam = "DESKTOP"; } break; case SYSTEM_DIR_DCIM: { - xdgparam = "PICTURES"; } break; case SYSTEM_DIR_DOCUMENTS: { - xdgparam = "DOCUMENTS"; } break; case SYSTEM_DIR_DOWNLOADS: { - xdgparam = "DOWNLOAD"; } break; case SYSTEM_DIR_MOVIES: { - xdgparam = "VIDEOS"; } break; case SYSTEM_DIR_MUSIC: { - xdgparam = "MUSIC"; } break; case SYSTEM_DIR_PICTURES: { - xdgparam = "PICTURES"; } break; case SYSTEM_DIR_RINGTONES: { - xdgparam = "MUSIC"; } break; @@ -231,17 +233,18 @@ String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const { List<String> arg; arg.push_back(xdgparam); Error err = const_cast<OS_LinuxBSD *>(this)->execute("xdg-user-dir", arg, true, nullptr, &pipe); - if (err != OK) + if (err != OK) { return "."; + } return pipe.strip_edges(); } void OS_LinuxBSD::run() { - force_quit = false; - if (!main_loop) + if (!main_loop) { return; + } main_loop->init(); @@ -251,13 +254,13 @@ void OS_LinuxBSD::run() { //uint64_t frame=0; while (!force_quit) { - DisplayServer::get_singleton()->process_events(); // get rid of pending events #ifdef JOYDEV_ENABLED joypad->process_joypads(); #endif - if (Main::iteration()) + if (Main::iteration()) { break; + } }; main_loop->finish(); @@ -363,7 +366,6 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { } OS_LinuxBSD::OS_LinuxBSD() { - main_loop = nullptr; force_quit = false; diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 100cb53ba3..cd4fbd9db5 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -31,7 +31,7 @@ #ifndef OS_LINUXBSD_H #define OS_LINUXBSD_H -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "crash_handler_linuxbsd.h" #include "drivers/alsa/audio_driver_alsa.h" #include "drivers/alsamidi/midi_driver_alsamidi.h" @@ -43,13 +43,12 @@ #include "servers/rendering_server.h" class OS_LinuxBSD : public OS_Unix { - virtual void delete_main_loop(); bool force_quit; #ifdef JOYDEV_ENABLED - JoypadLinux *joypad; + JoypadLinux *joypad = nullptr; #endif #ifdef ALSA_ENABLED diff --git a/platform/linuxbsd/platform_config.h b/platform/linuxbsd/platform_config.h index ac30519132..571ad03db0 100644 --- a/platform/linuxbsd/platform_config.h +++ b/platform/linuxbsd/platform_config.h @@ -31,9 +31,15 @@ #ifdef __linux__ #include <alloca.h> #endif -#if defined(__FreeBSD__) || defined(__OpenBSD__) -#include <stdlib.h> + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +#include <stdlib.h> // alloca +// FreeBSD and OpenBSD use pthread_set_name_np, while other platforms, +// include NetBSD, use pthread_setname_np. NetBSD's version however requires +// a different format, we handle this directly in thread_posix. +#ifdef __NetBSD__ +#define PTHREAD_NETBSD_SET_NAME +#else #define PTHREAD_BSD_SET_NAME #endif - -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h" +#endif diff --git a/platform/linuxbsd/vulkan_context_x11.cpp b/platform/linuxbsd/vulkan_context_x11.cpp index 1798a7026e..2eaa9f9446 100644 --- a/platform/linuxbsd/vulkan_context_x11.cpp +++ b/platform/linuxbsd/vulkan_context_x11.cpp @@ -36,7 +36,6 @@ const char *VulkanContextX11::_get_platform_surface_extension() const { } Error VulkanContextX11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) { - VkXlibSurfaceCreateInfoKHR createInfo; createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; createInfo.pNext = nullptr; diff --git a/platform/linuxbsd/vulkan_context_x11.h b/platform/linuxbsd/vulkan_context_x11.h index 6e144ab2d9..af3d923cfe 100644 --- a/platform/linuxbsd/vulkan_context_x11.h +++ b/platform/linuxbsd/vulkan_context_x11.h @@ -35,7 +35,6 @@ #include <X11/Xlib.h> class VulkanContextX11 : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; public: diff --git a/platform/osx/SCsub b/platform/osx/SCsub index ad62db358b..aa95a89444 100644 --- a/platform/osx/SCsub +++ b/platform/osx/SCsub @@ -18,5 +18,5 @@ files = [ prog = env.add_program("#bin/godot", files) -if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]: +if env["debug_symbols"] == "yes" and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_osx_builders.make_debug_osx)) diff --git a/platform/osx/context_gl_osx.h b/platform/osx/context_gl_osx.h index 7e436c5e36..1d467513e2 100644 --- a/platform/osx/context_gl_osx.h +++ b/platform/osx/context_gl_osx.h @@ -33,7 +33,7 @@ #if defined(OPENGL_ENABLED) || defined(GLES_ENABLED) -#include "core/error_list.h" +#include "core/error/error_list.h" #include "core/os/os.h" #include <AppKit/AppKit.h> @@ -41,7 +41,6 @@ #include <CoreVideo/CoreVideo.h> class ContextGL_OSX { - bool opengl_3_context; bool use_vsync; diff --git a/platform/osx/context_gl_osx.mm b/platform/osx/context_gl_osx.mm index 91d1332d24..2319e9eb1f 100644 --- a/platform/osx/context_gl_osx.mm +++ b/platform/osx/context_gl_osx.mm @@ -33,42 +33,34 @@ #if defined(OPENGL_ENABLED) || defined(GLES_ENABLED) void ContextGL_OSX::release_current() { - [NSOpenGLContext clearCurrentContext]; } void ContextGL_OSX::make_current() { - [context makeCurrentContext]; } void ContextGL_OSX::update() { - [context update]; } void ContextGL_OSX::set_opacity(GLint p_opacity) { - [context setValues:&p_opacity forParameter:NSOpenGLCPSurfaceOpacity]; } int ContextGL_OSX::get_window_width() { - return OS::get_singleton()->get_video_mode().width; } int ContextGL_OSX::get_window_height() { - return OS::get_singleton()->get_video_mode().height; } void ContextGL_OSX::swap_buffers() { - [context flushBuffer]; } void ContextGL_OSX::set_use_vsync(bool p_use) { - CGLContextObj ctx = CGLGetCurrentContext(); if (ctx) { GLint swapInterval = p_use ? 1 : 0; @@ -78,12 +70,10 @@ void ContextGL_OSX::set_use_vsync(bool p_use) { } bool ContextGL_OSX::is_using_vsync() const { - return use_vsync; } Error ContextGL_OSX::initialize() { - framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); ERR_FAIL_COND_V(!framework, ERR_CANT_CREATE); @@ -161,7 +151,6 @@ Error ContextGL_OSX::initialize() { } ContextGL_OSX::ContextGL_OSX(id p_view, bool p_opengl_3_context) { - opengl_3_context = p_opengl_3_context; window_view = p_view; use_vsync = false; diff --git a/platform/osx/crash_handler_osx.h b/platform/osx/crash_handler_osx.h index abd9812596..9970f6045a 100644 --- a/platform/osx/crash_handler_osx.h +++ b/platform/osx/crash_handler_osx.h @@ -32,7 +32,6 @@ #define CRASH_HANDLER_OSX_H class CrashHandler { - bool disabled; public: diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 5da0118686..1429024598 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -30,8 +30,8 @@ #include "crash_handler_osx.h" +#include "core/config/project_settings.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "main/main.h" #include <string.h> @@ -90,7 +90,7 @@ static void handle_crash(int sig) { if (OS::get_singleton()->get_main_loop()) OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); - fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); + fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { void *load_addr = (void *)load_address(); @@ -142,7 +142,7 @@ static void handle_crash(int sig) { } } - fprintf(stderr, "[%zu] %ls\n", i, output.c_str()); + fprintf(stderr, "[%zu] %s\n", i, output.utf8().get_data()); } free(strings); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 29aa8ece19..64e1d94f39 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -1,6 +1,5 @@ import os import sys -import subprocess from methods import detect_darwin_sdk_path @@ -24,14 +23,15 @@ def get_opts(): from SCons.Variables import BoolVariable, EnumVariable return [ - ("osxcross_sdk", "OSXCross SDK version", "darwin14"), + ("osxcross_sdk", "OSXCross SDK version", "darwin16"), ("MACOS_SDK_PATH", "Path to the macOS SDK", ""), BoolVariable( "use_static_mvk", - "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables validation layers)", + "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables" + " validation layers)", False, ), - EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), + EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False), BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False), @@ -50,13 +50,13 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) - env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize", "-msse2"]) + env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"]) else: # optimize for size - env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize", "-msse2"]) + env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) + if env["arch"] != "arm64": + env.Prepend(CCFLAGS=["-msse2"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": @@ -66,13 +66,12 @@ def configure(env): env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "debug": env.Prepend(CCFLAGS=["-g3"]) - env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) + env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"]) ## Architecture @@ -86,9 +85,16 @@ def configure(env): if "OSXCROSS_ROOT" in os.environ: env["osxcross"] = True + if env["arch"] == "arm64": + print("Building for macOS 10.15+, platform arm64.") + env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"]) + env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"]) + else: + print("Building for macOS 10.12+, platform x86-64.") + env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + if not "osxcross" in env: # regular native build - env.Append(CCFLAGS=["-arch", "x86_64"]) - env.Append(LINKFLAGS=["-arch", "x86_64"]) if env["macports_clang"] != "no": mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local") mpclangver = env["macports_clang"] @@ -109,7 +115,10 @@ def configure(env): else: # osxcross build root = os.environ.get("OSXCROSS_ROOT", 0) - basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-" + if env["arch"] == "arm64": + basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-" + else: + basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-" ccache_path = os.environ.get("CCACHE") if ccache_path is None: @@ -126,7 +135,8 @@ def configure(env): env.Append(CPPDEFINES=["__MACPORTS__"]) # hack to fix libvpx MM256_BROADCASTSI128_SI256 define if env["CXX"] == "clang++": - env.Append(CPPDEFINES=["TYPED_METHOD_BIND"]) + # This should now work with clang++, re-enable if there are issues + # env.Append(CPPDEFINES=["TYPED_METHOD_BIND"]) env["CC"] = "clang" env["LINK"] = "clang++" @@ -148,7 +158,8 @@ def configure(env): ## Dependencies if env["builtin_libtheora"]: - env["x86_libtheora_opt_gcc"] = True + if env["arch"] != "arm64": + env["x86_libtheora_opt_gcc"] = True ## Flags @@ -189,6 +200,3 @@ def configure(env): env.Append(LIBS=["vulkan"]) # env.Append(CPPDEFINES=['GLES_ENABLED', 'OPENGL_ENABLED']) - - env.Append(CCFLAGS=["-mmacosx-version-min=10.12"]) - env.Append(LINKFLAGS=["-mmacosx-version-min=10.12"]) diff --git a/platform/osx/dir_access_osx.h b/platform/osx/dir_access_osx.h index d61ee181f0..91b8f9b2c5 100644 --- a/platform/osx/dir_access_osx.h +++ b/platform/osx/dir_access_osx.h @@ -47,6 +47,8 @@ protected: virtual int get_drive_count(); virtual String get_drive(int p_drive); + + virtual bool is_hidden(const String &p_name); }; #endif //UNIX ENABLED diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm index 66ea380903..439c6a075f 100644 --- a/platform/osx/dir_access_osx.mm +++ b/platform/osx/dir_access_osx.mm @@ -38,7 +38,6 @@ #include <Foundation/Foundation.h> String DirAccessOSX::fix_unicode_name(const char *p_name) const { - String fname; NSString *nsstr = [[NSString stringWithUTF8String:p_name] precomposedStringWithCanonicalMapping]; @@ -69,4 +68,14 @@ String DirAccessOSX::get_drive(int p_drive) { return volname; } +bool DirAccessOSX::is_hidden(const String &p_name) { + String f = get_current_dir().plus_file(p_name); + NSURL *url = [NSURL fileURLWithPath:@(f.utf8().get_data())]; + NSNumber *hidden = nil; + if (![url getResourceValue:&hidden forKey:NSURLIsHiddenKey error:nil]) { + return DirAccessUnix::is_hidden(p_name); + } + return [hidden boolValue]; +} + #endif //posix_enabled diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index 86ceb51763..073d35008b 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -33,7 +33,7 @@ #define BitMap _QDBitMap // Suppress deprecated QuickDraw definition. -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "servers/display_server.h" #if defined(OPENGL_ENABLED) @@ -86,6 +86,14 @@ public: uint32_t unicode; }; + struct WarpEvent { + NSTimeInterval timestamp; + NSPoint delta; + }; + + List<WarpEvent> warp_events; + NSTimeInterval last_warp = 0; + Vector<KeyEvent> key_event_buffer; int key_event_pos; @@ -94,6 +102,8 @@ public: id window_object; id window_view; + Vector<Vector2> mpath; + #if defined(OPENGL_ENABLED) ContextGL_OSX *context_gles2 = nullptr; #endif @@ -124,6 +134,7 @@ public: bool on_top = false; bool borderless = false; bool resize_disabled = false; + bool no_focus = false; }; Point2i im_selection; @@ -142,7 +153,6 @@ public: void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window); - float _display_scale(id screen) const; Point2i _get_screens_origin() const; Point2i _get_native_screen_position(int p_screen) const; @@ -169,132 +179,139 @@ public: bool in_dispatch_input_event = false; public: - virtual bool has_feature(Feature p_feature) const; - virtual String get_name() const; + virtual bool has_feature(Feature p_feature) const override; + virtual String get_name() const override; - virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()); - virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()); - virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu); - virtual void global_menu_add_separator(const String &p_menu_root); + virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override; + virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override; + virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) override; + virtual void global_menu_add_separator(const String &p_menu_root) override; - virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const; - virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const; - virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx); - virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx); - virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx); - virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx); + virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const override; + virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override; + virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) override; + virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) override; + virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) override; + virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) override; - virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked); - virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable); - virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback); - virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag); - virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text); - virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu); + virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override; + virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; + virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override; + virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override; + virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override; + virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override; - virtual int global_menu_get_item_count(const String &p_menu_root) const; + virtual int global_menu_get_item_count(const String &p_menu_root) const override; - virtual void global_menu_remove_item(const String &p_menu_root, int p_idx); - virtual void global_menu_clear(const String &p_menu_root); + virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override; + virtual void global_menu_clear(const String &p_menu_root) override; - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback); - virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback); + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override; + virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; - virtual void mouse_set_mode(MouseMode p_mode); - virtual MouseMode mouse_get_mode() const; + virtual void mouse_set_mode(MouseMode p_mode) override; + virtual MouseMode mouse_get_mode() const override; - virtual void mouse_warp_to_position(const Point2i &p_to); - virtual Point2i mouse_get_position() const; - virtual Point2i mouse_get_absolute_position() const; - virtual int mouse_get_button_state() const; + virtual void mouse_warp_to_position(const Point2i &p_to) override; + virtual Point2i mouse_get_position() const override; + virtual Point2i mouse_get_absolute_position() const override; + virtual int mouse_get_button_state() const override; - virtual void clipboard_set(const String &p_text); - virtual String clipboard_get() const; + virtual void clipboard_set(const String &p_text) override; + virtual String clipboard_get() const override; - virtual int get_screen_count() const; - virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const; - virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const; - virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const; - virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const; - virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual int get_screen_count() const override; + virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_max_scale() const override; + virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual Vector<int> get_window_list() const; + virtual Vector<int> get_window_list() const override; - virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i & = Rect2i()); - virtual void delete_sub_window(WindowID p_id); + virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override; + virtual void show_window(WindowID p_id) override; + virtual void delete_sub_window(WindowID p_id) override; - virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); - virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); - virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); - virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); - virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override; - virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const; - virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID); + virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; - virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const; - virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID); + virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; - virtual void window_set_transient(WindowID p_window, WindowID p_parent); + virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; - virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); - virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); - virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); - virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID); - virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; + virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const; + virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID); - virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; + virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; - virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID); - virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID); + virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; - virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const; + virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual bool can_any_window_draw() const; + virtual bool can_any_window_draw() const override; - virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID); - virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_ime_active(const bool p_active, WindowID p_window = MAIN_WINDOW_ID) override; + virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window = MAIN_WINDOW_ID) override; - virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; - virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID); - virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; + virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Point2i ime_get_selection() const; - virtual String ime_get_text() const; + virtual Point2i ime_get_selection() const override; + virtual String ime_get_text() const override; - virtual void cursor_set_shape(CursorShape p_shape); - virtual CursorShape cursor_get_shape() const; - virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()); + virtual void cursor_set_shape(CursorShape p_shape) override; + virtual CursorShape cursor_get_shape() const override; + virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; - virtual bool get_swap_ok_cancel(); + virtual bool get_swap_cancel_ok() override; - virtual LatinKeyboardVariant get_latin_keyboard_variant() const; + virtual int keyboard_get_layout_count() const override; + virtual int keyboard_get_current_layout() const override; + virtual void keyboard_set_current_layout(int p_index) override; + virtual String keyboard_get_layout_language(int p_index) const override; + virtual String keyboard_get_layout_name(int p_index) const override; - virtual void process_events(); - virtual void force_process_and_drop_events(); + virtual void process_events() override; + virtual void force_process_and_drop_events() override; - virtual void release_rendering_thread(); - virtual void make_rendering_thread(); - virtual void swap_buffers(); + virtual void release_rendering_thread() override; + virtual void make_rendering_thread() override; + virtual void swap_buffers() override; - virtual void set_native_icon(const String &p_filename); - virtual void set_icon(const Ref<Image> &p_icon); + virtual void set_native_icon(const String &p_filename) override; + virtual void set_icon(const Ref<Image> &p_icon) override; - virtual void console_set_visible(bool p_enabled); - virtual bool is_console_visible() const; + virtual void console_set_visible(bool p_enabled) override; + virtual bool is_console_visible() const override; static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); static Vector<String> get_rendering_drivers_func(); diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 074fc3be0d..1ad7117b39 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -33,6 +33,7 @@ #include "os_osx.h" #include "core/io/marshalls.h" +#include "core/math/geometry_2d.h" #include "core/os/keyboard.h" #include "main/main.h" #include "scene/resources/texture.h" @@ -45,8 +46,9 @@ #include <IOKit/hid/IOHIDLib.h> #if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" //TODO - reimplement OpenGLES + +#import <AppKit/NSOpenGLView.h> #endif #if defined(VULKAN_ENABLED) @@ -55,8 +57,14 @@ #include <QuartzCore/CAMetalLayer.h> #endif +#ifndef NSAppKitVersionNumber10_14 +#define NSAppKitVersionNumber10_14 1671 +#endif + #define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton())) +static bool ignore_momentum_scroll = false; + static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) { r_state->set_shift((p_osx_state & NSEventModifierFlagShift)); r_state->set_control((p_osx_state & NSEventModifierFlagControl)); @@ -64,13 +72,13 @@ static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWith r_state->set_metakey((p_osx_state & NSEventModifierFlagCommand)); } -static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow, CGFloat p_backingScaleFactor) { +static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow) { const NSRect contentRect = [p_wd.window_view frame]; - const NSPoint p = p_locationInWindow; - p_wd.mouse_pos.x = p.x * p_backingScaleFactor; - p_wd.mouse_pos.y = (contentRect.size.height - p.y) * p_backingScaleFactor; + const float scale = DS_OSX->screen_get_max_scale(); + p_wd.mouse_pos.x = p_locationInWindow.x * scale; + p_wd.mouse_pos.y = (contentRect.size.height - p_locationInWindow.y) * scale; DS_OSX->last_mouse_pos = p_wd.mouse_pos; - InputFilter::get_singleton()->set_mouse_position(p_wd.mouse_pos); + Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); return p_wd.mouse_pos; } @@ -120,17 +128,18 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { k->set_physical_keycode(KEY_PERIOD); k->set_echo([event isARepeat]); - InputFilter::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->accumulate_input_event(k); } } // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost // This works around an AppKit bug, where key up events while holding // down the command key don't get sent to the key window. - if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) + if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { [[self keyWindow] sendEvent:event]; - else + } else { [super sendEvent:event]; + } } @end @@ -145,6 +154,7 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { Variant meta; bool checkable; } + @end @implementation GlobalMenuItem @@ -192,9 +202,22 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } } +- (void)applicationDidResignActive:(NSNotification *)notification { + if (OS_OSX::get_singleton()->get_main_loop()) { + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } +} + +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if (OS_OSX::get_singleton()->get_main_loop()) { + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } +} + - (void)globalMenuCallback:(id)sender { - if (![sender representedObject]) + if (![sender representedObject]) { return; + } GlobalMenuItem *value = [sender representedObject]; @@ -247,8 +270,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (void)showAbout:(id)sender { - if (OS_OSX::get_singleton()->get_main_loop()) + if (OS_OSX::get_singleton()->get_main_loop()) { OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + } } @end @@ -273,21 +297,23 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (BOOL)windowShouldClose:(id)sender { - ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), YES); + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return YES; + } DS_OSX->_send_window_event(DS_OSX->windows[window_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); return NO; } - (void)windowWillClose:(NSNotification *)notification { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; while (wd.transient_children.size()) { DS_OSX->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID); } - DS_OSX->windows.erase(window_id); - if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) { DisplayServerOSX::WindowData &pwd = DS_OSX->windows[wd.transient_parent]; [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to parent. @@ -297,15 +323,24 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to main window if there is no parent or other windows left. } +#if defined(OPENGL_ENABLED) + if (DS_OSX->rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif #ifdef VULKAN_ENABLED if (DS_OSX->rendering_driver == "vulkan") { DS_OSX->context_vulkan->window_destroy(window_id); } #endif + + DS_OSX->windows.erase(window_id); } - (void)windowDidEnterFullScreen:(NSNotification *)notification { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; wd.fullscreen = true; @@ -315,28 +350,32 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (void)windowDidExitFullScreen:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { return; + } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; wd.fullscreen = false; + const float scale = DS_OSX->screen_get_max_scale(); if (wd.min_size != Size2i()) { - Size2i size = wd.min_size / DS_OSX->_display_scale([wd.window_object screen]); + Size2i size = wd.min_size / scale; [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; } if (wd.max_size != Size2i()) { - Size2i size = wd.max_size / DS_OSX->_display_scale([wd.window_object screen]); + Size2i size = wd.max_size / scale; [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; } - if (wd.resize_disabled) + if (wd.resize_disabled) { [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } } - (void)windowDidChangeBackingProperties:(NSNotification *)notification { - if (!DisplayServerOSX::get_singleton()) + if (!DisplayServerOSX::get_singleton()) { return; + } ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; @@ -344,60 +383,50 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { CGFloat newBackingScaleFactor = [wd.window_object backingScaleFactor]; CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; -#if defined(OPENGL_ENABLED) - if (DS_OSX->rendering_driver == "opengl_es") { - //TODO - reimplement OpenGLES - if (OS_OSX::get_singleton()->is_hidpi_allowed()) { - [wd.window_view setWantsBestResolutionOpenGLSurface:YES]; - } else { - [wd.window_view setWantsBestResolutionOpenGLSurface:NO]; - } - } -#endif - if (newBackingScaleFactor != oldBackingScaleFactor) { //Set new display scale and window size - float newDisplayScale = OS_OSX::get_singleton()->is_hidpi_allowed() ? newBackingScaleFactor : 1.0; - + const float scale = DS_OSX->screen_get_max_scale(); const NSRect contentRect = [wd.window_view frame]; - wd.size.width = contentRect.size.width * newDisplayScale; - wd.size.height = contentRect.size.height * newDisplayScale; + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE); -#if defined(VULKAN_ENABLED) - if (DS_OSX->rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - layer.contentsScale = DS_OSX->_display_scale([wd.window_object screen]); + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; } -#endif + //Force window resize event [self windowDidResize:notification]; } } - (void)windowDidResize:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { return; + } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + const NSRect contentRect = [wd.window_view frame]; + + const float scale = DS_OSX->screen_get_max_scale(); + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + #if defined(OPENGL_ENABLED) if (DS_OSX->rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2->update(); } #endif - const NSRect contentRect = [wd.window_view frame]; - - float displayScale = DS_OSX->_display_scale([wd.window_object screen]); - wd.size.width = contentRect.size.width * displayScale; - wd.size.height = contentRect.size.height * displayScale; - #if defined(VULKAN_ENABLED) if (DS_OSX->rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - layer.contentsScale = displayScale; DS_OSX->context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); } #endif @@ -420,23 +449,39 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (void)windowDidMove:(NSNotification *)notification { + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + DS_OSX->_release_pressed_events(); + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id)); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } } - (void)windowDidBecomeKey:(NSNotification *)notification { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [wd.window_view backingScaleFactor] : 1.0; - _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream], backingScaleFactor); - InputFilter::get_singleton()->set_mouse_position(wd.mouse_pos); + _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + Input::get_singleton()->set_mouse_position(wd.mouse_pos); DS_OSX->window_focused = true; DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); } - (void)windowDidResignKey:(NSNotification *)notification { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; DS_OSX->window_focused = false; @@ -446,7 +491,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (void)windowDidMiniaturize:(NSNotification *)notification { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; DS_OSX->window_focused = false; @@ -456,7 +503,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (void)windowDidDeminiaturize:(NSNotification *)notification { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); + if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + return; + } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; DS_OSX->window_focused = true; @@ -497,6 +546,12 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (CALayer *)makeBackingLayer { +#if defined(OPENGL_ENABLED) + if (DS_OSX->rendering_driver == "opengl_es") { + CALayer *layer = [[NSOpenGLLayer class] layer]; + return layer; + } +#endif #if defined(VULKAN_ENABLED) if (DS_OSX->rendering_driver == "vulkan") { CALayer *layer = [[CAMetalLayer class] layer]; @@ -507,20 +562,17 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (void)updateLayer { -#if defined(VULKAN_ENABLED) - if (DS_OSX->rendering_driver == "vulkan") { - [super updateLayer]; - } -#endif #if defined(OPENGL_ENABLED) if (DS_OSX->rendering_driver == "opengl_es") { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - wd.context_gles2->update(); + [super updateLayer]; //TODO - reimplement OpenGLES } #endif +#if defined(VULKAN_ENABLED) + if (DS_OSX->rendering_driver == "vulkan") { + [super updateLayer]; + } +#endif } - (BOOL)wantsUpdateLayer { @@ -532,7 +584,11 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { trackingArea = nil; imeInputEventInProgress = false; [self updateTrackingAreas]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; +#else [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; +#endif markedText = [[NSMutableAttributedString alloc] init]; return self; } @@ -581,8 +637,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; } - (void)doCommandBySelector:(SEL)aSelector { - if ([self respondsToSelector:aSelector]) + if ([self respondsToSelector:aSelector]) { [self performSelector:aSelector]; + } } - (void)unmarkText { @@ -617,8 +674,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; const NSRect contentRect = [wd.window_view frame]; - float displayScale = DS_OSX->_display_scale([wd.window_object screen]); - NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / displayScale, contentRect.size.height - (wd.im_position.y / displayScale) - 1, 0, 0); + const float scale = DS_OSX->screen_get_max_scale(); + NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / scale, contentRect.size.height - (wd.im_position.y / scale) - 1, 0, 0); NSPoint pointOnScreen = [wd.window_object convertRectToScreen:pointInWindowRect].origin; return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0); @@ -657,8 +714,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; for (i = 0; i < length; i++) { const unichar codepoint = [characters characterAtIndex:i]; - if ((codepoint & 0xFF00) == 0xF700) + if ((codepoint & 0xFF00) == 0xF700) { continue; + } DisplayServerOSX::KeyEvent ke; @@ -689,11 +747,19 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; NSPasteboard *pboard = [sender draggingPasteboard]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + NSArray<NSURL *> *filenames = [pboard propertyListForType:NSPasteboardTypeFileURL]; +#else NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; +#endif Vector<String> files; for (NSUInteger i = 0; i < filenames.count; i++) { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + NSString *ns = [[filenames objectAtIndex:i] path]; +#else NSString *ns = [filenames objectAtIndex:i]; +#endif char *utfs = strdup([ns UTF8String]); String ret; ret.parse_utf8(utfs); @@ -717,6 +783,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; } - (BOOL)canBecomeKeyView { + if (DS_OSX->windows.has(window_id)) { + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + if (wd.no_focus) { + return NO; + } + } return YES; } @@ -743,8 +815,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i Ref<InputEventMouseButton> mb; mb.instance(); mb->set_window_id(window_id); - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0; - const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow], backingScaleFactor); + const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow]); _get_key_modifier_state([event modifierFlags], mb); mb->set_button_index(index); mb->set_pressed(pressed); @@ -755,7 +826,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i mb->set_doubleclick([event clickCount] == 2); } - InputFilter::get_singleton()->accumulate_input_event(mb); + Input::get_singleton()->accumulate_input_event(mb); } - (void)mouseDown:(NSEvent *)event { @@ -790,13 +861,58 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); + NSPoint mpos = [event locationInWindow]; + + if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED) { + // Discard late events + if (([event timestamp]) < DS_OSX->last_warp) { + return; + } + + // Warp affects next event delta, subtract previous warp deltas + List<DisplayServerOSX::WarpEvent>::Element *F = DS_OSX->warp_events.front(); + while (F) { + if (F->get().timestamp < [event timestamp]) { + List<DisplayServerOSX::WarpEvent>::Element *E = F; + delta.x -= E->get().delta.x; + delta.y -= E->get().delta.y; + F = F->next(); + DS_OSX->warp_events.erase(E); + } else { + F = F->next(); + } + } + + // Confine mouse position to the window, and update delta + NSRect frame = [wd.window_object frame]; + NSPoint conf_pos = mpos; + conf_pos.x = CLAMP(conf_pos.x + delta.x, 0.f, frame.size.width); + conf_pos.y = CLAMP(conf_pos.y - delta.y, 0.f, frame.size.height); + delta.x = conf_pos.x - mpos.x; + delta.y = mpos.y - conf_pos.y; + mpos = conf_pos; + + // Move mouse cursor + NSRect pointInWindowRect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); + conf_pos = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; + conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; + CGWarpMouseCursorPosition(conf_pos); + + // Save warp data + DS_OSX->last_warp = [[NSProcessInfo processInfo] systemUptime]; + DisplayServerOSX::WarpEvent ev; + ev.timestamp = DS_OSX->last_warp; + ev.delta = delta; + DS_OSX->warp_events.push_back(ev); + } + Ref<InputEventMouseMotion> mm; mm.instance(); mm->set_window_id(window_id); mm->set_button_mask(DS_OSX->last_button_state); - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0; - const Vector2i pos = _get_mouse_pos(wd, [event locationInWindow], backingScaleFactor); + const Vector2i pos = _get_mouse_pos(wd, mpos); mm->set_position(pos); mm->set_pressure([event pressure]); if ([event subtype] == NSEventSubtypeTabletPoint) { @@ -804,15 +920,13 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i mm->set_tilt(Vector2(p.x, p.y)); } mm->set_global_position(pos); - mm->set_speed(InputFilter::get_singleton()->get_last_mouse_speed()); - Vector2i relativeMotion = Vector2i(); - relativeMotion.x = [event deltaX] * backingScaleFactor; - relativeMotion.y = [event deltaY] * backingScaleFactor; + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * DS_OSX->screen_get_max_scale(); mm->set_relative(relativeMotion); _get_key_modifier_state([event modifierFlags], mm); - InputFilter::get_singleton()->set_mouse_position(wd.mouse_pos); - InputFilter::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->set_mouse_position(wd.mouse_pos); + Input::get_singleton()->accumulate_input_event(mm); } - (void)rightMouseDown:(NSEvent *)event { @@ -859,16 +973,18 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) + if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT); + } } - (void)mouseEntered:(NSEvent *)event { ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) + if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER); + } DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape; DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX; @@ -883,11 +999,10 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i ev.instance(); ev->set_window_id(window_id); _get_key_modifier_state([event modifierFlags], ev); - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0; - ev->set_position(_get_mouse_pos(wd, [event locationInWindow], backingScaleFactor)); + ev->set_position(_get_mouse_pos(wd, [event locationInWindow])); ev->set_factor([event magnification] + 1.0); - InputFilter::get_singleton()->accumulate_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); } - (void)viewDidChangeBackingProperties { @@ -900,24 +1015,14 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i [trackingArea release]; } - NSTrackingAreaOptions options = - NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect; - - trackingArea = [[NSTrackingArea alloc] - initWithRect:[self bounds] - options:options - owner:self - userInfo:nil]; + NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; [super updateTrackingAreas]; } static bool isNumpadKey(unsigned int key) { - static const unsigned int table[] = { 0x41, /* kVK_ANSI_KeypadDecimal */ 0x43, /* kVK_ANSI_KeypadMultiply */ @@ -941,8 +1046,9 @@ static bool isNumpadKey(unsigned int key) { 0x00 }; for (int i = 0; table[i] != 0; i++) { - if (key == table[i]) + if (key == table[i]) { return true; + } } return false; } @@ -950,7 +1056,6 @@ static bool isNumpadKey(unsigned int key) { // Translates a OS X keycode to a Godot keycode // static int translateKey(unsigned int key) { - // Keyboard symbol translation table static const unsigned int table[128] = { /* 00 */ KEY_A, @@ -1083,8 +1188,9 @@ static int translateKey(unsigned int key) { /* 7f */ KEY_UNKNOWN, }; - if (key >= 128) + if (key >= 128) { return KEY_UNKNOWN; + } return table[key]; } @@ -1153,16 +1259,19 @@ static const _KeyCodeMap _keycodes[55] = { }; static int remapKey(unsigned int key, unsigned int state) { - if (isNumpadKey(key)) + if (isNumpadKey(key)) { return translateKey(key); + } TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) + if (!currentKeyboard) { return translateKey(key); + } CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) + if (!layoutData) { return translateKey(key); + } const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); @@ -1197,6 +1306,8 @@ static int remapKey(unsigned int key, unsigned int state) { ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + ignore_momentum_scroll = true; + // Ignore all input if IME input is in progress if (!imeInputEventInProgress) { NSString *characters = [event characters]; @@ -1235,11 +1346,14 @@ static int remapKey(unsigned int key, unsigned int state) { } // Pass events to IME handler - if (wd.im_active) + if (wd.im_active) { [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + } } - (void)flagsChanged:(NSEvent *)event { + ignore_momentum_scroll = true; + // Ignore all input if IME input is in progress if (!imeInputEventInProgress) { DisplayServerOSX::KeyEvent ke; @@ -1353,7 +1467,7 @@ inline void sendScrollEvent(DisplayServer::WindowID window_id, int button, doubl DS_OSX->last_button_state |= mask; sc->set_button_mask(DS_OSX->last_button_state); - InputFilter::get_singleton()->accumulate_input_event(sc); + Input::get_singleton()->accumulate_input_event(sc); sc.instance(); sc->set_window_id(window_id); @@ -1365,7 +1479,7 @@ inline void sendScrollEvent(DisplayServer::WindowID window_id, int button, doubl DS_OSX->last_button_state &= ~mask; sc->set_button_mask(DS_OSX->last_button_state); - InputFilter::get_singleton()->accumulate_input_event(sc); + Input::get_singleton()->accumulate_input_event(sc); } inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy, int modifierFlags) { @@ -1380,7 +1494,7 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy pg->set_position(wd.mouse_pos); pg->set_delta(Vector2(-dx, -dy)); - InputFilter::get_singleton()->accumulate_input_event(pg); + Input::get_singleton()->accumulate_input_event(pg); } - (void)scrollWheel:(NSEvent *)event { @@ -1389,8 +1503,7 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy double deltaX, deltaY; - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0; - _get_mouse_pos(wd, [event locationInWindow], backingScaleFactor); + _get_mouse_pos(wd, [event locationInWindow]); deltaX = [event scrollingDeltaX]; deltaY = [event scrollingDeltaY]; @@ -1400,6 +1513,14 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy deltaY *= 0.03; } + if ([event momentumPhase] != NSEventPhaseNone) { + if (ignore_momentum_scroll) { + return; + } + } else { + ignore_momentum_scroll = false; + } + if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]); } else { @@ -1420,12 +1541,32 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy @interface GodotWindow : NSWindow { } + @end @implementation GodotWindow - (BOOL)canBecomeKeyWindow { // Required for NSBorderlessWindowMask windows + for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) { + if (E->get().window_object == self) { + if (E->get().no_focus) { + return NO; + } + } + } + return YES; +} + +- (BOOL)canBecomeMainWindow { + // Required for NSBorderlessWindowMask windows + for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) { + if (E->get().window_object == self) { + if (E->get().no_focus) { + return NO; + } + } + } return YES; } @@ -1452,6 +1593,7 @@ bool DisplayServerOSX::has_feature(Feature p_feature) const { case FEATURE_HIDPI: case FEATURE_ICON: case FEATURE_NATIVE_ICON: + //case FEATURE_KEEP_SCREEN_ON: case FEATURE_SWAP_BUFFERS: return true; default: { @@ -1652,7 +1794,8 @@ String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, const NSMenu *sub_menu = [menu_item submenu]; if (sub_menu) { for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) { - if (E->get() == sub_menu) return E->key(); + if (E->get() == sub_menu) + return E->key(); } } } @@ -1815,8 +1958,12 @@ void DisplayServerOSX::alert(const String &p_alert, const String &p_title) { [window setInformativeText:ns_alert]; [window setAlertStyle:NSAlertStyleWarning]; + id key_window = [[NSApplication sharedApplication] keyWindow]; [window runModal]; [window release]; + if (key_window) { + [key_window makeKeyAndOrderFront:nil]; + } } Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { @@ -1896,23 +2043,38 @@ Error DisplayServerOSX::dialog_input_text(String p_title, String p_description, void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ - if (p_mode == mouse_mode) + if (p_mode == mouse_mode) { return; + } if (p_mode == MOUSE_MODE_CAPTURED) { // Apple Docs state that the display parameter is not used. // "This parameter is not used. By default, you may pass kCGDirectMainDisplay." // https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/Quartz_Services_Ref/Reference/reference.html - CGDisplayHideCursor(kCGDirectMainDisplay); + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + CGDisplayHideCursor(kCGDirectMainDisplay); + } CGAssociateMouseAndMouseCursorPosition(false); + WindowData &wd = windows[MAIN_WINDOW_ID]; + const NSRect contentRect = [wd.window_view frame]; + NSRect pointInWindowRect = NSMakeRect(contentRect.size.width / 2, contentRect.size.height / 2, 0, 0); + NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; + CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; + CGWarpMouseCursorPosition(lMouseWarpPos); } else if (p_mode == MOUSE_MODE_HIDDEN) { - CGDisplayHideCursor(kCGDirectMainDisplay); + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + CGDisplayHideCursor(kCGDirectMainDisplay); + } CGAssociateMouseAndMouseCursorPosition(true); + } else if (p_mode == MOUSE_MODE_CONFINED) { + CGDisplayShowCursor(kCGDirectMainDisplay); + CGAssociateMouseAndMouseCursorPosition(false); } else { CGDisplayShowCursor(kCGDirectMainDisplay); CGAssociateMouseAndMouseCursorPosition(true); } + warp_events.clear(); mouse_mode = p_mode; } @@ -1930,8 +2092,8 @@ void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { //local point in window coords const NSRect contentRect = [wd.window_view frame]; - float displayScale = _display_scale([wd.window_object screen]); - NSRect pointInWindowRect = NSMakeRect(p_to.x / displayScale, contentRect.size.height - (p_to.y / displayScale) - 1, 0, 0); + const float scale = screen_get_max_scale(); + NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0); NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; //point in scren coords @@ -1942,7 +2104,9 @@ void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0); CGAssociateMouseAndMouseCursorPosition(false); CGWarpMouseCursorPosition(lMouseWarpPos); - CGAssociateMouseAndMouseCursorPosition(true); + if (mouse_mode != MOUSE_MODE_CONFINED) { + CGAssociateMouseAndMouseCursorPosition(true); + } } } @@ -1954,11 +2118,12 @@ Point2i DisplayServerOSX::mouse_get_absolute_position() const { _THREAD_SAFE_METHOD_ const NSPoint mouse_pos = [NSEvent mouseLocation]; + const float scale = screen_get_max_scale(); for (NSScreen *screen in [NSScreen screens]) { NSRect frame = [screen frame]; if (NSMouseInRect(mouse_pos, frame, NO)) { - return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) + _get_screens_origin(); + return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) * scale + _get_screens_origin(); } } return Vector2i(); @@ -2016,17 +2181,10 @@ int DisplayServerOSX::get_screen_count() const { // to convert between OS X native screen coordinates and the ones expected by Godot static bool displays_arrangement_dirty = true; +static bool displays_scale_dirty = true; static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { displays_arrangement_dirty = true; -} - -float DisplayServerOSX::_display_scale(id screen) const { - if (OS_OSX::get_singleton()->is_hidpi_allowed()) { - if ([screen respondsToSelector:@selector(backingScaleFactor)]) { - return fmax(1.0, [screen backingScaleFactor]); - } - } - return 1.0; + displays_scale_dirty = true; } Point2i DisplayServerOSX::_get_screens_origin() const { @@ -2053,10 +2211,9 @@ Point2i DisplayServerOSX::_get_screens_origin() const { Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - float display_scale = _display_scale([screenArray objectAtIndex:p_screen]); NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; // Return the top-left corner of the screen, for OS X the y starts at the bottom - return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * display_scale; + return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale(); } return Point2i(); @@ -2085,10 +2242,9 @@ Size2i DisplayServerOSX::screen_get_size(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); // Note: Use frame to get the whole screen size NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - return Size2i(nsrect.size.width, nsrect.size.height) * displayScale; + return Size2i(nsrect.size.width, nsrect.size.height) * screen_get_max_scale(); } return Size2i(); @@ -2103,16 +2259,19 @@ int DisplayServerOSX::screen_get_dpi(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; - NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; - CGSize displayPhysicalSize = CGDisplayScreenSize( - [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); - return (displayPixelSize.width * 25.4f / displayPhysicalSize.width) * displayScale; + const NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; + const CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + float scale = [[screenArray objectAtIndex:p_screen] backingScaleFactor]; + + float den2 = (displayPhysicalSize.width / 25.4f) * (displayPhysicalSize.width / 25.4f) + (displayPhysicalSize.height / 25.4f) * (displayPhysicalSize.height / 25.4f); + if (den2 > 0.0f) { + return ceil(sqrt(displayPixelSize.width * displayPixelSize.width + displayPixelSize.height * displayPixelSize.height) / sqrt(den2) * scale); + } } - return 96; + return 72; } float DisplayServerOSX::screen_get_scale(int p_screen) const { @@ -2121,14 +2280,32 @@ float DisplayServerOSX::screen_get_scale(int p_screen) const { if (p_screen == SCREEN_OF_MAIN_WINDOW) { p_screen = window_get_current_screen(); } - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - return _display_scale([screenArray objectAtIndex:p_screen]); + if (OS::get_singleton()->is_hidpi_allowed()) { + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + if ([[screenArray objectAtIndex:p_screen] respondsToSelector:@selector(backingScaleFactor)]) { + return fmax(1.0, [[screenArray objectAtIndex:p_screen] backingScaleFactor]); + } + } } return 1.f; } +float DisplayServerOSX::screen_get_max_scale() const { + _THREAD_SAFE_METHOD_ + + static float scale = 1.f; + if (displays_scale_dirty) { + int screen_count = get_screen_count(); + for (int i = 0; i < screen_count; i++) { + scale = fmax(scale, screen_get_scale(i)); + } + displays_scale_dirty = false; + } + return scale; +} + Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -2138,12 +2315,12 @@ Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); + const float scale = screen_get_max_scale(); NSRect nsrect = [[screenArray objectAtIndex:p_screen] visibleFrame]; - Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * displayScale - _get_screens_origin(); + Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * scale - _get_screens_origin(); position.y *= -1; - Size2i size = Size2i(nsrect.size.width, nsrect.size.height) / displayScale; + Size2i size = Size2i(nsrect.size.width, nsrect.size.height) * scale; return Rect2i(position, size); } @@ -2165,16 +2342,25 @@ DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, u _THREAD_SAFE_METHOD_ WindowID id = _create_window(p_mode, p_rect); - WindowData &wd = windows[id]; for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, id); } } - [wd.window_object makeKeyAndOrderFront:nil]; + return id; } +void DisplayServerOSX::show_window(WindowID p_id) { + WindowData &wd = windows[p_id]; + + if (wd.no_focus) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } +} + void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) { _THREAD_SAFE_METHOD_ @@ -2189,8 +2375,9 @@ void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_ev DisplayServerOSX::WindowID DisplayServerOSX::_find_window_id(id p_window) { for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (E->get().window_object == p_window) + if (E->get().window_object == p_window) { return E->key(); + } } return INVALID_WINDOW_ID; } @@ -2215,7 +2402,11 @@ void DisplayServerOSX::_update_window(WindowData p_wd) { [p_wd.window_object setHidesOnDeactivate:YES]; } else { // Reset these when our window is not a borderless window that covers up the screen - [p_wd.window_object setLevel:NSNormalWindowLevel]; + if (p_wd.on_top) { + [p_wd.window_object setLevel:NSFloatingWindowLevel]; + } else { + [p_wd.window_object setLevel:NSNormalWindowLevel]; + } [p_wd.window_object setHidesOnDeactivate:NO]; } } @@ -2241,6 +2432,15 @@ void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; } +void DisplayServerOSX::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; + + wd.mpath = p_region; +} + void DisplayServerOSX::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2314,7 +2514,7 @@ void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent wd_window.transient_parent = INVALID_WINDOW_ID; wd_parent.transient_children.erase(p_window); - [wd_window.window_object setParentWindow:nil]; + [wd_parent.window_object removeChildWindow:wd_window.window_object]; } else { ERR_FAIL_COND(!windows.has(p_parent)); ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); @@ -2323,7 +2523,7 @@ void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent wd_window.transient_parent = p_parent; wd_parent.transient_children.insert(p_window); - [wd_window.window_object setParentWindow:wd_parent.window_object]; + [wd_parent.window_object addChildWindow:wd_window.window_object ordered:NSWindowAbove]; } } @@ -2335,11 +2535,12 @@ Point2i DisplayServerOSX::window_get_position(WindowID p_window) const { NSRect nsrect = [wd.window_object frame]; Point2i pos; - float display_scale = _display_scale([wd.window_object screen]); // Return the position of the top-left corner, for OS X the y starts at the bottom - pos.x = nsrect.origin.x * display_scale; - pos.y = (nsrect.origin.y + nsrect.size.height) * display_scale; + const float scale = screen_get_max_scale(); + pos.x = nsrect.origin.x; + pos.y = (nsrect.origin.y + nsrect.size.height); + pos *= scale; pos -= _get_screens_origin(); // OS X native y-coordinate relative to _get_screens_origin() is negative, // Godot expects a positive value @@ -2358,17 +2559,12 @@ void DisplayServerOSX::window_set_position(const Point2i &p_position, WindowID p // Godot passes a positive value position.y *= -1; position += _get_screens_origin(); + position /= screen_get_max_scale(); - NSPoint pos; - float displayScale = _display_scale([wd.window_object screen]); - - pos.x = position.x / displayScale; - pos.y = position.y / displayScale; - - [wd.window_object setFrameTopLeftPoint:pos]; + [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x, position.y)]; _update_window(wd); - _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream], displayScale); + _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); } void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_window) { @@ -2384,7 +2580,7 @@ void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_windo wd.max_size = p_size; if ((wd.max_size != Size2i()) && !wd.fullscreen) { - Size2i size = wd.max_size / _display_scale([wd.window_object screen]); + Size2i size = wd.max_size / screen_get_max_scale(); [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; } else { [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; @@ -2412,7 +2608,7 @@ void DisplayServerOSX::window_set_min_size(const Size2i p_size, WindowID p_windo wd.min_size = p_size; if ((wd.min_size != Size2i()) && !wd.fullscreen) { - Size2i size = wd.min_size / _display_scale([wd.window_object screen]); + Size2i size = wd.min_size / screen_get_max_scale(); [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; } else { [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; @@ -2434,18 +2630,20 @@ void DisplayServerOSX::window_set_size(const Size2i p_size, WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - Size2i size = p_size / _display_scale([wd.window_object screen]); + Size2i size = p_size / screen_get_max_scale(); - if (!wd.borderless) { - // NSRect used by setFrame includes the title bar, so add it to our size.y - CGFloat menuBarHeight = [[[NSApplication sharedApplication] mainMenu] menuBarHeight]; - if (menuBarHeight != 0.f) { - size.y += menuBarHeight; - } - } + NSPoint top_left; + NSRect old_frame = [wd.window_object frame]; + top_left.x = old_frame.origin.x; + top_left.y = NSMaxY(old_frame); - NSRect frame = [wd.window_object frame]; - [wd.window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, size.x, size.y) display:YES]; + NSRect new_frame = NSMakeRect(0, 0, size.x, size.y); + new_frame = [wd.window_object frameRectForContentRect:new_frame]; + + new_frame.origin.x = top_left.x; + new_frame.origin.y = top_left.y - new_frame.size.height; + + [wd.window_object setFrame:new_frame display:YES]; _update_window(wd); } @@ -2464,7 +2662,7 @@ Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const { ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); const WindowData &wd = windows[p_window]; NSRect frame = [wd.window_object frame]; - return Size2i(frame.size.width, frame.size.height) * _display_scale([wd.window_object screen]); + return Size2i(frame.size.width, frame.size.height) * screen_get_max_scale(); } bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const { @@ -2475,23 +2673,26 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (!OS_OSX::get_singleton()->is_layered_allowed()) return; + if (!OS_OSX::get_singleton()->is_layered_allowed()) { + return; + } if (wd.layered_window != p_enabled) { if (p_enabled) { [wd.window_object setBackgroundColor:[NSColor clearColor]]; [wd.window_object setOpaque:NO]; [wd.window_object setHasShadow:NO]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setOpaque:NO]; + } #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - [layer setOpaque:NO]; //TODO - implement transparency for Vulkan } #endif #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2->set_opacity(0); } #endif wd.layered_window = true; @@ -2499,17 +2700,18 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; [wd.window_object setOpaque:YES]; [wd.window_object setHasShadow:YES]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setOpaque:YES]; + } #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - [layer setOpaque:YES]; //TODO - implement transparency for Vulkan } #endif #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2->set_opacity(1); } #endif wd.layered_window = false; @@ -2517,7 +2719,11 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2->update(); + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + //TODO - implement transparency for Vulkan } #endif NSRect frameRect = [wd.window_object frame]; @@ -2545,16 +2751,18 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { [wd.window_object deminiaturize:nil]; } break; case WINDOW_MODE_FULLSCREEN: { - if (wd.layered_window) + if (wd.layered_window) { _set_window_per_pixel_transparency_enabled(true, p_window); - if (wd.resize_disabled) //restore resize disabled + } + if (wd.resize_disabled) { //restore resize disabled [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } if (wd.min_size != Size2i()) { - Size2i size = wd.min_size / _display_scale([wd.window_object screen]); + Size2i size = wd.min_size / screen_get_max_scale(); [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; } if (wd.max_size != Size2i()) { - Size2i size = wd.max_size / _display_scale([wd.window_object screen]); + Size2i size = wd.max_size / screen_get_max_scale(); [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; } [wd.window_object toggleFullScreen:nil]; @@ -2623,8 +2831,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { wd.resize_disabled = p_enabled; - if (wd.fullscreen) //fullscreen window should be resizable, style will be applyed on exiting fs + if (wd.fullscreen) { //fullscreen window should be resizable, style will be applied on exiting fs return; + } if (p_enabled) { [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; } else { @@ -2633,7 +2842,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } break; case WINDOW_FLAG_BORDERLESS: { // OrderOut prevents a lose focus bug with the window - [wd.window_object orderOut:nil]; + if ([wd.window_object isVisible]) { + [wd.window_object orderOut:nil]; + } wd.borderless = p_enabled; if (p_enabled) { [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; @@ -2647,7 +2858,13 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo [wd.window_object setFrame:frameRect display:NO]; } _update_window(wd); - [wd.window_object makeKeyAndOrderFront:nil]; + if ([wd.window_object isVisible]) { + if (wd.no_focus) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } + } } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { wd.on_top = p_enabled; @@ -2665,7 +2882,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)]; } _set_window_per_pixel_transparency_enabled(p_enabled, p_window); - + } break; + case WINDOW_FLAG_NO_FOCUS: { + wd.no_focus = p_enabled; } break; default: { } @@ -2691,6 +2910,9 @@ bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) co case WINDOW_FLAG_TRANSPARENT: { return wd.layered_window; } break; + case WINDOW_FLAG_NO_FOCUS: { + return wd.no_focus; + } break; default: { } } @@ -2710,7 +2932,11 @@ void DisplayServerOSX::window_move_to_foreground(WindowID p_window) { const WindowData &wd = windows[p_window]; [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; - [wd.window_object makeKeyAndOrderFront:nil]; + if (wd.no_focus) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } } bool DisplayServerOSX::window_can_draw(WindowID p_window) const { @@ -2736,8 +2962,9 @@ void DisplayServerOSX::window_set_ime_active(const bool p_active, WindowID p_win wd.im_active = p_active; - if (!p_active) + if (!p_active) { [wd.window_view cancelComposition]; + } } void DisplayServerOSX::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { @@ -2749,8 +2976,8 @@ void DisplayServerOSX::window_set_ime_position(const Point2i &p_pos, WindowID p_ wd.im_position = p_pos; } -bool DisplayServerOSX::get_swap_ok_cancel() { - return true; +bool DisplayServerOSX::get_swap_cancel_ok() { + return false; } void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { @@ -2758,8 +2985,9 @@ void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); - if (cursor_shape == p_shape) + if (cursor_shape == p_shape) { return; + } if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { cursor_shape = p_shape; @@ -2770,23 +2998,57 @@ void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { [cursors[p_shape] set]; } else { switch (p_shape) { - case CURSOR_ARROW: [[NSCursor arrowCursor] set]; break; - case CURSOR_IBEAM: [[NSCursor IBeamCursor] set]; break; - case CURSOR_POINTING_HAND: [[NSCursor pointingHandCursor] set]; break; - case CURSOR_CROSS: [[NSCursor crosshairCursor] set]; break; - case CURSOR_WAIT: [[NSCursor arrowCursor] set]; break; - case CURSOR_BUSY: [[NSCursor arrowCursor] set]; break; - case CURSOR_DRAG: [[NSCursor closedHandCursor] set]; break; - case CURSOR_CAN_DROP: [[NSCursor openHandCursor] set]; break; - case CURSOR_FORBIDDEN: [[NSCursor operationNotAllowedCursor] set]; break; - case CURSOR_VSIZE: [_cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; break; - case CURSOR_HSIZE: [_cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; break; - case CURSOR_BDIAGSIZE: [_cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; break; - case CURSOR_FDIAGSIZE: [_cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; break; - case CURSOR_MOVE: [[NSCursor arrowCursor] set]; break; - case CURSOR_VSPLIT: [[NSCursor resizeUpDownCursor] set]; break; - case CURSOR_HSPLIT: [[NSCursor resizeLeftRightCursor] set]; break; - case CURSOR_HELP: [_cursorFromSelector(@selector(_helpCursor)) set]; break; + case CURSOR_ARROW: + [[NSCursor arrowCursor] set]; + break; + case CURSOR_IBEAM: + [[NSCursor IBeamCursor] set]; + break; + case CURSOR_POINTING_HAND: + [[NSCursor pointingHandCursor] set]; + break; + case CURSOR_CROSS: + [[NSCursor crosshairCursor] set]; + break; + case CURSOR_WAIT: + [[NSCursor arrowCursor] set]; + break; + case CURSOR_BUSY: + [[NSCursor arrowCursor] set]; + break; + case CURSOR_DRAG: + [[NSCursor closedHandCursor] set]; + break; + case CURSOR_CAN_DROP: + [[NSCursor openHandCursor] set]; + break; + case CURSOR_FORBIDDEN: + [[NSCursor operationNotAllowedCursor] set]; + break; + case CURSOR_VSIZE: + [_cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; + break; + case CURSOR_HSIZE: + [_cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; + break; + case CURSOR_BDIAGSIZE: + [_cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; + break; + case CURSOR_FDIAGSIZE: + [_cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; + break; + case CURSOR_MOVE: + [[NSCursor arrowCursor] set]; + break; + case CURSOR_VSPLIT: + [[NSCursor resizeUpDownCursor] set]; + break; + case CURSOR_HSPLIT: + [[NSCursor resizeLeftRightCursor] set]; + break; + case CURSOR_HELP: + [_cursorFromSelector(@selector(_helpCursor)) set]; + break; default: { } } @@ -2918,97 +3180,140 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape } } +struct LayoutInfo { + String name; + String code; +}; + +static Vector<LayoutInfo> kbd_layouts; +static int current_layout = 0; static bool keyboard_layout_dirty = true; static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { + kbd_layouts.clear(); + current_layout = 0; keyboard_layout_dirty = true; } -// Returns string representation of keys, if they are printable. -static NSString *createStringForKeys(const CGKeyCode *keyCode, int length) { - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) - return nil; +void _update_keyboard_layouts() { + @autoreleasepool { + TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource(); + NSString *cur_name = (NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName); + CFRelease(cur_source); - CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) - return nil; + // Enum IME layouts + NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; + NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false); + for (NSUInteger i = 0; i < [list_ime count]; i++) { + LayoutInfo ly; + NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); - const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); + NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages); + ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); + kbd_layouts.push_back(ly); - OSStatus err; - CFMutableStringRef output = CFStringCreateMutable(NULL, 0); + if ([name isEqualToString:cur_name]) { + current_layout = kbd_layouts.size() - 1; + } + } + [list_ime release]; - for (int i = 0; i < length; ++i) { - UInt32 keysDown = 0; - UniChar chars[4]; - UniCharCount realLength; + // Enum plain keyboard layouts + NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; + NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false); + for (NSUInteger i = 0; i < [list_kbd count]; i++) { + LayoutInfo ly; + NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); - err = UCKeyTranslate(keyboardLayout, - keyCode[i], - kUCKeyActionDisplay, - 0, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keysDown, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); + NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages); + ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); + kbd_layouts.push_back(ly); - if (err != noErr) { - CFRelease(output); - return nil; + if ([name isEqualToString:cur_name]) { + current_layout = kbd_layouts.size() - 1; + } } - - CFStringAppendCharacters(output, chars, 1); + [list_kbd release]; } - return (NSString *)output; + keyboard_layout_dirty = false; } -DisplayServerOSX::LatinKeyboardVariant DisplayServerOSX::get_latin_keyboard_variant() const { - _THREAD_SAFE_METHOD_ - - static LatinKeyboardVariant layout = LATIN_KEYBOARD_QWERTY; +int DisplayServerOSX::keyboard_get_layout_count() const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } + return kbd_layouts.size(); +} +void DisplayServerOSX::keyboard_set_current_layout(int p_index) { if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } + + ERR_FAIL_INDEX(p_index, kbd_layouts.size()); - layout = LATIN_KEYBOARD_QWERTY; + NSString *cur_name = [NSString stringWithUTF8String:kbd_layouts[p_index].name.utf8().get_data()]; - CGKeyCode keys[] = { kVK_ANSI_Q, kVK_ANSI_W, kVK_ANSI_E, kVK_ANSI_R, kVK_ANSI_T, kVK_ANSI_Y }; - NSString *test = createStringForKeys(keys, 6); + NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; + NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false); + for (NSUInteger i = 0; i < [list_kbd count]; i++) { + NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + if ([name isEqualToString:cur_name]) { + TISSelectInputSource((TISInputSourceRef)[list_kbd objectAtIndex:i]); + break; + } + } + [list_kbd release]; - if ([test isEqualToString:@"qwertz"]) { - layout = LATIN_KEYBOARD_QWERTZ; - } else if ([test isEqualToString:@"azerty"]) { - layout = LATIN_KEYBOARD_AZERTY; - } else if ([test isEqualToString:@"qzerty"]) { - layout = LATIN_KEYBOARD_QZERTY; - } else if ([test isEqualToString:@"',.pyf"]) { - layout = LATIN_KEYBOARD_DVORAK; - } else if ([test isEqualToString:@"xvlcwk"]) { - layout = LATIN_KEYBOARD_NEO; - } else if ([test isEqualToString:@"qwfpgj"]) { - layout = LATIN_KEYBOARD_COLEMAK; + NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; + NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false); + for (NSUInteger i = 0; i < [list_ime count]; i++) { + NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + if ([name isEqualToString:cur_name]) { + TISSelectInputSource((TISInputSourceRef)[list_ime objectAtIndex:i]); + break; } + } + [list_ime release]; +} - [test release]; +int DisplayServerOSX::keyboard_get_current_layout() const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } - keyboard_layout_dirty = false; - return layout; + return current_layout; +} + +String DisplayServerOSX::keyboard_get_layout_language(int p_index) const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); } - return layout; + ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); + return kbd_layouts[p_index].code; +} + +String DisplayServerOSX::keyboard_get_layout_name(int p_index) const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); + } + + ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); + return kbd_layouts[p_index].name; } void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) { Ref<InputEvent> ev = p_event; - InputFilter::get_singleton()->accumulate_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); } void DisplayServerOSX::_release_pressed_events() { _THREAD_SAFE_METHOD_ - if (InputFilter::get_singleton()) { - InputFilter::get_singleton()->release_pressed_events(); + if (Input::get_singleton()) { + Input::get_singleton()->release_pressed_events(); } } @@ -3076,15 +3381,36 @@ void DisplayServerOSX::process_events() { inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event == nil) + if (event == nil) { break; + } [NSApp sendEvent:event]; } if (!drop_events) { _process_key_events(); - InputFilter::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_accumulated_events(); + } + + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + WindowData &wd = E->get(); + if (wd.mpath.size() > 0) { + const Vector2 mpos = _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + if (Geometry2D::is_point_in_polygon(mpos, wd.mpath)) { + if ([wd.window_object ignoresMouseEvents]) { + [wd.window_object setIgnoresMouseEvents:NO]; + } + } else { + if (![wd.window_object ignoresMouseEvents]) { + [wd.window_object setIgnoresMouseEvents:YES]; + } + } + } else { + if ([wd.window_object ignoresMouseEvents]) { + [wd.window_object setIgnoresMouseEvents:NO]; + } + } } [autoreleasePool drain]; @@ -3165,11 +3491,11 @@ void DisplayServerOSX::set_icon(const Ref<Image> &p_icon) { Vector<String> DisplayServerOSX::get_rendering_drivers_func() { Vector<String> drivers; -#ifdef VULKAN_ENABLED +#if defined(VULKAN_ENABLED) drivers.push_back("vulkan"); #endif -#ifdef OPENGL_ENABLED - drivers.push_back("opengl"); +#if defined(OPENGL_ENABLED) + drivers.push_back("opengl_es"); #endif return drivers; @@ -3184,9 +3510,14 @@ String DisplayServerOSX::ime_get_text() const { } DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const { + Point2i position = p_position; + position.y *= -1; + position += _get_screens_origin(); + position /= screen_get_max_scale(); + + NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/]; for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - Rect2i win_rect = Rect2i(window_get_position(E->key()), window_get_size(E->key())); - if (win_rect.has_point(p_position)) { + if ([E->get().window_object windowNumber] == wnum) { return E->key(); } } @@ -3208,23 +3539,19 @@ ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) co } DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - return memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + DisplayServer *ds = memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + if (r_error != OK) { + ds->alert("Your video card driver does not support any of the supported Metal versions.", "Unable to initialize Video driver"); + } + return ds; } DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, const Rect2i &p_rect) { WindowID id; + const float scale = screen_get_max_scale(); { WindowData wd; - float displayScale = 1.0; - if (OS_OSX::get_singleton()->is_hidpi_allowed()) { - // note that mainScreen is not screen #0 but the one with the keyboard focus. - NSScreen *screen = [NSScreen mainScreen]; - if ([screen respondsToSelector:@selector(backingScaleFactor)]) { - displayScale = fmax(displayScale, [screen backingScaleFactor]); - } - } - wd.window_delegate = [[GodotWindowDelegate alloc] init]; ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate"); [wd.window_delegate setWindowID:window_id_counter]; @@ -3237,7 +3564,7 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, c // initWithContentRect uses bottom-left corner of the window’s frame as origin. wd.window_object = [[GodotWindow alloc] - initWithContentRect:NSMakeRect(position.x / displayScale, (position.y - p_rect.size.height) / displayScale, p_rect.size.width / displayScale, p_rect.size.height / displayScale) + initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale) styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO]; @@ -3246,24 +3573,7 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, c wd.window_view = [[GodotContentView alloc] init]; ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view"); [wd.window_view setWindowID:window_id_counter]; - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_14) { - [wd.window_view setWantsLayer:TRUE]; - } - - if (displayScale > 1.0) { -#if defined(OPENGL_ENABLED) - if (rendering_driver == "opengl_es") { - [wd.window_view setWantsBestResolutionOpenGLSurface:YES]; - } -#endif - [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - } else { -#if defined(OPENGL_ENABLED) - if (rendering_driver == "opengl_es") { - [wd.window_view setWantsBestResolutionOpenGLSurface:NO]; - } -#endif - } + [wd.window_view setWantsLayer:TRUE]; [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; [wd.window_object setContentView:wd.window_view]; @@ -3271,33 +3581,26 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, c [wd.window_object setAcceptsMouseMovedEvents:YES]; [wd.window_object setRestorable:NO]; - if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) + if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; + } + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { if (context_vulkan) { - CALayer *layer = [wd.window_view layer]; - layer.contentsScale = displayScale; Error err = context_vulkan->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height); ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context"); } } #endif -#ifdef OPENGL_ENABLED +#if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2 = memnew(ContextGL_OSX(wd.window_view, false)); - - if (wd.context_gles2->initialize() != OK) { - memdelete(wd.context_gles2); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a OpenGL context"); - } - - //if (RasterizerGLES2::is_viable() == OK) { - // RasterizerGLES2::register_config(); - // RasterizerGLES2::make_current(); - //} } #endif id = window_id_counter++; @@ -3307,25 +3610,22 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, c WindowData &wd = windows[id]; window_set_mode(p_mode, id); - float displayScale = _display_scale([wd.window_object screen]); const NSRect contentRect = [wd.window_view frame]; - wd.size.width = contentRect.size.width * displayScale; - wd.size.height = contentRect.size.height * displayScale; + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { - if (OS_OSX::singleton->is_hidpi_allowed()) { - [wd.window_view setWantsBestResolutionOpenGLSurface:YES]; - } else { - [wd.window_view setWantsBestResolutionOpenGLSurface:NO]; - } - wd.context_gles2->update(); + //TODO - reimplement OpenGLES } #endif #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - layer.contentsScale = displayScale; context_vulkan->window_resize(id, wd.size.width, wd.size.height); } #endif @@ -3393,7 +3693,7 @@ bool DisplayServerOSX::is_console_visible() const { } DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - InputFilter::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); r_error = OK; drop_events = false; @@ -3420,6 +3720,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode keyboard_layout_dirty = true; displays_arrangement_dirty = true; + displays_scale_dirty = true; // Register to be notified on keyboard layout changes CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), @@ -3437,8 +3738,9 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode NSString *title; NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil) + if (nsappname == nil) { nsappname = [[NSProcessInfo processInfo] processName]; + } // Setup Dock menu dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; @@ -3491,8 +3793,9 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event == nil) + if (event == nil) { break; + } [NSApp sendEvent:event]; } @@ -3513,7 +3816,6 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode #endif #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextOSX); if (context_vulkan->initialize() != OK) { memdelete(context_vulkan); @@ -3524,14 +3826,23 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode } #endif - WindowID main_window = _create_window(p_mode, Rect2i(Point2i(), p_resolution)); + Point2i window_position( + screen_get_position(0).x + (screen_get_size(0).width - p_resolution.width) / 2, + screen_get_position(0).y + (screen_get_size(0).height - p_resolution.height) / 2); + WindowID main_window = _create_window(p_mode, Rect2i(window_position, p_resolution)); + ERR_FAIL_COND(main_window == INVALID_WINDOW_ID); for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, main_window); } } - [windows[main_window].window_object makeKeyAndOrderFront:nil]; + show_window(MAIN_WINDOW_ID); +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { rendering_device_vulkan = memnew(RenderingDeviceVulkan); @@ -3542,14 +3853,6 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode #endif [NSApp activateIgnoringOtherApps:YES]; - - /* - visual_server = memnew(VisualServerRaster); - if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { - visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD)); - } - visual_server->init(); - */ } DisplayServerOSX::~DisplayServerOSX() { @@ -3562,22 +3865,29 @@ DisplayServerOSX::~DisplayServerOSX() { } //destroy all windows - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - [E->get().window_object setContentView:nil]; - [E->get().window_object close]; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E;) { + Map<WindowID, WindowData>::Element *F = E; + E = E->next(); + [F->get().window_object setContentView:nil]; + [F->get().window_object close]; } //destroy drivers +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - if (rendering_device_vulkan) { rendering_device_vulkan->finalize(); memdelete(rendering_device_vulkan); } - if (context_vulkan) + if (context_vulkan) { memdelete(context_vulkan); + } } #endif @@ -3585,9 +3895,6 @@ DisplayServerOSX::~DisplayServerOSX() { CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); cursors_cache.clear(); - - //visual_server->finish(); - //memdelete(visual_server); } void DisplayServerOSX::register_osx_driver() { diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index c2df9c7082..e988e51e72 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -30,13 +30,13 @@ #include "export.h" +#include "core/config/project_settings.h" #include "core/io/marshalls.h" #include "core/io/resource_saver.h" #include "core/io/zip_io.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "core/version.h" #include "editor/editor_export.h" #include "editor/editor_node.h" @@ -46,7 +46,6 @@ #include <sys/stat.h> class EditorExportPlatformOSX : public EditorExportPlatform { - GDCLASS(EditorExportPlatformOSX, EditorExportPlatform); int version_code; @@ -56,8 +55,10 @@ class EditorExportPlatformOSX : public EditorExportPlatform { void _fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary); void _make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data); + Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path); Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path); Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); + void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); #ifdef OSX_ENABLED bool use_codesign() const { return true; } @@ -66,17 +67,39 @@ class EditorExportPlatformOSX : public EditorExportPlatform { bool use_codesign() const { return false; } bool use_dmg() const { return false; } #endif + bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { + String pname = p_package; + + if (pname.length() == 0) { + if (r_error) { + *r_error = TTR("Identifier is missing."); + } + return false; + } + + for (int i = 0; i < pname.length(); i++) { + char32_t c = pname[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) { + if (r_error) { + *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); + } + return false; + } + } + + return true; + } protected: - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features); - virtual void get_export_options(List<ExportOption> *r_options); + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override; + virtual void get_export_options(List<ExportOption> *r_options) override; public: - virtual String get_name() const { return "Mac OSX"; } - virtual String get_os_name() const { return "OSX"; } - virtual Ref<Texture2D> get_logo() const { return logo; } + virtual String get_name() const override { return "macOS"; } + virtual String get_os_name() const override { return "macOS"; } + virtual Ref<Texture2D> get_logo() const override { return logo; } - virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { List<String> list; if (use_dmg()) { list.push_back("dmg"); @@ -84,18 +107,17 @@ public: list.push_back("zip"); return list; } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); - - virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - virtual void get_platform_features(List<String> *r_features) { + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; + virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("pc"); r_features->push_back("s3tc"); r_features->push_back("OSX"); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { } EditorExportPlatformOSX(); @@ -117,14 +139,13 @@ void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset> } void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); @@ -140,6 +161,11 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), "")); #endif r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); @@ -148,7 +174,6 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) } void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, Vector<uint8_t> &p_dest) { - int src_len = p_size * p_size; Vector<uint8_t> result; @@ -163,7 +188,6 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, uint8_t cur = p_source.ptr()[i * 4 + p_ch]; if (i < src_len - 2) { - if ((p_source.ptr()[(i + 1) * 4 + p_ch] == cur) && (p_source.ptr()[(i + 2) * 4 + p_ch] == cur)) { if (buf_size > 0) { result.write[res_size++] = (uint8_t)(buf_size - 1); @@ -215,7 +239,6 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, } void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data) { - Ref<ImageTexture> it = memnew(ImageTexture); Vector<uint8_t> data; @@ -320,7 +343,6 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ } void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary) { - String str; String strnew; str.parse_utf8((const char *)plist.ptr(), plist.size()); @@ -332,8 +354,8 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset strnew += lines[i].replace("$name", p_binary) + "\n"; } else if (lines[i].find("$info") != -1) { strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n"; - } else if (lines[i].find("$identifier") != -1) { - strnew += lines[i].replace("$identifier", p_preset->get("application/identifier")) + "\n"; + } else if (lines[i].find("$bundle_identifier") != -1) { + strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n"; } else if (lines[i].find("$version") != -1) { @@ -369,7 +391,54 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset - and then wrap it up in a DMG **/ +Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +#ifdef OSX_ENABLED + List<String> args; + + args.push_back("altool"); + args.push_back("--notarize-app"); + + args.push_back("--primary-bundle-id"); + args.push_back(p_preset->get("application/bundle_identifier")); + + args.push_back("--username"); + args.push_back(p_preset->get("notarization/apple_id_name")); + + args.push_back("--password"); + args.push_back(p_preset->get("notarization/apple_id_password")); + + args.push_back("--type"); + args.push_back("osx"); + + if (p_preset->get("notarization/apple_team_id")) { + args.push_back("--asc-provider"); + args.push_back(p_preset->get("notarization/apple_team_id")); + } + + args.push_back("--file"); + args.push_back(p_path); + + String str; + Error err = OS::get_singleton()->execute("xcrun", args, true, nullptr, &str, nullptr, true); + ERR_FAIL_COND_V(err != OK, err); + + print_line("altool (" + p_path + "):\n" + str); + if (str.find("RequestUUID") == -1) { + EditorNode::add_io_error("altool: " + str); + return FAILED; + } else { + print_line("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."); + print_line(" You can check progress manually by opening a Terminal and running the following command:"); + print_line(" \"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\""); + } + +#endif + + return OK; +} + Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +#ifdef OSX_ENABLED List<String> args; if (p_preset->get("codesign/timestamp")) { @@ -380,8 +449,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese args.push_back("runtime"); } - if (p_preset->get("codesign/entitlements") != "") { - /* this should point to our entitlements.plist file that sandboxes our application, I don't know if this should also be placed in our app bundle */ + if ((p_preset->get("codesign/entitlements") != "") && (p_path.get_extension() != "dmg")) { args.push_back("--entitlements"); args.push_back(p_preset->get("codesign/entitlements")); } @@ -405,7 +473,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese Error err = OS::get_singleton()->execute("codesign", args, true, nullptr, &str, nullptr, true); ERR_FAIL_COND_V(err != OK, err); - print_line("codesign (" + p_path + "): " + str); + print_line("codesign (" + p_path + "):\n" + str); if (str.find("no identity found") != -1) { EditorNode::add_io_error("codesign: no identity found"); return FAILED; @@ -414,6 +482,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese EditorNode::add_io_error("codesign: invalid entitlements file"); return FAILED; } +#endif return OK; } @@ -458,10 +527,11 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p EditorProgress ep("export", "Exporting for OSX", 3, true); - if (p_debug) + if (p_debug) { src_pkg_name = p_preset->get("custom_template/debug"); - else + } else { src_pkg_name = p_preset->get("custom_template/release"); + } if (src_pkg_name == "") { String err; @@ -485,7 +555,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); if (!src_pkg_zip) { - EditorNode::add_io_error("Could not find template app to export:\n" + src_pkg_name); return ERR_FILE_NOT_FOUND; } @@ -495,64 +564,54 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p String binary_to_use = "godot_osx_" + String(p_debug ? "debug" : "release") + ".64"; String pkg_name; - if (p_preset->get("application/name") != "") + if (p_preset->get("application/name") != "") { pkg_name = p_preset->get("application/name"); // app_name - else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") + } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); - else + } else { pkg_name = "Unnamed"; + } String pkg_name_safe = OS::get_singleton()->get_safe_dir_name(pkg_name); Error err = OK; String tmp_app_path_name = ""; - zlib_filefunc_def io2 = io; - FileAccess *dst_f = nullptr; - io2.opaque = &dst_f; - zipFile dst_pkg_zip = nullptr; DirAccess *tmp_app_path = nullptr; String export_format = use_dmg() && p_path.ends_with("dmg") ? "dmg" : "zip"; - if (export_format == "dmg") { - // We're on OSX so we can export to DMG, but first we create our application bundle - tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app"); - print_line("Exporting to " + tmp_app_path_name); - tmp_app_path = DirAccess::create_for_path(tmp_app_path_name); - if (!tmp_app_path) { - err = ERR_CANT_CREATE; - } - // Create our folder structure or rely on unzip? - if (err == OK) { - print_line("Creating " + tmp_app_path_name + "/Contents/MacOS"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); - } + // Create our application bundle. + tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".app"); + print_line("Exporting to " + tmp_app_path_name); + tmp_app_path = DirAccess::create_for_path(tmp_app_path_name); + if (!tmp_app_path) { + err = ERR_CANT_CREATE; + } - if (err == OK) { - print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); - } + // Create our folder structure. + if (err == OK) { + print_line("Creating " + tmp_app_path_name + "/Contents/MacOS"); + err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS"); + } - if (err == OK) { - print_line("Creating " + tmp_app_path_name + "/Contents/Resources"); - err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); - } - } else { - // Open our destination zip file - dst_pkg_zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2); - if (!dst_pkg_zip) { - err = ERR_CANT_CREATE; - } + if (err == OK) { + print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks"); + err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); } - // Now process our template + if (err == OK) { + print_line("Creating " + tmp_app_path_name + "/Contents/Resources"); + err = tmp_app_path->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); + } + + // Now process our template. bool found_binary = false; int total_size = 0; while (ret == UNZ_OK && err == OK) { bool is_execute = false; - //get filename + // Get filename. unz_file_info info; char fname[16384]; ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0); @@ -562,13 +621,12 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p Vector<uint8_t> data; data.resize(info.uncompressed_size); - //read + // Read. unzOpenCurrentFile(src_pkg_zip); unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size()); unzCloseCurrentFile(src_pkg_zip); - //write - + // Write. file = file.replace_first("osx_template.app/", ""); if (file == "Contents/Info.plist") { @@ -578,7 +636,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (file.begins_with("Contents/MacOS/godot_")) { if (file != "Contents/MacOS/" + binary_to_use) { ret = unzGoToNextFile(src_pkg_zip); - continue; //ignore! + continue; // skip } found_binary = true; is_execute = true; @@ -586,12 +644,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } if (file == "Contents/Resources/icon.icns") { - //see if there is an icon + // See if there is an icon. String iconpath; - if (p_preset->get("application/icon") != "") + if (p_preset->get("application/icon") != "") { iconpath = p_preset->get("application/icon"); - else + } else { iconpath = ProjectSettings::get_singleton()->get("application/config/icon"); + } if (iconpath != "") { if (iconpath.get_extension() == "icns") { @@ -614,18 +673,17 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } if (data.size() > 0) { - if (file.find("/data.mono.osx.64.release_debug/") != -1) { if (!p_debug) { ret = unzGoToNextFile(src_pkg_zip); - continue; //skip + continue; // skip } file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name_safe + "/"); } if (file.find("/data.mono.osx.64.release/") != -1) { if (p_debug) { ret = unzGoToNextFile(src_pkg_zip); - continue; //skip + continue; // skip } file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name_safe + "/"); } @@ -633,62 +691,31 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p print_line("ADDING: " + file + " size: " + itos(data.size())); total_size += data.size(); - if (export_format == "dmg") { - // write it into our application bundle - file = tmp_app_path_name.plus_file(file); - if (err == OK) { - err = tmp_app_path->make_dir_recursive(file.get_base_dir()); - } - if (err == OK) { - // write the file, need to add chmod - FileAccess *f = FileAccess::open(file, FileAccess::WRITE); - if (f) { - f->store_buffer(data.ptr(), data.size()); - f->close(); - if (is_execute) { - // Chmod with 0755 if the file is executable - FileAccess::set_unix_permissions(file, 0755); - } - memdelete(f); - } else { - err = ERR_CANT_CREATE; + // Write it into our application bundle. + file = tmp_app_path_name.plus_file(file); + if (err == OK) { + err = tmp_app_path->make_dir_recursive(file.get_base_dir()); + } + if (err == OK) { + FileAccess *f = FileAccess::open(file, FileAccess::WRITE); + if (f) { + f->store_buffer(data.ptr(), data.size()); + f->close(); + if (is_execute) { + // chmod with 0755 if the file is executable. + FileAccess::set_unix_permissions(file, 0755); } + memdelete(f); + } else { + err = ERR_CANT_CREATE; } - } else { - // add it to our zip file - file = pkg_name + ".app/" + file; - - zip_fileinfo fi; - fi.tmz_date.tm_hour = info.tmu_date.tm_hour; - fi.tmz_date.tm_min = info.tmu_date.tm_min; - fi.tmz_date.tm_sec = info.tmu_date.tm_sec; - fi.tmz_date.tm_mon = info.tmu_date.tm_mon; - fi.tmz_date.tm_mday = info.tmu_date.tm_mday; - fi.tmz_date.tm_year = info.tmu_date.tm_year; - fi.dosDate = info.dosDate; - fi.internal_fa = info.internal_fa; - fi.external_fa = info.external_fa; - - zipOpenNewFileInZip(dst_pkg_zip, - file.utf8().get_data(), - &fi, - nullptr, - 0, - nullptr, - 0, - nullptr, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); - - zipWriteInFileInZip(dst_pkg_zip, data.ptr(), data.size()); - zipCloseFileInZip(dst_pkg_zip); } } ret = unzGoToNextFile(src_pkg_zip); } - // we're done with our source zip + // We're done with our source zip. unzClose(src_pkg_zip); if (!found_binary) { @@ -701,124 +728,143 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p return ERR_SKIP; } - if (export_format == "dmg") { - String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; - Vector<SharedObject> shared_objects; - err = save_pack(p_preset, pack_path, &shared_objects); + String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; + Vector<SharedObject> shared_objects; + err = save_pack(p_preset, pack_path, &shared_objects); - // see if we can code sign our new package - bool sign_enabled = p_preset->get("codesign/enable"); + // See if we can code sign our new package. + bool sign_enabled = p_preset->get("codesign/enable"); - if (err == OK) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - for (int i = 0; i < shared_objects.size(); i++) { - err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); - if (err == OK && sign_enabled) { - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); - } + if (err == OK) { + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < shared_objects.size(); i++) { + err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); + if (err == OK && sign_enabled) { + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); } - memdelete(da); } + memdelete(da); + } - if (err == OK && sign_enabled) { - if (ep.step("Code signing bundle", 2)) { - return ERR_SKIP; - } - - // the order in which we code sign is important, this is a bit of a shame or we could do this in our loop that extracts the files from our ZIP - - // start with our application - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name); - - ///@TODO we should check the contents of /Contents/Frameworks for frameworks to sign + if (err == OK && sign_enabled) { + if (ep.step("Code signing bundle", 2)) { + return ERR_SKIP; } + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name); + } - // and finally create a DMG + if (export_format == "dmg") { + // Create a DMG. if (err == OK) { if (ep.step("Making DMG", 3)) { return ERR_SKIP; } err = _create_dmg(p_path, pkg_name, tmp_app_path_name); } - - // Clean up temporary .app dir - OS::get_singleton()->move_to_trash(tmp_app_path_name); - - } else { // pck - - String pack_path = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".pck"); - - Vector<SharedObject> shared_objects; - err = save_pack(p_preset, pack_path, &shared_objects); - - if (err == OK) { - zipOpenNewFileInZip(dst_pkg_zip, - (pkg_name + ".app/Contents/Resources/" + pkg_name + ".pck").utf8().get_data(), - nullptr, - nullptr, - 0, - nullptr, - 0, - nullptr, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); - - FileAccess *pf = FileAccess::open(pack_path, FileAccess::READ); - if (pf) { - const int BSIZE = 16384; - uint8_t buf[BSIZE]; - - while (true) { - - int r = pf->get_buffer(buf, BSIZE); - if (r <= 0) - break; - zipWriteInFileInZip(dst_pkg_zip, buf, r); - } - - zipCloseFileInZip(dst_pkg_zip); - memdelete(pf); - } else { - err = ERR_CANT_OPEN; + // Sign DMG. + if (err == OK && sign_enabled) { + if (ep.step("Code signing DMG", 3)) { + return ERR_SKIP; } + err = _code_sign(p_preset, p_path); } - + } else { + // Create ZIP. if (err == OK) { - //add shared objects - for (int i = 0; i < shared_objects.size(); i++) { - Vector<uint8_t> file = FileAccess::get_file_as_array(shared_objects[i].path); - ERR_CONTINUE(file.empty()); - - zipOpenNewFileInZip(dst_pkg_zip, - (pkg_name + ".app/Contents/Frameworks/").plus_file(shared_objects[i].path.get_file()).utf8().get_data(), - nullptr, - nullptr, - 0, - nullptr, - 0, - nullptr, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); - - zipWriteInFileInZip(dst_pkg_zip, file.ptr(), file.size()); - zipCloseFileInZip(dst_pkg_zip); + if (ep.step("Making ZIP", 3)) { + return ERR_SKIP; + } + if (FileAccess::exists(p_path)) { + OS::get_singleton()->move_to_trash(p_path); } + + FileAccess *dst_f = nullptr; + zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f); + zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); + + _zip_folder_recursive(zip, EditorSettings::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name); + + zipClose(zip, nullptr); } + } - // Clean up generated file. - DirAccess::remove_file_or_error(pack_path); + bool noto_enabled = p_preset->get("notarization/enable"); + if (err == OK && noto_enabled) { + if (ep.step("Sending archive for notarization", 4)) { + return ERR_SKIP; + } + err = _notarize(p_preset, p_path); } - } - if (dst_pkg_zip) { - zipClose(dst_pkg_zip, nullptr); + // Clean up temporary .app dir. + OS::get_singleton()->move_to_trash(tmp_app_path_name); } return err; } -bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { +void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { + String dir = p_root_path.plus_file(p_folder); + + DirAccess *da = DirAccess::open(dir); + da->list_dir_begin(); + String f; + while ((f = da->get_next()) != "") { + if (f == "." || f == "..") { + continue; + } + if (da->current_is_dir()) { + _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name); + } else { + bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)); + + OS::Time time = OS::get_singleton()->get_time(); + OS::Date date = OS::get_singleton()->get_date(); + + zip_fileinfo zipfi; + zipfi.tmz_date.tm_hour = time.hour; + zipfi.tmz_date.tm_mday = date.day; + zipfi.tmz_date.tm_min = time.min; + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_sec = time.sec; + zipfi.tmz_date.tm_year = date.year; + zipfi.dosDate = 0; + // 0100000: regular file type + // 0000755: permissions rwxr-xr-x + // 0000644: permissions rw-r--r-- + uint32_t _mode = (is_executable ? 0100755 : 0100644); + zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); + zipfi.internal_fa = 0; + + zipOpenNewFileInZip4(p_zip, + p_folder.plus_file(f).utf8().get_data(), + &zipfi, + nullptr, + 0, + nullptr, + 0, + nullptr, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION, + 0, + -MAX_WBITS, + DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, + nullptr, + 0, + 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions + 0); + + Vector<uint8_t> array = FileAccess::get_file_as_array(dir.plus_file(f)); + zipWriteInFileInZip(p_zip, array.ptr(), array.size()); + zipCloseFileInZip(p_zip); + } + } + da->list_dir_end(); + memdelete(da); +} +bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { String err; bool valid = false; @@ -843,13 +889,48 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset valid = dvalid || rvalid; r_missing_templates = !valid; - if (!err.empty()) + String identifier = p_preset->get("application/bundle_identifier"); + String pn_err; + if (!is_package_name_valid(identifier, &pn_err)) { + err += TTR("Invalid bundle identifier:") + " " + pn_err + "\n"; + valid = false; + } + + bool sign_enabled = p_preset->get("codesign/enable"); + if (sign_enabled) { + if (p_preset->get("codesign/identity") == "") { + err += TTR("Codesign: identity not specified.") + "\n"; + valid = false; + } + } + bool noto_enabled = p_preset->get("notarization/enable"); + if (noto_enabled) { + if (!sign_enabled) { + err += TTR("Notarization: code signing required.") + "\n"; + valid = false; + } + bool hr_enabled = p_preset->get("codesign/hardened_runtime"); + if (!hr_enabled) { + err += TTR("Notarization: hardened runtime required.") + "\n"; + valid = false; + } + if (p_preset->get("notarization/apple_id_name") == "") { + err += TTR("Notarization: Apple ID name not specified.") + "\n"; + valid = false; + } + if (p_preset->get("notarization/apple_id_password") == "") { + err += TTR("Notarization: Apple ID password not specified.") + "\n"; + valid = false; + } + } + + if (!err.empty()) { r_error = err; + } return valid; } EditorExportPlatformOSX::EditorExportPlatformOSX() { - Ref<Image> img = memnew(Image(_osx_logo)); logo.instance(); logo->create_from_image(img); @@ -859,7 +940,6 @@ EditorExportPlatformOSX::~EditorExportPlatformOSX() { } void register_osx_exporter() { - Ref<EditorExportPlatformOSX> platform; platform.instance(); diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index eacd2b5cc6..4e73d5441c 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -36,9 +36,8 @@ #include <unistd.h> int main(int argc, char **argv) { - #if defined(VULKAN_ENABLED) - //MoltenVK - enable full component swizzling support + // MoltenVK - enable full component swizzling support setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); #endif @@ -61,6 +60,9 @@ int main(int argc, char **argv) { OS_OSX os; Error err; + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE + if (os.open_with_filename != "") { char *argv_c = (char *)malloc(os.open_with_filename.utf8().size()); memcpy(argv_c, os.open_with_filename.utf8().get_data(), os.open_with_filename.utf8().size()); diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index 643acd8944..cfc371710b 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -98,6 +98,7 @@ int joypad::get_hid_element_state(rec_element *p_element) const { } return value; } + void joypad::add_hid_element(IOHIDElementRef p_element) { const CFTypeID elementTypeID = p_element ? CFGetTypeID(p_element) : 0; @@ -240,7 +241,6 @@ static bool is_joypad(IOHIDDeviceRef p_device_ref) { } void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) { - if (p_res != kIOReturnSuccess || have_device(p_device)) { return; } @@ -264,7 +264,6 @@ void JoypadOSX::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) { } void JoypadOSX::_device_removed(IOReturn p_res, IOHIDDeviceRef p_device) { - int device = get_joy_ref(p_device); ERR_FAIL_COND(device == -1); @@ -274,7 +273,6 @@ void JoypadOSX::_device_removed(IOReturn p_res, IOHIDDeviceRef p_device) { } static String _hex_str(uint8_t p_byte) { - static const char *dict = "0123456789abcdef"; char ret[3]; ret[2] = 0; @@ -286,7 +284,6 @@ static String _hex_str(uint8_t p_byte) { } bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { - p_joy->device_ref = p_device_ref; /* get device name */ String name; @@ -314,9 +311,16 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { if (refCF) { CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &product_id); } + + int version = 0; + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVersionNumberKey)); + if (refCF) { + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &version); + } + if (vendor && product_id) { char uid[128]; - sprintf(uid, "%04x%08x%04x%08x", OSSwapHostToBigInt32(vendor), 0, OSSwapHostToBigInt32(product_id), 0); + sprintf(uid, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version)); input->joy_connection_changed(id, true, name, uid); } else { //bluetooth device @@ -346,7 +350,6 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { } \ } bool joypad::config_force_feedback(io_service_t p_service) { - HRESULT ret = FFCreateDevice(p_service, &ff_device); ERR_FAIL_COND_V(ret != FF_OK, false); @@ -368,13 +371,13 @@ bool joypad::config_force_feedback(io_service_t p_service) { #define TEST_FF(ff) (features.supportedEffects & (ff)) bool joypad::check_ff_features() { - FFCAPABILITIES features; HRESULT ret = FFDeviceGetForceFeedbackCapabilities(ff_device, &features); if (ret == FF_OK && (features.supportedEffects & FFCAP_ET_CONSTANTFORCE)) { uint32_t val; ret = FFDeviceGetForceFeedbackProperty(ff_device, FFPROP_FFGAIN, &val, sizeof(val)); - if (ret != FF_OK) return false; + if (ret != FF_OK) + return false; int num_axes = features.numFfAxes; ff_axes = (DWORD *)memalloc(sizeof(DWORD) * num_axes); ff_directions = (LONG *)memalloc(sizeof(LONG) * num_axes); @@ -395,38 +398,38 @@ bool joypad::check_ff_features() { static int process_hat_value(int p_min, int p_max, int p_value) { int range = (p_max - p_min + 1); int value = p_value - p_min; - int hat_value = InputFilter::HAT_MASK_CENTER; + int hat_value = Input::HAT_MASK_CENTER; if (range == 4) { value *= 2; } switch (value) { case 0: - hat_value = InputFilter::HAT_MASK_UP; + hat_value = Input::HAT_MASK_UP; break; case 1: - hat_value = InputFilter::HAT_MASK_UP | InputFilter::HAT_MASK_RIGHT; + hat_value = Input::HAT_MASK_UP | Input::HAT_MASK_RIGHT; break; case 2: - hat_value = InputFilter::HAT_MASK_RIGHT; + hat_value = Input::HAT_MASK_RIGHT; break; case 3: - hat_value = InputFilter::HAT_MASK_DOWN | InputFilter::HAT_MASK_RIGHT; + hat_value = Input::HAT_MASK_DOWN | Input::HAT_MASK_RIGHT; break; case 4: - hat_value = InputFilter::HAT_MASK_DOWN; + hat_value = Input::HAT_MASK_DOWN; break; case 5: - hat_value = InputFilter::HAT_MASK_DOWN | InputFilter::HAT_MASK_LEFT; + hat_value = Input::HAT_MASK_DOWN | Input::HAT_MASK_LEFT; break; case 6: - hat_value = InputFilter::HAT_MASK_LEFT; + hat_value = Input::HAT_MASK_LEFT; break; case 7: - hat_value = InputFilter::HAT_MASK_UP | InputFilter::HAT_MASK_LEFT; + hat_value = Input::HAT_MASK_UP | Input::HAT_MASK_LEFT; break; default: - hat_value = InputFilter::HAT_MASK_CENTER; + hat_value = Input::HAT_MASK_CENTER; break; } return hat_value; @@ -438,8 +441,8 @@ void JoypadOSX::poll_joypads() const { } } -static const InputFilter::JoyAxis axis_correct(int p_value, int p_min, int p_max) { - InputFilter::JoyAxis jx; +static const Input::JoyAxis axis_correct(int p_value, int p_min, int p_max) { + Input::JoyAxis jx; if (p_min < 0) { jx.min = -1; if (p_value < 0) { @@ -509,14 +512,16 @@ void JoypadOSX::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { int JoypadOSX::get_joy_index(int p_id) const { for (int i = 0; i < device_list.size(); i++) { - if (device_list[i].id == p_id) return i; + if (device_list[i].id == p_id) + return i; } 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; + if (device_list[i].device_ref == p_device) + return i; } return -1; } @@ -556,7 +561,6 @@ static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 u } void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const { - CFRunLoopRef runloop = CFRunLoopGetCurrent(); IOReturn ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone); ERR_FAIL_COND(ret != kIOReturnSuccess); @@ -571,7 +575,7 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const { } } -JoypadOSX::JoypadOSX(InputFilter *in) { +JoypadOSX::JoypadOSX(Input *in) { self = this; input = in; @@ -600,7 +604,6 @@ JoypadOSX::JoypadOSX(InputFilter *in) { } JoypadOSX::~JoypadOSX() { - for (int i = 0; i < device_list.size(); i++) { device_list.write[i].free(); } diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index 62027c6a30..dc238e68e4 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -40,7 +40,7 @@ #include <ForceFeedback/ForceFeedbackConstants.h> #include <IOKit/hid/IOHIDLib.h> -#include "core/input/input_filter.h" +#include "core/input/input.h" struct rec_element { IOHIDElementRef ref; @@ -88,13 +88,12 @@ struct joypad { }; class JoypadOSX { - enum { JOYPADS_MAX = 16, }; private: - InputFilter *input; + Input *input; IOHIDManagerRef hid_manager; Vector<joypad> device_list; @@ -118,7 +117,7 @@ public: void _device_added(IOReturn p_res, IOHIDDeviceRef p_device); void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device); - JoypadOSX(InputFilter *in); + JoypadOSX(Input *in); ~JoypadOSX(); }; diff --git a/platform/osx/logo.png b/platform/osx/logo.png Binary files differindex 834bbf3ba6..b5a660b165 100644 --- a/platform/osx/logo.png +++ b/platform/osx/logo.png diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index d2c67cff9f..5a9e43450f 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -31,7 +31,7 @@ #ifndef OS_OSX_H #define OS_OSX_H -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "crash_handler_osx.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "drivers/coremidi/midi_driver_coremidi.h" @@ -44,7 +44,7 @@ class OS_OSX : public OS_Unix { bool force_quit; - JoypadOSX *joypad_osx; + JoypadOSX *joypad_osx = nullptr; #ifdef COREAUDIO_ENABLED AudioDriverCoreAudio audio_driver; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 49cb056c9f..399a29cbe0 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -60,39 +60,31 @@ public: switch (p_type) { case ERR_WARNING: - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) { - os_log_info(OS_LOG_DEFAULT, - "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - } + os_log_info(OS_LOG_DEFAULT, + "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details); logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); break; case ERR_SCRIPT: - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) { - os_log_error(OS_LOG_DEFAULT, - "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - } + os_log_error(OS_LOG_DEFAULT, + "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details); logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); break; case ERR_SHADER: - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) { - os_log_error(OS_LOG_DEFAULT, - "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - } + os_log_error(OS_LOG_DEFAULT, + "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details); logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); break; case ERR_ERROR: default: - if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_12) { - os_log_error(OS_LOG_DEFAULT, - "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - } + os_log_error(OS_LOG_DEFAULT, + "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details); logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); break; @@ -136,7 +128,7 @@ void OS_OSX::initialize_core() { } void OS_OSX::initialize_joypads() { - joypad_osx = memnew(JoypadOSX(InputFilter::get_singleton())); + joypad_osx = memnew(JoypadOSX(Input::get_singleton())); } void OS_OSX::initialize() { @@ -147,14 +139,15 @@ void OS_OSX::initialize() { } void OS_OSX::finalize() { - #ifdef COREMIDI_ENABLED midi_driver.close(); #endif delete_main_loop(); - memdelete(joypad_osx); + if (joypad_osx) { + memdelete(joypad_osx); + } } void OS_OSX::set_main_loop(MainLoop *p_main_loop) { @@ -269,10 +262,8 @@ String OS_OSX::get_system_dir(SystemDir p_dir) const { String ret; if (found) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES); if (paths && [paths count] >= 1) { - char *utfs = strdup([[paths firstObject] UTF8String]); ret.parse_utf8(utfs); free(utfs); @@ -283,7 +274,13 @@ String OS_OSX::get_system_dir(SystemDir p_dir) const { } Error OS_OSX::shell_open(String p_uri) { - [[NSWorkspace sharedWorkspace] openURL:[[NSURL alloc] initWithString:[[NSString stringWithUTF8String:p_uri.utf8().get_data()] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]]; + NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()]; + NSURL *uri = [[NSURL alloc] initWithString:string]; + // Escape special characters in filenames + if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) { + uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]; + } + [[NSWorkspace sharedWorkspace] openURL:uri]; return OK; } @@ -325,7 +322,7 @@ void OS_OSX::run() { } joypad_osx->process_joypads(); - if (Main::iteration() == true) { + if (Main::iteration()) { quit = true; } } @catch (NSException *exception) { diff --git a/platform/osx/platform_config.h b/platform/osx/platform_config.h index 155f37ed55..e657aca955 100644 --- a/platform/osx/platform_config.h +++ b/platform/osx/platform_config.h @@ -30,5 +30,4 @@ #include <alloca.h> -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h" #define PTHREAD_RENAME_SELF diff --git a/platform/osx/vulkan_context_osx.h b/platform/osx/vulkan_context_osx.h index 09a5494ae8..e996f176a9 100644 --- a/platform/osx/vulkan_context_osx.h +++ b/platform/osx/vulkan_context_osx.h @@ -35,7 +35,6 @@ #include <AppKit/AppKit.h> class VulkanContextOSX : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; public: diff --git a/platform/osx/vulkan_context_osx.mm b/platform/osx/vulkan_context_osx.mm index 320401cdcb..ec8745ff01 100644 --- a/platform/osx/vulkan_context_osx.mm +++ b/platform/osx/vulkan_context_osx.mm @@ -36,7 +36,6 @@ const char *VulkanContextOSX::_get_platform_surface_extension() const { } Error VulkanContextOSX::window_create(DisplayServer::WindowID p_window_id, id p_window, int p_width, int p_height) { - VkMacOSSurfaceCreateInfoMVK createInfo; createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; createInfo.pNext = NULL; diff --git a/platform/server/detect.py b/platform/server/detect.py index a73810cdf4..f9e151f956 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -39,7 +39,7 @@ def get_opts(): BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False), BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN))", False), BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), - EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), + EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False), ] @@ -61,8 +61,6 @@ def configure(env): env.Prepend(CCFLAGS=["-Os"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": @@ -73,13 +71,11 @@ def configure(env): env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "debug": env.Prepend(CCFLAGS=["-g3"]) - env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["-rdynamic"]) ## Architecture diff --git a/platform/server/godot_server.cpp b/platform/server/godot_server.cpp index df49bfaebf..9f22240a80 100644 --- a/platform/server/godot_server.cpp +++ b/platform/server/godot_server.cpp @@ -32,9 +32,11 @@ #include "os_server.h" int main(int argc, char *argv[]) { - OS_Server os; + // We must override main when testing is enabled + TEST_MAIN_OVERRIDE + Error err = Main::setup(argv[0], argc - 1, &argv[1]); if (err != OK) return 255; diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp index 0fda0663a2..9937ae5b62 100644 --- a/platform/server/os_server.cpp +++ b/platform/server/os_server.cpp @@ -30,7 +30,7 @@ #include "os_server.h" -#include "core/print_string.h" +#include "core/string/print_string.h" #include "drivers/dummy/rasterizer_dummy.h" #include "drivers/dummy/texture_loader_dummy.h" #include "servers/rendering/rendering_server_raster.h" @@ -42,11 +42,10 @@ #include <unistd.h> int OS_Server::get_video_driver_count() const { - return 1; } -const char *OS_Server::get_video_driver_name(int p_driver) const { +const char *OS_Server::get_video_driver_name(int p_driver) const { return "Dummy"; } @@ -55,7 +54,6 @@ int OS_Server::get_audio_driver_count() const { } const char *OS_Server::get_audio_driver_name(int p_driver) const { - return "Dummy"; } @@ -64,14 +62,12 @@ int OS_Server::get_current_video_driver() const { } void OS_Server::initialize_core() { - crash_handler.initialize(); OS_Unix::initialize_core(); } Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - args = OS::get_singleton()->get_cmdline_args(); current_videomode = p_desired; main_loop = nullptr; @@ -96,7 +92,6 @@ Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int } void OS_Server::finalize() { - if (main_loop) memdelete(main_loop); main_loop = nullptr; @@ -116,22 +111,18 @@ void OS_Server::set_mouse_show(bool p_show) { } void OS_Server::set_mouse_grab(bool p_grab) { - grab = p_grab; } bool OS_Server::is_mouse_grab_enabled() const { - return grab; } int OS_Server::get_mouse_button_state() const { - return 0; } Point2 OS_Server::get_mouse_position() const { - return Point2(); } @@ -142,12 +133,10 @@ void OS_Server::set_video_mode(const VideoMode &p_video_mode, int p_screen) { } OS::VideoMode OS_Server::get_video_mode(int p_screen) const { - return current_videomode; } Size2 OS_Server::get_window_size() const { - return Vector2(current_videomode.width, current_videomode.height); } @@ -155,30 +144,25 @@ void OS_Server::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) } MainLoop *OS_Server::get_main_loop() const { - return main_loop; } void OS_Server::delete_main_loop() { - if (main_loop) memdelete(main_loop); main_loop = nullptr; } void OS_Server::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; input->set_main_loop(p_main_loop); } bool OS_Server::can_draw() const { - return false; //can never draw }; String OS_Server::get_name() const { - return "Server"; } @@ -190,7 +174,6 @@ bool OS_Server::_check_internal_feature_support(const String &p_feature) { } void OS_Server::run() { - force_quit = false; if (!main_loop) @@ -199,7 +182,6 @@ void OS_Server::run() { main_loop->init(); while (!force_quit) { - if (Main::iteration()) break; }; @@ -208,7 +190,6 @@ void OS_Server::run() { } String OS_Server::get_config_path() const { - if (has_environment("XDG_CONFIG_HOME")) { return get_environment("XDG_CONFIG_HOME"); } else if (has_environment("HOME")) { @@ -219,7 +200,6 @@ String OS_Server::get_config_path() const { } String OS_Server::get_data_path() const { - if (has_environment("XDG_DATA_HOME")) { return get_environment("XDG_DATA_HOME"); } else if (has_environment("HOME")) { @@ -230,7 +210,6 @@ String OS_Server::get_data_path() const { } String OS_Server::get_cache_path() const { - if (has_environment("XDG_CACHE_HOME")) { return get_environment("XDG_CACHE_HOME"); } else if (has_environment("HOME")) { @@ -241,46 +220,37 @@ String OS_Server::get_cache_path() const { } String OS_Server::get_system_dir(SystemDir p_dir) const { - String xdgparam; switch (p_dir) { case SYSTEM_DIR_DESKTOP: { - xdgparam = "DESKTOP"; } break; case SYSTEM_DIR_DCIM: { - xdgparam = "PICTURES"; } break; case SYSTEM_DIR_DOCUMENTS: { - xdgparam = "DOCUMENTS"; } break; case SYSTEM_DIR_DOWNLOADS: { - xdgparam = "DOWNLOAD"; } break; case SYSTEM_DIR_MOVIES: { - xdgparam = "VIDEOS"; } break; case SYSTEM_DIR_MUSIC: { - xdgparam = "MUSIC"; } break; case SYSTEM_DIR_PICTURES: { - xdgparam = "PICTURES"; } break; case SYSTEM_DIR_RINGTONES: { - xdgparam = "MUSIC"; } break; @@ -304,7 +274,6 @@ bool OS_Server::is_disable_crash_handler() const { } OS_Server::OS_Server() { - //adriver here grab = false; }; diff --git a/platform/server/os_server.h b/platform/server/os_server.h index 62028b26e4..06ea483fd4 100644 --- a/platform/server/os_server.h +++ b/platform/server/os_server.h @@ -31,7 +31,7 @@ #ifndef OS_SERVER_H #define OS_SERVER_H -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "drivers/dummy/texture_loader_dummy.h" #include "drivers/unix/os_unix.h" #ifdef __APPLE__ @@ -47,7 +47,6 @@ #undef CursorShape class OS_Server : public OS_Unix { - RenderingServer *rendering_server; VideoMode current_videomode; List<String> args; diff --git a/platform/server/platform_config.h b/platform/server/platform_config.h index bdff93f02b..73136ec81b 100644 --- a/platform/server/platform_config.h +++ b/platform/server/platform_config.h @@ -31,10 +31,19 @@ #if defined(__linux__) || defined(__APPLE__) #include <alloca.h> #endif -#if defined(__FreeBSD__) || defined(__OpenBSD__) -#include <stdlib.h> + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +#include <stdlib.h> // alloca +// FreeBSD and OpenBSD use pthread_set_name_np, while other platforms, +// include NetBSD, use pthread_setname_np. NetBSD's version however requires +// a different format, we handle this directly in thread_posix. +#ifdef __NetBSD__ +#define PTHREAD_NETBSD_SET_NAME +#else #define PTHREAD_BSD_SET_NAME #endif +#endif + #ifdef __APPLE__ #define PTHREAD_RENAME_SELF #endif diff --git a/platform/uwp/app.cpp b/platform/uwp/app.cpp index d3870b0b6c..6090d13854 100644 --- a/platform/uwp/app.cpp +++ b/platform/uwp/app.cpp @@ -78,16 +78,6 @@ public: return 0; } -App::App() : - mWindowClosed(false), - mWindowVisible(true), - mWindowWidth(0), - mWindowHeight(0), - mEglDisplay(EGL_NO_DISPLAY), - mEglContext(EGL_NO_CONTEXT), - mEglSurface(EGL_NO_SURFACE) { -} - // The first method called when the IFrameworkView is being created. void App::Initialize(CoreApplicationView ^ applicationView) { // Register event handlers for app lifecycle. This example includes Activated, so that we @@ -156,7 +146,6 @@ void App::SetWindow(CoreWindow ^ p_window) { } static int _get_button(Windows::UI::Input::PointerPoint ^ pt) { - using namespace Windows::UI::Input; #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP @@ -207,7 +196,6 @@ static bool _is_touch(Windows::UI::Input::PointerPoint ^ pointerPoint) { } static Windows::Foundation::Point _get_pixel_position(CoreWindow ^ window, Windows::Foundation::Point rawPosition, OS *os) { - Windows::Foundation::Point outputPosition; // Compute coordinates normalized from 0..1. @@ -247,17 +235,14 @@ static Windows::Foundation::Point _get_pixel_position(CoreWindow ^ window, Windo }; static int _get_finger(uint32_t p_touch_id) { - return p_touch_id % 31; // for now }; void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args, bool p_pressed, bool p_is_wheel) { - Windows::UI::Input::PointerPoint ^ point = args->CurrentPoint; Windows::Foundation::Point pos = _get_pixel_position(window, point->Position, os); int but = _get_button(point); if (_is_touch(point)) { - Ref<InputEventScreenTouch> screen_touch; screen_touch.instance(); screen_touch->set_device(0); @@ -270,7 +255,6 @@ void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Cor os->input_event(screen_touch); } else { - Ref<InputEventMouseButton> mouse_button; mouse_button.instance(); mouse_button->set_device(0); @@ -301,22 +285,18 @@ void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Cor }; void App::OnPointerPressed(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) { - pointer_event(sender, args, true); }; void App::OnPointerReleased(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) { - pointer_event(sender, args, false); }; void App::OnPointerWheelChanged(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) { - pointer_event(sender, args, true, true); } void App::OnMouseModeChanged(Windows::System::Threading::Core::SignalNotifier ^ signalNotifier, bool timedOut) { - OS::MouseMode mode = os->get_mouse_mode(); SignalNotifier ^ notifier = mouseChangedNotifier; @@ -325,12 +305,10 @@ void App::OnMouseModeChanged(Windows::System::Threading::Core::SignalNotifier ^ ref new DispatchedHandler( [mode, notifier, this]() { if (mode == OS::MOUSE_MODE_CAPTURED) { - this->MouseMovedToken = MouseDevice::GetForCurrentView()->MouseMoved += ref new TypedEventHandler<MouseDevice ^, MouseEventArgs ^>(this, &App::OnMouseMoved); } else { - MouseDevice::GetForCurrentView()->MouseMoved -= MouseMovedToken; } @@ -341,12 +319,10 @@ void App::OnMouseModeChanged(Windows::System::Threading::Core::SignalNotifier ^ } void App::OnPointerMoved(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) { - Windows::UI::Input::PointerPoint ^ point = args->CurrentPoint; Windows::Foundation::Point pos = _get_pixel_position(window, point->Position, os); if (_is_touch(point)) { - Ref<InputEventScreenDrag> screen_drag; screen_drag.instance(); screen_drag->set_device(0); @@ -356,7 +332,6 @@ void App::OnPointerMoved(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Co os->input_event(screen_drag); } else { - // In case the mouse grabbed, MouseMoved will handle this if (os->get_mouse_mode() == OS::MouseMode::MOUSE_MODE_CAPTURED) return; @@ -375,7 +350,6 @@ void App::OnPointerMoved(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Co } void App::OnMouseMoved(MouseDevice ^ mouse_device, MouseEventArgs ^ args) { - // In case the mouse isn't grabbed, PointerMoved will handle this if (os->get_mouse_mode() != OS::MouseMode::MOUSE_MODE_CAPTURED) return; @@ -397,7 +371,6 @@ void App::OnMouseMoved(MouseDevice ^ mouse_device, MouseEventArgs ^ args) { } void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Windows::UI::Core::KeyEventArgs ^ key_args, Windows::UI::Core::CharacterReceivedEventArgs ^ char_args) { - OS_UWP::KeyEvent ke; ke.control = sender->GetAsyncKeyState(VirtualKey::Control) == CoreVirtualKeyStates::Down; @@ -407,7 +380,6 @@ void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Wind ke.pressed = p_pressed; if (key_args != nullptr) { - ke.type = OS_UWP::KeyEvent::MessageType::KEY_EVENT_MESSAGE; ke.unicode = 0; ke.keycode = KeyMappingWindows::get_keysym((unsigned int)key_args->VirtualKey); @@ -415,7 +387,6 @@ void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Wind 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.keycode = 0; @@ -425,6 +396,7 @@ void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Wind os->queue_key_event(ke); } + void App::OnKeyDown(CoreWindow ^ sender, KeyEventArgs ^ args) { key_event(sender, true, args); } @@ -506,14 +478,12 @@ void App::UpdateWindowSize(Size size) { } char **App::get_command_line(unsigned int *out_argc) { - static char *fail_cl[] = { "--path", "game", nullptr }; *out_argc = 2; FILE *f = _wfopen(L"__cl__.cl", L"rb"); if (f == nullptr) { - wprintf(L"Couldn't open command line file.\n"); return fail_cl; } @@ -535,7 +505,6 @@ char **App::get_command_line(unsigned int *out_argc) { int argc = READ_LE_4(len); for (int i = 0; i < argc; i++) { - r = fread(len, sizeof(uint8_t), 4, f); if (r < 4) { @@ -557,7 +526,6 @@ char **App::get_command_line(unsigned int *out_argc) { arg[strlen] = '\0'; if (r == strlen) { - int warg_size = MultiByteToWideChar(CP_UTF8, 0, arg, -1, nullptr, 0); wchar_t *warg = new wchar_t[warg_size]; @@ -566,7 +534,6 @@ char **App::get_command_line(unsigned int *out_argc) { cl.Append(ref new Platform::String(warg, warg_size)); } else { - delete[] arg; fclose(f); wprintf(L"Error reading command.\n"); @@ -582,7 +549,6 @@ char **App::get_command_line(unsigned int *out_argc) { char **ret = new char *[cl.Size + 1]; for (int i = 0; i < cl.Size; i++) { - int arg_size = WideCharToMultiByte(CP_UTF8, 0, cl.GetAt(i)->Data(), -1, nullptr, 0, nullptr, nullptr); char *arg = new char[arg_size]; diff --git a/platform/uwp/app.h b/platform/uwp/app.h index b7265ad086..5cffe378b1 100644 --- a/platform/uwp/app.h +++ b/platform/uwp/app.h @@ -45,7 +45,7 @@ namespace GodotUWP ref class App sealed : public Windows::ApplicationModel::Core::IFrameworkView { public: - App(); + App() {} // IFrameworkView Methods. virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView); @@ -92,14 +92,14 @@ namespace GodotUWP char** get_command_line(unsigned int* out_argc); - bool mWindowClosed; - bool mWindowVisible; - GLsizei mWindowWidth; - GLsizei mWindowHeight; + bool mWindowClosed = false; + bool mWindowVisible = true; + GLsizei mWindowWidth = 0; + GLsizei mWindowHeight = 0; - EGLDisplay mEglDisplay; - EGLContext mEglContext; - EGLSurface mEglSurface; + EGLDisplay mEglDisplay = EGL_NO_DISPLAY; + EGLContext mEglContext = EGL_NO_CONTEXT; + EGLSurface mEglSurface = EGL_NO_SURFACE; CoreWindow^ window; OS_UWP* os; diff --git a/platform/uwp/context_egl_uwp.cpp b/platform/uwp/context_egl_uwp.cpp index bc8ca2e36c..2da6c5897a 100644 --- a/platform/uwp/context_egl_uwp.cpp +++ b/platform/uwp/context_egl_uwp.cpp @@ -35,27 +35,22 @@ using Platform::Exception; void ContextEGL_UWP::release_current() { - eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext); }; void ContextEGL_UWP::make_current() { - eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); }; int ContextEGL_UWP::get_window_width() { - return width; }; int ContextEGL_UWP::get_window_height() { - return height; }; void ContextEGL_UWP::reset() { - cleanup(); window = CoreWindow::GetForCurrentThread(); @@ -63,7 +58,6 @@ void ContextEGL_UWP::reset() { }; void ContextEGL_UWP::swap_buffers() { - if (eglSwapBuffers(mEglDisplay, mEglSurface) != EGL_TRUE) { cleanup(); @@ -75,7 +69,6 @@ void ContextEGL_UWP::swap_buffers() { }; Error ContextEGL_UWP::initialize() { - EGLint configAttribList[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, @@ -115,7 +108,6 @@ Error ContextEGL_UWP::initialize() { } try { - const EGLint displayAttributes[] = { /*EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, 9, @@ -191,7 +183,6 @@ Error ContextEGL_UWP::initialize() { }; void ContextEGL_UWP::cleanup() { - if (mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE) { eglDestroySurface(mEglDisplay, mEglSurface); mEglSurface = EGL_NO_SURFACE; @@ -216,6 +207,5 @@ ContextEGL_UWP::ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver) : window(p_window) {} ContextEGL_UWP::~ContextEGL_UWP() { - cleanup(); }; diff --git a/platform/uwp/context_egl_uwp.h b/platform/uwp/context_egl_uwp.h index fa61cf50c6..5e7dc1802d 100644 --- a/platform/uwp/context_egl_uwp.h +++ b/platform/uwp/context_egl_uwp.h @@ -35,13 +35,12 @@ #include <EGL/egl.h> -#include "core/error_list.h" +#include "core/error/error_list.h" #include "core/os/os.h" using namespace Windows::UI::Core; class ContextEGL_UWP { - public: enum Driver { GLES_2_0, diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index 669bfe6814..2af7803749 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -65,12 +65,14 @@ def configure(env): env.Append(CCFLAGS=["/MD"]) env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) + env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) elif env["target"] == "debug": env.Append(CCFLAGS=["/Zi"]) env.Append(CCFLAGS=["/MDd"]) - env.Append(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) + env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) env.Append(LINKFLAGS=["/DEBUG"]) ## Compiler configuration @@ -78,6 +80,9 @@ def configure(env): env["ENV"] = os.environ vc_base_path = os.environ["VCTOOLSINSTALLDIR"] if "VCTOOLSINSTALLDIR" in os.environ else os.environ["VCINSTALLDIR"] + # Force to use Unicode encoding + env.AppendUnique(CCFLAGS=["/utf-8"]) + # ANGLE angle_root = os.getenv("ANGLE_SRC_PATH") env.Prepend(CPPPATH=[angle_root + "/include"]) @@ -120,7 +125,9 @@ def configure(env): print("Compiled program architecture will be a x86 executable. (forcing bits=32).") else: print( - "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup." + "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings" + " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture" + " this build is compiled for. You should check your settings/compilation setup." ) env["bits"] = "32" @@ -160,7 +167,10 @@ def configure(env): env.Append(CPPFLAGS=["/AI", vc_base_path + "lib/x86/store/references"]) env.Append( - CCFLAGS='/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX- /Zc:forScope /Gd /EHsc /nologo'.split() + CCFLAGS=( + '/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX-' + " /Zc:forScope /Gd /EHsc /nologo".split() + ) ) env.Append(CPPDEFINES=["_UNICODE", "UNICODE", ("WINAPI_FAMILY", "WINAPI_FAMILY_APP")]) env.Append(CXXFLAGS=["/ZW"]) diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 06bf738dc1..e0c03df48e 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -29,14 +29,15 @@ /*************************************************************************/ #include "export.h" -#include "core/bind/core_bind.h" + +#include "core/config/project_settings.h" +#include "core/core_bind.h" #include "core/crypto/crypto_core.h" #include "core/io/marshalls.h" #include "core/io/zip_io.h" -#include "core/object.h" +#include "core/object/class_db.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" -#include "core/project_settings.h" #include "core/version.h" #include "editor/editor_export.h" #include "editor/editor_node.h" @@ -83,7 +84,6 @@ static const char *uwp_device_capabilities[] = { }; class AppxPackager { - enum { FILE_HEADER_MAGIC = 0x04034b50, DATA_DESCRIPTOR_MAGIC = 0x08074b50, @@ -107,29 +107,21 @@ class AppxPackager { }; struct BlockHash { - String base64_hash; size_t compressed_size; }; struct FileMeta { - String name; - int lfh_size; - bool compressed; - size_t compressed_size; - size_t uncompressed_size; + int lfh_size = 0; + bool compressed = false; + size_t compressed_size = 0; + size_t uncompressed_size = 0; Vector<BlockHash> hashes; - uLong file_crc32; - ZPOS64_T zip_offset; - - FileMeta() : - lfh_size(0), - compressed(false), - compressed_size(0), - uncompressed_size(0), - file_crc32(0), - zip_offset(0) {} + uLong file_crc32 = 0; + ZPOS64_T zip_offset = 0; + + FileMeta() {} }; String progress_task; @@ -195,7 +187,6 @@ public: /////////////////////////////////////////////////////////////////////////// String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len) { - unsigned char hash[32]; char base64[45]; @@ -208,24 +199,22 @@ String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len) } void AppxPackager::make_block_map(const String &p_path) { - FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE); tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"); tmp_file->store_string("<BlockMap xmlns=\"http://schemas.microsoft.com/appx/2010/blockmap\" HashMethod=\"http://www.w3.org/2001/04/xmlenc#sha256\">"); for (int i = 0; i < file_metadata.size(); i++) { - FileMeta file = file_metadata[i]; tmp_file->store_string( "<File Name=\"" + file.name.replace("/", "\\") + "\" Size=\"" + itos(file.uncompressed_size) + "\" LfhSize=\"" + itos(file.lfh_size) + "\">"); for (int j = 0; j < file.hashes.size(); j++) { - tmp_file->store_string("<Block Hash=\"" + file.hashes[j].base64_hash + "\" "); - if (file.compressed) + if (file.compressed) { tmp_file->store_string("Size=\"" + itos(file.hashes[j].compressed_size) + "\" "); + } tmp_file->store_string("/>"); } @@ -239,21 +228,20 @@ void AppxPackager::make_block_map(const String &p_path) { } String AppxPackager::content_type(String p_extension) { - - if (p_extension == "png") + if (p_extension == "png") { return "image/png"; - else if (p_extension == "jpg") + } else if (p_extension == "jpg") { return "image/jpg"; - else if (p_extension == "xml") + } else if (p_extension == "xml") { return "application/xml"; - else if (p_extension == "exe" || p_extension == "dll") + } else if (p_extension == "exe" || p_extension == "dll") { return "application/x-msdownload"; - else + } else { return "application/octet-stream"; + } } void AppxPackager::make_content_types(const String &p_path) { - FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE); tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); @@ -262,10 +250,11 @@ void AppxPackager::make_content_types(const String &p_path) { Map<String, String> types; for (int i = 0; i < file_metadata.size(); i++) { + String ext = file_metadata[i].name.get_extension().to_lower(); - String ext = file_metadata[i].name.get_extension(); - - if (types.has(ext)) continue; + if (types.has(ext)) { + continue; + } types[ext] = content_type(ext); @@ -288,7 +277,6 @@ void AppxPackager::make_content_types(const String &p_path) { } Vector<uint8_t> AppxPackager::make_file_header(FileMeta p_file_meta) { - Vector<uint8_t> buf; buf.resize(BASE_FILE_HEADER_SIZE + p_file_meta.name.length()); @@ -331,7 +319,6 @@ Vector<uint8_t> AppxPackager::make_file_header(FileMeta p_file_meta) { } void AppxPackager::store_central_dir_header(const FileMeta &p_file, bool p_do_hash) { - Vector<uint8_t> &buf = central_dir_data; int offs = buf.size(); buf.resize(buf.size() + BASE_CENTRAL_DIR_SIZE + p_file.name.length()); @@ -383,7 +370,6 @@ void AppxPackager::store_central_dir_header(const FileMeta &p_file, bool p_do_ha } Vector<uint8_t> AppxPackager::make_end_of_central_record() { - Vector<uint8_t> buf; buf.resize(ZIP64_END_OF_CENTRAL_DIR_SIZE + 12 + END_OF_CENTRAL_DIR_SIZE); // Size plus magic @@ -453,14 +439,12 @@ Vector<uint8_t> AppxPackager::make_end_of_central_record() { } void AppxPackager::init(FileAccess *p_fa) { - package = p_fa; central_dir_offset = 0; end_of_central_dir_offset = 0; } Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) { - if (p_file_no >= 1 && p_total_files >= 1) { if (EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files)) { return ERR_SKIP; @@ -484,7 +468,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t Vector<uint8_t> strm_out; if (p_compress) { - strm.zalloc = zipio_alloc; strm.zfree = zipio_free; strm.opaque = &strm_f; @@ -497,7 +480,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t int step = 0; while (p_len - step > 0) { - size_t block_size = (p_len - step) > BLOCK_SIZE ? (size_t)BLOCK_SIZE : (p_len - step); for (uint64_t i = 0; i < block_size; i++) { @@ -508,7 +490,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t bh.base64_hash = hash_block(strm_in.ptr(), block_size); if (p_compress) { - strm.avail_in = block_size; strm.avail_out = strm_out.size(); strm.next_in = (uint8_t *)strm_in.ptr(); @@ -524,15 +505,17 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before); int start = file_buffer.size(); file_buffer.resize(file_buffer.size() + bh.compressed_size); - for (uint64_t i = 0; i < bh.compressed_size; i++) + for (uint64_t i = 0; i < bh.compressed_size; i++) { file_buffer.write[start + i] = strm_out[i]; + } } else { bh.compressed_size = block_size; //package->store_buffer(strm_in.ptr(), block_size); int start = file_buffer.size(); file_buffer.resize(file_buffer.size() + block_size); - for (uint64_t i = 0; i < bh.compressed_size; i++) + for (uint64_t i = 0; i < bh.compressed_size; i++) { file_buffer.write[start + i] = strm_in[i]; + } } meta.hashes.push_back(bh); @@ -541,7 +524,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t } if (p_compress) { - strm.avail_in = 0; strm.avail_out = strm_out.size(); strm.next_in = (uint8_t *)strm_in.ptr(); @@ -554,14 +536,14 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before); int start = file_buffer.size(); file_buffer.resize(file_buffer.size() + (strm.total_out - total_out_before)); - for (uint64_t i = 0; i < (strm.total_out - total_out_before); i++) + for (uint64_t i = 0; i < (strm.total_out - total_out_before); i++) { file_buffer.write[start + i] = strm_out[i]; + } deflateEnd(&strm); meta.compressed_size = strm.total_out; } else { - meta.compressed_size = p_len; } @@ -584,7 +566,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t } void AppxPackager::finish() { - // Create and add block map file EditorNode::progress_task_step("export", "Creating block map...", 4); @@ -651,7 +632,6 @@ AppxPackager::~AppxPackager() {} //////////////////////////////////////////////////////////////////// class EditorExportPlatformUWP : public EditorExportPlatform { - GDCLASS(EditorExportPlatformUWP, EditorExportPlatform); Ref<ImageTexture> logo; @@ -663,9 +643,12 @@ class EditorExportPlatformUWP : public EditorExportPlatform { }; bool _valid_resource_name(const String &p_name) const { - - if (p_name.empty()) return false; - if (p_name.ends_with(".")) return false; + if (p_name.empty()) { + return false; + } + if (p_name.ends_with(".")) { + return false; + } static const char *invalid_names[] = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", @@ -675,7 +658,9 @@ class EditorExportPlatformUWP : public EditorExportPlatform { const char **t = invalid_names; while (*t) { - if (p_name == *t) return false; + if (p_name == *t) { + return false; + } t++; } @@ -683,22 +668,33 @@ class EditorExportPlatformUWP : public EditorExportPlatform { } bool _valid_guid(const String &p_guid) const { - Vector<String> parts = p_guid.split("-"); - if (parts.size() != 5) return false; - if (parts[0].length() != 8) return false; - for (int i = 1; i < 4; i++) - if (parts[i].length() != 4) return false; - if (parts[4].length() != 12) return false; + if (parts.size() != 5) { + return false; + } + if (parts[0].length() != 8) { + return false; + } + for (int i = 1; i < 4; i++) { + if (parts[i].length() != 4) { + return false; + } + } + if (parts[4].length() != 12) { + return false; + } return true; } bool _valid_bgcolor(const String &p_color) const { - - if (p_color.empty()) return true; - if (p_color.begins_with("#") && p_color.is_valid_html_color()) return true; + if (p_color.empty()) { + return true; + } + if (p_color.begins_with("#") && p_color.is_valid_html_color()) { + return true; + } // Colors from https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx static const char *valid_colors[] = { @@ -732,41 +728,26 @@ class EditorExportPlatformUWP : public EditorExportPlatform { const char **color = valid_colors; while (*color) { - if (p_color == *color) return true; + if (p_color == *color) { + return true; + } color++; } return false; } - bool _valid_image(const StreamTexture *p_image, int p_width, int p_height) const { - + bool _valid_image(const StreamTexture2D *p_image, int p_width, int p_height) const { if (!p_image) { return false; } // TODO: Add resource creation or image rescaling to enable other scales: // 1.25, 1.5, 2.0 - real_t scales[] = { 1.0 }; - bool valid_w = false; - bool valid_h = false; - - for (int i = 0; i < 1; i++) { - - int w = ceil(p_width * scales[i]); - int h = ceil(p_height * scales[i]); - - if (w == p_image->get_width()) - valid_w = true; - if (h == p_image->get_height()) - valid_h = true; - } - - return valid_w && valid_h; + return p_width == p_image->get_width() && p_height == p_image->get_height(); } Vector<uint8_t> _fix_manifest(const Ref<EditorExportPreset> &p_preset, const Vector<uint8_t> &p_template, bool p_give_internet) const { - String result = String::utf8((const char *)p_template.ptr(), p_template.size()); result = result.replace("$godot_version$", VERSION_FULL_NAME); @@ -867,43 +848,44 @@ class EditorExportPlatformUWP : public EditorExportPlatform { Vector<uint8_t> r_ret; r_ret.resize(result.length()); - for (int i = 0; i < result.length(); i++) + for (int i = 0; i < result.length(); i++) { r_ret.write[i] = result.utf8().get(i); + } return r_ret; } Vector<uint8_t> _get_image_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) { - Vector<uint8_t> data; - StreamTexture *image = nullptr; + StreamTexture2D *image = nullptr; if (p_path.find("StoreLogo") != -1) { - image = p_preset->get("images/store_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/store_logo"))); + image = p_preset->get("images/store_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/store_logo"))); } else if (p_path.find("Square44x44Logo") != -1) { - image = p_preset->get("images/square44x44_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/square44x44_logo"))); + image = p_preset->get("images/square44x44_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square44x44_logo"))); } else if (p_path.find("Square71x71Logo") != -1) { - image = p_preset->get("images/square71x71_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/square71x71_logo"))); + image = p_preset->get("images/square71x71_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square71x71_logo"))); } else if (p_path.find("Square150x150Logo") != -1) { - image = p_preset->get("images/square150x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/square150x150_logo"))); + image = p_preset->get("images/square150x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square150x150_logo"))); } else if (p_path.find("Square310x310Logo") != -1) { - image = p_preset->get("images/square310x310_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/square310x310_logo"))); + image = p_preset->get("images/square310x310_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square310x310_logo"))); } else if (p_path.find("Wide310x150Logo") != -1) { - image = p_preset->get("images/wide310x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/wide310x150_logo"))); + image = p_preset->get("images/wide310x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/wide310x150_logo"))); } else if (p_path.find("SplashScreen") != -1) { - image = p_preset->get("images/splash_screen").is_zero() ? nullptr : Object::cast_to<StreamTexture>(((Object *)p_preset->get("images/splash_screen"))); + image = p_preset->get("images/splash_screen").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/splash_screen"))); } else { ERR_PRINT("Unable to load logo"); } - if (!image) return data; + if (!image) { + return data; + } String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("uwp_tmp_logo.png"); Error err = image->get_data()->save_png(tmp_path); if (err != OK) { - String err_string = "Couldn't save temp logo file."; EditorNode::add_io_error(err_string); @@ -913,7 +895,6 @@ class EditorExportPlatformUWP : public EditorExportPlatform { FileAccess *f = FileAccess::open(tmp_path, FileAccess::READ, &err); if (err != OK) { - String err_string = "Couldn't open temp logo file."; // Cleanup generated file. DirAccess::remove_file_or_error(tmp_path); @@ -932,17 +913,16 @@ class EditorExportPlatformUWP : public EditorExportPlatform { } static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) { - /* TODO: This was copied verbatim from Android export. It should be - * refactored to the parent class and also be used for .zip export. - */ + * refactored to the parent class and also be used for .zip export. + */ /* - * By not compressing files with little or not benefit in doing so, - * a performance gain is expected at runtime. Moreover, if the APK is - * zip-aligned, assets stored as they are can be efficiently read by - * Android by memory-mapping them. - */ + * By not compressing files with little or not benefit in doing so, + * a performance gain is expected at runtime. Moreover, if the APK is + * zip-aligned, assets stored as they are can be efficiently read by + * Android by memory-mapping them. + */ // -- Unconditional uncompress to mimic AAPT plus some other @@ -982,8 +962,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform { return true; } - static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { - + static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { AppxPackager *packager = (AppxPackager *)p_userdata; String dst_path = p_path.replace_first("res://", "game/"); @@ -991,24 +970,24 @@ class EditorExportPlatformUWP : public EditorExportPlatform { } public: - virtual String get_name() const { - return "Windows Universal"; + virtual String get_name() const override { + return "UWP"; } - virtual String get_os_name() const { + virtual String get_os_name() const override { return "UWP"; } - virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { List<String> list; list.push_back("appx"); return list; } - virtual Ref<Texture2D> get_logo() const { + virtual Ref<Texture2D> get_logo() const override { return logo; } - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override { r_features->push_back("s3tc"); r_features->push_back("etc"); switch ((int)p_preset->get("architecture/target")) { @@ -1024,7 +1003,7 @@ public: } } - virtual void get_export_options(List<ExportOption> *r_options) { + virtual void get_export_options(List<ExportOption> *r_options) override { r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "architecture/target", PROPERTY_HINT_ENUM, "arm,x86,x64"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); @@ -1054,13 +1033,13 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait_flipped"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "images/background_color"), "transparent")); - r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/store_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); - r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square44x44_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); - r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square71x71_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); - r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square150x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); - r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square310x310_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); - r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/wide310x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); - r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/splash_screen", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/store_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square44x44_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square71x71_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square150x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square310x310_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/wide310x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/splash_screen", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant())); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_square150x150"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_wide310x150"), false)); @@ -1089,8 +1068,7 @@ public: } } - virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override { String err; bool valid = false; @@ -1161,37 +1139,37 @@ public: err += TTR("Invalid background color.") + "\n"; } - if (!p_preset->get("images/store_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/store_logo"))), 50, 50)) { + if (!p_preset->get("images/store_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/store_logo"))), 50, 50)) { valid = false; err += TTR("Invalid Store Logo image dimensions (should be 50x50).") + "\n"; } - if (!p_preset->get("images/square44x44_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square44x44_logo"))), 44, 44)) { + if (!p_preset->get("images/square44x44_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square44x44_logo"))), 44, 44)) { valid = false; err += TTR("Invalid square 44x44 logo image dimensions (should be 44x44).") + "\n"; } - if (!p_preset->get("images/square71x71_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square71x71_logo"))), 71, 71)) { + if (!p_preset->get("images/square71x71_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square71x71_logo"))), 71, 71)) { valid = false; err += TTR("Invalid square 71x71 logo image dimensions (should be 71x71).") + "\n"; } - if (!p_preset->get("images/square150x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square150x150_logo"))), 150, 150)) { + if (!p_preset->get("images/square150x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square150x150_logo"))), 150, 150)) { valid = false; err += TTR("Invalid square 150x150 logo image dimensions (should be 150x150).") + "\n"; } - if (!p_preset->get("images/square310x310_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square310x310_logo"))), 310, 310)) { + if (!p_preset->get("images/square310x310_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square310x310_logo"))), 310, 310)) { valid = false; err += TTR("Invalid square 310x310 logo image dimensions (should be 310x310).") + "\n"; } - if (!p_preset->get("images/wide310x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/wide310x150_logo"))), 310, 150)) { + if (!p_preset->get("images/wide310x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/wide310x150_logo"))), 310, 150)) { valid = false; err += TTR("Invalid wide 310x150 logo image dimensions (should be 310x150).") + "\n"; } - if (!p_preset->get("images/splash_screen").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/splash_screen"))), 620, 300)) { + if (!p_preset->get("images/splash_screen").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/splash_screen"))), 620, 300)) { valid = false; err += TTR("Invalid splash screen image dimensions (should be 620x300).") + "\n"; } @@ -1200,16 +1178,16 @@ public: return valid; } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { - + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override { String src_appx; - EditorProgress ep("export", "Exporting for Windows Universal", 7, true); + EditorProgress ep("export", "Exporting for UWP", 7, true); - if (p_debug) + if (p_debug) { src_appx = p_preset->get("custom_template/debug"); - else + } else { src_appx = p_preset->get("custom_template/release"); + } src_appx = src_appx.strip_edges(); @@ -1261,7 +1239,6 @@ public: unzFile pkg = unzOpen2(src_appx.utf8().get_data(), &io); if (!pkg) { - EditorNode::add_io_error("Could not find template appx to export:\n" + src_appx); return ERR_FILE_NOT_FOUND; } @@ -1279,7 +1256,6 @@ public: int template_file_no = 1; while (ret == UNZ_OK) { - // get file name unz_file_info info; char fname[16834]; @@ -1297,11 +1273,12 @@ public: bool do_read = true; if (path.begins_with("Assets/")) { - path = path.replace(".scale-100", ""); data = _get_image_data(p_preset, path); - if (data.size() > 0) do_read = false; + if (data.size() > 0) { + do_read = false; + } } //read @@ -1313,7 +1290,6 @@ public: } if (path == "AppxManifest.xml") { - data = _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG)); } @@ -1356,7 +1332,6 @@ public: encode_uint32(cl.size(), clf.ptrw()); for (int i = 0; i < cl.size(); i++) { - CharString txt = cl[i].utf8(); int base = clf.size(); clf.resize(base + 4 + txt.length()); @@ -1444,13 +1419,12 @@ public: return OK; } - virtual void get_platform_features(List<String> *r_features) { - + virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("pc"); r_features->push_back("UWP"); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { } EditorExportPlatformUWP() { @@ -1461,7 +1435,6 @@ public: }; void register_uwp_exporter() { - #ifdef WINDOWS_ENABLED EDITOR_DEF("export/uwp/signtool", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/uwp/signtool", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); diff --git a/platform/uwp/joypad_uwp.cpp b/platform/uwp/joypad_uwp.cpp index 90df6fe5d7..4fdfde9673 100644 --- a/platform/uwp/joypad_uwp.cpp +++ b/platform/uwp/joypad_uwp.cpp @@ -35,7 +35,6 @@ using namespace Windows::Gaming::Input; using namespace Windows::Foundation; void JoypadUWP::register_events() { - Gamepad::GamepadAdded += ref new EventHandler<Gamepad ^>(this, &JoypadUWP::OnGamepadAdded); Gamepad::GamepadRemoved += @@ -43,32 +42,28 @@ void JoypadUWP::register_events() { } void JoypadUWP::process_controllers() { - for (int i = 0; i < MAX_CONTROLLERS; i++) { - ControllerDevice &joy = controllers[i]; - if (!joy.connected) break; + if (!joy.connected) + break; switch (joy.type) { - case ControllerType::GAMEPAD_CONTROLLER: { - GamepadReading reading = ((Gamepad ^) joy.controller_reference)->GetCurrentReading(); int button_mask = (int)GamepadButtons::Menu; for (int j = 0; j < 14; j++) { - input->joy_button(joy.id, j, (int)reading.Buttons & button_mask); button_mask *= 2; } - input->joy_axis(joy.id, JOY_AXIS_0, axis_correct(reading.LeftThumbstickX)); - input->joy_axis(joy.id, JOY_AXIS_1, axis_correct(reading.LeftThumbstickY, true)); - input->joy_axis(joy.id, JOY_AXIS_2, axis_correct(reading.RightThumbstickX)); - input->joy_axis(joy.id, JOY_AXIS_3, axis_correct(reading.RightThumbstickY, true)); - input->joy_axis(joy.id, JOY_AXIS_4, axis_correct(reading.LeftTrigger, false, true)); - input->joy_axis(joy.id, JOY_AXIS_5, axis_correct(reading.RightTrigger, false, true)); + input->joy_axis(joy.id, JOY_AXIS_LEFT_X, axis_correct(reading.LeftThumbstickX)); + input->joy_axis(joy.id, JOY_AXIS_LEFT_Y, axis_correct(reading.LeftThumbstickY, true)); + input->joy_axis(joy.id, JOY_AXIS_RIGHT_X, axis_correct(reading.RightThumbstickX)); + input->joy_axis(joy.id, JOY_AXIS_RIGHT_Y, axis_correct(reading.RightThumbstickY, true)); + input->joy_axis(joy.id, JOY_AXIS_TRIGGER_LEFT, axis_correct(reading.LeftTrigger, false, true)); + input->joy_axis(joy.id, JOY_AXIS_TRIGGER_RIGHT, axis_correct(reading.RightTrigger, false, true)); uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id); if (timestamp > joy.ff_timestamp) { @@ -92,24 +87,20 @@ void JoypadUWP::process_controllers() { } JoypadUWP::JoypadUWP() { - for (int i = 0; i < MAX_CONTROLLERS; i++) controllers[i].id = i; } JoypadUWP::JoypadUWP(InputDefault *p_input) { - input = p_input; JoypadUWP(); } void JoypadUWP::OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value) { - short idx = -1; for (int i = 0; i < MAX_CONTROLLERS; i++) { - if (!controllers[i].connected) { idx = i; break; @@ -127,11 +118,9 @@ void JoypadUWP::OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input } void JoypadUWP::OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value) { - short idx = -1; for (int i = 0; i < MAX_CONTROLLERS; i++) { - if (controllers[i].controller_reference == value) { idx = i; break; @@ -146,7 +135,6 @@ void JoypadUWP::OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Inp } InputDefault::JoyAxis JoypadUWP::axis_correct(double p_val, bool p_negate, bool p_trigger) const { - InputDefault::JoyAxis jx; jx.min = p_trigger ? 0 : -1; diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h index 054b67ddc8..13f246a438 100644 --- a/platform/uwp/joypad_uwp.h +++ b/platform/uwp/joypad_uwp.h @@ -31,10 +31,9 @@ #ifndef JOYPAD_UWP_H #define JOYPAD_UWP_H -#include "core/input/input_filter.h" +#include "core/input/input.h" ref class JoypadUWP sealed { - /** clang-format breaks this, it does not understand this token. */ /* clang-format off */ internal: @@ -57,7 +56,6 @@ private: }; struct ControllerDevice { - Windows::Gaming::Input::IGameController ^ controller_reference; int id; diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index f5e989b370..79508055e5 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -33,9 +33,8 @@ #include "os_uwp.h" +#include "core/config/project_settings.h" #include "core/io/marshalls.h" -#include "core/project_settings.h" -#include "drivers/gles2/rasterizer_gles2.h" #include "drivers/unix/ip_unix.h" #include "drivers/windows/dir_access_windows.h" #include "drivers/windows/file_access_windows.h" @@ -82,7 +81,6 @@ int OS_UWP::get_current_video_driver() const { } void OS_UWP::set_window_size(const Size2 p_size) { - Windows::Foundation::Size new_size; new_size.Width = p_size.width; new_size.Height = p_size.height; @@ -90,14 +88,12 @@ void OS_UWP::set_window_size(const Size2 p_size) { ApplicationView ^ view = ApplicationView::GetForCurrentView(); if (view->TryResizeView(new_size)) { - video_mode.width = p_size.width; video_mode.height = p_size.height; } } void OS_UWP::set_window_fullscreen(bool p_enabled) { - ApplicationView ^ view = ApplicationView::GetForCurrentView(); video_mode.fullscreen = view->IsFullScreenMode; @@ -106,24 +102,21 @@ void OS_UWP::set_window_fullscreen(bool p_enabled) { return; if (p_enabled) { - video_mode.fullscreen = view->TryEnterFullScreenMode(); } else { - view->ExitFullScreenMode(); video_mode.fullscreen = false; } } bool OS_UWP::is_window_fullscreen() const { - return ApplicationView::GetForCurrentView()->IsFullScreenMode; } void OS_UWP::set_keep_screen_on(bool p_enabled) { - - if (is_keep_screen_on() == p_enabled) return; + if (is_keep_screen_on() == p_enabled) + return; if (p_enabled) display_request->RequestActive(); @@ -134,7 +127,6 @@ void OS_UWP::set_keep_screen_on(bool p_enabled) { } void OS_UWP::initialize_core() { - last_button_state = 0; //RedirectIOToConsole(); @@ -165,7 +157,6 @@ void OS_UWP::initialize_core() { } bool OS_UWP::can_draw() const { - return !minimized; }; @@ -174,12 +165,10 @@ void OS_UWP::set_window(Windows::UI::Core::CoreWindow ^ p_window) { } void OS_UWP::screen_size_changed() { - gl_context->reset(); }; Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - main_loop = nullptr; outside = true; @@ -230,11 +219,9 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a if (p_desired.fullscreen != view->IsFullScreenMode) { if (p_desired.fullscreen) { - vm.fullscreen = view->TryEnterFullScreenMode(); } else { - view->ExitFullScreenMode(); vm.fullscreen = false; } @@ -247,7 +234,6 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a view->PreferredLaunchViewSize = desired; if (view->TryResizeView(desired)) { - vm.width = view->VisibleBounds.Width; vm.height = view->VisibleBounds.Height; } @@ -308,16 +294,14 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a } void OS_UWP::set_clipboard(const String &p_text) { - DataPackage ^ clip = ref new DataPackage(); clip->RequestedOperation = DataPackageOperation::Copy; - clip->SetText(ref new Platform::String((const wchar_t *)p_text.c_str())); + clip->SetText(ref new Platform::String((LPCWSTR)(p_text.utf16().get_data()))); Clipboard::SetContent(clip); }; String OS_UWP::get_clipboard() const { - if (managed_object->clipboard != nullptr) return managed_object->clipboard->Data(); else @@ -325,25 +309,21 @@ String OS_UWP::get_clipboard() const { }; void OS_UWP::input_event(const Ref<InputEvent> &p_event) { - input->parse_input_event(p_event); }; void OS_UWP::delete_main_loop() { - if (main_loop) memdelete(main_loop); main_loop = nullptr; } void OS_UWP::set_main_loop(MainLoop *p_main_loop) { - input->set_main_loop(p_main_loop); main_loop = p_main_loop; } void OS_UWP::finalize() { - if (main_loop) memdelete(main_loop); @@ -362,14 +342,12 @@ void OS_UWP::finalize() { } void OS_UWP::finalize_core() { - NetSocketPosix::cleanup(); } void OS_UWP::alert(const String &p_alert, const String &p_title) { - - Platform::String ^ alert = ref new Platform::String(p_alert.c_str()); - Platform::String ^ title = ref new Platform::String(p_title.c_str()); + Platform::String ^ alert = ref new Platform::String((LPCWSTR)(p_alert.utf16().get_data())); + Platform::String ^ title = ref new Platform::String((LPCWSTR)(p_title.utf16().get_data())); MessageDialog ^ msg = ref new MessageDialog(alert, title); @@ -383,21 +361,17 @@ void OS_UWP::alert(const String &p_alert, const String &p_title) { } void OS_UWP::ManagedType::alert_close(IUICommand ^ command) { - alert_close_handle = false; } void OS_UWP::ManagedType::on_clipboard_changed(Platform::Object ^ sender, Platform::Object ^ ev) { - update_clipboard(); } void OS_UWP::ManagedType::update_clipboard() { - DataPackageView ^ data = Clipboard::GetContent(); if (data->Contains(StandardDataFormats::Text)) { - create_task(data->GetTextAsync()).then([this](Platform::String ^ clipboard_content) { this->clipboard = clipboard_content; }); @@ -405,7 +379,6 @@ void OS_UWP::ManagedType::update_clipboard() { } void OS_UWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sender, AccelerometerReadingChangedEventArgs ^ args) { - AccelerometerReading ^ reading = args->Reading; os->input->set_accelerometer(Vector3( @@ -415,7 +388,6 @@ void OS_UWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sende } void OS_UWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, MagnetometerReadingChangedEventArgs ^ args) { - MagnetometerReading ^ reading = args->Reading; os->input->set_magnetometer(Vector3( @@ -425,7 +397,6 @@ void OS_UWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, } void OS_UWP::ManagedType::on_gyroscope_reading_changed(Gyrometer ^ sender, GyrometerReadingChangedEventArgs ^ args) { - GyrometerReading ^ reading = args->Reading; os->input->set_magnetometer(Vector3( @@ -435,22 +406,17 @@ void OS_UWP::ManagedType::on_gyroscope_reading_changed(Gyrometer ^ sender, Gyrom } void OS_UWP::set_mouse_mode(MouseMode p_mode) { - if (p_mode == MouseMode::MOUSE_MODE_CAPTURED) { - CoreWindow::GetForCurrentThread()->SetPointerCapture(); } else { - CoreWindow::GetForCurrentThread()->ReleasePointerCapture(); } if (p_mode == MouseMode::MOUSE_MODE_CAPTURED || p_mode == MouseMode::MOUSE_MODE_HIDDEN) { - CoreWindow::GetForCurrentThread()->PointerCursor = nullptr; } else { - CoreWindow::GetForCurrentThread()->PointerCursor = ref new CoreCursor(CoreCursorType::Arrow, 0); } @@ -460,17 +426,14 @@ void OS_UWP::set_mouse_mode(MouseMode p_mode) { } OS_UWP::MouseMode OS_UWP::get_mouse_mode() const { - return mouse_mode; } Point2 OS_UWP::get_mouse_position() const { - return Point2(old_x, old_y); } int OS_UWP::get_mouse_button_state() const { - return last_button_state; } @@ -478,23 +441,21 @@ void OS_UWP::set_window_title(const String &p_title) { } void OS_UWP::set_video_mode(const VideoMode &p_video_mode, int p_screen) { - video_mode = p_video_mode; } -OS::VideoMode OS_UWP::get_video_mode(int p_screen) const { +OS::VideoMode OS_UWP::get_video_mode(int p_screen) const { return video_mode; } + void OS_UWP::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { } String OS_UWP::get_name() const { - return "UWP"; } OS::Date OS_UWP::get_date(bool utc) const { - SYSTEMTIME systemtime; if (utc) GetSystemTime(&systemtime); @@ -509,8 +470,8 @@ OS::Date OS_UWP::get_date(bool utc) const { date.dst = false; return date; } -OS::Time OS_UWP::get_time(bool utc) const { +OS::Time OS_UWP::get_time(bool utc) const { SYSTEMTIME systemtime; if (utc) GetSystemTime(&systemtime); @@ -544,7 +505,6 @@ OS::TimeZoneInfo OS_UWP::get_time_zone_info() const { } uint64_t OS_UWP::get_unix_time() const { - FILETIME ft; SYSTEMTIME st; GetSystemTime(&st); @@ -566,20 +526,36 @@ uint64_t OS_UWP::get_unix_time() const { }; void OS_UWP::delay_usec(uint32_t p_usec) const { - int msec = p_usec < 1000 ? 1 : p_usec / 1000; // no Sleep() WaitForSingleObjectEx(GetCurrentThread(), msec, false); } -uint64_t OS_UWP::get_ticks_usec() const { +uint64_t OS_UWP::get_ticks_usec() const { uint64_t ticks; - uint64_t time; + // This is the number of clock ticks since start QueryPerformanceCounter((LARGE_INTEGER *)&ticks); + // Divide by frequency to get the time in seconds - time = ticks * 1000000L / ticks_per_second; + // original calculation shown below is subject to overflow + // with high ticks_per_second and a number of days since the last reboot. + // time = ticks * 1000000L / ticks_per_second; + + // we can prevent this by either using 128 bit math + // or separating into a calculation for seconds, and the fraction + uint64_t seconds = ticks / ticks_per_second; + + // compiler will optimize these two into one divide + uint64_t leftover = ticks % ticks_per_second; + + // remainder + uint64_t time = (leftover * 1000000L) / ticks_per_second; + + // seconds + time += seconds * 1000000L; + // Subtract the time at game start to get // the time since the game started time -= ticks_start; @@ -587,15 +563,12 @@ uint64_t OS_UWP::get_ticks_usec() const { } void OS_UWP::process_events() { - joypad->process_controllers(); process_key_events(); } void OS_UWP::process_key_events() { - for (int i = 0; i < key_event_pos; i++) { - KeyEvent &kev = key_event_buffer[i]; Ref<InputEventKey> key_event; @@ -618,7 +591,6 @@ void OS_UWP::queue_key_event(KeyEvent &p_event) { // This merges Char events with the previous Key event, so // the unicode can be retrieved without sending duplicate events. if (p_event.type == KeyEvent::MessageType::CHAR_EVENT_MESSAGE && key_event_pos > 0) { - KeyEvent &old = key_event_buffer[key_event_pos - 1]; ERR_FAIL_COND(old.type != KeyEvent::MessageType::KEY_EVENT_MESSAGE); @@ -632,7 +604,6 @@ void OS_UWP::queue_key_event(KeyEvent &p_event) { } void OS_UWP::set_cursor_shape(CursorShape p_shape) { - ERR_FAIL_INDEX(p_shape, CURSOR_MAX); if (cursor_shape == p_shape) @@ -664,7 +635,6 @@ void OS_UWP::set_cursor_shape(CursorShape p_shape) { } OS::CursorShape OS_UWP::get_cursor_shape() const { - return cursor_shape; } @@ -673,22 +643,18 @@ void OS_UWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c } Error OS_UWP::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { - return FAILED; }; Error OS_UWP::kill(const ProcessID &p_pid) { - return FAILED; }; Error OS_UWP::set_cwd(const String &p_cwd) { - return FAILED; } String OS_UWP::get_executable_path() const { - return ""; } @@ -696,22 +662,18 @@ void OS_UWP::set_icon(const Ref<Image> &p_icon) { } bool OS_UWP::has_environment(const String &p_var) const { - return false; }; String OS_UWP::get_environment(const String &p_var) const { - return ""; }; bool OS_UWP::set_environment(const String &p_var, const String &p_value) const { - return false; } String OS_UWP::get_stdin_string(bool p_block) { - return String(); } @@ -719,12 +681,10 @@ void OS_UWP::move_window_to_foreground() { } Error OS_UWP::shell_open(String p_uri) { - return FAILED; } String OS_UWP::get_locale() const { - #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP // this should work on phone 8.1, but it doesn't return "en"; #else @@ -734,45 +694,37 @@ String OS_UWP::get_locale() const { } void OS_UWP::release_rendering_thread() { - gl_context->release_current(); } void OS_UWP::make_rendering_thread() { - gl_context->make_current(); } void OS_UWP::swap_buffers() { - gl_context->swap_buffers(); } bool OS_UWP::has_touchscreen_ui_hint() const { - TouchCapabilities ^ tc = ref new TouchCapabilities(); return tc->TouchPresent != 0 || UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch; } bool OS_UWP::has_virtual_keyboard() const { - return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch; } -void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length) { - +void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { InputPane ^ pane = InputPane::GetForCurrentView(); pane->TryShow(); } void OS_UWP::hide_virtual_keyboard() { - InputPane ^ pane = InputPane::GetForCurrentView(); pane->TryHide(); } static String format_error_message(DWORD id) { - LPWSTR messageBuffer = nullptr; size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr); @@ -785,9 +737,8 @@ static String format_error_message(DWORD id) { } Error OS_UWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { - String full_path = "game/" + p_path; - p_library_handle = (void *)LoadPackagedLibrary(full_path.c_str(), 0); + p_library_handle = (void *)LoadPackagedLibrary((LPCWSTR)(full_path.utf16().get_data()), 0); ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + full_path + ", error: " + format_error_message(GetLastError()) + "."); return OK; } @@ -812,7 +763,6 @@ Error OS_UWP::get_dynamic_library_symbol_handle(void *p_library_handle, const St } void OS_UWP::run() { - if (!main_loop) return; @@ -824,9 +774,9 @@ void OS_UWP::run() { uint64_t frame = 0; while (!force_quit) { - CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); - if (managed_object->alert_close_handle) continue; + if (managed_object->alert_close_handle) + continue; process_events(); // get rid of pending events if (Main::iteration()) break; @@ -836,12 +786,10 @@ void OS_UWP::run() { } MainLoop *OS_UWP::get_main_loop() const { - return main_loop; } String OS_UWP::get_user_data_dir() const { - Windows::Storage::StorageFolder ^ data_folder = Windows::Storage::ApplicationData::Current->LocalFolder; return String(data_folder->Path->Data()).replace("\\", "/"); @@ -852,7 +800,6 @@ bool OS_UWP::_check_internal_feature_support(const String &p_feature) { } OS_UWP::OS_UWP() { - key_event_pos = 0; force_quit = false; alt_mem = false; diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index ad0efa1d03..88961bf143 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -32,10 +32,10 @@ #define OS_UWP_H #include "context_egl_uwp.h" -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "core/math/transform_2d.h" #include "core/os/os.h" -#include "core/ustring.h" +#include "core/string/ustring.h" #include "drivers/xaudio2/audio_driver_xaudio2.h" #include "joypad_uwp.h" #include "servers/audio_server.h" @@ -48,10 +48,8 @@ #include <windows.h> class OS_UWP : public OS { - public: struct KeyEvent { - enum MessageType { KEY_EVENT_MESSAGE, CHAR_EVENT_MESSAGE @@ -236,7 +234,7 @@ public: virtual bool has_touchscreen_ui_hint() const; virtual bool has_virtual_keyboard() const; - virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1); + virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); virtual void hide_virtual_keyboard(); virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); @@ -247,7 +245,7 @@ public: void run(); - virtual bool get_swap_ok_cancel() { return true; } + virtual bool get_swap_cancel_ok() { return true; } void input_event(const Ref<InputEvent> &p_event); diff --git a/platform/uwp/thread_uwp.cpp b/platform/uwp/thread_uwp.cpp index 9dc20a74e8..8e7bb144be 100644 --- a/platform/uwp/thread_uwp.cpp +++ b/platform/uwp/thread_uwp.cpp @@ -33,7 +33,6 @@ #include "core/os/memory.h" Thread *ThreadUWP::create_func_uwp(ThreadCreateCallback p_callback, void *p_user, const Settings &) { - ThreadUWP *thread = memnew(ThreadUWP); std::thread new_thread(p_callback, p_user); @@ -43,18 +42,15 @@ Thread *ThreadUWP::create_func_uwp(ThreadCreateCallback p_callback, void *p_user }; Thread::ID ThreadUWP::get_thread_id_func_uwp() { - return std::hash<std::thread::id>()(std::this_thread::get_id()); }; void ThreadUWP::wait_to_finish_func_uwp(Thread *p_thread) { - ThreadUWP *tp = static_cast<ThreadUWP *>(p_thread); tp->thread.join(); }; Thread::ID ThreadUWP::get_id() const { - return std::hash<std::thread::id>()(thread.get_id()); }; @@ -63,11 +59,3 @@ void ThreadUWP::make_default() { get_thread_id_func = get_thread_id_func_uwp; wait_to_finish_func = wait_to_finish_func_uwp; }; - -ThreadUWP::ThreadUWP(){ - -}; - -ThreadUWP::~ThreadUWP(){ - -}; diff --git a/platform/uwp/thread_uwp.h b/platform/uwp/thread_uwp.h index a2d367ae2f..9b2a2590a8 100644 --- a/platform/uwp/thread_uwp.h +++ b/platform/uwp/thread_uwp.h @@ -38,23 +38,22 @@ #include <thread> class ThreadUWP : public Thread { - std::thread thread; static Thread *create_func_uwp(ThreadCreateCallback p_callback, void *, const Settings &); static ID get_thread_id_func_uwp(); static void wait_to_finish_func_uwp(Thread *p_thread); - ThreadUWP(); + ThreadUWP() {} public: virtual ID get_id() const; static void make_default(); - ~ThreadUWP(); + ~ThreadUWP() {} }; -#endif +#endif // UWP_ENABLED -#endif +#endif // THREAD_UWP_H diff --git a/platform/windows/SCsub b/platform/windows/SCsub index daffe59f34..0c9aa77803 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -26,11 +26,11 @@ prog = env.add_program("#bin/godot", common_win + res_obj, PROGSUFFIX=env["PROGS # Microsoft Visual Studio Project Generation if env["vsproj"]: - env.vs_srcs = env.vs_srcs + ["platform/windows/" + res_file] - env.vs_srcs = env.vs_srcs + ["platform/windows/godot.natvis"] + env.vs_srcs += ["platform/windows/" + res_file] + env.vs_srcs += ["platform/windows/godot.natvis"] for x in common_win: - env.vs_srcs = env.vs_srcs + ["platform/windows/" + str(x)] + env.vs_srcs += ["platform/windows/" + str(x)] if not os.getenv("VCINSTALLDIR"): - if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]: + if env["debug_symbols"] == "yes" and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_windows_builders.make_debug_mingw)) diff --git a/platform/windows/context_gl_windows.cpp b/platform/windows/context_gl_windows.cpp index 5a36b5546d..1c32639a38 100644 --- a/platform/windows/context_gl_windows.cpp +++ b/platform/windows/context_gl_windows.cpp @@ -51,27 +51,22 @@ typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *); void ContextGL_Windows::release_current() { - wglMakeCurrent(hDC, nullptr); } void ContextGL_Windows::make_current() { - wglMakeCurrent(hDC, hRC); } int ContextGL_Windows::get_window_width() { - return OS::get_singleton()->get_video_mode().width; } int ContextGL_Windows::get_window_height() { - return OS::get_singleton()->get_video_mode().height; } bool ContextGL_Windows::should_vsync_via_compositor() { - if (OS::get_singleton()->is_window_fullscreen() || !OS::get_singleton()->is_vsync_via_compositor_enabled()) { return false; } @@ -88,7 +83,6 @@ bool ContextGL_Windows::should_vsync_via_compositor() { } void ContextGL_Windows::swap_buffers() { - SwapBuffers(hDC); if (use_vsync) { @@ -108,7 +102,6 @@ void ContextGL_Windows::swap_buffers() { } void ContextGL_Windows::set_use_vsync(bool p_use) { - vsync_via_compositor = p_use && should_vsync_via_compositor(); if (wglSwapIntervalEXT) { @@ -120,14 +113,12 @@ void ContextGL_Windows::set_use_vsync(bool p_use) { } bool ContextGL_Windows::is_using_vsync() const { - return use_vsync; } #define _WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 Error ContextGL_Windows::initialize() { - static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor 1, @@ -175,7 +166,6 @@ Error ContextGL_Windows::initialize() { wglMakeCurrent(hDC, hRC); if (opengl_3_context) { - int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, //we want a 3.3 context WGL_CONTEXT_MINOR_VERSION_ARB, 3, @@ -217,7 +207,6 @@ Error ContextGL_Windows::initialize() { } ContextGL_Windows::ContextGL_Windows(HWND hwnd, bool p_opengl_3_context) { - opengl_3_context = p_opengl_3_context; hWnd = hwnd; use_vsync = false; diff --git a/platform/windows/context_gl_windows.h b/platform/windows/context_gl_windows.h index 280c5a1e3c..0013177609 100644 --- a/platform/windows/context_gl_windows.h +++ b/platform/windows/context_gl_windows.h @@ -35,7 +35,7 @@ #ifndef CONTEXT_GL_WIN_H #define CONTEXT_GL_WIN_H -#include "core/error_list.h" +#include "core/error/error_list.h" #include "core/os/os.h" #include <windows.h> @@ -44,7 +44,6 @@ typedef bool(APIENTRY *PFNWGLSWAPINTERVALEXTPROC)(int interval); typedef int(APIENTRY *PFNWGLGETSWAPINTERVALEXTPROC)(void); class ContextGL_Windows { - HDC hDC; HGLRC hRC; unsigned int pixel_format; diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 1d9eba22d8..7abf451062 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -30,19 +30,21 @@ #include "crash_handler_windows.h" +#include "core/config/project_settings.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "main/main.h" #ifdef CRASH_HANDLER_EXCEPTION // Backtrace code code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app -#include <psapi.h> #include <algorithm> #include <iterator> +#include <string> #include <vector> +#include <psapi.h> + #pragma comment(lib, "psapi.lib") #pragma comment(lib, "dbghelp.lib") @@ -173,7 +175,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { msg = proj_settings->get("debug/settings/crash_handler/message"); } - fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); + fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); int n = 0; do { diff --git a/platform/windows/crash_handler_windows.h b/platform/windows/crash_handler_windows.h index adc548073c..66a4cac296 100644 --- a/platform/windows/crash_handler_windows.h +++ b/platform/windows/crash_handler_windows.h @@ -41,7 +41,6 @@ extern DWORD CrashHandlerException(EXCEPTION_POINTERS *ep); #endif class CrashHandler { - bool disabled; public: diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 9f79e92dcb..934314b2f2 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -64,7 +64,8 @@ def get_opts(): # XP support dropped after EOL due to missing API for IPv6 and other issues # Vista support dropped after EOL due to GH-10243 ("target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601"), - EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), + EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), + EnumVariable("windows_subsystem", "Windows subsystem", "default", ("default", "console", "gui")), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), ("msvc_version", "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", None), BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed. Only used on Windows.", False), @@ -128,7 +129,9 @@ def setup_msvc_manual(env): print("Compiled program architecture will be a 32 bit executable. (forcing bits=32).") else: print( - "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR." + "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings" + " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this" + " build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR." ) @@ -176,12 +179,20 @@ def configure_msvc(env, manual_msvc_config): # Build type + if env["tests"]: + env["windows_subsystem"] = "console" + elif env["windows_subsystem"] == "default": + # Default means we use console for debug, gui for release. + if "debug" in env["target"]: + env["windows_subsystem"] = "console" + else: + env["windows_subsystem"] = "gui" + if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) else: # optimize for size env.Append(CCFLAGS=["/O1"]) - env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"]) env.Append(LINKFLAGS=["/OPT:REF"]) @@ -191,24 +202,28 @@ def configure_msvc(env, manual_msvc_config): else: # optimize for size env.Append(CCFLAGS=["/O1"]) env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"]) - env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "debug": env.AppendUnique(CCFLAGS=["/Z7", "/Od", "/EHsc"]) - env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED", "D3D_DEBUG_INFO"]) - env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) + env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["/DEBUG"]) - if env["debug_symbols"] == "full" or env["debug_symbols"] == "yes": + if env["debug_symbols"] == "yes": env.AppendUnique(CCFLAGS=["/Z7"]) env.AppendUnique(LINKFLAGS=["/DEBUG"]) + if env["windows_subsystem"] == "gui": + env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) + else: + env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) + env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) + ## Compile/link flags env.AppendUnique(CCFLAGS=["/MT", "/Gd", "/GR", "/nologo"]) - if int(env["MSVC_VERSION"].split(".")[0]) >= 14: # vs2015 and later - env.AppendUnique(CCFLAGS=["/utf-8"]) + # Force to use Unicode encoding + env.AppendUnique(CCFLAGS=["/utf-8"]) env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++ if manual_msvc_config: # should be automatic if SCons found it if os.getenv("WindowsSdkDir") is not None: @@ -301,6 +316,15 @@ def configure_mingw(env): ## Build type + if env["tests"]: + env["windows_subsystem"] = "console" + elif env["windows_subsystem"] == "default": + # Default means we use console for debug, gui for release. + if "debug" in env["target"]: + env["windows_subsystem"] = "console" + else: + env["windows_subsystem"] = "gui" + if env["target"] == "release": env.Append(CCFLAGS=["-msse2"]) @@ -312,19 +336,13 @@ def configure_mingw(env): else: # optimize for size env.Prepend(CCFLAGS=["-Os"]) - env.Append(LINKFLAGS=["-Wl,--subsystem,windows"]) - if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": env.Append(CCFLAGS=["-O2"]) env.Append(CPPDEFINES=["DEBUG_ENABLED"]) if env["debug_symbols"] == "yes": - env.Prepend(CCFLAGS=["-g1"]) - if env["debug_symbols"] == "full": env.Prepend(CCFLAGS=["-g2"]) if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["-O2"]) @@ -333,7 +351,13 @@ def configure_mingw(env): elif env["target"] == "debug": env.Append(CCFLAGS=["-g3"]) - env.Append(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + + if env["windows_subsystem"] == "gui": + env.Append(LINKFLAGS=["-Wl,--subsystem,windows"]) + else: + env.Append(LINKFLAGS=["-Wl,--subsystem,console"]) + env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) ## Compiler configuration @@ -424,7 +448,7 @@ def configure_mingw(env): else: env.Append(LIBS=["cfgmgr32"]) - ## TODO !!! Reenable when OpenGLES Rendering Device is implemented !!! + ## TODO !!! Re-enable when OpenGLES Rendering Device is implemented !!! # env.Append(CPPDEFINES=['OPENGL_ENABLED']) env.Append(LIBS=["opengl32"]) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index ebe9a7d27a..dfbb734ee4 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -29,25 +29,22 @@ /*************************************************************************/ #include "display_server_windows.h" + #include "core/io/marshalls.h" +#include "core/math/geometry_2d.h" #include "main/main.h" #include "os_windows.h" #include "scene/resources/texture.h" #include <avrt.h> -#ifndef WM_POINTERUPDATE -#define WM_POINTERUPDATE 0x0245 -#endif - #ifdef DEBUG_ENABLED static String format_error_message(DWORD id) { - LPWSTR messageBuffer = nullptr; size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr); - String msg = "Error " + itos(id) + ": " + String(messageBuffer, size); + String msg = "Error " + itos(id) + ": " + String::utf16((const char16_t *)messageBuffer, size); LocalFree(messageBuffer); @@ -83,11 +80,10 @@ String DisplayServerWindows::get_name() const { } void DisplayServerWindows::alert(const String &p_alert, const String &p_title) { - MessageBoxW(nullptr, p_alert.c_str(), p_title.c_str(), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); + MessageBoxW(nullptr, (LPCWSTR)(p_alert.utf16().get_data()), (LPCWSTR)(p_title.utf16().get_data()), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); } void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { - if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED) { WindowData &wd = windows[MAIN_WINDOW_ID]; @@ -97,7 +93,6 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { ClientToScreen(wd.hWnd, (POINT *)&clipRect.right); ClipCursor(&clipRect); if (p_mode == MOUSE_MODE_CAPTURED) { - center = window_get_size() / 2; POINT pos = { (int)center.x, (int)center.y }; ClientToScreen(wd.hWnd, &pos); @@ -110,30 +105,34 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { } if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_HIDDEN) { - hCursor = SetCursor(nullptr); + if (hCursor == nullptr) { + hCursor = SetCursor(nullptr); + } else { + SetCursor(nullptr); + } } else { CursorShape c = cursor_shape; cursor_shape = CURSOR_MAX; cursor_set_shape(c); } } -void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { +void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ if (mouse_mode == p_mode) return; - _set_mouse_mode_impl(p_mode); - mouse_mode = p_mode; + + _set_mouse_mode_impl(p_mode); } + DisplayServer::MouseMode DisplayServerWindows::mouse_get_mode() const { return mouse_mode; } void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { - _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { @@ -141,11 +140,9 @@ void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { } if (mouse_mode == MOUSE_MODE_CAPTURED) { - old_x = p_to.x; old_y = p_to.y; } else { - POINT p; p.x = p_to.x; p.y = p_to.y; @@ -154,18 +151,19 @@ void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { SetCursorPos(p.x, p.y); } } + Point2i DisplayServerWindows::mouse_get_position() const { POINT p; GetCursorPos(&p); return Point2i(p.x, p.y); //return Point2(old_x, old_y); } + int DisplayServerWindows::mouse_get_button_state() const { return last_button_state; } void DisplayServerWindows::clipboard_set(const String &p_text) { - _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { @@ -174,18 +172,19 @@ void DisplayServerWindows::clipboard_set(const String &p_text) { // Convert LF line endings to CRLF in clipboard content // Otherwise, line endings won't be visible when pasted in other software - String text = p_text.replace("\n", "\r\n"); + String text = p_text.replace("\r\n", "\n").replace("\n", "\r\n"); // avoid \r\r\n if (!OpenClipboard(windows[last_focused_window].hWnd)) { ERR_FAIL_MSG("Unable to open clipboard."); } EmptyClipboard(); - HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (text.length() + 1) * sizeof(CharType)); + Char16String utf16 = text.utf16(); + HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (utf16.length() + 1) * sizeof(WCHAR)); ERR_FAIL_COND_MSG(mem == nullptr, "Unable to allocate memory for clipboard contents."); LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem); - memcpy(lptstrCopy, text.c_str(), (text.length() + 1) * sizeof(CharType)); + memcpy(lptstrCopy, utf16.get_data(), (utf16.length() + 1) * sizeof(WCHAR)); GlobalUnlock(mem); SetClipboardData(CF_UNICODETEXT, mem); @@ -204,8 +203,8 @@ void DisplayServerWindows::clipboard_set(const String &p_text) { CloseClipboard(); } -String DisplayServerWindows::clipboard_get() const { +String DisplayServerWindows::clipboard_get() const { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { @@ -218,26 +217,20 @@ String DisplayServerWindows::clipboard_get() const { }; if (IsClipboardFormatAvailable(CF_UNICODETEXT)) { - HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); if (mem != nullptr) { - LPWSTR ptr = (LPWSTR)GlobalLock(mem); if (ptr != nullptr) { - - ret = String((CharType *)ptr); + ret = String::utf16((const char16_t *)ptr); GlobalUnlock(mem); }; }; } else if (IsClipboardFormatAvailable(CF_TEXT)) { - HGLOBAL mem = GetClipboardData(CF_UNICODETEXT); if (mem != nullptr) { - LPTSTR ptr = (LPTSTR)GlobalLock(mem); if (ptr != nullptr) { - ret.parse_utf8((const char *)ptr); GlobalUnlock(mem); }; @@ -256,7 +249,6 @@ typedef struct { } EnumScreenData; static BOOL CALLBACK _MonitorEnumProcScreen(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - EnumScreenData *data = (EnumScreenData *)dwData; if (data->monitor == hMonitor) { data->screen = data->count; @@ -267,7 +259,6 @@ static BOOL CALLBACK _MonitorEnumProcScreen(HMONITOR hMonitor, HDC hdcMonitor, L } static BOOL CALLBACK _MonitorEnumProcCount(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - int *data = (int *)dwData; (*data)++; return TRUE; @@ -288,7 +279,6 @@ typedef struct { } EnumPosData; static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - EnumPosData *data = (EnumPosData *)dwData; if (data->count == data->screen) { data->pos.x = lprcMonitor->left; @@ -298,8 +288,8 @@ static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRE data->count++; return TRUE; } -Point2i DisplayServerWindows::screen_get_position(int p_screen) const { +Point2i DisplayServerWindows::screen_get_position(int p_screen) const { _THREAD_SAFE_METHOD_ EnumPosData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Point2() }; @@ -320,7 +310,6 @@ typedef struct { } EnumRectData; static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - EnumSizeData *data = (EnumSizeData *)dwData; if (data->count == data->screen) { data->size.x = lprcMonitor->right - lprcMonitor->left; @@ -332,7 +321,6 @@ static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPR } Size2i DisplayServerWindows::screen_get_size(int p_screen) const { - _THREAD_SAFE_METHOD_ EnumSizeData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Size2() }; @@ -341,7 +329,6 @@ Size2i DisplayServerWindows::screen_get_size(int p_screen) const { } static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - EnumRectData *data = (EnumRectData *)dwData; if (data->count == data->screen) { MONITORINFO minfo; @@ -360,7 +347,6 @@ static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonito } Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const { - _THREAD_SAFE_METHOD_ EnumRectData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, Rect2i() }; @@ -382,7 +368,6 @@ enum _MonitorDpiType { }; static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Default) { - int dpiX = 96, dpiY = 96; static HMODULE Shcore = nullptr; @@ -405,7 +390,6 @@ static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Defau if (hmon && (Shcore != (HMODULE)INVALID_HANDLE_VALUE)) { hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y); if (SUCCEEDED(hr) && (x > 0) && (y > 0)) { - dpiX = (int)x; dpiY = (int)y; } @@ -429,7 +413,6 @@ static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Defau } static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { - EnumDpiData *data = (EnumDpiData *)dwData; if (data->count == data->screen) { data->dpi = QueryDpiForMonitor(hMonitor); @@ -446,6 +429,7 @@ int DisplayServerWindows::screen_get_dpi(int p_screen) const { EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data); return data.dpi; } + bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const { #ifndef _MSC_VER #warning touchscreen not working @@ -455,18 +439,19 @@ bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const { void DisplayServerWindows::screen_set_orientation(ScreenOrientation p_orientation, int p_screen) { } + DisplayServer::ScreenOrientation DisplayServerWindows::screen_get_orientation(int p_screen) const { return SCREEN_LANDSCAPE; } void DisplayServerWindows::screen_set_keep_on(bool p_enable) { } + bool DisplayServerWindows::screen_is_kept_on() const { return false; } Vector<DisplayServer::WindowID> DisplayServerWindows::get_window_list() const { - _THREAD_SAFE_METHOD_ Vector<DisplayServer::WindowID> ret; @@ -477,7 +462,6 @@ Vector<DisplayServer::WindowID> DisplayServerWindows::get_window_list() const { } DisplayServer::WindowID DisplayServerWindows::get_window_at_screen_position(const Point2i &p_position) const { - POINT p; p.x = p_position.x; p.y = p_position.y; @@ -492,10 +476,10 @@ DisplayServer::WindowID DisplayServerWindows::get_window_at_screen_position(cons } DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { - _THREAD_SAFE_METHOD_ WindowID window_id = _create_window(p_mode, p_flags, p_rect); + ERR_FAIL_COND_V_MSG(window_id == INVALID_WINDOW_ID, INVALID_WINDOW_ID, "Failed to create sub window."); WindowData &wd = windows[window_id]; @@ -512,18 +496,24 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod wd.no_focus = true; } - _update_window_style(window_id); + return window_id; +} + +void DisplayServerWindows::show_window(WindowID p_id) { + WindowData &wd = windows[p_id]; + + if (p_id != MAIN_WINDOW_ID) { + _update_window_style(p_id); + } - ShowWindow(wd.hWnd, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window - if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) { + ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window + if (!wd.no_focus) { SetForegroundWindow(wd.hWnd); // Slightly Higher Priority SetFocus(wd.hWnd); // Sets Keyboard Focus To } - - return window_id; } -void DisplayServerWindows::delete_sub_window(WindowID p_window) { +void DisplayServerWindows::delete_sub_window(WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -545,12 +535,15 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) { } #endif + if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[p_window].wtctx) { + wintab_WTClose(windows[p_window].wtctx); + windows[p_window].wtctx = 0; + } DestroyWindow(windows[p_window].hWnd); windows.erase(p_window); } void DisplayServerWindows::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -558,7 +551,6 @@ void DisplayServerWindows::window_attach_instance_id(ObjectID p_instance, Window } ObjectID DisplayServerWindows::window_get_attached_instance_id(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); @@ -566,7 +558,6 @@ ObjectID DisplayServerWindows::window_get_attached_instance_id(WindowID p_window } void DisplayServerWindows::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -574,21 +565,20 @@ void DisplayServerWindows::window_set_rect_changed_callback(const Callable &p_ca } void DisplayServerWindows::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); windows[p_window].event_callback = p_callable; } -void DisplayServerWindows::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { +void DisplayServerWindows::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); windows[p_window].input_event_callback = p_callable; } -void DisplayServerWindows::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { +void DisplayServerWindows::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -596,7 +586,6 @@ void DisplayServerWindows::window_set_input_text_callback(const Callable &p_call } void DisplayServerWindows::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -604,15 +593,43 @@ void DisplayServerWindows::window_set_drop_files_callback(const Callable &p_call } void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_window) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(!windows.has(p_window)); + SetWindowTextW(windows[p_window].hWnd, (LPCWSTR)(p_title.utf16().get_data())); +} + +void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); - SetWindowTextW(windows[p_window].hWnd, p_title.c_str()); + windows[p_window].mpath = p_region; + _update_window_mouse_passthrough(p_window); } -int DisplayServerWindows::window_get_current_screen(WindowID p_window) const { +void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) { + if (windows[p_window].mpath.size() == 0) { + SetWindowRgn(windows[p_window].hWnd, nullptr, TRUE); + } else { + POINT *points = (POINT *)memalloc(sizeof(POINT) * windows[p_window].mpath.size()); + for (int i = 0; i < windows[p_window].mpath.size(); i++) { + if (windows[p_window].borderless) { + points[i].x = windows[p_window].mpath[i].x; + points[i].y = windows[p_window].mpath[i].y; + } else { + points[i].x = windows[p_window].mpath[i].x + GetSystemMetrics(SM_CXSIZEFRAME); + points[i].y = windows[p_window].mpath[i].y + GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYCAPTION); + } + } + HRGN region = CreatePolygonRgn(points, windows[p_window].mpath.size(), ALTERNATE); + SetWindowRgn(windows[p_window].hWnd, region, TRUE); + DeleteObject(region); + memfree(points); + } +} + +int DisplayServerWindows::window_get_current_screen(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), -1); @@ -621,8 +638,8 @@ int DisplayServerWindows::window_get_current_screen(WindowID p_window) const { EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcScreen, (LPARAM)&data); return data.screen; } -void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_window) { +void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -633,7 +650,6 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi } Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); @@ -658,26 +674,27 @@ Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { return Point2(r.left, r.top); #endif } -void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) { +void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) { POINT mouse_pos; if (GetCursorPos(&mouse_pos) && ScreenToClient(windows[p_window].hWnd, &mouse_pos)) { if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= windows[p_window].width && mouse_pos.y <= windows[p_window].height) { old_x = mouse_pos.x; old_y = mouse_pos.y; old_invalid = false; - InputFilter::get_singleton()->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y)); + Input::get_singleton()->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y)); } } } -void DisplayServerWindows::window_set_position(const Point2i &p_position, WindowID p_window) { +void DisplayServerWindows::window_set_position(const Point2i &p_position, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (wd.fullscreen) return; + if (wd.fullscreen) + return; #if 0 //wrong needs to account properly for decorations RECT r; @@ -711,7 +728,6 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window } void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_parent) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(p_window == p_parent); @@ -748,7 +764,6 @@ void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_pa } void DisplayServerWindows::window_set_max_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -760,8 +775,8 @@ void DisplayServerWindows::window_set_max_size(const Size2i p_size, WindowID p_w } wd.max_size = p_size; } -Size2i DisplayServerWindows::window_get_max_size(WindowID p_window) const { +Size2i DisplayServerWindows::window_get_max_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -770,7 +785,6 @@ Size2i DisplayServerWindows::window_get_max_size(WindowID p_window) const { } void DisplayServerWindows::window_set_min_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -782,8 +796,8 @@ void DisplayServerWindows::window_set_min_size(const Size2i p_size, WindowID p_w } wd.min_size = p_size; } -Size2i DisplayServerWindows::window_get_min_size(WindowID p_window) const { +Size2i DisplayServerWindows::window_get_min_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -792,7 +806,6 @@ Size2i DisplayServerWindows::window_get_min_size(WindowID p_window) const { } void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -836,8 +849,8 @@ void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_windo ClipCursor(&crect); } } -Size2i DisplayServerWindows::window_get_size(WindowID p_window) const { +Size2i DisplayServerWindows::window_get_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -853,8 +866,8 @@ Size2i DisplayServerWindows::window_get_size(WindowID p_window) const { } return Size2(); } -Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -868,7 +881,6 @@ Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { } void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { - r_style = 0; r_style_ex = WS_EX_WINDOWEDGE; if (p_main_window) { @@ -881,7 +893,6 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre // r_style_ex |= WS_EX_TOOLWINDOW; //} } else { - if (p_resizable) { if (p_maximized) { r_style = WS_OVERLAPPEDWINDOW | WS_MAXIMIZE; @@ -900,7 +911,6 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre } void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repaint, bool p_maximized) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -924,14 +934,12 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain } void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) { - RECT rect; wd.fullscreen = false; @@ -953,21 +961,18 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) } if (p_mode == WINDOW_MODE_MAXIMIZED) { - ShowWindow(wd.hWnd, SW_MAXIMIZE); wd.maximized = true; wd.minimized = false; } if (p_mode == WINDOW_MODE_WINDOWED) { - ShowWindow(wd.hWnd, SW_RESTORE); wd.maximized = false; wd.minimized = false; } if (p_mode == WINDOW_MODE_MINIMIZED) { - ShowWindow(wd.hWnd, SW_MINIMIZE); wd.maximized = false; wd.minimized = true; @@ -996,8 +1001,8 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE); } } -DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const { +DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); @@ -1015,7 +1020,6 @@ DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_windo } bool DisplayServerWindows::window_is_maximize_allowed(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), false); @@ -1026,76 +1030,66 @@ bool DisplayServerWindows::window_is_maximize_allowed(WindowID p_window) const { } void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { - wd.resizable = !p_enabled; _update_window_style(p_window); } break; case WINDOW_FLAG_BORDERLESS: { - wd.borderless = p_enabled; _update_window_style(p_window); + _update_window_mouse_passthrough(p_window); } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { - ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top"); wd.always_on_top = p_enabled; _update_window_style(p_window); } break; case WINDOW_FLAG_TRANSPARENT: { - // FIXME: Implement. } break; case WINDOW_FLAG_NO_FOCUS: { - wd.no_focus = p_enabled; _update_window_style(p_window); } break; - case WINDOW_FLAG_MAX: break; + case WINDOW_FLAG_MAX: + break; } } bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), false); const WindowData &wd = windows[p_window]; switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { - return !wd.resizable; } break; case WINDOW_FLAG_BORDERLESS: { - return wd.borderless; } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { - return wd.always_on_top; } break; case WINDOW_FLAG_TRANSPARENT: { - // FIXME: Implement. } break; case WINDOW_FLAG_NO_FOCUS: { - return wd.no_focus; } break; - case WINDOW_FLAG_MAX: break; + case WINDOW_FLAG_MAX: + break; } return false; } void DisplayServerWindows::window_request_attention(WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1109,8 +1103,8 @@ void DisplayServerWindows::window_request_attention(WindowID p_window) { info.uCount = 2; FlashWindowEx(&info); } -void DisplayServerWindows::window_move_to_foreground(WindowID p_window) { +void DisplayServerWindows::window_move_to_foreground(WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1120,7 +1114,6 @@ void DisplayServerWindows::window_move_to_foreground(WindowID p_window) { } bool DisplayServerWindows::window_can_draw(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), false); @@ -1129,7 +1122,6 @@ bool DisplayServerWindows::window_can_draw(WindowID p_window) const { } bool DisplayServerWindows::can_any_window_draw() const { - _THREAD_SAFE_METHOD_ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { @@ -1142,7 +1134,6 @@ bool DisplayServerWindows::can_any_window_draw() const { } void DisplayServerWindows::window_set_ime_active(const bool p_active, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1156,8 +1147,8 @@ void DisplayServerWindows::window_set_ime_active(const bool p_active, WindowID p ImmAssociateContext(wd.hWnd, (HIMC)0); } } -void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { +void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1178,7 +1169,6 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI } void DisplayServerWindows::console_set_visible(bool p_enabled) { - _THREAD_SAFE_METHOD_ if (console_visible == p_enabled) @@ -1186,12 +1176,12 @@ void DisplayServerWindows::console_set_visible(bool p_enabled) { ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE); console_visible = p_enabled; } + bool DisplayServerWindows::is_console_visible() const { return console_visible; } void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) { - _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX(p_shape, CURSOR_MAX); @@ -1232,12 +1222,12 @@ void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) { cursor_shape = p_shape; } + DisplayServer::CursorShape DisplayServerWindows::cursor_get_shape() const { return cursor_shape; } void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap) { - // Get the system display DC HDC hDC = GetDC(nullptr); @@ -1287,11 +1277,9 @@ void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTra } void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { - _THREAD_SAFE_METHOD_ if (p_cursor.is_valid()) { - Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); if (cursor_c) { @@ -1419,7 +1407,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh } } -bool DisplayServerWindows::get_swap_ok_cancel() { +bool DisplayServerWindows::get_swap_cancel_ok() { return true; } @@ -1429,72 +1417,102 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) { AllowSetForegroundWindow(pid); } -DisplayServer::LatinKeyboardVariant DisplayServerWindows::get_latin_keyboard_variant() const { +int DisplayServerWindows::keyboard_get_layout_count() const { + return GetKeyboardLayoutList(0, NULL); +} - _THREAD_SAFE_METHOD_ +int DisplayServerWindows::keyboard_get_current_layout() const { + HKL cur_layout = GetKeyboardLayout(0); - unsigned long azerty[] = { - 0x00020401, // Arabic (102) AZERTY - 0x0001080c, // Belgian (Comma) - 0x0000080c, // Belgian French - 0x0000040c, // French - 0 // <--- STOP MARK - }; - unsigned long qwertz[] = { - 0x0000041a, // Croation - 0x00000405, // Czech - 0x00000407, // German - 0x00010407, // German (IBM) - 0x0000040e, // Hungarian - 0x0000046e, // Luxembourgish - 0x00010415, // Polish (214) - 0x00000418, // Romanian (Legacy) - 0x0000081a, // Serbian (Latin) - 0x0000041b, // Slovak - 0x00000424, // Slovenian - 0x0001042e, // Sorbian Extended - 0x0002042e, // Sorbian Standard - 0x0000042e, // Sorbian Standard (Legacy) - 0x0000100c, // Swiss French - 0x00000807, // Swiss German - 0 // <--- STOP MARK - }; - unsigned long dvorak[] = { - 0x00010409, // US-Dvorak - 0x00030409, // US-Dvorak for left hand - 0x00040409, // US-Dvorak for right hand - 0 // <--- STOP MARK - }; + int layout_count = GetKeyboardLayoutList(0, NULL); + HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL)); + GetKeyboardLayoutList(layout_count, layouts); + + for (int i = 0; i < layout_count; i++) { + if (cur_layout == layouts[i]) { + memfree(layouts); + return i; + } + } + memfree(layouts); + return -1; +} + +void DisplayServerWindows::keyboard_set_current_layout(int p_index) { + int layout_count = GetKeyboardLayoutList(0, NULL); - char name[KL_NAMELENGTH + 1]; - name[0] = 0; - GetKeyboardLayoutNameA(name); + ERR_FAIL_INDEX(p_index, layout_count); + + HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL)); + GetKeyboardLayoutList(layout_count, layouts); + ActivateKeyboardLayout(layouts[p_index], KLF_SETFORPROCESS); + memfree(layouts); +} + +String DisplayServerWindows::keyboard_get_layout_language(int p_index) const { + int layout_count = GetKeyboardLayoutList(0, NULL); + + ERR_FAIL_INDEX_V(p_index, layout_count, ""); + + HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL)); + GetKeyboardLayoutList(layout_count, layouts); + + WCHAR buf[LOCALE_NAME_MAX_LENGTH]; + memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR)); + LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); + + memfree(layouts); + + return String::utf16((const char16_t *)buf).substr(0, 2); +} + +String _get_full_layout_name_from_registry(HKL p_layout) { + String id = "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + String::num_int64((int64_t)p_layout, 16, false).lpad(8, "0"); + String ret; - unsigned long hex = strtoul(name, nullptr, 16); + HKEY hkey; + WCHAR layout_text[1024]; + memset(layout_text, 0, 1024 * sizeof(WCHAR)); - int i = 0; - while (azerty[i] != 0) { - if (azerty[i] == hex) return LATIN_KEYBOARD_AZERTY; - i++; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) { + return ret; } - i = 0; - while (qwertz[i] != 0) { - if (qwertz[i] == hex) return LATIN_KEYBOARD_QWERTZ; - i++; + DWORD buffer = 1024; + DWORD vtype = REG_SZ; + if (RegQueryValueExW(hkey, L"Layout Text", NULL, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) { + ret = String::utf16((const char16_t *)layout_text); } + RegCloseKey(hkey); + return ret; +} + +String DisplayServerWindows::keyboard_get_layout_name(int p_index) const { + int layout_count = GetKeyboardLayoutList(0, NULL); + + ERR_FAIL_INDEX_V(p_index, layout_count, ""); + + HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL)); + GetKeyboardLayoutList(layout_count, layouts); + + String ret = _get_full_layout_name_from_registry(layouts[p_index]); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine). + if (ret == String()) { + WCHAR buf[LOCALE_NAME_MAX_LENGTH]; + memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR)); + LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); - i = 0; - while (dvorak[i] != 0) { - if (dvorak[i] == hex) return LATIN_KEYBOARD_DVORAK; - i++; + WCHAR name[1024]; + memset(name, 0, 1024 * sizeof(WCHAR)); + GetLocaleInfoEx(buf, LOCALE_SLOCALIZEDDISPLAYNAME, (LPWSTR)&name, 1024); + + ret = String::utf16((const char16_t *)name); } + memfree(layouts); - return LATIN_KEYBOARD_QWERTY; + return ret; } void DisplayServerWindows::process_events() { - _THREAD_SAFE_METHOD_ MSG msg; @@ -1504,19 +1522,17 @@ void DisplayServerWindows::process_events() { } while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); DispatchMessageW(&msg); } if (!drop_events) { _process_key_events(); - InputFilter::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_accumulated_events(); } } void DisplayServerWindows::force_process_and_drop_events() { - _THREAD_SAFE_METHOD_ drop_events = true; @@ -1526,13 +1542,14 @@ void DisplayServerWindows::force_process_and_drop_events() { void DisplayServerWindows::release_rendering_thread() { } + void DisplayServerWindows::make_rendering_thread() { } + void DisplayServerWindows::swap_buffers() { } void DisplayServerWindows::set_native_icon(const String &p_filename) { - _THREAD_SAFE_METHOD_ FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); @@ -1625,8 +1642,8 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) { memdelete(f); memdelete(icon_dir); } -void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { +void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!p_icon.is_valid()); @@ -1658,9 +1675,7 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { const uint8_t *r = icon->get_data().ptr(); for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4]; uint8_t *wpx = &wr[(i * w + j) * 4]; wpx[0] = rpx[2]; @@ -1681,6 +1696,7 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { void DisplayServerWindows::vsync_set_use_via_compositor(bool p_enable) { } + bool DisplayServerWindows::vsync_is_using_via_compositor() const { return false; } @@ -1697,7 +1713,6 @@ void DisplayServerWindows::set_context(Context p_context) { #define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80)) void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx) { - // Defensive if (touch_state.has(idx) == p_pressed) return; @@ -1715,11 +1730,10 @@ void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float event->set_pressed(p_pressed); event->set_position(Vector2(p_x, p_y)); - InputFilter::get_singleton()->accumulate_input_event(event); + Input::get_singleton()->accumulate_input_event(event); } void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, int idx) { - Map<int, Vector2>::Element *curr = touch_state.find(idx); // Defensive if (!curr) @@ -1735,13 +1749,12 @@ void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, event->set_position(Vector2(p_x, p_y)); event->set_relative(Vector2(p_x, p_y) - curr->get()); - InputFilter::get_singleton()->accumulate_input_event(event); + Input::get_singleton()->accumulate_input_event(event); curr->get() = Vector2(p_x, p_y); } void DisplayServerWindows::_send_window_event(const WindowData &wd, WindowEvent p_event) { - if (!wd.event_callback.is_null()) { Variant event = int(p_event); Variant *eventp = &event; @@ -1756,7 +1769,12 @@ void DisplayServerWindows::_dispatch_input_events(const Ref<InputEvent> &p_event } void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) { + _THREAD_SAFE_METHOD_ + if (in_dispatch_input_event) { + return; + } + in_dispatch_input_event = true; Variant ev = p_event; Variant *evp = &ev; Variant ret; @@ -1768,6 +1786,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) ERR_FAIL_COND(!windows.has(event_from_window->get_window_id())); Callable callable = windows[event_from_window->get_window_id()].input_event_callback; if (callable.is_null()) { + in_dispatch_input_event = false; return; } callable.call((const Variant **)&evp, 1, ret, ce); @@ -1781,14 +1800,13 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) callable.call((const Variant **)&evp, 1, ret, ce); } } + + in_dispatch_input_event = false; } LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (drop_events) { - if (user_proc) { - return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); } else { return DefWindowProcW(hWnd, uMsg, wParam, lParam); @@ -1796,24 +1814,37 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA }; WindowID window_id = INVALID_WINDOW_ID; + bool window_created = false; for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { if (E->get().hWnd == hWnd) { window_id = E->key(); + window_created = true; break; } } + if (!window_created) { + // Window creation in progress. + window_id = window_id_counter; + ERR_FAIL_COND_V(!windows.has(window_id), 0); + } + switch (uMsg) // Check For Windows Messages { case WM_SETFOCUS: { - windows[window_id].window_has_focus = true; last_focused_window = window_id; // Restore mouse mode _set_mouse_mode_impl(mouse_mode); + if (!app_focused) { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } + app_focused = true; + } break; } case WM_KILLFOCUS: { @@ -1829,6 +1860,19 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } touch_state.clear(); + bool self_steal = false; + HWND new_hwnd = (HWND)wParam; + if (IsWindow(new_hwnd)) { + self_steal = true; + } + + if (!self_steal) { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } + app_focused = false; + } + break; } case WM_ACTIVATE: // Watch For Window Activate Message @@ -1836,20 +1880,23 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].minimized = HIWORD(wParam) != 0; if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) { - _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN); windows[window_id].window_focused = true; alt_mem = false; control_mem = false; shift_mem = false; } else { // WM_INACTIVE - InputFilter::get_singleton()->release_pressed_events(); + Input::get_singleton()->release_pressed_events(); _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); windows[window_id].window_focused = false; alt_mem = false; }; - return 0; // Return To The Message Loop + if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) { + wintab_WTEnable(windows[window_id].wtctx, GET_WM_ACTIVATE_STATE(wParam, lParam)); + } + + return 0; // Return To The Message Loop } case WM_GETMINMAXINFO: { if (windows[window_id].resizable && !windows[window_id].fullscreen) { @@ -1889,13 +1936,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_CLOSE: // Did We Receive A Close Message? { - _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); return 0; // Jump Back } case WM_MOUSELEAVE: { - old_invalid = true; outside = true; @@ -1929,6 +1974,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_shift(shift_mem); mm->set_alt(alt_mem); + mm->set_pressure((raw->data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN) ? 1.0f : 0.0f); + mm->set_button_mask(last_button_state); Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); @@ -1940,14 +1987,13 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_position(c); mm->set_global_position(c); - InputFilter::get_singleton()->set_mouse_position(c); + Input::get_singleton()->set_mouse_position(c); mm->set_speed(Vector2(0, 0)); if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY)); } else if (raw->data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) { - int nScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); int nScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); int nScreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN); @@ -1973,16 +2019,132 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (windows[window_id].window_has_focus && mm->get_relative() != Vector2()) - InputFilter::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->accumulate_input_event(mm); } delete[] lpb; } break; + case WT_CSRCHANGE: + case WT_PROXIMITY: { + if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) { + AXIS pressure; + if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_NPRESSURE, &pressure)) { + windows[window_id].min_pressure = int(pressure.axMin); + windows[window_id].max_pressure = int(pressure.axMax); + } + AXIS orientation[3]; + if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_ORIENTATION, &orientation)) { + windows[window_id].tilt_supported = orientation[0].axResolution && orientation[1].axResolution; + } + return 0; + } + } break; + case WT_PACKET: { + if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) { + PACKET packet; + if (wintab_WTPacket(windows[window_id].wtctx, wParam, &packet)) { + float pressure = float(packet.pkNormalPressure - windows[window_id].min_pressure) / float(windows[window_id].max_pressure - windows[window_id].min_pressure); + windows[window_id].last_pressure = pressure; + windows[window_id].last_pressure_update = 0; + + double azim = (packet.pkOrientation.orAzimuth / 10.0f) * (Math_PI / 180); + double alt = Math::tan((Math::abs(packet.pkOrientation.orAltitude / 10.0f)) * (Math_PI / 180)); + + if (windows[window_id].tilt_supported) { + windows[window_id].last_tilt = Vector2(Math::atan(Math::sin(azim) / alt), Math::atan(Math::cos(azim) / alt)); + } else { + windows[window_id].last_tilt = Vector2(); + } + + POINT coords; + GetCursorPos(&coords); + ScreenToClient(windows[window_id].hWnd, &coords); + + // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. + if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) + break; + + Ref<InputEventMouseMotion> mm; + mm.instance(); + mm->set_window_id(window_id); + mm->set_control(GetKeyState(VK_CONTROL) < 0); + mm->set_shift(GetKeyState(VK_SHIFT) < 0); + mm->set_alt(alt_mem); + + mm->set_pressure(windows[window_id].last_pressure); + mm->set_tilt(windows[window_id].last_tilt); + + mm->set_button_mask(last_button_state); + + mm->set_position(Vector2(coords.x, coords.y)); + mm->set_global_position(Vector2(coords.x, coords.y)); + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); + old_x = c.x; + old_y = c.y; + + if (mm->get_position() == c) { + center = c; + return 0; + } + + Point2i ncenter = mm->get_position(); + center = ncenter; + POINT pos = { (int)c.x, (int)c.y }; + ClientToScreen(windows[window_id].hWnd, &pos); + SetCursorPos(pos.x, pos.y); + } + + Input::get_singleton()->set_mouse_position(mm->get_position()); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); + + if (old_invalid) { + old_x = mm->get_position().x; + old_y = mm->get_position().y; + old_invalid = false; + } + + mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + old_x = mm->get_position().x; + old_y = mm->get_position().y; + if (windows[window_id].window_has_focus) + Input::get_singleton()->accumulate_input_event(mm); + } + return 0; + } + } break; + case WM_POINTERENTER: { + if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { + break; + } + + if ((OS::get_singleton()->get_current_tablet_driver() != "winink") || !winink_available) { + break; + } + + uint32_t pointer_id = LOWORD(wParam); + POINTER_INPUT_TYPE pointer_type = PT_POINTER; + if (!win8p_GetPointerType(pointer_id, &pointer_type)) { + break; + } + + if (pointer_type != PT_PEN) { + break; + } + + windows[window_id].block_mm = true; + return 0; + } break; + case WM_POINTERLEAVE: { + windows[window_id].block_mm = false; + return 0; + } break; case WM_POINTERUPDATE: { if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { break; } - if (!win8p_GetPointerType || !win8p_GetPointerPenInfo) { + if ((OS::get_singleton()->get_current_tablet_driver() != "winink") || !winink_available) { break; } @@ -2001,7 +2163,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA break; } - if (InputFilter::get_singleton()->is_emulating_mouse_from_touch()) { + if (Input::get_singleton()->is_emulating_mouse_from_touch()) { // Universal translation enabled; ignore OS translation LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { @@ -2038,11 +2200,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm.instance(); mm->set_window_id(window_id); - mm->set_pressure(pen_info.pressure ? (float)pen_info.pressure / 1024 : 0); - mm->set_tilt(Vector2(pen_info.tiltX ? (float)pen_info.tiltX / 90 : 0, pen_info.tiltY ? (float)pen_info.tiltY / 90 : 0)); + if (pen_info.penMask & PEN_MASK_PRESSURE) { + mm->set_pressure((float)pen_info.pressure / 1024); + } else { + mm->set_pressure((HIWORD(wParam) & POINTER_MESSAGE_FLAG_FIRSTBUTTON) ? 1.0f : 0.0f); + } + if ((pen_info.penMask & PEN_MASK_TILT_X) && (pen_info.penMask & PEN_MASK_TILT_Y)) { + mm->set_tilt(Vector2((float)pen_info.tiltX / 90, (float)pen_info.tiltY / 90)); + } - mm->set_control((wParam & MK_CONTROL) != 0); - mm->set_shift((wParam & MK_SHIFT) != 0); + mm->set_control(GetKeyState(VK_CONTROL) < 0); + mm->set_shift(GetKeyState(VK_SHIFT) < 0); mm->set_alt(alt_mem); mm->set_button_mask(last_button_state); @@ -2057,7 +2225,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_global_position(Vector2(coords.x, coords.y)); if (mouse_mode == MOUSE_MODE_CAPTURED) { - Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); old_x = c.x; old_y = c.y; @@ -2074,11 +2241,10 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA SetCursorPos(pos.x, pos.y); } - InputFilter::get_singleton()->set_mouse_position(mm->get_position()); - mm->set_speed(InputFilter::get_singleton()->get_last_mouse_speed()); + Input::get_singleton()->set_mouse_position(mm->get_position()); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); if (old_invalid) { - old_x = mm->get_position().x; old_y = mm->get_position().y; old_invalid = false; @@ -2088,17 +2254,21 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus) { - InputFilter::get_singleton()->parse_input_event(mm); + Input::get_singleton()->accumulate_input_event(mm); } return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event } break; case WM_MOUSEMOVE: { + if (windows[window_id].block_mm) { + break; + } + if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { break; } - if (InputFilter::get_singleton()->is_emulating_mouse_from_touch()) { + if (Input::get_singleton()->is_emulating_mouse_from_touch()) { // Universal translation enabled; ignore OS translation LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { @@ -2138,13 +2308,28 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_shift((wParam & MK_SHIFT) != 0); mm->set_alt(alt_mem); + if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) { + // Note: WinTab sends both WT_PACKET and WM_xBUTTONDOWN/UP/MOUSEMOVE events, use mouse 1/0 pressure only when last_pressure was not update recently. + if (windows[window_id].last_pressure_update < 10) { + windows[window_id].last_pressure_update++; + } else { + windows[window_id].last_tilt = Vector2(); + windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f; + } + } else { + windows[window_id].last_tilt = Vector2(); + windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f; + } + + mm->set_pressure(windows[window_id].last_pressure); + mm->set_tilt(windows[window_id].last_tilt); + mm->set_button_mask(last_button_state); mm->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); mm->set_global_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); if (mouse_mode == MOUSE_MODE_CAPTURED) { - Point2i c(windows[window_id].width / 2, windows[window_id].height / 2); old_x = c.x; old_y = c.y; @@ -2161,11 +2346,10 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA SetCursorPos(pos.x, pos.y); } - InputFilter::get_singleton()->set_mouse_position(mm->get_position()); - mm->set_speed(InputFilter::get_singleton()->get_last_mouse_speed()); + Input::get_singleton()->set_mouse_position(mm->get_position()); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); if (old_invalid) { - old_x = mm->get_position().x; old_y = mm->get_position().y; old_invalid = false; @@ -2175,12 +2359,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus) - InputFilter::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->accumulate_input_event(mm); } break; case WM_LBUTTONDOWN: case WM_LBUTTONUP: - if (InputFilter::get_singleton()->is_emulating_mouse_from_touch()) { + if (Input::get_singleton()->is_emulating_mouse_from_touch()) { // Universal translation enabled; ignore OS translations for left button LPARAM extra = GetMessageExtraInfo(); if (IsTouchEvent(extra)) { @@ -2200,7 +2384,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_XBUTTONDBLCLK: case WM_XBUTTONDOWN: case WM_XBUTTONUP: { - Ref<InputEventMouseButton> mb; mb.instance(); mb->set_window_id(window_id); @@ -2246,7 +2429,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_doubleclick(true); } break; case WM_MOUSEWHEEL: { - mb->set_pressed(true); int motion = (short)HIWORD(wParam); if (!motion) @@ -2259,7 +2441,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_MOUSEHWHEEL: { - mb->set_pressed(true); int motion = (short)HIWORD(wParam); if (!motion) @@ -2274,7 +2455,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } break; case WM_XBUTTONDOWN: { - mb->set_pressed(true); if (HIWORD(wParam) == XBUTTON1) mb->set_button_index(BUTTON_XBUTTON1); @@ -2282,7 +2462,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_button_index(BUTTON_XBUTTON2); } break; case WM_XBUTTONUP: { - mb->set_pressed(false); if (HIWORD(wParam) == XBUTTON1) mb->set_button_index(BUTTON_XBUTTON1); @@ -2290,7 +2469,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_button_index(BUTTON_XBUTTON2); } break; case WM_XBUTTONDBLCLK: { - mb->set_pressed(true); if (HIWORD(wParam) == XBUTTON1) mb->set_button_index(BUTTON_XBUTTON1); @@ -2316,17 +2494,14 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); if (mouse_mode == MOUSE_MODE_CAPTURED && !use_raw_input) { - mb->set_position(Vector2(old_x, old_y)); } if (uMsg != WM_MOUSEWHEEL && uMsg != WM_MOUSEHWHEEL) { if (mb->is_pressed()) { - if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED) SetCapture(hWnd); } else { - if (--pressrc <= 0) { if (mouse_mode != MOUSE_MODE_CAPTURED) { ReleaseCapture(); @@ -2347,7 +2522,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_global_position(mb->get_position()); - InputFilter::get_singleton()->accumulate_input_event(mb); + Input::get_singleton()->accumulate_input_event(mb); if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) { //send release for mouse wheel Ref<InputEventMouseButton> mbd = mb->duplicate(); @@ -2355,7 +2530,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA last_button_state &= ~(1 << (mbd->get_button_index() - 1)); mbd->set_button_mask(last_button_state); mbd->set_pressed(false); - InputFilter::get_singleton()->accumulate_input_event(mbd); + Input::get_singleton()->accumulate_input_event(mbd); } } break; @@ -2367,7 +2542,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].last_pos = Point2(x, y); if (!windows[window_id].rect_changed_callback.is_null()) { - Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); Variant *sizep = &size; Variant ret; @@ -2387,7 +2561,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].height = window_h; #if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { + if ((rendering_driver == "vulkan") && window_created) { context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height); } #endif @@ -2399,7 +2573,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (!windows[window_id].rect_changed_callback.is_null()) { - Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); Variant *sizep = &size; Variant ret; @@ -2444,7 +2617,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_ENTERSIZEMOVE: { - InputFilter::get_singleton()->release_pressed_events(); + Input::get_singleton()->release_pressed_events(); move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); } break; case WM_EXITSIZEMOVE: { @@ -2463,11 +2636,10 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_SYSKEYUP: case WM_KEYUP: case WM_KEYDOWN: { - if (wParam == VK_SHIFT) - shift_mem = uMsg == WM_KEYDOWN; + shift_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); if (wParam == VK_CONTROL) - control_mem = uMsg == WM_KEYDOWN; + control_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); if (wParam == VK_MENU) { alt_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN); if (lParam & (1 << 24)) @@ -2487,7 +2659,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA [[fallthrough]]; } case WM_CHAR: { - ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE); // Make sure we don't include modifiers for the modifier key itself. @@ -2510,12 +2681,10 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_INPUTLANGCHANGEREQUEST: { - // FIXME: Do something? } break; case WM_TOUCH: { - BOOL bHandled = FALSE; UINT cInputs = LOWORD(wParam); PTOUCHINPUT pInputs = memnew_arr(TOUCHINPUT, cInputs); @@ -2530,10 +2699,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA ScreenToClient(hWnd, &touch_pos); //do something with each touch input entry if (ti.dwFlags & TOUCHEVENTF_MOVE) { - _drag_event(window_id, touch_pos.x, touch_pos.y, ti.dwID); } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) { - _touch_event(window_id, ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID); }; } @@ -2553,17 +2720,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_DEVICECHANGE: { - joypad->probe_joypads(); } break; case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) { if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED)) { //Hide the cursor - if (hCursor == nullptr) + if (hCursor == nullptr) { hCursor = SetCursor(nullptr); - else + } else { SetCursor(nullptr); + } } else { if (hCursor != nullptr) { CursorShape c = cursor_shape; @@ -2576,19 +2743,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_DROPFILES: { - HDROP hDropInfo = (HDROP)wParam; const int buffsize = 4096; - wchar_t buf[buffsize]; + WCHAR buf[buffsize]; int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0); Vector<String> files; for (int i = 0; i < fcount; i++) { - DragQueryFileW(hDropInfo, i, buf, buffsize); - String file = buf; + String file = String::utf16((const char16_t *)buf); files.push_back(file); } @@ -2603,9 +2768,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; default: { - if (user_proc) { - return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); }; }; @@ -2615,7 +2778,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton()); if (ds_win) return ds_win->WndProc(hWnd, uMsg, wParam, lParam); @@ -2624,14 +2786,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { } void DisplayServerWindows::_process_key_events() { - for (int i = 0; i < key_event_pos; i++) { - KeyEvent &ke = key_event_buffer[i]; switch (ke.uMsg) { - case WM_CHAR: { - if ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR)) { + // extended keys should only be processed as WM_KEYDOWN message. + if (!KeyMappingWindows::is_extended_key(ke.wParam) && ((i == 0 && ke.uMsg == WM_CHAR) || (i > 0 && key_event_buffer[i - 1].uMsg == WM_CHAR))) { Ref<InputEventKey> k; k.instance(); @@ -2652,14 +2812,13 @@ void DisplayServerWindows::_process_key_events() { if (k->get_unicode() < 32) k->set_unicode(0); - InputFilter::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->accumulate_input_event(k); } //do nothing } break; case WM_KEYUP: case WM_KEYDOWN: { - Ref<InputEventKey> k; k.instance(); @@ -2693,7 +2852,7 @@ void DisplayServerWindows::_process_key_events() { k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30)))); - InputFilter::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->accumulate_input_event(k); } break; } @@ -2702,8 +2861,46 @@ void DisplayServerWindows::_process_key_events() { key_event_pos = 0; } -DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { +void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const String &p_new_driver) { + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + WindowData &wd = E->get(); + wd.block_mm = false; + if ((p_old_driver == "wintab") && wintab_available && wd.wtctx) { + wintab_WTEnable(wd.wtctx, false); + wintab_WTClose(wd.wtctx); + wd.wtctx = 0; + } + if ((p_new_driver == "wintab") && wintab_available) { + wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc); + wd.wtlc.lcOptions |= CXO_MESSAGES; + wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION; + wd.wtlc.lcMoveMask = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE; + wd.wtlc.lcPktMode = 0; + wd.wtlc.lcOutOrgX = 0; + wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX; + wd.wtlc.lcOutOrgY = 0; + wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY; + wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false); + if (wd.wtctx) { + wintab_WTEnable(wd.wtctx, true); + AXIS pressure; + if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) { + wd.min_pressure = int(pressure.axMin); + wd.max_pressure = int(pressure.axMax); + } + AXIS orientation[3]; + if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) { + wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution; + } + wintab_WTEnable(wd.wtctx, true); + } else { + print_verbose("WinTab context creation failed."); + } + } + } +} +DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { DWORD dwExStyle; DWORD dwStyle; @@ -2716,11 +2913,32 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, WindowRect.top = p_rect.position.y; WindowRect.bottom = p_rect.position.y + p_rect.size.y; + if (p_mode == WINDOW_MODE_FULLSCREEN) { + int nearest_area = 0; + Rect2i screen_rect; + for (int i = 0; i < get_screen_count(); i++) { + Rect2i r; + r.position = screen_get_position(i); + r.size = screen_get_size(i); + Rect2 inters = r.clip(p_rect); + int area = inters.size.width * inters.size.height; + if (area >= nearest_area) { + screen_rect = r; + nearest_area = area; + } + } + + WindowRect.left = screen_rect.position.x; + WindowRect.right = screen_rect.position.x + screen_rect.size.x; + WindowRect.top = screen_rect.position.y; + WindowRect.bottom = screen_rect.position.y + screen_rect.size.y; + } + AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); WindowID id = window_id_counter; { - WindowData wd; + WindowData &wd = windows[id]; wd.hWnd = CreateWindowExW( dwExStyle, @@ -2735,6 +2953,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, nullptr, nullptr, hInstance, nullptr); if (!wd.hWnd) { MessageBoxW(nullptr, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION); + windows.erase(id); return INVALID_WINDOW_ID; } #ifdef VULKAN_ENABLED @@ -2743,7 +2962,8 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, if (context_vulkan->window_create(id, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) == -1) { memdelete(context_vulkan); context_vulkan = nullptr; - ERR_FAIL_V(INVALID_WINDOW_ID); + windows.erase(id); + ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Vulkan Window."); } } #endif @@ -2759,6 +2979,39 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DragAcceptFiles(wd.hWnd, true); + if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available) { + wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc); + wd.wtlc.lcOptions |= CXO_MESSAGES; + wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION; + wd.wtlc.lcMoveMask = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE; + wd.wtlc.lcPktMode = 0; + wd.wtlc.lcOutOrgX = 0; + wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX; + wd.wtlc.lcOutOrgY = 0; + wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY; + wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false); + if (wd.wtctx) { + wintab_WTEnable(wd.wtctx, true); + AXIS pressure; + if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) { + wd.min_pressure = int(pressure.axMin); + wd.max_pressure = int(pressure.axMax); + } + AXIS orientation[3]; + if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) { + wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution; + } + } else { + print_verbose("WinTab context creation failed."); + } + } else { + wd.wtctx = 0; + } + + wd.last_pressure = 0; + wd.last_pressure_update = 0; + wd.last_tilt = Vector2(); + // IME wd.im_himc = ImmGetContext(wd.hWnd); ImmReleaseContext(wd.hWnd, wd.im_himc); @@ -2768,14 +3021,22 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd.width = p_rect.size.width; wd.height = p_rect.size.height; - windows[id] = wd; - window_id_counter++; } return id; } +// WinTab API +bool DisplayServerWindows::wintab_available = false; +WTOpenPtr DisplayServerWindows::wintab_WTOpen = nullptr; +WTClosePtr DisplayServerWindows::wintab_WTClose = nullptr; +WTInfoPtr DisplayServerWindows::wintab_WTInfo = nullptr; +WTPacketPtr DisplayServerWindows::wintab_WTPacket = nullptr; +WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr; + +// Windows Ink API +bool DisplayServerWindows::winink_available = false; GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr; GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr; @@ -2786,14 +3047,6 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS { } SHC_PROCESS_DPI_AWARENESS; DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - - //Note: Functions for pen input, available on Windows 8+ - HMODULE user32_lib = LoadLibraryW(L"user32.dll"); - if (user32_lib) { - win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); - win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); - } - drop_events = false; key_event_pos = 0; @@ -2863,7 +3116,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextWindows); if (context_vulkan->initialize() != OK) { memdelete(context_vulkan); @@ -2875,7 +3127,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win #endif #if defined(OPENGL_ENABLED) if (rendering_driver_index == VIDEO_DRIVER_GLES2) { - context_gles2 = memnew(ContextGL_Windows(hWnd, false)); if (context_gles2->initialize() != OK) { @@ -2897,21 +3148,24 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } #endif - WindowID main_window = _create_window(p_mode, 0, Rect2i(Point2i(), p_resolution)); + Point2i window_position( + (screen_get_size(0).width - p_resolution.width) / 2, + (screen_get_size(0).height - p_resolution.height) / 2); + + WindowID main_window = _create_window(p_mode, 0, Rect2i(window_position, p_resolution)); + ERR_FAIL_COND_MSG(main_window == INVALID_WINDOW_ID, "Failed to create main window."); + for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, main_window); } } - ShowWindow(windows[MAIN_WINDOW_ID].hWnd, SW_SHOW); // Show The Window - SetForegroundWindow(windows[MAIN_WINDOW_ID].hWnd); // Slightly Higher Priority - SetFocus(windows[MAIN_WINDOW_ID].hWnd); // Sets Keyboard Focus To + show_window(MAIN_WINDOW_ID); #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); @@ -2945,7 +3199,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win r_error = OK; ((OS_Windows *)OS::get_singleton())->set_main_window(windows[MAIN_WINDOW_ID].hWnd); - InputFilter::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); } Vector<String> DisplayServerWindows::get_rendering_drivers_func() { @@ -2962,35 +3216,25 @@ Vector<String> DisplayServerWindows::get_rendering_drivers_func() { } DisplayServer *DisplayServerWindows::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - - return memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + DisplayServer *ds = memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + if (r_error != OK) { + ds->alert("Your video card driver does not support any of the supported Vulkan versions.\n" + "Please update your drivers or if you have a very old or integrated GPU upgrade it.", + "Unable to initialize Video driver"); + } + return ds; } void DisplayServerWindows::register_windows_driver() { - register_create_function("windows", create_func, get_rendering_drivers_func); } DisplayServerWindows::~DisplayServerWindows() { - delete joypad; touch_state.clear(); cursors_cache.clear(); -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - } - - if (context_vulkan) - memdelete(context_vulkan); - } -#endif - if (user_proc) { SetWindowLongPtr(windows[MAIN_WINDOW_ID].hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc); }; @@ -3001,7 +3245,22 @@ DisplayServerWindows::~DisplayServerWindows() { context_vulkan->window_destroy(MAIN_WINDOW_ID); } #endif - + if (wintab_available && windows[MAIN_WINDOW_ID].wtctx) { + wintab_WTClose(windows[MAIN_WINDOW_ID].wtctx); + windows[MAIN_WINDOW_ID].wtctx = 0; + } DestroyWindow(windows[MAIN_WINDOW_ID].hWnd); } + +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); + } + + if (context_vulkan) + memdelete(context_vulkan); + } +#endif } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 5cd240ffb0..c64a1b3b09 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -33,9 +33,9 @@ #include "servers/display_server.h" -#include "core/input/input_filter.h" +#include "core/config/project_settings.h" +#include "core/input/input.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "crash_handler_windows.h" #include "drivers/unix/ip_unix.h" #include "drivers/wasapi/audio_driver_wasapi.h" @@ -66,6 +66,87 @@ #include <windows.h> #include <windowsx.h> +// WinTab API +#define WT_PACKET 0x7FF0 +#define WT_PROXIMITY 0x7FF5 +#define WT_INFOCHANGE 0x7FF6 +#define WT_CSRCHANGE 0x7FF7 + +#define WTI_DEFSYSCTX 4 +#define WTI_DEVICES 100 +#define DVC_NPRESSURE 15 +#define DVC_TPRESSURE 16 +#define DVC_ORIENTATION 17 +#define DVC_ROTATION 18 + +#define CXO_MESSAGES 0x0004 +#define PK_NORMAL_PRESSURE 0x0400 +#define PK_TANGENT_PRESSURE 0x0800 +#define PK_ORIENTATION 0x1000 + +typedef struct tagLOGCONTEXTW { + WCHAR lcName[40]; + UINT lcOptions; + UINT lcStatus; + UINT lcLocks; + UINT lcMsgBase; + UINT lcDevice; + UINT lcPktRate; + DWORD lcPktData; + DWORD lcPktMode; + DWORD lcMoveMask; + DWORD lcBtnDnMask; + DWORD lcBtnUpMask; + LONG lcInOrgX; + LONG lcInOrgY; + LONG lcInOrgZ; + LONG lcInExtX; + LONG lcInExtY; + LONG lcInExtZ; + LONG lcOutOrgX; + LONG lcOutOrgY; + LONG lcOutOrgZ; + LONG lcOutExtX; + LONG lcOutExtY; + LONG lcOutExtZ; + DWORD lcSensX; + DWORD lcSensY; + DWORD lcSensZ; + BOOL lcSysMode; + int lcSysOrgX; + int lcSysOrgY; + int lcSysExtX; + int lcSysExtY; + DWORD lcSysSensX; + DWORD lcSysSensY; +} LOGCONTEXTW; + +typedef struct tagAXIS { + LONG axMin; + LONG axMax; + UINT axUnits; + DWORD axResolution; +} AXIS; + +typedef struct tagORIENTATION { + int orAzimuth; + int orAltitude; + int orTwist; +} ORIENTATION; + +typedef struct tagPACKET { + int pkNormalPressure; + int pkTangentPressure; + ORIENTATION pkOrientation; +} PACKET; + +typedef HANDLE(WINAPI *WTOpenPtr)(HWND p_window, LOGCONTEXTW *p_ctx, BOOL p_enable); +typedef BOOL(WINAPI *WTClosePtr)(HANDLE p_ctx); +typedef UINT(WINAPI *WTInfoPtr)(UINT p_category, UINT p_index, LPVOID p_output); +typedef BOOL(WINAPI *WTPacketPtr)(HANDLE p_ctx, UINT p_param, LPVOID p_packets); +typedef BOOL(WINAPI *WTEnablePtr)(HANDLE p_ctx, BOOL p_enable); + +// Windows Ink API #ifndef POINTER_STRUCTURES #define POINTER_STRUCTURES @@ -75,6 +156,22 @@ typedef UINT32 POINTER_FLAGS; typedef UINT32 PEN_FLAGS; typedef UINT32 PEN_MASK; +#ifndef PEN_MASK_PRESSURE +#define PEN_MASK_PRESSURE 0x00000001 +#endif + +#ifndef PEN_MASK_TILT_X +#define PEN_MASK_TILT_X 0x00000004 +#endif + +#ifndef PEN_MASK_TILT_Y +#define PEN_MASK_TILT_Y 0x00000008 +#endif + +#ifndef POINTER_MESSAGE_FLAG_FIRSTBUTTON +#define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010 +#endif + enum tagPOINTER_INPUT_TYPE { PT_POINTER = 0x00000001, PT_TOUCH = 0x00000002, @@ -128,6 +225,18 @@ typedef struct tagPOINTER_PEN_INFO { #endif //POINTER_STRUCTURES +#ifndef WM_POINTERUPDATE +#define WM_POINTERUPDATE 0x0245 +#endif + +#ifndef WM_POINTERENTER +#define WM_POINTERENTER 0x0249 +#endif + +#ifndef WM_POINTERLEAVE +#define WM_POINTERLEAVE 0x024A +#endif + typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type); typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info); @@ -155,9 +264,23 @@ class DisplayServerWindows : public DisplayServer { _THREAD_SAFE_CLASS_ +public: + // WinTab API + static bool wintab_available; + static WTOpenPtr wintab_WTOpen; + static WTClosePtr wintab_WTClose; + static WTInfoPtr wintab_WTInfo; + static WTPacketPtr wintab_WTPacket; + static WTEnablePtr wintab_WTEnable; + + // Windows Ink API + static bool winink_available; static GetPointerTypePtr win8p_GetPointerType; static GetPointerPenInfoPtr win8p_GetPointerPenInfo; + void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver); + +private: void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap); enum { @@ -165,7 +288,6 @@ class DisplayServerWindows : public DisplayServer { }; struct KeyEvent { - WindowID window_id; bool alt, shift, control, meta; UINT uMsg; @@ -195,11 +317,14 @@ class DisplayServerWindows : public DisplayServer { int pressrc; HINSTANCE hInstance; // Holds The Instance Of The Application String rendering_driver; + bool app_focused = false; struct WindowData { HWND hWnd; //layered window + Vector<Vector2> mpath; + bool preserve_window_size = false; bool pre_fs_valid = false; RECT pre_fs_rect; @@ -214,6 +339,17 @@ class DisplayServerWindows : public DisplayServer { bool no_focus = false; bool window_has_focus = false; + HANDLE wtctx; + LOGCONTEXTW wtlc; + int min_pressure; + int max_pressure; + bool tilt_supported; + bool block_mm = false; + + int last_pressure_update; + float last_pressure; + Vector2 last_tilt; + HBITMAP hBitmap; //DIB section for layered window uint8_t *dib_data = nullptr; Size2 dib_size; @@ -269,6 +405,7 @@ class DisplayServerWindows : public DisplayServer { uint32_t last_button_state = 0; bool use_raw_input = false; bool drop_events = false; + bool in_dispatch_input_event = false; bool console_visible = false; WNDCLASSEXW wc; @@ -281,6 +418,7 @@ class DisplayServerWindows : public DisplayServer { void _touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx); void _update_window_style(WindowID p_window, bool p_repaint = true, bool p_maximized = false); + void _update_window_mouse_passthrough(WindowID p_window); void _update_real_mouse_position(WindowID p_window); @@ -325,6 +463,7 @@ public: virtual Vector<DisplayServer::WindowID> get_window_list() const; virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); + virtual void show_window(WindowID p_window); virtual void delete_sub_window(WindowID p_window); virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; @@ -341,6 +480,7 @@ public: virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID); virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const; virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID); @@ -385,11 +525,15 @@ public: virtual CursorShape cursor_get_shape() const; virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()); - virtual bool get_swap_ok_cancel(); + virtual bool get_swap_cancel_ok(); virtual void enable_for_stealing_focus(OS::ProcessID pid); - virtual LatinKeyboardVariant get_latin_keyboard_variant() const; + virtual int keyboard_get_layout_count() const; + virtual int keyboard_get_current_layout() const; + virtual void keyboard_set_current_layout(int p_index); + virtual String keyboard_get_layout_language(int p_index) const; + virtual String keyboard_get_layout_name(int p_index) const; virtual void process_events(); diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index d63067587c..c2436e8b64 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -38,7 +38,6 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); class EditorExportPlatformWindows : public EditorExportPlatformPC { - void _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path); Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path); @@ -331,7 +330,6 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p } void register_windows_exporter() { - EDITOR_DEF("export/windows/rcedit", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/rcedit", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); #ifdef WINDOWS_ENABLED @@ -366,7 +364,6 @@ void register_windows_exporter() { } static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { - // Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); @@ -408,7 +405,6 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, bool found = false; for (int i = 0; i < num_sections; ++i) { - int64_t section_header_pos = section_table_pos + i * 40; f->seek(section_header_pos); diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis index 593557cc69..1f625cfb77 100644 --- a/platform/windows/godot.natvis +++ b/platform/windows/godot.natvis @@ -19,7 +19,7 @@ </ArrayItems> </Expand> </Type> - + <Type Name="List<*>"> <Expand> <Item Name="[size]">_data ? (_data->size_cache) : 0</Item> @@ -49,7 +49,7 @@ <DisplayString Condition="type == Variant::QUAT">{*(Quat *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::COLOR">{*(Color *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::NODE_PATH">{*(NodePath *)_data._mem}</DisplayString> - <DisplayString Condition="type == Variant::_RID">{*(RID *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::RID">{*(RID *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::OBJECT">{*(Object *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::DICTIONARY">{*(Dictionary *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::ARRAY">{*(Array *)_data._mem}</DisplayString> @@ -62,7 +62,7 @@ <DisplayString Condition="type == Variant::POOL_COLOR_ARRAY">{*(PoolColorArray *)_data._mem}</DisplayString> <StringView Condition="type == Variant::STRING && ((String *)(_data._mem))->_cowdata._ptr">((String *)(_data._mem))->_cowdata._ptr,su</StringView> - + <Expand> <Item Name="[value]" Condition="type == Variant::BOOL">_data._bool</Item> <Item Name="[value]" Condition="type == Variant::INT">_data._int</Item> @@ -79,7 +79,7 @@ <Item Name="[value]" Condition="type == Variant::QUAT">*(Quat *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::COLOR">*(Color *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::NODE_PATH">*(NodePath *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::_RID">*(RID *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::RID">*(RID *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::OBJECT">*(Object *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::DICTIONARY">*(Dictionary *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::ARRAY">*(Array *)_data._mem</Item> @@ -143,7 +143,7 @@ <Item Name="alpha">a</Item> </Expand> </Type> - + <Type Name="Node" Inheritable="false"> <Expand> <Item Name="Object">(Object*)this</Item> diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 2aa928c2a7..add559a717 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -136,7 +136,6 @@ char *wc_to_utf8(const wchar_t *wc) { } int widechar_main(int argc, wchar_t **argv) { - OS_Windows os(nullptr); setlocale(LC_CTYPE, ""); @@ -147,6 +146,8 @@ int widechar_main(int argc, wchar_t **argv) { argv_utf8[i] = wc_to_utf8(argv[i]); } + TEST_MAIN_PARAM_OVERRIDE(argc, argv_utf8) + Error err = Main::setup(argv_utf8[0], argc - 1, &argv_utf8[1]); if (err != OK) { @@ -187,10 +188,12 @@ int _main() { return result; } -int main(int _argc, char **_argv) { +int main(int argc, char **argv) { + // override the arguments for the test handler / if symbol is provided + // TEST_MAIN_OVERRIDE + // _argc and _argv are ignored // we are going to use the WideChar version of them instead - #ifdef CRASH_HANDLER_EXCEPTION __try { return _main(); diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index 437c3b733d..2a5c8a7763 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -33,10 +33,6 @@ #include <oleauto.h> #include <wbemidl.h> -#ifndef __GNUC__ -#define __builtin_bswap32 _byteswap_ulong -#endif - #if defined(__GNUC__) // Workaround GCC warning from -Wcast-function-type. #define GetProcAddress (void *)GetProcAddress @@ -45,6 +41,7 @@ DWORD WINAPI _xinput_get_state(DWORD dwUserIndex, XINPUT_STATE *pState) { return ERROR_DEVICE_NOT_CONNECTED; } + DWORD WINAPI _xinput_set_state(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) { return ERROR_DEVICE_NOT_CONNECTED; } @@ -53,8 +50,7 @@ JoypadWindows::JoypadWindows() { } JoypadWindows::JoypadWindows(HWND *hwnd) { - - input = InputFilter::get_singleton(); + input = Input::get_singleton(); hWnd = hwnd; joypad_count = 0; dinput = nullptr; @@ -67,27 +63,31 @@ JoypadWindows::JoypadWindows(HWND *hwnd) { for (int i = 0; i < JOYPADS_MAX; i++) attached_joypads[i] = false; - HRESULT result; - result = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, nullptr); - if (FAILED(result)) { - printf("failed init DINPUT: %ld\n", result); + HRESULT result = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, nullptr); + if (result == DI_OK) { + probe_joypads(); + } else { + ERR_PRINT("Couldn't initialize DirectInput. Error: " + itos(result)); + if (result == DIERR_OUTOFMEMORY) { + ERR_PRINT("The Windows DirectInput subsystem could not allocate sufficient memory."); + ERR_PRINT("Rebooting your PC may solve this issue."); + } + // Ensure dinput is still a nullptr. + dinput = nullptr; } - probe_joypads(); } JoypadWindows::~JoypadWindows() { - close_joypad(); - dinput->Release(); + if (dinput) { + dinput->Release(); + } unload_xinput(); } bool JoypadWindows::have_device(const GUID &p_guid) { - for (int i = 0; i < JOYPADS_MAX; i++) { - if (d_joypads[i].guid == p_guid) { - d_joypads[i].confirmed = true; return true; } @@ -97,7 +97,6 @@ bool JoypadWindows::have_device(const GUID &p_guid) { // adapted from SDL2, works a lot better than the MSDN version bool JoypadWindows::is_xinput_device(const GUID *p_guid) { - static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; @@ -112,14 +111,14 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) { return false; } dev_list = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count); - if (!dev_list) return false; + if (!dev_list) + return false; if (GetRawInputDeviceList(dev_list, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) { free(dev_list); return false; } for (unsigned int i = 0; i < dev_list_count; i++) { - RID_DEVICE_INFO rdi; char dev_name[128]; UINT rdiSize = sizeof(rdi); @@ -131,7 +130,6 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) { (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == (LONG)p_guid->Data1) && (GetRawInputDeviceInfoA(dev_list[i].hDevice, RIDI_DEVICENAME, &dev_name, &nameSize) != (UINT)-1) && (strstr(dev_name, "IG_") != nullptr)) { - free(dev_list); return true; } @@ -141,15 +139,15 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) { } bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) { - + ERR_FAIL_NULL_V_MSG(dinput, false, "DirectInput not initialized. Rebooting your PC may solve this issue."); HRESULT hr; int num = input->get_unused_joy_id(); if (have_device(instance->guidInstance) || num == -1) return false; - d_joypads[joypad_count] = dinput_gamepad(); - dinput_gamepad *joy = &d_joypads[joypad_count]; + d_joypads[num] = dinput_gamepad(); + dinput_gamepad *joy = &d_joypads[num]; const DWORD devtype = (instance->dwDevType & 0xFF); @@ -165,12 +163,16 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) { const GUID &guid = instance->guidProduct; char uid[128]; - sprintf_s(uid, "%08lx%04hx%04hx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", - __builtin_bswap32(guid.Data1), guid.Data2, guid.Data3, - guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], - guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); - id_to_change = joypad_count; + ERR_FAIL_COND_V_MSG(memcmp(&guid.Data4[2], "PIDVID", 6), false, "DirectInput device not recognised."); + WORD type = BSWAP16(0x03); + WORD vendor = BSWAP16(LOWORD(guid.Data1)); + WORD product = BSWAP16(HIWORD(guid.Data1)); + WORD version = 0; + sprintf_s(uid, "%04x%04x%04x%04x%04x%04x%04x%04x", type, 0, vendor, 0, product, 0, version, 0); + + id_to_change = num; + slider_count = 0; joy->di_joy->SetDataFormat(&c_dfDIJoystick2); joy->di_joy->SetCooperativeLevel(*hWnd, DISCL_FOREGROUND); @@ -188,13 +190,11 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) { } void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id) { - if (ob->dwType & DIDFT_AXIS) { - HRESULT res; DIPROPRANGE prop_range; DIPROPDWORD dilong; - DWORD ofs; + LONG ofs; if (ob->guidType == GUID_XAxis) ofs = DIJOFS_X; else if (ob->guidType == GUID_YAxis) @@ -207,9 +207,14 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_ ofs = DIJOFS_RY; else if (ob->guidType == GUID_RzAxis) ofs = DIJOFS_RZ; - else if (ob->guidType == GUID_Slider) - ofs = DIJOFS_SLIDER(0); - else + else if (ob->guidType == GUID_Slider) { + if (slider_count < 2) { + ofs = DIJOFS_SLIDER(slider_count); + slider_count++; + } else { + return; + } + } else return; prop_range.diph.dwSize = sizeof(DIPROPRANGE); prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER); @@ -239,7 +244,6 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_ } BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context) { - JoypadWindows *self = (JoypadWindows *)p_context; if (self->is_xinput_device(&p_instance->guidProduct)) { return DIENUM_CONTINUE; @@ -249,7 +253,6 @@ BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, vo } BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *instance, void *context) { - JoypadWindows *self = (JoypadWindows *)context; self->setup_joypad_object(instance, self->id_to_change); @@ -257,17 +260,15 @@ BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *insta } void JoypadWindows::close_joypad(int id) { - if (id == -1) { - for (int i = 0; i < JOYPADS_MAX; i++) { - close_joypad(i); } return; } - if (!d_joypads[id].attached) return; + if (!d_joypads[id].attached) + return; d_joypads[id].di_joy->Unacquire(); d_joypads[id].di_joy->Release(); @@ -279,18 +280,15 @@ void JoypadWindows::close_joypad(int id) { } void JoypadWindows::probe_joypads() { - + ERR_FAIL_NULL_MSG(dinput, "DirectInput not initialized. Rebooting your PC may solve this issue."); DWORD dwResult; for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { - ZeroMemory(&x_joypads[i].state, sizeof(XINPUT_STATE)); dwResult = xinput_get_state(i, &x_joypads[i].state); if (dwResult == ERROR_SUCCESS) { - int id = input->get_unused_joy_id(); if (id != -1 && !x_joypads[i].attached) { - x_joypads[i].attached = true; x_joypads[i].id = id; x_joypads[i].ff_timestamp = 0; @@ -300,7 +298,6 @@ void JoypadWindows::probe_joypads() { input->joy_connection_changed(id, true, "XInput Gamepad", "__XINPUT_DEVICE__"); } } else if (x_joypads[i].attached) { - x_joypads[i].attached = false; attached_joypads[x_joypads[i].id] = false; input->joy_connection_changed(x_joypads[i].id, false, ""); @@ -308,27 +305,22 @@ void JoypadWindows::probe_joypads() { } for (int i = 0; i < joypad_count; i++) { - d_joypads[i].confirmed = false; } dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumCallback, this, DIEDFL_ATTACHEDONLY); for (int i = 0; i < joypad_count; i++) { - if (!d_joypads[i].confirmed) { - close_joypad(i); } } } void JoypadWindows::process_joypads() { - HRESULT hr; for (int i = 0; i < XUSER_MAX_COUNT; i++) { - xinput_gamepad &joy = x_joypads[i]; if (!joy.attached) { continue; @@ -337,20 +329,18 @@ void JoypadWindows::process_joypads() { xinput_get_state(i, &joy.state); if (joy.state.dwPacketNumber != joy.last_packet) { - int button_mask = XINPUT_GAMEPAD_DPAD_UP; for (int j = 0; j <= 16; j++) { - input->joy_button(joy.id, j, joy.state.Gamepad.wButtons & button_mask); button_mask = button_mask * 2; } - input->joy_axis(joy.id, JOY_AXIS_0, axis_correct(joy.state.Gamepad.sThumbLX, true)); - input->joy_axis(joy.id, JOY_AXIS_1, axis_correct(joy.state.Gamepad.sThumbLY, true, false, true)); - input->joy_axis(joy.id, JOY_AXIS_2, axis_correct(joy.state.Gamepad.sThumbRX, true)); - input->joy_axis(joy.id, JOY_AXIS_3, axis_correct(joy.state.Gamepad.sThumbRY, true, false, true)); - input->joy_axis(joy.id, JOY_AXIS_4, axis_correct(joy.state.Gamepad.bLeftTrigger, true, true)); - input->joy_axis(joy.id, JOY_AXIS_5, axis_correct(joy.state.Gamepad.bRightTrigger, true, true)); + input->joy_axis(joy.id, JOY_AXIS_LEFT_X, axis_correct(joy.state.Gamepad.sThumbLX, true)); + input->joy_axis(joy.id, JOY_AXIS_LEFT_Y, axis_correct(joy.state.Gamepad.sThumbLY, true, false, true)); + input->joy_axis(joy.id, JOY_AXIS_RIGHT_X, axis_correct(joy.state.Gamepad.sThumbRX, true)); + input->joy_axis(joy.id, JOY_AXIS_RIGHT_Y, axis_correct(joy.state.Gamepad.sThumbRY, true, false, true)); + input->joy_axis(joy.id, JOY_AXIS_TRIGGER_LEFT, axis_correct(joy.state.Gamepad.bLeftTrigger, true, true)); + input->joy_axis(joy.id, JOY_AXIS_TRIGGER_RIGHT, axis_correct(joy.state.Gamepad.bRightTrigger, true, true)); joy.last_packet = joy.state.dwPacketNumber; } uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id); @@ -370,7 +360,6 @@ void JoypadWindows::process_joypads() { } for (int i = 0; i < JOYPADS_MAX; i++) { - dinput_gamepad *joy = &d_joypads[i]; if (!joy->attached) @@ -391,18 +380,13 @@ void JoypadWindows::process_joypads() { post_hat(joy->id, js.rgdwPOV[0]); for (int j = 0; j < 128; j++) { - if (js.rgbButtons[j] & 0x80) { - if (!joy->last_buttons[j]) { - input->joy_button(joy->id, j, true); joy->last_buttons[j] = true; } } else { - if (joy->last_buttons[j]) { - input->joy_button(joy->id, j, false); joy->last_buttons[j] = false; } @@ -410,12 +394,11 @@ void JoypadWindows::process_joypads() { } // on mingw, these constants are not constants - int count = 6; - unsigned int axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ }; - int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz }; + int count = 8; + LONG axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ, (LONG)DIJOFS_SLIDER(0), (LONG)DIJOFS_SLIDER(1) }; + int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz, js.rglSlider[0], js.rglSlider[1] }; for (int j = 0; j < joy->joy_axis.size(); j++) { - for (int k = 0; k < count; k++) { if (joy->joy_axis[j] == axes[k]) { input->joy_axis(joy->id, j, axis_correct(values[k])); @@ -428,7 +411,6 @@ void JoypadWindows::process_joypads() { } void JoypadWindows::post_hat(int p_device, DWORD p_dpad) { - int dpad_val = 0; // Should be -1 when centered, but according to docs: @@ -436,53 +418,43 @@ void JoypadWindows::post_hat(int p_device, DWORD p_dpad) { // BOOL POVCentered = (LOWORD(dwPOV) == 0xFFFF);" // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416628(v%3Dvs.85)#remarks if (LOWORD(p_dpad) == 0xFFFF) { - dpad_val = InputFilter::HAT_MASK_CENTER; + dpad_val = Input::HAT_MASK_CENTER; } if (p_dpad == 0) { - - dpad_val = InputFilter::HAT_MASK_UP; + dpad_val = Input::HAT_MASK_UP; } else if (p_dpad == 4500) { - - dpad_val = (InputFilter::HAT_MASK_UP | InputFilter::HAT_MASK_RIGHT); + dpad_val = (Input::HAT_MASK_UP | Input::HAT_MASK_RIGHT); } else if (p_dpad == 9000) { - - dpad_val = InputFilter::HAT_MASK_RIGHT; + dpad_val = Input::HAT_MASK_RIGHT; } else if (p_dpad == 13500) { - - dpad_val = (InputFilter::HAT_MASK_RIGHT | InputFilter::HAT_MASK_DOWN); + dpad_val = (Input::HAT_MASK_RIGHT | Input::HAT_MASK_DOWN); } else if (p_dpad == 18000) { - - dpad_val = InputFilter::HAT_MASK_DOWN; + dpad_val = Input::HAT_MASK_DOWN; } else if (p_dpad == 22500) { - - dpad_val = (InputFilter::HAT_MASK_DOWN | InputFilter::HAT_MASK_LEFT); + dpad_val = (Input::HAT_MASK_DOWN | Input::HAT_MASK_LEFT); } else if (p_dpad == 27000) { - - dpad_val = InputFilter::HAT_MASK_LEFT; + dpad_val = Input::HAT_MASK_LEFT; } else if (p_dpad == 31500) { - - dpad_val = (InputFilter::HAT_MASK_LEFT | InputFilter::HAT_MASK_UP); + dpad_val = (Input::HAT_MASK_LEFT | Input::HAT_MASK_UP); } input->joy_hat(p_device, dpad_val); }; -InputFilter::JoyAxis JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { - - InputFilter::JoyAxis jx; +Input::JoyAxis JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { + Input::JoyAxis jx; if (Math::abs(p_val) < MIN_JOY_AXIS) { jx.min = p_trigger ? 0 : -1; jx.value = 0.0f; return jx; } if (p_xinput) { - if (p_trigger) { jx.min = 0; jx.value = (float)p_val / MAX_TRIGGER; @@ -532,7 +504,6 @@ void JoypadWindows::joypad_vibration_stop_xinput(int p_device, uint64_t p_timest } void JoypadWindows::load_xinput() { - xinput_get_state = &_xinput_get_state; xinput_set_state = &_xinput_set_state; xinput_dll = LoadLibrary("XInput1_4.dll"); @@ -559,9 +530,7 @@ void JoypadWindows::load_xinput() { } void JoypadWindows::unload_xinput() { - if (xinput_dll) { - FreeLibrary((HMODULE)xinput_dll); } } diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h index 0db789c335..223b44fcd6 100644 --- a/platform/windows/joypad_windows.h +++ b/platform/windows/joypad_windows.h @@ -70,7 +70,6 @@ private: }; struct dinput_gamepad { - int id; bool attached; bool confirmed; @@ -78,7 +77,7 @@ private: DWORD last_pad; LPDIRECTINPUTDEVICE8 di_joy; - List<DWORD> joy_axis; + List<LONG> joy_axis; GUID guid; dinput_gamepad() { @@ -93,7 +92,6 @@ private: }; struct xinput_gamepad { - int id; bool attached; bool vibrating; @@ -117,9 +115,10 @@ private: HWND *hWnd; HANDLE xinput_dll; LPDIRECTINPUT8 dinput; - InputFilter *input; + Input *input; int id_to_change; + int slider_count; int joypad_count; bool attached_joypads[JOYPADS_MAX]; dinput_gamepad d_joypads[JOYPADS_MAX]; @@ -141,7 +140,7 @@ private: void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp); - InputFilter::JoyAxis axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; + Input::JoyAxis axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; XInputGetState_t xinput_get_state; XInputSetState_t xinput_set_state; }; diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp index da63e92622..25eff7df57 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -33,13 +33,11 @@ #include <stdio.h> struct _WinTranslatePair { - unsigned int keysym; unsigned int keycode; }; static _WinTranslatePair _vk_to_keycode[] = { - { KEY_BACKSPACE, VK_BACK }, // (0x08) // backspace { KEY_TAB, VK_TAB }, //(0x09) @@ -130,7 +128,7 @@ static _WinTranslatePair _vk_to_keycode[] = { { KEY_MASK_META, VK_LWIN }, //(0x5B) { KEY_MASK_META, VK_RWIN }, //(0x5C) - //VK_APPS (0x5D) + { KEY_MENU, VK_APPS }, //(0x5D) { KEY_STANDBY, VK_SLEEP }, //(0x5F) { KEY_KP_0, VK_NUMPAD0 }, //(0x60) { KEY_KP_1, VK_NUMPAD1 }, //(0x61) @@ -239,7 +237,6 @@ VK_OEM_CLEAR (0xFE) */ static _WinTranslatePair _scancode_to_keycode[] = { - { KEY_ESCAPE, 0x01 }, { KEY_1, 0x02 }, { KEY_2, 0x03 }, @@ -337,9 +334,7 @@ static _WinTranslatePair _scancode_to_keycode[] = { }; unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) { - for (int i = 0; _vk_to_keycode[i].keysym != KEY_UNKNOWN; i++) { - if (_vk_to_keycode[i].keycode == p_code) { //printf("outcode: %x\n",_vk_to_keycode[i].keysym); @@ -353,7 +348,6 @@ unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) { 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; @@ -415,3 +409,16 @@ unsigned int KeyMappingWindows::get_scansym(unsigned int p_code, bool p_extended return keycode; } + +bool KeyMappingWindows::is_extended_key(unsigned int p_code) { + return p_code == VK_INSERT || + p_code == VK_DELETE || + p_code == VK_HOME || + p_code == VK_END || + p_code == VK_PRIOR || + p_code == VK_NEXT || + p_code == VK_LEFT || + p_code == VK_UP || + p_code == VK_RIGHT || + p_code == VK_DOWN; +} diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h index 3361ad397f..f64f1feb9f 100644 --- a/platform/windows/key_mapping_windows.h +++ b/platform/windows/key_mapping_windows.h @@ -38,12 +38,12 @@ #include <winuser.h> class KeyMappingWindows { - - KeyMappingWindows(){}; + KeyMappingWindows() {} public: static unsigned int get_keysym(unsigned int p_code); static unsigned int get_scansym(unsigned int p_code, bool p_extended); + static bool is_extended_key(unsigned int p_code); }; #endif // KEY_MAPPING_WINDOWS_H diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 0a67a591b7..646bc3aa4c 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -80,12 +80,11 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #ifdef DEBUG_ENABLED static String format_error_message(DWORD id) { - LPWSTR messageBuffer = nullptr; size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr); - String msg = "Error " + itos(id) + ": " + String(messageBuffer, size); + String msg = "Error " + itos(id) + ": " + String::utf16((const char16_t *)messageBuffer, size); LocalFree(messageBuffer); @@ -94,7 +93,6 @@ static String format_error_message(DWORD id) { #endif // DEBUG_ENABLED void RedirectIOToConsole() { - int hConHandle; intptr_t lStdHandle; @@ -109,15 +107,11 @@ void RedirectIOToConsole() { // set the screen buffer to be big enough to let us scroll text - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), - - &coninfo); + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); coninfo.dwSize.Y = MAX_CONSOLE_LINES; - SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), - - coninfo.dwSize); + SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); // redirect unbuffered STDOUT to the console @@ -175,12 +169,10 @@ BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) { } void OS_Windows::initialize_debugging() { - SetConsoleCtrlHandler(HandlerRoutine, TRUE); } void OS_Windows::initialize() { - crash_handler.initialize(); //RedirectIOToConsole(); @@ -212,24 +204,28 @@ void OS_Windows::initialize() { process_map = memnew((Map<ProcessID, ProcessInfo>)); + // Add current Godot PID to the list of known PIDs + ProcessInfo current_pi = {}; + PROCESS_INFORMATION current_pi_pi = {}; + current_pi.pi = current_pi_pi; + current_pi.pi.hProcess = GetCurrentProcess(); + process_map->insert(GetCurrentProcessId(), current_pi); + IP_Unix::make_default(); main_loop = nullptr; } void OS_Windows::delete_main_loop() { - if (main_loop) memdelete(main_loop); main_loop = nullptr; } void OS_Windows::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; } void OS_Windows::finalize() { - #ifdef WINMIDI_ENABLED driver_midi.close(); #endif @@ -241,7 +237,6 @@ void OS_Windows::finalize() { } void OS_Windows::finalize_core() { - timeEndPeriod(1); memdelete(process_map); @@ -249,8 +244,7 @@ void OS_Windows::finalize_core() { } Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { - - String path = p_path; + String path = p_path.replace("/", "\\"); if (!FileAccess::exists(path)) { //this code exists so gdnative can load .dll files from within the executable path @@ -267,10 +261,10 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han DLL_DIRECTORY_COOKIE cookie = nullptr; if (p_also_set_library_path && has_dll_directory_api) { - cookie = add_dll_directory(path.get_base_dir().c_str()); + cookie = add_dll_directory((LPCWSTR)(path.get_base_dir().utf16().get_data())); } - p_library_handle = (void *)LoadLibraryExW(path.c_str(), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); + p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + format_error_message(GetLastError()) + "."); if (cookie) { @@ -300,12 +294,10 @@ Error OS_Windows::get_dynamic_library_symbol_handle(void *p_library_handle, cons } String OS_Windows::get_name() const { - return "Windows"; } OS::Date OS_Windows::get_date(bool utc) const { - SYSTEMTIME systemtime; if (utc) GetSystemTime(&systemtime); @@ -320,8 +312,8 @@ OS::Date OS_Windows::get_date(bool utc) const { date.dst = false; return date; } -OS::Time OS_Windows::get_time(bool utc) const { +OS::Time OS_Windows::get_time(bool utc) const { SYSTEMTIME systemtime; if (utc) GetSystemTime(&systemtime); @@ -354,107 +346,91 @@ OS::TimeZoneInfo OS_Windows::get_time_zone_info() const { return ret; } -uint64_t OS_Windows::get_unix_time() const { - - FILETIME ft; - SYSTEMTIME st; - GetSystemTime(&st); - SystemTimeToFileTime(&st, &ft); - - SYSTEMTIME ep; - ep.wYear = 1970; - ep.wMonth = 1; - ep.wDayOfWeek = 4; - ep.wDay = 1; - ep.wHour = 0; - ep.wMinute = 0; - ep.wSecond = 0; - ep.wMilliseconds = 0; - FILETIME fep; - SystemTimeToFileTime(&ep, &fep); - - // Type punning through unions (rather than pointer cast) as per: - // https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime#remarks - ULARGE_INTEGER ft_punning; - ft_punning.LowPart = ft.dwLowDateTime; - ft_punning.HighPart = ft.dwHighDateTime; - - ULARGE_INTEGER fep_punning; - fep_punning.LowPart = fep.dwLowDateTime; - fep_punning.HighPart = fep.dwHighDateTime; - - return (ft_punning.QuadPart - fep_punning.QuadPart) / 10000000; -}; - -uint64_t OS_Windows::get_system_time_secs() const { - - return get_system_time_msecs() / 1000; -} - -uint64_t OS_Windows::get_system_time_msecs() const { - - const uint64_t WINDOWS_TICK = 10000; - const uint64_t MSEC_TO_UNIX_EPOCH = 11644473600000LL; +double OS_Windows::get_unix_time() const { + // 1 Windows tick is 100ns + const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000; + const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL; SYSTEMTIME st; GetSystemTime(&st); FILETIME ft; SystemTimeToFileTime(&st, &ft); - uint64_t ret; - ret = ft.dwHighDateTime; - ret <<= 32; - ret |= ft.dwLowDateTime; + uint64_t ticks_time; + ticks_time = ft.dwHighDateTime; + ticks_time <<= 32; + ticks_time |= ft.dwLowDateTime; - return (uint64_t)(ret / WINDOWS_TICK - MSEC_TO_UNIX_EPOCH); + return (double)(ticks_time - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND; } void OS_Windows::delay_usec(uint32_t p_usec) const { - if (p_usec < 1000) Sleep(1); else Sleep(p_usec / 1000); } -uint64_t OS_Windows::get_ticks_usec() const { +uint64_t OS_Windows::get_ticks_usec() const { uint64_t ticks; - uint64_t time; + // This is the number of clock ticks since start if (!QueryPerformanceCounter((LARGE_INTEGER *)&ticks)) ticks = (UINT64)timeGetTime(); + // Divide by frequency to get the time in seconds - time = ticks * 1000000L / ticks_per_second; + // original calculation shown below is subject to overflow + // with high ticks_per_second and a number of days since the last reboot. + // time = ticks * 1000000L / ticks_per_second; + + // we can prevent this by either using 128 bit math + // or separating into a calculation for seconds, and the fraction + uint64_t seconds = ticks / ticks_per_second; + + // compiler will optimize these two into one divide + uint64_t leftover = ticks % ticks_per_second; + + // remainder + uint64_t time = (leftover * 1000000L) / ticks_per_second; + + // seconds + time += seconds * 1000000L; + // Subtract the time at game start to get // the time since the game started time -= ticks_start; return time; } +String OS_Windows::_quote_command_line_argument(const String &p_text) const { + for (int i = 0; i < p_text.size(); i++) { + char32_t c = p_text[i]; + if (c == ' ' || c == '&' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '=' || c == ';' || c == '!' || c == '\'' || c == '+' || c == ',' || c == '`' || c == '~') { + return "\"" + p_text + "\""; + } + } + return p_text; +} + Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { + String path = p_path.replace("/", "\\"); if (p_blocking && r_pipe) { - - String argss; - argss = "\"\"" + p_path + "\""; - + String argss = _quote_command_line_argument(path); for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) { - - argss += " \"" + E->get() + "\""; + argss += " " + _quote_command_line_argument(E->get()); } - argss += "\""; - if (read_stderr) { argss += " 2>&1"; // Read stderr too } + // Note: _wpopen is calling command as "cmd.exe /c argss", instead of executing it directly, add extra quotes around full command, to prevent it from stripping quotes in the command. + argss = _quote_command_line_argument(argss); - FILE *f = _wpopen(argss.c_str(), L"r"); - + FILE *f = _wpopen((LPCWSTR)(argss.utf16().get_data()), L"r"); ERR_FAIL_COND_V(!f, ERR_CANT_OPEN); char buf[65535]; while (fgets(buf, 65535, f)) { - if (p_pipe_mutex) { p_pipe_mutex->lock(); } @@ -465,20 +441,19 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, } int rv = _pclose(f); - if (r_exitcode) + if (r_exitcode) { *r_exitcode = rv; + } return OK; } - String cmdline = "\"" + p_path + "\""; + String cmdline = _quote_command_line_argument(path); const List<String>::Element *I = p_arguments.front(); while (I) { - - cmdline += " \"" + I->get() + "\""; - + cmdline += " " + _quote_command_line_argument(I->get()); I = I->next(); - }; + } ProcessInfo pi; ZeroMemory(&pi.si, sizeof(pi.si)); @@ -486,34 +461,31 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, ZeroMemory(&pi.pi, sizeof(pi.pi)); LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; - Vector<CharType> modstr; //windows wants to change this no idea why - modstr.resize(cmdline.size()); - for (int i = 0; i < cmdline.size(); i++) - modstr.write[i] = cmdline[i]; - int ret = CreateProcessW(nullptr, modstr.ptrw(), nullptr, nullptr, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi); + Char16String modstr = cmdline.utf16(); // Windows wants to change this no idea why. + int ret = CreateProcessW(nullptr, (LPWSTR)(modstr.ptrw()), nullptr, nullptr, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi); ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK); if (p_blocking) { - - DWORD ret2 = WaitForSingleObject(pi.pi.hProcess, INFINITE); - if (r_exitcode) + WaitForSingleObject(pi.pi.hProcess, INFINITE); + if (r_exitcode) { + DWORD ret2; + GetExitCodeProcess(pi.pi.hProcess, &ret2); *r_exitcode = ret2; + } CloseHandle(pi.pi.hProcess); CloseHandle(pi.pi.hThread); } else { - ProcessID pid = pi.pi.dwProcessId; if (r_child_id) { *r_child_id = pid; - }; + } process_map->insert(pid, pi); - }; + } return OK; }; Error OS_Windows::kill(const ProcessID &p_pid) { - ERR_FAIL_COND_V(!process_map->has(p_pid), FAILED); const PROCESS_INFORMATION pi = (*process_map)[p_pid].pi; @@ -532,29 +504,26 @@ int OS_Windows::get_process_id() const { } Error OS_Windows::set_cwd(const String &p_cwd) { - - if (_wchdir(p_cwd.c_str()) != 0) + if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0) return ERR_CANT_OPEN; return OK; } String OS_Windows::get_executable_path() const { - - wchar_t bufname[4096]; + WCHAR bufname[4096]; GetModuleFileNameW(nullptr, bufname, 4096); - String s = bufname; + String s = String::utf16((const char16_t *)bufname).replace("\\", "/"); return s; } bool OS_Windows::has_environment(const String &p_var) const { - #ifdef MINGW_ENABLED - return _wgetenv(p_var.c_str()) != nullptr; + return _wgetenv((LPCWSTR)(p_var.utf16().get_data())) != nullptr; #else - wchar_t *env; + WCHAR *env; size_t len; - _wdupenv_s(&env, &len, p_var.c_str()); + _wdupenv_s(&env, &len, (LPCWSTR)(p_var.utf16().get_data())); const bool has_env = env != nullptr; free(env); return has_env; @@ -562,22 +531,19 @@ bool OS_Windows::has_environment(const String &p_var) const { }; String OS_Windows::get_environment(const String &p_var) const { - - wchar_t wval[0x7Fff]; // MSDN says 32767 char is the maximum - int wlen = GetEnvironmentVariableW(p_var.c_str(), wval, 0x7Fff); + WCHAR wval[0x7fff]; // MSDN says 32767 char is the maximum + int wlen = GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), wval, 0x7fff); if (wlen > 0) { - return wval; + return String::utf16((const char16_t *)wval); } return ""; } bool OS_Windows::set_environment(const String &p_var, const String &p_value) const { - - return (bool)SetEnvironmentVariableW(p_var.c_str(), p_value.c_str()); + return (bool)SetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), (LPCWSTR)(p_value.utf16().get_data())); } String OS_Windows::get_stdin_string(bool p_block) { - if (p_block) { char buff[1024]; return fgets(buff, 1024, stdin); @@ -587,13 +553,11 @@ String OS_Windows::get_stdin_string(bool p_block) { } Error OS_Windows::shell_open(String p_uri) { - - ShellExecuteW(nullptr, nullptr, p_uri.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ShellExecuteW(nullptr, nullptr, (LPCWSTR)(p_uri.utf16().get_data()), nullptr, nullptr, SW_SHOWNORMAL); return OK; } String OS_Windows::get_locale() const { - const _WinLocale *wl = &_win_locales[0]; LANGID langid = GetUserDefaultUILanguage(); @@ -602,7 +566,6 @@ String OS_Windows::get_locale() const { int sublang = langid & ~((1 << 9) - 1); while (wl->locale) { - if (wl->main_lang == lang && wl->sublang == SUBLANG_NEUTRAL) neutral = wl->locale; @@ -649,14 +612,12 @@ int OS_Windows::get_processor_count() const { } void OS_Windows::run() { - if (!main_loop) return; main_loop->init(); while (!force_quit) { - DisplayServer::get_singleton()->process_events(); // get rid of pending events if (Main::iteration()) break; @@ -666,12 +627,10 @@ void OS_Windows::run() { } MainLoop *OS_Windows::get_main_loop() const { - return main_loop; } String OS_Windows::get_config_path() const { - if (has_environment("XDG_CONFIG_HOME")) { // unlikely, but after all why not? return get_environment("XDG_CONFIG_HOME"); } else if (has_environment("APPDATA")) { @@ -682,7 +641,6 @@ String OS_Windows::get_config_path() const { } String OS_Windows::get_data_path() const { - if (has_environment("XDG_DATA_HOME")) { return get_environment("XDG_DATA_HOME"); } else { @@ -691,7 +649,6 @@ String OS_Windows::get_data_path() const { } String OS_Windows::get_cache_path() const { - if (has_environment("XDG_CACHE_HOME")) { return get_environment("XDG_CACHE_HOME"); } else if (has_environment("TEMP")) { @@ -703,12 +660,10 @@ String OS_Windows::get_cache_path() const { // Get properly capitalized engine name for system paths String OS_Windows::get_godot_dir_name() const { - return String(VERSION_SHORT_NAME).capitalize(); } String OS_Windows::get_system_dir(SystemDir p_dir) const { - KNOWNFOLDERID id; switch (p_dir) { @@ -741,13 +696,12 @@ String OS_Windows::get_system_dir(SystemDir p_dir) const { PWSTR szPath; HRESULT res = SHGetKnownFolderPath(id, 0, nullptr, &szPath); ERR_FAIL_COND_V(res != S_OK, String()); - String path = String(szPath); + String path = String::utf16((const char16_t *)szPath); CoTaskMemFree(szPath); return path; } String OS_Windows::get_user_data_dir() const { - String appname = get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name")); if (appname != "") { bool use_custom_dir = ProjectSettings::get_singleton()->get("application/config/use_custom_user_dir"); @@ -766,14 +720,12 @@ String OS_Windows::get_user_data_dir() const { } String OS_Windows::get_unique_id() const { - HW_PROFILE_INFO HwProfInfo; ERR_FAIL_COND_V(!GetCurrentHwProfile(&HwProfInfo), ""); - return String(HwProfInfo.szHwProfileGuid); + return String::utf16((const char16_t *)(HwProfInfo.szHwProfileGuid), HW_PROFILE_GUIDLEN); } bool OS_Windows::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; } @@ -787,9 +739,11 @@ bool OS_Windows::is_disable_crash_handler() const { Error OS_Windows::move_to_trash(const String &p_path) { SHFILEOPSTRUCTW sf; - WCHAR *from = new WCHAR[p_path.length() + 2]; - wcscpy_s(from, p_path.length() + 1, p_path.c_str()); - from[p_path.length() + 1] = 0; + + Char16String utf16 = p_path.utf16(); + WCHAR *from = new WCHAR[utf16.length() + 2]; + wcscpy_s(from, utf16.length() + 1, (LPCWSTR)(utf16.get_data())); + from[utf16.length() + 1] = 0; sf.hwnd = main_window; sf.wFunc = FO_DELETE; @@ -811,7 +765,71 @@ Error OS_Windows::move_to_trash(const String &p_path) { return OK; } +int OS_Windows::get_tablet_driver_count() const { + return tablet_drivers.size(); +} + +String OS_Windows::get_tablet_driver_name(int p_driver) const { + if (p_driver < 0 || p_driver >= tablet_drivers.size()) { + return ""; + } else { + return tablet_drivers[p_driver]; + } +} + +String OS_Windows::get_current_tablet_driver() const { + return tablet_driver; +} + +void OS_Windows::set_current_tablet_driver(const String &p_driver) { + if (get_tablet_driver_count() == 0) { + return; + } + bool found = false; + for (int i = 0; i < get_tablet_driver_count(); i++) { + if (p_driver == get_tablet_driver_name(i)) { + found = true; + } + } + if (found) { + if (DisplayServerWindows::get_singleton()) { + ((DisplayServerWindows *)DisplayServerWindows::get_singleton())->_update_tablet_ctx(tablet_driver, p_driver); + } + tablet_driver = p_driver; + } else { + ERR_PRINT("Unknown tablet driver " + p_driver + "."); + } +} + OS_Windows::OS_Windows(HINSTANCE _hInstance) { + //Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. + HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); + if (wintab_lib) { + DisplayServerWindows::wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW"); + DisplayServerWindows::wintab_WTClose = (WTClosePtr)GetProcAddress(wintab_lib, "WTClose"); + DisplayServerWindows::wintab_WTInfo = (WTInfoPtr)GetProcAddress(wintab_lib, "WTInfoW"); + DisplayServerWindows::wintab_WTPacket = (WTPacketPtr)GetProcAddress(wintab_lib, "WTPacket"); + DisplayServerWindows::wintab_WTEnable = (WTEnablePtr)GetProcAddress(wintab_lib, "WTEnable"); + + DisplayServerWindows::wintab_available = DisplayServerWindows::wintab_WTOpen && DisplayServerWindows::wintab_WTClose && DisplayServerWindows::wintab_WTInfo && DisplayServerWindows::wintab_WTPacket && DisplayServerWindows::wintab_WTEnable; + } + + if (DisplayServerWindows::wintab_available) { + tablet_drivers.push_back("wintab"); + } + + //Note: Windows Ink API for pen input, available on Windows 8+ only. + HMODULE user32_lib = LoadLibraryW(L"user32.dll"); + if (user32_lib) { + DisplayServerWindows::win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); + DisplayServerWindows::win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); + + DisplayServerWindows::winink_available = DisplayServerWindows::win8p_GetPointerType && DisplayServerWindows::win8p_GetPointerPenInfo; + } + + if (DisplayServerWindows::winink_available) { + tablet_drivers.push_back("winink"); + } force_quit = false; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 6bdfc75ebb..a3dbb23182 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -31,9 +31,9 @@ #ifndef OS_WINDOWS_H #define OS_WINDOWS_H -#include "core/input/input_filter.h" +#include "core/config/project_settings.h" +#include "core/input/input.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "crash_handler_windows.h" #include "drivers/unix/ip_unix.h" #include "drivers/wasapi/audio_driver_wasapi.h" @@ -63,7 +63,6 @@ class JoypadWindows; class OS_Windows : public OS { - #ifdef STDOUT_FILE FILE *stdo; #endif @@ -74,6 +73,9 @@ class OS_Windows : public OS { HINSTANCE hInstance; MainLoop *main_loop; + String tablet_driver; + Vector<String> tablet_drivers; + #ifdef WASAPI_ENABLED AudioDriverWASAPI driver_wasapi; #endif @@ -100,8 +102,9 @@ protected: virtual void finalize_core(); virtual String get_stdin_string(bool p_block); - struct ProcessInfo { + String _quote_command_line_argument(const String &p_text) const; + struct ProcessInfo { STARTUPINFO si; PROCESS_INFORMATION pi; }; @@ -116,14 +119,17 @@ public: virtual String get_name() const; + virtual int get_tablet_driver_count() const; + virtual String get_tablet_driver_name(int p_driver) const; + virtual String get_current_tablet_driver() const; + virtual void set_current_tablet_driver(const String &p_driver); + virtual void initialize_joypads() {} virtual Date get_date(bool utc) const; virtual Time get_time(bool utc) const; virtual TimeZoneInfo get_time_zone_info() const; - virtual uint64_t get_unix_time() const; - virtual uint64_t get_system_time_secs() const; - virtual uint64_t get_system_time_msecs() const; + virtual double get_unix_time() const; virtual Error set_cwd(const String &p_cwd); diff --git a/platform/windows/platform_config.h b/platform/windows/platform_config.h index 290decac5f..09a16614e0 100644 --- a/platform/windows/platform_config.h +++ b/platform/windows/platform_config.h @@ -29,5 +29,3 @@ /*************************************************************************/ #include <malloc.h> - -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h" diff --git a/platform/windows/vulkan_context_win.cpp b/platform/windows/vulkan_context_win.cpp index 98aa21411f..2c63281c49 100644 --- a/platform/windows/vulkan_context_win.cpp +++ b/platform/windows/vulkan_context_win.cpp @@ -36,7 +36,6 @@ const char *VulkanContextWindows::_get_platform_surface_extension() const { } int VulkanContextWindows::window_create(DisplayServer::WindowID p_window_id, HWND p_window, HINSTANCE p_instance, int p_width, int p_height) { - VkWin32SurfaceCreateInfoKHR createInfo; createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; createInfo.pNext = nullptr; diff --git a/platform/windows/vulkan_context_win.h b/platform/windows/vulkan_context_win.h index c9fea9369b..6e80db0286 100644 --- a/platform/windows/vulkan_context_win.h +++ b/platform/windows/vulkan_context_win.h @@ -35,7 +35,6 @@ #include <windows.h> class VulkanContextWindows : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; public: diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index 884d95e082..0938b65b04 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -81,7 +81,6 @@ void WindowsTerminalLogger::log_error(const char *p_function, const char *p_file StdLogger::log_error(p_function, p_file, p_line, p_code, p_rationale, p_type); #ifndef UWP_ENABLED } else { - CONSOLE_SCREEN_BUFFER_INFO sbi; //original GetConsoleScreenBufferInfo(hCon, &sbi); @@ -89,20 +88,36 @@ void WindowsTerminalLogger::log_error(const char *p_function, const char *p_file uint32_t basecol = 0; switch (p_type) { - case ERR_ERROR: basecol = FOREGROUND_RED; break; - case ERR_WARNING: basecol = FOREGROUND_RED | FOREGROUND_GREEN; break; - case ERR_SCRIPT: basecol = FOREGROUND_RED | FOREGROUND_BLUE; break; - case ERR_SHADER: basecol = FOREGROUND_GREEN | FOREGROUND_BLUE; break; + case ERR_ERROR: + basecol = FOREGROUND_RED; + break; + case ERR_WARNING: + basecol = FOREGROUND_RED | FOREGROUND_GREEN; + break; + case ERR_SCRIPT: + basecol = FOREGROUND_RED | FOREGROUND_BLUE; + break; + case ERR_SHADER: + basecol = FOREGROUND_GREEN | FOREGROUND_BLUE; + break; } basecol |= current_bg; SetConsoleTextAttribute(hCon, basecol | FOREGROUND_INTENSITY); switch (p_type) { - case ERR_ERROR: logf("ERROR:"); break; - case ERR_WARNING: logf("WARNING:"); break; - case ERR_SCRIPT: logf("SCRIPT ERROR:"); break; - case ERR_SHADER: logf("SHADER ERROR:"); break; + case ERR_ERROR: + logf("ERROR:"); + break; + case ERR_WARNING: + logf("WARNING:"); + break; + case ERR_SCRIPT: + logf("SCRIPT ERROR:"); + break; + case ERR_SHADER: + logf("SHADER ERROR:"); + break; } SetConsoleTextAttribute(hCon, basecol); @@ -115,10 +130,18 @@ void WindowsTerminalLogger::log_error(const char *p_function, const char *p_file // `FOREGROUND_INTENSITY` alone results in gray text. SetConsoleTextAttribute(hCon, FOREGROUND_INTENSITY); switch (p_type) { - case ERR_ERROR: logf(" at: "); break; - case ERR_WARNING: logf(" at: "); break; - case ERR_SCRIPT: logf(" at: "); break; - case ERR_SHADER: logf(" at: "); break; + case ERR_ERROR: + logf(" at: "); + break; + case ERR_WARNING: + logf(" at: "); + break; + case ERR_SCRIPT: + logf(" at: "); + break; + case ERR_SHADER: + logf(" at: "); + break; } if (p_rationale && p_rationale[0]) { |