diff options
Diffstat (limited to 'platform')
191 files changed, 5246 insertions, 6203 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..fb442f4c54 100644 --- a/platform/android/android_keys_utils.h +++ b/platform/android/android_keys_utils.h @@ -154,7 +154,6 @@ enum { }; struct _WinTranslatePair { - unsigned int keysym; unsigned int keycode; }; diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp index 4fe868d4f0..1f140f7119 100644 --- a/platform/android/api/api.cpp +++ b/platform/android/api/api.cpp @@ -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..e34f2a9f69 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -43,7 +43,6 @@ class JavaObject; #endif class JavaClass : public Reference { - GDCLASS(JavaClass, Reference); #ifdef ANDROID_ENABLED @@ -68,7 +67,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 +75,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 +121,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 +161,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; } } @@ -174,7 +186,6 @@ public: }; class JavaObject : public Reference { - GDCLASS(JavaObject, Reference); #ifdef ANDROID_ENABLED @@ -194,7 +205,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..ed69f8d6e4 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -38,12 +38,10 @@ #endif class JNISingleton : public Object { - GDCLASS(JNISingleton, Object); #ifdef ANDROID_ENABLED struct MethodData { - jmethodID method; Variant::Type ret_type; Vector<Variant::Type> argtypes; @@ -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..09c981b3fa 100644 --- a/platform/android/audio_driver_jandroid.cpp +++ b/platform/android/audio_driver_jandroid.cpp @@ -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..9858a40822 100644 --- a/platform/android/audio_driver_opensl.h +++ b/platform/android/audio_driver_opensl.h @@ -38,7 +38,6 @@ #include <SLES/OpenSLES_Android.h> class AudioDriverOpenSL : public AudioDriver { - bool active; Mutex mutex; diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index f8571e6277..ca312b427f 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -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..1436d832de 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -155,12 +155,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, 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_max_length, p_cursor_start, p_cursor_end); } else { ERR_PRINT("Virtual keyboard not available"); } @@ -367,6 +367,25 @@ 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; @@ -424,7 +443,7 @@ 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); } DisplayServerAndroid::~DisplayServerAndroid() { @@ -445,16 +464,16 @@ 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; @@ -484,7 +503,7 @@ 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()->parse_input_event(ev); } void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector<DisplayServerAndroid::TouchPos> &p_points) { @@ -493,13 +512,12 @@ void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector 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()->parse_input_event(ev); } } @@ -511,13 +529,12 @@ 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()->parse_input_event(ev); } } break; @@ -525,10 +542,8 @@ void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector 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,7 +560,7 @@ 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()->parse_input_event(ev); touch.write[i].pos = p_points[idx].pos; } @@ -554,13 +569,12 @@ void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector 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()->parse_input_event(ev); } touch.clear(); } @@ -577,7 +591,7 @@ 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()->parse_input_event(ev); break; } @@ -586,13 +600,12 @@ void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector case 4: { // 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()->parse_input_event(ev); touch.remove(i); break; @@ -613,7 +626,7 @@ void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) { 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()->parse_input_event(ev); hover_prev_pos = p_pos; } break; } @@ -626,7 +639,7 @@ void DisplayServerAndroid::process_double_tap(Point2 p_pos) { ev->set_global_position(p_pos); ev->set_pressed(false); ev->set_doubleclick(true); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->parse_input_event(ev); } void DisplayServerAndroid::process_scroll(Point2 p_pos) { @@ -634,22 +647,22 @@ void DisplayServerAndroid::process_scroll(Point2 p_pos) { ev.instance(); ev->set_position(p_pos); ev->set_delta(p_pos - scroll_prev_pos); - InputFilter::get_singleton()->parse_input_event(ev); + Input::get_singleton()->parse_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); } diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 2096ba68f1..d64542df58 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; @@ -107,7 +106,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(), 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; @@ -167,6 +166,8 @@ public: static Vector<String> get_rendering_drivers_func(); static void register_android_driver(); + void reset_window(); + 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 1eb1ee0d29..a663a847c2 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -44,6 +44,7 @@ #include "editor/editor_node.h" #include "editor/editor_settings.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> @@ -235,14 +236,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 +249,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 +305,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 +319,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 +330,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 +361,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 +392,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } d.name = vendor + " " + device; - if (device == String()) continue; + if (device == String()) { + continue; + } } ndevices.push_back(d); @@ -388,8 +410,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 +429,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } String get_project_name(const String &p_name) const { - String aname; if (p_name != "") { aname = p_name; @@ -422,7 +444,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(); @@ -439,8 +460,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { first = false; } } - if (name == "") + if (name == "") { name = "noname"; + } pname = pname.replace("$genname", name); @@ -448,7 +470,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) { @@ -512,7 +533,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 +579,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(); @@ -586,6 +605,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, @@ -648,7 +734,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } 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; @@ -687,17 +772,18 @@ 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) + if (enabled) { perms.push_back("android.permission." + String(*aperms)); + } aperms++; } @@ -711,19 +797,17 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } if (p_give_internet) { - if (perms.find("android.permission.INTERNET") == -1) + if (perms.find("android.permission.INTERNET") == -1) { perms.push_back("android.permission.INTERNET"); + } } 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,14 +828,12 @@ 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; @@ -774,7 +856,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } break; case CHUNK_XML_START_TAG: { - int iofs = ofs + 8; uint32_t name = decode_uint32(&p_manifest[iofs + 12]); @@ -804,8 +885,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 +895,20 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } if (tname == "activity" && attrname == "screenOrientation") { - encode_uint32(orientation == 0 ? 0 : 1, &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,9 +929,14 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } - if (tname == "meta-data" && attrname == "value" && value == "custom_template_plugins_value") { + if (tname == "meta-data" && attrname == "value" && value == "oculus_focus_aware_value") { + // Update the focus awareness meta-data value + string_table.write[attr_value] = xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? "true" : "false"; + } + + 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; } iofs += 20; @@ -889,8 +970,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { 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"); + if (perms.find("com.oculus.permission.HAND_TRACKING") == -1) { + perms.push_back("com.oculus.permission.HAND_TRACKING"); } } } @@ -1037,7 +1118,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 +1203,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 +1216,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 +1232,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 +1257,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,19 +1297,18 @@ 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) + if (c == 0) { break; + } str += String::chr(c); } return str; } } void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest) { - const int UTF8_FLAG = 0x00000100; uint32_t string_block_len = decode_uint32(&p_manifest[16]); @@ -1225,20 +1321,17 @@ 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]); offset += string_table_begins + string_count * 4; String str = _parse_string(&p_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 prop = "application/config/name_" + lang; if (ProjectSettings::get_singleton()->has_setting(prop)) { @@ -1257,13 +1350,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]; } 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 +1362,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 +1374,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]); @@ -1344,7 +1435,6 @@ public: public: virtual void 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"); @@ -1361,16 +1451,23 @@ public: } virtual void get_export_options(List<ExportOption> *r_options) { - 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,..."), "")); + + 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")); @@ -1408,7 +1505,6 @@ public: const char **perms = android_perms; while (*perms) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "permissions/" + String(*perms).to_lower()), false)); perms++; } @@ -1426,8 +1522,16 @@ public: return logo; } - virtual bool poll_export() { + virtual bool should_update_export_options() { + 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() { bool dc = devices_changed; if (dc) { // don't clear unless we're reporting true, to avoid race @@ -1437,25 +1541,21 @@ public: } virtual int get_options_count() const { - MutexLock lock(device_lock); return devices.size(); } virtual String get_options_tooltip() const { - return TTR("Select device from the list"); } virtual String get_option_label(int p_index) const { - 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 { - ERR_FAIL_INDEX_V(p_index, devices.size(), ""); MutexLock lock(device_lock); String s = devices[p_index].description; @@ -1469,7 +1569,6 @@ public: } virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { - ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); String can_export_error; @@ -1493,8 +1592,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 +1653,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 +1665,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 +1678,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 +1691,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()); @@ -1634,30 +1730,38 @@ public: } virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - 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 +1772,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 +1779,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 +1786,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 +1793,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,7 +1807,7 @@ public: valid = false; } else { Error errn; - DirAccessRef da = DirAccess::open(sdk_path.plus_file("tools"), &errn); + 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"; valid = false; @@ -1707,7 +1815,6 @@ public: } 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 +1823,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 +1836,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,6 +1846,40 @@ 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"; + } + } + r_error = err; return valid; } @@ -1751,8 +1890,30 @@ public: 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; + } + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); String src_apk; @@ -1789,18 +1950,26 @@ 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"); + + 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; + if (clean_build_required) { + cmdline.push_back("clean"); + } cmdline.push_back("build"); cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name. - cmdline.push_back("-Pcustom_template_plugins_dir=" + plugins_dir); // argument to specify the plugins directory. - cmdline.push_back("-Pcustom_template_plugins=" + plugins); // argument to specify the list of plugins to enable. + cmdline.push_back("-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 @@ -1827,11 +1996,11 @@ public: } } else { - - if (p_debug) + if (p_debug) { src_apk = p_preset->get("custom_template/debug"); - else + } else { src_apk = p_preset->get("custom_template/release"); + } src_apk = src_apk.strip_edges(); if (src_apk == "") { @@ -1860,7 +2029,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; } @@ -1934,7 +2102,6 @@ public: Vector<String> invalid_abis(enabled_abis); while (ret == UNZ_OK) { - //get filename unz_file_info info; char fname[16384]; @@ -2040,7 +2207,6 @@ public: gen_export_flags(cl, p_flags); if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; @@ -2049,7 +2215,6 @@ public: //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); @@ -2068,7 +2233,6 @@ public: cl.push_back(apk_expansion_pkey.strip_edges()); } else { - APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; @@ -2085,14 +2249,17 @@ public: cl.push_back("--xr_mode_regular"); } - if (use_32_fb) + if (use_32_fb) { cl.push_back("--use_depth_32"); + } - if (immersive) + if (immersive) { cl.push_back("--use_immersive"); + } - if (debug_opengl) + if (debug_opengl) { cl.push_back("--debug_opengl"); + } if (cl.size()) { //add comandline @@ -2100,13 +2267,13 @@ public: 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) + if (!length) { continue; + } clf.resize(base + 4 + length); encode_uint32(length, &clf.write[base]); copymem(&clf.write[base + 4], txt.ptr(), length); @@ -2137,7 +2304,6 @@ 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."); @@ -2148,13 +2314,11 @@ public: 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"); @@ -2231,7 +2395,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 +2411,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)); @@ -2307,7 +2469,6 @@ public: } virtual void get_platform_features(List<String> *r_features) { - r_features->push_back("mobile"); r_features->push_back("Android"); } @@ -2316,7 +2477,6 @@ public: } EditorExportPlatformAndroid() { - Ref<Image> img = memnew(Image(_android_logo)); logo.instance(); logo->create_from_image(img); @@ -2326,19 +2486,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"; diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index fa805ec4f3..05d5fb576d 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -39,12 +39,10 @@ AAssetManager *FileAccessAndroid::asset_manager = nullptr; }*/ 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 +62,6 @@ Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) { } void FileAccessAndroid::close() { - if (!a) return; AAsset_close(a); @@ -72,12 +69,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 +85,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 +115,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 +122,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 +131,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..dbf1dc0f3c 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -32,8 +32,8 @@ <!-- 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" @@ -45,6 +45,9 @@ android:resizeableActivity="false" tools:ignore="UnusedAttribute" > + <!-- Focus awareness metadata populated at export time if the user enables it in the 'Xr Features' section. --> + <meta-data android:name="com.oculus.vr.focusaware" android:value="oculus_focus_aware_value" /> + <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 99080eeb3c..19202d2310 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) } } @@ -62,6 +75,11 @@ android { } 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:!*~" + } + // Feel free to modify the application id to your own. applicationId getExportPackageName() minSdkVersion versions.minSdk @@ -76,7 +94,9 @@ android { packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' - doNotStrip '**/*.so' + + // Should be uncommented for development purpose within Android Studio + // doNotStrip '**/*.so' } // Both signing and zip-aligning will be done at export time diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index aa98194a10..acfdef531e 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -4,18 +4,18 @@ ext.versions = [ minSdk : 18, targetSdk : 29, buildTools : '29.0.3', - supportCoreUtils : '28.0.0', + supportCoreUtils : '1.0.0', kotlinVersion : '1.3.61', - v4Support : '28.0.0' + v4Support : '1.0.0' ] 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 +28,63 @@ ext.getExportPackageName = { -> return appId } +final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|" + /** - * 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 '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_remote_binaries' property and return + * it 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.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 +} - return pluginsDir +/** + * Parse the project properties for the 'plugins_local_binaries' property and return + * their binaries for inclusion in the build dependencies. + */ +ext.getGodotPluginsLocalBinaries = { -> + Set<String> binDeps = [] + + // 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/build.gradle b/platform/android/java/build.gradle index 865b61956c..80c6be0fae 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -140,7 +140,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) { @@ -161,6 +161,7 @@ task generateGodotTemplates(type: GradleBuild) { } } + dependsOn 'copyGodotPaymentPluginToAppModule' finalizedBy 'zipCustomBuild' } @@ -192,4 +193,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..e14cd5ba5c 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 diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index c69e19fbfa..19eee5a315 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -12,7 +12,6 @@ def pathToRootDir = "../../../../" android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools - useLibrary 'org.apache.http.legacy' defaultConfig { minSdkVersion versions.minSdk @@ -27,7 +26,9 @@ android { packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' - doNotStrip '**/*.so' + + // Should be uncommented for development purpose within Android Studio + // doNotStrip '**/*.so' } sourceSets { 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/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index bf0d1c6273..8ba9b0400f 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,13 @@ package org.godotengine.godot; +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,10 +66,6 @@ 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.KeyEvent; import android.view.MotionEvent; @@ -77,6 +80,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.FragmentActivity; + 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 +93,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 +101,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 { - private IStub mDownloaderClientStub; private TextView mStatusText; private TextView mProgressFraction; @@ -248,7 +251,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(); @@ -343,7 +345,8 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe return deviceInfo.reqGlEsVersion; } - private String[] getCommandLine() { + @CallSuper + protected String[] getCommandLine() { InputStream is; try { is = getAssets().open("_cl_"); @@ -358,7 +361,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 +408,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) { @@ -455,7 +455,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override protected void onCreate(Bundle icicle) { - super.onCreate(icicle); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); @@ -472,7 +471,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe 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; @@ -516,7 +514,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe 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) { @@ -538,7 +535,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe boolean pack_valid = true; if (!f.exists()) { - pack_valid = false; } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) { @@ -550,7 +546,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } if (!pack_valid) { - Intent notifierIntent = new Intent(this, this.getClass()); notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -599,7 +594,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override protected void onDestroy() { - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onMainDestroy(); } @@ -634,7 +628,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public String getClipboard() { - String copiedText = ""; if (mClipboard.getPrimaryClip() != null) { @@ -646,7 +639,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public void setClipboard(String p_text) { - ClipData clip = ClipData.newPlainText("myLabel", p_text); mClipboard.setPrimaryClip(clip); } @@ -806,9 +798,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } private boolean obbIsCorrupted(String f, String main_pack_md5) { - try { - InputStream fis = new FileInputStream(f); // Create MD5 Hash @@ -849,7 +839,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public boolean gotTouchEvent(final MotionEvent event) { - final int evcount = event.getPointerCount(); if (evcount == 0) return true; @@ -858,7 +847,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe 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); @@ -917,7 +905,8 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe 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 super.onKeyMultiple(inKeyCode, repeatCount, event); mRenderView.queueOnRenderThread(new Runnable() { // This method will be called on the rendering thread: public void run() { 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..14dd893faa 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,7 @@ /*************************************************************************/ 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 +41,14 @@ 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.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,7 +68,6 @@ 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 GodotInputHandler inputHandler; private final GestureDetector detector; @@ -138,11 +139,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..93f4786e83 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -29,6 +29,9 @@ /*************************************************************************/ package org.godotengine.godot; + +import org.godotengine.godot.input.*; + import android.content.*; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -39,16 +42,14 @@ import android.os.*; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; + 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; GodotEditText edit; @@ -68,7 +69,6 @@ public class GodotIO { public int last_file_id = 1; class AssetData { - public boolean eof = false; public String path; public InputStream is; @@ -79,7 +79,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 +91,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 +98,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 +110,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 +118,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 +130,6 @@ public class GodotIO { bytes = 0; try { - if (bytes > (int)ad.pos) { int todo = bytes - (int)ad.pos; while (todo > 0) { @@ -143,7 +137,6 @@ public class GodotIO { } ad.pos = bytes; } else if (bytes < (int)ad.pos) { - ad.is = am.open(ad.path); ad.pos = bytes; @@ -155,14 +148,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 +163,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 +173,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 +181,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 +194,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 +205,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 +228,6 @@ public class GodotIO { ///////////////////////// class AssetDir { - public String[] files; public int current; public String path; @@ -256,7 +238,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 +250,6 @@ public class GodotIO { return -1; } } catch (IOException e) { - System.out.printf("Exception on dir_open: %s\n", e); return -1; } @@ -308,7 +288,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 +306,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; @@ -337,7 +315,6 @@ public class GodotIO { } GodotIO(Godot p_activity) { - am = p_activity.getAssets(); activity = p_activity; //streams = new HashMap<Integer, AssetData>(); @@ -428,7 +405,6 @@ public class GodotIO { } public void audioPause(boolean p_pause) { - if (p_pause) mAudioTrack.pause(); else @@ -440,7 +416,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 +424,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 +439,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 +460,9 @@ public class GodotIO { return (int)(metrics.density * 160f); } - public void showKeyboard(String p_existing_text, int p_max_input_length) { + public void showKeyboard(String p_existing_text, 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_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 +474,7 @@ public class GodotIO { }; public void setScreenOrientation(int p_orientation) { - switch (p_orientation) { - case SCREEN_LANDSCAPE: { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } break; @@ -548,7 +517,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 +561,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..3693f36557 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 { @@ -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. @@ -189,7 +190,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 +198,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..27e63f3a66 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -33,7 +33,6 @@ package org.godotengine.godot; import android.view.SurfaceView; public interface GodotRenderView { - abstract public SurfaceView getView(); abstract public void initInputDevices(); 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..e9872b58ff 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -30,18 +30,18 @@ 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.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.vulkan.VkRenderer; -import org.godotengine.godot.vulkan.VkSurfaceView; public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { - private final Godot mActivity; private final GodotInputHandler mInputHandler; private final GestureDetector mGestureDetector; 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..7f596575a8 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,6 +29,9 @@ /*************************************************************************/ package org.godotengine.godot.input; + +import org.godotengine.godot.*; + import android.content.Context; import android.os.Handler; import android.os.Message; @@ -38,8 +41,8 @@ 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 +58,7 @@ public class GodotEditText extends EditText { private GodotTextInputWrapper mInputWrapper; private EditHandler sHandler = new EditHandler(this); private String mOriginText; + private int mMaxInputLength; private static class EditHandler extends Handler { private final WeakReference<GodotEditText> mEdit; @@ -101,11 +105,18 @@ 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); + } + 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 +133,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); } // =========================================================== @@ -161,13 +168,24 @@ public class GodotEditText extends EditText { // =========================================================== // Methods // =========================================================== - public void showKeyboard(String p_existing_text, int p_max_input_length) { - mOriginText = p_existing_text; + public void showKeyboard(String p_existing_text, 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); + } 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..1c9a683bbd 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,18 @@ package org.godotengine.godot.input; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.GodotRenderView; + import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.GodotRenderView; /** * 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) { 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..9abd65cc67 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,25 @@ 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.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 +85,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 +127,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; @@ -159,7 +158,6 @@ public class GodotInputHandler implements InputDeviceListener { 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 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..9c7cf9f341 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; + } } } }); 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..431bd4f5f9 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,27 @@ 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.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 +76,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; 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..12d2ed09fb 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,27 @@ package org.godotengine.godot.plugin; +import org.godotengine.godot.Godot; + 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 +58,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; @@ -127,13 +130,13 @@ public final class GodotPluginRegistry { } // 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 +160,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 +181,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/CustomSSLSocketFactory.java b/platform/android/java/lib/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java deleted file mode 100644 index c78e8c1c66..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java +++ /dev/null @@ -1,69 +0,0 @@ -/*************************************************************************/ -/* CustomSSLSocketFactory.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.utils; -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; - -/** - * - * @author Luis Linietsky <luis.linietsky@gmail.com> - */ -public class CustomSSLSocketFactory extends SSLSocketFactory { - SSLContext sslContext = SSLContext.getInstance("TLS"); - - public CustomSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - super(truststore); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); - tmf.init(truststore); - - sslContext.init(null, tmf.getTrustManagers(), null); - } - - @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { - return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); - } - - @Override - public Socket createSocket() throws IOException { - return sslContext.getSocketFactory().createSocket(); - } -} 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..0832a9b965 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,11 @@ package org.godotengine.godot.utils; +import org.godotengine.godot.Godot; + 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,7 +42,6 @@ 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; diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java b/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java deleted file mode 100644 index 68f9a83597..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java +++ /dev/null @@ -1,227 +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.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; - -/** - * - * @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/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 7cf32b00fe..6837e4f147 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 @@ -30,21 +30,26 @@ package org.godotengine.godot.utils; +import org.godotengine.godot.Godot; + import android.Manifest; 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; @@ -113,8 +118,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); } } @@ -153,8 +158,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); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/RequestParams.java b/platform/android/java/lib/src/org/godotengine/godot/utils/RequestParams.java deleted file mode 100644 index 25fa10647d..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/RequestParams.java +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************/ -/* RequestParams.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.utils; - -import java.util.ArrayList; -import java.util.Date; -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); - } - - public String get(String key) { - return params.get(key); - } - - public void remove(Object key) { - params.remove(key); - } - - public boolean has(String key) { - return params.containsKey(key); - } - - public List<NameValuePair> toPairsList() { - List<NameValuePair> fields = new ArrayList<NameValuePair>(); - - for (String key : params.keySet()) { - fields.add(new BasicNameValuePair(key, this.get(key))); - } - return fields; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } -} 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..aeb4628d5d 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 @@ -59,9 +59,7 @@ internal class VkRenderer { * 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,12 +70,7 @@ 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/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/plugins/godotpayment/build.gradle b/platform/android/java/plugins/godotpayment/build.gradle index 4f376c4587..fb3aa8bba2 100644 --- a/platform/android/java/plugins/godotpayment/build.gradle +++ b/platform/android/java/plugins/godotpayment/build.gradle @@ -20,6 +20,7 @@ android { dependencies { implementation libraries.supportCoreUtils implementation libraries.v4Support + implementation 'com.android.billingclient:billing:2.2.1' if (rootProject.findProject(":lib")) { compileOnly project(":lib") 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 index c7d0a5de65..6447a9ec3f 100644 --- 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 @@ -30,213 +30,179 @@ 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; +import org.godotengine.godot.plugin.SignalInfo; +import org.godotengine.godot.plugin.payment.utils.GodotPaymentUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.ArraySet; + +import com.android.billingclient.api.AcknowledgePurchaseParams; +import com.android.billingclient.api.AcknowledgePurchaseResponseListener; +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.ConsumeParams; +import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchasesUpdatedListener; +import com.android.billingclient.api.SkuDetails; +import com.android.billingclient.api.SkuDetailsParams; +import com.android.billingclient.api.SkuDetailsResponseListener; -public class GodotPayment extends GodotPlugin { +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Set; - private Integer purchaseCallbackId = 0; - private String accessToken; - private String purchaseValidationUrlPrefix; - private String transactionId; - private final PaymentsManager mPaymentManager; - private final Dictionary mSkuDetails = new Dictionary(); +public class GodotPayment extends GodotPlugin implements PurchasesUpdatedListener, BillingClientStateListener { + private final BillingClient billingClient; + private final HashMap<String, SkuDetails> skuDetailsCache = new HashMap<>(); // sku → SkuDetails 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(); - } - }); + billingClient = BillingClient + .newBuilder(getGodot()) + .enablePendingPurchases() + .setListener(this) + .build(); } - private String signature; - - public String getSignature() { - return this.signature; + public void startConnection() { + billingClient.startConnection(this); } - public void callbackSuccess(String ticket, String signature, String sku) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku }); + public void endConnection() { + billingClient.endConnection(); } - 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 boolean isReady() { + return this.billingClient.isReady(); } - 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 Dictionary queryPurchases(String type) { + Purchase.PurchasesResult result = billingClient.queryPurchases(type); - 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; - } + Dictionary returnValue = new Dictionary(); + if (result.getBillingResult().getResponseCode() == BillingClient.BillingResponseCode.OK) { + returnValue.put("status", 0); // OK = 0 + returnValue.put("purchases", GodotPaymentUtils.convertPurchaseListToDictionaryObjectArray(result.getPurchasesList())); + } else { + returnValue.put("status", 1); // FAILED = 1 + returnValue.put("response_code", result.getBillingResult().getResponseCode()); + returnValue.put("debug_message", result.getBillingResult().getDebugMessage()); + } - public String getPurchaseValidationUrlPrefix() { - return this.purchaseValidationUrlPrefix; + return returnValue; } - public void setPurchaseValidationUrlPrefix(String url) { - this.purchaseValidationUrlPrefix = url; - } + public void querySkuDetails(final String[] list, String type) { + List<String> skuList = Arrays.asList(list); - public String getAccessToken() { - return accessToken; - } + SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder() + .setSkusList(skuList) + .setType(type); - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; + billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, + List<SkuDetails> skuDetailsList) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + for (SkuDetails skuDetails : skuDetailsList) { + skuDetailsCache.put(skuDetails.getSku(), skuDetails); + } + emitSignal("sku_details_query_completed", (Object)GodotPaymentUtils.convertSkuDetailsListToDictionaryObjectArray(skuDetailsList)); + } else { + emitSignal("sku_details_query_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), list); + } + } + }); } - public void setTransactionId(String transactionId) { - this.transactionId = transactionId; + public void acknowledgePurchase(final String purchaseToken) { + AcknowledgePurchaseParams acknowledgePurchaseParams = + AcknowledgePurchaseParams.newBuilder() + .setPurchaseToken(purchaseToken) + .build(); + billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() { + @Override + public void onAcknowledgePurchaseResponse(BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("purchase_acknowledged", purchaseToken); + } else { + emitSignal("purchase_acknowledgement_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken); + } + } + }); } - public String getTransactionId() { - return this.transactionId; - } + public void consumePurchase(String purchaseToken) { + ConsumeParams consumeParams = ConsumeParams.newBuilder() + .setPurchaseToken(purchaseToken) + .build(); - // request purchased items are not consumed - public void requestPurchased() { - runOnUiThread(new Runnable() { + billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() { @Override - public void run() { - mPaymentManager.requestPurchased(); + public void onConsumeResponse(BillingResult billingResult, String purchaseToken) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("purchase_consumed", purchaseToken); + } else { + emitSignal("purchase_consumption_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken); + } } }); } - // 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[] {}); + @Override + public void onBillingSetupFinished(BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("connected"); + } else { + emitSignal("connect_error", billingResult.getResponseCode(), billingResult.getDebugMessage()); + } } - public void callbackConnected() { - GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {}); + @Override + public void onBillingServiceDisconnected() { + emitSignal("disconnected"); } - // true if connected, false otherwise - public boolean isConnected() { - return mPaymentManager.isConnected(); - } + public Dictionary purchase(String sku) { + if (!skuDetailsCache.containsKey(sku)) { + emitSignal("purchase_error", null, "You must query the sku details and wait for the result before purchasing!"); + } - // consume item automatically after purchase. default is true. - public void setAutoConsume(boolean autoConsume) { - mPaymentManager.setAutoConsume(autoConsume); - } + SkuDetails skuDetails = skuDetailsCache.get(sku); + BillingFlowParams purchaseParams = BillingFlowParams.newBuilder() + .setSkuDetails(skuDetails) + .build(); - // consume a specific item - public void consume(String sku) { - mPaymentManager.consume(sku); - } + BillingResult result = billingClient.launchBillingFlow(getGodot(), purchaseParams); - // 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])); + Dictionary returnValue = new Dictionary(); + if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { + returnValue.put("status", 0); // OK = 0 } else { - completeSkuDetail(); + returnValue.put("status", 1); // FAILED = 1 + returnValue.put("response_code", result.getResponseCode()); + returnValue.put("debug_message", result.getDebugMessage()); } - } - 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(); - } + return returnValue; } - 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 }); + @Override + public void onPurchasesUpdated(final BillingResult billingResult, @Nullable final List<Purchase> list) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) { + emitSignal("purchases_updated", (Object)GodotPaymentUtils.convertPurchaseListToDictionaryObjectArray(list)); + } else { + emitSignal("purchase_error", billingResult.getResponseCode(), billingResult.getDebugMessage()); + } } @NonNull @@ -248,8 +214,26 @@ public class GodotPayment extends GodotPlugin { @NonNull @Override public List<String> getPluginMethods() { - return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", - "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", - "setAutoConsume", "consume", "querySkuDetails", "isConnected"); + return Arrays.asList("startConnection", "endConnection", "purchase", "querySkuDetails", "isReady", "queryPurchases", "acknowledgePurchase", "consumePurchase"); + } + + @NonNull + @Override + public Set<SignalInfo> getPluginSignals() { + Set<SignalInfo> signals = new ArraySet<>(); + + signals.add(new SignalInfo("connected")); + signals.add(new SignalInfo("disconnected")); + signals.add(new SignalInfo("connect_error", Integer.class, String.class)); + signals.add(new SignalInfo("purchases_updated", Object[].class)); + signals.add(new SignalInfo("purchase_error", Integer.class, String.class)); + signals.add(new SignalInfo("sku_details_query_completed", Object[].class)); + signals.add(new SignalInfo("sku_details_query_error", Integer.class, String.class, String[].class)); + signals.add(new SignalInfo("purchase_acknowledged", String.class)); + signals.add(new SignalInfo("purchase_acknowledgement_error", Integer.class, String.class, String.class)); + signals.add(new SignalInfo("purchase_consumed", String.class)); + signals.add(new SignalInfo("purchase_consumption_error", Integer.class, String.class, String.class)); + + return signals; } } 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/PaymentsCache.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java deleted file mode 100644 index d5919e3d9d..0000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java +++ /dev/null @@ -1,71 +0,0 @@ -/*************************************************************************/ -/* PaymentsCache.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.content.SharedPreferences; - -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); - } -} 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 179cc08ed1..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.utils.HttpRequester; -import org.godotengine.godot.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/GodotPaymentUtils.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/GodotPaymentUtils.java new file mode 100644 index 0000000000..f569c1b8bf --- /dev/null +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/GodotPaymentUtils.java @@ -0,0 +1,66 @@ +package org.godotengine.godot.plugin.payment.utils; + +import org.godotengine.godot.Dictionary; + +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.SkuDetails; + +import java.util.List; + +public class GodotPaymentUtils { + public static Dictionary convertPurchaseToDictionary(Purchase purchase) { + Dictionary dictionary = new Dictionary(); + dictionary.put("order_id", purchase.getOrderId()); + dictionary.put("package_name", purchase.getPackageName()); + dictionary.put("purchase_state", Integer.valueOf(purchase.getPurchaseState())); + dictionary.put("purchase_time", Long.valueOf(purchase.getPurchaseTime())); + dictionary.put("purchase_token", purchase.getPurchaseToken()); + dictionary.put("signature", purchase.getSignature()); + dictionary.put("sku", purchase.getSku()); + dictionary.put("is_acknowledged", Boolean.valueOf(purchase.isAcknowledged())); + dictionary.put("is_auto_renewing", Boolean.valueOf(purchase.isAutoRenewing())); + return dictionary; + } + + public static Dictionary convertSkuDetailsToDictionary(SkuDetails details) { + Dictionary dictionary = new Dictionary(); + dictionary.put("sku", details.getSku()); + dictionary.put("title", details.getTitle()); + dictionary.put("description", details.getDescription()); + dictionary.put("price", details.getPrice()); + dictionary.put("price_currency_code", details.getPriceCurrencyCode()); + dictionary.put("price_amount_micros", Long.valueOf(details.getPriceAmountMicros())); + dictionary.put("free_trial_period", details.getFreeTrialPeriod()); + dictionary.put("icon_url", details.getIconUrl()); + dictionary.put("introductory_price", details.getIntroductoryPrice()); + dictionary.put("introductory_price_amount_micros", Long.valueOf(details.getIntroductoryPriceAmountMicros())); + dictionary.put("introductory_price_cycles", details.getIntroductoryPriceCycles()); + dictionary.put("introductory_price_period", details.getIntroductoryPricePeriod()); + dictionary.put("original_price", details.getOriginalPrice()); + dictionary.put("original_price_amount_micros", Long.valueOf(details.getOriginalPriceAmountMicros())); + dictionary.put("subscription_period", details.getSubscriptionPeriod()); + dictionary.put("type", details.getType()); + dictionary.put("is_rewarded", Boolean.valueOf(details.isRewarded())); + return dictionary; + } + + public static Object[] convertPurchaseListToDictionaryObjectArray(List<Purchase> purchases) { + Object[] purchaseDictionaries = new Object[purchases.size()]; + + for (int i = 0; i < purchases.size(); i++) { + purchaseDictionaries[i] = GodotPaymentUtils.convertPurchaseToDictionary(purchases.get(i)); + } + + return purchaseDictionaries; + } + + public static Object[] convertSkuDetailsListToDictionaryObjectArray(List<SkuDetails> skuDetails) { + Object[] skuDetailsDictionaries = new Object[skuDetails.size()]; + + for (int i = 0; i < skuDetails.size(); i++) { + skuDetailsDictionaries[i] = GodotPaymentUtils.convertSkuDetailsToDictionary(skuDetails.get(i)); + } + + return skuDetailsDictionaries; + } +} diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 6b9105b8e5..39de3cb642 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,7 +1146,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { JavaClassWrapper *JavaClassWrapper::singleton = nullptr; JavaClassWrapper::JavaClassWrapper(jobject p_activity) { - singleton = this; JNIEnv *env = ThreadAndroid::get_env(); diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 0da0bd6387..0a42adeaf2 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -53,7 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()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;III)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"); @@ -132,11 +132,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, 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_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..1742021379 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -70,7 +70,7 @@ public: int get_screen_dpi(); 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, 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..1f61c4a805 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -38,7 +38,7 @@ #include "api/jni_singleton.h" #include "audio_driver_jandroid.h" #include "core/engine.h" -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "core/project_settings.h" #include "dir_access_jandroid.h" #include "display_server_android.h" @@ -78,7 +78,6 @@ 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) { - initialized = true; JavaVM *jvm; @@ -137,7 +136,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 +164,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)); - if (os_android) - os_android->set_display_size(Size2i(width, 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); + + 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,19 +233,16 @@ 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) { - 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); DisplayServerAndroid::TouchPos tp; @@ -311,15 +316,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 +334,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 +362,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 +369,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 +376,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 +399,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 +416,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,7 +429,6 @@ 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); diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 221d701e2b..e8be7be0d0 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -40,7 +40,7 @@ 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_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); @@ -61,8 +61,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/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..5320715853 100644 --- a/platform/android/jni_utils.h +++ b/platform/android/jni_utils.h @@ -37,7 +37,6 @@ #include <jni.h> struct jvalret { - jobject obj; jvalue val; jvalret() { obj = nullptr; } 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..baf6ee952a 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -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,25 @@ 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; return Main::iteration(); } void OS_Android::main_loop_end() { - if (main_loop) main_loop->finish(); } @@ -185,17 +176,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 +193,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 +201,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 +230,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 +238,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..5bc0fc3a58 --- /dev/null +++ b/platform/android/plugin/godot_plugin_config.h @@ -0,0 +1,267 @@ +/*************************************************************************/ +/* 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_list.h" +#include "core/io/config_file.h" +#include "core/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. + */ +static const PluginConfig GODOT_PAYMENT = { + /*.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(GODOT_PAYMENT, 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..d2528bebeb 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -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/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index 729327f6f0..13aa313ebf 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -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.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/audio_driver_media_kit.cpp b/platform/haiku/audio_driver_media_kit.cpp index 94c9e83368..2fbbeeb176 100644 --- a/platform/haiku/audio_driver_media_kit.cpp +++ b/platform/haiku/audio_driver_media_kit.cpp @@ -39,11 +39,11 @@ int32_t *AudioDriverMediaKit::samples_in = nullptr; Error AudioDriverMediaKit::init() { active = false; - mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); + mix_rate = GLOBAL_GET("audio/mix_rate"); speaker_mode = SPEAKER_MODE_STEREO; channels = 2; - int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); + int latency = GLOBAL_GET("audio/output_latency"); buffer_size = next_power_of_2(latency * mix_rate / 1000); samples_in = memnew_arr(int32_t, buffer_size * channels); diff --git a/platform/haiku/haiku_direct_window.h b/platform/haiku/haiku_direct_window.h index 925bb9ac5d..4817abbb7a 100644 --- a/platform/haiku/haiku_direct_window.h +++ b/platform/haiku/haiku_direct_window.h @@ -35,7 +35,7 @@ #include <DirectWindow.h> -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "core/os/os.h" #include "haiku_gl_view.h" diff --git a/platform/haiku/key_mapping_haiku.h b/platform/haiku/key_mapping_haiku.h index a0e85e3390..e735108e44 100644 --- a/platform/haiku/key_mapping_haiku.h +++ b/platform/haiku/key_mapping_haiku.h @@ -32,7 +32,7 @@ #define KEY_MAPPING_HAIKU_H class KeyMappingHaiku { - KeyMappingHaiku(){}; + KeyMappingHaiku() {} public: static unsigned int get_keysym(int32 raw_char, int32 key); diff --git a/platform/haiku/os_haiku.cpp b/platform/haiku/os_haiku.cpp index 07cb18d7cd..7a2591784f 100644 --- a/platform/haiku/os_haiku.cpp +++ b/platform/haiku/os_haiku.cpp @@ -324,12 +324,10 @@ String OS_Haiku::get_executable_path() const { } 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")) { @@ -340,7 +338,6 @@ String OS_Haiku::get_config_path() const { } String OS_Haiku::get_data_path() const { - if (has_environment("XDG_DATA_HOME")) { return get_environment("XDG_DATA_HOME"); } else if (has_environment("HOME")) { @@ -351,7 +348,6 @@ String OS_Haiku::get_data_path() const { } String OS_Haiku::get_cache_path() const { - if (has_environment("XDG_CACHE_HOME")) { return get_environment("XDG_CACHE_HOME"); } else if (has_environment("HOME")) { diff --git a/platform/haiku/os_haiku.h b/platform/haiku/os_haiku.h index 64f5690dd1..d3ef9400d4 100644 --- a/platform/haiku/os_haiku.h +++ b/platform/haiku/os_haiku.h @@ -33,7 +33,7 @@ #include "audio_driver_media_kit.h" #include "context_gl_haiku.h" -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "drivers/unix/os_unix.h" #include "haiku_application.h" #include "haiku_direct_window.h" diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index 0ac8bb7a56..c4ef185bf1 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -247,37 +247,31 @@ static void on_focus_in(ViewController *view_controller, bool *is_focus_out) { int joy_id = [self getJoyIdForController:controller]; if (element == gamepad.buttonA) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, gamepad.buttonA.isPressed); } else if (element == gamepad.buttonB) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, gamepad.buttonB.isPressed); } else if (element == gamepad.buttonX) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, gamepad.buttonX.isPressed); } else if (element == gamepad.buttonY) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, gamepad.buttonY.isPressed); } else if (element == gamepad.leftShoulder) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_L, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, gamepad.leftShoulder.isPressed); } else if (element == gamepad.rightShoulder) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_R, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, 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, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, gamepad.dpad.up.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, gamepad.dpad.down.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, gamepad.dpad.left.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, gamepad.dpad.right.isPressed); }; @@ -285,20 +279,20 @@ static void on_focus_in(ViewController *view_controller, bool *is_focus_out) { jx.min = -1; if (element == gamepad.leftThumbstick) { jx.value = gamepad.leftThumbstick.xAxis.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LX, jx); + OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx); jx.value = -gamepad.leftThumbstick.yAxis.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LY, jx); + OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx); } else if (element == gamepad.rightThumbstick) { jx.value = gamepad.rightThumbstick.xAxis.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RX, jx); + OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx); jx.value = -gamepad.rightThumbstick.yAxis.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RY, jx); + OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx); } else if (element == gamepad.leftTrigger) { jx.value = gamepad.leftTrigger.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx); + OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx); } else if (element == gamepad.rightTrigger) { jx.value = gamepad.rightTrigger.value; - OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx); + OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx); }; }; } else if (controller.gamepad != nil) { @@ -309,31 +303,31 @@ static void on_focus_in(ViewController *view_controller, bool *is_focus_out) { int joy_id = [self getJoyIdForController:controller]; if (element == gamepad.buttonA) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, gamepad.buttonA.isPressed); } else if (element == gamepad.buttonB) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, gamepad.buttonB.isPressed); } else if (element == gamepad.buttonX) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, gamepad.buttonX.isPressed); } else if (element == gamepad.buttonY) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, gamepad.buttonY.isPressed); } else if (element == gamepad.leftShoulder) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_L, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, gamepad.leftShoulder.isPressed); } else if (element == gamepad.rightShoulder) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_R, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, gamepad.rightShoulder.isPressed); } else if (element == gamepad.dpad) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, gamepad.dpad.up.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, gamepad.dpad.down.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, gamepad.dpad.left.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, gamepad.dpad.right.isPressed); }; }; @@ -347,19 +341,19 @@ static void on_focus_in(ViewController *view_controller, bool *is_focus_out) { int joy_id = [self getJoyIdForController:controller]; if (element == gamepad.buttonA) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, gamepad.buttonA.isPressed); } else if (element == gamepad.buttonX) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, gamepad.buttonX.isPressed); } else if (element == gamepad.dpad) { - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, gamepad.dpad.up.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, gamepad.dpad.down.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, gamepad.dpad.left.isPressed); - OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT, + OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, gamepad.dpad.right.isPressed); }; }; @@ -432,7 +426,6 @@ OS::VideoMode _get_video_mode() { static int frame_count = 0; - (void)drawView:(UIView *)view; { - switch (frame_count) { case 0: { OS::get_singleton()->set_video_mode(_get_video_mode()); @@ -469,7 +462,6 @@ static int frame_count = 0; }; break; case 1: { - Main::setup2(); ++frame_count; @@ -496,7 +488,6 @@ static int frame_count = 0; ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval); } else if ([value isKindOfClass:[NSNumber class]]) { - NSNumber *n = (NSNumber *)value; double dval = [n doubleValue]; @@ -508,7 +499,6 @@ static int frame_count = 0; }; break; case 2: { - Main::start(); ++frame_count; diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 3efe338ac7..63c3cb8c23 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "export.h" + #include "core/io/image_loader.h" #include "core/io/marshalls.h" #include "core/io/resource_saver.h" @@ -47,7 +48,6 @@ #include <sys/stat.h> class EditorExportPlatformIOS : public EditorExportPlatform { - GDCLASS(EditorExportPlatformIOS, EditorExportPlatform); int version_code; @@ -72,14 +72,10 @@ class EditorExportPlatformIOS : public EditorExportPlatform { String modules_buildgrp; }; 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; @@ -107,7 +103,6 @@ class EditorExportPlatformIOS : public EditorExportPlatform { Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets); bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { - String pname = p_package; if (pname.length() == 0) { @@ -150,7 +145,6 @@ public: 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) { - r_features->push_back("mobile"); r_features->push_back("iOS"); } @@ -163,7 +157,6 @@ public: }; 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"); @@ -209,7 +202,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"), "")); @@ -423,7 +415,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 +437,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 +449,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++) { @@ -927,8 +924,9 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir 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_FAIL_COND_V(err, err); @@ -987,10 +985,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 +1035,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 +1047,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; @@ -1231,16 +1232,19 @@ 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; + } err = _export_loading_screens(p_preset, dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"); - if (err) + if (err) { return err; + } print_line("Exporting additional assets"); Vector<IOSExportAsset> assets; @@ -1310,7 +1314,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; @@ -1368,14 +1371,14 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset 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); @@ -1385,7 +1388,6 @@ EditorExportPlatformIOS::~EditorExportPlatformIOS() { } void register_iphone_exporter() { - Ref<EditorExportPlatformIOS> platform; platform.instance(); diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h index d35cc4b87c..0d3ef5b696 100644 --- a/platform/iphone/game_center.h +++ b/platform/iphone/game_center.h @@ -36,7 +36,6 @@ #include "core/object.h" class GameCenter : public Object { - GDCLASS(GameCenter, Object); static GameCenter *instance; diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index 99d539d4ff..8d470da1a8 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -75,7 +75,6 @@ void GameCenter::return_connect_error(const char *p_error_description) { } void GameCenter::connect() { - //if this class isn't available, game center isn't implemented if ((NSClassFromString(@"GKLocalPlayer")) == nil) { return_connect_error("GameCenter not available"); @@ -125,7 +124,6 @@ bool GameCenter::is_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"]; @@ -156,7 +154,6 @@ Error GameCenter::post_score(Variant p_score) { }; 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"]; @@ -192,7 +189,6 @@ Error GameCenter::award_achievement(Variant p_params) { }; void GameCenter::request_achievement_descriptions() { - [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) { Dictionary ret; ret["type"] = "achievement_descriptions"; @@ -207,7 +203,6 @@ void GameCenter::request_achievement_descriptions() { Array replayable; for (int i = 0; i < [descriptions count]; i++) { - GKAchievementDescription *description = [descriptions objectAtIndex:i]; const char *str = [description.identifier UTF8String]; @@ -247,7 +242,6 @@ void GameCenter::request_achievement_descriptions() { }; void GameCenter::request_achievements() { - [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) { Dictionary ret; ret["type"] = "achievements"; @@ -257,7 +251,6 @@ void GameCenter::request_achievements() { 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 : "")); @@ -278,7 +271,6 @@ void GameCenter::request_achievements() { }; void GameCenter::reset_achievements() { - [GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error) { Dictionary ret; ret["type"] = "reset_achievements"; @@ -294,7 +286,6 @@ void GameCenter::reset_achievements() { }; Error GameCenter::show_game_center(Variant p_params) { - ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED); Dictionary params = p_params; @@ -338,7 +329,6 @@ Error GameCenter::show_game_center(Variant p_params) { }; Error GameCenter::request_identity_verification_signature() { - ERR_FAIL_COND_V(!is_authenticated(), ERR_UNAUTHORIZED); GKLocalPlayer *player = [GKLocalPlayer localPlayer]; @@ -365,7 +355,6 @@ Error GameCenter::request_identity_verification_signature() { }; void GameCenter::game_center_closed() { - Dictionary ret; ret["type"] = "show_game_center"; ret["result"] = "ok"; @@ -373,12 +362,10 @@ void GameCenter::game_center_closed() { } 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(); @@ -395,6 +382,6 @@ GameCenter::GameCenter() { authenticated = false; }; -GameCenter::~GameCenter(){}; +GameCenter::~GameCenter() {} #endif diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm index ede60a502d..1169ebc6b4 100644 --- a/platform/iphone/gl_view.mm +++ b/platform/iphone/gl_view.mm @@ -174,7 +174,6 @@ void _focus_out_video() { }; void _unpause_video() { - [_instance.avPlayer play]; video_playing = true; }; @@ -207,14 +206,12 @@ 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) { @@ -234,10 +231,8 @@ static int get_touch_id(UITouch *p_touch) { }; 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) @@ -249,9 +244,7 @@ static int remove_touch(UITouch *p_touch) { }; static void clear_touches() { - for (int i = 0; i < max_touches; i++) { - touches[i] = NULL; }; }; @@ -396,7 +389,6 @@ static void clear_touches() { active = TRUE; printf("start animation!\n"); if (useCADisplayLink) { - // Approximate frame rate // assumes device refreshes at 60 fps int frameInterval = (int)floor(animationInterval * 60.0f); @@ -446,7 +438,6 @@ static void clear_touches() { // Updates the OpenGL view when the timer fires - (void)drawView { - if (!active) { printf("draw view not active!\n"); return; @@ -489,9 +480,7 @@ static void clear_touches() { - (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; @@ -504,12 +493,9 @@ static void clear_touches() { } - (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; @@ -525,9 +511,7 @@ static void clear_touches() { - (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; @@ -541,7 +525,6 @@ static void clear_touches() { } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - OSIPhone::get_singleton()->touches_cancelled(); clear_touches(); }; @@ -599,7 +582,6 @@ static void clear_touches() { NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; switch (routeChangeReason) { - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); NSLog(@"Headphone/Line plugged in"); @@ -609,7 +591,6 @@ static void clear_touches() { 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"); @@ -685,7 +666,6 @@ static void clear_touches() { } - (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(); @@ -695,7 +675,6 @@ static void clear_touches() { 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]; diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.cpp index 3e67362e16..b9d217c9d2 100644 --- a/platform/iphone/godot_iphone.cpp +++ b/platform/iphone/godot_iphone.cpp @@ -46,11 +46,11 @@ int add_cmdline(int p_argc, char **p_args); int iphone_main(int, int, int, char **, String); int iphone_main(int width, int height, 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) { @@ -85,7 +85,6 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) { }; void iphone_finish() { - printf("iphone_finish\n"); Main::cleanup(); delete os; diff --git a/platform/iphone/icloud.h b/platform/iphone/icloud.h index 401a6cbeb8..b11e22fec6 100644 --- a/platform/iphone/icloud.h +++ b/platform/iphone/icloud.h @@ -36,7 +36,6 @@ #include "core/object.h" class ICloud : public Object { - GDCLASS(ICloud, Object); static ICloud *instance; diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index 251f78f2da..c768274b1f 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -58,12 +58,10 @@ void ICloud::_bind_methods() { }; 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(); @@ -284,6 +282,7 @@ Error ICloud::synchronize_key_values() { return FAILED; } } + /* Error ICloud::initial_sync() { //you sometimes have to write something to the store to get it to download new data. go apple! @@ -298,6 +297,7 @@ Error ICloud::initial_sync() { } return synchronize(); } + */ ICloud::ICloud() { ERR_FAIL_COND(instance != NULL); @@ -354,6 +354,6 @@ ICloud::ICloud() { }]; } -ICloud::~ICloud(){}; +ICloud::~ICloud() {} #endif diff --git a/platform/iphone/in_app_store.h b/platform/iphone/in_app_store.h index 493877a5a7..44e65e77ed 100644 --- a/platform/iphone/in_app_store.h +++ b/platform/iphone/in_app_store.h @@ -36,7 +36,6 @@ #include "core/object.h" class InAppStore : public Object { - GDCLASS(InAppStore, Object); static InAppStore *instance; diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index a8a887824f..a2efd6691b 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -57,6 +57,7 @@ NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary]; [numberFormatter release]; return formattedString; } + @end InAppStore *InAppStore::instance = NULL; @@ -80,7 +81,6 @@ void InAppStore::_bind_methods() { @implementation ProductsDelegate - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { - NSArray *products = response.products; Dictionary ret; ret["type"] = "product_info"; @@ -93,7 +93,6 @@ void InAppStore::_bind_methods() { PackedStringArray currency_codes; for (NSUInteger i = 0; i < [products count]; i++) { - SKProduct *product = [products objectAtIndex:i]; const char *str = [product.localizedTitle UTF8String]; @@ -116,7 +115,6 @@ void InAppStore::_bind_methods() { PackedStringArray invalid_ids; for (NSString *ipid in response.invalidProductIdentifiers) { - invalid_ids.push_back(String::utf8([ipid UTF8String])); }; ret["invalid_ids"] = invalid_ids; @@ -129,7 +127,6 @@ void InAppStore::_bind_methods() { @end Error InAppStore::request_product_info(Variant p_params) { - Dictionary params = p_params; ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER); @@ -155,7 +152,6 @@ Error InAppStore::request_product_info(Variant p_params) { }; Error InAppStore::restore_purchases() { - printf("restoring purchases!\n"); [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; @@ -169,10 +165,8 @@ Error InAppStore::restore_purchases() { @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"); @@ -189,11 +183,9 @@ Error InAppStore::restore_purchases() { 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]; @@ -263,7 +255,6 @@ Error InAppStore::restore_purchases() { @end Error InAppStore::purchase(Variant p_params) { - ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE); if (![SKPaymentQueue canMakePayments]) return ERR_UNAVAILABLE; @@ -286,7 +277,6 @@ int InAppStore::get_pending_event_count() { }; Variant InAppStore::pop_pending_event() { - Variant front = pending_events.front()->get(); pending_events.pop_front(); @@ -294,12 +284,10 @@ Variant InAppStore::pop_pending_event() { }; 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]; @@ -307,7 +295,6 @@ void InAppStore::_record_purchase(String product_id) { }; InAppStore *InAppStore::get_singleton() { - return instance; }; @@ -334,6 +321,6 @@ void InAppStore::set_auto_finish_transaction(bool b) { auto_finish_transactions = b; } -InAppStore::~InAppStore(){}; +InAppStore::~InAppStore() {} #endif diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h index 1378fdbbc5..2b29e6f268 100644 --- a/platform/iphone/ios.h +++ b/platform/iphone/ios.h @@ -34,7 +34,6 @@ #include "core/object.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..5923f558a5 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -34,7 +34,6 @@ #import <UIKit/UIKit.h> void iOS::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url); }; @@ -81,4 +80,4 @@ String iOS::get_rate_url(int p_app_id) const { return ret; }; -iOS::iOS(){}; +iOS::iOS() {} diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp index 3ef521a61a..41dd623e69 100644 --- a/platform/iphone/os_iphone.cpp +++ b/platform/iphone/os_iphone.cpp @@ -58,12 +58,10 @@ #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"; @@ -72,14 +70,12 @@ const char *OSIPhone::get_video_driver_name(int p_driver) const { }; 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(); @@ -88,17 +84,14 @@ void OSIPhone::set_data_dir(String p_dir) { }; 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); @@ -174,12 +167,10 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p }; 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) { @@ -189,13 +180,11 @@ void OSIPhone::set_main_loop(MainLoop *p_main_loop) { }; 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]); }; }; @@ -205,7 +194,6 @@ bool OSIPhone::iterate() { }; void OSIPhone::key(uint32_t p_key, bool p_pressed) { - Ref<InputEventKey> ev; ev.instance(); ev->set_echo(false); @@ -217,7 +205,6 @@ void OSIPhone::key(uint32_t p_key, bool p_pressed) { }; 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(); @@ -232,9 +219,7 @@ void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_d }; 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); @@ -245,18 +230,14 @@ void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_ }; 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); }; @@ -270,7 +251,6 @@ void OSIPhone::update_gravity(float p_x, float p_y, float 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)); @@ -333,7 +313,6 @@ void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p }; void OSIPhone::delete_main_loop() { - if (main_loop) { main_loop->finish(); memdelete(main_loop); @@ -343,7 +322,6 @@ void OSIPhone::delete_main_loop() { }; void OSIPhone::finalize() { - delete_main_loop(); memdelete(input); @@ -372,28 +350,24 @@ void OSIPhone::finalize() { event_count = 0; }; -void OSIPhone::set_mouse_show(bool p_show){}; -void OSIPhone::set_mouse_grab(bool p_grab){}; +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::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()); @@ -431,22 +405,18 @@ Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const } 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; @@ -471,7 +441,7 @@ 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) { +void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) { _show_keyboard(p_existing_text); }; @@ -497,17 +467,14 @@ void OSIPhone::set_keep_screen_on(bool 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; @@ -516,7 +483,6 @@ String OSIPhone::get_model_name() const { } Size2 OSIPhone::get_window_size() const { - return Vector2(video_mode.width, video_mode.height); } @@ -527,7 +493,6 @@ Rect2 OSIPhone::get_window_safe_area() const { } bool OSIPhone::has_touchscreen_ui_hint() const { - return true; } @@ -600,7 +565,6 @@ void OSIPhone::vibrate_handheld(int p_duration_ms) { } bool OSIPhone::_check_internal_feature_support(const String &p_feature) { - return p_feature == "mobile"; } diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 96cf041c37..955eb15d57 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -33,7 +33,7 @@ #ifndef OS_IPHONE_H #define OS_IPHONE_H -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" #include "drivers/unix/os_unix.h" #include "game_center.h" @@ -50,7 +50,6 @@ #endif class OSIPhone : public OS_Unix { - private: enum { MAX_MOUSE_COUNT = 8, @@ -99,7 +98,6 @@ private: virtual void finalize(); struct MouseList { - bool pressed[MAX_MOUSE_COUNT]; MouseList() { for (int i = 0; i < MAX_MOUSE_COUNT; i++) @@ -172,7 +170,7 @@ public: virtual bool can_draw() 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(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); virtual void hide_virtual_keyboard(); virtual int get_virtual_keyboard_height() const; diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 465e38e45e..279bcc1226 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -40,7 +40,6 @@ int add_path(int, char **); int add_cmdline(int, char **); int add_path(int p_argc, char **p_args) { - NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; if (!str) return p_argc; @@ -54,13 +53,11 @@ int add_path(int p_argc, char **p_args) { }; int add_cmdline(int p_argc, char **p_args) { - NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; if (!arr) return p_argc; for (int i = 0; i < [arr count]; i++) { - NSString *str = [arr objectAtIndex:i]; if (!str) continue; @@ -81,7 +78,6 @@ int add_cmdline(int p_argc, char **p_args) { @implementation ViewController - (void)didReceiveMemoryWarning { - printf("*********** did receive memory warning!\n"); }; diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h index 625e41f4b9..cadd701636 100644 --- a/platform/iphone/vulkan_context_iphone.h +++ b/platform/iphone/vulkan_context_iphone.h @@ -35,7 +35,6 @@ // #import <UIKit/UIKit.h> class VulkanContextIPhone : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; public: diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm index 701ac0d9bb..44c940dc3a 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/iphone/vulkan_context_iphone.mm @@ -36,7 +36,6 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const { } int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) { - VkIOSSurfaceCreateInfoMVK createInfo; createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; createInfo.pNext = NULL; diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 7239648937..dcf9a46bf9 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -4,6 +4,7 @@ Import("env") javascript_files = [ "audio_driver_javascript.cpp", + "display_server_javascript.cpp", "http_client_javascript.cpp", "javascript_eval.cpp", "javascript_main.cpp", @@ -17,22 +18,22 @@ if env["threads_enabled"]: build = env.add_program(build_targets, javascript_files) js_libraries = [ - "http_request.js", + "native/http_request.js", ] for lib in js_libraries: env.Append(LINKFLAGS=["--js-library", env.File(lib).path]) env.Depends(build, js_libraries) -js_modules = [ - "id_handler.js", +js_pre = [ + "native/id_handler.js", + "native/utils.js", ] -for module in js_modules: - env.Append(LINKFLAGS=["--pre-js", env.File(module).path]) -env.Depends(build, js_modules) +for js in js_pre: + env.Append(LINKFLAGS=["--pre-js", env.File(js).path]) +env.Depends(build, js_pre) engine = [ "engine/preloader.js", - "engine/loader.js", "engine/utils.js", "engine/engine.js", ] @@ -47,11 +48,17 @@ 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"), +] +html_file = "#misc/dist/html/full-size.html" +in_files = [js_wrapped, build[1], html_file] 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( diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 45cb82b351..9c73e5c4c4 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -35,26 +35,22 @@ static JavaScript *javascript_eval; void register_javascript_api() { - 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 +58,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/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index 8f857478e5..b8914414e6 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -30,27 +30,25 @@ #include "audio_driver_javascript.h" +#include "core/project_settings.h" + #include <emscripten.h> AudioDriverJavaScript *AudioDriverJavaScript::singleton = nullptr; const char *AudioDriverJavaScript::get_name() const { - return "JavaScript"; } extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_js_mix() { - AudioDriverJavaScript::singleton->mix_to_js(); } extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_process_capture(float sample) { - AudioDriverJavaScript::singleton->process_capture(sample); } void AudioDriverJavaScript::mix_to_js() { - 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); @@ -61,45 +59,41 @@ void AudioDriverJavaScript::mix_to_js() { } void AudioDriverJavaScript::process_capture(float sample) { - int32_t sample32 = int32_t(sample * 32768.f) * (1U << 16); input_buffer_write(sample32); } Error AudioDriverJavaScript::init() { + int mix_rate = GLOBAL_GET("audio/mix_rate"); + int latency = GLOBAL_GET("audio/output_latency"); /* clang-format off */ _driver_id = EM_ASM_INT({ + const MIX_RATE = $0; + const LATENCY = $1 / 1000; return Module.IDHandler.add({ - 'context': new (window.AudioContext || window.webkitAudioContext), + 'context': new (window.AudioContext || window.webkitAudioContext)({ sampleRate: MIX_RATE, latencyHint: LATENCY}), 'input': null, 'stream': null, 'script': null }); - }); + }, mix_rate, latency); /* clang-format on */ int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode()); + buffer_length = closest_power_of_2((latency * mix_rate / 1000) * channel_count); /* 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); - } + const ctx = ref['context']; + const BUFFER_LENGTH = $1; + const CHANNEL_COUNT = $2; + + var script = ctx.createScriptProcessor(BUFFER_LENGTH, 2, CHANNEL_COUNT); script.connect(ctx.destination); ref['script'] = script; - return script.bufferSize; - }, _driver_id, channel_count); + }, _driver_id, buffer_length, channel_count); /* clang-format on */ if (!buffer_length) { return FAILED; @@ -115,7 +109,6 @@ Error AudioDriverJavaScript::init() { } void AudioDriverJavaScript::start() { - /* clang-format off */ EM_ASM({ const ref = Module.IDHandler.get($0); @@ -163,8 +156,26 @@ void AudioDriverJavaScript::resume() { /* clang-format on */ } -int AudioDriverJavaScript::get_mix_rate() const { +float AudioDriverJavaScript::get_latency() { + /* clang-format off */ + return EM_ASM_DOUBLE({ + const ref = Module.IDHandler.get($0); + var latency = 0; + if (ref && ref['context']) { + const ctx = ref['context']; + if (ctx.baseLatency) { + latency += ctx.baseLatency; + } + if (ctx.outputLatency) { + latency += ctx.outputLatency; + } + } + return latency; + }, _driver_id); + /* clang-format on */ +} +int AudioDriverJavaScript::get_mix_rate() const { /* clang-format off */ return EM_ASM_INT({ const ref = Module.IDHandler.get($0); @@ -174,7 +185,6 @@ int AudioDriverJavaScript::get_mix_rate() const { } 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); @@ -190,23 +200,45 @@ void AudioDriverJavaScript::lock() { void AudioDriverJavaScript::unlock() { } -void AudioDriverJavaScript::finish() { +void AudioDriverJavaScript::finish_async() { + // Close the context, add the operation to the async_finish list in module. + int id = _driver_id; + _driver_id = 0; /* clang-format off */ EM_ASM({ + var ref = Module.IDHandler.get($0); + Module.async_finish.push(new Promise(function(accept, reject) { + if (!ref) { + console.log("Ref not found!", $0, Module.IDHandler); + setTimeout(accept, 0); + } else { + const context = ref['context']; + // Disconnect script and input. + ref['script'].disconnect(); + if (ref['input']) + ref['input'].disconnect(); + ref = null; + context.close().then(function() { + accept(); + }).catch(function(e) { + accept(); + }); + } + })); Module.IDHandler.remove($0); - }, _driver_id); + }, id); /* clang-format on */ +} +void AudioDriverJavaScript::finish() { if (internal_buffer) { memdelete_arr(internal_buffer); internal_buffer = nullptr; } - _driver_id = 0; } Error AudioDriverJavaScript::capture_start() { - input_buffer_init(buffer_length); /* clang-format off */ @@ -236,7 +268,6 @@ Error AudioDriverJavaScript::capture_start() { } Error AudioDriverJavaScript::capture_stop() { - /* clang-format off */ EM_ASM({ var ref = Module.IDHandler.get($0); @@ -262,7 +293,6 @@ Error AudioDriverJavaScript::capture_stop() { } AudioDriverJavaScript::AudioDriverJavaScript() { - _driver_id = 0; internal_buffer = nullptr; buffer_length = 0; diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index f6f2dacd4e..9b26be001e 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -34,7 +34,6 @@ #include "servers/audio_server.h" class AudioDriverJavaScript : public AudioDriver { - float *internal_buffer; int _driver_id; @@ -51,11 +50,13 @@ 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(); virtual void unlock(); virtual void finish(); + void finish_async(); virtual Error capture_start(); virtual Error capture_stop(); diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 9486e10717..81287cead8 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -22,7 +22,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 +57,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"]) @@ -150,7 +150,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 +162,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', 'FS', 'PATH']"]) + # 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..0312efb377 --- /dev/null +++ b/platform/javascript/display_server_javascript.cpp @@ -0,0 +1,1193 @@ +/*************************************************************************/ +/* 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" + +#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) +extern "C" EMSCRIPTEN_KEEPALIVE void _set_canvas_id(uint8_t *p_data, int p_data_size) { + DisplayServerJavaScript *display = DisplayServerJavaScript::get_singleton(); + display->canvas_id.parse_utf8((const char *)p_data, p_data_size); + display->canvas_id = "#" + display->canvas_id; +} + +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) { + DisplayServerJavaScript *display = DisplayServerJavaScript::get_singleton(); + int canvas_x = EM_ASM_INT({ + return Module['canvas'].getBoundingClientRect().x; + }); + int canvas_y = EM_ASM_INT({ + return Module['canvas'].getBoundingClientRect().y; + }); + int canvas_width; + int canvas_height; + emscripten_get_canvas_element_size(display->canvas_id.utf8().get_data(), &canvas_width, &canvas_height); + + double element_width; + double element_height; + emscripten_get_element_css_size(display->canvas_id.utf8().get_data(), &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 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 == 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 (see native/utils.js). +extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p_filec) { + DisplayServerJavaScript *ds = DisplayServerJavaScript::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); +} + +// 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(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) { + 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 (!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 +static const char *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"; + } +} + +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 DisplayServerJavaScript::cursor_set_shape(CursorShape p_shape) { + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + + if (mouse_get_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; +} + +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()) { + 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) { + cursor_set_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() && 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); + } + + 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) { + // set_css_cursor must be called before set_cursor_shape to make the cursor visible + set_css_cursor(godot2dom_cursor(cursor_shape)); + cursor_set_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 cursor_set_shape to make the cursor visible + set_css_cursor(godot2dom_cursor(cursor_shape)); + cursor_set_shape(cursor_shape); + } +} + +DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_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 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); + if (!is_canvas_focused()) { + if (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 EM_ASM_INT({ return 'ontouchstart' in window; }); +} + +// 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 +extern "C" EMSCRIPTEN_KEEPALIVE void update_clipboard(const char *p_text) { + // Only call set_clipboard from OS (sets local clipboard) + DisplayServerJavaScript::get_singleton()->clipboard = p_text; +} + +void DisplayServerJavaScript::clipboard_set(const String &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 DisplayServerJavaScript::clipboard_get() 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 clipboard; +} + +extern "C" EMSCRIPTEN_KEEPALIVE void send_window_event(int p_notification) { + if (p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER || p_notification == DisplayServer::WINDOW_EVENT_MOUSE_EXIT) { + cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER; + } + OS_JavaScript *os = OS_JavaScript::get_singleton(); + if (os->is_finalizing()) + return; // We don't want events anymore. + DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton(); + if (ds && !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) { + /* clang-format off */ + EM_ASM_({ + window.alert(UTF8ToString($0)); + }, p_alert.utf8().get_data()); + /* clang-format on */ +} + +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)); + + /* clang-format off */ + EM_ASM({ + 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 */ +} + +void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_event) { + OS_JavaScript *os = OS_JavaScript::get_singleton(); + if (os->is_finalizing()) + return; // We don't want events anymore. + + // 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) { + /* clang-format off */ + EM_ASM({ + const canvas = Module['canvas']; + var enc = new TextEncoder("utf-8"); + var buffer = new Uint8Array(enc.encode(canvas.id)); + var len = buffer.byteLength; + var out = _malloc(len); + HEAPU8.set(buffer, out); + ccall("_set_canvas_id", + "void", + ["number", "number"], + [out, len] + ); + _free(out); + }); + /* clang-format on */ + + 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.utf8().get_data(), &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 + + /* clang-format off */ + window_set_mode(p_mode); + if (EM_ASM_INT_V({ return Module['resizeCanvasOnStart'] })) { + /* clang-format on */ + window_set_size(p_resolution); + } + + EMSCRIPTEN_RESULT result; + CharString id = canvas_id.utf8(); +#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(id.get_data(), mousedown, mouse_button_callback) + SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mouseup, mouse_button_callback) + SET_EM_CALLBACK(id.get_data(), wheel, wheel_callback) + SET_EM_CALLBACK(id.get_data(), touchstart, touch_press_callback) + SET_EM_CALLBACK(id.get_data(), touchmove, touchmove_callback) + SET_EM_CALLBACK(id.get_data(), touchend, touch_press_callback) + SET_EM_CALLBACK(id.get_data(), touchcancel, touch_press_callback) + SET_EM_CALLBACK(id.get_data(), keydown, keydown_callback) + SET_EM_CALLBACK(id.get_data(), keypress, keypress_callback) + SET_EM_CALLBACK(id.get_data(), 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({ + Module.listeners = {}; + const canvas = Module['canvas']; + const send_window_event = cwrap('send_window_event', null, ['number']); + const notifications = arguments; + (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) { + Module.listeners[event] = send_window_event.bind(null, notifications[index]); + canvas.addEventListener(event, Module.listeners[event]); + }); + // Clipboard + const update_clipboard = cwrap('update_clipboard', null, ['string']); + Module.listeners['paste'] = function(evt) { + update_clipboard(evt.clipboardData.getData('text')); + }; + window.addEventListener('paste', Module.listeners['paste'], false); + Module.listeners['dragover'] = function(ev) { + // Prevent default behavior (which would try to open the file(s)) + ev.preventDefault(); + }; + Module.listeners['drop'] = Module.drop_handler; // Defined in native/utils.js + canvas.addEventListener('dragover', Module.listeners['dragover'], false); + canvas.addEventListener('drop', Module.listeners['drop'], false); + }, + WINDOW_EVENT_MOUSE_ENTER, + WINDOW_EVENT_MOUSE_EXIT, + WINDOW_EVENT_FOCUS_IN, + WINDOW_EVENT_FOCUS_OUT + ); + /* clang-format on */ + + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event); +} + +DisplayServerJavaScript::~DisplayServerJavaScript() { + EM_ASM({ + Object.entries(Module.listeners).forEach(function(kv) { + if (kv[0] == 'paste') { + window.removeEventListener(kv[0], kv[1], true); + } else { + Module['canvas'].removeEventListener(kv[0], kv[1]); + } + }); + Module.listeners = {}; + }); + //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.utf8().get_data(), 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) { + /* clang-format off */ + EM_ASM_({ + document.title = UTF8ToString($0); + }, p_title.utf8().get_data()); + /* clang-format on */ +} + +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) { + emscripten_set_canvas_element_size(canvas_id.utf8().get_data(), 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.utf8().get_data(), 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(windowed_size); + } 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.utf8().get_data(), 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; +} + +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..9860ecdf98 --- /dev/null +++ b/platform/javascript/display_server_javascript.h @@ -0,0 +1,187 @@ +/*************************************************************************/ +/* 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 { + //int video_driver_index; + + Vector2 windowed_size; + + ObjectID window_attached_instance_id = {}; + + Ref<InputEventKey> deferred_key_event; + CursorShape cursor_shape = CURSOR_ARROW; + String cursors[CURSOR_MAX]; + Map<CursorShape, Vector<Variant>> cursors_cache; + Point2 touches[32]; + + Point2i last_click_pos = Point2(-100, -100); // TODO check this again. + double last_click_ms = 0; + int last_click_button_index = -1; + + 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); + +protected: + virtual int get_current_video_driver() const; + +public: + // Override return type to make writing static callbacks less tedious. + static DisplayServerJavaScript *get_singleton(); + + WindowMode window_mode = WINDOW_MODE_WINDOWED; + + String clipboard; + String canvas_id; + + Callable window_event_callback; + Callable input_event_callback; + Callable input_text_callback; + Callable drop_files_callback; + + // from DisplayServer + virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); + virtual bool has_feature(Feature p_feature) const; + virtual String get_name() const; + + // cursor + 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()); + + // mouse + virtual void mouse_set_mode(MouseMode p_mode); + virtual MouseMode mouse_get_mode() const; + + // touch + virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + + // clipboard + virtual void clipboard_set(const String &p_text); + virtual String clipboard_get() const; + + // screen + 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 Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + + // windows + virtual Vector<DisplayServer::WindowID> get_window_list() const; + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; + + 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_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_title(const String &p_title, 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); + + 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 void window_set_transient(WindowID p_window, WindowID p_parent); + + 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_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_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; // FIXME: Find clearer name for this. + + 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 bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const; + + 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_request_attention(WindowID p_window = MAIN_WINDOW_ID); + virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID); + + virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const; + + virtual bool can_any_window_draw() const; + + // events + virtual void process_events(); + + // icon + virtual void set_icon(const Ref<Image> &p_icon); + + // others + virtual void swap_buffers(); + + 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/engine/engine.js b/platform/javascript/engine/engine.js index 6d7509377f..d709422abb 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,43 @@ 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; + }; 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; + var me = this; + initPromise = new Promise(function(resolve, reject) { + config['locateFile'] = Utils.createLocateRewrite(loadPath); + config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise); + Godot(config).then(function(module) { + me.rtenv = module; if (unloadAfterInit) { - loadPromise = null; + unload(); } resolve(); + config = null; }); }); return initPromise; @@ -76,33 +80,72 @@ 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]; + } + me.rtenv['locale'] = locale; + me.rtenv['canvas'] = me.canvas; + me.rtenv['thisProgram'] = me.executableName; + me.rtenv['resizeCanvasOnStart'] = me.resizeCanvasOnStart; + me.rtenv['noExitRuntime'] = true; + me.rtenv['onExecute'] = me.onExecute; + me.rtenv['onExit'] = function(code) { + if (me.onExit) + me.onExit(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 +161,85 @@ 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) { + if (this.rtenv) + this.rtenv.onExecute = 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); + } + // 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; 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/utils.js b/platform/javascript/engine/utils.js index fdff90a923..0c97b38199 100644 --- a/platform/javascript/engine/utils.js +++ b/platform/javascript/engine/utils.js @@ -27,24 +27,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) { diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 39faae2d17..3573ddac95 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; @@ -148,11 +148,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 +162,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 +192,6 @@ public: }; class EditorExportPlatformJavaScript : public EditorExportPlatform { - GDCLASS(EditorExportPlatformJavaScript, EditorExportPlatform); Ref<ImageTexture> logo; @@ -198,7 +199,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; @@ -230,7 +231,6 @@ public: virtual Ref<Texture2D> get_run_icon() const; virtual void get_platform_features(List<String> *r_features) { - r_features->push_back("web"); r_features->push_back(get_os_name()); } @@ -238,22 +238,28 @@ public: virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { } + String get_debug_protocol() const { 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_DEBUG_ENABLED", p_debug ? "true" : "false"); + current_line = current_line.replace("$GODOT_ARGS", flags_json); str_export += current_line + "\n"; } @@ -265,7 +271,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,7 +287,6 @@ 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"), "")); @@ -292,22 +296,18 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op } 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 +342,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 +367,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 +395,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 +424,18 @@ 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") { - file = p_path.get_file().get_basename() + ".wasm"; } @@ -458,7 +453,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 +462,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 +516,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 +544,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(); @@ -605,7 +595,6 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese } Ref<Texture2D> EditorExportPlatformJavaScript::get_run_icon() const { - return run_icon; } @@ -621,7 +610,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 +623,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 +640,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/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..3a72b10dd4 100644 --- a/platform/javascript/javascript_eval.cpp +++ b/platform/javascript/javascript_eval.cpp @@ -34,14 +34,12 @@ #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) { - p_arr->resize(p_len); *r_write = p_arr->write; return p_arr->ptrw(); } Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { - union { bool b; double d; diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 815bc7e456..fd61c46e63 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -34,38 +34,77 @@ #include <emscripten/emscripten.h> -extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) { +static OS_JavaScript *os = nullptr; + +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 main_loop_callback() { + if (os->main_loop_iterate()) { + emscripten_cancel_main_loop(); // Cancel current loop and wait for finalize_async. + EM_ASM({ + // This will contain the list of operations that need to complete before cleanup. + Module.async_finish = []; + }); + os->get_main_loop()->finish(); + os->finalize_async(); // Will add all the async finish functions. + EM_ASM({ + Promise.all(Module.async_finish).then(function() { + Module.async_finish = []; + ccall("cleanup_after_sync", null, []); + }); + }); + } +} + +extern "C" EMSCRIPTEN_KEEPALIVE void cleanup_after_sync() { + emscripten_set_main_loop(exit_callback, -1, false); +} +extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) { String idbfs_err = String::utf8(p_idbfs_err); if (!idbfs_err.empty()) { print_line("IndexedDB not available: " + idbfs_err); } - OS_JavaScript *os = OS_JavaScript::get_singleton(); os->set_idb_available(idbfs_err.empty()); + // TODO: Check error return value. + Main::setup2(); // Manual second phase. // Ease up compatibility. ResourceLoader::set_abort_on_missing_resources(false); Main::start(); - os->run_async(); + os->get_main_loop()->init(); + emscripten_resume_main_loop(); + // Immediately run the first iteration. + // We are inside an animation frame, we want to immediately draw on the newly setup canvas. + main_loop_callback(); } int main(int argc, char *argv[]) { + os = new OS_JavaScript(); + Main::setup(argv[0], argc - 1, &argv[1], false); + emscripten_set_main_loop(main_loop_callback, -1, false); + emscripten_pause_main_loop(); // Will need to wait for FS sync. // Sync from persistent state into memory and then // run the 'main_after_fs_sync' function. /* clang-format off */ - EM_ASM( + EM_ASM({ FS.mkdir('/userfs'); FS.mount(IDBFS, {}, '/userfs'); FS.syncfs(true, function(err) { - ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]) + requestAnimationFrame(function() { + ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]); + }); }); - ); + }); /* clang-format on */ - new OS_JavaScript(argc, argv); - // TODO: Check error return value. - Main::setup(argv[0], argc - 1, &argv[1]); - return 0; // Continued async in main_after_fs_sync() from the syncfs() callback. } diff --git a/platform/javascript/http_request.js b/platform/javascript/native/http_request.js index f621689f9d..f621689f9d 100644 --- a/platform/javascript/http_request.js +++ b/platform/javascript/native/http_request.js diff --git a/platform/javascript/id_handler.js b/platform/javascript/native/id_handler.js index 67d29075b8..67d29075b8 100644 --- a/platform/javascript/id_handler.js +++ b/platform/javascript/native/id_handler.js diff --git a/platform/javascript/native/utils.js b/platform/javascript/native/utils.js new file mode 100644 index 0000000000..95585d26ae --- /dev/null +++ b/platform/javascript/native/utils.js @@ -0,0 +1,204 @@ +/*************************************************************************/ +/* utils.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. */ +/*************************************************************************/ + +Module['copyToFS'] = function(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 !== ERRNO_CODES.ENOENT) { // '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+'}); +} + +Module.drop_handler = (function() { + var upload = []; + var uploadPromises = []; + var uploadCallback = null; + + function readFilePromise(entry, path) { + return new Promise(function(resolve, reject) { + entry.file(function(file) { + var reader = new FileReader(); + reader.onload = function() { + var 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']; + upload.push(f); + resolve() + }; + reader.onerror = function() { + console.log("Error reading file"); + reject(); + } + + reader.readAsArrayBuffer(file); + + }, function(err) { + console.log("Error!"); + reject(); + }); + }); + } + + function readDirectoryPromise(entry) { + return new Promise(function(resolve, reject) { + var reader = entry.createReader(); + reader.readEntries(function(entries) { + for (var i = 0; i < entries.length; i++) { + var ent = entries[i]; + if (ent.isDirectory) { + uploadPromises.push(readDirectoryPromise(ent)); + } else if (ent.isFile) { + uploadPromises.push(readFilePromise(ent)); + } + } + resolve(); + }); + }); + } + + function processUploadsPromises(resolve, reject) { + if (uploadPromises.length == 0) { + resolve(); + return; + } + uploadPromises.pop().then(function() { + setTimeout(function() { + processUploadsPromises(resolve, reject); + //processUploadsPromises.bind(null, resolve, reject) + }, 0); + }); + } + + function dropFiles(files) { + var args = files || []; + var argc = args.length; + var argv = stackAlloc((argc + 1) * 4); + for (var i = 0; i < argc; i++) { + HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i]); + } + HEAP32[(argv >> 2) + argc] = 0; + // Defined in display_server_javascript.cpp + ccall('_drop_files_callback', 'void', ['number', 'number'], [argv, argc]); + } + + return function(ev) { + ev.preventDefault(); + if (ev.dataTransfer.items) { + // Use DataTransferItemList interface to access the file(s) + for (var i = 0; i < ev.dataTransfer.items.length; i++) { + const item = ev.dataTransfer.items[i]; + var entry = null; + if ("getAsEntry" in item) { + entry = item.getAsEntry(); + } else if ("webkitGetAsEntry" in item) { + entry = item.webkitGetAsEntry(); + } + if (!entry) { + console.error("File upload not supported"); + } else if (entry.isDirectory) { + uploadPromises.push(readDirectoryPromise(entry)); + } else if (entry.isFile) { + uploadPromises.push(readFilePromise(entry)); + } else { + console.error("Unrecognized entry...", entry); + } + } + } else { + console.error("File upload not supported"); + } + uploadCallback = new Promise(processUploadsPromises).then(function() { + const DROP = "/tmp/drop-" + parseInt(Math.random() * Math.pow(2, 31)) + "/"; + var drops = []; + var files = []; + upload.forEach((elem) => { + var path = elem['path']; + Module['copyToFS'](DROP + path, elem['data']); + var idx = path.indexOf("/"); + if (idx == -1) { + // Root file + drops.push(DROP + path); + } else { + // Subdir + var sub = path.substr(0, idx); + idx = sub.indexOf("/"); + if (idx < 0 && drops.indexOf(DROP + sub) == -1) { + drops.push(DROP + sub); + } + } + files.push(DROP + path); + }); + uploadPromises = []; + upload = []; + dropFiles(drops); + var dirs = [DROP.substr(0, DROP.length -1)]; + files.forEach(function (file) { + FS.unlink(file); + var dir = file.replace(DROP, ""); + var 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 = dirs.sort(function(a, b) { + var al = (a.match(/\//g) || []).length; + var bl = (b.match(/\//g) || []).length; + if (al > bl) + return -1; + else if (al < bl) + return 1; + return 0; + }); + dirs.forEach(function(dir) { + FS.rmdir(dir); + }); + }); + } +})(); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index ad06aef86e..ad4b5a5afa 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -30,668 +30,23 @@ #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; @@ -699,352 +54,53 @@ bool OS_JavaScript::has_touchscreen_ui_hint() const { /* 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(); -} - // 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); - } + DisplayServerJavaScript::register_javascript_driver(); - 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; - } - - 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); + EM_ASM({ + 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() { + 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(); } 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; @@ -1063,72 +119,59 @@ bool OS_JavaScript::main_loop_iterate() { } } - 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; - } - - 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() { + if (main_loop) { + memdelete(main_loop); + } + main_loop = nullptr; +} - memdelete(main_loop); +void OS_JavaScript::finalize_async() { + finalizing = true; + audio_driver_javascript.finish_async(); } void OS_JavaScript::finalize() { - - memdelete(input); + delete_main_loop(); } // 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); + /* clang-format off */ + int failed = EM_ASM_INT({ + const json_args = UTF8ToString($0); + const args = JSON.parse(json_args); + if (Module["onExecute"]) { + Module["onExecute"](args); + return 0; + } + return 1; + }, json_args.utf8().get_data()); + /* clang-format on */ + 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,79 +183,11 @@ 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_({ @@ -1223,27 +198,30 @@ Error OS_JavaScript::shell_open(String p_uri) { } 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 { +String OS_JavaScript::get_cache_path() const { + return "/home/web_user/.cache"; +} + +String OS_JavaScript::get_config_path() const { + return "/home/web_user/.config"; +} - return "/"; +String OS_JavaScript::get_data_path() const { + return "/home/web_user/.local/share"; } 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(); @@ -1253,42 +231,21 @@ void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags } void OS_JavaScript::set_idb_available(bool p_idb_available) { - idb_available = p_idb_available; } 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; - - main_loop = nullptr; - - idb_available = false; - sync_wait_time = -1; +void OS_JavaScript::initialize_joypads() { +} +OS_JavaScript::OS_JavaScript() { AudioDriverManager::add_driver(&audio_driver_javascript); Vector<Logger *> loggers; diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 81fe4cf0cc..f0f18b44f8 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -32,67 +32,27 @@ #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 { - - 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; + MainLoop *main_loop = nullptr; 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 finalizing = false; + bool idb_available = false; + int64_t sync_wait_time = -1; + int64_t last_sync_check_time = -1; static void main_loop_callback(); static void file_access_close_callback(const String &p_file, int p_flags); protected: - 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 initialize(); virtual void set_main_loop(MainLoop *p_main_loop); virtual void delete_main_loop(); @@ -105,65 +65,38 @@ 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 void initialize_joypads(); virtual bool has_touchscreen_ui_hint() const; - 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(); + void finalize_async(); 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; - 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; - virtual String get_resource_dir() const; + virtual String get_cache_path() const; + virtual String get_config_path() const; + virtual String get_data_path() const; virtual String get_user_data_dir() const; void set_idb_available(bool p_idb_available); virtual bool is_userfs_persistent() const; - OS_JavaScript(int p_argc, char *p_argv[]); + void resume_audio(); + bool is_finalizing() { return finalizing; } + + OS_JavaScript(); }; #endif 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..b3553e961a 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -63,8 +63,9 @@ 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()); char **strings = backtrace_symbols(bt_buffer, size); @@ -85,8 +86,9 @@ static void handle_crash(int sig) { snprintf(fname, 1024, "%s", demangled); } - if (demangled) + if (demangled) { free(demangled); + } } } @@ -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..07fa06bc06 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -217,15 +217,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..1e46d3222d 100644 --- a/platform/linuxbsd/detect_prime_x11.cpp +++ b/platform/linuxbsd/detect_prime_x11.cpp @@ -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 6049dbf4d6..a0954600a2 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -127,6 +127,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { return false; } + String DisplayServerX11::get_name() const { return "X11"; } @@ -148,8 +149,9 @@ void DisplayServerX11::alert(const String &p_alert, const String &p_title) { } } - if (program.length()) + if (program.length()) { break; + } } List<String> args; @@ -204,11 +206,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 +246,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 +278,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 +305,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); @@ -350,20 +357,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 +380,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 +398,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,19 +406,17 @@ 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? */ @@ -447,7 +451,6 @@ int DisplayServerX11::mouse_get_button_state() const { } void DisplayServerX11::clipboard_set(const String &p_text) { - _THREAD_SAFE_METHOD_ internal_clipboard = p_text; @@ -456,7 +459,6 @@ void DisplayServerX11::clipboard_set(const String &p_text) { } static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) { - String ret; Atom type; @@ -467,7 +469,6 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x Window Sown = XGetSelectionOwner(x11_display, p_source); if (Sown == x11_window) { - return p_internal_clipboard; }; @@ -503,8 +504,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); } @@ -527,7 +529,6 @@ static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_di } String DisplayServerX11::clipboard_get() const { - _THREAD_SAFE_METHOD_ String ret; @@ -541,21 +542,22 @@ String DisplayServerX11::clipboard_get() const { } 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 +598,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 +614,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 +650,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 +679,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); @@ -687,7 +692,6 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, u } void DisplayServerX11::delete_sub_window(WindowID p_id) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_id)); @@ -718,7 +722,6 @@ void DisplayServerX11::delete_sub_window(WindowID 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 +729,16 @@ 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 { - return INVALID_WINDOW_ID; } void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -752,7 +752,6 @@ void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window } 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 +760,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 +768,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 +784,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 +792,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 +805,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 +837,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); @@ -875,7 +872,6 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent } Point2i DisplayServerX11::window_get_position(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); @@ -885,7 +881,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 +913,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)); @@ -950,8 +944,8 @@ void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_windo 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 +955,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)); @@ -993,8 +986,8 @@ void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_windo 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 +997,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 +1007,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); @@ -1059,22 +1052,23 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { 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()); @@ -1105,7 +1099,6 @@ Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { } bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), false); @@ -1140,13 +1133,16 @@ 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) + if (found_wm_act_max_horz || found_wm_act_max_vert) { return true; + } } XFree(atoms); } @@ -1155,7 +1151,6 @@ bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { } 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 +1182,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]; @@ -1272,7 +1266,6 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { } void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1333,7 +1326,6 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_MAXIMIZED: { - _set_wm_maximized(p_window, false); } break; } @@ -1378,7 +1370,6 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { wd.fullscreen = true; } break; case WINDOW_MODE_MAXIMIZED: { - _set_wm_maximized(p_window, true); } break; @@ -1386,7 +1377,6 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); @@ -1427,10 +1417,12 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c bool found_wm_max_vert = false; for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_max_horz) + if (atoms[i] == wm_max_horz) { found_wm_max_horz = true; - if (atoms[i] == wm_max_vert) + } + if (atoms[i] == wm_max_vert) { found_wm_max_vert = true; + } if (found_wm_max_horz && found_wm_max_vert) { retval = true; @@ -1471,8 +1463,11 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c if (result == Success && data) { long *state = (long *)data; - if (state[0] == WM_IconicState) + if (state[0] == WM_IconicState) { + XFree(data); return WINDOW_MODE_MINIMIZED; + } + XFree(data); } } @@ -1482,7 +1477,6 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c } void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); @@ -1523,7 +1517,6 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } break; case WINDOW_FLAG_BORDERLESS: { - Hints hints; Atom property; hints.flags = 2; @@ -1537,7 +1530,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); @@ -1570,8 +1562,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); @@ -1579,15 +1571,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; @@ -1605,7 +1594,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: { @@ -1619,7 +1607,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)); @@ -1646,7 +1633,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)); @@ -1668,12 +1654,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()) { @@ -1686,7 +1671,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)); @@ -1694,8 +1678,9 @@ 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; + } if (p_active) { XSetICFocus(wd.xic); @@ -1704,8 +1689,8 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win 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)); @@ -1713,8 +1698,9 @@ 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); @@ -1725,7 +1711,6 @@ void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_ } void DisplayServerX11::cursor_set_shape(CursorShape p_shape) { - _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX(p_shape, CURSOR_MAX); @@ -1748,15 +1733,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) { @@ -1861,41 +1846,109 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape } } -DisplayServerX11::LatinKeyboardVariant DisplayServerX11::get_latin_keyboard_variant() const { - _THREAD_SAFE_METHOD_ +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); - XkbDescRec *xkbdesc = XkbAllocKeyboard(); - ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY); + 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; @@ -1907,8 +1960,9 @@ DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, //Keep trying to read the property until there are no //bytes unread. do { - if (ret != nullptr) + if (ret != nullptr) { XFree(ret); + } XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, @@ -1924,36 +1978,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 @@ -1961,7 +2015,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) { @@ -1974,7 +2027,6 @@ unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button } void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) { - WindowData wd = windows[p_window]; // X11 functions don't know what const is XKeyEvent *xkeyevent = p_event; @@ -2018,7 +2070,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; @@ -2038,8 +2089,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); @@ -2074,7 +2126,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; @@ -2082,7 +2134,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'; @@ -2150,7 +2201,6 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, // difference in time is below a threshold. if (xkeyevent->type != KeyPress) { - p_echo = false; // make sure there are events pending, @@ -2189,8 +2239,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); @@ -2207,29 +2258,29 @@ 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::_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; @@ -2240,7 +2291,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 @@ -2305,7 +2355,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; @@ -2341,8 +2390,8 @@ 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::process_events() { _THREAD_SAFE_METHOD_ do_mouse_warp = false; @@ -2350,6 +2399,10 @@ void DisplayServerX11::process_events() { // 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; + xi.pressure = 0; + xi.tilt = Vector2(); + xi.pressure_supported = false; + while (XPending(x11_display) > 0) { XEvent event; XNextEvent(x11_display, &event); @@ -2369,9 +2422,7 @@ void DisplayServerX11::process_events() { } 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); @@ -2396,9 +2447,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; @@ -2411,24 +2459,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 @@ -2464,7 +2529,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; @@ -2475,39 +2539,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; } @@ -2552,11 +2616,11 @@ void DisplayServerX11::process_events() { // 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, @@ -2577,13 +2641,12 @@ void DisplayServerX11::process_events() { case FocusOut: window_has_focus = false; - InputFilter::get_singleton()->release_pressed_events(); + Input::get_singleton()->release_pressed_events(); _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); window_focused = false; 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. @@ -2600,13 +2663,12 @@ 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 @@ -2620,7 +2682,6 @@ void DisplayServerX11::process_events() { break; case ButtonPress: case ButtonRelease: { - /* exit in case of a mouse button press */ last_timestamp = event.xbutton.time; if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -2634,10 +2695,11 @@ 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()); @@ -2645,13 +2707,10 @@ void DisplayServerX11::process_events() { mb->set_pressed((event.type == ButtonPress)); if (event.type == ButtonPress) { - 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; @@ -2668,11 +2727,10 @@ void DisplayServerX11::process_events() { } } - 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. @@ -2728,7 +2786,6 @@ void DisplayServerX11::process_events() { } if (!last_mouse_pos_valid) { - last_mouse_pos = pos; last_mouse_pos_valid = true; } @@ -2760,7 +2817,11 @@ 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 :) @@ -2770,8 +2831,8 @@ void DisplayServerX11::process_events() { 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()); + Input::get_singleton()->set_mouse_position(posi); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); mm->set_relative(rel); @@ -2781,13 +2842,13 @@ 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 (window_has_focus || !mouse_mode_grab) { + Input::get_singleton()->accumulate_input_event(mm); + } } break; case KeyPress: case KeyRelease: { - last_timestamp = event.xkey.time; // key event is a little complex, so @@ -2795,7 +2856,6 @@ void DisplayServerX11::process_events() { _handle_key_event(window_id, (XKeyEvent *)&event); } break; case SelectionRequest: { - XSelectionRequestEvent *req; XEvent e, respond; e = event; @@ -2818,7 +2878,6 @@ void DisplayServerX11::process_events() { 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); @@ -2841,8 +2900,9 @@ void DisplayServerX11::process_events() { } else { char *targetname = XGetAtomName(x11_display, req->target); printf("No Target '%s'\n", targetname); - if (targetname) + if (targetname) { XFree(targetname); + } respond.xselection.property = None; } @@ -2859,7 +2919,6 @@ void DisplayServerX11::process_events() { 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); @@ -2898,7 +2957,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]; @@ -2906,10 +2964,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; @@ -2928,13 +2986,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; @@ -2959,7 +3017,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); @@ -2975,7 +3032,7 @@ void DisplayServerX11::process_events() { */ } - InputFilter::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_accumulated_events(); } void DisplayServerX11::release_rendering_thread() { @@ -2991,7 +3048,6 @@ void DisplayServerX11::_update_context(WindowData &wd) { XClassHint *classHint = XAllocClassHint(); if (classHint) { - CharString name_str; switch (context) { case CONTEXT_EDITOR: @@ -3024,8 +3080,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; @@ -3034,6 +3090,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."); } @@ -3109,8 +3166,9 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) { 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); @@ -3134,12 +3192,10 @@ 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)); } DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect) { - //Create window long visualMask = VisualScreenMask; @@ -3221,7 +3277,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); if (xim && xim_style) { - 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) { WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); @@ -3234,7 +3289,6 @@ 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"); } @@ -3246,9 +3300,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u windows[id] = wd; { - if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) { - XSizeHints *xsh; xsh = XAllocSizeHints(); @@ -3312,7 +3364,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u } if (id != MAIN_WINDOW_ID) { - XSizeHints my_hints = XSizeHints(); my_hints.flags = PPosition | PSize; /* I want to specify position and size */ @@ -3362,15 +3413,13 @@ 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; @@ -3378,7 +3427,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; } @@ -3413,7 +3461,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. @@ -3490,10 +3537,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; } @@ -3528,7 +3573,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); @@ -3550,7 +3594,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; } @@ -3562,7 +3605,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; } @@ -3603,8 +3645,10 @@ 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)); for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, main_window); @@ -3614,7 +3658,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode //create RenderingDevice if used #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - //temporary rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); @@ -3660,7 +3703,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", @@ -3795,8 +3837,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode r_error = OK; } -DisplayServerX11::~DisplayServerX11() { +DisplayServerX11::~DisplayServerX11() { //destroy all windows for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { #ifdef VULKAN_ENABLED @@ -3815,25 +3857,28 @@ DisplayServerX11::~DisplayServerX11() { //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) { @@ -3841,12 +3886,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..f01b9a2323 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -35,7 +35,7 @@ #include "servers/display_server.h" -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "drivers/alsa/audio_driver_alsa.h" #include "drivers/alsamidi/midi_driver_alsamidi.h" @@ -170,10 +170,13 @@ class DisplayServerX11 : public DisplayServer { 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; @@ -324,7 +327,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..3ed64e9d46 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -37,7 +37,6 @@ #include "os_linuxbsd.h" int main(int argc, char *argv[]) { - OS_LinuxBSD os; setlocale(LC_CTYPE, ""); @@ -52,8 +51,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..5edaf35c50 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 }; @@ -369,7 +346,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 +356,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 +411,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 +431,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 +447,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 +465,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 |= Input::HAT_MASK_LEFT; + } else { + joy->dpad |= Input::HAT_MASK_RIGHT; + } + } 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 |= Input::HAT_MASK_UP; + } else { + joy->dpad |= Input::HAT_MASK_DOWN; + } + } 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..77512b1a9e 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,7 +180,6 @@ static _XTranslatePair _xkeysym_to_keycode[] = { }; struct _TranslatePair { - unsigned int keysym; unsigned int keycode; }; @@ -301,10 +299,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 +311,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,7 +349,6 @@ KeySym KeyMappingX11::get_keysym(unsigned int p_code) { // Tables taken from FOX toolkit struct _XTranslateUnicodePair { - KeySym keysym; unsigned int unicode; }; @@ -1125,37 +1120,41 @@ 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; }; @@ -1919,24 +1918,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..09a5eca914 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -55,21 +55,18 @@ #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 +81,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 @@ -99,24 +96,21 @@ void OS_LinuxBSD::finalize() { } 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 +123,26 @@ String OS_LinuxBSD::get_name() const { } Error OS_LinuxBSD::shell_open(String p_uri) { - Error ok; List<String> args; args.push_back(p_uri); ok = execute("xdg-open", args, false); - if (ok == OK) + if (ok == OK) { return OK; + } ok = execute("gnome-open", args, false); - if (ok == OK) + if (ok == OK) { return OK; + } ok = execute("kde-open", args, false); return ok; } 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 +153,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 +163,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 +173,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 +213,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 +234,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 +346,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..4295721c68 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,7 +43,6 @@ #include "servers/rendering_server.h" class OS_LinuxBSD : public OS_Unix { - virtual void delete_main_loop(); bool force_quit; 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/context_gl_osx.h b/platform/osx/context_gl_osx.h index 7e436c5e36..cce00fb35f 100644 --- a/platform/osx/context_gl_osx.h +++ b/platform/osx/context_gl_osx.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/dir_access_osx.mm b/platform/osx/dir_access_osx.mm index 66ea380903..7791ba5407 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]; diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index 86ceb51763..fddb1d0ca6 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) @@ -281,7 +281,11 @@ public: virtual bool get_swap_ok_cancel(); - 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(); virtual void force_process_and_drop_events(); diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 074fc3be0d..4a94e09c1c 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -55,6 +55,10 @@ #include <QuartzCore/CAMetalLayer.h> #endif +#ifndef NSAppKitVersionNumber10_14 +#define NSAppKitVersionNumber10_14 1671 +#endif + #define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton())) static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) { @@ -70,7 +74,7 @@ static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_loc p_wd.mouse_pos.x = p.x * p_backingScaleFactor; p_wd.mouse_pos.y = (contentRect.size.height - p.y) * p_backingScaleFactor; 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,7 +124,7 @@ 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); } } @@ -145,6 +149,7 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { Variant meta; bool checkable; } + @end @implementation GlobalMenuItem @@ -273,13 +278,17 @@ 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()) { @@ -305,7 +314,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (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,8 +326,9 @@ 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; @@ -378,8 +390,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (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]; #if defined(OPENGL_ENABLED) @@ -420,23 +433,40 @@ 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); + 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 +476,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 +488,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; @@ -755,7 +789,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 { @@ -804,15 +838,15 @@ 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()); + mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); Vector2i relativeMotion = Vector2i(); relativeMotion.x = [event deltaX] * backingScaleFactor; relativeMotion.y = [event deltaY] * backingScaleFactor; 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 { @@ -887,7 +921,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i ev->set_position(_get_mouse_pos(wd, [event locationInWindow], backingScaleFactor)); ev->set_factor([event magnification] + 1.0); - InputFilter::get_singleton()->accumulate_input_event(ev); + Input::get_singleton()->accumulate_input_event(ev); } - (void)viewDidChangeBackingProperties { @@ -917,7 +951,6 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i } static bool isNumpadKey(unsigned int key) { - static const unsigned int table[] = { 0x41, /* kVK_ANSI_KeypadDecimal */ 0x43, /* kVK_ANSI_KeypadMultiply */ @@ -950,7 +983,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, @@ -1353,7 +1385,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 +1397,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 +1412,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 { @@ -1420,6 +1452,7 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy @interface GodotWindow : NSWindow { } + @end @implementation GodotWindow @@ -1652,7 +1685,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(); } } } @@ -2143,7 +2177,7 @@ Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const { Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * displayScale - _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) * displayScale; return Rect2i(position, size); } @@ -2475,7 +2509,8 @@ 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]]; @@ -2770,23 +2805,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 +2987,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(); + } - layout = LATIN_KEYBOARD_QWERTY; + ERR_FAIL_INDEX(p_index, kbd_layouts.size()); - 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); + NSString *cur_name = [NSString stringWithUTF8String:kbd_layouts[p_index].name.utf8().get_data()]; - 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_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]; - [test release]; + 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]; +} - keyboard_layout_dirty = false; - return layout; +int DisplayServerOSX::keyboard_get_current_layout() const { + if (keyboard_layout_dirty) { + _update_keyboard_layouts(); } - return layout; + return current_layout; +} + +String DisplayServerOSX::keyboard_get_layout_language(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].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(); } } @@ -3084,7 +3196,7 @@ void DisplayServerOSX::process_events() { if (!drop_events) { _process_key_events(); - InputFilter::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_accumulated_events(); } [autoreleasePool drain]; @@ -3393,7 +3505,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; @@ -3513,7 +3625,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,7 +3635,10 @@ 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_size(0).width - p_resolution.width) / 2, + (screen_get_size(0).height - p_resolution.height) / 2); + WindowID main_window = _create_window(p_mode, Rect2i(window_position, p_resolution)); for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, main_window); @@ -3570,7 +3684,6 @@ DisplayServerOSX::~DisplayServerOSX() { //destroy drivers #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - if (rendering_device_vulkan) { rendering_device_vulkan->finalize(); memdelete(rendering_device_vulkan); diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index c2df9c7082..c9b01ebbb4 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -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,6 +67,28 @@ 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++) { + CharType 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); @@ -89,7 +112,6 @@ public: 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) { - r_features->push_back("pc"); r_features->push_back("s3tc"); r_features->push_back("OSX"); @@ -117,7 +139,6 @@ 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"), "")); @@ -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()); @@ -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/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,139 @@ 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; + zipfi.tmz_date.tm_sec = time.sec; + zipfi.tmz_date.tm_year = date.year; + zipfi.dosDate = 0; + zipfi.external_fa = (is_executable ? 0755 : 0644) << 16L; + 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 +885,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/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 +936,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..93d0d6168c 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -36,7 +36,6 @@ #include <unistd.h> 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); 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/os_osx.h b/platform/osx/os_osx.h index d2c67cff9f..9204a145bf 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" diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 49cb056c9f..dba96ccfcd 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,7 +139,6 @@ void OS_OSX::initialize() { } void OS_OSX::finalize() { - #ifdef COREMIDI_ENABLED midi_driver.close(); #endif @@ -269,10 +260,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); 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/godot_server.cpp b/platform/server/godot_server.cpp index df49bfaebf..32bd943ac3 100644 --- a/platform/server/godot_server.cpp +++ b/platform/server/godot_server.cpp @@ -32,7 +32,6 @@ #include "os_server.h" int main(int argc, char *argv[]) { - OS_Server os; Error err = Main::setup(argv[0], argc - 1, &argv[1]); diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp index 0fda0663a2..fbe526ef6d 100644 --- a/platform/server/os_server.cpp +++ b/platform/server/os_server.cpp @@ -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/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..6f333b8e6a 100644 --- a/platform/uwp/context_egl_uwp.h +++ b/platform/uwp/context_egl_uwp.h @@ -41,7 +41,6 @@ using namespace Windows::UI::Core; class ContextEGL_UWP { - public: enum Driver { GLES_2_0, diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 06bf738dc1..cb4716bd65 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -83,7 +83,6 @@ static const char *uwp_device_capabilities[] = { }; class AppxPackager { - enum { FILE_HEADER_MAGIC = 0x04034b50, DATA_DESCRIPTOR_MAGIC = 0x08074b50, @@ -107,29 +106,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 +186,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 +198,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 +227,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 +249,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(); - if (types.has(ext)) continue; + if (types.has(ext)) { + continue; + } types[ext] = content_type(ext); @@ -288,7 +276,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 +318,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 +369,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 +438,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 +467,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 +479,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 +489,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 +504,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 +523,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 +535,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 +565,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 +631,6 @@ AppxPackager::~AppxPackager() {} //////////////////////////////////////////////////////////////////// class EditorExportPlatformUWP : public EditorExportPlatform { - GDCLASS(EditorExportPlatformUWP, EditorExportPlatform); Ref<ImageTexture> logo; @@ -663,9 +642,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 +657,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 +667,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 +727,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 +847,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 +894,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 +912,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 @@ -983,7 +962,6 @@ class EditorExportPlatformUWP : public EditorExportPlatform { } static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { - AppxPackager *packager = (AppxPackager *)p_userdata; String dst_path = p_path.replace_first("res://", "game/"); @@ -1054,13 +1032,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)); @@ -1090,7 +1068,6 @@ public: } virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - String err; bool valid = false; @@ -1161,37 +1138,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"; } @@ -1201,15 +1178,15 @@ public: } virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { - String src_appx; EditorProgress ep("export", "Exporting for Windows Universal", 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 +1238,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 +1255,6 @@ public: int template_file_no = 1; while (ret == UNZ_OK) { - // get file name unz_file_info info; char fname[16834]; @@ -1297,11 +1272,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 +1289,6 @@ public: } if (path == "AppxManifest.xml") { - data = _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG)); } @@ -1356,7 +1331,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()); @@ -1445,7 +1419,6 @@ public: } virtual void get_platform_features(List<String> *r_features) { - r_features->push_back("pc"); r_features->push_back("UWP"); } @@ -1461,7 +1434,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..ee25754704 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -82,7 +82,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 +89,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 +103,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 +128,6 @@ void OS_UWP::set_keep_screen_on(bool p_enabled) { } void OS_UWP::initialize_core() { - last_button_state = 0; //RedirectIOToConsole(); @@ -165,7 +158,6 @@ void OS_UWP::initialize_core() { } bool OS_UWP::can_draw() const { - return !minimized; }; @@ -174,12 +166,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 +220,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 +235,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,7 +295,6 @@ 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())); @@ -317,7 +303,6 @@ void OS_UWP::set_clipboard(const String &p_text) { }; String OS_UWP::get_clipboard() const { - if (managed_object->clipboard != nullptr) return managed_object->clipboard->Data(); else @@ -325,25 +310,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,12 +343,10 @@ 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()); @@ -383,21 +362,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 +380,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 +389,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 +398,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 +407,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 +427,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 +442,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 +471,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 +506,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 +527,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 +564,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 +592,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 +605,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 +636,6 @@ void OS_UWP::set_cursor_shape(CursorShape p_shape) { } OS::CursorShape OS_UWP::get_cursor_shape() const { - return cursor_shape; } @@ -673,22 +644,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 +663,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 +682,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 +695,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, 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,7 +738,6 @@ 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); ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + full_path + ", error: " + format_error_message(GetLastError()) + "."); @@ -812,7 +764,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 +775,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 +787,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 +801,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..95359c68b0 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -32,7 +32,7 @@ #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" @@ -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(), 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); 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/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..046e3437ea 100644 --- a/platform/windows/context_gl_windows.h +++ b/platform/windows/context_gl_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..996d9722f5 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -38,11 +38,13 @@ // 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") 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/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index ebe9a7d27a..0b7130db74 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -36,13 +36,8 @@ #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); @@ -87,7 +82,6 @@ void DisplayServerWindows::alert(const String &p_alert, const String &p_title) { } 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 +91,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); @@ -117,8 +110,8 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { 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) @@ -128,12 +121,12 @@ void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { mouse_mode = 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 +134,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 +145,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)) { @@ -204,8 +196,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 +210,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); 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 +242,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 +252,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 +272,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 +281,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 +303,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 +314,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 +322,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 +340,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 +361,6 @@ enum _MonitorDpiType { }; static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Default) { - int dpiX = 96, dpiY = 96; static HMODULE Shcore = nullptr; @@ -405,7 +383,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 +406,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 +422,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 +432,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 +455,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,7 +469,6 @@ 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); @@ -522,8 +498,8 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod 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 +521,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 +537,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 +544,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 +551,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 +572,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,7 +579,6 @@ 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)); @@ -612,7 +586,6 @@ void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_wi } int DisplayServerWindows::window_get_current_screen(WindowID p_window) const { - _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), -1); @@ -621,8 +594,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 +606,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 +630,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 +684,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 +720,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 +731,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 +741,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 +752,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 +762,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 +805,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 +822,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 +837,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 +849,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 +867,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 +890,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 +917,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 +957,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 +976,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 +986,65 @@ 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); } 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 +1058,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 +1069,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 +1077,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 +1089,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 +1102,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 +1124,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 +1131,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 +1177,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 +1232,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) { @@ -1429,72 +1372,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); - char name[KL_NAMELENGTH + 1]; - name[0] = 0; - GetKeyboardLayoutNameA(name); + 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); + + 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); - unsigned long hex = strtoul(name, nullptr, 16); + wchar_t buf[LOCALE_NAME_MAX_LENGTH]; + memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(wchar_t)); + LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); - int i = 0; - while (azerty[i] != 0) { - if (azerty[i] == hex) return LATIN_KEYBOARD_AZERTY; - i++; + memfree(layouts); + + return String(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; + + HKEY hkey; + wchar_t layout_text[1024]; + memset(layout_text, 0, 1024 * sizeof(wchar_t)); + + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)id.c_str(), 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(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, ""); - i = 0; - while (dvorak[i] != 0) { - if (dvorak[i] == hex) return LATIN_KEYBOARD_DVORAK; - i++; + 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_t buf[LOCALE_NAME_MAX_LENGTH]; + memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(wchar_t)); + LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); + + wchar_t name[1024]; + memset(name, 0, 1024 * sizeof(wchar_t)); + GetLocaleInfoEx(buf, LOCALE_SLOCALIZEDDISPLAYNAME, (LPWSTR)&name, 1024); + + ret = String(name); } + memfree(layouts); - return LATIN_KEYBOARD_QWERTY; + return ret; } void DisplayServerWindows::process_events() { - _THREAD_SAFE_METHOD_ MSG msg; @@ -1504,19 +1477,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 +1497,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 +1597,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 +1630,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 +1651,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 +1668,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 +1685,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 +1704,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 +1724,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 +1741,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 +1755,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); @@ -1807,7 +1780,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA switch (uMsg) // Check For Windows Messages { case WM_SETFOCUS: { - windows[window_id].window_has_focus = true; last_focused_window = window_id; @@ -1836,20 +1808,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 +1864,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 +1902,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 +1915,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 +1947,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 +2091,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,8 +2128,14 @@ 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); @@ -2057,7 +2153,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 +2169,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 +2182,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 +2236,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 +2274,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 +2287,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 +2312,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 +2357,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 +2369,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 +2383,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 +2390,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 +2397,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 +2422,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 +2450,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 +2458,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 +2470,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; @@ -2399,7 +2501,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 +2545,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,7 +2564,6 @@ 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; if (wParam == VK_CONTROL) @@ -2487,7 +2587,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 +2609,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 +2627,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,7 +2648,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_DEVICECHANGE: { - joypad->probe_joypads(); } break; case WM_SETCURSOR: { @@ -2576,7 +2670,6 @@ 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]; @@ -2586,7 +2679,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Vector<String> files; for (int i = 0; i < fcount; i++) { - DragQueryFileW(hDropInfo, i, buf, buffsize); String file = buf; files.push_back(file); @@ -2603,9 +2695,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 +2705,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 +2713,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 +2739,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 +2779,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 +2788,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; @@ -2759,6 +2883,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); @@ -2776,6 +2933,16 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, 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 +2953,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 +3022,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 +3033,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,7 +3054,10 @@ 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)); for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, main_window); @@ -2911,7 +3071,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - rendering_device_vulkan = memnew(RenderingDeviceVulkan); rendering_device_vulkan->initialize(context_vulkan); @@ -2945,7 +3104,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 +3121,19 @@ 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)); } 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 +3144,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..995ced0809 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -33,7 +33,7 @@ #include "servers/display_server.h" -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "core/os/os.h" #include "core/project_settings.h" #include "crash_handler_windows.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; @@ -214,6 +336,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 +402,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; @@ -389,7 +523,11 @@ public: 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_windows.cpp b/platform/windows/godot_windows.cpp index 2aa928c2a7..910059a9fc 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, ""); diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index 437c3b733d..271a4e41bc 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,7 +139,7 @@ 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(); @@ -165,10 +163,13 @@ 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]); + + 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 = joypad_count; @@ -188,9 +189,7 @@ 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; @@ -239,7 +238,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 +247,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 +254,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 +274,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 +292,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 +299,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 +323,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 +354,6 @@ void JoypadWindows::process_joypads() { } for (int i = 0; i < JOYPADS_MAX; i++) { - dinput_gamepad *joy = &d_joypads[i]; if (!joy->attached) @@ -391,18 +374,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; } @@ -415,7 +393,6 @@ void JoypadWindows::process_joypads() { int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz }; 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 +405,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 +412,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 +498,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 +524,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..6c06b3f6f0 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; @@ -93,7 +92,6 @@ private: }; struct xinput_gamepad { - int id; bool attached; bool vibrating; @@ -117,7 +115,7 @@ private: HWND *hWnd; HANDLE xinput_dll; LPDIRECTINPUT8 dinput; - InputFilter *input; + Input *input; int id_to_change; int joypad_count; @@ -141,7 +139,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..d8d0b13068 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -33,7 +33,6 @@ #include <stdio.h> struct _WinTranslatePair { - unsigned int keysym; unsigned int keycode; }; @@ -130,7 +129,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) @@ -337,9 +336,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 +350,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 +411,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..9c1b8f2949 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -80,7 +80,6 @@ __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); @@ -94,7 +93,6 @@ static String format_error_message(DWORD id) { #endif // DEBUG_ENABLED void RedirectIOToConsole() { - int hConHandle; intptr_t lStdHandle; @@ -175,12 +173,10 @@ BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) { } void OS_Windows::initialize_debugging() { - SetConsoleCtrlHandler(HandlerRoutine, TRUE); } void OS_Windows::initialize() { - crash_handler.initialize(); //RedirectIOToConsole(); @@ -217,19 +213,16 @@ void OS_Windows::initialize() { } 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 +234,6 @@ void OS_Windows::finalize() { } void OS_Windows::finalize_core() { - timeEndPeriod(1); memdelete(process_map); @@ -249,7 +241,6 @@ 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; if (!FileAccess::exists(path)) { @@ -300,12 +291,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 +309,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 +343,89 @@ 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; } -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 OS_Windows::_quote_command_line_argument(const String &p_text) const { + for (int i = 0; i < p_text.size(); i++) { + CharType 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) { if (p_blocking && r_pipe) { - - String argss; - argss = "\"\"" + p_path + "\""; - + String argss = _quote_command_line_argument(p_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"); - 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 +436,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(p_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 +456,34 @@ 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 + Vector<CharType> modstr; // Windows wants to change this no idea why. modstr.resize(cmdline.size()); - for (int i = 0; i < cmdline.size(); i++) + 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); ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK); if (p_blocking) { - DWORD ret2 = WaitForSingleObject(pi.pi.hProcess, INFINITE); - if (r_exitcode) + if (r_exitcode) { *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,7 +502,6 @@ int OS_Windows::get_process_id() const { } Error OS_Windows::set_cwd(const String &p_cwd) { - if (_wchdir(p_cwd.c_str()) != 0) return ERR_CANT_OPEN; @@ -540,7 +509,6 @@ Error OS_Windows::set_cwd(const String &p_cwd) { } String OS_Windows::get_executable_path() const { - wchar_t bufname[4096]; GetModuleFileNameW(nullptr, bufname, 4096); String s = bufname; @@ -548,7 +516,6 @@ String OS_Windows::get_executable_path() const { } bool OS_Windows::has_environment(const String &p_var) const { - #ifdef MINGW_ENABLED return _wgetenv(p_var.c_str()) != nullptr; #else @@ -562,7 +529,6 @@ 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); if (wlen > 0) { @@ -572,12 +538,10 @@ String OS_Windows::get_environment(const String &p_var) const { } 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()); } String OS_Windows::get_stdin_string(bool p_block) { - if (p_block) { char buff[1024]; return fgets(buff, 1024, stdin); @@ -587,13 +551,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); return OK; } String OS_Windows::get_locale() const { - const _WinLocale *wl = &_win_locales[0]; LANGID langid = GetUserDefaultUILanguage(); @@ -602,7 +564,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 +610,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 +625,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 +639,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 +647,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 +658,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) { @@ -747,7 +700,6 @@ String OS_Windows::get_system_dir(SystemDir p_dir) const { } 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 +718,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); } bool OS_Windows::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; } @@ -811,7 +761,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..910a83539a 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -31,7 +31,7 @@ #ifndef OS_WINDOWS_H #define OS_WINDOWS_H -#include "core/input/input_filter.h" +#include "core/input/input.h" #include "core/os/os.h" #include "core/project_settings.h" #include "crash_handler_windows.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/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]) { |