diff options
Diffstat (limited to 'platform')
135 files changed, 6523 insertions, 2568 deletions
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index 49c0104e67..965eaabf81 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -83,7 +83,7 @@ public: v = (jvalue *)alloca(sizeof(jvalue) * p_argcount); } - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); int res = env->PushLocalFrame(16); diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp index 825132ac8c..3a2ccac481 100644 --- a/platform/android/audio_driver_jandroid.cpp +++ b/platform/android/audio_driver_jandroid.cpp @@ -72,10 +72,10 @@ Error AudioDriverAndroid::init() { // __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device"); - JNIEnv *env = ThreadAndroid::get_env(); - int mix_rate = GLOBAL_GET("audio/mix_rate"); + JNIEnv *env = get_jni_env(); + int mix_rate = GLOBAL_GET("audio/driver/mix_rate"); - int latency = GLOBAL_GET("audio/output_latency"); + int latency = GLOBAL_GET("audio/driver/output_latency"); unsigned int buffer_size = next_power_of_2(latency * mix_rate / 1000); print_verbose("Audio buffer size: " + itos(buffer_size)); @@ -98,7 +98,7 @@ void AudioDriverAndroid::start() { } void AudioDriverAndroid::setup(jobject p_io) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); io = p_io; jclass c = env->GetObjectClass(io); @@ -162,7 +162,7 @@ void AudioDriverAndroid::unlock() { } void AudioDriverAndroid::finish() { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(io, _quit); if (audioBuffer) { @@ -175,7 +175,7 @@ void AudioDriverAndroid::finish() { } void AudioDriverAndroid::set_pause(bool p_pause) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(io, _pause, p_pause); } diff --git a/platform/android/detect.py b/platform/android/detect.py index 650606ff8b..2a80a3c45b 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -13,7 +13,7 @@ def get_name(): def can_build(): - return "ANDROID_NDK_ROOT" in os.environ + return ("ANDROID_SDK_ROOT" in os.environ) or ("ANDROID_HOME" in os.environ) def get_platform(platform): @@ -24,13 +24,33 @@ def get_opts(): from SCons.Variables import BoolVariable, EnumVariable return [ - ("ANDROID_NDK_ROOT", "Path to the Android NDK", os.environ.get("ANDROID_NDK_ROOT", 0)), + ("ANDROID_NDK_ROOT", "Path to the Android NDK", get_android_ndk_root()), + ("ANDROID_SDK_ROOT", "Path to the Android SDK", get_android_sdk_root()), ("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"), EnumVariable("android_arch", "Target architecture", "armv7", ("armv7", "arm64v8", "x86", "x86_64")), BoolVariable("android_neon", "Enable NEON support (armv7 only)", True), ] +# Return the ANDROID_SDK_ROOT environment variable. +# While ANDROID_HOME has been deprecated, it's used as a fallback for backward +# compatibility purposes. +def get_android_sdk_root(): + if "ANDROID_SDK_ROOT" in os.environ: + return os.environ.get("ANDROID_SDK_ROOT", 0) + else: + return os.environ.get("ANDROID_HOME", 0) + + +# Return the ANDROID_NDK_ROOT environment variable. +# We generate one for this build using the ANDROID_SDK_ROOT env +# variable and the project ndk version. +# If the env variable is already defined, we override it with +# our own to match what the project expects. +def get_android_ndk_root(): + return get_android_sdk_root() + "/ndk/" + get_project_ndk_version() + + def get_flags(): return [ ("tools", False), @@ -47,7 +67,31 @@ def create(env): return env.Clone(tools=tools) +# Check if ANDROID_NDK_ROOT is valid. +# If not, install the ndk using ANDROID_SDK_ROOT and sdkmanager. +def install_ndk_if_needed(env): + print("Checking for Android NDK...") + env_ndk_version = get_env_ndk_version(env["ANDROID_NDK_ROOT"]) + if env_ndk_version is None: + # Reinstall the ndk and update ANDROID_NDK_ROOT. + print("Installing Android NDK...") + if env["ANDROID_SDK_ROOT"] is None: + raise Exception("Invalid ANDROID_SDK_ROOT environment variable.") + + import subprocess + + extension = ".bat" if os.name == "nt" else "" + sdkmanager_path = env["ANDROID_SDK_ROOT"] + "/cmdline-tools/latest/bin/sdkmanager" + extension + ndk_download_args = "ndk;" + get_project_ndk_version() + subprocess.check_call([sdkmanager_path, ndk_download_args]) + + env["ANDROID_NDK_ROOT"] = env["ANDROID_SDK_ROOT"] + "/ndk/" + get_project_ndk_version() + print("ANDROID_NDK_ROOT: " + env["ANDROID_NDK_ROOT"]) + + def configure(env): + install_ndk_if_needed(env) + # Workaround for MinGW. See: # http://www.scons.org/wiki/LongCmdLinesOnWin32 if os.name == "nt": @@ -153,12 +197,11 @@ def configure(env): if env["optimize"] == "speed": # optimize for speed (default) env.Append(LINKFLAGS=["-O2"]) env.Append(CCFLAGS=["-O2", "-fomit-frame-pointer"]) - env.Append(CPPDEFINES=["NDEBUG"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["-Os"]) - env.Append(CPPDEFINES=["NDEBUG"]) env.Append(LINKFLAGS=["-Os"]) + env.Append(CPPDEFINES=["NDEBUG"]) if can_vectorize: env.Append(CCFLAGS=["-ftree-vectorize"]) if env["target"] == "release_debug": @@ -215,8 +258,10 @@ def configure(env): env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++abi/include"]) # Disable exceptions and rtti on non-tools (template) builds - if env["tools"] or env["builtin_icu"]: + if env["tools"]: env.Append(CXXFLAGS=["-frtti"]) + elif env["builtin_icu"]: + env.Append(CXXFLAGS=["-frtti", "-fno-exceptions"]) else: env.Append(CXXFLAGS=["-fno-rtti", "-fno-exceptions"]) # Don't use dynamic_cast, necessary with no-rtti. @@ -270,7 +315,7 @@ def configure(env): # Link flags - ndk_version = get_ndk_version(env["ANDROID_NDK_ROOT"]) + ndk_version = get_env_ndk_version(env["ANDROID_NDK_ROOT"]) if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("17.1.4828580"): env.Append(LINKFLAGS=["-Wl,--exclude-libs,libgcc.a", "-Wl,--exclude-libs,libatomic.a", "-nostdlib++"]) else: @@ -323,8 +368,14 @@ def configure(env): env.Append(LIBS=["OpenSLES", "EGL", "GLESv2", "vulkan", "android", "log", "z", "dl"]) +# Return the project NDK version. +# This is kept in sync with the value in 'platform/android/java/app/config.gradle'. +def get_project_ndk_version(): + return "21.4.7075529" + + # Return NDK version string in source.properties (adapted from the Chromium project). -def get_ndk_version(path): +def get_env_ndk_version(path): if path is None: return None prop_file_path = os.path.join(path, "source.properties") diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 78685991a8..f8ac29c738 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -47,7 +47,7 @@ DirAccess *DirAccessJAndroid::create_fs() { Error DirAccessJAndroid::list_dir_begin() { list_dir_end(); - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring js = env->NewStringUTF(current_dir.utf8().get_data()); int res = env->CallIntMethod(io, _dir_open, js); @@ -62,7 +62,7 @@ Error DirAccessJAndroid::list_dir_begin() { String DirAccessJAndroid::get_next() { ERR_FAIL_COND_V(id == 0, ""); - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring str = (jstring)env->CallObjectMethod(io, _dir_next, id); if (!str) return ""; @@ -73,7 +73,7 @@ String DirAccessJAndroid::get_next() { } bool DirAccessJAndroid::current_is_dir() const { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); return env->CallBooleanMethod(io, _dir_is_dir, id); } @@ -86,7 +86,7 @@ void DirAccessJAndroid::list_dir_end() { if (id == 0) return; - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(io, _dir_close, id); id = 0; } @@ -100,7 +100,7 @@ String DirAccessJAndroid::get_drive(int p_drive) { } Error DirAccessJAndroid::change_dir(String p_dir) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); if (p_dir == "" || p_dir == "." || (p_dir == ".." && current_dir == "")) return OK; @@ -154,7 +154,7 @@ bool DirAccessJAndroid::file_exists(String p_file) { } bool DirAccessJAndroid::dir_exists(String p_dir) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); String sd; @@ -207,7 +207,7 @@ size_t DirAccessJAndroid::get_space_left() { } void DirAccessJAndroid::setup(jobject p_io) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); io = p_io; jclass c = env->GetObjectClass(io); diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 5f7e5eaa83..51cea39a43 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -477,7 +477,7 @@ void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p Input::get_singleton()->joy_button(p_event.device, p_event.index, p_event.pressed); break; case JOY_EVENT_AXIS: - Input::JoyAxis value; + Input::JoyAxisValue value; value.min = -1; value.value = p_event.value; Input::get_singleton()->joy_axis(p_event.device, p_event.index, value); @@ -741,15 +741,15 @@ void DisplayServerAndroid::process_mouse_event(int input_device, int event_actio ev->set_pressed(true); buttons_state = event_buttons_mask; if (event_vertical_factor > 0) { - _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_UP, event_vertical_factor); + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_UP, event_vertical_factor); } else if (event_vertical_factor < 0) { - _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_DOWN, -event_vertical_factor); + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_DOWN, -event_vertical_factor); } if (event_horizontal_factor > 0) { - _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_RIGHT, event_horizontal_factor); + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_RIGHT, event_horizontal_factor); } else if (event_horizontal_factor < 0) { - _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_LEFT, -event_horizontal_factor); + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_LEFT, -event_horizontal_factor); } } break; } @@ -778,22 +778,22 @@ void DisplayServerAndroid::process_double_tap(int event_android_button_mask, Poi ev->set_pressed(event_button_mask != 0); ev->set_button_index(_button_index_from_mask(event_button_mask)); ev->set_button_mask(event_button_mask); - ev->set_doubleclick(true); + ev->set_double_click(true); Input::get_singleton()->accumulate_input_event(ev); } int DisplayServerAndroid::_button_index_from_mask(int button_mask) { switch (button_mask) { - case BUTTON_MASK_LEFT: - return BUTTON_LEFT; - case BUTTON_MASK_RIGHT: - return BUTTON_RIGHT; - case BUTTON_MASK_MIDDLE: - return BUTTON_MIDDLE; - case BUTTON_MASK_XBUTTON1: - return BUTTON_XBUTTON1; - case BUTTON_MASK_XBUTTON2: - return BUTTON_XBUTTON2; + case MOUSE_BUTTON_MASK_LEFT: + return MOUSE_BUTTON_LEFT; + case MOUSE_BUTTON_MASK_RIGHT: + return MOUSE_BUTTON_RIGHT; + case MOUSE_BUTTON_MASK_MIDDLE: + return MOUSE_BUTTON_MIDDLE; + case MOUSE_BUTTON_MASK_XBUTTON1: + return MOUSE_BUTTON_XBUTTON1; + case MOUSE_BUTTON_MASK_XBUTTON2: + return MOUSE_BUTTON_XBUTTON2; default: return 0; } @@ -854,19 +854,19 @@ int DisplayServerAndroid::mouse_get_button_state() const { int DisplayServerAndroid::_android_button_mask_to_godot_button_mask(int android_button_mask) { int godot_button_mask = 0; if (android_button_mask & AMOTION_EVENT_BUTTON_PRIMARY) { - godot_button_mask |= BUTTON_MASK_LEFT; + godot_button_mask |= MOUSE_BUTTON_MASK_LEFT; } if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { - godot_button_mask |= BUTTON_MASK_RIGHT; + godot_button_mask |= MOUSE_BUTTON_MASK_RIGHT; } if (android_button_mask & AMOTION_EVENT_BUTTON_TERTIARY) { - godot_button_mask |= BUTTON_MASK_MIDDLE; + godot_button_mask |= MOUSE_BUTTON_MASK_MIDDLE; } if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) { - godot_button_mask |= BUTTON_MASK_XBUTTON1; + godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1; } if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { - godot_button_mask |= BUTTON_MASK_XBUTTON2; + godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2; } return godot_button_mask; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 08ee410a96..cd3f00f935 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -37,6 +37,7 @@ #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" +#include "core/templates/safe_refcount.h" #include "core/version.h" #include "drivers/png/png_driver_common.h" #include "editor/editor_export.h" @@ -200,8 +201,23 @@ static const char *android_perms[] = { nullptr }; -static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable/splash.png"; -static const char *SPLASH_BG_COLOR_PATH = "res/drawable/splash_bg_color.png"; +static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi/splash.png"; +static const char *LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi-v4/splash.png"; +static const char *SPLASH_BG_COLOR_PATH = "res/drawable-nodpi/splash_bg_color.png"; +static const char *LEGACY_BUILD_SPLASH_BG_COLOR_PATH = "res/drawable-nodpi-v4/splash_bg_color.png"; +static const char *SPLASH_CONFIG_PATH = "res://android/build/res/drawable/splash_drawable.xml"; + +const String SPLASH_CONFIG_XML_CONTENT = R"SPLASH(<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/splash_bg_color" /> + <item> + <bitmap + android:gravity="center" + android:filter="%s" + android:src="@drawable/splash" /> + </item> +</layer-list> +)SPLASH"; struct LauncherIcon { const char *export_path; @@ -261,41 +277,41 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { EditorProgress *ep = nullptr; }; - Vector<PluginConfig> plugins; + Vector<PluginConfigAndroid> plugins; String last_plugin_names; uint64_t last_custom_build_time = 0; - volatile bool plugins_changed; + SafeFlag plugins_changed; Mutex plugins_lock; Vector<Device> devices; - volatile bool devices_changed; + SafeFlag devices_changed; Mutex device_lock; - Thread *check_for_changes_thread; - volatile bool quit_request; + Thread check_for_changes_thread; + SafeFlag quit_request; static void _check_for_changes_poll_thread(void *ud) { EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud; - while (!ea->quit_request) { + while (!ea->quit_request.is_set()) { // 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(); + if (!ea->plugins_changed.is_set()) { + Vector<PluginConfigAndroid> loaded_plugins = get_plugins(); MutexLock lock(ea->plugins_lock); if (ea->plugins.size() != loaded_plugins.size()) { - ea->plugins_changed = true; + ea->plugins_changed.set(); } else { for (int i = 0; i < ea->plugins.size(); i++) { if (ea->plugins[i].name != loaded_plugins[i].name) { - ea->plugins_changed = true; + ea->plugins_changed.set(); break; } } } - if (ea->plugins_changed) { + if (ea->plugins_changed.is_set()) { ea->plugins = loaded_plugins; } } @@ -308,7 +324,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { List<String> args; args.push_back("devices"); int ec; - OS::get_singleton()->execute(adb, args, true, nullptr, &devices, &ec); + OS::get_singleton()->execute(adb, args, &devices, &ec); Vector<String> ds = devices.split("\n"); Vector<String> ldevices; @@ -361,7 +377,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int ec2; String dp; - OS::get_singleton()->execute(adb, args, true, nullptr, &dp, &ec2); + OS::get_singleton()->execute(adb, args, &dp, &ec2); Vector<String> props = dp.split("\n"); String vendor; @@ -409,7 +425,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } ea->devices = ndevices; - ea->devices_changed = true; + ea->devices_changed.set(); } } @@ -418,7 +434,7 @@ 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.is_set()) { break; } } @@ -432,7 +448,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { List<String> args; args.push_back("kill-server"); - OS::get_singleton()->execute(adb, args, true); + OS::get_singleton()->execute(adb, args); }; } @@ -629,7 +645,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { continue; } - if (file.ends_with(PLUGIN_CONFIG_EXT)) { + if (file.ends_with(PluginConfigAndroid::PLUGIN_CONFIG_EXT)) { dir_files.push_back(file); } } @@ -639,8 +655,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { return dir_files; } - static Vector<PluginConfig> get_plugins() { - Vector<PluginConfig> loaded_plugins; + static Vector<PluginConfigAndroid> get_plugins() { + Vector<PluginConfigAndroid> loaded_plugins; String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins"); @@ -653,7 +669,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { if (!plugins_filenames.is_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])); + PluginConfigAndroid config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i])); if (config.valid_config) { loaded_plugins.push_back(config); } else { @@ -666,11 +682,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { return loaded_plugins; } - static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) { - Vector<PluginConfig> enabled_plugins; - Vector<PluginConfig> all_plugins = get_plugins(); + static Vector<PluginConfigAndroid> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) { + Vector<PluginConfigAndroid> enabled_plugins; + Vector<PluginConfigAndroid> all_plugins = get_plugins(); for (int i = 0; i < all_plugins.size(); i++) { - PluginConfig plugin = all_plugins[i]; + PluginConfigAndroid plugin = all_plugins[i]; bool enabled = p_presets->get("plugins/" + plugin.name); if (enabled) { enabled_plugins.push_back(plugin); @@ -775,6 +791,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { + print_verbose("Building temporary manifest.."); String manifest_text = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" @@ -791,10 +808,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { manifest_text += _get_xr_features_tag(p_preset); manifest_text += _get_instrumentation_tag(p_preset); - String plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); - manifest_text += _get_application_tag(p_preset, plugins_names); + manifest_text += _get_application_tag(p_preset); manifest_text += "</manifest>\n"; String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); + + print_verbose("Storing manifest into " + manifest_path + ": " + "\n" + manifest_text); store_string_at_path(manifest_path, manifest_text); } @@ -837,9 +855,6 @@ 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_names = get_plugins_names(get_enabled_plugins(p_preset)); Vector<String> perms; // Write permissions into the perms variable. @@ -905,7 +920,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String tname = string_table[name]; uint32_t attrcount = decode_uint32(&p_manifest[iofs + 20]); iofs += 28; - bool is_focus_aware_metadata = false; for (uint32_t i = 0; i < attrcount; i++) { uint32_t attr_nspace = decode_uint32(&p_manifest[iofs]); @@ -957,33 +971,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } - // FIXME: `attr_value != 0xFFFFFFFF` below added as a stopgap measure for GH-32553, - // but the issue should be debugged further and properly addressed. - if (tname == "meta-data" && attrname == "name" && value == "xr_mode_metadata_name") { - // Update the meta-data 'android:name' attribute based on the selected XR mode. - if (xr_mode_index == 1 /* XRMode.OVR */) { - string_table.write[attr_value] = "com.samsung.android.vr.application.mode"; - } - } - - if (tname == "meta-data" && attrname == "value" && value == "xr_mode_metadata_value") { - // Update the meta-data 'android:value' attribute based on the selected XR mode. - if (xr_mode_index == 1 /* XRMode.OVR */) { - string_table.write[attr_value] = "vr_only"; - } - } - - if (tname == "meta-data" && attrname == "value" && is_focus_aware_metadata) { - // Update the focus awareness meta-data value - encode_uint32(xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); - } - - if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.is_empty()) { - // Update the meta-data 'android:value' attribute with the list of enabled plugins. - string_table.write[attr_value] = plugins_names; - } - - is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware"; iofs += 20; } @@ -999,15 +986,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { Vector<int> feature_versions; if (xr_mode_index == 1 /* XRMode.OVR */) { - // Check for degrees of freedom - int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof - - if (dof_index > 0) { - feature_names.push_back("android.hardware.vr.headtracking"); - feature_required_list.push_back(dof_index == 2); - feature_versions.push_back(1); - } - // Check for hand tracking int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required if (hand_tracking_index > 0) { @@ -1470,23 +1448,44 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } - void load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) { - // TODO: Figure out how to handle remaining boot splash parameters (e.g: fullsize, filter) + String load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) { + bool scale_splash = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize"); + bool apply_filter = ProjectSettings::get_singleton()->get("application/boot_splash/use_filter"); String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); if (!project_splash_path.is_empty()) { splash_image.instance(); + print_verbose("Loading splash image: " + project_splash_path); const Error err = ImageLoader::load_image(project_splash_path, splash_image); if (err) { + if (OS::get_singleton()->is_stdout_verbose()) { + print_error("- unable to load splash image from " + project_splash_path + " (" + itos(err) + ")"); + } splash_image.unref(); } } if (splash_image.is_null()) { // Use the default + print_verbose("Using default splash image."); splash_image = Ref<Image>(memnew(Image(boot_splash_png))); } + if (scale_splash) { + Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + int width, height; + if (screen_size.width > screen_size.height) { + // scale horizontally + height = screen_size.height; + width = splash_image->get_width() * screen_size.height / splash_image->get_height(); + } else { + // scale vertically + width = screen_size.width; + height = splash_image->get_height() * screen_size.width / splash_image->get_width(); + } + splash_image->resize(width, height); + } + // Setup the splash bg color bool bg_color_valid; Color bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color", &bg_color_valid); @@ -1494,9 +1493,13 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { bg_color = boot_splash_bg_color; } + print_verbose("Creating splash background color image."); splash_bg_color_image.instance(); splash_bg_color_image->create(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format()); splash_bg_color_image->fill(bg_color); + + String processed_splash_config_xml = vformat(SPLASH_CONFIG_XML_CONTENT, bool_to_string(apply_filter)); + return processed_splash_config_xml; } void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) { @@ -1508,19 +1511,24 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { // Regular icon: user selection -> project icon -> default. String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges(); + print_verbose("Loading regular icon from " + path); if (path.is_empty() || ImageLoader::load_image(path, icon) != OK) { + print_verbose("- falling back to project icon: " + project_icon_path); ImageLoader::load_image(project_icon_path, icon); } // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges(); + print_verbose("Loading adaptive foreground icon from " + path); if (path.is_empty() || ImageLoader::load_image(path, foreground) != OK) { + print_verbose("- falling back to using the regular icon"); foreground = icon; } // Adaptive background: user selection -> default. path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges(); if (!path.is_empty()) { + print_verbose("Loading adaptive background icon from " + path); ImageLoader::load_image(path, background); } } @@ -1535,13 +1543,21 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, + const String &processed_splash_config_xml, const Ref<Image> &splash_image, const Ref<Image> &splash_bg_color_image, const Ref<Image> &main_image, const Ref<Image> &foreground, const Ref<Image> &background) { + // Store the splash configuration + if (!processed_splash_config_xml.is_empty()) { + print_verbose("Storing processed splash configuration: " + String("\n") + processed_splash_config_xml); + store_string_at_path(SPLASH_CONFIG_PATH, processed_splash_config_xml); + } + // Store the splash image if (splash_image.is_valid() && !splash_image->is_empty()) { + print_verbose("Storing splash image in " + String(SPLASH_IMAGE_EXPORT_PATH)); Vector<uint8_t> data; _load_image_data(splash_image, data); store_image(SPLASH_IMAGE_EXPORT_PATH, data); @@ -1549,6 +1565,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { // Store the splash bg color image if (splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) { + print_verbose("Storing splash background image in " + String(SPLASH_BG_COLOR_PATH)); Vector<uint8_t> data; _load_image_data(splash_bg_color_image, data); store_image(SPLASH_BG_COLOR_PATH, data); @@ -1559,12 +1576,14 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { for (int i = 0; i < icon_densities_count; ++i) { if (main_image.is_valid() && !main_image->is_empty()) { + print_verbose("Processing launcher icon for dimension " + itos(launcher_icons[i].dimensions) + " into " + launcher_icons[i].export_path); Vector<uint8_t> data; _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data); store_image(launcher_icons[i], data); } if (foreground.is_valid() && !foreground->is_empty()) { + print_verbose("Processing launcher adaptive icon foreground for dimension " + itos(launcher_adaptive_icon_foregrounds[i].dimensions) + " into " + launcher_adaptive_icon_foregrounds[i].export_path); Vector<uint8_t> data; _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); @@ -1572,6 +1591,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } if (background.is_valid() && !background->is_empty()) { + print_verbose("Processing launcher adaptive icon background for dimension " + itos(launcher_adaptive_icon_backgrounds[i].dimensions) + " into " + launcher_adaptive_icon_backgrounds[i].export_path); Vector<uint8_t> data; _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); @@ -1597,7 +1617,7 @@ public: public: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override { - String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); + String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name"); if (driver == "GLES2") { r_features->push_back("etc"); } @@ -1618,12 +1638,12 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK)); - Vector<PluginConfig> plugins_configs = get_plugins(); + Vector<PluginConfigAndroid> 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; + plugins_changed.clear(); Vector<String> abis = get_abis(); for (int i = 0; i < abis.size(); ++i) { @@ -1655,9 +1675,7 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/opengl_debug"), false)); 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, "screen/immersive_mode"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true)); @@ -1693,19 +1711,19 @@ public: } virtual bool should_update_export_options() override { - bool export_options_changed = plugins_changed; + bool export_options_changed = plugins_changed.is_set(); if (export_options_changed) { // don't clear unless we're reporting true, to avoid race - plugins_changed = false; + plugins_changed.clear(); } return export_options_changed; } virtual bool poll_export() override { - bool dc = devices_changed; + bool dc = devices_changed.is_set(); if (dc) { // don't clear unless we're reporting true, to avoid race - devices_changed = false; + devices_changed.clear(); } return dc; } @@ -1766,7 +1784,7 @@ public: p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; } - String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk"); + String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); #define CLEANUP_AND_RETURN(m_err) \ { \ @@ -1783,6 +1801,7 @@ public: List<String> args; int rv; + String output; bool remove_prev = p_preset->get("one_click_deploy/clear_previous_install"); String version_name = p_preset->get("version/name"); @@ -1800,7 +1819,9 @@ public: args.push_back("uninstall"); args.push_back(get_package_name(package_name)); - err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv); + output.clear(); + err = OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); } print_line("Installing to device (please wait...): " + devices[p_device].name); @@ -1815,7 +1836,9 @@ public: args.push_back("-r"); args.push_back(tmp_export_path); - err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv); + output.clear(); + err = OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); if (err || rv != 0) { EditorNode::add_io_error("Could not install to device."); CLEANUP_AND_RETURN(ERR_CANT_CREATE); @@ -1832,7 +1855,9 @@ public: args.push_back(devices[p_device].id); args.push_back("reverse"); args.push_back("--remove-all"); - OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv); + output.clear(); + OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); @@ -1843,7 +1868,9 @@ public: args.push_back("tcp:" + itos(dbg_port)); args.push_back("tcp:" + itos(dbg_port)); - OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv); + output.clear(); + OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); print_line("Reverse result: " + itos(rv)); } @@ -1857,7 +1884,9 @@ public: args.push_back("tcp:" + itos(fs_port)); args.push_back("tcp:" + itos(fs_port)); - err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv); + output.clear(); + err = OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); print_line("Reverse result2: " + itos(rv)); } } else { @@ -1885,7 +1914,9 @@ public: args.push_back("-n"); args.push_back(get_package_name(package_name) + "/com.godot.game.GodotApp"); - err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv); + output.clear(); + err = OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); if (err || rv != 0) { EditorNode::add_io_error("Could not execute on device."); CLEANUP_AND_RETURN(ERR_CANT_CREATE); @@ -1958,6 +1989,7 @@ public: String template_err; bool dvalid = false; bool rvalid = false; + bool has_export_templates = false; if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); @@ -1965,7 +1997,7 @@ public: template_err += TTR("Custom debug template not found.") + "\n"; } } else { - dvalid = exists_export_template("android_debug.apk", &template_err); + has_export_templates |= exists_export_template("android_debug.apk", &template_err); } if (p_preset->get("custom_template/release") != "") { @@ -1974,22 +2006,24 @@ public: template_err += TTR("Custom release template not found.") + "\n"; } } else { - rvalid = exists_export_template("android_release.apk", &template_err); + has_export_templates |= exists_export_template("android_release.apk", &template_err); } - valid = dvalid || rvalid; + r_missing_templates = !has_export_templates; + valid = dvalid || rvalid || has_export_templates; if (!valid) { err += template_err; } } else { - valid = exists_export_template("android_source.zip", &err); + r_missing_templates = !exists_export_template("android_source.zip", &err); - if (!FileAccess::exists("res://android/build/build.gradle")) { + bool installed_android_build_template = FileAccess::exists("res://android/build/build.gradle"); + if (!installed_android_build_template) { err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n"; - valid = false; } + + valid = installed_android_build_template && !r_missing_templates; } - r_missing_templates = !valid; // Validate the rest of the configuration. @@ -2090,27 +2124,13 @@ public: // Validate the Xr features are properly populated int xr_mode_index = p_preset->get("xr_features/xr_mode"); - int degrees_of_freedom = p_preset->get("xr_features/degrees_of_freedom"); int hand_tracking = p_preset->get("xr_features/hand_tracking"); - bool focus_awareness = p_preset->get("xr_features/focus_awareness"); if (xr_mode_index != /* XRMode.OVR*/ 1) { - if (degrees_of_freedom > 0) { - valid = false; - err += TTR("\"Degrees Of Freedom\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); - err += "\n"; - } - if (hand_tracking > 0) { valid = false; err += TTR("\"Hand Tracking\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); err += "\n"; } - - if (focus_awareness) { - valid = false; - err += TTR("\"Focus Awareness\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); - err += "\n"; - } } if (int(p_preset->get("custom_template/export_format")) == EXPORT_FORMAT_AAB && @@ -2131,7 +2151,7 @@ public: return list; } - inline bool is_clean_build_required(Vector<PluginConfig> enabled_plugins) { + inline bool is_clean_build_required(Vector<PluginConfigAndroid> enabled_plugins) { String plugin_names = get_plugins_names(enabled_plugins); bool first_build = last_custom_build_time == 0; bool have_plugins_changed = false; @@ -2222,11 +2242,12 @@ public: CharString command_line_argument = command_line_strings[i].utf8(); int base = r_command_line_flags.size(); int length = command_line_argument.length(); - if (length == 0) + if (length == 0) { continue; + } r_command_line_flags.resize(base + 4 + length); encode_uint32(length, &r_command_line_flags.write[base]); - copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length); + memcpy(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length); } } } @@ -2239,6 +2260,7 @@ public: String release_password = p_preset->get("keystore/release_password"); String apksigner = get_apksigner_path(); + print_verbose("Starting signing of the " + export_label + " binary using " + apksigner); if (!FileAccess::exists(apksigner)) { EditorNode::add_io_error("'apksigner' could not be found.\nPlease check the command is available in the Android SDK build-tools directory.\nThe resulting " + export_label + " is unsigned."); return OK; @@ -2277,6 +2299,7 @@ public: return ERR_FILE_CANT_OPEN; } + String output; List<String> args; args.push_back("sign"); args.push_back("--verbose"); @@ -2287,8 +2310,14 @@ public: args.push_back("--ks-key-alias"); args.push_back(user); args.push_back(export_path); + if (p_debug) { + // We only print verbose logs for debug builds to avoid leaking release keystore credentials. + print_verbose("Signing debug binary using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); + } int retval; - OS::get_singleton()->execute(apksigner, args, true, NULL, NULL, &retval); + output.clear(); + OS::get_singleton()->execute(apksigner, args, &output, &retval, true); + print_verbose(output); if (retval) { EditorNode::add_io_error("'apksigner' returned with error #" + itos(retval)); return ERR_CANT_CREATE; @@ -2302,24 +2331,43 @@ public: args.push_back("verify"); args.push_back("--verbose"); args.push_back(export_path); + if (p_debug) { + print_verbose("Verifying signed build using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); + } - OS::get_singleton()->execute(apksigner, args, true, NULL, NULL, &retval); + output.clear(); + OS::get_singleton()->execute(apksigner, args, &output, &retval, true); + print_verbose(output); if (retval) { EditorNode::add_io_error("'apksigner' verification of " + export_label + " failed."); return ERR_CANT_CREATE; } + + print_verbose("Successfully completed signing build."); return OK; } void _clear_assets_directory() { DirAccessRef da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (da_res->dir_exists("res://android/build/assets")) { + print_verbose("Clearing assets directory.."); DirAccessRef da_assets = DirAccess::open("res://android/build/assets"); da_assets->erase_contents_recursive(); da_res->remove("res://android/build/assets"); } } + String join_list(List<String> parts, const String &separator) const { + String ret; + for (int i = 0; i < parts.size(); ++i) { + if (i > 0) { + ret += separator; + } + ret += parts[i]; + } + return ret; + } + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override { int export_format = int(p_preset->get("custom_template/export_format")); bool should_sign = p_preset->get("package/signed"); @@ -2339,9 +2387,21 @@ public: bool apk_expansion = p_preset->get("apk_expansion/enable"); Vector<String> enabled_abis = get_enabled_abis(p_preset); + print_verbose("Exporting for Android..."); + print_verbose("- debug build: " + bool_to_string(p_debug)); + print_verbose("- export path: " + p_path); + print_verbose("- export format: " + itos(export_format)); + print_verbose("- sign build: " + bool_to_string(should_sign)); + print_verbose("- custom build enabled: " + bool_to_string(use_custom_build)); + print_verbose("- apk expansion enabled: " + bool_to_string(apk_expansion)); + print_verbose("- enabled abis: " + String(",").join(enabled_abis)); + print_verbose("- export filter: " + itos(p_preset->get_export_filter())); + print_verbose("- include filter: " + p_preset->get_include_filter()); + print_verbose("- exclude filter: " + p_preset->get_exclude_filter()); + Ref<Image> splash_image; Ref<Image> splash_bg_color_image; - load_splash_refs(splash_image, splash_bg_color_image); + String processed_splash_config_xml = load_splash_refs(splash_image, splash_bg_color_image); Ref<Image> main_image; Ref<Image> foreground; @@ -2374,14 +2434,17 @@ public: } if (use_custom_build) { + print_verbose("Starting custom build.."); //test that installed build version is alright { + print_verbose("Checking build version.."); FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ); if (!f) { EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); return ERR_UNCONFIGURED; } String version = f->get_line().strip_edges(); + print_verbose("- build version: " + version); f->close(); if (version != VERSION_FULL_CONFIG) { EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); @@ -2389,7 +2452,8 @@ public: } } String sdk_path = EDITOR_GET("export/android/android_sdk_path"); - ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'."); + ERR_FAIL_COND_V_MSG(sdk_path.is_empty(), ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'."); + print_verbose("Android sdk path: " + sdk_path); // TODO: should we use "package/name" or "application/config/name"? String project_name = get_project_name(p_preset->get("package/name")); @@ -2398,27 +2462,31 @@ public: EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name"); } // Copies the project icon files into the appropriate Gradle project directory. - _copy_icons_to_gradle_project(p_preset, splash_image, splash_bg_color_image, main_image, foreground, background); + _copy_icons_to_gradle_project(p_preset, processed_splash_config_xml, splash_image, splash_bg_color_image, main_image, foreground, background); // Write an AndroidManifest.xml file into the Gradle project directory. _write_tmp_manifest(p_preset, p_give_internet, p_debug); //stores all the project files inside the Gradle project directory. Also includes all ABIs _clear_assets_directory(); if (!apk_expansion) { - err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file); + print_verbose("Exporting project files.."); + err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, nullptr, ignore_so_file); if (err != OK) { EditorNode::add_io_error("Could not export project files to gradle project\n"); return err; } } else { + print_verbose("Saving apk expansion file.."); err = save_apk_expansion_file(p_preset, p_path); if (err != OK) { EditorNode::add_io_error("Could not write expansion package file!"); return err; } } + print_verbose("Storing command line flags.."); store_file_at_path("res://android/build/assets/_cl_", command_line_flags); + print_verbose("Updating ANDROID_HOME environment to " + sdk_path); OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required String build_command; @@ -2438,9 +2506,9 @@ public: String sign_flag = should_sign ? "true" : "false"; String zipalign_flag = "true"; - 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); + Vector<PluginConfigAndroid> enabled_plugins = get_enabled_plugins(p_preset); + String local_plugins_binaries = get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_LOCAL, enabled_plugins); + String remote_plugins_binaries = get_plugins_binaries(PluginConfigAndroid::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); @@ -2458,6 +2526,8 @@ public: cmdline.push_back(apk_build_command); } + cmdline.push_back("-p"); // argument to specify the start directory. + cmdline.push_back(build_path); // start directory. cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name. cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code. cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name. @@ -2467,6 +2537,14 @@ public: 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("-Pperform_zipalign=" + zipalign_flag); // argument to specify whether the build should be zipaligned. cmdline.push_back("-Pperform_signing=" + sign_flag); // argument to specify whether the build should be signed. + cmdline.push_back("-Pgodot_editor_version=" + String(VERSION_FULL_CONFIG)); + + // NOTE: The release keystore is not included in the verbose logging + // to avoid accidentally leaking sensitive information when sharing verbose logs for troubleshooting. + // Any non-sensitive additions to the command line arguments must be done above this section. + // Sensitive additions must be done below the logging statement. + print_verbose("Build Android project using gradle command: " + String("\n") + build_command + " " + join_list(cmdline, String(" "))); + if (should_sign && !p_debug) { // Pass the release keystore info as well String release_keystore = p_preset->get("keystore/release"); @@ -2481,8 +2559,6 @@ public: cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias. cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specity the release keystore password. } - cmdline.push_back("-p"); // argument to specify the start directory. - cmdline.push_back(build_path); // start directory. int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline); if (result != 0) { @@ -2513,19 +2589,23 @@ public: copy_args.push_back("-Pexport_path=file:" + export_path); copy_args.push_back("-Pexport_filename=" + export_filename); + print_verbose("Copying Android binary using gradle command: " + String("\n") + build_command + " " + join_list(copy_args, String(" "))); int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args); if (copy_result != 0) { EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs.")); return ERR_CANT_CREATE; } + print_verbose("Successfully completed Android custom build."); return OK; } // This is the start of the Legacy build system - if (p_debug) + print_verbose("Starting legacy build system.."); + 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 == "") { if (p_debug) { @@ -2562,7 +2642,7 @@ public: FileAccess *dst_f = nullptr; io2.opaque = &dst_f; - String tmp_unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned.apk"); + String tmp_unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); #define CLEANUP_AND_RETURN(m_err) \ { \ @@ -2607,12 +2687,12 @@ public: } // Process the splash image - if (file == SPLASH_IMAGE_EXPORT_PATH && splash_image.is_valid() && !splash_image->is_empty()) { + if ((file == SPLASH_IMAGE_EXPORT_PATH || file == LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH) && splash_image.is_valid() && !splash_image->is_empty()) { _load_image_data(splash_image, data); } // Process the splash bg color image - if (file == SPLASH_BG_COLOR_PATH && splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) { + if ((file == SPLASH_BG_COLOR_PATH || file == LEGACY_BUILD_SPLASH_BG_COLOR_PATH) && splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) { _load_image_data(splash_bg_color_image, data); } @@ -2720,11 +2800,11 @@ public: zipOpenNewFileInZip(unaligned_apk, "assets/_cl_", &zipfi, - NULL, + nullptr, 0, - NULL, + nullptr, 0, - NULL, + nullptr, 0, // No compress (little size gain and potentially slower startup) Z_DEFAULT_COMPRESSION); zipWriteInFileInZip(unaligned_apk, command_line_flags.ptr(), command_line_flags.size()); @@ -2845,16 +2925,14 @@ public: run_icon.instance(); run_icon->create_from_image(img); - devices_changed = true; - plugins_changed = true; - quit_request = false; - check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this); + devices_changed.set(); + plugins_changed.set(); + check_for_changes_thread.start(_check_for_changes_poll_thread, this); } ~EditorExportPlatformAndroid() { - quit_request = true; - Thread::wait_to_finish(check_for_changes_thread); - memdelete(check_for_changes_thread); + quit_request.set(); + check_for_changes_thread.wait_to_finish(); } }; diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index fc54b65d26..bbbb526af9 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -47,20 +47,21 @@ const String godot_project_name_xml_string = R"(<?xml version="1.0" encoding="ut DisplayServer::ScreenOrientation _get_screen_orientation() { String orientation_settings = ProjectSettings::get_singleton()->get("display/window/handheld/orientation"); DisplayServer::ScreenOrientation screen_orientation; - if (orientation_settings == "portrait") + if (orientation_settings == "portrait") { screen_orientation = DisplayServer::SCREEN_PORTRAIT; - else if (orientation_settings == "reverse_landscape") + } else if (orientation_settings == "reverse_landscape") { screen_orientation = DisplayServer::SCREEN_REVERSE_LANDSCAPE; - else if (orientation_settings == "reverse_portrait") + } else if (orientation_settings == "reverse_portrait") { screen_orientation = DisplayServer::SCREEN_REVERSE_PORTRAIT; - else if (orientation_settings == "sensor_landscape") + } else if (orientation_settings == "sensor_landscape") { screen_orientation = DisplayServer::SCREEN_SENSOR_LANDSCAPE; - else if (orientation_settings == "sensor_portrait") + } else if (orientation_settings == "sensor_portrait") { screen_orientation = DisplayServer::SCREEN_SENSOR_PORTRAIT; - else if (orientation_settings == "sensor") + } else if (orientation_settings == "sensor") { screen_orientation = DisplayServer::SCREEN_SENSOR; - else + } else { screen_orientation = DisplayServer::SCREEN_LANDSCAPE; + } return screen_orientation; } @@ -146,6 +147,9 @@ Error store_string_at_path(const String &p_path, const String &p_data) { String dir = p_path.get_base_dir(); Error err = create_directory(dir); if (err != OK) { + if (OS::get_singleton()->is_stdout_verbose()) { + print_error("Unable to write data into " + p_path); + } return err; } FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE); @@ -162,12 +166,14 @@ Error store_string_at_path(const String &p_path, const String &p_data) { // This method will be called ONLY when custom build is enabled. Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { String dst_path = p_path.replace_first("res://", "res://android/build/assets/"); + print_verbose("Saving project files from " + p_path + " into " + dst_path); Error err = store_file_at_path(dst_path, p_data); return err; } // Creates strings.xml files inside the gradle project for different locales. Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name) { + print_verbose("Creating strings resources for supported locales for project " + project_name); // Stores the string into the default values directory. String processed_default_xml_string = vformat(godot_project_name_xml_string, project_name.xml_escape(true)); store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string); @@ -175,6 +181,9 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset // Searches the Gradle project res/ directory to find all supported locales DirAccessRef da = DirAccess::open("res://android/build/res"); if (!da) { + if (OS::get_singleton()->is_stdout_verbose()) { + print_error("Unable to open Android resources directory."); + } return ERR_CANT_OPEN; } da->list_dir_begin(); @@ -193,6 +202,7 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset if (ProjectSettings::get_singleton()->has_setting(property_name)) { String locale_project_name = ProjectSettings::get_singleton()->get(property_name); String processed_xml_string = vformat(godot_project_name_xml_string, locale_project_name.xml_escape(true)); + print_verbose("Storing project name for locale " + locale + " under " + locale_directory); store_string_at_path(locale_directory, processed_xml_string); } else { // TODO: Once the legacy build system is deprecated we don't need to have xml files for this else branch @@ -208,8 +218,8 @@ String bool_to_string(bool v) { } String _get_gles_tag() { - bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" && - !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2"); + bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/driver/driver_name") == "GLES3" && + !ProjectSettings::get_singleton()->get("rendering/driver/fallback_to_gles2"); return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : ""; } @@ -231,12 +241,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { String manifest_xr_features; bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; if (uses_xr) { - int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof - if (dof_index == 1) { - manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n"; - } else if (dof_index == 2) { - manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n"; - } int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required if (hand_tracking_index == 1) { manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n"; @@ -260,14 +264,6 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) { return manifest_instrumentation_text; } -String _get_plugins_tag(const String &plugins_names) { - if (!plugins_names.is_empty()) { - return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names); - } else { - return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n"; - } -} - String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; String orientation = _get_android_orientation_label(_get_screen_orientation()); @@ -276,28 +272,19 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { "tools:replace=\"android:screenOrientation\" " "android:screenOrientation=\"%s\">\n", orientation); - if (uses_xr) { - String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness")); - manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness); - } else { + if (!uses_xr) { manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n"; } manifest_activity_text += " </activity>\n"; return manifest_activity_text; } -String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) { - bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; +String _get_application_tag(const Ref<EditorExportPreset> &p_preset) { String manifest_application_text = " <application android:label=\"@string/godot_project_name_string\"\n" " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n" - " android:icon=\"@mipmap/icon\">\n\n" - " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n"; + " android:icon=\"@mipmap/icon\">\n\n"; - manifest_application_text += _get_plugins_tag(plugins_names); - if (uses_xr) { - manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n"; - } manifest_application_text += _get_activity_tag(p_preset); manifest_application_text += " </application>\n"; return manifest_application_text; diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index 165d5da3ae..705891713f 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -114,6 +114,9 @@ uint8_t FileAccessAndroid::get_8() const { } int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + ERR_FAIL_COND_V(p_length < 0, -1); + off_t r = AAsset_read(a, p_dst, p_length); if (pos + p_length > len) { diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index e94681659c..15feea15a4 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -22,18 +22,13 @@ tools:ignore="GoogleAppIndexingWarning" android:icon="@mipmap/icon" > - <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. --> - <!-- Do these changes in the export preset. Adding new ones is fine. --> - - <!-- XR mode metadata. This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. --> + <!-- Records the version of the Godot editor used for building --> <meta-data - android:name="xr_mode_metadata_name" - android:value="xr_mode_metadata_value" /> + android:name="org.godotengine.editor.version" + android:value="${godotEditorVersion}" /> - <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. --> - <meta-data - android:name="plugins" - android:value="plugins_value"/> + <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. --> + <!-- Do these changes in the export preset. Adding new ones is fine. --> <activity android:name=".GodotApp" @@ -45,8 +40,8 @@ android:resizeableActivity="false" tools:ignore="UnusedAttribute" > - <!-- Focus awareness metadata is updated at export time if the user enables it in the 'Xr Features' section. --> - <meta-data android:name="com.oculus.vr.focusaware" android:value="false" /> + <!-- Focus awareness metadata is removed at export time if the xr mode is not VR. --> + <meta-data android:name="com.oculus.vr.focusaware" android:value="true" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/platform/android/java/app/assets/.gitignore b/platform/android/java/app/assets/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/platform/android/java/app/assets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 53d11fda5b..1b1fb47bd8 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -77,7 +77,7 @@ 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:!*~" + ignoreAssetsPattern "!.svn:!.git:!.gitignore:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~" } ndk { @@ -85,6 +85,8 @@ android { abiFilters export_abi_list } + manifestPlaceholders = [godotEditorVersion: getGodotEditorVersion()] + // Feel free to modify the application id to your own. applicationId getExportPackageName() versionCode getExportVersionCode() @@ -98,12 +100,16 @@ android { disable 'MissingTranslation', 'UnusedResources' } + ndkVersion versions.ndkVersion + packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' - // Should be uncommented for development purpose within Android Studio - // doNotStrip '**/*.so' + // 'doNotStrip' is enabled for development within Android Studio + if (shouldNotStrip()) { + doNotStrip '**/*.so' + } } signingConfigs { @@ -151,7 +157,7 @@ android { aidl.srcDirs = ['aidl'] assets.srcDirs = ['assets'] } - debug.jniLibs.srcDirs = ['libs/debug'] + debug.jniLibs.srcDirs = ['libs/debug', 'libs/debug/vulkan_validation_layers'] release.jniLibs.srcDirs = ['libs/release'] } diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 80cf6f7ede..b278d15bdf 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,13 +1,14 @@ ext.versions = [ - androidGradlePlugin: '4.1.0', + androidGradlePlugin: '4.0.1', compileSdk : 29, minSdk : 18, targetSdk : 29, - buildTools : '30.0.1', + buildTools : '30.0.3', supportCoreUtils : '1.0.0', kotlinVersion : '1.4.10', v4Support : '1.0.0', - javaVersion : 1.8 + javaVersion : 1.8, + ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. ] @@ -49,7 +50,56 @@ ext.getExportVersionName = { -> return versionName } -final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|" +ext.getGodotEditorVersion = { -> + String editorVersion = project.hasProperty("godot_editor_version") ? project.property("godot_editor_version") : "" + if (editorVersion == null || editorVersion.isEmpty()) { + // Try the library version first + editorVersion = getGodotLibraryVersion() + + if (editorVersion.isEmpty()) { + // Fallback value. + editorVersion = "custom_build" + } + } + return editorVersion +} + +ext.getGodotLibraryVersion = { -> + // Attempt to read the version from the `version.py` file. + String libraryVersion = "" + + File versionFile = new File("../../../version.py") + if (versionFile.isFile()) { + List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"] + def map = [:] + + List<String> lines = versionFile.readLines() + for (String line in lines) { + String[] keyValue = line.split("=") + String key = keyValue[0].trim() + String value = keyValue[1].trim().replaceAll("\"", "") + + if (requiredKeys.contains(key)) { + if (!value.isEmpty()) { + map[key] = value + } + requiredKeys.remove(key) + } + } + + if (requiredKeys.empty) { + libraryVersion = map.values().join(".") + } + } + + if (libraryVersion.isEmpty()) { + // Fallback value in case we're unable to read the file. + libraryVersion = "custom_build" + } + return libraryVersion +} + +final String VALUE_SEPARATOR_REGEX = "\\|" // get the list of ABIs the project should be exported to ext.getExportEnabledABIs = { -> @@ -58,7 +108,7 @@ ext.getExportEnabledABIs = { -> enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|" } Set<String> exportAbiFilter = []; - for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) { if (!abi_name.trim().isEmpty()){ exportAbiFilter.add(abi_name); } @@ -93,7 +143,7 @@ ext.getGodotPluginsMavenRepos = { -> 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)) { + for (String mavenRepoUrl : mavenReposProperty.split(VALUE_SEPARATOR_REGEX)) { mavenRepos += mavenRepoUrl.trim() } } @@ -113,7 +163,7 @@ ext.getGodotPluginsRemoteBinaries = { -> 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)) { + for (String dep: remoteDepsList.split(VALUE_SEPARATOR_REGEX)) { remoteDeps += dep.trim() } } @@ -132,7 +182,7 @@ ext.getGodotPluginsLocalBinaries = { -> 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)) { + for (String plugin : pluginsList.split(VALUE_SEPARATOR_REGEX)) { binDeps += plugin.trim() } } @@ -159,10 +209,19 @@ ext.getReleaseKeyAlias = { -> return keyAlias } +ext.isAndroidStudio = { -> + def sysProps = System.getProperties() + return sysProps != null && sysProps['idea.platform.prefix'] != null +} + ext.shouldZipAlign = { -> String zipAlignFlag = project.hasProperty("perform_zipalign") ? project.property("perform_zipalign") : "" if (zipAlignFlag == null || zipAlignFlag.isEmpty()) { - zipAlignFlag = "false" + if (isAndroidStudio()) { + zipAlignFlag = "true" + } else { + zipAlignFlag = "false" + } } return Boolean.parseBoolean(zipAlignFlag) } @@ -170,7 +229,15 @@ ext.shouldZipAlign = { -> ext.shouldSign = { -> String signFlag = project.hasProperty("perform_signing") ? project.property("perform_signing") : "" if (signFlag == null || signFlag.isEmpty()) { - signFlag = "false" + if (isAndroidStudio()) { + signFlag = "true" + } else { + signFlag = "false" + } } return Boolean.parseBoolean(signFlag) } + +ext.shouldNotStrip = { -> + return isAndroidStudio() || project.hasProperty("doNotStrip") +} diff --git a/platform/android/java/app/res/drawable/splash.png b/platform/android/java/app/res/drawable-nodpi/splash.png Binary files differindex 7bddd4325a..7bddd4325a 100644 --- a/platform/android/java/app/res/drawable/splash.png +++ b/platform/android/java/app/res/drawable-nodpi/splash.png diff --git a/platform/android/java/app/res/drawable/splash_bg_color.png b/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png Binary files differindex 004b6fd508..004b6fd508 100644 --- a/platform/android/java/app/res/drawable/splash_bg_color.png +++ b/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png diff --git a/platform/android/java/app/res/drawable/splash_drawable.xml b/platform/android/java/app/res/drawable/splash_drawable.xml index 2794a40817..30627b998c 100644 --- a/platform/android/java/app/res/drawable/splash_drawable.xml +++ b/platform/android/java/app/res/drawable/splash_drawable.xml @@ -6,7 +6,7 @@ <item> <bitmap android:gravity="center" + android:filter="false" android:src="@drawable/splash" /> </item> - </layer-list> diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 73c136ed0e..a7fe500be2 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -112,7 +112,7 @@ task copyReleaseAARToBin(type: Copy) { * The zip file also includes some gradle tools to allow building of the custom build. */ task zipCustomBuild(type: Zip) { - dependsOn ':generateGodotTemplates' + onlyIf { generateGodotTemplates.state.executed || generateDevTemplate.state.executed } doFirst { logger.lifecycle("Generating Godot custom build template") } @@ -122,16 +122,17 @@ task zipCustomBuild(type: Zip) { destinationDir(file(binDir)) } -/** - * Master task used to coordinate the tasks defined above to generate the set of Godot templates. - */ -task generateGodotTemplates(type: GradleBuild) { +def templateExcludedBuildTask() { // We exclude these gradle tasks so we can run the scons command manually. + def excludedTasks = [] for (String buildType : supportedTargets) { - startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType) + excludedTasks += ":lib:" + getSconsTaskName(buildType) } + return excludedTasks +} - tasks = [] +def templateBuildTasks() { + def tasks = [] // Only build the apks and aar files for which we have native shared libraries. for (String target : supportedTargets) { @@ -152,6 +153,29 @@ task generateGodotTemplates(type: GradleBuild) { } } + return tasks +} + +/** + * Master task used to coordinate the tasks defined above to generate the set of Godot templates. + */ +task generateGodotTemplates(type: GradleBuild) { + startParameter.excludedTaskNames = templateExcludedBuildTask() + tasks = templateBuildTasks() + + finalizedBy 'zipCustomBuild' +} + +/** + * Generates the same output as generateGodotTemplates but with dev symbols + */ +task generateDevTemplate (type: GradleBuild) { + // add parameter to set symbols to true + startParameter.projectProperties += [doNotStrip: true] + + startParameter.excludedTaskNames = templateExcludedBuildTask() + tasks = templateBuildTasks() + finalizedBy 'zipCustomBuild' } @@ -165,12 +189,6 @@ task cleanGodotTemplates(type: Delete) { // Delete the library generated AAR files delete("lib/build/outputs/aar") - // Delete the godotpayment libs directory contents - delete("plugins/godotpayment/libs") - - // Delete the generated godotpayment aar - delete("plugins/godotpayment/build/outputs/aar") - // Delete the app libs directory contents delete("app/libs") diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties index 2dc069ad2f..6b3b62a9da 100644 --- a/platform/android/java/gradle.properties +++ b/platform/android/java/gradle.properties @@ -12,7 +12,7 @@ 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 +org.gradle.jvmargs=-Xmx4536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml index fa39bc0f1d..3034794d69 100644 --- a/platform/android/java/lib/AndroidManifest.xml +++ b/platform/android/java/lib/AndroidManifest.xml @@ -6,6 +6,11 @@ <application> + <!-- Records the version of the Godot library --> + <meta-data + android:name="org.godotengine.library.version" + android:value="${godotLibraryVersion}" /> + <service android:name=".GodotDownloaderService" /> </application> diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 89ce3d15e6..663ba73d40 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -13,9 +13,13 @@ android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools + ndkVersion versions.ndkVersion + defaultConfig { minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk + + manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersion()] } compileOptions { @@ -32,8 +36,10 @@ android { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' - // Should be uncommented for development purpose within Android Studio - // doNotStrip '**/*.so' + // 'doNotStrip' is enabled for development within Android Studio + if (shouldNotStrip()) { + doNotStrip '**/*.so' + } } sourceSets { @@ -68,7 +74,7 @@ android { File sconsExecutableFile = null def sconsName = "scons" def sconsExts = (org.gradle.internal.os.OperatingSystem.current().isWindows() - ? [".bat", ".exe"] + ? [".bat", ".cmd", ".ps1", ".exe"] : [""]) logger.lifecycle("Looking for $sconsName executable path") for (ext in sconsExts) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java index 4e67402c63..1ed16e04ca 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -34,6 +34,7 @@ import android.content.Intent; import android.os.Bundle; import android.view.KeyEvent; +import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; @@ -44,7 +45,7 @@ import androidx.fragment.app.FragmentActivity; * It's also a reference implementation for how to setup and use the {@link Godot} fragment * within an Android app. */ -public abstract class FullScreenGodotApp extends FragmentActivity { +public abstract class FullScreenGodotApp extends FragmentActivity implements GodotHost { @Nullable private Godot godotFragment; @@ -62,11 +63,30 @@ public abstract class FullScreenGodotApp extends FragmentActivity { @Override public void onNewIntent(Intent intent) { + super.onNewIntent(intent); if (godotFragment != null) { godotFragment.onNewIntent(intent); } } + @CallSuper + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (godotFragment != null) { + godotFragment.onActivityResult(requestCode, resultCode, data); + } + } + + @CallSuper + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (godotFragment != null) { + godotFragment.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + @Override public void onBackPressed() { if (godotFragment != null) { 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 7d396b402e..0c16214c8a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -103,6 +103,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.security.MessageDigest; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -131,6 +132,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC private boolean activityResumed; private int mState; + private GodotHost godotHost; private GodotPluginRegistry pluginRegistry; static private Intent mCurrentIntent; @@ -178,7 +180,25 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC public ResultCallback result_callback; @Override + public void onAttach(Context context) { + super.onAttach(context); + if (getParentFragment() instanceof GodotHost) { + godotHost = (GodotHost)getParentFragment(); + } else if (getActivity() instanceof GodotHost) { + godotHost = (GodotHost)getActivity(); + } + } + + @Override + public void onDetach() { + super.onDetach(); + godotHost = null; + } + + @CallSuper + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); if (result_callback != null) { result_callback.callback(requestCode, resultCode, data); result_callback = null; @@ -189,8 +209,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC } } + @CallSuper @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults); } @@ -201,6 +223,20 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC }; /** + * Invoked on the render thread when the Godot setup is complete. + */ + @CallSuper + protected void onGodotSetupCompleted() { + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGodotSetupCompleted(); + } + + if (godotHost != null) { + godotHost.onGodotSetupCompleted(); + } + } + + /** * Invoked on the render thread when the Godot main loop has started. */ @CallSuper @@ -208,6 +244,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onGodotMainLoopStarted(); } + + if (godotHost != null) { + godotHost.onGodotMainLoopStarted(); + } } /** @@ -228,7 +268,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC GodotLib.setup(command_line); - final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name"); + final String videoDriver = GodotLib.getGlobal("rendering/driver/driver_name"); if (videoDriver.equals("Vulkan")) { mRenderView = new GodotVulkanRenderView(activity, this); } else { @@ -301,7 +341,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)); } else { - //deprecated in API 26 + // deprecated in API 26 v.vibrate(durationMs); } } @@ -356,6 +396,21 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC @CallSuper protected String[] getCommandLine() { + String[] original = parseCommandLine(); + String[] updated; + List<String> hostCommandLine = godotHost != null ? godotHost.getCommandLine() : null; + if (hostCommandLine == null || hostCommandLine.isEmpty()) { + updated = original; + } else { + updated = Arrays.copyOf(original, original.length + hostCommandLine.size()); + for (int i = 0; i < hostCommandLine.size(); i++) { + updated[original.length + i] = hostCommandLine.get(i); + } + } + return updated; + } + + private String[] parseCommandLine() { InputStream is; try { is = getActivity().getAssets().open("_cl_"); @@ -464,14 +519,16 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) { + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + final Activity activity = getActivity(); Window window = activity.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE); pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); - //check for apk expansion API + // check for apk expansion API boolean md5mismatch = false; command_line = getCommandLine(); String main_pack_md5 = null; @@ -527,9 +584,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC command_line = new_args.toArray(new String[new_args.size()]); } if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { - //check that environment is ok! + // check that environment is ok! if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - //show popup and die + // show popup and die } // Build the full path to the app's expansion files @@ -572,24 +629,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download - // progress (next step) + // progress (next step in onCreateView) mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, GodotDownloaderService.class); - View downloadingExpansionView = - inflater.inflate(R.layout.downloading_expansion, container, false); - mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar); - mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText); - mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction); - mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage); - mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed); - mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining); - mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard); - mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular); - mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton); - mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton); - - return downloadingExpansionView; + return; } } catch (NameNotFoundException e) { // TODO Auto-generated catch block @@ -600,6 +644,27 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC mCurrentIntent = activity.getIntent(); initializeGodot(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) { + if (mDownloaderClientStub != null) { + View downloadingExpansionView = + inflater.inflate(R.layout.downloading_expansion, container, false); + mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar); + mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText); + mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction); + mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage); + mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed); + mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining); + mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard); + mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular); + mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton); + mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton); + + return downloadingExpansionView; + } + return containerLayout; } 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 b536733201..63c91561ff 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -188,15 +188,15 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView if (GLUtils.use_32) { setEGLConfigChooser(translucent ? - new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, + new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, new RegularConfigChooser(8, 8, 8, 8, 16, stencil)) : - new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, + new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, new RegularConfigChooser(5, 6, 5, 0, 16, stencil))); } else { setEGLConfigChooser(translucent ? - new RegularConfigChooser(8, 8, 8, 8, 16, stencil) : - new RegularConfigChooser(5, 6, 5, 0, 16, stencil)); + new RegularConfigChooser(8, 8, 8, 8, 16, stencil) : + new RegularConfigChooser(5, 6, 5, 0, 16, stencil)); } break; } diff --git a/platform/uwp/thread_uwp.cpp b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index 364c414375..317fd13535 100644 --- a/platform/uwp/thread_uwp.cpp +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -1,5 +1,5 @@ /*************************************************************************/ -/* thread_uwp.cpp */ +/* GodotHost.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,34 +28,29 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "thread_uwp.h" - -#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); - std::swap(thread->thread, new_thread); - - return thread; -}; - -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()); -}; - -void ThreadUWP::make_default() { - create_func = create_func_uwp; - get_thread_id_func = get_thread_id_func_uwp; - wait_to_finish_func = wait_to_finish_func_uwp; -}; +package org.godotengine.godot; + +import java.util.Collections; +import java.util.List; + +/** + * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} fragment. + */ +public interface GodotHost { + /** + * Provides a set of command line parameters to setup the engine. + */ + default List<String> getCommandLine() { + return Collections.emptyList(); + } + + /** + * Invoked on the render thread when the Godot setup is complete. + */ + default void onGodotSetupCompleted() {} + + /** + * Invoked on the render thread when the Godot main loop has started. + */ + default void onGodotMainLoopStarted() {} +} 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 3368363ce7..435b8b325f 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 @@ -38,6 +38,8 @@ import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; import android.os.Build; import android.util.Log; +import android.util.SparseArray; +import android.util.SparseIntArray; import android.view.InputDevice; import android.view.InputDevice.MotionRange; import android.view.KeyEvent; @@ -46,17 +48,24 @@ import android.view.MotionEvent; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** * 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; private final InputManagerCompat mInputManager; + private final String tag = this.getClass().getSimpleName(); + + private final SparseIntArray mJoystickIds = new SparseIntArray(4); + private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<Joystick>(4); + public GodotInputHandler(GodotRenderView godotView) { mRenderView = godotView; mInputManager = InputManagerCompat.Factory.getInputManager(mRenderView.getView().getContext()); @@ -82,19 +91,20 @@ public class GodotInputHandler implements InputDeviceListener { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { return false; - }; + } int source = event.getSource(); if (isKeyEvent_GameDevice(source)) { - final int button = getGodotButton(keyCode); - final int device_id = findJoystickDevice(event.getDeviceId()); - // Check if the device exists - if (device_id > -1) { + final int deviceId = event.getDeviceId(); + if (mJoystickIds.indexOfKey(deviceId) >= 0) { + final int button = getGodotButton(keyCode); + final int godotJoyId = mJoystickIds.get(deviceId); + queueEvent(new Runnable() { @Override public void run() { - GodotLib.joybutton(device_id, button, false); + GodotLib.joybutton(godotJoyId, button, false); } }); } @@ -107,7 +117,7 @@ public class GodotInputHandler implements InputDeviceListener { GodotLib.key(keyCode, scanCode, chr, false); } }); - }; + } return true; } @@ -122,24 +132,25 @@ public class GodotInputHandler implements InputDeviceListener { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { return false; - }; + } int source = event.getSource(); //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))); + final int deviceId = event.getDeviceId(); + // Check if source is a game device and that the device is a registered gamepad if (isKeyEvent_GameDevice(source)) { if (event.getRepeatCount() > 0) // ignore key echo return true; - final int button = getGodotButton(keyCode); - final int device_id = findJoystickDevice(event.getDeviceId()); + if (mJoystickIds.indexOfKey(deviceId) >= 0) { + final int button = getGodotButton(keyCode); + final int godotJoyId = mJoystickIds.get(deviceId); - // Check if the device exists - if (device_id > -1) { queueEvent(new Runnable() { @Override public void run() { - GodotLib.joybutton(device_id, button, true); + GodotLib.joybutton(godotJoyId, button, true); } }); } @@ -152,7 +163,7 @@ public class GodotInputHandler implements InputDeviceListener { GodotLib.key(keyCode, scanCode, chr, true); } }); - }; + } return true; } @@ -203,38 +214,52 @@ 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()); - + if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) { // Check if the device exists - if (device_id > -1) { - Joystick joy = mJoysticksDevices.get(device_id); - - for (int i = 0; i < joy.axes.size(); i++) { - InputDevice.MotionRange range = joy.axes.get(i); - final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f; - final int idx = i; - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyaxis(device_id, idx, value); - } - }); + final int deviceId = event.getDeviceId(); + if (mJoystickIds.indexOfKey(deviceId) >= 0) { + final int godotJoyId = mJoystickIds.get(deviceId); + Joystick joystick = mJoysticksDevices.get(deviceId); + + for (int i = 0; i < joystick.axes.size(); i++) { + final int axis = joystick.axes.get(i); + final float value = event.getAxisValue(axis); + /** + * As all axes are polled for each event, only fire an axis event if the value has actually changed. + * Prevents flooding Godot with repeated events. + */ + if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) { + // save value to prevent repeats + joystick.axesValues.put(axis, value); + final int godotAxisIdx = i; + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyaxis(godotJoyId, godotAxisIdx, value); + //Log.i(tag, "GodotLib.joyaxis("+godotJoyId+", "+godotAxisIdx+", "+value+");"); + } + }); + } } - for (int i = 0; i < joy.hats.size(); i += 2) { - final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis())); - final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis())); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyhat(device_id, hatX, hatY); - } - }); + if (joystick.hasAxisHat) { + final int hatX = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_X)); + final int hatY = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + if (joystick.hatX != hatX || joystick.hatY != hatY) { + joystick.hatX = hatX; + joystick.hatY = hatY; + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyhat(godotJoyId, hatX, hatY); + //Log.i(tag, "GodotLib.joyhat("+godotJoyId+", "+hatX+", "+hatY+");"); + } + }); + } } return true; } - } else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) { + } else if (event.isFromSource(InputDevice.SOURCE_STYLUS)) { final float x = event.getX(); final float y = event.getY(); final int type = event.getAction(); @@ -245,6 +270,7 @@ public class GodotInputHandler implements InputDeviceListener { } }); return true; + } else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return handleMouseEvent(event); @@ -266,67 +292,98 @@ public class GodotInputHandler implements InputDeviceListener { } } + private int assignJoystickIdNumber(int deviceId) { + int godotJoyId = 0; + while (mJoystickIds.indexOfValue(godotJoyId) >= 0) { + godotJoyId++; + } + mJoystickIds.put(deviceId, godotJoyId); + return godotJoyId; + } + @Override public void onInputDeviceAdded(int deviceId) { - int id = findJoystickDevice(deviceId); - // Check if the device has not been already added - if (id < 0) { - InputDevice device = mInputManager.getInputDevice(deviceId); - //device can be null if deviceId is not found - if (device != null) { - int sources = device.getSources(); - if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || - ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) { - id = mJoysticksDevices.size(); - - Joystick joy = new Joystick(); - joy.device_id = deviceId; - joy.name = device.getName(); - joy.axes = new ArrayList<InputDevice.MotionRange>(); - joy.hats = new ArrayList<InputDevice.MotionRange>(); - - List<InputDevice.MotionRange> ranges = device.getMotionRanges(); - Collections.sort(ranges, new RangeComparator()); - - for (InputDevice.MotionRange range : ranges) { - if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { - joy.hats.add(range); - } else { - joy.axes.add(range); - } - } - mJoysticksDevices.add(joy); + if (mJoystickIds.indexOfKey(deviceId) >= 0) { + return; + } + + InputDevice device = mInputManager.getInputDevice(deviceId); + //device can be null if deviceId is not found + if (device == null) { + return; + } + + int sources = device.getSources(); + + // Device may not be a joystick or gamepad + if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD && + (sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) { + return; + } + + // Assign first available number. Re-use numbers where possible. + final int id = assignJoystickIdNumber(deviceId); + + final Joystick joystick = new Joystick(); + joystick.device_id = deviceId; + joystick.name = device.getName(); + + //Helps with creating new joypad mappings. + Log.i(tag, "=== New Input Device: " + joystick.name); - final int device_id = id; - final String name = joy.name; - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(device_id, true, name); - } - }); + Set<Integer> already = new HashSet<Integer>(); + for (InputDevice.MotionRange range : device.getMotionRanges()) { + boolean isJoystick = range.isFromSource(InputDevice.SOURCE_JOYSTICK); + boolean isGamepad = range.isFromSource(InputDevice.SOURCE_GAMEPAD); + //Log.i(tag, "axis: "+range.getAxis()+ ", isJoystick: "+isJoystick+", isGamepad: "+isGamepad); + if (!isJoystick && !isGamepad) { + continue; + } + final int axis = range.getAxis(); + if (axis == MotionEvent.AXIS_HAT_X || axis == MotionEvent.AXIS_HAT_Y) { + joystick.hasAxisHat = true; + } else { + if (!already.contains(axis)) { + already.add(axis); + joystick.axes.add(axis); + } else { + Log.w(tag, " - DUPLICATE AXIS VALUE IN LIST: " + axis); } } } + Collections.sort(joystick.axes); + for (int idx = 0; idx < joystick.axes.size(); idx++) { + //Helps with creating new joypad mappings. + Log.i(tag, " - Mapping Android axis " + joystick.axes.get(idx) + " to Godot axis " + idx); + } + mJoysticksDevices.put(deviceId, joystick); + + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyconnectionchanged(id, true, joystick.name); + } + }); } @Override public void onInputDeviceRemoved(int deviceId) { - final int device_id = findJoystickDevice(deviceId); - - // Check if the evice has not been already removed - if (device_id > -1) { - mJoysticksDevices.remove(device_id); - - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(device_id, false, ""); - } - }); + // Check if the device has not been already removed + if (mJoystickIds.indexOfKey(deviceId) < 0) { + return; } + final int godotJoyId = mJoystickIds.get(deviceId); + mJoystickIds.delete(deviceId); + mJoysticksDevices.delete(deviceId); + + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyconnectionchanged(godotJoyId, false, ""); + } + }); } @Override @@ -407,16 +464,6 @@ public class GodotInputHandler implements InputDeviceListener { return button; } - private int findJoystickDevice(int device_id) { - for (int i = 0; i < mJoysticksDevices.size(); i++) { - if (mJoysticksDevices.get(i).device_id == device_id) { - return i; - } - } - - return -1; - } - private boolean handleMouseEvent(final MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_HOVER_ENTER: 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 82bd45ee3f..4b7318c718 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 @@ -30,9 +30,10 @@ package org.godotengine.godot.input; -import android.view.InputDevice.MotionRange; +import android.util.SparseArray; import java.util.ArrayList; +import java.util.List; /** * POJO class to represent a Joystick input device. @@ -40,6 +41,12 @@ import java.util.ArrayList; class Joystick { int device_id; String name; - ArrayList<MotionRange> axes; - ArrayList<MotionRange> hats; + List<Integer> axes = new ArrayList<Integer>(); + protected boolean hasAxisHat = false; + /* + * Keep track of values so we can prevent flooding the engine with useless events. + */ + protected final SparseArray axesValues = new SparseArray<Float>(4); + protected int hatX; + protected int hatY; } 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 a22b80761d..6c8a3d4219 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 @@ -46,7 +46,10 @@ import androidx.annotation.Nullable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -107,23 +110,52 @@ public abstract class GodotPlugin { * This method is invoked on the render thread. */ public final void onRegisterPluginWithGodotNative() { - nativeRegisterSingleton(getPluginName()); + registeredSignals.putAll( + registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals(), + getPluginGDNativeLibrariesPaths())); + } + + /** + * Register the plugin with Godot native code. + * + * This method must be invoked on the render thread. + */ + public static void registerPluginWithGodotNative(Object pluginObject, + GodotPluginInfoProvider pluginInfoProvider) { + registerPluginWithGodotNative(pluginObject, pluginInfoProvider.getPluginName(), + Collections.emptyList(), pluginInfoProvider.getPluginSignals(), + pluginInfoProvider.getPluginGDNativeLibrariesPaths()); + + // Notify that registration is complete. + pluginInfoProvider.onPluginRegistered(); + } + + private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject, + String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals, + Set<String> pluginGDNativeLibrariesPaths) { + nativeRegisterSingleton(pluginName, pluginObject); + + Set<Method> filteredMethods = new HashSet<>(); + Class clazz = pluginObject.getClass(); - Class clazz = getClass(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { - boolean found = false; - - for (String s : getPluginMethods()) { - if (s.equals(method.getName())) { - found = true; - break; + // Check if the method is annotated with {@link UsedByGodot}. + if (method.getAnnotation(UsedByGodot.class) != null) { + filteredMethods.add(method); + } else { + // For backward compatibility, process the methods from the given <pluginMethods> argument. + for (String methodName : pluginMethods) { + if (methodName.equals(method.getName())) { + filteredMethods.add(method); + break; + } } } - if (!found) - continue; + } - List<String> ptr = new ArrayList<String>(); + for (Method method : filteredMethods) { + List<String> ptr = new ArrayList<>(); Class[] paramTypes = method.getParameterTypes(); for (Class c : paramTypes) { @@ -133,26 +165,28 @@ public abstract class GodotPlugin { String[] pt = new String[ptr.size()]; ptr.toArray(pt); - nativeRegisterMethod(getPluginName(), method.getName(), method.getReturnType().getName(), pt); + nativeRegisterMethod(pluginName, method.getName(), method.getReturnType().getName(), pt); } // Register the signals for this plugin. - for (SignalInfo signalInfo : getPluginSignals()) { + Map<String, SignalInfo> registeredSignals = new HashMap<>(); + for (SignalInfo signalInfo : pluginSignals) { String signalName = signalInfo.getName(); - nativeRegisterSignal(getPluginName(), signalName, signalInfo.getParamTypesNames()); + nativeRegisterSignal(pluginName, signalName, signalInfo.getParamTypesNames()); registeredSignals.put(signalName, signalInfo); } // Get the list of gdnative libraries to register. - Set<String> gdnativeLibrariesPaths = getPluginGDNativeLibrariesPaths(); - if (!gdnativeLibrariesPaths.isEmpty()) { - nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0])); + if (!pluginGDNativeLibrariesPaths.isEmpty()) { + nativeRegisterGDNativeLibraries(pluginGDNativeLibrariesPaths.toArray(new String[0])); } + + return registeredSignals; } /** * Invoked once during the Godot Android initialization process after creation of the - * {@link org.godotengine.godot.GodotView} view. + * {@link org.godotengine.godot.GodotRenderView} view. * <p> * The plugin can return a non-null {@link View} layout in order to add it to the Godot view * hierarchy. @@ -198,6 +232,11 @@ public abstract class GodotPlugin { public boolean onMainBackPressed() { return false; } /** + * Invoked on the render thread when the Godot setup is complete. + */ + public void onGodotSetupCompleted() {} + + /** * Invoked on the render thread when the Godot main loop has started. */ public void onGodotMainLoopStarted() {} @@ -244,8 +283,11 @@ public abstract class GodotPlugin { /** * Returns the list of methods to be exposed to Godot. + * + * @deprecated Used the {@link UsedByGodot} annotation instead. */ @NonNull + @Deprecated public List<String> getPluginMethods() { return Collections.emptyList(); } @@ -290,8 +332,8 @@ public abstract class GodotPlugin { /** * Emit a registered Godot signal. - * @param signalName - * @param signalArgs + * @param signalName Name of the signal to emit. It will be validated against the set of registered signals. + * @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the {@link SignalInfo} matching the registered signalName parameter. */ protected void emitSignal(final String signalName, final Object... signalArgs) { try { @@ -301,6 +343,27 @@ public abstract class GodotPlugin { throw new IllegalArgumentException( "Signal " + signalName + " is not registered for this plugin."); } + emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs); + } catch (IllegalArgumentException exception) { + Log.w(TAG, exception.getMessage()); + if (BuildConfig.DEBUG) { + throw exception; + } + } + } + + /** + * Emit a Godot signal. + * @param godot + * @param pluginName Name of the Godot plugin the signal will be emitted from. The plugin must already be registered with the Godot engine. + * @param signalInfo Information about the signal to emit. + * @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the given {@link SignalInfo} parameter. + */ + public static void emitSignal(Godot godot, String pluginName, SignalInfo signalInfo, final Object... signalArgs) { + try { + if (signalInfo == null) { + throw new IllegalArgumentException("Signal must be non null."); + } // Validate the arguments count. Class<?>[] signalParamTypes = signalInfo.getParamTypes(); @@ -317,12 +380,8 @@ public abstract class GodotPlugin { } } - runOnRenderThread(new Runnable() { - @Override - public void run() { - nativeEmitSignal(getPluginName(), signalName, signalArgs); - } - }); + godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs)); + } catch (IllegalArgumentException exception) { Log.w(TAG, exception.getMessage()); if (BuildConfig.DEBUG) { @@ -335,7 +394,7 @@ public abstract class GodotPlugin { * Used to setup a {@link GodotPlugin} instance. * @param p_name Name of the instance. */ - private native void nativeRegisterSingleton(String p_name); + private static native void nativeRegisterSingleton(String p_name, Object object); /** * Used to complete registration of the {@link GodotPlugin} instance's methods. @@ -344,13 +403,13 @@ public abstract class GodotPlugin { * @param p_ret Return type of the registered method * @param p_params Method parameters types */ - private native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params); + private static native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params); /** * Used to register gdnative libraries bundled by the plugin. * @param gdnlibPaths Paths to the libraries relative to the 'assets' directory. */ - private native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths); + private static native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths); /** * Used to complete registration of the {@link GodotPlugin} instance's methods. @@ -358,7 +417,7 @@ public abstract class GodotPlugin { * @param signalName Name of the signal to register * @param signalParamTypes Signal parameters types */ - private native void nativeRegisterSignal(String pluginName, String signalName, String[] signalParamTypes); + private static native void nativeRegisterSignal(String pluginName, String signalName, String[] signalParamTypes); /** * Used to emit signal by {@link GodotPlugin} instance. @@ -366,5 +425,5 @@ public abstract class GodotPlugin { * @param signalName Name of the signal to emit * @param signalParams Signal parameters */ - private native void nativeEmitSignal(String pluginName, String signalName, Object[] signalParams); + private static native void nativeEmitSignal(String pluginName, String signalName, Object[] signalParams); } diff --git a/platform/javascript/http_request.h b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java index d32b2f265e..09366384c2 100644 --- a/platform/javascript/http_request.h +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java @@ -1,5 +1,5 @@ /*************************************************************************/ -/* http_request.h */ +/* GodotPluginInfoProvider.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,48 +28,45 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef HTTP_REQUEST_H -#define HTTP_REQUEST_H +package org.godotengine.godot.plugin; -#ifdef __cplusplus -extern "C" { -#endif +import androidx.annotation.NonNull; -#include "stddef.h" +import java.util.Collections; +import java.util.Set; -typedef enum { - XHR_READY_STATE_UNSENT = 0, - XHR_READY_STATE_OPENED = 1, - XHR_READY_STATE_HEADERS_RECEIVED = 2, - XHR_READY_STATE_LOADING = 3, - XHR_READY_STATE_DONE = 4, -} godot_xhr_ready_state_t; +/** + * Provides the set of information expected from a Godot plugin. + */ +public interface GodotPluginInfoProvider { + /** + * Returns the name of the plugin. + */ + @NonNull + String getPluginName(); -extern int godot_xhr_new(); -extern void godot_xhr_reset(int p_xhr_id); -extern void godot_xhr_free(int p_xhr_id); + /** + * Returns the list of signals to be exposed to Godot. + */ + @NonNull + default Set<SignalInfo> getPluginSignals() { + return Collections.emptySet(); + } -extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = nullptr, const char *p_password = nullptr); + /** + * Returns the paths for the plugin's gdnative libraries (if any). + * + * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file. + */ + @NonNull + default Set<String> getPluginGDNativeLibrariesPaths() { + return Collections.emptySet(); + } -extern void godot_xhr_set_request_header(int p_xhr_id, const char *p_header, const char *p_value); - -extern void godot_xhr_send_null(int p_xhr_id); -extern void godot_xhr_send_string(int p_xhr_id, const char *p_data); -extern void godot_xhr_send_data(int p_xhr_id, const void *p_data, int p_len); -extern void godot_xhr_abort(int p_xhr_id); - -/* this is an HTTPClient::ResponseCode, not ::Status */ -extern int godot_xhr_get_status(int p_xhr_id); -extern godot_xhr_ready_state_t godot_xhr_get_ready_state(int p_xhr_id); - -extern int godot_xhr_get_response_headers_length(int p_xhr_id); -extern void godot_xhr_get_response_headers(int p_xhr_id, char *r_dst, int p_len); - -extern int godot_xhr_get_response_length(int p_xhr_id); -extern void godot_xhr_get_response(int p_xhr_id, void *r_dst, int p_len); - -#ifdef __cplusplus + /** + * This is invoked on the render thread when the plugin described by this instance has been + * registered. + */ + default void onPluginRegistered() { + } } -#endif - -#endif /* HTTP_REQUEST_H */ 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 99811f72ed..5b41205253 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 @@ -44,8 +44,6 @@ import androidx.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -56,13 +54,6 @@ public final class GodotPluginRegistry { private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1."; - /** - * Name for the metadata containing the list of Godot plugins to enable. - */ - private static final String GODOT_ENABLED_PLUGINS_LABEL = "plugins"; - - private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"; - private static GodotPluginRegistry instance; private final ConcurrentHashMap<String, GodotPlugin> registry; @@ -132,37 +123,11 @@ public final class GodotPluginRegistry { return; } - // When using the Godot editor for building and exporting the apk, this is used to check - // 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(PLUGIN_VALUE_SEPARATOR_REGEX); - if (enabledPluginsList.length == 0) { - // No plugins to enable. Aborting early. - return; - } - - enabledPluginsSet = new HashSet<>(); - for (String enabledPlugin : enabledPluginsList) { - enabledPluginsSet.add(enabledPlugin.trim()); - } - } else { - enabledPluginsSet = null; - } - int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length(); for (String metaDataName : metaData.keySet()) { // Parse the meta-data looking for entry with the Godot plugin name prefix. if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) { String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim(); - if (enabledPluginsSet != null && !enabledPluginsSet.contains(pluginName)) { - Log.w(TAG, "Plugin " + pluginName + " is listed in the dependencies but is not enabled."); - continue; - } - Log.i(TAG, "Initializing Godot plugin " + pluginName); // Retrieve the plugin class full name. @@ -177,8 +142,7 @@ public final class GodotPluginRegistry { .getConstructor(Godot.class); GodotPlugin pluginHandle = pluginConstructor.newInstance(godot); - // Load the plugin initializer into the registry using the plugin name - // as key. + // Load the plugin initializer into the registry using the plugin name as key. if (!pluginName.equals(pluginHandle.getPluginName())) { Log.w(TAG, "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName()); diff --git a/platform/uwp/thread_uwp.h b/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java index 0bfc71d2e0..04c091d944 100644 --- a/platform/uwp/thread_uwp.h +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java @@ -1,5 +1,5 @@ /*************************************************************************/ -/* thread_uwp.h */ +/* UsedByGodot.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,32 +28,18 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef THREAD_UWP_H -#define THREAD_UWP_H - -#ifdef UWP_ENABLED - -#include "core/os/thread.h" - -#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() {} - -public: - virtual ID get_id() const; - - static void make_default(); - - ~ThreadUWP() {} -}; - -#endif // UWP_ENABLED - -#endif // THREAD_UWP_H +package org.godotengine.godot.plugin; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to indicate a method is being invoked from the Godot game logic. + * + * At runtime, annotated plugin methods are detected and automatically registered. + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface UsedByGodot {} diff --git a/platform/android/java/nativeSrcsConfigs/README.md b/platform/android/java/nativeSrcsConfigs/README.md index e48505ccda..9d884415cc 100644 --- a/platform/android/java/nativeSrcsConfigs/README.md +++ b/platform/android/java/nativeSrcsConfigs/README.md @@ -1,4 +1,4 @@ ## Native sources configs -This is a non functional Android library used to provide Android Studio editor support to the Godot project native files. +This is a non-functional Android library used to provide Android Studio editor support to the Godot project native files. Nothing else should be added to this library. diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle index 65b7bb9dc9..158bb2b98e 100644 --- a/platform/android/java/nativeSrcsConfigs/build.gradle +++ b/platform/android/java/nativeSrcsConfigs/build.gradle @@ -20,9 +20,6 @@ android { packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' - - // Should be uncommented for development purpose within Android Studio - // doNotStrip '**/*.so' } sourceSets { @@ -31,6 +28,8 @@ android { } } + ndkVersion versions.ndkVersion + externalNativeBuild { cmake { path "CMakeLists.txt" diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 2b5ca530da..f49b0e843a 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -37,7 +37,8 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, if (!M) return false; - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); MethodInfo *method = nullptr; for (List<MethodInfo>::Element *E = M->get().front(); E; E = E->next()) { @@ -964,7 +965,8 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { if (class_cache.has(p_class)) return class_cache[p_class]; - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, Ref<JavaClass>()); jclass bclass = env->FindClass(p_class.utf8().get_data()); ERR_FAIL_COND_V(!bclass, Ref<JavaClass>()); @@ -1148,7 +1150,8 @@ JavaClassWrapper *JavaClassWrapper::singleton = nullptr; JavaClassWrapper::JavaClassWrapper(jobject p_activity) { singleton = this; - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jclass activityClass = env->FindClass("android/app/Activity"); jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 8f8275826d..ec3b6f8ac0 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -34,7 +34,7 @@ // JNIEnv is only valid within the thread it belongs to, in a multi threading environment // we can't cache it. // For GodotIO we call all access methods from our thread and we thus get a valid JNIEnv -// from ThreadAndroid. +// from get_jni_env(). GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instance) { godot_io_instance = p_env->NewGlobalRef(p_godot_io_instance); @@ -72,7 +72,8 @@ jobject GodotIOJavaWrapper::get_instance() { Error GodotIOJavaWrapper::open_uri(const String &p_uri) { if (_open_URI) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, ERR_UNAVAILABLE); jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; } else { @@ -82,7 +83,8 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { String GodotIOJavaWrapper::get_user_data_dir() { if (_get_data_dir) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); return jstring_to_string(s, env); } else { @@ -92,7 +94,8 @@ String GodotIOJavaWrapper::get_user_data_dir() { String GodotIOJavaWrapper::get_locale() { if (_get_locale) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_locale); return jstring_to_string(s, env); } else { @@ -102,7 +105,8 @@ String GodotIOJavaWrapper::get_locale() { String GodotIOJavaWrapper::get_model() { if (_get_model) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_model); return jstring_to_string(s, env); } else { @@ -112,7 +116,8 @@ String GodotIOJavaWrapper::get_model() { int GodotIOJavaWrapper::get_screen_dpi() { if (_get_screen_DPI) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 160); return env->CallIntMethod(godot_io_instance, _get_screen_DPI); } else { return 160; @@ -121,7 +126,8 @@ int GodotIOJavaWrapper::get_screen_dpi() { void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { if (_screen_get_usable_rect) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _screen_get_usable_rect); ERR_FAIL_COND(env->GetArrayLength(returnArray) != 4); jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE); @@ -134,7 +140,8 @@ void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { String GodotIOJavaWrapper::get_unique_id() { if (_get_unique_id) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_id); return jstring_to_string(s, env); } else { @@ -148,7 +155,8 @@ bool GodotIOJavaWrapper::has_vk() { void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { if (_show_keyboard) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end); } @@ -156,21 +164,24 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int void GodotIOJavaWrapper::hide_vk() { if (_hide_keyboard) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); env->CallVoidMethod(godot_io_instance, _hide_keyboard); } } void GodotIOJavaWrapper::set_screen_orientation(int p_orient) { if (_set_screen_orientation) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); env->CallVoidMethod(godot_io_instance, _set_screen_orientation, p_orient); } } int GodotIOJavaWrapper::get_screen_orientation() { if (_get_screen_orientation) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 0); return env->CallIntMethod(godot_io_instance, _get_screen_orientation); } else { return 0; @@ -179,7 +190,8 @@ int GodotIOJavaWrapper::get_screen_orientation() { String GodotIOJavaWrapper::get_system_dir(int p_dir) { if (_get_system_dir) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String(".")); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir); return jstring_to_string(s, env); } else { @@ -187,14 +199,14 @@ String GodotIOJavaWrapper::get_system_dir(int p_dir) { } } -// volatile because it can be changed from non-main thread and we need to +// SafeNumeric because it can be changed from non-main thread and we need to // ensure the change is immediately visible to other threads. -static volatile int virtual_keyboard_height; +static SafeNumeric<int> virtual_keyboard_height; int GodotIOJavaWrapper::get_vk_height() { - return virtual_keyboard_height; + return virtual_keyboard_height.get(); } void GodotIOJavaWrapper::set_vk_height(int p_height) { - virtual_keyboard_height = p_height; + virtual_keyboard_height.set(p_height); } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index e3a4ce63ef..0c342dc280 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -87,7 +87,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en godot_java = new GodotJavaWrapper(env, activity, godot_instance); godot_io_java = new GodotIOJavaWrapper(env, godot_java->get_member_object("io", "Lorg/godotengine/godot/GodotIO;", env)); - ThreadAndroid::make_default(jvm); + init_thread_jandroid(jvm, env); jobject amgr = env->NewGlobalRef(p_asset_manager); @@ -119,7 +119,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) { - ThreadAndroid::setup_thread(); + setup_android_thread(); const char **cmdline = nullptr; jstring *j_cmdline = nullptr; @@ -127,9 +127,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc if (p_cmdline) { cmdlen = env->GetArrayLength(p_cmdline); if (cmdlen) { - cmdline = (const char **)malloc((cmdlen + 1) * sizeof(const char *)); + cmdline = (const char **)memalloc((cmdlen + 1) * sizeof(const char *)); + ERR_FAIL_NULL_MSG(cmdline, "Out of memory."); cmdline[cmdlen] = nullptr; - j_cmdline = (jstring *)malloc(cmdlen * sizeof(jstring)); + j_cmdline = (jstring *)memalloc(cmdlen * sizeof(jstring)); + ERR_FAIL_NULL_MSG(j_cmdline, "Out of memory."); for (int i = 0; i < cmdlen; i++) { jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i); @@ -147,13 +149,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc for (int i = 0; i < cmdlen; ++i) { env->ReleaseStringUTFChars(j_cmdline[i], cmdline[i]); } - free(j_cmdline); + memfree(j_cmdline); } - free(cmdline); + memfree(cmdline); } if (err != OK) { - return; //should exit instead and print the error + return; // should exit instead and print the error } java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity())); @@ -206,7 +208,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl return; if (step == 0) { - // Since Godot is initialized on the UI thread, _main_thread_id was set to that thread's id, + // Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id, // but for Godot purposes, the main thread is the one running the game loop Main::setup2(Thread::get_caller_id()); ++step; @@ -215,9 +217,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl if (step == 1) { if (!Main::start()) { - return; //should exit instead and print the error + return; // should exit instead and print the error } + godot_java->on_godot_setup_completed(env); os_android->main_loop_begin(); godot_java->on_godot_main_loop_started(env); ++step; @@ -382,7 +385,7 @@ 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(); + setup_android_thread(); AudioDriverAndroid::thread_func(env); } diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index cb26c7b8c5..6b5e44f371 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -33,7 +33,8 @@ #include "thread_jandroid.h" GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); _godot_view = env->NewGlobalRef(godot_view); @@ -47,20 +48,26 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { void GodotJavaViewWrapper::request_pointer_capture() { if (_request_pointer_capture != 0) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(_godot_view, _request_pointer_capture); } } void GodotJavaViewWrapper::release_pointer_capture() { if (_request_pointer_capture != 0) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(_godot_view, _release_pointer_capture); } } GodotJavaViewWrapper::~GodotJavaViewWrapper() { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->DeleteGlobalRef(_godot_view); env->DeleteGlobalRef(_cls); } diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h index 548c278292..bfb4369fb8 100644 --- a/platform/android/java_godot_view_wrapper.h +++ b/platform/android/java_godot_view_wrapper.h @@ -34,6 +34,8 @@ #include <android/log.h> #include <jni.h> +#include "string_android.h" + // Class that makes functions in java/src/org/godotengine/godot/GodotView.java callable from C++ class GodotJavaViewWrapper { private: diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index c4e7f272d3..bfd93345f3 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -33,7 +33,7 @@ // JNIEnv is only valid within the thread it belongs to, in a multi threading environment // we can't cache it. // For Godot we call most access methods from our thread and we thus get a valid JNIEnv -// from ThreadAndroid. For one or two we expect to pass the environment +// from get_jni_env(). For one or two we expect to pass the environment // TODO we could probably create a base class for this... @@ -74,6 +74,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z"); _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V"); _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;"); + _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V"); _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); // get some Activity method pointers... @@ -91,7 +92,9 @@ jobject GodotJavaWrapper::get_activity() { jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) { if (godot_class) { if (p_env == nullptr) - p_env = ThreadAndroid::get_env(); + p_env = get_jni_env(); + + ERR_FAIL_COND_V(p_env == nullptr, nullptr); jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class); return p_env->GetStaticObjectField(godot_class, fid); @@ -102,7 +105,9 @@ jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_cl jobject GodotJavaWrapper::get_class_loader() { if (_get_class_loader) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + return env->CallObjectMethod(activity, _get_class_loader); } else { return nullptr; @@ -113,55 +118,77 @@ GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { if (_godot_view != nullptr) { return _godot_view; } - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + jmethodID godot_view_getter = env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); _godot_view = new GodotJavaViewWrapper(env->CallObjectMethod(godot_instance, godot_view_getter)); return _godot_view; } void GodotJavaWrapper::on_video_init(JNIEnv *p_env) { - if (_on_video_init) + if (_on_video_init) { if (p_env == nullptr) - p_env = ThreadAndroid::get_env(); + p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); + + p_env->CallVoidMethod(godot_instance, _on_video_init); + } +} - p_env->CallVoidMethod(godot_instance, _on_video_init); +void GodotJavaWrapper::on_godot_setup_completed(JNIEnv *p_env) { + if (_on_godot_setup_completed) { + if (p_env == nullptr) { + p_env = get_jni_env(); + } + p_env->CallVoidMethod(godot_instance, _on_godot_setup_completed); + } } void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) { if (_on_godot_main_loop_started) { if (p_env == nullptr) { - p_env = ThreadAndroid::get_env(); + p_env = get_jni_env(); } + ERR_FAIL_COND(p_env == nullptr); + p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started); } - p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started); } void GodotJavaWrapper::restart(JNIEnv *p_env) { - if (_restart) + if (_restart) { if (p_env == nullptr) - p_env = ThreadAndroid::get_env(); + p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); - p_env->CallVoidMethod(godot_instance, _restart); + p_env->CallVoidMethod(godot_instance, _restart); + } } void GodotJavaWrapper::force_quit(JNIEnv *p_env) { - if (_finish) + if (_finish) { if (p_env == nullptr) - p_env = ThreadAndroid::get_env(); + p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); - p_env->CallVoidMethod(godot_instance, _finish); + p_env->CallVoidMethod(godot_instance, _finish); + } } void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) { if (_set_keep_screen_on) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _set_keep_screen_on, p_enabled); } } void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { if (_alert) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle); @@ -169,7 +196,9 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { } int GodotJavaWrapper::get_gles_version_code() { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 0); + if (_get_GLES_version_code) { return env->CallIntMethod(godot_instance, _get_GLES_version_code); } @@ -183,7 +212,9 @@ bool GodotJavaWrapper::has_get_clipboard() { String GodotJavaWrapper::get_clipboard() { if (_get_clipboard) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); + jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_clipboard); return jstring_to_string(s, env); } else { @@ -193,7 +224,9 @@ String GodotJavaWrapper::get_clipboard() { String GodotJavaWrapper::get_input_fallback_mapping() { if (_get_input_fallback_mapping) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); + jstring fallback_mapping = (jstring)env->CallObjectMethod(godot_instance, _get_input_fallback_mapping); return jstring_to_string(fallback_mapping, env); } else { @@ -207,7 +240,9 @@ bool GodotJavaWrapper::has_set_clipboard() { void GodotJavaWrapper::set_clipboard(const String &p_text) { if (_set_clipboard) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); env->CallVoidMethod(godot_instance, _set_clipboard, jStr); } @@ -215,7 +250,9 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) { bool GodotJavaWrapper::request_permission(const String &p_name) { if (_request_permission) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + jstring jStrName = env->NewStringUTF(p_name.utf8().get_data()); return env->CallBooleanMethod(godot_instance, _request_permission, jStrName); } else { @@ -225,7 +262,9 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { bool GodotJavaWrapper::request_permissions() { if (_request_permissions) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + return env->CallBooleanMethod(godot_instance, _request_permissions); } else { return false; @@ -235,7 +274,9 @@ bool GodotJavaWrapper::request_permissions() { Vector<String> GodotJavaWrapper::get_granted_permissions() const { Vector<String> permissions_list; if (_get_granted_permissions) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, permissions_list); + jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions); jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object); @@ -253,14 +294,18 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const { void GodotJavaWrapper::init_input_devices() { if (_init_input_devices) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _init_input_devices); } } jobject GodotJavaWrapper::get_surface() { if (_get_surface) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + return env->CallObjectMethod(godot_instance, _get_surface); } else { return nullptr; @@ -269,7 +314,9 @@ jobject GodotJavaWrapper::get_surface() { bool GodotJavaWrapper::is_activity_resumed() { if (_is_activity_resumed) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + return env->CallBooleanMethod(godot_instance, _is_activity_resumed); } else { return false; @@ -278,7 +325,9 @@ bool GodotJavaWrapper::is_activity_resumed() { void GodotJavaWrapper::vibrate(int p_duration_ms) { if (_vibrate) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms); } } diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 99a60dffa1..0e20747a16 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -66,6 +66,7 @@ private: jmethodID _is_activity_resumed = 0; jmethodID _vibrate = 0; jmethodID _get_input_fallback_mapping = 0; + jmethodID _on_godot_setup_completed = 0; jmethodID _on_godot_main_loop_started = 0; jmethodID _get_class_loader = 0; @@ -80,6 +81,7 @@ public: GodotJavaViewWrapper *get_godot_view(); void on_video_init(JNIEnv *p_env = nullptr); + void on_godot_setup_completed(JNIEnv *p_env = nullptr); void on_godot_main_loop_started(JNIEnv *p_env = nullptr); void restart(JNIEnv *p_env = nullptr); void force_quit(JNIEnv *p_env = nullptr); diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index ba7b3d3775..dbd1870ee6 100644 --- a/platform/android/net_socket_android.cpp +++ b/platform/android/net_socket_android.cpp @@ -38,7 +38,7 @@ jmethodID NetSocketAndroid::_multicast_lock_acquire = 0; jmethodID NetSocketAndroid::_multicast_lock_release = 0; void NetSocketAndroid::setup(jobject p_net_utils) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); net_utils = env->NewGlobalRef(p_net_utils); @@ -51,14 +51,14 @@ void NetSocketAndroid::setup(jobject p_net_utils) { void NetSocketAndroid::multicast_lock_acquire() { if (_multicast_lock_acquire) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(net_utils, _multicast_lock_acquire); } } void NetSocketAndroid::multicast_lock_release() { if (_multicast_lock_release) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(net_utils, _multicast_lock_release); } } @@ -103,7 +103,7 @@ Error NetSocketAndroid::set_broadcasting_enabled(bool p_enabled) { return OK; } -Error NetSocketAndroid::join_multicast_group(const IP_Address &p_multi_address, String p_if_name) { +Error NetSocketAndroid::join_multicast_group(const IPAddress &p_multi_address, String p_if_name) { Error err = NetSocketPosix::join_multicast_group(p_multi_address, p_if_name); if (err != OK) return err; @@ -115,7 +115,7 @@ Error NetSocketAndroid::join_multicast_group(const IP_Address &p_multi_address, return OK; } -Error NetSocketAndroid::leave_multicast_group(const IP_Address &p_multi_address, String p_if_name) { +Error NetSocketAndroid::leave_multicast_group(const IPAddress &p_multi_address, String p_if_name) { Error err = NetSocketPosix::leave_multicast_group(p_multi_address, p_if_name); if (err != OK) return err; diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h index cc2a68ac49..60090c26bb 100644 --- a/platform/android/net_socket_android.h +++ b/platform/android/net_socket_android.h @@ -67,8 +67,8 @@ public: virtual void close(); virtual Error set_broadcasting_enabled(bool p_enabled); - 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); + virtual Error join_multicast_group(const IPAddress &p_multi_address, String p_if_name); + virtual Error leave_multicast_group(const IPAddress &p_multi_address, String p_if_name); NetSocketAndroid() {} ~NetSocketAndroid(); diff --git a/platform/android/plugin/godot_plugin_config.h b/platform/android/plugin/godot_plugin_config.h index 611053ccba..173ac115a2 100644 --- a/platform/android/plugin/godot_plugin_config.h +++ b/platform/android/plugin/godot_plugin_config.h @@ -35,23 +35,6 @@ #include "core/io/config_file.h" #include "core/string/ustring.h" -static const char *PLUGIN_CONFIG_EXT = ".gdap"; - -static const char *CONFIG_SECTION = "config"; -static const char *CONFIG_NAME_KEY = "name"; -static const char *CONFIG_BINARY_TYPE_KEY = "binary_type"; -static const char *CONFIG_BINARY_KEY = "binary"; - -static const char *DEPENDENCIES_SECTION = "dependencies"; -static const char *DEPENDENCIES_LOCAL_KEY = "local"; -static const char *DEPENDENCIES_REMOTE_KEY = "remote"; -static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos"; - -static const char *BINARY_TYPE_LOCAL = "local"; -static const char *BINARY_TYPE_REMOTE = "remote"; - -static const char *PLUGIN_VALUE_SEPARATOR = "|"; - /* The `config` section and fields are required and defined as follow: - **name**: name of the plugin @@ -67,7 +50,24 @@ The `dependencies` section and fields are optional and defined as follow: See https://github.com/godotengine/godot/issues/38157#issuecomment-618773871 */ -struct PluginConfig { +struct PluginConfigAndroid { + inline static const char *PLUGIN_CONFIG_EXT = ".gdap"; + + inline static const char *CONFIG_SECTION = "config"; + inline static const char *CONFIG_NAME_KEY = "name"; + inline static const char *CONFIG_BINARY_TYPE_KEY = "binary_type"; + inline static const char *CONFIG_BINARY_KEY = "binary"; + + inline static const char *DEPENDENCIES_SECTION = "dependencies"; + inline static const char *DEPENDENCIES_LOCAL_KEY = "local"; + inline static const char *DEPENDENCIES_REMOTE_KEY = "remote"; + inline static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos"; + + inline static const char *BINARY_TYPE_LOCAL = "local"; + inline static const char *BINARY_TYPE_REMOTE = "remote"; + + inline static const char *PLUGIN_VALUE_SEPARATOR = "|"; + // Set to true when the config file is properly loaded. bool valid_config = false; // Unix timestamp of last change to this plugin. @@ -88,7 +88,7 @@ struct PluginConfig { * Set of prebuilt plugins. * Currently unused, this is just for future reference: */ -// static const PluginConfig MY_PREBUILT_PLUGIN = { +// static const PluginConfigAndroid MY_PREBUILT_PLUGIN = { // /*.valid_config =*/true, // /*.last_updated =*/0, // /*.name =*/"GodotPayment", @@ -112,9 +112,9 @@ static inline String resolve_local_dependency_path(String plugin_config_dir, Str 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; +static inline PluginConfigAndroid resolve_prebuilt_plugin(PluginConfigAndroid prebuilt_plugin, String plugin_config_dir) { + PluginConfigAndroid resolved = prebuilt_plugin; + resolved.binary = resolved.binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL ? resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.binary) : prebuilt_plugin.binary; if (!prebuilt_plugin.local_dependencies.is_empty()) { resolved.local_dependencies.clear(); for (int i = 0; i < prebuilt_plugin.local_dependencies.size(); i++) { @@ -124,21 +124,22 @@ static inline PluginConfig resolve_prebuilt_plugin(PluginConfig prebuilt_plugin, return resolved; } -static inline Vector<PluginConfig> get_prebuilt_plugins(String plugins_base_dir) { - Vector<PluginConfig> prebuilt_plugins; +static inline Vector<PluginConfigAndroid> get_prebuilt_plugins(String plugins_base_dir) { + Vector<PluginConfigAndroid> prebuilt_plugins; // prebuilt_plugins.push_back(resolve_prebuilt_plugin(MY_PREBUILT_PLUGIN, plugins_base_dir)); return prebuilt_plugins; } -static inline bool is_plugin_config_valid(PluginConfig plugin_config) { +static inline bool is_plugin_config_valid(PluginConfigAndroid plugin_config) { bool valid_name = !plugin_config.name.is_empty(); - bool valid_binary_type = plugin_config.binary_type == BINARY_TYPE_LOCAL || - plugin_config.binary_type == BINARY_TYPE_REMOTE; + bool valid_binary_type = plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL || + plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE; bool valid_binary = false; if (valid_binary_type) { valid_binary = !plugin_config.binary.is_empty() && - (plugin_config.binary_type == BINARY_TYPE_REMOTE || + (plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE || + FileAccess::exists(plugin_config.binary)); } @@ -154,7 +155,7 @@ static inline bool is_plugin_config_valid(PluginConfig plugin_config) { 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) { +static inline uint64_t get_plugin_modification_time(const PluginConfigAndroid &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)); @@ -166,30 +167,30 @@ static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_c return last_updated; } -static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) { - PluginConfig plugin_config = {}; +static inline PluginConfigAndroid load_plugin_config(Ref<ConfigFile> config_file, const String &path) { + PluginConfigAndroid 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()); + plugin_config.name = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::CONFIG_NAME_KEY, String()); + plugin_config.binary_type = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::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; + String binary_path = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::CONFIG_BINARY_KEY, String()); + plugin_config.binary = plugin_config.binary_type == PluginConfigAndroid::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 (config_file->has_section(PluginConfigAndroid::DEPENDENCIES_SECTION)) { + Vector<String> local_dependencies_paths = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_LOCAL_KEY, Vector<String>()); if (!local_dependencies_paths.is_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.remote_dependencies = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_REMOTE_KEY, Vector<String>()); + plugin_config.custom_maven_repos = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY, Vector<String>()); } plugin_config.valid_config = is_plugin_config_valid(plugin_config); @@ -200,12 +201,12 @@ static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const return plugin_config; } -static inline String get_plugins_binaries(String binary_type, Vector<PluginConfig> plugins_configs) { +static inline String get_plugins_binaries(String binary_type, Vector<PluginConfigAndroid> plugins_configs) { String plugins_binaries; if (!plugins_configs.is_empty()) { Vector<String> binaries; for (int i = 0; i < plugins_configs.size(); i++) { - PluginConfig config = plugins_configs[i]; + PluginConfigAndroid config = plugins_configs[i]; if (!config.valid_config) { continue; } @@ -214,27 +215,27 @@ static inline String get_plugins_binaries(String binary_type, Vector<PluginConfi binaries.push_back(config.binary); } - if (binary_type == BINARY_TYPE_LOCAL) { + if (binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL) { binaries.append_array(config.local_dependencies); } - if (binary_type == BINARY_TYPE_REMOTE) { + if (binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE) { binaries.append_array(config.remote_dependencies); } } - plugins_binaries = String(PLUGIN_VALUE_SEPARATOR).join(binaries); + plugins_binaries = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(binaries); } return plugins_binaries; } -static inline String get_plugins_custom_maven_repos(Vector<PluginConfig> plugins_configs) { +static inline String get_plugins_custom_maven_repos(Vector<PluginConfigAndroid> plugins_configs) { String custom_maven_repos; if (!plugins_configs.is_empty()) { Vector<String> repos_urls; for (int i = 0; i < plugins_configs.size(); i++) { - PluginConfig config = plugins_configs[i]; + PluginConfigAndroid config = plugins_configs[i]; if (!config.valid_config) { continue; } @@ -242,24 +243,24 @@ static inline String get_plugins_custom_maven_repos(Vector<PluginConfig> plugins repos_urls.append_array(config.custom_maven_repos); } - custom_maven_repos = String(PLUGIN_VALUE_SEPARATOR).join(repos_urls); + custom_maven_repos = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(repos_urls); } return custom_maven_repos; } -static inline String get_plugins_names(Vector<PluginConfig> plugins_configs) { +static inline String get_plugins_names(Vector<PluginConfigAndroid> plugins_configs) { String plugins_names; if (!plugins_configs.is_empty()) { Vector<String> names; for (int i = 0; i < plugins_configs.size(); i++) { - PluginConfig config = plugins_configs[i]; + PluginConfigAndroid config = plugins_configs[i]; if (!config.valid_config) { continue; } names.push_back(config.name); } - plugins_names = String(PLUGIN_VALUE_SEPARATOR).join(names); + plugins_names = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(names); } return plugins_names; diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index f602e99e61..ba3e9fa20f 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -41,7 +41,7 @@ static HashMap<String, JNISingleton *> jni_singletons; extern "C" { -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jclass clazz, jstring name, jobject obj) { String singname = jstring_to_string(name, env); JNISingleton *s = (JNISingleton *)ClassDB::instance("JNISingleton"); s->set_instance(env->NewGlobalRef(obj)); @@ -51,7 +51,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis ProjectSettings::get_singleton()->set(singname, s); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args) { String singname = jstring_to_string(sname, env); ERR_FAIL_COND(!jni_singletons.has(singname)); @@ -83,7 +83,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis s->add_method(mname, mid, types, get_jni_type(retval)); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types) { String singleton_name = jstring_to_string(j_plugin_name, env); ERR_FAIL_COND(!jni_singletons.has(singleton_name)); @@ -104,7 +104,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis singleton->add_signal(signal_name, types); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params) { String singleton_name = jstring_to_string(j_plugin_name, env); ERR_FAIL_COND(!jni_singletons.has(singleton_name)); @@ -129,7 +129,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS singleton->emit_signal(signal_name, args, count); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths) { int gdnlib_count = env->GetArrayLength(gdnlib_paths); if (gdnlib_count == 0) { return; diff --git a/platform/android/plugin/godot_plugin_jni.h b/platform/android/plugin/godot_plugin_jni.h index 8a08ec3709..b87f922e03 100644 --- a/platform/android/plugin/godot_plugin_jni.h +++ b/platform/android/plugin/godot_plugin_jni.h @@ -35,11 +35,11 @@ #include <jni.h> extern "C" { -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name); -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args); -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types); -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params); -JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jclass clazz, jstring name, jobject obj); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths); } #endif // GODOT_PLUGIN_JNI_H diff --git a/platform/android/string_android.h b/platform/android/string_android.h index 25c6f749d4..3721315d3f 100644 --- a/platform/android/string_android.h +++ b/platform/android/string_android.h @@ -37,14 +37,14 @@ /** * Converts JNI jstring to Godot String. * @param source Source JNI string. If null an empty string is returned. - * @param env JNI environment instance. If null obtained by ThreadAndroid::get_env(). + * @param env JNI environment instance. If null obtained by get_jni_env(). * @return Godot string instance. */ static inline String jstring_to_string(jstring source, JNIEnv *env = nullptr) { String result; if (source) { if (!env) { - env = ThreadAndroid::get_env(); + env = get_jni_env(); } const char *const source_utf8 = env->GetStringUTFChars(source, nullptr); if (source_utf8) { diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index cb3527067f..ba379c8d43 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -30,116 +30,54 @@ #include "thread_jandroid.h" -#include "core/object/script_language.h" -#include "core/os/memory.h" -#include "core/templates/safe_refcount.h" +#include <android/log.h> -static void _thread_id_key_destr_callback(void *p_value) { - memdelete(static_cast<Thread::ID *>(p_value)); -} - -static pthread_key_t _create_thread_id_key() { - pthread_key_t key; - pthread_key_create(&key, &_thread_id_key_destr_callback); - return key; -} - -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; -} +#include "core/os/thread.h" -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 - t->id = atomic_increment(&next_thread_id); - pthread_setspecific(thread_id_key, (void *)memnew(ID(t->id))); - t->callback(t->user); - ScriptServer::thread_exit(); - return nullptr; -} +static JavaVM *java_vm = nullptr; +static thread_local JNIEnv *env = nullptr; -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; - pthread_attr_init(&tr->pthread_attr); - pthread_attr_setdetachstate(&tr->pthread_attr, PTHREAD_CREATE_JOINABLE); +// The logic here need to improve, init_thread/term_tread are designed to work with Thread::callback +// Calling init_thread from setup_android_thread and get_jni_env to setup an env we're keeping and not detaching +// could cause issues on app termination. +// +// We should be making sure that any thread started calls a nice cleanup function when it's done, +// especially now that we use many more threads. - pthread_create(&tr->pthread, &tr->pthread_attr, thread_callback, tr); +static void init_thread() { + if (env) { + // thread never detached! just keep using... + return; + } - return tr; + java_vm->AttachCurrentThread(&env, nullptr); } -Thread::ID ThreadAndroid::get_thread_id_func_jandroid() { - void *value = pthread_getspecific(thread_id_key); - - if (value) - return *static_cast<ID *>(value); +static void term_thread() { + java_vm->DetachCurrentThread(); - ID new_id = atomic_increment(&next_thread_id); - pthread_setspecific(thread_id_key, (void *)memnew(ID(new_id))); - return new_id; + // this is no longer valid, must called init_thread to re-establish + env = nullptr; } -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); - - pthread_join(tp->pthread, nullptr); - tp->pthread = 0; +void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) { + java_vm = p_jvm; + env = p_env; + Thread::_set_platform_funcs(nullptr, nullptr, &init_thread, &term_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) { - java_vm->DetachCurrentThread(); - pthread_setspecific(jvm_key, nullptr); +void setup_android_thread() { + if (!env) { + // !BAS! see remarks above + init_thread(); } } -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; - java_vm->AttachCurrentThread(&env, nullptr); - pthread_setspecific(jvm_key, (void *)env); -} - -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; - wait_to_finish_func = wait_to_finish_func_jandroid; - pthread_key_create(&jvm_key, _thread_destroyed); - setup_thread(); -} - -JNIEnv *ThreadAndroid::get_env() { - if (!pthread_getspecific(jvm_key)) { - setup_thread(); +JNIEnv *get_jni_env() { + if (!env) { + // !BAS! see remarks above + init_thread(); } - JNIEnv *env = nullptr; - java_vm->AttachCurrentThread(&env, nullptr); return env; } - -ThreadAndroid::ThreadAndroid() { - pthread = 0; -} - -ThreadAndroid::~ThreadAndroid() { -} diff --git a/platform/android/thread_jandroid.h b/platform/android/thread_jandroid.h index c37e2d740e..ff13ae911f 100644 --- a/platform/android/thread_jandroid.h +++ b/platform/android/thread_jandroid.h @@ -28,46 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef THREAD_POSIX_H -#define THREAD_POSIX_H +#ifndef THREAD_JANDROID_H +#define THREAD_JANDROID_H -#include "core/os/thread.h" #include <jni.h> -#include <pthread.h> -#include <sys/types.h> -class ThreadAndroid : public Thread { - static pthread_key_t thread_id_key; - static ID next_thread_id; +void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env); - pthread_t pthread; - pthread_attr_t pthread_attr; - ThreadCreateCallback callback; - void *user; - ID id; - - static Thread *create_thread_jandroid(); - - static void *thread_callback(void *userdata); - - static Thread *create_func_jandroid(ThreadCreateCallback p_callback, void *, const Settings &); - static ID get_thread_id_func_jandroid(); - static void wait_to_finish_func_jandroid(Thread *p_thread); - - static void _thread_destroyed(void *value); - ThreadAndroid(); - - static pthread_key_t jvm_key; - static JavaVM *java_vm; - -public: - virtual ID get_id() const; - - static void make_default(JavaVM *p_java_vm); - static void setup_thread(); - static JNIEnv *get_env(); - - ~ThreadAndroid(); -}; +void setup_android_thread(); +JNIEnv *get_jni_env(); #endif diff --git a/platform/android/vulkan/vulkan_context_android.cpp b/platform/android/vulkan/vulkan_context_android.cpp index 1bf85f07f1..63f2026fae 100644 --- a/platform/android/vulkan/vulkan_context_android.cpp +++ b/platform/android/vulkan/vulkan_context_android.cpp @@ -52,10 +52,10 @@ int VulkanContextAndroid::window_create(ANativeWindow *p_window, int p_width, in return _window_create(DisplayServer::MAIN_WINDOW_ID, surface, p_width, p_height); } -VulkanContextAndroid::VulkanContextAndroid() { - // TODO: fix validation layers - use_validation_layers = false; -} +bool VulkanContextAndroid::_use_validation_layers() { + uint32_t count = 0; + _get_preferred_validation_layers(&count, nullptr); -VulkanContextAndroid::~VulkanContextAndroid() { + // On Android, we use validation layers automatically if they were explicitly linked with the app. + return count > 0; } diff --git a/platform/android/vulkan/vulkan_context_android.h b/platform/android/vulkan/vulkan_context_android.h index c608f2d665..5a84eaf8f3 100644 --- a/platform/android/vulkan/vulkan_context_android.h +++ b/platform/android/vulkan/vulkan_context_android.h @@ -36,13 +36,16 @@ struct ANativeWindow; class VulkanContextAndroid : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; + virtual const char *_get_platform_surface_extension() const override; public: int window_create(ANativeWindow *p_window, int p_width, int p_height); - VulkanContextAndroid(); - ~VulkanContextAndroid(); + VulkanContextAndroid() = default; + ~VulkanContextAndroid() override = default; + +protected: + bool _use_validation_layers() override; }; #endif // VULKAN_CONTEXT_ANDROID_H diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index ad4af9ba6a..cf358e0878 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -34,6 +34,7 @@ def get_opts(): " validation layers)", False, ), + BoolVariable("ios_simulator", "Build for iOS Simulator", False), BoolVariable("ios_exceptions", "Enable exceptions", False), ("ios_triple", "Triple for ios toolchain", ""), ] @@ -53,7 +54,7 @@ def configure(env): if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["-O2", "-ftree-vectorize", "-fomit-frame-pointer"]) env.Append(LINKFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["-Os", "-ftree-vectorize"]) env.Append(LINKFLAGS=["-Os"]) @@ -107,8 +108,17 @@ def configure(env): ## Compile flags - if env["arch"] == "x86" or env["arch"] == "x86_64": + if env["ios_simulator"]: detect_darwin_sdk_path("iphonesimulator", env) + env.Append(CCFLAGS=["-mios-simulator-version-min=13.0"]) + env.Append(LINKFLAGS=["-mios-simulator-version-min=13.0"]) + env.extra_suffix = ".simulator" + env.extra_suffix + else: + detect_darwin_sdk_path("iphone", env) + env.Append(CCFLAGS=["-miphoneos-version-min=11.0"]) + env.Append(LINKFLAGS=["-miphoneos-version-min=11.0"]) + + if env["arch"] == "x86" or env["arch"] == "x86_64": env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9" arch_flag = "i386" if env["arch"] == "x86" else env["arch"] env.Append( @@ -116,11 +126,10 @@ def configure(env): "-fobjc-arc -arch " + arch_flag + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks" - " -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=13.0" + " -fasm-blocks -isysroot $IPHONESDK" ).split() ) elif env["arch"] == "arm": - detect_darwin_sdk_path("iphone", env) env.Append( CCFLAGS=( "-fobjc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing" @@ -128,16 +137,15 @@ def configure(env): " -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb" ' "-DIBOutlet=__attribute__((iboutlet))"' ' "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))"' - ' "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=11.0 -MMD -MT dependencies'.split() + ' "-DIBAction=void)__attribute__((ibaction)" -MMD -MT dependencies'.split() ) ) elif env["arch"] == "arm64": - detect_darwin_sdk_path("iphone", env) env.Append( CCFLAGS=( "-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing" " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" - " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0" + " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies" " -isysroot $IPHONESDK".split() ) ) @@ -162,7 +170,6 @@ def configure(env): LINKFLAGS=[ "-arch", arch_flag, - "-mios-simulator-version-min=13.0", "-isysroot", "$IPHONESDK", "-Xlinker", @@ -173,46 +180,14 @@ def configure(env): ] ) elif env["arch"] == "arm": - env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"]) + env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip"]) if env["arch"] == "arm64": - env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"]) + env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip"]) env.Append( LINKFLAGS=[ "-isysroot", "$IPHONESDK", - "-framework", - "AudioToolbox", - "-framework", - "AVFoundation", - "-framework", - "CoreAudio", - "-framework", - "CoreGraphics", - "-framework", - "CoreMedia", - "-framework", - "CoreVideo", - "-framework", - "CoreMotion", - "-framework", - "Foundation", - "-framework", - "GameController", - "-framework", - "MediaPlayer", - "-framework", - "Metal", - "-framework", - "QuartzCore", - "-framework", - "Security", - "-framework", - "SystemConfiguration", - "-framework", - "UIKit", - "-framework", - "ARKit", ] ) diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm index fb57db4518..b8df81b89a 100644 --- a/platform/iphone/display_layer.mm +++ b/platform/iphone/display_layer.mm @@ -89,7 +89,7 @@ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back? // Create GL ES 2 context - if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") { + if (GLOBAL_GET("rendering/driver/driver_name") == "GLES2") { context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; NSLog(@"Setting up an OpenGL ES 2.0 context."); if (!context) { diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h index 31a44723a5..7bf1557873 100644 --- a/platform/iphone/display_server_iphone.h +++ b/platform/iphone/display_server_iphone.h @@ -99,7 +99,7 @@ public: // MARK: Touches - void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); + void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click); void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y); void touches_cancelled(int p_idx); diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index 05dc78bb4d..a852bea207 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -136,13 +136,13 @@ DisplayServerIPhone::~DisplayServerIPhone() { if (rendering_device_vulkan) { rendering_device_vulkan->finalize(); memdelete(rendering_device_vulkan); - rendering_device_vulkan = NULL; + rendering_device_vulkan = nullptr; } if (context_vulkan) { context_vulkan->window_destroy(MAIN_WINDOW_ID); memdelete(context_vulkan); - context_vulkan = NULL; + context_vulkan = nullptr; } } #endif @@ -222,7 +222,7 @@ void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Var // MARK: Touches -void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) { +void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) { if (!GLOBAL_DEF("debug/disable_touch", false)) { Ref<InputEventScreenTouch> ev; ev.instance(); diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 3253112bf3..c585c2afbe 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -37,6 +37,7 @@ #include "core/io/zip_io.h" #include "core/os/file_access.h" #include "core/os/os.h" +#include "core/templates/safe_refcount.h" #include "core/version.h" #include "editor/editor_export.h" #include "editor/editor_node.h" @@ -56,11 +57,11 @@ class EditorExportPlatformIOS : public EditorExportPlatform { Ref<ImageTexture> logo; // Plugins - volatile bool plugins_changed; - Thread *check_for_changes_thread; - volatile bool quit_request; + SafeFlag plugins_changed; + Thread check_for_changes_thread; + SafeFlag quit_request; Mutex plugins_lock; - Vector<PluginConfig> plugins; + Vector<PluginConfigIOS> plugins; typedef Error (*FileHandler)(String p_file, void *p_userdata); static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata); @@ -141,19 +142,19 @@ class EditorExportPlatformIOS : public EditorExportPlatform { static void _check_for_changes_poll_thread(void *ud) { EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud; - while (!ea->quit_request) { + while (!ea->quit_request.is_set()) { // Nothing to do if we already know the plugins have changed. - if (!ea->plugins_changed) { + if (!ea->plugins_changed.is_set()) { MutexLock lock(ea->plugins_lock); - Vector<PluginConfig> loaded_plugins = get_plugins(); + Vector<PluginConfigIOS> loaded_plugins = get_plugins(); if (ea->plugins.size() != loaded_plugins.size()) { - ea->plugins_changed = true; + ea->plugins_changed.set(); } else { for (int i = 0; i < ea->plugins.size(); i++) { if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) { - ea->plugins_changed = true; + ea->plugins_changed.set(); break; } } @@ -165,7 +166,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { while (OS::get_singleton()->get_ticks_usec() - time < wait) { OS::get_singleton()->delay_usec(300000); - if (ea->quit_request) { + if (ea->quit_request.is_set()) { break; } } @@ -182,10 +183,10 @@ public: virtual Ref<Texture2D> get_logo() const override { return logo; } virtual bool should_update_export_options() override { - bool export_options_changed = plugins_changed; + bool export_options_changed = plugins_changed.is_set(); if (export_options_changed) { // don't clear unless we're reporting true, to avoid race - plugins_changed = false; + plugins_changed.clear(); } return export_options_changed; } @@ -241,7 +242,7 @@ public: continue; } - if (file.ends_with(PLUGIN_CONFIG_EXT)) { + if (file.ends_with(PluginConfigIOS::PLUGIN_CONFIG_EXT)) { dir_files.push_back(file); } } @@ -251,8 +252,8 @@ public: return dir_files; } - static Vector<PluginConfig> get_plugins() { - Vector<PluginConfig> loaded_plugins; + static Vector<PluginConfigIOS> get_plugins() { + Vector<PluginConfigIOS> loaded_plugins; String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins"); @@ -262,7 +263,7 @@ public: if (!plugins_filenames.is_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])); + PluginConfigIOS config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i])); if (config.valid_config) { loaded_plugins.push_back(config); } else { @@ -275,11 +276,11 @@ public: return loaded_plugins; } - static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) { - Vector<PluginConfig> enabled_plugins; - Vector<PluginConfig> all_plugins = get_plugins(); + static Vector<PluginConfigIOS> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) { + Vector<PluginConfigIOS> enabled_plugins; + Vector<PluginConfigIOS> all_plugins = get_plugins(); for (int i = 0; i < all_plugins.size(); i++) { - PluginConfig plugin = all_plugins[i]; + PluginConfigIOS plugin = all_plugins[i]; bool enabled = p_presets->get("plugins/" + plugin.name); if (enabled) { enabled_plugins.push_back(plugin); @@ -291,7 +292,7 @@ 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"); + String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name"); r_features->push_back("pvrtc"); if (driver == "Vulkan") { // FIXME: Review if this is correct. @@ -360,11 +361,11 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); - Vector<PluginConfig> found_plugins = get_plugins(); + Vector<PluginConfigIOS> found_plugins = get_plugins(); for (int i = 0; i < found_plugins.size(); i++) { r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false)); } - plugins_changed = false; + plugins_changed.clear(); plugins = found_plugins; r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); @@ -979,7 +980,7 @@ Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) { codesign_args.push_back("-s"); codesign_args.push_back(data->preset->get(data->debug ? "application/code_sign_identity_debug" : "application/code_sign_identity_release")); codesign_args.push_back(p_file); - return OS::get_singleton()->execute("codesign", codesign_args, true); + return OS::get_singleton()->execute("codesign", codesign_args); } return OK; } @@ -1229,7 +1230,7 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name)); install_name_args.push_back(destination); - OS::get_singleton()->execute("install_name_tool", install_name_args, true); + OS::get_singleton()->execute("install_name_tool", install_name_args); } // Creating Info.plist @@ -1345,28 +1346,25 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> Vector<String> plugin_embedded_dependencies; Vector<String> plugin_files; - Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset); + Vector<PluginConfigIOS> enabled_plugins = get_enabled_plugins(p_preset); Vector<String> added_linked_dependenciy_names; Vector<String> added_embedded_dependenciy_names; HashMap<String, String> plist_values; + Set<String> plugin_linker_flags; + Error err; for (int i = 0; i < enabled_plugins.size(); i++) { - PluginConfig plugin = enabled_plugins[i]; + PluginConfigIOS plugin = enabled_plugins[i]; // Export plugin binary. - if (!plugin.supports_targets) { - err = _copy_asset(dest_dir, plugin.binary, nullptr, true, true, r_exported_assets); - } else { - String plugin_binary_dir = plugin.binary.get_base_dir(); - String plugin_name_prefix = plugin.binary.get_basename().get_file(); - String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + ".a"; - String result_file_name = plugin.binary.get_file(); - - err = _copy_asset(dest_dir, plugin_binary_dir.plus_file(plugin_file), &result_file_name, true, true, r_exported_assets); - } + String plugin_main_binary = get_plugin_main_binary(plugin, p_debug); + String plugin_binary_result_file = plugin.binary.get_file(); + // We shouldn't embed .xcframework that contains static libraries. + // Static libraries are not embedded anyway. + err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets); ERR_FAIL_COND_V(err, err); @@ -1422,6 +1420,13 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> p_config_data.capabilities.push_back(capability); } + // Linker flags + // Checking duplicates + for (int j = 0; j < plugin.linker_flags.size(); j++) { + String linker_flag = plugin.linker_flags[j]; + plugin_linker_flags.insert(linker_flag); + } + // Plist // Using hash map container to remove duplicates const String *K = nullptr; @@ -1502,6 +1507,27 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_"); } + + // Update Linker Flag Values + { + String result_linker_flags = " "; + for (Set<String>::Element *E = plugin_linker_flags.front(); E; E = E->next()) { + const String &flag = E->get(); + + if (flag.length() == 0) { + continue; + } + + if (result_linker_flags.length() > 0) { + result_linker_flags += ' '; + } + + result_linker_flags += flag; + } + result_linker_flags = result_linker_flags.replace("\"", "\\\""); + p_config_data.linker_flags += result_linker_flags; + } + return OK; } @@ -1575,9 +1601,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p return ERR_SKIP; } - String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".fat.a"; + String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".xcframework"; - print_line("Static library: " + library_to_use); + print_line("Static framework: " + library_to_use); String pkg_name; if (p_preset->get("application/name") != "") { pkg_name = p_preset->get("application/name"); // app_name @@ -1663,7 +1689,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p if (files_to_parse.has(file)) { _fix_config_file(p_preset, data, config_data, p_debug); } else if (file.begins_with("libgodot.iphone")) { - if (file != library_to_use) { + if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) { ret = unzGoToNextFile(src_pkg_zip); continue; //ignore! } @@ -1671,7 +1697,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p #if defined(OSX_ENABLED) || defined(X11_ENABLED) is_execute = true; #endif - file = "godot_ios.a"; + file = file.replace(library_to_use, binary_name + ".xcframework"); } if (file == project_file) { @@ -1848,7 +1874,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p archive_args.push_back("archive"); archive_args.push_back("-archivePath"); archive_args.push_back(archive_path); - err = OS::get_singleton()->execute("xcodebuild", archive_args, true); + err = OS::get_singleton()->execute("xcodebuild", archive_args); ERR_FAIL_COND_V(err, err); if (ep.step("Making .ipa", 4)) { @@ -1863,7 +1889,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p export_args.push_back("-allowProvisioningUpdates"); export_args.push_back("-exportPath"); export_args.push_back(dest_dir); - err = OS::get_singleton()->execute("xcodebuild", export_args, true); + err = OS::get_singleton()->execute("xcodebuild", export_args); ERR_FAIL_COND_V(err, err); #else print_line(".ipa can only be built on macOS. Leaving Xcode project without building the package."); @@ -1942,16 +1968,14 @@ EditorExportPlatformIOS::EditorExportPlatformIOS() { logo.instance(); logo->create_from_image(img); - plugins_changed = true; - quit_request = false; + plugins_changed.set(); - check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this); + check_for_changes_thread.start(_check_for_changes_poll_thread, this); } EditorExportPlatformIOS::~EditorExportPlatformIOS() { - quit_request = true; - Thread::wait_to_finish(check_for_changes_thread); - memdelete(check_for_changes_thread); + quit_request.set(); + check_for_changes_thread.wait_to_finish(); } void register_iphone_exporter() { diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m index 7b9cf7893c..3ce9bffc79 100644 --- a/platform/iphone/godot_app_delegate.m +++ b/platform/iphone/godot_app_delegate.m @@ -302,37 +302,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil; // MARK: Remote Notification -- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; - } -} - -- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application didFailToRegisterForRemoteNotificationsWithError:error]; - } -} - -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { - for (ApplicationDelegateService *service in services) { - if (![service respondsToSelector:_cmd]) { - continue; - } - - [service application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; - } - - completionHandler(UIBackgroundFetchResultNoData); -} +// Moved to the iOS Plugin // MARK: User Activity and Handling Quick Actions diff --git a/platform/iphone/godot_iphone.mm b/platform/iphone/godot_iphone.mm index 62bc2e6e52..6c3e1eabde 100644 --- a/platform/iphone/godot_iphone.mm +++ b/platform/iphone/godot_iphone.mm @@ -50,7 +50,7 @@ int add_path(int p_argc, char **p_args) { p_args[p_argc++] = (char *)"--path"; p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; - p_args[p_argc] = NULL; + p_args[p_argc] = nullptr; return p_argc; }; @@ -69,7 +69,7 @@ int add_cmdline(int p_argc, char **p_args) { p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; }; - p_args[p_argc] = NULL; + p_args[p_argc] = nullptr; return p_argc; }; diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h index 29960c47a8..265f826173 100644 --- a/platform/iphone/godot_view.h +++ b/platform/iphone/godot_view.h @@ -32,12 +32,20 @@ class String; +@class GodotView; @protocol DisplayLayer; @protocol GodotViewRendererProtocol; +@protocol GodotViewDelegate + +- (BOOL)godotViewFinishedSetup:(GodotView *)view; + +@end + @interface GodotView : UIView @property(assign, nonatomic) id<GodotViewRendererProtocol> renderer; +@property(assign, nonatomic) id<GodotViewDelegate> delegate; @property(assign, readonly, nonatomic) BOOL isActive; diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index bf073ae295..00a88d79c5 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -39,6 +39,7 @@ #import <CoreMotion/CoreMotion.h> static const int max_touches = 8; +static const float earth_gravity = 9.80665; @interface GodotView () { UITouch *godot_touches[max_touches]; @@ -120,6 +121,7 @@ static const int max_touches = 8; [self stopRendering]; self.renderer = nil; + self.delegate = nil; if (self.renderingLayer) { [self.renderingLayer removeFromSuperlayer]; @@ -241,6 +243,14 @@ static const int max_touches = 8; return; } + if (self.delegate) { + BOOL delegateFinishedSetup = [self.delegate godotViewFinishedSetup:self]; + + if (!delegateFinishedSetup) { + return; + } + } + [self handleMotion]; [self.renderer renderOnView:self]; } @@ -281,14 +291,14 @@ static const int max_touches = 8; - (void)initTouches { for (int i = 0; i < max_touches; i++) { - godot_touches[i] = NULL; + godot_touches[i] = nullptr; } } - (int)getTouchIDForTouch:(UITouch *)p_touch { int first = -1; for (int i = 0; i < max_touches; i++) { - if (first == -1 && godot_touches[i] == NULL) { + if (first == -1 && godot_touches[i] == nullptr) { first = i; continue; } @@ -308,11 +318,11 @@ static const int max_touches = 8; - (int)removeTouch:(UITouch *)p_touch { int remaining = 0; for (int i = 0; i < max_touches; i++) { - if (godot_touches[i] == NULL) { + if (godot_touches[i] == nullptr) { continue; } if (godot_touches[i] == p_touch) { - godot_touches[i] = NULL; + godot_touches[i] = nullptr; } else { ++remaining; } @@ -322,7 +332,7 @@ static const int max_touches = 8; - (void)clearTouches { for (int i = 0; i < max_touches; i++) { - godot_touches[i] = NULL; + godot_touches[i] = nullptr; } } @@ -393,10 +403,19 @@ static const int max_touches = 8; // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc // Apple splits our accelerometer date into a gravity and user movement - // component. We add them back together + // component. We add them back together. CMAcceleration gravity = self.motionManager.deviceMotion.gravity; CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration; + // To be consistent with Android we convert the unit of measurement from g (Earth's gravity) + // to m/s^2. + gravity.x *= earth_gravity; + gravity.y *= earth_gravity; + gravity.z *= earth_gravity; + acceleration.x *= earth_gravity; + acceleration.y *= earth_gravity; + acceleration.z *= earth_gravity; + ///@TODO We don't seem to be getting data here, is my device broken or /// is this code incorrect? CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field; diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index cef03534c4..3430a9cba7 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -56,16 +56,16 @@ void iOS::alert(const char *p_alert, const char *p_title) { String iOS::get_model() const { // [[UIDevice currentDevice] model] only returns "iPad" or "iPhone". size_t size; - sysctlbyname("hw.machine", NULL, &size, NULL, 0); + sysctlbyname("hw.machine", nullptr, &size, nullptr, 0); char *model = (char *)malloc(size); - if (model == NULL) { + if (model == nullptr) { return ""; } - sysctlbyname("hw.machine", model, &size, NULL, 0); + sysctlbyname("hw.machine", model, &size, nullptr, 0); NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding]; free(model); const char *str = [platform UTF8String]; - return String(str != NULL ? str : ""); + return String(str != nullptr ? str : ""); } String iOS::get_rate_url(int p_app_id) const { diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm index a0f0eee5d3..45842b38aa 100644 --- a/platform/iphone/joypad_iphone.mm +++ b/platform/iphone/joypad_iphone.mm @@ -287,7 +287,7 @@ void JoypadIPhone::start_processing() { gamepad.dpad.right.isPressed); }; - Input::JoyAxis jx; + Input::JoyAxisValue jx; jx.min = -1; if (element == gamepad.leftThumbstick) { jx.value = gamepad.leftThumbstick.xAxis.value; diff --git a/platform/iphone/keyboard_input_view.mm b/platform/iphone/keyboard_input_view.mm index 1408f78e90..0e5a98a3e6 100644 --- a/platform/iphone/keyboard_input_view.mm +++ b/platform/iphone/keyboard_input_view.mm @@ -88,13 +88,15 @@ self.text = existingString; self.previousText = existingString; + NSInteger safeStartIndex = MAX(start, 0); + NSRange textRange; // Either a simple cursor or a selection. if (end > 0) { - textRange = NSMakeRange(start, end - start); + textRange = NSMakeRange(safeStartIndex, end - start); } else { - textRange = NSMakeRange(start, 0); + textRange = NSMakeRange(safeStartIndex, 0); } self.selectedRange = textRange; diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 625cbf178e..51c4da2960 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -144,8 +144,6 @@ void OSIPhone::deinitialize_modules() { } void OSIPhone::set_main_loop(MainLoop *p_main_loop) { - godot_ios_plugins_initialize(); - main_loop = p_main_loop; if (main_loop) { @@ -179,6 +177,8 @@ bool OSIPhone::iterate() { } void OSIPhone::start() { + godot_ios_plugins_initialize(); + Main::start(); if (joypad_iphone) { diff --git a/platform/iphone/plugin/godot_plugin_config.h b/platform/iphone/plugin/godot_plugin_config.h index 89f657821e..e2546e733c 100644 --- a/platform/iphone/plugin/godot_plugin_config.h +++ b/platform/iphone/plugin/godot_plugin_config.h @@ -35,23 +35,6 @@ #include "core/io/config_file.h" #include "core/string/ustring.h" -static const char *PLUGIN_CONFIG_EXT = ".gdip"; - -static const char *CONFIG_SECTION = "config"; -static const char *CONFIG_NAME_KEY = "name"; -static const char *CONFIG_BINARY_KEY = "binary"; -static const char *CONFIG_INITIALIZE_KEY = "initialization"; -static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization"; - -static const char *DEPENDENCIES_SECTION = "dependencies"; -static const char *DEPENDENCIES_LINKED_KEY = "linked"; -static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded"; -static const char *DEPENDENCIES_SYSTEM_KEY = "system"; -static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities"; -static const char *DEPENDENCIES_FILES_KEY = "files"; - -static const char *PLIST_SECTION = "plist"; - /* The `config` section and fields are required and defined as follow: - **name**: name of the plugin @@ -68,7 +51,25 @@ The `plist` section are optional. - **key**: key and value that would be added in Info.plist file. */ -struct PluginConfig { +struct PluginConfigIOS { + inline static const char *PLUGIN_CONFIG_EXT = ".gdip"; + + inline static const char *CONFIG_SECTION = "config"; + inline static const char *CONFIG_NAME_KEY = "name"; + inline static const char *CONFIG_BINARY_KEY = "binary"; + inline static const char *CONFIG_INITIALIZE_KEY = "initialization"; + inline static const char *CONFIG_DEINITIALIZE_KEY = "deinitialization"; + + inline static const char *DEPENDENCIES_SECTION = "dependencies"; + inline static const char *DEPENDENCIES_LINKED_KEY = "linked"; + inline static const char *DEPENDENCIES_EMBEDDED_KEY = "embedded"; + inline static const char *DEPENDENCIES_SYSTEM_KEY = "system"; + inline static const char *DEPENDENCIES_CAPABILITIES_KEY = "capabilities"; + inline static const char *DEPENDENCIES_FILES_KEY = "files"; + inline static const char *DEPENDENCIES_LINKER_FLAGS = "linker_flags"; + + inline static const char *PLIST_SECTION = "plist"; + // Set to true when the config file is properly loaded. bool valid_config = false; bool supports_targets = false; @@ -89,6 +90,8 @@ struct PluginConfig { Vector<String> files_to_copy; Vector<String> capabilities; + Vector<String> linker_flags; + // Optional plist section // Supports only string types for now HashMap<String, String> plist; @@ -159,7 +162,7 @@ static inline Vector<String> resolve_system_dependencies(Vector<String> p_paths) return paths; } -static inline bool validate_plugin(PluginConfig &plugin_config) { +static inline bool validate_plugin(PluginConfigIOS &plugin_config) { bool valid_name = !plugin_config.name.is_empty(); bool valid_binary_name = !plugin_config.binary.is_empty(); bool valid_initialize = !plugin_config.initialization_method.is_empty(); @@ -167,16 +170,25 @@ static inline bool validate_plugin(PluginConfig &plugin_config) { bool fields_value = valid_name && valid_binary_name && valid_initialize && valid_deinitialize; - if (fields_value && FileAccess::exists(plugin_config.binary)) { + if (!fields_value) { + return false; + } + + String plugin_extension = plugin_config.binary.get_extension().to_lower(); + + if ((plugin_extension == "a" && FileAccess::exists(plugin_config.binary)) || + (plugin_extension == "xcframework" && DirAccess::exists(plugin_config.binary))) { plugin_config.valid_config = true; plugin_config.supports_targets = false; - } else if (fields_value) { + } else { String file_path = plugin_config.binary.get_base_dir(); String file_name = plugin_config.binary.get_basename().get_file(); - String release_file_name = file_path.plus_file(file_name + ".release.a"); - String debug_file_name = file_path.plus_file(file_name + ".debug.a"); + String file_extension = plugin_config.binary.get_extension(); + String release_file_name = file_path.plus_file(file_name + ".release." + file_extension); + String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension); - if (FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) { + if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) || + (plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) { plugin_config.valid_config = true; plugin_config.supports_targets = true; } @@ -185,7 +197,20 @@ static inline bool validate_plugin(PluginConfig &plugin_config) { return plugin_config.valid_config; } -static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_config, const String &config_path) { +static inline String get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug) { + if (!plugin_config.supports_targets) { + return plugin_config.binary; + } + + String plugin_binary_dir = plugin_config.binary.get_base_dir(); + String plugin_name_prefix = plugin_config.binary.get_basename().get_file(); + String plugin_extension = plugin_config.binary.get_extension(); + String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension; + + return plugin_binary_dir.plus_file(plugin_file); +} + +static inline uint64_t get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) { uint64_t last_updated = FileAccess::get_modified_time(config_path); if (!plugin_config.supports_targets) { @@ -193,8 +218,9 @@ static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_c } else { String file_path = plugin_config.binary.get_base_dir(); String file_name = plugin_config.binary.get_basename().get_file(); - String release_file_name = file_path.plus_file(file_name + ".release.a"); - String debug_file_name = file_path.plus_file(file_name + ".debug.a"); + String plugin_extension = plugin_config.binary.get_extension(); + String release_file_name = file_path.plus_file(file_name + ".release." + plugin_extension); + String debug_file_name = file_path.plus_file(file_name + ".debug." + plugin_extension); last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name)); last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name)); @@ -203,8 +229,8 @@ static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_c return last_updated; } -static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) { - PluginConfig plugin_config = {}; +static inline PluginConfigIOS load_plugin_config(Ref<ConfigFile> config_file, const String &path) { + PluginConfigIOS plugin_config = {}; if (!config_file.is_valid()) { return plugin_config; @@ -218,18 +244,18 @@ static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String config_base_dir = path.get_base_dir(); - plugin_config.name = config_file->get_value(CONFIG_SECTION, CONFIG_NAME_KEY, String()); - plugin_config.initialization_method = config_file->get_value(CONFIG_SECTION, CONFIG_INITIALIZE_KEY, String()); - plugin_config.deinitialization_method = config_file->get_value(CONFIG_SECTION, CONFIG_DEINITIALIZE_KEY, String()); + plugin_config.name = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_NAME_KEY, String()); + plugin_config.initialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_INITIALIZE_KEY, String()); + plugin_config.deinitialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_DEINITIALIZE_KEY, String()); - String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String()); + String binary_path = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_BINARY_KEY, String()); plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path); - if (config_file->has_section(DEPENDENCIES_SECTION)) { - Vector<String> linked_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LINKED_KEY, Vector<String>()); - Vector<String> embedded_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_EMBEDDED_KEY, Vector<String>()); - Vector<String> system_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_SYSTEM_KEY, Vector<String>()); - Vector<String> files = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_FILES_KEY, Vector<String>()); + if (config_file->has_section(PluginConfigIOS::DEPENDENCIES_SECTION)) { + Vector<String> linked_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKED_KEY, Vector<String>()); + Vector<String> embedded_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_EMBEDDED_KEY, Vector<String>()); + Vector<String> system_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_SYSTEM_KEY, Vector<String>()); + Vector<String> files = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_FILES_KEY, Vector<String>()); plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies); plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies); @@ -237,15 +263,17 @@ static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files); - plugin_config.capabilities = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_CAPABILITIES_KEY, Vector<String>()); + plugin_config.capabilities = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_CAPABILITIES_KEY, Vector<String>()); + + plugin_config.linker_flags = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKER_FLAGS, Vector<String>()); } - if (config_file->has_section(PLIST_SECTION)) { + if (config_file->has_section(PluginConfigIOS::PLIST_SECTION)) { List<String> keys; - config_file->get_section_keys(PLIST_SECTION, &keys); + config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION, &keys); for (int i = 0; i < keys.size(); i++) { - String value = config_file->get_value(PLIST_SECTION, keys[i], String()); + String value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); if (value.is_empty()) { continue; diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index c41aa13bb7..6cef244567 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -40,12 +40,14 @@ #import <AVFoundation/AVFoundation.h> #import <GameController/GameController.h> -@interface ViewController () +@interface ViewController () <GodotViewDelegate> @property(strong, nonatomic) GodotViewRenderer *renderer; @property(strong, nonatomic) GodotNativeVideoView *videoView; @property(strong, nonatomic) GodotKeyboardInputView *keyboardView; +@property(strong, nonatomic) UIView *godotLoadingOverlay; + @end @implementation ViewController @@ -62,6 +64,7 @@ self.view = view; view.renderer = self.renderer; + view.delegate = self; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { @@ -97,6 +100,7 @@ [super viewDidLoad]; [self observeKeyboard]; + [self displayLoadingOverlay]; if (@available(iOS 11.0, *)) { [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; @@ -121,6 +125,31 @@ object:nil]; } +- (void)displayLoadingOverlay { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *storyboardName = @"Launch Screen"; + + if ([bundle pathForResource:storyboardName ofType:@"storyboardc"] == nil) { + return; + } + + UIStoryboard *launchStoryboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle]; + + UIViewController *controller = [launchStoryboard instantiateInitialViewController]; + self.godotLoadingOverlay = controller.view; + self.godotLoadingOverlay.frame = self.view.bounds; + self.godotLoadingOverlay.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + + [self.view addSubview:self.godotLoadingOverlay]; +} + +- (BOOL)godotViewFinishedSetup:(GodotView *)view { + [self.godotLoadingOverlay removeFromSuperview]; + self.godotLoadingOverlay = nil; + + return YES; +} + - (void)dealloc { [self.videoView stopVideo]; @@ -130,6 +159,11 @@ self.renderer = nil; + if (self.godotLoadingOverlay) { + [self.godotLoadingOverlay removeFromSuperview]; + self.godotLoadingOverlay = nil; + } + [[NSNotificationCenter defaultCenter] removeObserver:self]; } diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm index b980ae99f0..08c9007fbb 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/iphone/vulkan_context_iphone.mm @@ -38,13 +38,13 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const { Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height) { VkIOSSurfaceCreateInfoMVK createInfo; createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; - createInfo.pNext = NULL; + createInfo.pNext = nullptr; createInfo.flags = 0; createInfo.pView = (__bridge const void *)p_metal_layer; VkSurfaceKHR surface; VkResult err = - vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); + vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, nullptr, &surface); ERR_FAIL_COND_V(err, ERR_CANT_CREATE); return _window_create(p_window_id, surface, p_width, p_height); diff --git a/platform/javascript/.eslintrc.engine.js b/platform/javascript/.eslintrc.engine.js index 00f0f147a9..78df6d41d9 100644 --- a/platform/javascript/.eslintrc.engine.js +++ b/platform/javascript/.eslintrc.engine.js @@ -3,8 +3,8 @@ module.exports = { "./.eslintrc.js", ], "globals": { + "InternalConfig": true, "Godot": true, "Preloader": true, - "Utils": true, }, }; diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 1d3f96a6b8..a760e36982 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -17,7 +17,7 @@ sys_env.AddJSLibraries( [ "js/libs/library_godot_audio.js", "js/libs/library_godot_display.js", - "js/libs/library_godot_http_request.js", + "js/libs/library_godot_fetch.js", "js/libs/library_godot_os.js", "js/libs/library_godot_runtime.js", ] @@ -27,8 +27,13 @@ if env["tools"]: sys_env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) if env["javascript_eval"]: sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"]) + for lib in sys_env["JS_LIBS"]: sys_env.Append(LINKFLAGS=["--js-library", lib]) +for js in env["JS_PRE"]: + sys_env.Append(LINKFLAGS=["--pre-js", env.File(js).path]) +for ext in env["JS_EXTERNS"]: + sys_env["ENV"]["EMCC_CLOSURE_ARGS"] += " --externs " + ext.path build = [] if env["gdnative_enabled"]: @@ -37,8 +42,6 @@ if env["gdnative_enabled"]: sys_env["LIBS"] = [] # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly. sys_env.Append(LIBS=["idbfs.js"]) - # JS prepended to the module code loading the side library. - sys_env.Append(LINKFLAGS=["--pre-js", sys_env.File("js/dynlink.pre.js")]) # Configure it as a main module (dynamic linking support). sys_env.Append(CCFLAGS=["-s", "MAIN_MODULE=1"]) sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"]) @@ -48,7 +51,6 @@ if env["gdnative_enabled"]: sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi" # The main emscripten runtime, with exported standard libraries. sys = sys_env.Program(build_targets, ["javascript_runtime.cpp"]) - sys_env.Depends(sys, "js/dynlink.pre.js") # The side library, containing all Godot code. wasm_env = env.Clone() @@ -66,20 +68,12 @@ else: build = sys_env.Program(build_targets, javascript_files + ["javascript_runtime.cpp"]) sys_env.Depends(build[0], sys_env["JS_LIBS"]) - -if "JS_PRE" in env: - for js in env["JS_PRE"]: - env.Append(LINKFLAGS=["--pre-js", env.File(js).path]) - env.Depends(build, env["JS_PRE"]) - -if "JS_EXTERNS" in env: - for ext in env["JS_EXTERNS"]: - env["ENV"]["EMCC_CLOSURE_ARGS"] += " --externs " + ext.path - env.Depends(build, env["JS_EXTERNS"]) +sys_env.Depends(build[0], sys_env["JS_PRE"]) +sys_env.Depends(build[0], sys_env["JS_EXTERNS"]) engine = [ "js/engine/preloader.js", - "js/engine/utils.js", + "js/engine/config.js", "js/engine/engine.js", ] externs = [env.File("#platform/javascript/js/engine/engine.externs.js")] @@ -92,34 +86,6 @@ 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") -binary_name = "godot.tools" if env["tools"] else "godot" -out_files = [ - zip_dir.File(binary_name + ".js"), - zip_dir.File(binary_name + ".wasm"), - zip_dir.File(binary_name + ".html"), - zip_dir.File(binary_name + ".audio.worklet.js"), -] -html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html" -in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"] -if env["gdnative_enabled"]: - in_files.append(build[2]) # Runtime - out_files.append(zip_dir.File(binary_name + ".side.wasm")) -elif env["threads_enabled"]: - in_files.append(build[2]) # Worker - out_files.append(zip_dir.File(binary_name + ".worker.js")) - -if env["tools"]: - in_files.append("#misc/dist/html/logo.svg") - out_files.append(zip_dir.File("logo.svg")) - in_files.append("#icon.png") - out_files.append(zip_dir.File("favicon.png")) - -zip_files = env.InstallAs(out_files, in_files) -env.Zip( - "#bin/godot", - zip_files, - ZIPROOT=zip_dir, - ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}", - ZIPCOMSTR="Archiving $SOURCES as $TARGET", -) +# Extra will be the thread worker, or the GDNative side, or None +extra = build[2] if len(build) > 2 else None +env.CreateTemplateZip(js_wrapped, build[1], extra) diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index 8355faccc2..7a2c2b2335 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -65,10 +65,10 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { FileAccess *src_f; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, NULL, &io); + zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, nullptr, &io); String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; _zip_recursive(resource_path, base_path, zip); - zipClose(zip, NULL); + zipClose(zip, nullptr); godot_js_editor_download_file("/tmp/project.zip", "project.zip", "application/zip"); } @@ -88,12 +88,12 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z String path = p_path.replace_first(p_base_path, ""); zipOpenNewFileInZip(p_zip, path.utf8().get_data(), - NULL, - NULL, + nullptr, + nullptr, 0, - NULL, + nullptr, 0, - NULL, + nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION); zipWriteInFileInZip(p_zip, data.ptr(), data.size()); @@ -116,12 +116,12 @@ void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_pa String path = cs.replace_first(p_base_path, "") + "/"; zipOpenNewFileInZip(p_zip, path.utf8().get_data(), - NULL, - NULL, + nullptr, + nullptr, 0, - NULL, + nullptr, 0, - NULL, + nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION); zipCloseFileInZip(p_zip); diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index 6395fdf721..478e848675 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -105,8 +105,8 @@ void AudioDriverJavaScript::_audio_driver_capture(int p_from, int p_samples) { } Error AudioDriverJavaScript::init() { - mix_rate = GLOBAL_GET("audio/mix_rate"); - int latency = GLOBAL_GET("audio/output_latency"); + mix_rate = GLOBAL_GET("audio/driver/mix_rate"); + int latency = GLOBAL_GET("audio/driver/output_latency"); channel_count = godot_audio_init(mix_rate, latency, &_state_change_callback, &_latency_update_callback); buffer_length = closest_power_of_2((latency * mix_rate / 1000)); @@ -267,7 +267,7 @@ int AudioDriverJavaScript::WorkletNode::create(int p_buffer_size, int p_channels void AudioDriverJavaScript::WorkletNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { godot_audio_worklet_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, state); - thread = Thread::create(_audio_thread_func, this); + thread.start(_audio_thread_func, this); } void AudioDriverJavaScript::WorkletNode::lock() { @@ -280,8 +280,6 @@ void AudioDriverJavaScript::WorkletNode::unlock() { void AudioDriverJavaScript::WorkletNode::finish() { quit = true; // Ask thread to quit. - Thread::wait_to_finish(thread); - memdelete(thread); - thread = nullptr; + thread.wait_to_finish(); } #endif diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index d55ec261a4..393693640f 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -59,7 +59,7 @@ public: STATE_MAX, }; Mutex mutex; - Thread *thread = nullptr; + Thread thread; bool quit = false; int32_t state[STATE_MAX] = { 0 }; diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 7d501e94b2..d01e8a8bd4 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -1,7 +1,14 @@ import os import sys -from emscripten_helpers import run_closure_compiler, create_engine_file, add_js_libraries, add_js_pre, add_js_externs +from emscripten_helpers import ( + run_closure_compiler, + create_engine_file, + add_js_libraries, + add_js_pre, + add_js_externs, + create_template_zip, +) from methods import get_compiler_version from SCons.Util import WhereIs @@ -50,27 +57,28 @@ def get_flags(): def configure(env): - if not isinstance(env["initial_memory"], int): + try: + env["initial_memory"] = int(env["initial_memory"]) + except Exception: print("Initial memory must be a valid integer") sys.exit(255) ## Build type - - if env["target"] == "release": + if env["target"].startswith("release"): # Use -Os to prioritize optimizing for reduced file size. This is # particularly valuable for the web platform because it directly # decreases download time. # -Os reduces file size by around 5 MiB over -O3. -Oz only saves about # 100 KiB over -Os, which does not justify the negative impact on # run-time performance. - env.Append(CCFLAGS=["-Os"]) - env.Append(LINKFLAGS=["-Os"]) - elif env["target"] == "release_debug": - env.Append(CCFLAGS=["-Os"]) - env.Append(LINKFLAGS=["-Os"]) - env.Append(CPPDEFINES=["DEBUG_ENABLED"]) - # Retain function names for backtraces at the cost of file size. - env.Append(LINKFLAGS=["--profiling-funcs"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["-Os"]) + env.Append(LINKFLAGS=["-Os"]) + + if env["target"] == "release_debug": + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + # Retain function names for backtraces at the cost of file size. + env.Append(LINKFLAGS=["--profiling-funcs"]) else: # "debug" env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(CCFLAGS=["-O1", "-g"]) @@ -84,11 +92,12 @@ def configure(env): if not env["threads_enabled"]: print("Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option") sys.exit(255) - if env["initial_memory"] < 32: - print("Editor build requires at least 32MiB of initial memory. Forcing it.") - env["initial_memory"] = 32 - elif env["builtin_icu"]: + if env["initial_memory"] < 64: + print("Editor build requires at least 64MiB of initial memory. Forcing it.") + env["initial_memory"] = 64 env.Append(CCFLAGS=["-frtti"]) + elif env["builtin_icu"]: + env.Append(CCFLAGS=["-fno-exceptions", "-frtti"]) else: # Disable exceptions and rtti on non-tools (template) builds # These flags help keep the file size down. @@ -131,7 +140,10 @@ def configure(env): jscc = env.Builder(generator=run_closure_compiler, suffix=".cc.js", src_suffix=".js") env.Append(BUILDERS={"BuildJS": jscc}) - # Add helper method for adding libraries. + # Add helper method for adding libraries, externs, pre-js. + env["JS_LIBS"] = [] + env["JS_PRE"] = [] + env["JS_EXTERNS"] = [] env.AddMethod(add_js_libraries, "AddJSLibraries") env.AddMethod(add_js_pre, "AddJSPre") env.AddMethod(add_js_externs, "AddJSExterns") @@ -139,6 +151,9 @@ def configure(env): # Add method that joins/compiles our Engine files. env.AddMethod(create_engine_file, "CreateEngineFile") + # Add method for creating the final zip file + env.AddMethod(create_template_zip, "CreateTemplateZip") + # Closure compiler extern and support for ecmascript specs (const, let, etc). env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT6" @@ -160,7 +175,7 @@ def configure(env): # Program() output consists of multiple files, so specify suffixes manually at builder. env["PROGSUFFIX"] = "" env["LIBPREFIX"] = "lib" - env["LIBSUFFIX"] = ".bc" + env["LIBSUFFIX"] = ".a" env["LIBPREFIXES"] = ["$LIBPREFIX"] env["LIBSUFFIXES"] = ["$LIBSUFFIX"] @@ -214,8 +229,16 @@ def configure(env): # Allow use to take control of swapping WebGL buffers. env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"]) - # callMain for manual start. - env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"]) + # callMain for manual start, cwrap for the mono version. + env.Append(LINKFLAGS=["-s", "EXPORTED_RUNTIME_METHODS=['callMain','cwrap']"]) # Add code that allow exiting runtime. env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"]) + + # TODO remove once we have GLES support back (temporary fix undefined symbols due to dead code elimination). + env.Append( + LINKFLAGS=[ + "-s", + "EXPORTED_FUNCTIONS=['_main', '_emscripten_webgl_get_current_context']", + ] + ) diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index f10627b0b6..234e42376d 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -59,41 +59,20 @@ bool DisplayServerJavaScript::is_canvas_focused() { } bool DisplayServerJavaScript::check_size_force_redraw() { - int canvas_width; - int canvas_height; - emscripten_get_canvas_element_size(DisplayServerJavaScript::canvas_id, &canvas_width, &canvas_height); - if (last_width != canvas_width || last_height != canvas_height) { - last_width = canvas_width; - last_height = canvas_height; - // Update the framebuffer size for redraw. - emscripten_set_canvas_element_size(DisplayServerJavaScript::canvas_id, canvas_width, canvas_height); - return true; - } - return false; + return godot_js_display_size_update() != 0; } Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) { - DisplayServerJavaScript *display = get_singleton(); - int canvas_x; - int canvas_y; - godot_js_display_canvas_bounding_rect_position_get(&canvas_x, &canvas_y); - int canvas_width; - int canvas_height; - emscripten_get_canvas_element_size(display->canvas_id, &canvas_width, &canvas_height); - - double element_width; - double element_height; - emscripten_get_element_css_size(display->canvas_id, &element_width, &element_height); - - return Point2((int)(canvas_width / element_width * (p_x - canvas_x)), - (int)(canvas_height / element_height * (p_y - canvas_y))); + int point[2]; + godot_js_display_compute_position(p_x, p_y, point, point + 1); + return Point2(point[0], point[1]); } 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.is_empty() || target_id == String::utf8(display->canvas_id)) { + if (target_id.is_empty() || target_id == String::utf8(&(display->canvas_id[1]))) { // This event property is the only reliable data on // browser fullscreen state. if (p_event->isFullscreen) { @@ -210,19 +189,19 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E switch (p_event->button) { case DOM_BUTTON_LEFT: - ev->set_button_index(BUTTON_LEFT); + ev->set_button_index(MOUSE_BUTTON_LEFT); break; case DOM_BUTTON_MIDDLE: - ev->set_button_index(BUTTON_MIDDLE); + ev->set_button_index(MOUSE_BUTTON_MIDDLE); break; case DOM_BUTTON_RIGHT: - ev->set_button_index(BUTTON_RIGHT); + ev->set_button_index(MOUSE_BUTTON_RIGHT); break; case DOM_BUTTON_XBUTTON1: - ev->set_button_index(BUTTON_XBUTTON1); + ev->set_button_index(MOUSE_BUTTON_XBUTTON1); break; case DOM_BUTTON_XBUTTON2: - ev->set_button_index(BUTTON_XBUTTON2); + ev->set_button_index(MOUSE_BUTTON_XBUTTON2); break; default: return false; @@ -236,14 +215,14 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E display->last_click_ms = 0; display->last_click_pos = Point2(-100, -100); display->last_click_button_index = -1; - ev->set_doubleclick(true); + ev->set_double_click(true); } } else { display->last_click_button_index = ev->get_button_index(); } - if (!ev->is_doubleclick()) { + if (!ev->is_double_click()) { display->last_click_ms += diff; display->last_click_pos = ev->get_position(); } @@ -362,7 +341,7 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso Rect2 atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -385,7 +364,7 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso 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(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -420,7 +399,7 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), png.ptr(), len, p_hotspot.x, p_hotspot.y); } else { - godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), NULL, 0, 0, 0); + godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), nullptr, 0, 0, 0); } cursor_set_shape(cursor_shape); @@ -455,7 +434,7 @@ DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_mode() const { EmscriptenPointerlockChangeEvent ev; emscripten_get_pointerlock_status(&ev); - return (ev.isActive && String::utf8(ev.id) == String::utf8(canvas_id)) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE; + return (ev.isActive && String::utf8(ev.id) == String::utf8(&canvas_id[1])) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE; } // Wheel @@ -482,13 +461,13 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript ev->set_metakey(input->is_key_pressed(KEY_META)); if (p_event->deltaY < 0) - ev->set_button_index(BUTTON_WHEEL_UP); + ev->set_button_index(MOUSE_BUTTON_WHEEL_UP); else if (p_event->deltaY > 0) - ev->set_button_index(BUTTON_WHEEL_DOWN); + ev->set_button_index(MOUSE_BUTTON_WHEEL_DOWN); else if (p_event->deltaX > 0) - ev->set_button_index(BUTTON_WHEEL_LEFT); + ev->set_button_index(MOUSE_BUTTON_WHEEL_LEFT); else if (p_event->deltaX < 0) - ev->set_button_index(BUTTON_WHEEL_RIGHT); + ev->set_button_index(MOUSE_BUTTON_WHEEL_RIGHT); else return false; @@ -557,58 +536,89 @@ bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { return godot_js_display_touchscreen_is_available(); } -// Gamepad +// Virtual Keybaord +void DisplayServerJavaScript::vk_input_text_callback(const char *p_text, int p_cursor) { + DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton(); + if (!ds || ds->input_text_callback.is_null()) { + return; + } + // Call input_text + Variant event = String(p_text); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + ds->input_text_callback.call((const Variant **)&eventp, 1, ret, ce); + // Insert key right to reach position. + Input *input = Input::get_singleton(); + Ref<InputEventKey> k; + for (int i = 0; i < p_cursor; i++) { + k.instance(); + k->set_pressed(true); + k->set_echo(false); + k->set_keycode(KEY_RIGHT); + input->parse_input_event(k); + k.instance(); + k->set_pressed(false); + k->set_echo(false); + k->set_keycode(KEY_RIGHT); + input->parse_input_event(k); + } +} + +void DisplayServerJavaScript::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { + godot_js_display_vk_show(p_existing_text.utf8().get_data(), p_multiline, p_cursor_start, p_cursor_end); +} -EM_BOOL DisplayServerJavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) { +void DisplayServerJavaScript::virtual_keyboard_hide() { + godot_js_display_vk_hide(); +} + +// Gamepad +void DisplayServerJavaScript::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) { 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); + if (p_connected) { + input->joy_connection_changed(p_index, true, String::utf8(p_id), String::utf8(p_guid)); } else { - input->joy_connection_changed(p_event->index, false, ""); + input->joy_connection_changed(p_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); + int32_t pads = godot_js_display_gamepad_sample_count(); + int32_t s_btns_num = 0; + int32_t s_axes_num = 0; + int32_t s_standard = 0; + float s_btns[16]; + float s_axes[10]; + for (int idx = 0; idx < pads; idx++) { + int err = godot_js_display_gamepad_sample_get(idx, s_btns, &s_btns_num, s_axes, &s_axes_num, &s_standard); + if (err) { + continue; + } + for (int b = 0; b < s_btns_num; b++) { + float value = s_btns[b]; + // Buttons 6 and 7 in the standard mapping need to be + // axis to be handled as JOY_AXIS_TRIGGER by Godot. + if (s_standard && (b == 6 || b == 7)) { + Input::JoyAxisValue joy_axis; + joy_axis.min = 0; + joy_axis.value = value; + int a = b == 6 ? JOY_AXIS_TRIGGER_LEFT : JOY_AXIS_TRIGGER_RIGHT; + input->joy_axis(idx, a, joy_axis); + } else { + input->joy_button(idx, b, value); } } + for (int a = 0; a < s_axes_num; a++) { + Input::JoyAxisValue joy_axis; + joy_axis.min = -1; + joy_axis.value = s_axes[a]; + input->joy_axis(idx, a, 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"); @@ -709,6 +719,9 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive // Ensure the canvas ID. godot_js_config_canvas_id_get(canvas_id, 256); + // Handle contextmenu, webglcontextlost + godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, p_mode == WINDOW_MODE_FULLSCREEN, OS::get_singleton()->is_hidpi_allowed() ? 1 : 0); + // Check if it's windows. swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1; @@ -751,11 +764,6 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive video_driver_index = p_video_driver; #endif - window_set_mode(p_mode); - if (godot_js_config_is_resize_on_start()) { - window_set_size(p_resolution); - } - EMSCRIPTEN_RESULT result; #define EM_CHECK(ev) \ if (result != EMSCRIPTEN_RESULT_SUCCESS) \ @@ -763,11 +771,8 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive #define SET_EM_CALLBACK(target, ev, cb) \ result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \ EM_CHECK(ev) -#define SET_EM_WINDOW_CALLBACK(ev, cb) \ - result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, &cb); \ - EM_CHECK(ev) -#define SET_EM_CALLBACK_NOTARGET(ev, cb) \ - result = emscripten_set_##ev##_callback(nullptr, true, &cb); \ +#define SET_EM_WINDOW_CALLBACK(ev, cb) \ + result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, &cb); \ EM_CHECK(ev) // These callbacks from Emscripten's html5.h suffice to access most // JavaScript APIs. @@ -783,9 +788,6 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive SET_EM_CALLBACK(canvas_id, keypress, keypress_callback) SET_EM_CALLBACK(canvas_id, keyup, keyup_callback) SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback) - SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback) - SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback) -#undef SET_EM_CALLBACK_NOTARGET #undef SET_EM_CALLBACK #undef EM_CHECK @@ -798,6 +800,8 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive WINDOW_EVENT_FOCUS_OUT); godot_js_display_paste_cb(update_clipboard_callback); godot_js_display_drop_files_cb(drop_files_js_callback); + godot_js_display_gamepad_cb(&DisplayServerJavaScript::gamepad_callback); + godot_js_display_vk_cb(&vk_input_text_callback); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event); } @@ -827,7 +831,8 @@ bool DisplayServerJavaScript::has_feature(Feature p_feature) const { //case FEATURE_WINDOW_TRANSPARENCY: //case FEATURE_KEEP_SCREEN_ON: //case FEATURE_ORIENTATION: - //case FEATURE_VIRTUAL_KEYBOARD: + case FEATURE_VIRTUAL_KEYBOARD: + return godot_js_display_vk_available() != 0; default: return false; } @@ -850,20 +855,23 @@ Point2i DisplayServerJavaScript::screen_get_position(int p_screen) const { } 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); + int size[2]; + godot_js_display_screen_size_get(size, size + 1); + return Size2(size[0], size[1]); } Rect2i DisplayServerJavaScript::screen_get_usable_rect(int p_screen) const { - int canvas[2]; - emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); - return Rect2i(0, 0, canvas[0], canvas[1]); + int size[2]; + godot_js_display_window_size_get(size, size + 1); + return Rect2i(0, 0, size[0], size[1]); } int DisplayServerJavaScript::screen_get_dpi(int p_screen) const { - return 96; // TODO maybe check pixel ratio via window.devicePixelRatio * 96? Inexact. + return godot_js_display_screen_dpi_get(); +} + +float DisplayServerJavaScript::screen_get_scale(int p_screen) const { + return godot_js_display_pixel_ratio_get(); } Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const { @@ -897,7 +905,7 @@ void DisplayServerJavaScript::window_set_input_event_callback(const Callable &p_ } 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? + input_text_callback = p_callable; } void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { @@ -945,17 +953,13 @@ Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const { } void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) { - last_width = p_size.x; - last_height = p_size.y; - double scale = godot_js_display_pixel_ratio_get(); - emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y); - emscripten_set_element_css_size(canvas_id, p_size.x / scale, p_size.y / scale); + godot_js_display_desired_size_set(p_size.x, p_size.y); } Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { - int canvas[2]; - emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); - return Size2(canvas[0], canvas[1]); + int size[2]; + godot_js_display_window_size_get(size, size + 1); + return Size2i(size[0], size[1]); } Size2i DisplayServerJavaScript::window_get_real_size(WindowID p_window) const { @@ -969,20 +973,13 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind switch (p_mode) { case WINDOW_MODE_WINDOWED: { if (window_mode == WINDOW_MODE_FULLSCREEN) { - emscripten_exit_fullscreen(); + godot_js_display_fullscreen_exit(); } window_mode = WINDOW_MODE_WINDOWED; - window_set_size(Size2i(last_width, last_height)); } break; case WINDOW_MODE_FULLSCREEN: { - EmscriptenFullscreenStrategy strategy; - strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; - strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; - strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; - strategy.canvasResizedCallback = nullptr; - EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id, false, &strategy); - ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); - ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); + int result = godot_js_display_fullscreen_request(); + ERR_FAIL_COND_MSG(result, "The request was denied. Remember that enabling fullscreen is only possible from an input callback for the HTML5 platform."); } break; case WINDOW_MODE_MAXIMIZED: case WINDOW_MODE_MINIMIZED: @@ -1026,8 +1023,9 @@ bool DisplayServerJavaScript::can_any_window_draw() const { } void DisplayServerJavaScript::process_events() { - if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) + if (godot_js_display_gamepad_sample() == OK) { process_joypads(); + } } int DisplayServerJavaScript::get_current_video_driver() const { diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index 916be1ae45..ece38f1a95 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -57,9 +57,6 @@ private: double last_click_ms = 0; int last_click_button_index = -1; - int last_width = 0; - int last_height = 0; - bool swap_cancel_ok = false; // utilities @@ -78,6 +75,8 @@ private: 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 void vk_input_text_callback(const char *p_text, int p_cursor); + 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); @@ -86,7 +85,7 @@ private: 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); + static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid); void process_joypads(); static Vector<String> get_rendering_drivers_func(); @@ -136,6 +135,10 @@ public: Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + + void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override; + void virtual_keyboard_hide() override; // windows Vector<DisplayServer::WindowID> get_window_list() const override; diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py index 278186e4c0..ab98838e20 100644 --- a/platform/javascript/emscripten_helpers.py +++ b/platform/javascript/emscripten_helpers.py @@ -1,4 +1,4 @@ -import os +import os, json from SCons.Util import WhereIs @@ -15,25 +15,111 @@ def run_closure_compiler(target, source, env, for_signature): return " ".join(cmd) +def get_build_version(): + import version + + name = "custom_build" + if os.getenv("BUILD_NAME") != None: + name = os.getenv("BUILD_NAME") + v = "%d.%d" % (version.major, version.minor) + if version.patch > 0: + v += ".%d" % version.patch + v += ".%s.%s" % (version.status, name) + return v + + def create_engine_file(env, target, source, externs): if env["use_closure_compiler"]: return env.BuildJS(target, source, JSEXTERNS=externs) return env.Textfile(target, [env.File(s) for s in source]) +def create_template_zip(env, js, wasm, extra): + binary_name = "godot.tools" if env["tools"] else "godot" + zip_dir = env.Dir("#bin/.javascript_zip") + in_files = [ + js, + wasm, + "#platform/javascript/js/libs/audio.worklet.js", + ] + out_files = [ + zip_dir.File(binary_name + ".js"), + zip_dir.File(binary_name + ".wasm"), + zip_dir.File(binary_name + ".audio.worklet.js"), + ] + # GDNative/Threads specific + if env["gdnative_enabled"]: + in_files.append(extra) # Runtime + out_files.append(zip_dir.File(binary_name + ".side.wasm")) + elif env["threads_enabled"]: + in_files.append(extra) # Worker + out_files.append(zip_dir.File(binary_name + ".worker.js")) + + service_worker = "#misc/dist/html/service-worker.js" + if env["tools"]: + # HTML + html = "#misc/dist/html/editor.html" + cache = [ + "godot.tools.html", + "offline.html", + "godot.tools.js", + "godot.tools.worker.js", + "godot.tools.audio.worklet.js", + "logo.svg", + "favicon.png", + ] + opt_cache = ["godot.tools.wasm"] + subst_dict = { + "@GODOT_VERSION@": get_build_version(), + "@GODOT_NAME@": "GodotEngine", + "@GODOT_CACHE@": json.dumps(cache), + "@GODOT_OPT_CACHE@": json.dumps(opt_cache), + "@GODOT_OFFLINE_PAGE@": "offline.html", + } + html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict) + in_files.append(html) + out_files.append(zip_dir.File(binary_name + ".html")) + # And logo/favicon + in_files.append("#misc/dist/html/logo.svg") + out_files.append(zip_dir.File("logo.svg")) + in_files.append("#icon.png") + out_files.append(zip_dir.File("favicon.png")) + # PWA + service_worker = env.Substfile( + target="#bin/godot${PROGSUFFIX}.service.worker.js", source=service_worker, SUBST_DICT=subst_dict + ) + in_files.append(service_worker) + out_files.append(zip_dir.File("service.worker.js")) + in_files.append("#misc/dist/html/manifest.json") + out_files.append(zip_dir.File("manifest.json")) + in_files.append("#misc/dist/html/offline.html") + out_files.append(zip_dir.File("offline.html")) + else: + # HTML + in_files.append("#misc/dist/html/full-size.html") + out_files.append(zip_dir.File(binary_name + ".html")) + in_files.append(service_worker) + out_files.append(zip_dir.File(binary_name + ".service.worker.js")) + in_files.append("#misc/dist/html/offline-export.html") + out_files.append(zip_dir.File("godot.offline.html")) + + zip_files = env.InstallAs(out_files, in_files) + env.Zip( + "#bin/godot", + zip_files, + ZIPROOT=zip_dir, + ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}", + ZIPCOMSTR="Archiving $SOURCES as $TARGET", + ) + + def add_js_libraries(env, libraries): - if "JS_LIBS" not in env: - env["JS_LIBS"] = [] env.Append(JS_LIBS=env.File(libraries)) def add_js_pre(env, js_pre): - if "JS_PRE" not in env: - env["JS_PRE"] = [] env.Append(JS_PRE=env.File(js_pre)) def add_js_externs(env, externs): - if "JS_EXTERNS" not in env: - env["JS_EXTERNS"] = [] env.Append(JS_EXTERNS=env.File(externs)) diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 48ccc1f87a..951d86d09e 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -28,7 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#include "core/io/image_loader.h" #include "core/io/json.h" +#include "core/io/stream_peer_ssl.h" #include "core/io/tcp_server.h" #include "core/io/zip_io.h" #include "editor/editor_export.h" @@ -39,21 +41,56 @@ class EditorHTTPServer : public Reference { private: - Ref<TCP_Server> server; - Ref<StreamPeerTCP> connection; + Ref<TCPServer> server; + Map<String, String> mimes; + Ref<StreamPeerTCP> tcp; + Ref<StreamPeerSSL> ssl; + Ref<StreamPeer> peer; + Ref<CryptoKey> key; + Ref<X509Certificate> cert; + bool use_ssl = false; uint64_t time = 0; uint8_t req_buf[4096]; int req_pos = 0; void _clear_client() { - connection = Ref<StreamPeerTCP>(); + peer = Ref<StreamPeer>(); + ssl = Ref<StreamPeerSSL>(); + tcp = Ref<StreamPeerTCP>(); memset(req_buf, 0, sizeof(req_buf)); time = 0; req_pos = 0; } + void _set_internal_certs(Ref<Crypto> p_crypto) { + const String cache_path = EditorSettings::get_singleton()->get_cache_dir(); + const String key_path = cache_path.plus_file("html5_server.key"); + const String crt_path = cache_path.plus_file("html5_server.crt"); + bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path); + if (!regen) { + key = Ref<CryptoKey>(CryptoKey::create()); + cert = Ref<X509Certificate>(X509Certificate::create()); + if (key->load(key_path) != OK || cert->load(crt_path) != OK) { + regen = true; + } + } + if (regen) { + key = p_crypto->generate_rsa(2048); + key->save(key_path); + cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000"); + cert->save(crt_path); + } + } + public: EditorHTTPServer() { + mimes["html"] = "text/html"; + mimes["js"] = "application/javascript"; + mimes["json"] = "application/json"; + mimes["pck"] = "application/octet-stream"; + mimes["png"] = "image/png"; + mimes["svg"] = "image/svg"; + mimes["wasm"] = "application/wasm"; server.instance(); stop(); } @@ -63,7 +100,24 @@ public: _clear_client(); } - Error listen(int p_port, IP_Address p_address) { + Error listen(int p_port, IPAddress p_address, bool p_use_ssl, String p_ssl_key, String p_ssl_cert) { + use_ssl = p_use_ssl; + if (use_ssl) { + Ref<Crypto> crypto = Crypto::create(); + if (crypto.is_null()) { + return ERR_UNAVAILABLE; + } + if (!p_ssl_key.is_empty() && !p_ssl_cert.is_empty()) { + key = Ref<CryptoKey>(CryptoKey::create()); + Error err = key->load(p_ssl_key); + ERR_FAIL_COND_V(err != OK, err); + cert = Ref<X509Certificate>(X509Certificate::create()); + err = cert->load(p_ssl_cert); + ERR_FAIL_COND_V(err != OK, err); + } else { + _set_internal_certs(crypto); + } + } return server->listen(p_port, p_address); } @@ -82,51 +136,21 @@ public: // Wrong protocol ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); - const String cache_path = EditorSettings::get_singleton()->get_cache_dir(); - const String basereq = "/tmp_js_export"; - String filepath; - String ctype; - if (req[1] == basereq + ".html") { - filepath = cache_path.plus_file(req[1].get_file()); - ctype = "text/html"; - } else if (req[1] == basereq + ".js") { - filepath = cache_path.plus_file(req[1].get_file()); - ctype = "application/javascript"; - } else if (req[1] == basereq + ".audio.worklet.js") { - filepath = cache_path.plus_file(req[1].get_file()); - ctype = "application/javascript"; - } else if (req[1] == basereq + ".worker.js") { - filepath = cache_path.plus_file(req[1].get_file()); - ctype = "application/javascript"; - } else if (req[1] == basereq + ".pck") { - filepath = cache_path.plus_file(req[1].get_file()); - ctype = "application/octet-stream"; - } else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") { - // Also allow serving the generated favicon for a smoother loading experience. - if (req[1] == "/favicon.png") { - filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"); - } else { - filepath = basereq + ".png"; - } - ctype = "image/png"; - } else if (req[1] == basereq + ".side.wasm") { - filepath = cache_path.plus_file(req[1].get_file()); - ctype = "application/wasm"; - } else if (req[1] == basereq + ".wasm") { - filepath = cache_path.plus_file(req[1].get_file()); - ctype = "application/wasm"; - } else if (req[1].ends_with(".wasm")) { - filepath = cache_path.plus_file(req[1].get_file()); // TODO dangerous? - ctype = "application/wasm"; - } - if (filepath.is_empty() || !FileAccess::exists(filepath)) { + const String req_file = req[1].get_file(); + const String req_ext = req[1].get_extension(); + const String cache_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("web"); + const String filepath = cache_path.plus_file(req_file); + + if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) { String s = "HTTP/1.1 404 Not Found\r\n"; s += "Connection: Close\r\n"; s += "\r\n"; CharString cs = s.utf8(); - connection->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); + peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); return; } + const String ctype = mimes[req_ext]; + FileAccess *f = FileAccess::open(filepath, FileAccess::READ); ERR_FAIL_COND(!f); String s = "HTTP/1.1 200 OK\r\n"; @@ -135,9 +159,10 @@ public: s += "Access-Control-Allow-Origin: *\r\n"; s += "Cross-Origin-Opener-Policy: same-origin\r\n"; s += "Cross-Origin-Embedder-Policy: require-corp\r\n"; + s += "Cache-Control: no-store, max-age=0\r\n"; s += "\r\n"; CharString cs = s.utf8(); - Error err = connection->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); + Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); if (err != OK) { memdelete(f); ERR_FAIL(); @@ -149,7 +174,7 @@ public: if (read < 1) { break; } - err = connection->put_data(bytes, read); + err = peer->put_data(bytes, read); if (err != OK) { memdelete(f); ERR_FAIL(); @@ -162,21 +187,43 @@ public: if (!server->is_listening()) { return; } - if (connection.is_null()) { + if (tcp.is_null()) { if (!server->is_connection_available()) { return; } - connection = server->take_connection(); + tcp = server->take_connection(); + peer = tcp; time = OS::get_singleton()->get_ticks_usec(); } if (OS::get_singleton()->get_ticks_usec() - time > 1000000) { _clear_client(); return; } - if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { return; } + if (use_ssl) { + if (ssl.is_null()) { + ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + peer = ssl; + ssl->set_blocking_handshake_enabled(false); + if (ssl->accept_stream(tcp, key, cert) != OK) { + _clear_client(); + return; + } + } + ssl->poll(); + if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING) { + // Still handshaking, keep waiting. + return; + } + if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED) { + _clear_client(); + return; + } + } + while (true) { char *r = (char *)req_buf; int l = req_pos - 1; @@ -188,7 +235,7 @@ public: int read = 0; ERR_FAIL_COND(req_pos >= 4096); - Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); + Error err = peer->get_partial_data(&req_buf[req_pos], 1, read); if (err != OK) { // Got an error _clear_client(); @@ -213,7 +260,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { Ref<EditorHTTPServer> server; bool server_quit = false; Mutex server_lock; - Thread *server_thread = nullptr; + Thread server_thread; enum ExportMode { EXPORT_MODE_NORMAL = 0, @@ -241,7 +288,32 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { return name; } - void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects); + Ref<Image> _get_project_icon() const { + Ref<Image> icon; + icon.instance(); + const String icon_path = String(GLOBAL_GET("application/config/icon")).strip_edges(); + if (icon_path.is_empty() || ImageLoader::load_image(icon_path, icon) != OK) { + return EditorNode::get_singleton()->get_editor_theme()->get_icon("DefaultProjectIcon", "EditorIcons")->get_image(); + } + return icon; + } + + Ref<Image> _get_project_splash() const { + Ref<Image> splash; + splash.instance(); + const String splash_path = String(GLOBAL_GET("application/boot_splash/image")).strip_edges(); + if (splash_path.is_empty() || ImageLoader::load_image(splash_path, splash) != OK) { + return Ref<Image>(memnew(Image(boot_splash_png))); + } + return splash; + } + + Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa); + void _replace_strings(Map<String, String> p_replaces, Vector<uint8_t> &r_template); + void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); + Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr); + Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); + Error _write_or_error(const uint8_t *p_content, int p_len, String p_path); static void _server_thread_poll(void *data); @@ -280,45 +352,275 @@ public: ~EditorExportPlatformJavaScript(); }; -void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects) { - 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); - String libs; - for (int i = 0; i < p_shared_objects.size(); i++) { - libs += "\"" + p_shared_objects[i].path.get_file() + "\","; +Error EditorExportPlatformJavaScript::_extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa) { + FileAccess *src_f = nullptr; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + unzFile pkg = unzOpen2(p_template.utf8().get_data(), &io); + + if (!pkg) { + EditorNode::get_singleton()->show_warning(TTR("Could not open template for export:") + "\n" + p_template); + return ERR_FILE_NOT_FOUND; + } + + if (unzGoToFirstFile(pkg) != UNZ_OK) { + EditorNode::get_singleton()->show_warning(TTR("Invalid export template:") + "\n" + p_template); + unzClose(pkg); + return ERR_FILE_CORRUPT; } + do { + //get filename + unz_file_info info; + char fname[16384]; + unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); + + String file = fname; + + // Skip service worker and offline page if not exporting pwa. + if (!pwa && (file == "godot.service.worker.js" || file == "godot.offline.html")) { + continue; + } + Vector<uint8_t> data; + data.resize(info.uncompressed_size); + + //read + unzOpenCurrentFile(pkg); + unzReadCurrentFile(pkg, data.ptrw(), data.size()); + unzCloseCurrentFile(pkg); + + //write + String dst = p_dir.plus_file(file.replace("godot", p_name)); + FileAccess *f = FileAccess::open(dst, FileAccess::WRITE); + if (!f) { + EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + dst); + unzClose(pkg); + return ERR_FILE_CANT_WRITE; + } + f->store_buffer(data.ptr(), data.size()); + memdelete(f); + + } while (unzGoToNextFile(pkg) == UNZ_OK); + unzClose(pkg); + return OK; +} + +Error EditorExportPlatformJavaScript::_write_or_error(const uint8_t *p_content, int p_size, String p_path) { + FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE); + if (!f) { + EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path); + return ERR_FILE_CANT_WRITE; + } + f->store_buffer(p_content, p_size); + memdelete(f); + return OK; +} + +void EditorExportPlatformJavaScript::_replace_strings(Map<String, String> p_replaces, Vector<uint8_t> &r_template) { + String str_template = String::utf8(reinterpret_cast<const char *>(r_template.ptr()), r_template.size()); + String out; + Vector<String> lines = str_template.split("\n"); for (int i = 0; i < lines.size(); i++) { String current_line = lines[i]; - current_line = current_line.replace("$GODOT_BASENAME", p_name); - current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name")); - current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); - current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false"); - current_line = current_line.replace("$GODOT_GDNATIVE_LIBS", libs); - 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"; - } - - CharString cs = str_export.utf8(); - p_html.resize(cs.length()); + for (Map<String, String>::Element *E = p_replaces.front(); E; E = E->next()) { + current_line = current_line.replace(E->key(), E->get()); + } + out += current_line + "\n"; + } + CharString cs = out.utf8(); + r_template.resize(cs.length()); for (int i = 0; i < cs.length(); i++) { - p_html.write[i] = cs[i]; + r_template.write[i] = cs[i]; } } +void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) { + // Engine.js config + Dictionary config; + Array libs; + for (int i = 0; i < p_shared_objects.size(); i++) { + libs.push_back(p_shared_objects[i].path.get_file()); + } + Vector<String> flags; + gen_export_flags(flags, p_flags & (~DEBUG_FLAG_DUMB_CLIENT)); + Array args; + for (int i = 0; i < flags.size(); i++) { + args.push_back(flags[i]); + } + config["canvasResizePolicy"] = p_preset->get("html/canvas_resize_policy"); + config["experimentalVK"] = p_preset->get("html/experimental_virtual_keyboard"); + config["gdnativeLibs"] = libs; + config["executable"] = p_name; + config["args"] = args; + config["fileSizes"] = p_file_sizes; + + String head_include; + if (p_preset->get("html/export_icon")) { + head_include += "<link id='-gd-engine-icon' rel='icon' type='image/png' href='" + p_name + ".icon.png' />\n"; + head_include += "<link rel='apple-touch-icon' href='" + p_name + ".apple-touch-icon.png'/>\n"; + } + if (p_preset->get("progressive_web_app/enabled")) { + head_include += "<link rel='manifest' href='" + p_name + ".manifest.json'>\n"; + head_include += "<script type='application/javascript'>window.addEventListener('load', () => {if ('serviceWorker' in navigator) {navigator.serviceWorker.register('" + + p_name + ".service.worker.js');}});</script>\n"; + } + + // Replaces HTML string + const String str_config = JSON::print(config); + const String custom_head_include = p_preset->get("html/head_include"); + Map<String, String> replaces; + replaces["$GODOT_URL"] = p_name + ".js"; + replaces["$GODOT_PROJECT_NAME"] = ProjectSettings::get_singleton()->get_setting("application/config/name"); + replaces["$GODOT_HEAD_INCLUDE"] = head_include + custom_head_include; + replaces["$GODOT_CONFIG"] = str_config; + _replace_strings(replaces, p_html); +} + +Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr) { + const String name = p_path.get_file().get_basename(); + const String icon_name = vformat("%s.%dx%d.png", name, p_size, p_size); + const String icon_dest = p_path.get_base_dir().plus_file(icon_name); + + Ref<Image> icon; + if (!p_icon.is_empty()) { + icon.instance(); + const Error err = ImageLoader::load_image(p_icon, icon); + if (err != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + p_icon); + return err; + } + if (icon->get_width() != p_size || icon->get_height() != p_size) { + icon->resize(p_size, p_size); + } + } else { + icon = _get_project_icon(); + icon->resize(p_size, p_size); + } + const Error err = icon->save_png(icon_dest); + if (err != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + icon_dest); + return err; + } + Dictionary icon_dict; + icon_dict["sizes"] = vformat("%dx%d", p_size, p_size); + icon_dict["type"] = "image/png"; + icon_dict["src"] = icon_name; + r_arr.push_back(icon_dict); + return err; +} + +Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects) { + // Service worker + const String dir = p_path.get_base_dir(); + const String name = p_path.get_file().get_basename(); + const ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); + Map<String, String> replaces; + replaces["@GODOT_VERSION@"] = "1"; + replaces["@GODOT_NAME@"] = name; + replaces["@GODOT_OFFLINE_PAGE@"] = name + ".offline.html"; + Array files; + replaces["@GODOT_OPT_CACHE@"] = JSON::print(files); + files.push_back(name + ".html"); + files.push_back(name + ".js"); + files.push_back(name + ".wasm"); + files.push_back(name + ".pck"); + files.push_back(name + ".offline.html"); + if (p_preset->get("html/export_icon")) { + files.push_back(name + ".icon.png"); + files.push_back(name + ".apple-touch-icon.png"); + } + if (mode == EXPORT_MODE_THREADS) { + files.push_back(name + ".worker.js"); + files.push_back(name + ".audio.worklet.js"); + } else if (mode == EXPORT_MODE_GDNATIVE) { + files.push_back(name + ".side.wasm"); + for (int i = 0; i < p_shared_objects.size(); i++) { + files.push_back(p_shared_objects[i].path.get_file()); + } + } + replaces["@GODOT_CACHE@"] = JSON::print(files); + + const String sw_path = dir.plus_file(name + ".service.worker.js"); + Vector<uint8_t> sw; + { + FileAccess *f = FileAccess::open(sw_path, FileAccess::READ); + if (!f) { + EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + sw_path); + return ERR_FILE_CANT_READ; + } + sw.resize(f->get_len()); + f->get_buffer(sw.ptrw(), sw.size()); + memdelete(f); + f = nullptr; + } + _replace_strings(replaces, sw); + Error err = _write_or_error(sw.ptr(), sw.size(), dir.plus_file(name + ".service.worker.js")); + if (err != OK) { + return err; + } + + // Custom offline page + const String offline_page = p_preset->get("progressive_web_app/offline_page"); + if (!offline_page.is_empty()) { + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + const String offline_dest = dir.plus_file(name + ".offline.html"); + err = da->copy(ProjectSettings::get_singleton()->globalize_path(offline_page), offline_dest); + if (err != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + offline_dest); + return err; + } + } + + // Manifest + const char *modes[4] = { "fullscreen", "standalone", "minimal-ui", "browser" }; + const char *orientations[3] = { "any", "landscape", "portrait" }; + const int display = CLAMP(int(p_preset->get("progressive_web_app/display")), 0, 4); + const int orientation = CLAMP(int(p_preset->get("progressive_web_app/orientation")), 0, 3); + + Dictionary manifest; + String proj_name = ProjectSettings::get_singleton()->get_setting("application/config/name"); + if (proj_name.is_empty()) { + proj_name = "Godot Game"; + } + manifest["name"] = proj_name; + manifest["start_url"] = "./" + name + ".html"; + manifest["display"] = String::utf8(modes[display]); + manifest["orientation"] = String::utf8(orientations[orientation]); + manifest["background_color"] = "#" + p_preset->get("progressive_web_app/background_color").operator Color().to_html(false); + + Array icons_arr; + const String icon144_path = p_preset->get("progressive_web_app/icon_144x144"); + err = _add_manifest_icon(p_path, icon144_path, 144, icons_arr); + if (err != OK) { + return err; + } + const String icon180_path = p_preset->get("progressive_web_app/icon_180x180"); + err = _add_manifest_icon(p_path, icon180_path, 180, icons_arr); + if (err != OK) { + return err; + } + const String icon512_path = p_preset->get("progressive_web_app/icon_512x512"); + err = _add_manifest_icon(p_path, icon512_path, 512, icons_arr); + if (err != OK) { + return err; + } + manifest["icons"] = icons_arr; + + CharString cs = JSON::print(manifest).utf8(); + err = _write_or_error((const uint8_t *)cs.get_data(), cs.length(), dir.plus_file(name + ".manifest.json")); + if (err != OK) { + return err; + } + + return OK; +} + 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"); } if (p_preset->get("vram_texture_compression/for_mobile")) { - String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); + String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name"); if (driver == "GLES2") { r_features->push_back("etc"); } else if (driver == "Vulkan") { @@ -342,9 +644,19 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op 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::BOOL, "html/export_icon"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/full_window_size"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "html/canvas_resize_policy", PROPERTY_HINT_ENUM, "None,Project,Adaptive"), 2)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/experimental_virtual_keyboard"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "progressive_web_app/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/offline_page", PROPERTY_HINT_FILE, "*.html"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "progressive_web_app/display", PROPERTY_HINT_ENUM, "Fullscreen,Standalone,Minimal Ui,Browser"), 1)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "progressive_web_app/orientation", PROPERTY_HINT_ENUM, "Any,Landscape,Portrait"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/icon_144x144", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg,*.svgz"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/icon_180x180", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg,*.svgz"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/icon_512x512", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg,*.svgz"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "progressive_web_app/background_color", PROPERTY_HINT_COLOR_NO_ALPHA), Color())); } String EditorExportPlatformJavaScript::get_name() const { @@ -410,20 +722,25 @@ List<String> EditorExportPlatformJavaScript::get_binary_extensions(const Ref<Edi Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); - String custom_debug = p_preset->get("custom_template/debug"); - String custom_release = p_preset->get("custom_template/release"); - String custom_html = p_preset->get("html/custom_html_shell"); + const String custom_debug = p_preset->get("custom_template/debug"); + const String custom_release = p_preset->get("custom_template/release"); + const String custom_html = p_preset->get("html/custom_html_shell"); + const bool export_icon = p_preset->get("html/export_icon"); + const bool pwa = p_preset->get("progressive_web_app/enabled"); - String template_path = p_debug ? custom_debug : custom_release; + const String base_dir = p_path.get_base_dir(); + const String base_path = p_path.get_basename(); + const String base_name = p_path.get_file().get_basename(); + // Find the correct template + String template_path = p_debug ? custom_debug : custom_release; template_path = template_path.strip_edges(); - if (template_path == String()) { ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); template_path = find_export_template(_get_template_name(mode, p_debug)); } - if (!DirAccess::exists(p_path.get_base_dir())) { + if (!DirAccess::exists(base_dir)) { return ERR_FILE_BAD_PATH; } @@ -432,8 +749,9 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese return ERR_FILE_NOT_FOUND; } + // Export pck and shared objects Vector<SharedObject> shared_objects; - String pck_path = p_path.get_basename() + ".pck"; + String pck_path = base_path + ".pck"; Error error = save_pack(p_preset, pck_path, &shared_objects); if (error != OK) { EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path); @@ -441,7 +759,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese } DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { - String dst = p_path.get_base_dir().plus_file(shared_objects[i].path.get_file()); + String dst = base_dir.plus_file(shared_objects[i].path.get_file()); error = da->copy(shared_objects[i].path, dst); if (error != OK) { EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file()); @@ -450,111 +768,54 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese } } memdelete(da); + da = nullptr; - FileAccess *src_f = nullptr; - zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - 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; - } - - if (unzGoToFirstFile(pkg) != UNZ_OK) { - EditorNode::get_singleton()->show_warning(TTR("Invalid export template:") + "\n" + template_path); - unzClose(pkg); - return ERR_FILE_CORRUPT; + // Extract templates. + error = _extract_template(template_path, base_dir, base_name, pwa); + if (error) { + return error; } - do { - //get filename - unz_file_info info; - char fname[16384]; - unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); - - String file = fname; - - Vector<uint8_t> data; - data.resize(info.uncompressed_size); - - //read - unzOpenCurrentFile(pkg); - unzReadCurrentFile(pkg, data.ptrw(), data.size()); - unzCloseCurrentFile(pkg); - - //write - - if (file == "godot.html") { - if (!custom_html.is_empty()) { - continue; - } - _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); - 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.side.wasm") { - file = p_path.get_file().get_basename() + ".side.wasm"; - - } else if (file == "godot.audio.worklet.js") { - file = p_path.get_file().get_basename() + ".audio.worklet.js"; - - } else if (file == "godot.wasm") { - file = p_path.get_file().get_basename() + ".wasm"; - } - - String dst = p_path.get_base_dir().plus_file(file); - FileAccess *f = FileAccess::open(dst, FileAccess::WRITE); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + dst); - unzClose(pkg); - return ERR_FILE_CANT_WRITE; - } - f->store_buffer(data.ptr(), data.size()); - memdelete(f); - - } while (unzGoToNextFile(pkg) == UNZ_OK); - unzClose(pkg); - - if (!custom_html.is_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); - return ERR_FILE_CANT_READ; - } - Vector<uint8_t> buf; - buf.resize(f->get_len()); - f->get_buffer(buf.ptrw(), buf.size()); + // Parse generated file sizes (pck and wasm, to help show a meaningful loading bar). + Dictionary file_sizes; + FileAccess *f = nullptr; + f = FileAccess::open(pck_path, FileAccess::READ); + if (f) { + file_sizes[pck_path.get_file()] = (uint64_t)f->get_len(); memdelete(f); - _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); - - f = FileAccess::open(p_path, FileAccess::WRITE); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path); - return ERR_FILE_CANT_WRITE; - } - f->store_buffer(buf.ptr(), buf.size()); + f = nullptr; + } + f = FileAccess::open(base_path + ".wasm", FileAccess::READ); + if (f) { + file_sizes[base_name + ".wasm"] = (uint64_t)f->get_len(); memdelete(f); + f = nullptr; } - Ref<Image> splash; - const String splash_path = String(GLOBAL_GET("application/boot_splash/image")).strip_edges(); - if (!splash_path.is_empty()) { - splash.instance(); - const Error err = splash->load(splash_path); - if (err) { - EditorNode::get_singleton()->show_warning(TTR("Could not read boot splash image file:") + "\n" + splash_path + "\n" + TTR("Using default boot splash image.")); - splash.unref(); - } + // Read the HTML shell file (custom or from template). + const String html_path = custom_html.is_empty() ? base_path + ".html" : custom_html; + Vector<uint8_t> html; + f = FileAccess::open(html_path, FileAccess::READ); + if (!f) { + EditorNode::get_singleton()->show_warning(TTR("Could not read HTML shell:") + "\n" + html_path); + return ERR_FILE_CANT_READ; } - if (splash.is_null()) { - splash = Ref<Image>(memnew(Image(boot_splash_png))); + html.resize(f->get_len()); + f->get_buffer(html.ptrw(), html.size()); + memdelete(f); + f = nullptr; + + // Generate HTML file with replaced strings. + _fix_html(html, p_preset, base_name, p_debug, p_flags, shared_objects, file_sizes); + Error err = _write_or_error(html.ptr(), html.size(), p_path); + if (err != OK) { + return err; } - const String splash_png_path = p_path.get_base_dir().plus_file(p_path.get_file().get_basename() + ".png"); + html.resize(0); + + // Export splash (why?) + Ref<Image> splash = _get_project_splash(); + const String splash_png_path = base_path + ".png"; if (splash->save_png(splash_png_path) != OK) { EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + splash_png_path); return ERR_FILE_CANT_WRITE; @@ -562,22 +823,27 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese // Save a favicon that can be accessed without waiting for the project to finish loading. // This way, the favicon can be displayed immediately when loading the page. - Ref<Image> favicon; - const String favicon_path = String(GLOBAL_GET("application/config/icon")).strip_edges(); - if (!favicon_path.is_empty()) { - favicon.instance(); - const Error err = favicon->load(favicon_path); - if (err) { - favicon.unref(); - } - } - - if (favicon.is_valid()) { - const String favicon_png_path = p_path.get_base_dir().plus_file("favicon.png"); + if (export_icon) { + Ref<Image> favicon = _get_project_icon(); + const String favicon_png_path = base_path + ".icon.png"; if (favicon->save_png(favicon_png_path) != OK) { EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + favicon_png_path); return ERR_FILE_CANT_WRITE; } + favicon->resize(180, 180); + const String apple_icon_png_path = base_path + ".apple-touch-icon.png"; + if (favicon->save_png(apple_icon_png_path) != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + apple_icon_png_path); + return ERR_FILE_CANT_WRITE; + } + } + + // Generate the PWA worker and manifest + if (pwa) { + err = _build_pwa(p_preset, p_path, shared_objects); + if (err != OK) { + return err; + } } return OK; @@ -622,26 +888,38 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese return OK; } - const String basepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export"); + const String dest = EditorSettings::get_singleton()->get_cache_dir().plus_file("web"); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (!da->dir_exists(dest)) { + Error err = da->make_dir_recursive(dest); + if (err != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not create HTTP server directory:") + "\n" + dest); + return err; + } + } + const String basepath = dest.plus_file("tmp_js_export"); Error err = export_project(p_preset, true, basepath + ".html", p_debug_flags); if (err != OK) { // Export generates several files, clean them up on failure. DirAccess::remove_file_or_error(basepath + ".html"); + DirAccess::remove_file_or_error(basepath + ".offline.html"); DirAccess::remove_file_or_error(basepath + ".js"); DirAccess::remove_file_or_error(basepath + ".worker.js"); DirAccess::remove_file_or_error(basepath + ".audio.worklet.js"); + DirAccess::remove_file_or_error(basepath + ".service.worker.js"); DirAccess::remove_file_or_error(basepath + ".pck"); DirAccess::remove_file_or_error(basepath + ".png"); DirAccess::remove_file_or_error(basepath + ".side.wasm"); DirAccess::remove_file_or_error(basepath + ".wasm"); - DirAccess::remove_file_or_error(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png")); + DirAccess::remove_file_or_error(basepath + ".icon.png"); + DirAccess::remove_file_or_error(basepath + ".apple-touch-icon.png"); return err; } const uint16_t bind_port = EDITOR_GET("export/web/http_port"); // Resolve host if needed. const String bind_host = EDITOR_GET("export/web/http_host"); - IP_Address bind_ip; + IPAddress bind_ip; if (bind_host.is_valid_ip_address()) { bind_ip = bind_host; } else { @@ -649,16 +927,23 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese } ERR_FAIL_COND_V_MSG(!bind_ip.is_valid(), ERR_INVALID_PARAMETER, "Invalid editor setting 'export/web/http_host': '" + bind_host + "'. Try using '127.0.0.1'."); + const bool use_ssl = EDITOR_GET("export/web/use_ssl"); + const String ssl_key = EDITOR_GET("export/web/ssl_key"); + const String ssl_cert = EDITOR_GET("export/web/ssl_certificate"); + // Restart server. { MutexLock lock(server_lock); server->stop(); - err = server->listen(bind_port, bind_ip); + err = server->listen(bind_port, bind_ip, use_ssl, ssl_key, ssl_cert); + } + if (err != OK) { + EditorNode::get_singleton()->show_warning(TTR("Error starting HTTP server:") + "\n" + itos(err)); + return err; } - ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to start HTTP server."); - OS::get_singleton()->shell_open(String("http://" + bind_host + ":" + itos(bind_port) + "/tmp_js_export.html")); + OS::get_singleton()->shell_open(String((use_ssl ? "https://" : "http://") + bind_host + ":" + itos(bind_port) + "/tmp_js_export.html")); // FIXME: Find out how to clean up export files after running the successfully // exported game. Might not be trivial. return OK; @@ -681,7 +966,7 @@ void EditorExportPlatformJavaScript::_server_thread_poll(void *data) { EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { server.instance(); - server_thread = Thread::create(_server_thread_poll, this); + server_thread.start(_server_thread_poll, this); Ref<Image> img = memnew(Image(_javascript_logo)); logo.instance(); @@ -702,14 +987,18 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() { server->stop(); server_quit = true; - Thread::wait_to_finish(server_thread); - memdelete(server_thread); + server_thread.wait_to_finish(); } void register_javascript_exporter() { EDITOR_DEF("export/web/http_host", "localhost"); EDITOR_DEF("export/web/http_port", 8060); + EDITOR_DEF("export/web/use_ssl", false); + EDITOR_DEF("export/web/ssl_key", ""); + EDITOR_DEF("export/web/ssl_certificate", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/web/http_port", PROPERTY_HINT_RANGE, "1,65535,1")); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/web/ssl_key", PROPERTY_HINT_GLOBAL_FILE, "*.key")); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/web/ssl_certificate", PROPERTY_HINT_GLOBAL_FILE, "*.crt,*.pem")); Ref<EditorExportPlatformJavaScript> platform; platform.instance(); diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h index 5b98253b08..8927a83cb3 100644 --- a/platform/javascript/godot_js.h +++ b/platform/javascript/godot_js.h @@ -40,7 +40,6 @@ extern "C" { // Config extern void godot_js_config_locale_get(char *p_ptr, int p_ptr_max); extern void godot_js_config_canvas_id_get(char *p_ptr, int p_ptr_max); -extern int godot_js_config_is_resize_on_start(); // OS extern void godot_js_os_finish_async(void (*p_callback)()); @@ -49,8 +48,10 @@ extern int godot_js_os_fs_is_persistent(); extern void godot_js_os_fs_sync(void (*p_callback)()); extern int godot_js_os_execute(const char *p_json); extern void godot_js_os_shell_open(const char *p_uri); +extern int godot_js_os_hw_concurrency_get(); // Display +extern int godot_js_display_screen_dpi_get(); extern double godot_js_display_pixel_ratio_get(); extern void godot_js_display_alert(const char *p_text); extern int godot_js_display_touchscreen_is_available(); @@ -59,12 +60,18 @@ extern int godot_js_display_is_swap_ok_cancel(); // Display canvas extern void godot_js_display_canvas_focus(); extern int godot_js_display_canvas_is_focused(); -extern void godot_js_display_canvas_bounding_rect_position_get(int32_t *p_x, int32_t *p_y); // Display window -extern void godot_js_display_window_request_fullscreen(); +extern void godot_js_display_desired_size_set(int p_width, int p_height); +extern int godot_js_display_size_update(); +extern void godot_js_display_window_size_get(int32_t *p_x, int32_t *p_y); +extern void godot_js_display_screen_size_get(int32_t *p_x, int32_t *p_y); +extern int godot_js_display_fullscreen_request(); +extern int godot_js_display_fullscreen_exit(); +extern void godot_js_display_compute_position(int p_x, int p_y, int32_t *r_x, int32_t *r_y); extern void godot_js_display_window_title_set(const char *p_text); extern void godot_js_display_window_icon_set(const uint8_t *p_ptr, int p_len); +extern int godot_js_display_has_webgl(int p_version); // Display clipboard extern int godot_js_display_clipboard_set(const char *p_text); @@ -76,10 +83,24 @@ extern int godot_js_display_cursor_is_hidden(); extern void godot_js_display_cursor_set_custom_shape(const char *p_shape, const uint8_t *p_ptr, int p_len, int p_hotspot_x, int p_hotspot_y); extern void godot_js_display_cursor_set_visible(int p_visible); +// Display gamepad +extern char *godot_js_display_gamepad_cb(void (*p_on_change)(int p_index, int p_connected, const char *p_id, const char *p_guid)); +extern int godot_js_display_gamepad_sample(); +extern int godot_js_display_gamepad_sample_count(); +extern int godot_js_display_gamepad_sample_get(int p_idx, float r_btns[16], int32_t *r_btns_num, float r_axes[10], int32_t *r_axes_num, int32_t *r_standard); + // Display listeners extern void godot_js_display_notification_cb(void (*p_callback)(int p_notification), int p_enter, int p_exit, int p_in, int p_out); extern void godot_js_display_paste_cb(void (*p_callback)(const char *p_text)); extern void godot_js_display_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec)); +extern void godot_js_display_setup_canvas(int p_width, int p_height, int p_fullscreen, int p_hidpi); + +// Display Virtual Keyboard +extern int godot_js_display_vk_available(); +extern void godot_js_display_vk_cb(void (*p_input)(const char *p_text, int p_cursor)); +extern void godot_js_display_vk_show(const char *p_text, int p_multiline, int p_start, int p_end); +extern void godot_js_display_vk_hide(); + #ifdef __cplusplus } #endif diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc index 2ef1ad5b83..842a93fcba 100644 --- a/platform/javascript/http_client.h.inc +++ b/platform/javascript/http_client.h.inc @@ -30,24 +30,21 @@ // HTTPClient's additional private members in the javascript platform -Error prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers); +Error make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len); +static void _parse_headers(int p_len, const char **p_headers, void *p_ref); -int xhr_id; +int js_id = 0; int read_limit = 4096; -int response_read_offset = 0; Status status = STATUS_DISCONNECTED; String host; int port = -1; bool use_tls = false; -String username; -String password; int polled_response_code = 0; -String polled_response_header; -PackedByteArray polled_response; +Vector<String> response_headers; +Vector<uint8_t> response_buffer; #ifdef DEBUG_ENABLED -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 44819c495c..a6cf4b0eb8 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -30,7 +30,38 @@ #include "core/io/http_client.h" -#include "http_request.h" +#ifdef __cplusplus +extern "C" { +#endif + +#include "stddef.h" + +typedef enum { + GODOT_JS_FETCH_STATE_REQUESTING = 0, + GODOT_JS_FETCH_STATE_BODY = 1, + GODOT_JS_FETCH_STATE_DONE = 2, + GODOT_JS_FETCH_STATE_ERROR = -1, +} godot_js_fetch_state_t; + +extern int godot_js_fetch_create(const char *p_method, const char *p_url, const char **p_headers, int p_headers_len, const uint8_t *p_body, int p_body_len); +extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_size, const char **p_headers, void *p_ref), void *p_ref); +extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size); +extern void godot_js_fetch_free(int p_id); +extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id); +extern int godot_js_fetch_body_length_get(int p_id); +extern int godot_js_fetch_http_status_get(int p_id); +extern int godot_js_fetch_is_chunked(int p_id); + +#ifdef __cplusplus +} +#endif + +void HTTPClient::_parse_headers(int p_len, const char **p_headers, void *p_ref) { + HTTPClient *client = static_cast<HTTPClient *>(p_ref); + for (int i = 0; i < p_len; i++) { + client->response_headers.push_back(String::utf8(p_headers[i])); + } +} Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { close(); @@ -74,7 +105,7 @@ 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) { +Error HTTPClient::make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) { 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); @@ -83,37 +114,33 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER); String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url; - godot_xhr_reset(xhr_id); - godot_xhr_open(xhr_id, _methods[p_method], url.utf8().get_data(), - username.is_empty() ? nullptr : username.utf8().get_data(), - password.is_empty() ? nullptr : password.utf8().get_data()); - + Vector<CharString> keeper; + Vector<const char *> c_strings; for (int i = 0; i < p_headers.size(); i++) { - int header_separator = p_headers[i].find(": "); - ERR_FAIL_COND_V(header_separator < 0, ERR_INVALID_PARAMETER); - godot_xhr_set_request_header(xhr_id, - p_headers[i].left(header_separator).utf8().get_data(), - p_headers[i].right(header_separator + 2).utf8().get_data()); + keeper.push_back(p_headers[i].utf8()); + c_strings.push_back(keeper[i].get_data()); + } + if (js_id) { + godot_js_fetch_free(js_id); } - response_read_offset = 0; + js_id = godot_js_fetch_create(_methods[p_method], url.utf8().get_data(), c_strings.ptrw(), c_strings.size(), p_body, p_body_len); status = STATUS_REQUESTING; return OK; } 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; - godot_xhr_send_data(xhr_id, p_body.ptr(), p_body.size()); - return OK; + if (p_body.is_empty()) { + return make_request(p_method, p_url, p_headers, nullptr, 0); + } + return make_request(p_method, p_url, p_headers, p_body.ptr(), p_body.size()); } 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; - godot_xhr_send_string(xhr_id, p_body.utf8().get_data()); - return OK; + if (p_body.is_empty()) { + return make_request(p_method, p_url, p_headers, nullptr, 0); + } + const CharString cs = p_body.utf8(); + return make_request(p_method, p_url, p_headers, (const uint8_t *)cs.get_data(), cs.size() - 1); } void HTTPClient::close() { @@ -121,10 +148,13 @@ void HTTPClient::close() { port = -1; use_tls = false; status = STATUS_DISCONNECTED; - polled_response.resize(0); polled_response_code = 0; - polled_response_header = String(); - godot_xhr_reset(xhr_id); + response_headers.resize(0); + response_buffer.resize(0); + if (js_id) { + godot_js_fetch_free(js_id); + js_id = 0; + } } HTTPClient::Status HTTPClient::get_status() const { @@ -132,12 +162,11 @@ HTTPClient::Status HTTPClient::get_status() const { } bool HTTPClient::has_response() const { - return !polled_response_header.is_empty(); + return response_headers.size() > 0; } bool HTTPClient::is_response_chunked() const { - // TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream - return false; + return godot_js_fetch_is_chunked(js_id); } int HTTPClient::get_response_code() const { @@ -145,36 +174,42 @@ int HTTPClient::get_response_code() const { } Error HTTPClient::get_response_headers(List<String> *r_response) { - if (polled_response_header.is_empty()) + if (!response_headers.size()) { return ERR_INVALID_PARAMETER; - - Vector<String> header_lines = polled_response_header.split("\r\n", false); - for (int i = 0; i < header_lines.size(); ++i) { - r_response->push_back(header_lines[i]); } - polled_response_header = String(); + for (int i = 0; i < response_headers.size(); i++) { + r_response->push_back(response_headers[i]); + } + response_headers.clear(); return OK; } int HTTPClient::get_response_body_length() const { - return polled_response.size(); + return godot_js_fetch_body_length_get(js_id); } 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); - PackedByteArray chunk; - chunk.resize(to_read); - memcpy(chunk.ptrw(), polled_response.ptr() + response_read_offset, to_read); - response_read_offset += to_read; - - if (response_read_offset == polled_response.size()) { - status = STATUS_CONNECTED; - polled_response.resize(0); - godot_xhr_reset(xhr_id); + if (response_buffer.size() != read_limit) { + response_buffer.resize(read_limit); + } + int read = godot_js_fetch_read_chunk(js_id, response_buffer.ptrw(), read_limit); + + // Check if the stream is over. + godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id); + if (state == GODOT_JS_FETCH_STATE_DONE) { + status = STATUS_DISCONNECTED; + } else if (state != GODOT_JS_FETCH_STATE_BODY) { + status = STATUS_CONNECTION_ERROR; } + PackedByteArray chunk; + if (!read) { + return chunk; + } + chunk.resize(read); + memcpy(chunk.ptrw(), response_buffer.ptr(), read); return chunk; } @@ -208,48 +243,48 @@ Error HTTPClient::poll() { return OK; case STATUS_CONNECTED: - case STATUS_BODY: return OK; + case STATUS_BODY: { + godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id); + if (state == GODOT_JS_FETCH_STATE_DONE) { + status = STATUS_DISCONNECTED; + } else if (state != GODOT_JS_FETCH_STATE_BODY) { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + return OK; + } + case STATUS_CONNECTION_ERROR: return ERR_CONNECTION_ERROR; case STATUS_REQUESTING: { #ifdef DEBUG_ENABLED - if (!has_polled) { - has_polled = true; - } else { - // forcing synchronous requests is not possible on the web - if (last_polling_frame == Engine::get_singleton()->get_idle_frames()) { - WARN_PRINT("HTTPClient polled multiple times in one frame, " - "but request cannot progress more than once per " - "frame on the HTML5 platform."); - } + // forcing synchronous requests is not possible on the web + if (last_polling_frame == Engine::get_singleton()->get_process_frames()) { + WARN_PRINT("HTTPClient polled multiple times in one frame, " + "but request cannot progress more than once per " + "frame on the HTML5 platform."); } - last_polling_frame = Engine::get_singleton()->get_idle_frames(); + last_polling_frame = Engine::get_singleton()->get_process_frames(); #endif - polled_response_code = godot_xhr_get_status(xhr_id); - if (godot_xhr_get_ready_state(xhr_id) != XHR_READY_STATE_DONE) { + polled_response_code = godot_js_fetch_http_status_get(js_id); + godot_js_fetch_state_t js_state = godot_js_fetch_state_get(js_id); + if (js_state == GODOT_JS_FETCH_STATE_REQUESTING) { return OK; - } else if (!polled_response_code) { + } else if (js_state == GODOT_JS_FETCH_STATE_ERROR) { + // Fetch is in error state. + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + if (godot_js_fetch_read_headers(js_id, &_parse_headers, this)) { + // Failed to parse headers. status = STATUS_CONNECTION_ERROR; return ERR_CONNECTION_ERROR; } - status = STATUS_BODY; - - PackedByteArray bytes; - int len = godot_xhr_get_response_headers_length(xhr_id); - bytes.resize(len + 1); - - godot_xhr_get_response_headers(xhr_id, reinterpret_cast<char *>(bytes.ptrw()), len); - bytes.ptrw()[len] = 0; - - polled_response_header = String::utf8(reinterpret_cast<const char *>(bytes.ptr())); - - polled_response.resize(godot_xhr_get_response_length(xhr_id)); - godot_xhr_get_response(xhr_id, polled_response.ptrw(), polled_response.size()); break; } @@ -260,9 +295,8 @@ Error HTTPClient::poll() { } HTTPClient::HTTPClient() { - xhr_id = godot_xhr_new(); } HTTPClient::~HTTPClient() { - godot_xhr_free(xhr_id); + close(); } diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 5656ecd7dc..0fe95b0a8f 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -87,7 +87,14 @@ extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) { ResourceLoader::set_abort_on_missing_resources(false); Main::start(); - os->get_main_loop()->init(); + os->get_main_loop()->initialize(); +#ifdef TOOLS_ENABLED + if (Main::is_project_manager() && FileAccess::exists("/tmp/preload.zip")) { + PackedStringArray ps; + ps.push_back("/tmp/preload.zip"); + os->get_main_loop()->emit_signal("files_dropped", ps, -1); + } +#endif emscripten_set_main_loop(main_loop_callback, -1, false); // Immediately run the first iteration. // We are inside an animation frame, we want to immediately draw on the newly setup canvas. diff --git a/platform/javascript/js/dynlink.pre.js b/platform/javascript/js/dynlink.pre.js deleted file mode 100644 index 34bc371ea9..0000000000 --- a/platform/javascript/js/dynlink.pre.js +++ /dev/null @@ -1 +0,0 @@ -Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'].concat(Module['dynamicLibraries'] ? Module['dynamicLibraries'] : []); diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js new file mode 100644 index 0000000000..6072782875 --- /dev/null +++ b/platform/javascript/js/engine/config.js @@ -0,0 +1,337 @@ +/** + * An object used to configure the Engine instance based on godot export options, and to override those in custom HTML + * templates if needed. + * + * @header Engine configuration + * @summary The Engine configuration object. This is just a typedef, create it like a regular object, e.g.: + * + * ``const MyConfig = { executable: 'godot', unloadAfterInit: false }`` + * + * @typedef {Object} EngineConfig + */ +const EngineConfig = {}; // eslint-disable-line no-unused-vars + +/** + * @struct + * @constructor + * @ignore + */ +const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-vars + const cfg = /** @lends {InternalConfig.prototype} */ { + /** + * Whether the unload the engine automatically after the instance is initialized. + * + * @memberof EngineConfig + * @default + * @type {boolean} + */ + unloadAfterInit: true, + /** + * The HTML DOM Canvas object to use. + * + * By default, the first canvas element in the document will be used is none is specified. + * + * @memberof EngineConfig + * @default + * @type {?HTMLCanvasElement} + */ + canvas: null, + /** + * The name of the WASM file without the extension. (Set by Godot Editor export process). + * + * @memberof EngineConfig + * @default + * @type {string} + */ + executable: '', + /** + * An alternative name for the game pck to load. The executable name is used otherwise. + * + * @memberof EngineConfig + * @default + * @type {?string} + */ + mainPack: null, + /** + * Specify a language code to select the proper localization for the game. + * + * The browser locale will be used if none is specified. See complete list of + * :ref:`supported locales <doc_locales>`. + * + * @memberof EngineConfig + * @type {?string} + * @default + */ + locale: null, + /** + * The canvas resize policy determines how the canvas should be resized by Godot. + * + * ``0`` means Godot won't do any resizing. This is useful if you want to control the canvas size from + * javascript code in your template. + * + * ``1`` means Godot will resize the canvas on start, and when changing window size via engine functions. + * + * ``2`` means Godot will adapt the canvas size to match the whole browser window. + * + * @memberof EngineConfig + * @type {number} + * @default + */ + canvasResizePolicy: 2, + /** + * The arguments to be passed as command line arguments on startup. + * + * See :ref:`command line tutorial <doc_command_line_tutorial>`. + * + * **Note**: :js:meth:`startGame <Engine.prototype.startGame>` will always add the ``--main-pack`` argument. + * + * @memberof EngineConfig + * @type {Array<string>} + * @default + */ + args: [], + /** + * When enabled, this will turn on experimental virtual keyboard support on mobile. + * + * @memberof EngineConfig + * @type {boolean} + * @default + */ + experimentalVK: false, + /** + * @ignore + * @type {Array.<string>} + */ + persistentPaths: ['/userfs'], + /** + * @ignore + * @type {boolean} + */ + persistentDrops: false, + /** + * @ignore + * @type {Array.<string>} + */ + gdnativeLibs: [], + /** + * @ignore + * @type {Array.<string>} + */ + fileSizes: [], + /** + * A callback function for handling Godot's ``OS.execute`` calls. + * + * This is for example used in the Web Editor template to switch between project manager and editor, and for running the game. + * + * @callback EngineConfig.onExecute + * @param {string} path The path that Godot's wants executed. + * @param {Array.<string>} args The arguments of the "command" to execute. + */ + /** + * @ignore + * @type {?function(string, Array.<string>)} + */ + onExecute: null, + /** + * A callback function for being notified when the Godot instance quits. + * + * **Note**: This function will not be called if the engine crashes or become unresponsive. + * + * @callback EngineConfig.onExit + * @param {number} status_code The status code returned by Godot on exit. + */ + /** + * @ignore + * @type {?function(number)} + */ + onExit: null, + /** + * A callback function for displaying download progress. + * + * The function is called once per frame while downloading files, so the usage of ``requestAnimationFrame()`` + * is not necessary. + * + * If the callback function receives a total amount of bytes as 0, this means that it is impossible to calculate. + * Possible reasons include: + * + * - Files are delivered with server-side chunked compression + * - Files are delivered with server-side compression on Chromium + * - Not all file downloads have started yet (usually on servers without multi-threading) + * + * @callback EngineConfig.onProgress + * @param {number} current The current amount of downloaded bytes so far. + * @param {number} total The total amount of bytes to be downloaded. + */ + /** + * @ignore + * @type {?function(number, number)} + */ + onProgress: null, + /** + * A callback function for handling the standard output stream. This method should usually only be used in debug pages. + * + * By default, ``console.log()`` is used. + * + * @callback EngineConfig.onPrint + * @param {...*} [var_args] A variadic number of arguments to be printed. + */ + /** + * @ignore + * @type {?function(...*)} + */ + onPrint: function () { + console.log.apply(console, Array.from(arguments)); // eslint-disable-line no-console + }, + /** + * A callback function for handling the standard error stream. This method should usually only be used in debug pages. + * + * By default, ``console.error()`` is used. + * + * @callback EngineConfig.onPrintError + * @param {...*} [var_args] A variadic number of arguments to be printed as errors. + */ + /** + * @ignore + * @type {?function(...*)} + */ + onPrintError: function (var_args) { + console.error.apply(console, Array.from(arguments)); // eslint-disable-line no-console + }, + }; + + /** + * @ignore + * @struct + * @constructor + * @param {EngineConfig} opts + */ + function Config(opts) { + this.update(opts); + } + + Config.prototype = cfg; + + /** + * @ignore + * @param {EngineConfig} opts + */ + Config.prototype.update = function (opts) { + const config = opts || {}; + function parse(key, def) { + if (typeof (config[key]) === 'undefined') { + return def; + } + return config[key]; + } + // Module config + this.unloadAfterInit = parse('unloadAfterInit', this.unloadAfterInit); + this.onPrintError = parse('onPrintError', this.onPrintError); + this.onPrint = parse('onPrint', this.onPrint); + this.onProgress = parse('onProgress', this.onProgress); + + // Godot config + this.canvas = parse('canvas', this.canvas); + this.executable = parse('executable', this.executable); + this.mainPack = parse('mainPack', this.mainPack); + this.locale = parse('locale', this.locale); + this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy); + this.persistentPaths = parse('persistentPaths', this.persistentPaths); + this.persistentDrops = parse('persistentDrops', this.persistentDrops); + this.experimentalVK = parse('experimentalVK', this.experimentalVK); + this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs); + this.fileSizes = parse('fileSizes', this.fileSizes); + this.args = parse('args', this.args); + this.onExecute = parse('onExecute', this.onExecute); + this.onExit = parse('onExit', this.onExit); + }; + + /** + * @ignore + * @param {string} loadPath + * @param {Response} response + */ + Config.prototype.getModuleConfig = function (loadPath, response) { + let r = response; + return { + 'print': this.onPrint, + 'printErr': this.onPrintError, + 'thisProgram': this.executable, + 'noExitRuntime': true, + 'dynamicLibraries': [`${loadPath}.side.wasm`], + 'instantiateWasm': function (imports, onSuccess) { + function done(result) { + onSuccess(result['instance'], result['module']); + } + if (typeof (WebAssembly.instantiateStreaming) !== 'undefined') { + WebAssembly.instantiateStreaming(Promise.resolve(r), imports).then(done); + } else { + r.arrayBuffer().then(function (buffer) { + WebAssembly.instantiate(buffer, imports).then(done); + }); + } + r = null; + return {}; + }, + 'locateFile': function (path) { + if (path.endsWith('.worker.js')) { + return `${loadPath}.worker.js`; + } else if (path.endsWith('.audio.worklet.js')) { + return `${loadPath}.audio.worklet.js`; + } else if (path.endsWith('.js')) { + return `${loadPath}.js`; + } else if (path.endsWith('.side.wasm')) { + return `${loadPath}.side.wasm`; + } else if (path.endsWith('.wasm')) { + return `${loadPath}.wasm`; + } + return path; + }, + }; + }; + + /** + * @ignore + * @param {function()} cleanup + */ + Config.prototype.getGodotConfig = function (cleanup) { + // Try to find a canvas + if (!(this.canvas instanceof HTMLCanvasElement)) { + const nodes = document.getElementsByTagName('canvas'); + if (nodes.length && nodes[0] instanceof HTMLCanvasElement) { + this.canvas = nodes[0]; + } + if (!this.canvas) { + throw new Error('No canvas found in page'); + } + } + // Canvas can grab focus on click, or key events won't work. + if (this.canvas.tabIndex < 0) { + this.canvas.tabIndex = 0; + } + + // Browser locale, or custom one if defined. + let locale = this.locale; + if (!locale) { + locale = navigator.languages ? navigator.languages[0] : navigator.language; + locale = locale.split('.')[0]; + } + const onExit = this.onExit; + + // Godot configuration. + return { + 'canvas': this.canvas, + 'canvasResizePolicy': this.canvasResizePolicy, + 'locale': locale, + 'persistentDrops': this.persistentDrops, + 'virtualKeyboard': this.experimentalVK, + 'onExecute': this.onExecute, + 'onExit': function (p_code) { + cleanup(); // We always need to call the cleanup callback to free memory. + if (typeof (onExit) === 'function') { + onExit(p_code); + } + }, + }; + }; + return new Config(initConfig); +}; diff --git a/platform/javascript/js/engine/engine.externs.js b/platform/javascript/js/engine/engine.externs.js index 1a94dd15ec..35a66a93ae 100644 --- a/platform/javascript/js/engine/engine.externs.js +++ b/platform/javascript/js/engine/engine.externs.js @@ -1,3 +1,4 @@ var Godot; var WebAssembly = {}; WebAssembly.instantiate = function(buffer, imports) {}; +WebAssembly.instantiateStreaming = function(response, imports) {}; diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js index 4b8a7dde69..17a8df9e29 100644 --- a/platform/javascript/js/engine/engine.js +++ b/platform/javascript/js/engine/engine.js @@ -1,291 +1,277 @@ +/** + * Projects exported for the Web expose the :js:class:`Engine` class to the JavaScript environment, that allows + * fine control over the engine's start-up process. + * + * This API is built in an asynchronous manner and requires basic understanding + * of `Promises <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises>`__. + * + * @module Engine + * @header HTML5 shell class reference + */ const Engine = (function () { const preloader = new Preloader(); - let wasmExt = '.wasm'; - let unloadAfterInit = true; - let loadPath = ''; let loadPromise = null; + let loadPath = ''; let initPromise = null; - let stderr = null; - let stdout = null; - let progressFunc = null; - function load(basePath) { + /** + * @classdesc The ``Engine`` class provides methods for loading and starting exported projects on the Web. For default export + * settings, this is already part of the exported HTML page. To understand practical use of the ``Engine`` class, + * see :ref:`Custom HTML page for Web export <doc_customizing_html5_shell>`. + * + * @description Create a new Engine instance with the given configuration. + * + * @global + * @constructor + * @param {EngineConfig} initConfig The initial config for this instance. + */ + function Engine(initConfig) { // eslint-disable-line no-shadow + this.config = new InternalConfig(initConfig); + this.rtenv = null; + } + + /** + * Load the engine from the specified base path. + * + * @param {string} basePath Base path of the engine to load. + * @param {number=} [size=0] The file size if known. + * @returns {Promise} A Promise that resolves once the engine is loaded. + * + * @function Engine.load + */ + Engine.load = function (basePath, size) { if (loadPromise == null) { loadPath = basePath; - loadPromise = preloader.loadPromise(basePath + wasmExt); - preloader.setProgressFunc(progressFunc); + loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true); requestAnimationFrame(preloader.animateProgress); } return loadPromise; - } + }; - function unload() { + /** + * Unload the engine to free memory. + * + * This method will be called automatically depending on the configuration. See :js:attr:`unloadAfterInit`. + * + * @function Engine.unload + */ + Engine.unload = function () { loadPromise = null; - } - - /** @constructor */ - function Engine() { // eslint-disable-line no-shadow - this.canvas = null; - this.executableName = ''; - this.rtenv = null; - this.customLocale = null; - this.resizeCanvasOnStart = false; - this.onExecute = null; - this.onExit = null; - this.persistentPaths = ['/userfs']; - this.gdnativeLibs = []; - } - - Engine.prototype.init = /** @param {string=} basePath */ function (basePath) { - if (initPromise) { - return initPromise; - } - 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); - } - let config = {}; - if (typeof stdout === 'function') { - config.print = stdout; - } - if (typeof stderr === 'function') { - config.printErr = stderr; - } - const me = this; - initPromise = new Promise(function (resolve, reject) { - config['locateFile'] = Utils.createLocateRewrite(loadPath); - config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise); - // Emscripten configuration. - config['thisProgram'] = me.executableName; - config['noExitRuntime'] = true; - config['dynamicLibraries'] = me.gdnativeLibs; - Godot(config).then(function (module) { - module['initFS'](me.persistentPaths).then(function (fs_err) { - me.rtenv = module; - if (unloadAfterInit) { - unload(); - } - resolve(); - config = null; - }); - }); - }); - return initPromise; }; - /** @type {function(string, string):Object} */ - Engine.prototype.preloadFile = function (file, path) { - return preloader.preload(file, path); + /** + * Check whether WebGL is available. Optionally, specify a particular version of WebGL to check for. + * + * @param {number=} [majorVersion=1] The major WebGL version to check for. + * @returns {boolean} If the given major version of WebGL is available. + * @function Engine.isWebGLAvailable + */ + Engine.isWebGLAvailable = function (majorVersion = 1) { + try { + return !!document.createElement('canvas').getContext(['webgl', 'webgl2'][majorVersion - 1]); + } catch (e) { /* Not available */ } + return false; }; - /** @type {function(...string):Object} */ - Engine.prototype.start = function () { - // Start from arguments. - const args = []; - for (let i = 0; i < arguments.length; i++) { - args.push(arguments[i]); - } - const me = this; - 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(); - if (!me.canvas) { - return Promise.reject(new Error('No canvas found in page')); + /** + * Safe Engine constructor, creates a new prototype for every new instance to avoid prototype pollution. + * @ignore + * @constructor + */ + function SafeEngine(initConfig) { + const proto = /** @lends Engine.prototype */ { + /** + * Initialize the engine instance. Optionally, pass the base path to the engine to load it, + * if it hasn't been loaded yet. See :js:meth:`Engine.load`. + * + * @param {string=} basePath Base path of the engine to load. + * @return {Promise} A ``Promise`` that resolves once the engine is loaded and initialized. + */ + init: function (basePath) { + if (initPromise) { + return initPromise; } - } - - // 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); + 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; + } + Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]); + } + const me = this; + function doInit(promise) { + // Care! Promise chaining is bogus with old emscripten versions. + // This caused a regression with the Mono build (which uses an older emscripten version). + // Make sure to test that when refactoring. + return new Promise(function (resolve, reject) { + promise.then(function (response) { + const cloned = new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] }); + Godot(me.config.getModuleConfig(loadPath, cloned)).then(function (module) { + const paths = me.config.persistentPaths; + module['initFS'](paths).then(function (err) { + me.rtenv = module; + if (me.config.unloadAfterInit) { + Engine.unload(); + } + resolve(); + }); + }); + }); + }); + } + preloader.setProgressFunc(this.config.onProgress); + initPromise = doInit(loadPromise); + return initPromise; + }, - // 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'); // eslint-disable-line no-alert - ev.preventDefault(); - }, false); + /** + * Load a file so it is available in the instance's file system once it runs. Must be called **before** starting the + * instance. + * + * If not provided, the ``path`` is derived from the URL of the loaded file. + * + * @param {string|ArrayBuffer} file The file to preload. + * + * If a ``string`` the file will be loaded from that path. + * + * If an ``ArrayBuffer`` or a view on one, the buffer will used as the content of the file. + * + * @param {string=} path Path by which the file will be accessible. Required, if ``file`` is not a string. + * + * @returns {Promise} A Promise that resolves once the file is loaded. + */ + preloadFile: function (file, path) { + return preloader.preload(file, path, this.config.fileSizes[file]); + }, - // Browser locale, or custom one if defined. - let locale = me.customLocale; - if (!locale) { - locale = navigator.languages ? navigator.languages[0] : navigator.language; - locale = locale.split('.')[0]; - } - // Godot configuration. - me.rtenv['initConfig']({ - 'resizeCanvasOnStart': me.resizeCanvasOnStart, - 'canvas': me.canvas, - 'locale': locale, - 'onExecute': function (p_args) { - if (me.onExecute) { - me.onExecute(p_args); - return 0; + /** + * Start the engine instance using the given override configuration (if any). + * :js:meth:`startGame <Engine.prototype.startGame>` can be used in typical cases instead. + * + * This will initialize the instance if it is not initialized. For manual initialization, see :js:meth:`init <Engine.prototype.init>`. + * The engine must be loaded beforehand. + * + * Fails if a canvas cannot be found on the page, or not specified in the configuration. + * + * @param {EngineConfig} override An optional configuration override. + * @return {Promise} Promise that resolves once the engine started. + */ + start: function (override) { + this.config.update(override); + const me = this; + return me.init().then(function () { + if (!me.rtenv) { + return Promise.reject(new Error('The engine must be initialized before it can be started')); } - return 1; - }, - 'onExit': function (p_code) { - me.rtenv['deinitFS'](); - if (me.onExit) { - me.onExit(p_code); + + let config = {}; + try { + config = me.config.getGodotConfig(function () { + me.rtenv = null; + }); + } catch (e) { + return Promise.reject(e); } - me.rtenv = null; - }, - }); + // Godot configuration. + me.rtenv['initConfig'](config); - return new Promise(function (resolve, reject) { - preloader.preloadedFiles.forEach(function (file) { - me.rtenv['copyToFS'](file.path, file.buffer); + // Preload GDNative libraries. + const libs = []; + me.config.gdnativeLibs.forEach(function (lib) { + libs.push(me.rtenv['loadDynamicLibrary'](lib, { 'loadAsync': true })); + }); + return Promise.all(libs).then(function () { + 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'](me.config.args); + initPromise = null; + resolve(); + }); + }); }); - preloader.preloadedFiles.length = 0; // Clear memory - me.rtenv['callMain'](args); - initPromise = null; - resolve(); - }); - }); - }; + }, - Engine.prototype.startGame = function (execName, mainPack, extraArgs) { - // Start and init with execName as loadPath if not inited. - this.executableName = execName; - const me = this; - return Promise.all([ - this.init(execName), - this.preloadFile(mainPack, mainPack), - ]).then(function () { - let args = ['--main-pack', mainPack]; - if (extraArgs) { - args = args.concat(extraArgs); - } - return me.start.apply(me, args); - }); - }; - - Engine.prototype.setWebAssemblyFilenameExtension = function (override) { - if (String(override).length === 0) { - throw new Error('Invalid WebAssembly filename extension override'); - } - wasmExt = String(override); - }; - - Engine.prototype.setUnloadAfterInit = function (enabled) { - unloadAfterInit = enabled; - }; - - Engine.prototype.setCanvas = function (canvasElem) { - this.canvas = canvasElem; - }; - - Engine.prototype.setCanvasResizedOnStart = function (enabled) { - this.resizeCanvasOnStart = enabled; - }; - - Engine.prototype.setLocale = function (locale) { - this.customLocale = locale; - }; - - Engine.prototype.setExecutableName = function (newName) { - this.executableName = newName; - }; - - Engine.prototype.setProgressFunc = function (func) { - progressFunc = func; - }; + /** + * Start the game instance using the given configuration override (if any). + * + * This will initialize the instance if it is not initialized. For manual initialization, see :js:meth:`init <Engine.prototype.init>`. + * + * This will load the engine if it is not loaded, and preload the main pck. + * + * This method expects the initial config (or the override) to have both the :js:attr:`executable` and :js:attr:`mainPack` + * properties set (normally done by the editor during export). + * + * @param {EngineConfig} override An optional configuration override. + * @return {Promise} Promise that resolves once the game started. + */ + startGame: function (override) { + this.config.update(override); + // Add main-pack argument. + const exe = this.config.executable; + const pack = this.config.mainPack || `${exe}.pck`; + this.config.args = ['--main-pack', pack].concat(this.config.args); + // Start and init with execName as loadPath if not inited. + const me = this; + return Promise.all([ + this.init(exe), + this.preloadFile(pack, pack), + ]).then(function () { + return me.start.apply(me); + }); + }, - Engine.prototype.setStdoutFunc = function (func) { - const print = function (text) { - let msg = text; - if (arguments.length > 1) { - msg = Array.prototype.slice.call(arguments).join(' '); - } - func(msg); - }; - if (this.rtenv) { - this.rtenv.print = print; - } - stdout = print; - }; + /** + * Create a file at the specified ``path`` with the passed as ``buffer`` in the instance's file system. + * + * @param {string} path The location where the file will be created. + * @param {ArrayBuffer} buffer The content of the file. + */ + copyToFS: function (path, buffer) { + if (this.rtenv == null) { + throw new Error('Engine must be inited before copying files'); + } + this.rtenv['copyToFS'](path, buffer); + }, - Engine.prototype.setStderrFunc = function (func) { - const printErr = function (text) { - let msg = text; - if (arguments.length > 1) { - msg = Array.prototype.slice.call(arguments).join(' '); - } - func(msg); + /** + * Request that the current instance quit. + * + * This is akin the user pressing the close button in the window manager, and will + * have no effect if the engine has crashed, or is stuck in a loop. + * + */ + requestQuit: function () { + if (this.rtenv) { + this.rtenv['request_quit'](); + } + }, }; - if (this.rtenv) { - this.rtenv.printErr = printErr; - } - stderr = printErr; - }; - - Engine.prototype.setOnExecute = function (onExecute) { - this.onExecute = onExecute; - }; - - Engine.prototype.setOnExit = function (onExit) { - this.onExit = onExit; - }; - - Engine.prototype.copyToFS = function (path, buffer) { - if (this.rtenv == null) { - throw new Error('Engine must be inited before copying files'); - } - this.rtenv['copyToFS'](path, buffer); - }; - - Engine.prototype.setPersistentPaths = function (persistentPaths) { - this.persistentPaths = persistentPaths; - }; - Engine.prototype.setGDNativeLibraries = function (gdnativeLibs) { - this.gdnativeLibs = gdnativeLibs; - }; + Engine.prototype = proto; + // Closure compiler exported instance methods. + 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['copyToFS'] = Engine.prototype.copyToFS; + Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; + // Also expose static methods as instance methods + Engine.prototype['load'] = Engine.load; + Engine.prototype['unload'] = Engine.unload; + Engine.prototype['isWebGLAvailable'] = Engine.isWebGLAvailable; + return new Engine(initConfig); + } - Engine.prototype.requestQuit = function () { - if (this.rtenv) { - this.rtenv['request_quit'](); - } - }; + // Closure compiler exported static methods. + SafeEngine['load'] = Engine.load; + SafeEngine['unload'] = Engine.unload; + SafeEngine['isWebGLAvailable'] = Engine.isWebGLAvailable; - // 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['setOnExecute'] = Engine.prototype.setOnExecute; - Engine.prototype['setOnExit'] = Engine.prototype.setOnExit; - Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; - Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths; - Engine.prototype['setGDNativeLibraries'] = Engine.prototype.setGDNativeLibraries; - Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; - return Engine; + return SafeEngine; }()); if (typeof window !== 'undefined') { window['Engine'] = Engine; diff --git a/platform/javascript/js/engine/preloader.js b/platform/javascript/js/engine/preloader.js index ec34fb93f2..564c68d264 100644 --- a/platform/javascript/js/engine/preloader.js +++ b/platform/javascript/js/engine/preloader.js @@ -1,54 +1,62 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars - const loadXHR = function (resolve, reject, file, tracker, attempts) { - const xhr = new XMLHttpRequest(); + function getTrackedResponse(response, load_status) { + function onloadprogress(reader, controller) { + return reader.read().then(function (result) { + if (load_status.done) { + return Promise.resolve(); + } + if (result.value) { + controller.enqueue(result.value); + load_status.loaded += result.value.length; + } + if (!result.done) { + return onloadprogress(reader, controller); + } + load_status.done = true; + return Promise.resolve(); + }); + } + const reader = response.body.getReader(); + return new Response(new ReadableStream({ + start: function (controller) { + onloadprogress(reader, controller).then(function () { + controller.close(); + }); + }, + }), { headers: response.headers }); + } + + function loadFetch(file, tracker, fileSize, raw) { tracker[file] = { - total: 0, + total: fileSize || 0, loaded: 0, - final: false, + done: false, }; - xhr.onerror = function () { + return fetch(file).then(function (response) { + if (!response.ok) { + return Promise.reject(new Error(`Failed loading file '${file}'`)); + } + const tr = getTrackedResponse(response, tracker[file]); + if (raw) { + return Promise.resolve(tr); + } + return tr.arrayBuffer(); + }); + } + + function retry(func, attempts = 1) { + function onerror(err) { if (attempts <= 1) { - reject(new Error(`Failed loading file '${file}'`)); - } else { + return Promise.reject(err); + } + return new Promise(function (resolve, reject) { setTimeout(function () { - loadXHR(resolve, reject, file, tracker, attempts - 1); + retry(func, attempts - 1).then(resolve).catch(reject); }, 1000); - } - }; - xhr.onabort = function () { - tracker[file].final = true; - reject(new Error(`Loading file '${file}' was aborted.`)); - }; - xhr.onloadstart = function (ev) { - tracker[file].total = ev.total; - tracker[file].loaded = ev.loaded; - }; - xhr.onprogress = function (ev) { - tracker[file].loaded = ev.loaded; - tracker[file].total = ev.total; - }; - xhr.onload = function () { - if (xhr.status >= 400) { - if (xhr.status < 500 || attempts <= 1) { - reject(new Error(`Failed loading file '${file}': ${xhr.statusText}`)); - xhr.abort(); - } else { - setTimeout(function () { - loadXHR(resolve, reject, file, tracker, attempts - 1); - }, 1000); - } - } else { - tracker[file].final = true; - resolve(xhr); - } - }; - // Make request. - xhr.open('GET', file); - if (!file.endsWith('.js')) { - xhr.responseType = 'arraybuffer'; + }); } - xhr.send(); - }; + return func().catch(onerror); + } const DOWNLOAD_ATTEMPTS_MAX = 4; const loadingFiles = {}; @@ -63,7 +71,7 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un Object.keys(loadingFiles).forEach(function (file) { const stat = loadingFiles[file]; - if (!stat.final) { + if (!stat.done) { progressIsFinal = false; } if (!totalIsValid || stat.total === 0) { @@ -92,21 +100,19 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un progressFunc = callback; }; - this.loadPromise = function (file) { - return new Promise(function (resolve, reject) { - loadXHR(resolve, reject, file, loadingFiles, DOWNLOAD_ATTEMPTS_MAX); - }); + this.loadPromise = function (file, fileSize, raw = false) { + return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX); }; this.preloadedFiles = []; - this.preload = function (pathOrBuffer, destPath) { + this.preload = function (pathOrBuffer, destPath, fileSize) { let buffer = null; if (typeof pathOrBuffer === 'string') { const me = this; - return this.loadPromise(pathOrBuffer).then(function (xhr) { + return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) { me.preloadedFiles.push({ path: destPath || pathOrBuffer, - buffer: xhr.response, + buffer: buf, }); return Promise.resolve(); }); diff --git a/platform/javascript/js/engine/utils.js b/platform/javascript/js/engine/utils.js deleted file mode 100644 index 9273bbad42..0000000000 --- a/platform/javascript/js/engine/utils.js +++ /dev/null @@ -1,58 +0,0 @@ -const Utils = { // eslint-disable-line no-unused-vars - - createLocateRewrite: function (execName) { - function rw(path) { - if (path.endsWith('.worker.js')) { - return `${execName}.worker.js`; - } else if (path.endsWith('.audio.worklet.js')) { - return `${execName}.audio.worklet.js`; - } else if (path.endsWith('.js')) { - return `${execName}.js`; - } else if (path.endsWith('.side.wasm')) { - return `${execName}.side.wasm`; - } else if (path.endsWith('.wasm')) { - return `${execName}.wasm`; - } - return path; - } - return rw; - }, - - createInstantiatePromise: function (wasmLoader) { - let loader = wasmLoader; - function instantiateWasm(imports, onSuccess) { - loader.then(function (xhr) { - WebAssembly.instantiate(xhr.response, imports).then(function (result) { - onSuccess(result['instance'], result['module']); - }); - }); - loader = null; - return {}; - } - - return instantiateWasm; - }, - - findCanvas: function () { - const nodes = document.getElementsByTagName('canvas'); - if (nodes.length && nodes[0] instanceof HTMLCanvasElement) { - return nodes[0]; - } - return null; - }, - - isWebGLAvailable: function (majorVersion = 1) { - let testContext = false; - try { - const testCanvas = document.createElement('canvas'); - if (majorVersion === 1) { - testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl'); - } else if (majorVersion === 2) { - testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2'); - } - } catch (e) { - // Not available - } - return !!testContext; - }, -}; diff --git a/platform/javascript/js/jsdoc2rst/publish.js b/platform/javascript/js/jsdoc2rst/publish.js new file mode 100644 index 0000000000..ad9c0fbaaa --- /dev/null +++ b/platform/javascript/js/jsdoc2rst/publish.js @@ -0,0 +1,354 @@ +/* eslint-disable strict */ + +'use strict'; + +const fs = require('fs'); + +class JSDoclet { + constructor(doc) { + this.doc = doc; + this.description = doc['description'] || ''; + this.name = doc['name'] || 'unknown'; + this.longname = doc['longname'] || ''; + this.types = []; + if (doc['type'] && doc['type']['names']) { + this.types = doc['type']['names'].slice(); + } + this.type = this.types.length > 0 ? this.types.join('\\|') : '*'; + this.variable = doc['variable'] || false; + this.kind = doc['kind'] || ''; + this.memberof = doc['memberof'] || null; + this.scope = doc['scope'] || ''; + this.members = []; + this.optional = doc['optional'] || false; + this.defaultvalue = doc['defaultvalue']; + this.summary = doc['summary'] || null; + this.classdesc = doc['classdesc'] || null; + + // Parameters (functions) + this.params = []; + this.returns = doc['returns'] ? doc['returns'][0]['type']['names'][0] : 'void'; + this.returns_desc = doc['returns'] ? doc['returns'][0]['description'] : null; + + this.params = (doc['params'] || []).slice().map((p) => new JSDoclet(p)); + + // Custom tags + this.tags = doc['tags'] || []; + this.header = this.tags.filter((t) => t['title'] === 'header').map((t) => t['text']).pop() || null; + } + + add_member(obj) { + this.members.push(obj); + } + + is_static() { + return this.scope === 'static'; + } + + is_instance() { + return this.scope === 'instance'; + } + + is_object() { + return this.kind === 'Object' || (this.kind === 'typedef' && this.type === 'Object'); + } + + is_class() { + return this.kind === 'class'; + } + + is_function() { + return this.kind === 'function' || (this.kind === 'typedef' && this.type === 'function'); + } + + is_module() { + return this.kind === 'module'; + } +} + +function format_table(f, data, depth = 0) { + if (!data.length) { + return; + } + + const column_sizes = new Array(data[0].length).fill(0); + + data.forEach((row) => { + row.forEach((e, idx) => { + column_sizes[idx] = Math.max(e.length, column_sizes[idx]); + }); + }); + + const indent = ' '.repeat(depth); + let sep = indent; + column_sizes.forEach((size) => { + sep += '+'; + sep += '-'.repeat(size + 2); + }); + sep += '+\n'; + f.write(sep); + + data.forEach((row) => { + let row_text = `${indent}|`; + row.forEach((entry, idx) => { + row_text += ` ${entry.padEnd(column_sizes[idx])} |`; + }); + row_text += '\n'; + f.write(row_text); + f.write(sep); + }); + + f.write('\n'); +} + +function make_header(header, sep) { + return `${header}\n${sep.repeat(header.length)}\n\n`; +} + +function indent_multiline(text, depth) { + const indent = ' '.repeat(depth); + return text.split('\n').map((l) => (l === '' ? l : indent + l)).join('\n'); +} + +function make_rst_signature(obj, types = false, style = false) { + let out = ''; + const fmt = style ? '*' : ''; + obj.params.forEach((arg, idx) => { + if (idx > 0) { + if (arg.optional) { + out += ` ${fmt}[`; + } + out += ', '; + } else { + out += ' '; + if (arg.optional) { + out += `${fmt}[ `; + } + } + if (types) { + out += `${arg.type} `; + } + const variable = arg.variable ? '...' : ''; + const defval = arg.defaultvalue !== undefined ? `=${arg.defaultvalue}` : ''; + out += `${variable}${arg.name}${defval}`; + if (arg.optional) { + out += ` ]${fmt}`; + } + }); + out += ' '; + return out; +} + +function make_rst_param(f, obj, depth = 0) { + const indent = ' '.repeat(depth * 3); + f.write(indent); + f.write(`:param ${obj.type} ${obj.name}:\n`); + f.write(indent_multiline(obj.description, (depth + 1) * 3)); + f.write('\n\n'); +} + +function make_rst_attribute(f, obj, depth = 0, brief = false) { + const indent = ' '.repeat(depth * 3); + f.write(indent); + f.write(`.. js:attribute:: ${obj.name}\n\n`); + + if (brief) { + if (obj.summary) { + f.write(indent_multiline(obj.summary, (depth + 1) * 3)); + } + f.write('\n\n'); + return; + } + + f.write(indent_multiline(obj.description, (depth + 1) * 3)); + f.write('\n\n'); + + f.write(indent); + f.write(` :type: ${obj.type}\n\n`); + + if (obj.defaultvalue !== undefined) { + let defval = obj.defaultvalue; + if (defval === '') { + defval = '""'; + } + f.write(indent); + f.write(` :value: \`\`${defval}\`\`\n\n`); + } +} + +function make_rst_function(f, obj, depth = 0) { + let prefix = ''; + if (obj.is_instance()) { + prefix = 'prototype.'; + } + + const indent = ' '.repeat(depth * 3); + const sig = make_rst_signature(obj); + f.write(indent); + f.write(`.. js:function:: ${prefix}${obj.name}(${sig})\n`); + f.write('\n'); + + f.write(indent_multiline(obj.description, (depth + 1) * 3)); + f.write('\n\n'); + + obj.params.forEach((param) => { + make_rst_param(f, param, depth + 1); + }); + + if (obj.returns !== 'void') { + f.write(indent); + f.write(' :return:\n'); + f.write(indent_multiline(obj.returns_desc, (depth + 2) * 3)); + f.write('\n\n'); + f.write(indent); + f.write(` :rtype: ${obj.returns}\n\n`); + } +} + +function make_rst_object(f, obj) { + let brief = false; + // Our custom header flag. + if (obj.header !== null) { + f.write(make_header(obj.header, '-')); + f.write(`${obj.description}\n\n`); + brief = true; + } + + // Format members table and descriptions + const data = [['type', 'name']].concat(obj.members.map((m) => [m.type, `:js:attr:\`${m.name}\``])); + + f.write(make_header('Properties', '^')); + format_table(f, data, 0); + + make_rst_attribute(f, obj, 0, brief); + + if (!obj.members.length) { + return; + } + + f.write(' **Property Descriptions**\n\n'); + + // Properties first + obj.members.filter((m) => !m.is_function()).forEach((m) => { + make_rst_attribute(f, m, 1); + }); + + // Callbacks last + obj.members.filter((m) => m.is_function()).forEach((m) => { + make_rst_function(f, m, 1); + }); +} + +function make_rst_class(f, obj) { + const header = obj.header ? obj.header : obj.name; + f.write(make_header(header, '-')); + + if (obj.classdesc) { + f.write(`${obj.classdesc}\n\n`); + } + + const funcs = obj.members.filter((m) => m.is_function()); + function make_data(m) { + const base = m.is_static() ? obj.name : `${obj.name}.prototype`; + const params = make_rst_signature(m, true, true); + const sig = `:js:attr:\`${m.name} <${base}.${m.name}>\` **(**${params}**)**`; + return [m.returns, sig]; + } + const sfuncs = funcs.filter((m) => m.is_static()); + const ifuncs = funcs.filter((m) => !m.is_static()); + + f.write(make_header('Static Methods', '^')); + format_table(f, sfuncs.map((m) => make_data(m))); + + f.write(make_header('Instance Methods', '^')); + format_table(f, ifuncs.map((m) => make_data(m))); + + const sig = make_rst_signature(obj); + f.write(`.. js:class:: ${obj.name}(${sig})\n\n`); + f.write(indent_multiline(obj.description, 3)); + f.write('\n\n'); + + obj.params.forEach((p) => { + make_rst_param(f, p, 1); + }); + + f.write(' **Static Methods**\n\n'); + sfuncs.forEach((m) => { + make_rst_function(f, m, 1); + }); + + f.write(' **Instance Methods**\n\n'); + ifuncs.forEach((m) => { + make_rst_function(f, m, 1); + }); +} + +function make_rst_module(f, obj) { + const header = obj.header !== null ? obj.header : obj.name; + f.write(make_header(header, '=')); + f.write(obj.description); + f.write('\n\n'); +} + +function write_base_object(f, obj) { + if (obj.is_object()) { + make_rst_object(f, obj); + } else if (obj.is_function()) { + make_rst_function(f, obj); + } else if (obj.is_class()) { + make_rst_class(f, obj); + } else if (obj.is_module()) { + make_rst_module(f, obj); + } +} + +function generate(f, docs) { + const globs = []; + const SYMBOLS = {}; + docs.filter((d) => !d.ignore && d.kind !== 'package').forEach((d) => { + SYMBOLS[d.name] = d; + if (d.memberof) { + const up = SYMBOLS[d.memberof]; + if (up === undefined) { + console.log(d); // eslint-disable-line no-console + console.log(`Undefined symbol! ${d.memberof}`); // eslint-disable-line no-console + throw new Error('Undefined symbol!'); + } + SYMBOLS[d.memberof].add_member(d); + } else { + globs.push(d); + } + }); + + f.write('.. _doc_html5_shell_classref:\n\n'); + globs.forEach((obj) => write_base_object(f, obj)); +} + +/** + * Generate documentation output. + * + * @param {TAFFY} data - A TaffyDB collection representing + * all the symbols documented in your code. + * @param {object} opts - An object with options information. + */ +exports.publish = function (data, opts) { + const docs = data().get().filter((doc) => !doc.undocumented && !doc.ignore).map((doc) => new JSDoclet(doc)); + const dest = opts.destination; + if (dest === 'dry-run') { + process.stdout.write('Dry run... '); + generate({ + write: function () { /* noop */ }, + }, docs); + process.stdout.write('Okay!\n'); + return; + } + if (dest !== '' && !dest.endsWith('.rst')) { + throw new Error('Destination file must be either a ".rst" file, or an empty string (for printing to stdout)'); + } + if (dest !== '') { + const f = fs.createWriteStream(dest); + generate(f, docs); + } else { + generate(process.stdout, docs); + } +}; diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index 8e385e9176..ac4055516c 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -238,6 +238,9 @@ const GodotAudioWorklet = { close: function () { return new Promise(function (resolve, reject) { + if (GodotAudioWorklet.promise === null) { + return; + } GodotAudioWorklet.promise.then(function () { GodotAudioWorklet.worklet.port.postMessage({ 'cmd': 'stop', @@ -247,7 +250,7 @@ const GodotAudioWorklet = { GodotAudioWorklet.worklet = null; GodotAudioWorklet.promise = null; resolve(); - }); + }).catch(function (err) { /* aborted? */ }); }); }, }, diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js index d1f4d9595b..91cab5eacc 100644 --- a/platform/javascript/js/libs/library_godot_display.js +++ b/platform/javascript/js/libs/library_godot_display.js @@ -192,33 +192,45 @@ const GodotDisplayDragDrop = { GodotDisplayDragDrop.promises = []; GodotDisplayDragDrop.pending_files = []; callback(drops); - const dirs = [DROP.substr(0, DROP.length - 1)]; - // Remove temporary files - files.forEach(function (file) { - FS.unlink(file); - let dir = file.replace(DROP, ''); - let idx = dir.lastIndexOf('/'); - while (idx > 0) { - dir = dir.substr(0, idx); - if (dirs.indexOf(DROP + dir) === -1) { - dirs.push(DROP + dir); - } - idx = dir.lastIndexOf('/'); - } - }); - // Remove dirs. - dirs.sort(function (a, b) { - const al = (a.match(/\//g) || []).length; - const bl = (b.match(/\//g) || []).length; - if (al > bl) { - return -1; - } else if (al < bl) { - return 1; + if (GodotConfig.persistent_drops) { + // Delay removal at exit. + GodotOS.atexit(function (resolve, reject) { + GodotDisplayDragDrop.remove_drop(files, DROP); + resolve(); + }); + } else { + GodotDisplayDragDrop.remove_drop(files, DROP); + } + }); + }, + + remove_drop: function (files, drop_path) { + const dirs = [drop_path.substr(0, drop_path.length - 1)]; + // Remove temporary files + files.forEach(function (file) { + FS.unlink(file); + let dir = file.replace(drop_path, ''); + let idx = dir.lastIndexOf('/'); + while (idx > 0) { + dir = dir.substr(0, idx); + if (dirs.indexOf(drop_path + dir) === -1) { + dirs.push(drop_path + dir); } - return 0; - }).forEach(function (dir) { - FS.rmdir(dir); - }); + idx = dir.lastIndexOf('/'); + } + }); + // Remove dirs. + dirs.sort(function (a, b) { + const al = (a.match(/\//g) || []).length; + const bl = (b.match(/\//g) || []).length; + if (al > bl) { + return -1; + } else if (al < bl) { + return 1; + } + return 0; + }).forEach(function (dir) { + FS.rmdir(dir); }); }, @@ -231,6 +243,105 @@ const GodotDisplayDragDrop = { }; mergeInto(LibraryManager.library, GodotDisplayDragDrop); +const GodotDisplayVK = { + + $GodotDisplayVK__deps: ['$GodotRuntime', '$GodotConfig', '$GodotDisplayListeners'], + $GodotDisplayVK__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayVK.clear(); resolve(); });', + $GodotDisplayVK: { + textinput: null, + textarea: null, + + available: function () { + return GodotConfig.virtual_keyboard && 'ontouchstart' in window; + }, + + init: function (input_cb) { + function create(what) { + const elem = document.createElement(what); + elem.style.display = 'none'; + elem.style.position = 'absolute'; + elem.style.zIndex = '-1'; + elem.style.background = 'transparent'; + elem.style.padding = '0px'; + elem.style.margin = '0px'; + elem.style.overflow = 'hidden'; + elem.style.width = '0px'; + elem.style.height = '0px'; + elem.style.border = '0px'; + elem.style.outline = 'none'; + elem.readonly = true; + elem.disabled = true; + GodotDisplayListeners.add(elem, 'input', function (evt) { + const c_str = GodotRuntime.allocString(elem.value); + input_cb(c_str, elem.selectionEnd); + GodotRuntime.free(c_str); + }, false); + GodotDisplayListeners.add(elem, 'blur', function (evt) { + elem.style.display = 'none'; + elem.readonly = true; + elem.disabled = true; + }, false); + GodotConfig.canvas.insertAdjacentElement('beforebegin', elem); + return elem; + } + GodotDisplayVK.textinput = create('input'); + GodotDisplayVK.textarea = create('textarea'); + GodotDisplayVK.updateSize(); + }, + show: function (text, multiline, start, end) { + if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) { + return; + } + if (GodotDisplayVK.textinput.style.display !== '' || GodotDisplayVK.textarea.style.display !== '') { + GodotDisplayVK.hide(); + } + GodotDisplayVK.updateSize(); + const elem = multiline ? GodotDisplayVK.textarea : GodotDisplayVK.textinput; + elem.readonly = false; + elem.disabled = false; + elem.value = text; + elem.style.display = 'block'; + elem.focus(); + elem.setSelectionRange(start, end); + }, + hide: function () { + if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) { + return; + } + [GodotDisplayVK.textinput, GodotDisplayVK.textarea].forEach(function (elem) { + elem.blur(); + elem.style.display = 'none'; + elem.value = ''; + }); + }, + updateSize: function () { + if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) { + return; + } + const rect = GodotConfig.canvas.getBoundingClientRect(); + function update(elem) { + elem.style.left = `${rect.left}px`; + elem.style.top = `${rect.top}px`; + elem.style.width = `${rect.width}px`; + elem.style.height = `${rect.height}px`; + } + update(GodotDisplayVK.textinput); + update(GodotDisplayVK.textarea); + }, + clear: function () { + if (GodotDisplayVK.textinput) { + GodotDisplayVK.textinput.remove(); + GodotDisplayVK.textinput = null; + } + if (GodotDisplayVK.textarea) { + GodotDisplayVK.textarea.remove(); + GodotDisplayVK.textarea = null; + } + }, + }, +}; +mergeInto(LibraryManager.library, GodotDisplayVK); + /* * Display server cursor helper. * Keeps track of cursor status and custom shapes. @@ -269,15 +380,272 @@ const GodotDisplayCursor = { }; mergeInto(LibraryManager.library, GodotDisplayCursor); +/* + * Display Gamepad API helper. + */ +const GodotDisplayGamepads = { + $GodotDisplayGamepads__deps: ['$GodotRuntime', '$GodotDisplayListeners'], + $GodotDisplayGamepads: { + samples: [], + + get_pads: function () { + try { + // Will throw in iframe when permission is denied. + // Will throw/warn in the future for insecure contexts. + // See https://github.com/w3c/gamepad/pull/120 + const pads = navigator.getGamepads(); + if (pads) { + return pads; + } + return []; + } catch (e) { + return []; + } + }, + + get_samples: function () { + return GodotDisplayGamepads.samples; + }, + + get_sample: function (index) { + const samples = GodotDisplayGamepads.samples; + return index < samples.length ? samples[index] : null; + }, + + sample: function () { + const pads = GodotDisplayGamepads.get_pads(); + const samples = []; + for (let i = 0; i < pads.length; i++) { + const pad = pads[i]; + if (!pad) { + samples.push(null); + continue; + } + const s = { + standard: pad.mapping === 'standard', + buttons: [], + axes: [], + connected: pad.connected, + }; + for (let b = 0; b < pad.buttons.length; b++) { + s.buttons.push(pad.buttons[b].value); + } + for (let a = 0; a < pad.axes.length; a++) { + s.axes.push(pad.axes[a]); + } + samples.push(s); + } + GodotDisplayGamepads.samples = samples; + }, + + init: function (onchange) { + GodotDisplayListeners.samples = []; + function add(pad) { + const guid = GodotDisplayGamepads.get_guid(pad); + const c_id = GodotRuntime.allocString(pad.id); + const c_guid = GodotRuntime.allocString(guid); + onchange(pad.index, 1, c_id, c_guid); + GodotRuntime.free(c_id); + GodotRuntime.free(c_guid); + } + const pads = GodotDisplayGamepads.get_pads(); + for (let i = 0; i < pads.length; i++) { + // Might be reserved space. + if (pads[i]) { + add(pads[i]); + } + } + GodotDisplayListeners.add(window, 'gamepadconnected', function (evt) { + add(evt.gamepad); + }, false); + GodotDisplayListeners.add(window, 'gamepaddisconnected', function (evt) { + onchange(evt.gamepad.index, 0); + }, false); + }, + + get_guid: function (pad) { + if (pad.mapping) { + return pad.mapping; + } + const ua = navigator.userAgent; + let os = 'Unknown'; + if (ua.indexOf('Android') >= 0) { + os = 'Android'; + } else if (ua.indexOf('Linux') >= 0) { + os = 'Linux'; + } else if (ua.indexOf('iPhone') >= 0) { + os = 'iOS'; + } else if (ua.indexOf('Macintosh') >= 0) { + // Updated iPads will fall into this category. + os = 'MacOSX'; + } else if (ua.indexOf('Windows') >= 0) { + os = 'Windows'; + } + + const id = pad.id; + // Chrom* style: NAME (Vendor: xxxx Product: xxxx) + const exp1 = /vendor: ([0-9a-f]{4}) product: ([0-9a-f]{4})/i; + // Firefox/Safari style (safari may remove leading zeores) + const exp2 = /^([0-9a-f]+)-([0-9a-f]+)-/i; + let vendor = ''; + let product = ''; + if (exp1.test(id)) { + const match = exp1.exec(id); + vendor = match[1].padStart(4, '0'); + product = match[2].padStart(4, '0'); + } else if (exp2.test(id)) { + const match = exp2.exec(id); + vendor = match[1].padStart(4, '0'); + product = match[2].padStart(4, '0'); + } + if (!vendor || !product) { + return `${os}Unknown`; + } + return os + vendor + product; + }, + }, +}; +mergeInto(LibraryManager.library, GodotDisplayGamepads); + +const GodotDisplayScreen = { + $GodotDisplayScreen__deps: ['$GodotConfig', '$GodotOS', '$GL', 'emscripten_webgl_get_current_context'], + $GodotDisplayScreen: { + desired_size: [0, 0], + hidpi: true, + getPixelRatio: function () { + return GodotDisplayScreen.hidpi ? window.devicePixelRatio || 1 : 1; + }, + isFullscreen: function () { + const elem = document.fullscreenElement || document.mozFullscreenElement + || document.webkitFullscreenElement || document.msFullscreenElement; + if (elem) { + return elem === GodotConfig.canvas; + } + // But maybe knowing the element is not supported. + return document.fullscreen || document.mozFullScreen + || document.webkitIsFullscreen; + }, + hasFullscreen: function () { + return document.fullscreenEnabled || document.mozFullScreenEnabled + || document.webkitFullscreenEnabled; + }, + requestFullscreen: function () { + if (!GodotDisplayScreen.hasFullscreen()) { + return 1; + } + const canvas = GodotConfig.canvas; + try { + const promise = (canvas.requestFullscreen || canvas.msRequestFullscreen + || canvas.mozRequestFullScreen || canvas.mozRequestFullscreen + || canvas.webkitRequestFullscreen + ).call(canvas); + // Some browsers (Safari) return undefined. + // For the standard ones, we need to catch it. + if (promise) { + promise.catch(function () { + // nothing to do. + }); + } + } catch (e) { + return 1; + } + return 0; + }, + exitFullscreen: function () { + if (!GodotDisplayScreen.isFullscreen()) { + return 0; + } + try { + const promise = document.exitFullscreen(); + if (promise) { + promise.catch(function () { + // nothing to do. + }); + } + } catch (e) { + return 1; + } + return 0; + }, + _updateGL: function () { + const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef + const gl = GL.getContext(gl_context_handle); + if (gl) { + GL.resizeOffscreenFramebuffer(gl); + } + }, + updateSize: function () { + const isFullscreen = GodotDisplayScreen.isFullscreen(); + const wantsFullWindow = GodotConfig.canvas_resize_policy === 2; + const noResize = GodotConfig.canvas_resize_policy === 0; + const wwidth = GodotDisplayScreen.desired_size[0]; + const wheight = GodotDisplayScreen.desired_size[1]; + const canvas = GodotConfig.canvas; + let width = wwidth; + let height = wheight; + if (noResize) { + // Don't resize canvas, just update GL if needed. + if (canvas.width !== width || canvas.height !== height) { + GodotDisplayScreen.desired_size = [canvas.width, canvas.height]; + GodotDisplayScreen._updateGL(); + return 1; + } + return 0; + } + const scale = GodotDisplayScreen.getPixelRatio(); + if (isFullscreen || wantsFullWindow) { + // We need to match screen size. + width = window.innerWidth * scale; + height = window.innerHeight * scale; + } + const csw = `${width / scale}px`; + const csh = `${height / scale}px`; + if (canvas.style.width !== csw || canvas.style.height !== csh || canvas.width !== width || canvas.height !== height) { + // Size doesn't match. + // Resize canvas, set correct CSS pixel size, update GL. + canvas.width = width; + canvas.height = height; + canvas.style.width = csw; + canvas.style.height = csh; + GodotDisplayScreen._updateGL(); + return 1; + } + return 0; + }, + }, +}; +mergeInto(LibraryManager.library, GodotDisplayScreen); + /** * Display server interface. * * Exposes all the functions needed by DisplayServer implementation. */ const GodotDisplay = { - $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop'], + $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop', '$GodotDisplayGamepads', '$GodotDisplayScreen', '$GodotDisplayVK'], $GodotDisplay: { window_icon: '', + findDPI: function () { + function testDPI(dpi) { + return window.matchMedia(`(max-resolution: ${dpi}dpi)`).matches; + } + function bisect(low, high, func) { + const mid = parseInt(((high - low) / 2) + low, 10); + if (high - low <= 1) { + return func(high) ? high : low; + } + if (func(mid)) { + return bisect(low, mid, func); + } + return bisect(mid, high, func); + } + try { + const dpi = bisect(0, 800, testDPI); + return dpi >= 96 ? dpi : 96; + } catch (e) { + return 96; + } + }, }, godot_js_display_is_swap_ok_cancel__sig: 'i', @@ -295,9 +663,71 @@ const GodotDisplay = { window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert }, + godot_js_display_screen_dpi_get__sig: 'i', + godot_js_display_screen_dpi_get: function () { + return GodotDisplay.findDPI(); + }, + godot_js_display_pixel_ratio_get__sig: 'f', godot_js_display_pixel_ratio_get: function () { - return window.devicePixelRatio || 1; + return GodotDisplayScreen.getPixelRatio(); + }, + + godot_js_display_fullscreen_request__sig: 'i', + godot_js_display_fullscreen_request: function () { + return GodotDisplayScreen.requestFullscreen(); + }, + + godot_js_display_fullscreen_exit__sig: 'i', + godot_js_display_fullscreen_exit: function () { + return GodotDisplayScreen.exitFullscreen(); + }, + + godot_js_display_desired_size_set__sig: 'v', + godot_js_display_desired_size_set: function (width, height) { + GodotDisplayScreen.desired_size = [width, height]; + GodotDisplayScreen.updateSize(); + }, + + godot_js_display_size_update__sig: 'i', + godot_js_display_size_update: function () { + const updated = GodotDisplayScreen.updateSize(); + if (updated) { + GodotDisplayVK.updateSize(); + } + return updated; + }, + + godot_js_display_screen_size_get__sig: 'vii', + godot_js_display_screen_size_get: function (width, height) { + const scale = GodotDisplayScreen.getPixelRatio(); + GodotRuntime.setHeapValue(width, window.screen.width * scale, 'i32'); + GodotRuntime.setHeapValue(height, window.screen.height * scale, 'i32'); + }, + + godot_js_display_window_size_get: function (p_width, p_height) { + GodotRuntime.setHeapValue(p_width, GodotConfig.canvas.width, 'i32'); + GodotRuntime.setHeapValue(p_height, GodotConfig.canvas.height, 'i32'); + }, + + godot_js_display_compute_position: function (x, y, r_x, r_y) { + const canvas = GodotConfig.canvas; + const rect = canvas.getBoundingClientRect(); + const rw = canvas.width / rect.width; + const rh = canvas.height / rect.height; + GodotRuntime.setHeapValue(r_x, (x - rect.x) * rw, 'i32'); + GodotRuntime.setHeapValue(r_y, (y - rect.y) * rh, 'i32'); + }, + + godot_js_display_has_webgl__sig: 'ii', + godot_js_display_has_webgl: function (p_version) { + if (p_version !== 1 && p_version !== 2) { + return false; + } + try { + return !!document.createElement('canvas').getContext(p_version === 2 ? 'webgl2' : 'webgl'); + } catch (e) { /* Not available */ } + return false; }, /* @@ -313,13 +743,6 @@ const GodotDisplay = { return document.activeElement === GodotConfig.canvas; }, - godot_js_display_canvas_bounding_rect_position_get__sig: 'vii', - godot_js_display_canvas_bounding_rect_position_get: function (r_x, r_y) { - const brect = GodotConfig.canvas.getBoundingClientRect(); - GodotRuntime.setHeapValue(r_x, brect.x, 'i32'); - GodotRuntime.setHeapValue(r_y, brect.y, 'i32'); - }, - /* * Touchscreen */ @@ -363,15 +786,6 @@ const GodotDisplay = { /* * Window */ - godot_js_display_window_request_fullscreen__sig: 'v', - godot_js_display_window_request_fullscreen: function () { - const canvas = GodotConfig.canvas; - (canvas.requestFullscreen || canvas.msRequestFullscreen - || canvas.mozRequestFullScreen || canvas.mozRequestFullscreen - || canvas.webkitRequestFullscreen - ).call(canvas); - }, - godot_js_display_window_title_set__sig: 'vi', godot_js_display_window_title_set: function (p_data) { document.title = GodotRuntime.parseString(p_data); @@ -387,7 +801,7 @@ const GodotDisplay = { document.head.appendChild(link); } const old_icon = GodotDisplay.window_icon; - const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); + const png = new Blob([GodotRuntime.heapSlice(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); GodotDisplay.window_icon = URL.createObjectURL(png); link.href = GodotDisplay.window_icon; if (old_icon) { @@ -427,7 +841,7 @@ const GodotDisplay = { const shape = GodotRuntime.parseString(p_shape); const old_shape = GodotDisplayCursor.cursors[shape]; if (p_len > 0) { - const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); + const png = new Blob([GodotRuntime.heapSlice(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); const url = URL.createObjectURL(png); GodotDisplayCursor.cursors[shape] = { url: url, @@ -455,7 +869,7 @@ const GodotDisplay = { const notif = [p_enter, p_exit, p_in, p_out]; ['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function (evt_name, idx) { GodotDisplayListeners.add(canvas, evt_name, function () { - func.bind(null, notif[idx]); + func(notif[idx]); }, true); }); }, @@ -491,6 +905,109 @@ const GodotDisplay = { }, false); GodotDisplayListeners.add(canvas, 'drop', GodotDisplayDragDrop.handler(dropFiles)); }, + + godot_js_display_setup_canvas__sig: 'viiii', + godot_js_display_setup_canvas: function (p_width, p_height, p_fullscreen, p_hidpi) { + const canvas = GodotConfig.canvas; + GodotDisplayListeners.add(canvas, 'contextmenu', function (ev) { + ev.preventDefault(); + }, false); + GodotDisplayListeners.add(canvas, 'webglcontextlost', function (ev) { + alert('WebGL context lost, please reload the page'); // eslint-disable-line no-alert + ev.preventDefault(); + }, false); + GodotDisplayScreen.hidpi = !!p_hidpi; + switch (GodotConfig.canvas_resize_policy) { + case 0: // None + GodotDisplayScreen.desired_size = [canvas.width, canvas.height]; + break; + case 1: // Project + GodotDisplayScreen.desired_size = [p_width, p_height]; + break; + default: // Full window + // Ensure we display in the right place, the size will be handled by updateSize + canvas.style.position = 'absolute'; + canvas.style.top = 0; + canvas.style.left = 0; + break; + } + GodotDisplayScreen.updateSize(); + if (p_fullscreen) { + GodotDisplayScreen.requestFullscreen(); + } + }, + + /* + * Virtual Keyboard + */ + godot_js_display_vk_show__sig: 'viiii', + godot_js_display_vk_show: function (p_text, p_multiline, p_start, p_end) { + const text = GodotRuntime.parseString(p_text); + const start = p_start > 0 ? p_start : 0; + const end = p_end > 0 ? p_end : start; + GodotDisplayVK.show(text, p_multiline, start, end); + }, + + godot_js_display_vk_hide__sig: 'v', + godot_js_display_vk_hide: function () { + GodotDisplayVK.hide(); + }, + + godot_js_display_vk_available__sig: 'i', + godot_js_display_vk_available: function () { + return GodotDisplayVK.available(); + }, + + godot_js_display_vk_cb__sig: 'vi', + godot_js_display_vk_cb: function (p_input_cb) { + const input_cb = GodotRuntime.get_func(p_input_cb); + if (GodotDisplayVK.available()) { + GodotDisplayVK.init(input_cb); + } + }, + + /* + * Gamepads + */ + godot_js_display_gamepad_cb__sig: 'vi', + godot_js_display_gamepad_cb: function (change_cb) { + const onchange = GodotRuntime.get_func(change_cb); + GodotDisplayGamepads.init(onchange); + }, + + godot_js_display_gamepad_sample_count__sig: 'i', + godot_js_display_gamepad_sample_count: function () { + return GodotDisplayGamepads.get_samples().length; + }, + + godot_js_display_gamepad_sample__sig: 'i', + godot_js_display_gamepad_sample: function () { + GodotDisplayGamepads.sample(); + return 0; + }, + + godot_js_display_gamepad_sample_get__sig: 'iiiiiii', + godot_js_display_gamepad_sample_get: function (p_index, r_btns, r_btns_num, r_axes, r_axes_num, r_standard) { + const sample = GodotDisplayGamepads.get_sample(p_index); + if (!sample || !sample.connected) { + return 1; + } + const btns = sample.buttons; + const btns_len = btns.length < 16 ? btns.length : 16; + for (let i = 0; i < btns_len; i++) { + GodotRuntime.setHeapValue(r_btns + (i << 2), btns[i], 'float'); + } + GodotRuntime.setHeapValue(r_btns_num, btns_len, 'i32'); + const axes = sample.axes; + const axes_len = axes.length < 10 ? axes.length : 10; + for (let i = 0; i < axes_len; i++) { + GodotRuntime.setHeapValue(r_axes + (i << 2), axes[i], 'float'); + } + GodotRuntime.setHeapValue(r_axes_num, axes_len, 'i32'); + const is_standard = sample.standard ? 1 : 0; + GodotRuntime.setHeapValue(r_standard, is_standard, 'i32'); + return 0; + }, }; autoAddDeps(GodotDisplay, '$GodotDisplay'); diff --git a/platform/javascript/js/libs/library_godot_fetch.js b/platform/javascript/js/libs/library_godot_fetch.js new file mode 100644 index 0000000000..de5ae2b1ae --- /dev/null +++ b/platform/javascript/js/libs/library_godot_fetch.js @@ -0,0 +1,247 @@ +/*************************************************************************/ +/* library_godot_fetch.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +const GodotFetch = { + $GodotFetch__deps: ['$GodotRuntime'], + $GodotFetch: { + + onread: function (id, result) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + if (result.value) { + obj.chunks.push(result.value); + } + obj.reading = false; + obj.done = result.done; + }, + + onresponse: function (id, response) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + let chunked = false; + response.headers.forEach(function (value, header) { + const v = value.toLowerCase().trim(); + const h = header.toLowerCase().trim(); + if (h === 'transfer-encoding' && v === 'chunked') { + chunked = true; + } + }); + obj.status = response.status; + obj.response = response; + obj.reader = response.body.getReader(); + obj.chunked = chunked; + }, + + onerror: function (id, err) { + GodotRuntime.error(err); + const obj = IDHandler.get(id); + if (!obj) { + return; + } + obj.error = err; + }, + + create: function (method, url, headers, body) { + const obj = { + request: null, + response: null, + reader: null, + error: null, + done: false, + reading: false, + status: 0, + chunks: [], + bodySize: -1, + }; + const id = IDHandler.add(obj); + const init = { + method: method, + headers: headers, + body: body, + }; + obj.request = fetch(url, init); + obj.request.then(GodotFetch.onresponse.bind(null, id)).catch(GodotFetch.onerror.bind(null, id)); + return id; + }, + + free: function (id) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + IDHandler.remove(id); + if (!obj.request) { + return; + } + // Try to abort + obj.request.then(function (response) { + response.abort(); + }).catch(function (e) { /* nothing to do */ }); + }, + + read: function (id) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + if (obj.reader && !obj.reading) { + if (obj.done) { + obj.reader = null; + return; + } + obj.reading = true; + obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id)); + } + }, + }, + + godot_js_fetch_create__sig: 'iii', + godot_js_fetch_create: function (p_method, p_url, p_headers, p_headers_size, p_body, p_body_size) { + const method = GodotRuntime.parseString(p_method); + const url = GodotRuntime.parseString(p_url); + const headers = GodotRuntime.parseStringArray(p_headers, p_headers_size); + const body = p_body_size ? GodotRuntime.heapSlice(HEAP8, p_body, p_body_size) : null; + return GodotFetch.create(method, url, headers.map(function (hv) { + const idx = hv.indexOf(':'); + if (idx <= 0) { + return []; + } + return [ + hv.slice(0, idx).trim(), + hv.slice(idx + 1).trim(), + ]; + }).filter(function (v) { + return v.length === 2; + }), body); + }, + + godot_js_fetch_state_get__sig: 'ii', + godot_js_fetch_state_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj) { + return -1; + } + if (obj.error) { + return -1; + } + if (!obj.response) { + return 0; + } + if (obj.reader) { + return 1; + } + if (obj.done) { + return 2; + } + return -1; + }, + + godot_js_fetch_http_status_get__sig: 'ii', + godot_js_fetch_http_status_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 0; + } + return obj.status; + }, + + godot_js_fetch_read_headers__sig: 'iii', + godot_js_fetch_read_headers: function (p_id, p_parse_cb, p_ref) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 1; + } + const cb = GodotRuntime.get_func(p_parse_cb); + const arr = []; + obj.response.headers.forEach(function (v, h) { + arr.push(`${h}:${v}`); + }); + const c_ptr = GodotRuntime.allocStringArray(arr); + cb(arr.length, c_ptr, p_ref); + GodotRuntime.freeStringArray(c_ptr, arr.length); + return 0; + }, + + godot_js_fetch_read_chunk__sig: 'ii', + godot_js_fetch_read_chunk: function (p_id, p_buf, p_buf_size) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 0; + } + let to_read = p_buf_size; + const chunks = obj.chunks; + while (to_read && chunks.length) { + const chunk = obj.chunks[0]; + if (chunk.length > to_read) { + GodotRuntime.heapCopy(HEAP8, chunk.slice(0, to_read), p_buf); + chunks[0] = chunk.slice(to_read); + to_read = 0; + } else { + GodotRuntime.heapCopy(HEAP8, chunk, p_buf); + to_read -= chunk.length; + chunks.pop(); + } + } + if (!chunks.length) { + GodotFetch.read(p_id); + } + return p_buf_size - to_read; + }, + + godot_js_fetch_body_length_get__sig: 'ii', + godot_js_fetch_body_length_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return -1; + } + return obj.bodySize; + }, + + godot_js_fetch_is_chunked__sig: 'ii', + godot_js_fetch_is_chunked: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return -1; + } + return obj.chunked ? 1 : 0; + }, + + godot_js_fetch_free__sig: 'vi', + godot_js_fetch_free: function (id) { + GodotFetch.free(id); + }, +}; + +autoAddDeps(GodotFetch, '$GodotFetch'); +mergeInto(LibraryManager.library, GodotFetch); diff --git a/platform/javascript/js/libs/library_godot_http_request.js b/platform/javascript/js/libs/library_godot_http_request.js deleted file mode 100644 index 930d3209f8..0000000000 --- a/platform/javascript/js/libs/library_godot_http_request.js +++ /dev/null @@ -1,160 +0,0 @@ -/*************************************************************************/ -/* http_request.js */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -const GodotHTTPRequest = { - $GodotHTTPRequest__deps: ['$GodotRuntime'], - $GodotHTTPRequest: { - requests: [], - - getUnusedRequestId: function () { - const idMax = GodotHTTPRequest.requests.length; - for (let potentialId = 0; potentialId < idMax; ++potentialId) { - if (GodotHTTPRequest.requests[potentialId] instanceof XMLHttpRequest) { - continue; - } - return potentialId; - } - GodotHTTPRequest.requests.push(null); - return idMax; - }, - - setupRequest: function (xhr) { - xhr.responseType = 'arraybuffer'; - }, - }, - - godot_xhr_new__sig: 'i', - godot_xhr_new: function () { - const newId = GodotHTTPRequest.getUnusedRequestId(); - GodotHTTPRequest.requests[newId] = new XMLHttpRequest(); - GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]); - return newId; - }, - - godot_xhr_reset__sig: 'vi', - godot_xhr_reset: function (xhrId) { - GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest(); - GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); - }, - - godot_xhr_free__sig: 'vi', - godot_xhr_free: function (xhrId) { - GodotHTTPRequest.requests[xhrId].abort(); - GodotHTTPRequest.requests[xhrId] = null; - }, - - godot_xhr_open__sig: 'viiiii', - godot_xhr_open: function (xhrId, method, url, p_user, p_password) { - const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null; - const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null; - GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password); - }, - - godot_xhr_set_request_header__sig: 'viii', - godot_xhr_set_request_header: function (xhrId, header, value) { - GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value)); - }, - - godot_xhr_send_null__sig: 'vi', - godot_xhr_send_null: function (xhrId) { - GodotHTTPRequest.requests[xhrId].send(); - }, - - godot_xhr_send_string__sig: 'vii', - godot_xhr_send_string: function (xhrId, strPtr) { - if (!strPtr) { - GodotRuntime.error('Failed to send string per XHR: null pointer'); - return; - } - GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr)); - }, - - godot_xhr_send_data__sig: 'viii', - godot_xhr_send_data: function (xhrId, ptr, len) { - if (!ptr) { - GodotRuntime.error('Failed to send data per XHR: null pointer'); - return; - } - if (len < 0) { - GodotRuntime.error('Failed to send data per XHR: buffer length less than 0'); - return; - } - GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len)); - }, - - godot_xhr_abort__sig: 'vi', - godot_xhr_abort: function (xhrId) { - GodotHTTPRequest.requests[xhrId].abort(); - }, - - godot_xhr_get_status__sig: 'ii', - godot_xhr_get_status: function (xhrId) { - return GodotHTTPRequest.requests[xhrId].status; - }, - - godot_xhr_get_ready_state__sig: 'ii', - godot_xhr_get_ready_state: function (xhrId) { - return GodotHTTPRequest.requests[xhrId].readyState; - }, - - godot_xhr_get_response_headers_length__sig: 'ii', - godot_xhr_get_response_headers_length: function (xhrId) { - const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); - return headers === null ? 0 : GodotRuntime.strlen(headers); - }, - - godot_xhr_get_response_headers__sig: 'viii', - godot_xhr_get_response_headers: function (xhrId, dst, len) { - const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); - if (str === null) { - return; - } - GodotRuntime.stringToHeap(str, dst, len); - }, - - godot_xhr_get_response_length__sig: 'ii', - godot_xhr_get_response_length: function (xhrId) { - const body = GodotHTTPRequest.requests[xhrId].response; - return body === null ? 0 : body.byteLength; - }, - - godot_xhr_get_response__sig: 'viii', - godot_xhr_get_response: function (xhrId, dst, len) { - let buf = GodotHTTPRequest.requests[xhrId].response; - if (buf === null) { - return; - } - buf = new Uint8Array(buf).subarray(0, len); - HEAPU8.set(buf, dst); - }, -}; - -autoAddDeps(GodotHTTPRequest, '$GodotHTTPRequest'); -mergeInto(LibraryManager.library, GodotHTTPRequest); diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 9fde4a84e1..1d9f889bce 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -58,21 +58,34 @@ const GodotConfig = { $GodotConfig: { canvas: null, locale: 'en', - resize_on_start: false, + canvas_resize_policy: 2, // Adaptive + virtual_keyboard: false, + persistent_drops: false, on_execute: null, + on_exit: null, init_config: function (p_opts) { - GodotConfig.resize_on_start = !!p_opts['resizeCanvasOnStart']; + GodotConfig.canvas_resize_policy = p_opts['canvasResizePolicy']; GodotConfig.canvas = p_opts['canvas']; GodotConfig.locale = p_opts['locale'] || GodotConfig.locale; + GodotConfig.virtual_keyboard = p_opts['virtualKeyboard']; + GodotConfig.persistent_drops = !!p_opts['persistentDrops']; GodotConfig.on_execute = p_opts['onExecute']; - // This is called by emscripten, even if undocumented. - Module['onExit'] = p_opts['onExit']; // eslint-disable-line no-undef + GodotConfig.on_exit = p_opts['onExit']; }, locate_file: function (file) { return Module['locateFile'](file); // eslint-disable-line no-undef }, + clear: function () { + GodotConfig.canvas = null; + GodotConfig.locale = 'en'; + GodotConfig.canvas_resize_policy = 2; + GodotConfig.virtual_keyboard = false; + GodotConfig.persistent_drops = false; + GodotConfig.on_execute = null; + GodotConfig.on_exit = null; + }, }, godot_js_config_canvas_id_get__sig: 'vii', @@ -84,11 +97,6 @@ const GodotConfig = { godot_js_config_locale_get: function (p_ptr, p_ptr_max) { GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max); }, - - godot_js_config_is_resize_on_start__sig: 'i', - godot_js_config_is_resize_on_start: function () { - return GodotConfig.resize_on_start ? 1 : 0; - }, }; autoAddDeps(GodotConfig, '$GodotConfig'); @@ -98,7 +106,6 @@ const GodotFS = { $GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'], $GodotFS__postset: [ 'Module["initFS"] = GodotFS.init;', - 'Module["deinitFS"] = GodotFS.deinit;', 'Module["copyToFS"] = GodotFS.copy_to_fs;', ].join(''), $GodotFS: { @@ -210,9 +217,10 @@ const GodotFS = { mergeInto(LibraryManager.library, GodotFS); const GodotOS = { - $GodotOS__deps: ['$GodotFS', '$GodotRuntime'], + $GodotOS__deps: ['$GodotRuntime', '$GodotConfig', '$GodotFS'], $GodotOS__postset: [ 'Module["request_quit"] = function() { GodotOS.request_quit() };', + 'Module["onExit"] = GodotOS.cleanup;', 'GodotOS._fs_sync_promise = Promise.resolve();', ].join(''), $GodotOS: { @@ -224,6 +232,15 @@ const GodotOS = { GodotOS._async_cbs.push(p_promise_cb); }, + cleanup: function (exit_code) { + const cb = GodotConfig.on_exit; + GodotFS.deinit(); + GodotConfig.clear(); + if (cb) { + cb(exit_code); + } + }, + finish_async: function (callback) { GodotOS._fs_sync_promise.then(function (err) { const promises = []; @@ -282,6 +299,11 @@ const GodotOS = { godot_js_os_shell_open: function (p_uri) { window.open(GodotRuntime.parseString(p_uri), '_blank'); }, + + godot_js_os_hw_concurrency_get__sig: 'i', + godot_js_os_hw_concurrency_get: function () { + return navigator.hardwareConcurrency || 1; + }, }; autoAddDeps(GodotOS, '$GodotOS'); diff --git a/platform/javascript/js/libs/library_godot_runtime.js b/platform/javascript/js/libs/library_godot_runtime.js index 7e36ff8ab5..3da1ed8f06 100644 --- a/platform/javascript/js/libs/library_godot_runtime.js +++ b/platform/javascript/js/libs/library_godot_runtime.js @@ -72,11 +72,16 @@ const GodotRuntime = { return p_heap.subarray(p_ptr / bytes, p_ptr / bytes + p_len); }, - heapCopy: function (p_heap, p_ptr, p_len) { + heapSlice: function (p_heap, p_ptr, p_len) { const bytes = p_heap.BYTES_PER_ELEMENT; return p_heap.slice(p_ptr / bytes, p_ptr / bytes + p_len); }, + heapCopy: function (p_dst, p_src, p_ptr) { + const bytes = p_src.BYTES_PER_ELEMENT; + return p_dst.set(p_src, p_ptr / bytes); + }, + /* * Strings */ @@ -84,6 +89,15 @@ const GodotRuntime = { return UTF8ToString(p_ptr); // eslint-disable-line no-undef }, + parseStringArray: function (p_ptr, p_size) { + const strings = []; + const ptrs = GodotRuntime.heapSub(HEAP32, p_ptr, p_size); // TODO wasm64 + ptrs.forEach(function (ptr) { + strings.push(GodotRuntime.parseString(ptr)); + }); + return strings; + }, + strlen: function (p_str) { return lengthBytesUTF8(p_str); // eslint-disable-line no-undef }, diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 3fb5d4ad6a..0b1650076c 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -106,14 +106,18 @@ void OS_JavaScript::finalize() { // 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) { +Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { + return create_process(p_path, p_arguments); +} + +Error OS_JavaScript::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) { Array args; for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) { args.push_back(E->get()); } String json_args = JSON::print(args); int failed = godot_js_os_execute(json_args.utf8().get_data()); - ERR_FAIL_COND_V_MSG(failed, ERR_UNAVAILABLE, "OS::execute() must be implemented in JavaScript via 'engine.setOnExecute' if required."); + ERR_FAIL_COND_V_MSG(failed, ERR_UNAVAILABLE, "OS::execute() or create_process() must be implemented in JavaScript via 'engine.setOnExecute' if required."); return OK; } @@ -125,6 +129,10 @@ int OS_JavaScript::get_process_id() const { ERR_FAIL_V_MSG(0, "OS::get_process_id() is not available on the HTML5 platform."); } +int OS_JavaScript::get_processor_count() const { + return godot_js_os_hw_concurrency_get(); +} + bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { if (p_feature == "HTML5" || p_feature == "web") { return true; diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 9c8da0c898..81bb9c5f3d 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -70,9 +70,11 @@ public: MainLoop *get_main_loop() const override; bool main_loop_iterate(); - Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override; + Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override; + Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override; Error kill(const ProcessID &p_pid) override; int get_process_id() const override; + int get_processor_count() const override; String get_executable_path() const override; Error shell_open(String p_uri) override; diff --git a/platform/javascript/package-lock.json b/platform/javascript/package-lock.json index 8e298a495e..b8c434b3dd 100644 --- a/platform/javascript/package-lock.json +++ b/platform/javascript/package-lock.json @@ -43,6 +43,12 @@ } } }, + "@babel/parser": { + "version": "7.13.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", + "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", + "dev": true + }, "@eslint/eslintrc": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", @@ -202,6 +208,12 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -218,6 +230,15 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "catharsis": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", + "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -362,6 +383,12 @@ "ansi-colors": "^4.1.1" } }, + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -925,6 +952,51 @@ "esprima": "^4.0.0" } }, + "js2xmlparser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", + "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.3" + } + }, + "jsdoc": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.6.tgz", + "integrity": "sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.8.11", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^0.8.2", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.10.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -946,6 +1018,15 @@ "minimist": "^1.2.0" } }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -956,6 +1037,15 @@ "type-check": "~0.4.0" } }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -984,6 +1074,37 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", + "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "dev": true + }, + "marked": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", + "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", + "dev": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1287,6 +1408,15 @@ "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, + "requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", @@ -1513,6 +1643,12 @@ "string-width": "^3.0.0" } }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -1546,6 +1682,18 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "dev": true + }, "uri-js": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", @@ -1600,6 +1748,12 @@ "requires": { "mkdirp": "^0.5.1" } + }, + "xmlcreate": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", + "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "dev": true } } } diff --git a/platform/javascript/package.json b/platform/javascript/package.json index 630b584f5b..d9d272923e 100644 --- a/platform/javascript/package.json +++ b/platform/javascript/package.json @@ -5,20 +5,24 @@ "description": "Linting setup for Godot's HTML5 platform code", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules", + "docs": "jsdoc --template js/jsdoc2rst/ js/engine/engine.js js/engine/config.js --destination ''", + "lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules && npm run lint:tools", "lint:engine": "eslint \"js/engine/*.js\" --no-eslintrc -c .eslintrc.engine.js", "lint:libs": "eslint \"js/libs/*.js\" --no-eslintrc -c .eslintrc.libs.js", "lint:modules": "eslint \"../../modules/**/*.js\" --no-eslintrc -c .eslintrc.libs.js", - "format": "npm run format:engine && npm run format:libs && npm run format:modules", + "lint:tools": "eslint \"js/jsdoc2rst/**/*.js\" --no-eslintrc -c .eslintrc.engine.js", + "format": "npm run format:engine && npm run format:libs && npm run format:modules && npm run format:tools", "format:engine": "npm run lint:engine -- --fix", "format:libs": "npm run lint:libs -- --fix", - "format:modules": "npm run lint:modules -- --fix" + "format:modules": "npm run lint:modules -- --fix", + "format:tools": "npm run lint:tools -- --fix" }, "author": "Godot Engine contributors", "license": "MIT", "devDependencies": { "eslint": "^7.9.0", "eslint-config-airbnb-base": "^14.2.0", - "eslint-plugin-import": "^2.22.0" + "eslint-plugin-import": "^2.22.0", + "jsdoc": "^3.6.6" } } diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub index 6e43ffcedb..46714e9502 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -16,7 +16,10 @@ common_x11 = [ "key_mapping_x11.cpp", ] +if "udev" in env and env["udev"]: + common_x11.append("libudev-so_wrap.c") + prog = env.add_program("#bin/godot", ["godot_linuxbsd.cpp"] + common_x11) -if env["debug_symbols"] == "yes" and env["separate_debug_symbols"]: +if env["debug_symbols"] and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_linuxbsd_builders.make_debug_linuxbsd)) diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index 90e34f8e77..ea0222cb19 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -104,7 +104,7 @@ static void handle_crash(int sig) { // Try to get the file/line number using addr2line int ret; - Error err = OS::get_singleton()->execute(String("addr2line"), args, true, nullptr, &output, &ret); + Error err = OS::get_singleton()->execute(String("addr2line"), args, &output, &ret); if (err == OK) { output.erase(output.length() - 1, 1); } diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index a819731328..1876960c57 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -64,15 +64,16 @@ def get_opts(): BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_lld", "Use the LLD linker", False), BoolVariable("use_thinlto", "Use ThinLTO", False), - BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", False), + BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", True), BoolVariable("use_coverage", "Test Godot coverage", False), BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False), - BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False), - BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN))", False), - BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), + BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN)", False), + BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN)", False), + BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), + BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False), BoolVariable("pulseaudio", "Detect and use PulseAudio", True), - BoolVariable("udev", "Use udev for gamepad connection callbacks", False), - EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), + BoolVariable("udev", "Use udev for gamepad connection callbacks", True), + BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("touch", "Enable touch events", True), BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False), @@ -89,20 +90,20 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O3"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) - if env["debug_symbols"] == "yes": + if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) - if env["debug_symbols"] == "yes": + if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "debug": @@ -142,15 +143,27 @@ def configure(env): env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"]) env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"]) - if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"]: + if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"] or env["use_msan"]: env.extra_suffix += "s" if env["use_ubsan"]: - env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append( + CCFLAGS=[ + "-fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin" + ] + ) env.Append(LINKFLAGS=["-fsanitize=undefined"]) + if env["use_llvm"]: + env.Append( + CCFLAGS=[ + "-fsanitize=nullability-return,nullability-arg,function,nullability-assign,implicit-integer-sign-change" + ] + ) + else: + env.Append(CCFLAGS=["-fsanitize=bounds-strict"]) if env["use_asan"]: - env.Append(CCFLAGS=["-fsanitize=address"]) + env.Append(CCFLAGS=["-fsanitize=address,pointer-subtract,pointer-compare"]) env.Append(LINKFLAGS=["-fsanitize=address"]) if env["use_lsan"]: @@ -161,6 +174,12 @@ def configure(env): env.Append(CCFLAGS=["-fsanitize=thread"]) env.Append(LINKFLAGS=["-fsanitize=thread"]) + if env["use_msan"] and env["use_llvm"]: + env.Append(CCFLAGS=["-fsanitize=memory"]) + env.Append(CCFLAGS=["-fsanitize-memory-track-origins"]) + env.Append(CCFLAGS=["-fsanitize-recover=memory"]) + env.Append(LINKFLAGS=["-fsanitize=memory"]) + if env["use_lto"]: if not env["use_llvm"] and env.GetOption("num_jobs") > 1: env.Append(CCFLAGS=["-flto"]) @@ -306,13 +325,16 @@ def configure(env): if not env["builtin_pcre2"]: env.ParseConfig("pkg-config libpcre2-32 --cflags --libs") + if not env["builtin_embree"]: + # No pkgconfig file so far, hardcode expected lib name. + env.Append(LIBS=["embree3"]) + ## Flags if os.system("pkg-config --exists alsa") == 0: # 0 means found print("Enabling ALSA") + env["alsa"] = True env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"]) - # Don't parse --cflags, we don't need to add /usr/include/alsa to include path - env.ParseConfig("pkg-config alsa --libs") else: print("ALSA libraries not found, disabling driver") @@ -320,20 +342,20 @@ def configure(env): if os.system("pkg-config --exists libpulse") == 0: # 0 means found print("Enabling PulseAudio") env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"]) - env.ParseConfig("pkg-config --cflags --libs libpulse") + env.ParseConfig("pkg-config --cflags libpulse") else: print("PulseAudio development libraries not found, disabling driver") if platform.system() == "Linux": env.Append(CPPDEFINES=["JOYDEV_ENABLED"]) - if env["udev"]: if os.system("pkg-config --exists libudev") == 0: # 0 means found print("Enabling udev support") env.Append(CPPDEFINES=["UDEV_ENABLED"]) - env.ParseConfig("pkg-config libudev --cflags --libs") else: print("libudev development libraries not found, disabling udev support") + else: + env["udev"] = False # Linux specific # Linkflags below this line should typically stay the last ones if not env["builtin_zlib"]: @@ -391,3 +413,9 @@ def configure(env): # Link those statically for portability if env["use_static_cpp"]: env.Append(LINKFLAGS=["-static-libgcc", "-static-libstdc++"]) + if env["use_llvm"]: + env["LINKCOM"] = env["LINKCOM"] + " -l:libatomic.a" + + else: + if env["use_llvm"]: + env.Append(LIBS=["atomic"]) diff --git a/platform/linuxbsd/detect_prime_x11.cpp b/platform/linuxbsd/detect_prime_x11.cpp index 0f8d108dff..da1c95a593 100644 --- a/platform/linuxbsd/detect_prime_x11.cpp +++ b/platform/linuxbsd/detect_prime_x11.cpp @@ -61,6 +61,7 @@ struct vendor { vendor vendormap[] = { { "Advanced Micro Devices, Inc.", 30 }, + { "AMD", 30 }, { "NVIDIA Corporation", 30 }, { "X.Org", 30 }, { "Intel Open Source Technology Center", 20 }, @@ -128,7 +129,7 @@ void create_context() { int detect_prime() { pid_t p; - int priorities[2]; + int priorities[2] = {}; String vendors[2]; String renderers[2]; diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 1ee5cd3923..12f030bd58 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -191,7 +191,7 @@ void DisplayServerX11::alert(const String &p_alert, const String &p_title) { } if (program.length()) { - OS::get_singleton()->execute(program, args, true); + OS::get_singleton()->execute(program, args); } else { print_line(p_alert); } @@ -727,9 +727,9 @@ Point2i DisplayServerX11::screen_get_position(int p_screen) const { int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - if (p_screen >= count) { - return Point2i(0, 0); - } + + // Check if screen is valid + ERR_FAIL_INDEX_V(p_screen, count, Point2i(0, 0)); Point2i position = Point2i(xsi[p_screen].x_org, xsi[p_screen].y_org); @@ -758,9 +758,9 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - if (p_screen >= count) { - return Rect2i(0, 0, 0, 0); - } + + // Check if screen is valid + ERR_FAIL_INDEX_V(p_screen, count, 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); @@ -1041,11 +1041,13 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window 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 == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); } + // Check if screen is valid + ERR_FAIL_INDEX(p_screen, get_screen_count()); + if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) { Point2i position = screen_get_position(p_screen); Size2i size = screen_get_size(p_screen); @@ -1915,7 +1917,7 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape Rect2i atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -1938,7 +1940,7 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape 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(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -2010,7 +2012,7 @@ int DisplayServerX11::keyboard_get_layout_count() const { XkbGetNames(x11_display, XkbSymbolsNameMask, kbd); const Atom *groups = kbd->names->groups; - if (kbd->ctrls != NULL) { + if (kbd->ctrls != nullptr) { _group_count = kbd->ctrls->num_groups; } else { while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { @@ -2044,7 +2046,7 @@ String DisplayServerX11::keyboard_get_layout_language(int p_index) const { int _group_count = 0; const Atom *groups = kbd->names->groups; - if (kbd->ctrls != NULL) { + if (kbd->ctrls != nullptr) { _group_count = kbd->ctrls->num_groups; } else { while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { @@ -2083,7 +2085,7 @@ String DisplayServerX11::keyboard_get_layout_name(int p_index) const { int _group_count = 0; const Atom *groups = kbd->names->groups; - if (kbd->ctrls != NULL) { + if (kbd->ctrls != nullptr) { _group_count = kbd->ctrls->num_groups; } else { while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { @@ -2682,7 +2684,7 @@ bool DisplayServerX11::_wait_for_events() const { tv.tv_sec = 1; // Wait for next event or timeout. - int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + int num_ready_fds = select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv); if (num_ready_fds > 0) { // Event received. @@ -2697,7 +2699,7 @@ bool DisplayServerX11::_wait_for_events() const { } void DisplayServerX11::_poll_events() { - while (!events_thread_done) { + while (!events_thread_done.is_set()) { _wait_for_events(); // Process events from the queue. @@ -3160,14 +3162,14 @@ void DisplayServerX11::process_events() { last_click_ms = 0; last_click_pos = Point2i(-100, -100); last_click_button_index = -1; - mb->set_doubleclick(true); + mb->set_double_click(true); } } else if (mb->get_button_index() < 4 || mb->get_button_index() > 7) { last_click_button_index = mb->get_button_index(); } - if (!mb->is_doubleclick()) { + if (!mb->is_double_click()) { last_click_ms += diff; last_click_pos = Point2i(event.xbutton.x, event.xbutton.y); } @@ -3296,7 +3298,7 @@ void DisplayServerX11::process_events() { 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_pressure((mouse_get_button_state() & (1 << (MOUSE_BUTTON_LEFT - 1))) ? 1.0f : 0.0f); } mm->set_tilt(xi.tilt); @@ -3360,7 +3362,7 @@ void DisplayServerX11::process_events() { Vector<String> files = String((char *)p.data).split("\n", false); for (int i = 0; i < files.size(); i++) { - files.write[i] = files[i].replace("file://", "").http_unescape().strip_edges(); + files.write[i] = files[i].replace("file://", "").uri_decode().strip_edges(); } if (!windows[window_id].drop_files_callback.is_null()) { @@ -4028,7 +4030,10 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode use_prime = 0; } - if (getenv("LD_LIBRARY_PATH")) { + // Some tools use fake libGL libraries and have them override the real one using + // LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its + // runtime and includes system `/lib` and `/lib64`... so ignore Steam. + if (use_prime == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) { String ld_library_path(getenv("LD_LIBRARY_PATH")); Vector<String> libraries = ld_library_path.split(":"); @@ -4266,7 +4271,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } - events_thread = Thread::create(_poll_events_thread, this); + events_thread.start(_poll_events_thread, this); _update_real_mouse_position(windows[MAIN_WINDOW_ID]); @@ -4279,10 +4284,8 @@ DisplayServerX11::~DisplayServerX11() { _clipboard_transfer_ownership(XA_PRIMARY, x11_main_window); _clipboard_transfer_ownership(XInternAtom(x11_display, "CLIPBOARD", 0), x11_main_window); - events_thread_done = true; - Thread::wait_to_finish(events_thread); - memdelete(events_thread); - events_thread = nullptr; + events_thread_done.set(); + events_thread.wait_to_finish(); //destroy all windows for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 7784ba82b5..10686d8424 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -252,8 +252,8 @@ class DisplayServerX11 : public DisplayServer { void _dispatch_input_event(const Ref<InputEvent> &p_event); mutable Mutex events_mutex; - Thread *events_thread = nullptr; - bool events_thread_done = false; + Thread events_thread; + SafeFlag events_thread_done; LocalVector<XEvent> polled_events; static void _poll_events_thread(void *ud); bool _wait_for_events() const; diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 291ca49585..e8f4352dff 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -32,13 +32,14 @@ #include "joypad_linux.h" +#include <dirent.h> #include <errno.h> #include <fcntl.h> #include <linux/input.h> #include <unistd.h> #ifdef UDEV_ENABLED -#include <libudev.h> +#include "libudev-so_wrap.h" #endif #define LONG_BITS (sizeof(long) * 8) @@ -61,7 +62,7 @@ void JoypadLinux::Joypad::reset() { dpad = 0; fd = -1; - Input::JoyAxis jx; + Input::JoyAxisValue jx; jx.min = -1; jx.value = 0.0f; for (int i = 0; i < MAX_ABS; i++) { @@ -71,15 +72,28 @@ void JoypadLinux::Joypad::reset() { } JoypadLinux::JoypadLinux(Input *in) { - exit_udev = false; +#ifdef UDEV_ENABLED +#ifdef DEBUG_ENABLED + int dylibloader_verbose = 1; +#else + int dylibloader_verbose = 0; +#endif + use_udev = initialize_libudev(dylibloader_verbose) == 0; + if (use_udev) { + print_verbose("JoypadLinux: udev enabled and loaded successfully."); + } else { + print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads."); + } +#else + print_verbose("JoypadLinux: udev disabled, parsing /dev/input to detect joypads."); +#endif input = in; - joy_thread = Thread::create(joy_thread_func, this); + joy_thread.start(joy_thread_func, this); } JoypadLinux::~JoypadLinux() { - exit_udev = true; - Thread::wait_to_finish(joy_thread); - memdelete(joy_thread); + exit_monitor.set(); + joy_thread.wait_to_finish(); close_joypad(); } @@ -92,11 +106,20 @@ void JoypadLinux::joy_thread_func(void *p_user) { void JoypadLinux::run_joypad_thread() { #ifdef UDEV_ENABLED - udev *_udev = udev_new(); - ERR_FAIL_COND(!_udev); - enumerate_joypads(_udev); - monitor_joypads(_udev); - udev_unref(_udev); + if (use_udev) { + udev *_udev = udev_new(); + if (!_udev) { + use_udev = false; + ERR_PRINT("Failed getting an udev context, falling back to parsing /dev/input."); + monitor_joypads(); + } else { + enumerate_joypads(_udev); + monitor_joypads(_udev); + udev_unref(_udev); + } + } else { + monitor_joypads(); + } #else monitor_joypads(); #endif @@ -137,7 +160,7 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { udev_monitor_enable_receiving(mon); int fd = udev_monitor_get_fd(mon); - while (!exit_udev) { + while (!exit_monitor.is_set()) { fd_set fds; struct timeval tv; int ret; @@ -155,17 +178,18 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { select() ensured that this will not block. */ dev = udev_monitor_receive_device(mon); - if (dev && udev_device_get_devnode(dev) != 0) { + if (dev && udev_device_get_devnode(dev) != nullptr) { 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") + if (action == "add") { open_joypad(devnode); - else if (String(action) == "remove") + } else if (String(action) == "remove") { close_joypad(get_joy_from_path(devnode)); + } } } @@ -179,17 +203,27 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { #endif void JoypadLinux::monitor_joypads() { - while (!exit_udev) { + while (!exit_monitor.is_set()) { { MutexLock lock(joy_mutex); - for (int i = 0; i < 32; i++) { + DIR *input_directory; + input_directory = opendir("/dev/input"); + if (input_directory) { + struct dirent *current; char fname[64]; - sprintf(fname, "/dev/input/event%d", i); - if (attached_devices.find(fname) == -1) { - open_joypad(fname); + + while ((current = readdir(input_directory)) != nullptr) { + if (strncmp(current->d_name, "event", 5) != 0) { + continue; + } + sprintf(fname, "/dev/input/%.*s", 16, current->d_name); + if (attached_devices.find(fname) == -1) { + open_joypad(fname); + } } } + closedir(input_directory); } usleep(1000000); // 1s } @@ -395,10 +429,10 @@ void JoypadLinux::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { joy.ff_effect_timestamp = p_timestamp; } -Input::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { +Input::JoyAxisValue JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { int min = p_abs->minimum; int max = p_abs->maximum; - Input::JoyAxis jx; + Input::JoyAxisValue jx; if (min < 0) { jx.min = -1; @@ -480,7 +514,7 @@ void JoypadLinux::process_joypads() { return; } if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) { - Input::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value); + Input::JoyAxisValue 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 20d30b510c..177d7a51ce 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -53,7 +53,7 @@ private: }; struct Joypad { - Input::JoyAxis curr_axis[MAX_ABS]; + Input::JoyAxisValue curr_axis[MAX_ABS]; int key_map[MAX_KEY]; int abs_map[MAX_ABS]; int dpad = 0; @@ -70,10 +70,13 @@ private: void reset(); }; - bool exit_udev; +#ifdef UDEV_ENABLED + bool use_udev = false; +#endif + SafeFlag exit_monitor; Mutex joy_mutex; - Thread *joy_thread; - Input *input; + Thread joy_thread; + Input *input = nullptr; Joypad joypads[JOYPADS_MAX]; Vector<String> attached_devices; @@ -94,7 +97,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); - Input::JoyAxis axis_correct(const input_absinfo *p_abs, int p_value) const; + Input::JoyAxisValue axis_correct(const input_absinfo *p_abs, int p_value) const; }; #endif diff --git a/platform/linuxbsd/libudev-so_wrap.c b/platform/linuxbsd/libudev-so_wrap.c new file mode 100644 index 0000000000..a9fa4a494a --- /dev/null +++ b/platform/linuxbsd/libudev-so_wrap.c @@ -0,0 +1,1013 @@ +// This file is generated. Do not edit! +// see https://github.com/hpvb/dynload-wrapper for details +// generated by /home/hp/Projects/godot/pulse/generate-wrapper.py 0.3 on 2021-02-20 00:08:59 +// flags: /home/hp/Projects/godot/pulse/generate-wrapper.py --include /usr/include/libudev.h --sys-include <libudev.h> --soname libudev.so.1 --init-name libudev --omit-prefix gnu_ --output-header libudev-so_wrap.h --output-implementation libudev-so_wrap.c +// +#include <stdint.h> + +#define udev_ref udev_ref_dylibloader_orig_libudev +#define udev_unref udev_unref_dylibloader_orig_libudev +#define udev_new udev_new_dylibloader_orig_libudev +#define udev_set_log_fn udev_set_log_fn_dylibloader_orig_libudev +#define udev_get_log_priority udev_get_log_priority_dylibloader_orig_libudev +#define udev_set_log_priority udev_set_log_priority_dylibloader_orig_libudev +#define udev_get_userdata udev_get_userdata_dylibloader_orig_libudev +#define udev_set_userdata udev_set_userdata_dylibloader_orig_libudev +#define udev_list_entry_get_next udev_list_entry_get_next_dylibloader_orig_libudev +#define udev_list_entry_get_by_name udev_list_entry_get_by_name_dylibloader_orig_libudev +#define udev_list_entry_get_name udev_list_entry_get_name_dylibloader_orig_libudev +#define udev_list_entry_get_value udev_list_entry_get_value_dylibloader_orig_libudev +#define udev_device_ref udev_device_ref_dylibloader_orig_libudev +#define udev_device_unref udev_device_unref_dylibloader_orig_libudev +#define udev_device_get_udev udev_device_get_udev_dylibloader_orig_libudev +#define udev_device_new_from_syspath udev_device_new_from_syspath_dylibloader_orig_libudev +#define udev_device_new_from_devnum udev_device_new_from_devnum_dylibloader_orig_libudev +#define udev_device_new_from_subsystem_sysname udev_device_new_from_subsystem_sysname_dylibloader_orig_libudev +#define udev_device_new_from_device_id udev_device_new_from_device_id_dylibloader_orig_libudev +#define udev_device_new_from_environment udev_device_new_from_environment_dylibloader_orig_libudev +#define udev_device_get_parent udev_device_get_parent_dylibloader_orig_libudev +#define udev_device_get_parent_with_subsystem_devtype udev_device_get_parent_with_subsystem_devtype_dylibloader_orig_libudev +#define udev_device_get_devpath udev_device_get_devpath_dylibloader_orig_libudev +#define udev_device_get_subsystem udev_device_get_subsystem_dylibloader_orig_libudev +#define udev_device_get_devtype udev_device_get_devtype_dylibloader_orig_libudev +#define udev_device_get_syspath udev_device_get_syspath_dylibloader_orig_libudev +#define udev_device_get_sysname udev_device_get_sysname_dylibloader_orig_libudev +#define udev_device_get_sysnum udev_device_get_sysnum_dylibloader_orig_libudev +#define udev_device_get_devnode udev_device_get_devnode_dylibloader_orig_libudev +#define udev_device_get_is_initialized udev_device_get_is_initialized_dylibloader_orig_libudev +#define udev_device_get_devlinks_list_entry udev_device_get_devlinks_list_entry_dylibloader_orig_libudev +#define udev_device_get_properties_list_entry udev_device_get_properties_list_entry_dylibloader_orig_libudev +#define udev_device_get_tags_list_entry udev_device_get_tags_list_entry_dylibloader_orig_libudev +#define udev_device_get_sysattr_list_entry udev_device_get_sysattr_list_entry_dylibloader_orig_libudev +#define udev_device_get_property_value udev_device_get_property_value_dylibloader_orig_libudev +#define udev_device_get_driver udev_device_get_driver_dylibloader_orig_libudev +#define udev_device_get_devnum udev_device_get_devnum_dylibloader_orig_libudev +#define udev_device_get_action udev_device_get_action_dylibloader_orig_libudev +#define udev_device_get_seqnum udev_device_get_seqnum_dylibloader_orig_libudev +#define udev_device_get_usec_since_initialized udev_device_get_usec_since_initialized_dylibloader_orig_libudev +#define udev_device_get_sysattr_value udev_device_get_sysattr_value_dylibloader_orig_libudev +#define udev_device_set_sysattr_value udev_device_set_sysattr_value_dylibloader_orig_libudev +#define udev_device_has_tag udev_device_has_tag_dylibloader_orig_libudev +#define udev_monitor_ref udev_monitor_ref_dylibloader_orig_libudev +#define udev_monitor_unref udev_monitor_unref_dylibloader_orig_libudev +#define udev_monitor_get_udev udev_monitor_get_udev_dylibloader_orig_libudev +#define udev_monitor_new_from_netlink udev_monitor_new_from_netlink_dylibloader_orig_libudev +#define udev_monitor_enable_receiving udev_monitor_enable_receiving_dylibloader_orig_libudev +#define udev_monitor_set_receive_buffer_size udev_monitor_set_receive_buffer_size_dylibloader_orig_libudev +#define udev_monitor_get_fd udev_monitor_get_fd_dylibloader_orig_libudev +#define udev_monitor_receive_device udev_monitor_receive_device_dylibloader_orig_libudev +#define udev_monitor_filter_add_match_subsystem_devtype udev_monitor_filter_add_match_subsystem_devtype_dylibloader_orig_libudev +#define udev_monitor_filter_add_match_tag udev_monitor_filter_add_match_tag_dylibloader_orig_libudev +#define udev_monitor_filter_update udev_monitor_filter_update_dylibloader_orig_libudev +#define udev_monitor_filter_remove udev_monitor_filter_remove_dylibloader_orig_libudev +#define udev_enumerate_ref udev_enumerate_ref_dylibloader_orig_libudev +#define udev_enumerate_unref udev_enumerate_unref_dylibloader_orig_libudev +#define udev_enumerate_get_udev udev_enumerate_get_udev_dylibloader_orig_libudev +#define udev_enumerate_new udev_enumerate_new_dylibloader_orig_libudev +#define udev_enumerate_add_match_subsystem udev_enumerate_add_match_subsystem_dylibloader_orig_libudev +#define udev_enumerate_add_nomatch_subsystem udev_enumerate_add_nomatch_subsystem_dylibloader_orig_libudev +#define udev_enumerate_add_match_sysattr udev_enumerate_add_match_sysattr_dylibloader_orig_libudev +#define udev_enumerate_add_nomatch_sysattr udev_enumerate_add_nomatch_sysattr_dylibloader_orig_libudev +#define udev_enumerate_add_match_property udev_enumerate_add_match_property_dylibloader_orig_libudev +#define udev_enumerate_add_match_sysname udev_enumerate_add_match_sysname_dylibloader_orig_libudev +#define udev_enumerate_add_match_tag udev_enumerate_add_match_tag_dylibloader_orig_libudev +#define udev_enumerate_add_match_parent udev_enumerate_add_match_parent_dylibloader_orig_libudev +#define udev_enumerate_add_match_is_initialized udev_enumerate_add_match_is_initialized_dylibloader_orig_libudev +#define udev_enumerate_add_syspath udev_enumerate_add_syspath_dylibloader_orig_libudev +#define udev_enumerate_scan_devices udev_enumerate_scan_devices_dylibloader_orig_libudev +#define udev_enumerate_scan_subsystems udev_enumerate_scan_subsystems_dylibloader_orig_libudev +#define udev_enumerate_get_list_entry udev_enumerate_get_list_entry_dylibloader_orig_libudev +#define udev_queue_ref udev_queue_ref_dylibloader_orig_libudev +#define udev_queue_unref udev_queue_unref_dylibloader_orig_libudev +#define udev_queue_get_udev udev_queue_get_udev_dylibloader_orig_libudev +#define udev_queue_new udev_queue_new_dylibloader_orig_libudev +#define udev_queue_get_kernel_seqnum udev_queue_get_kernel_seqnum_dylibloader_orig_libudev +#define udev_queue_get_udev_seqnum udev_queue_get_udev_seqnum_dylibloader_orig_libudev +#define udev_queue_get_udev_is_active udev_queue_get_udev_is_active_dylibloader_orig_libudev +#define udev_queue_get_queue_is_empty udev_queue_get_queue_is_empty_dylibloader_orig_libudev +#define udev_queue_get_seqnum_is_finished udev_queue_get_seqnum_is_finished_dylibloader_orig_libudev +#define udev_queue_get_seqnum_sequence_is_finished udev_queue_get_seqnum_sequence_is_finished_dylibloader_orig_libudev +#define udev_queue_get_fd udev_queue_get_fd_dylibloader_orig_libudev +#define udev_queue_flush udev_queue_flush_dylibloader_orig_libudev +#define udev_queue_get_queued_list_entry udev_queue_get_queued_list_entry_dylibloader_orig_libudev +#define udev_hwdb_new udev_hwdb_new_dylibloader_orig_libudev +#define udev_hwdb_ref udev_hwdb_ref_dylibloader_orig_libudev +#define udev_hwdb_unref udev_hwdb_unref_dylibloader_orig_libudev +#define udev_hwdb_get_properties_list_entry udev_hwdb_get_properties_list_entry_dylibloader_orig_libudev +#define udev_util_encode_string udev_util_encode_string_dylibloader_orig_libudev +#include <libudev.h> +#undef udev_ref +#undef udev_unref +#undef udev_new +#undef udev_set_log_fn +#undef udev_get_log_priority +#undef udev_set_log_priority +#undef udev_get_userdata +#undef udev_set_userdata +#undef udev_list_entry_get_next +#undef udev_list_entry_get_by_name +#undef udev_list_entry_get_name +#undef udev_list_entry_get_value +#undef udev_device_ref +#undef udev_device_unref +#undef udev_device_get_udev +#undef udev_device_new_from_syspath +#undef udev_device_new_from_devnum +#undef udev_device_new_from_subsystem_sysname +#undef udev_device_new_from_device_id +#undef udev_device_new_from_environment +#undef udev_device_get_parent +#undef udev_device_get_parent_with_subsystem_devtype +#undef udev_device_get_devpath +#undef udev_device_get_subsystem +#undef udev_device_get_devtype +#undef udev_device_get_syspath +#undef udev_device_get_sysname +#undef udev_device_get_sysnum +#undef udev_device_get_devnode +#undef udev_device_get_is_initialized +#undef udev_device_get_devlinks_list_entry +#undef udev_device_get_properties_list_entry +#undef udev_device_get_tags_list_entry +#undef udev_device_get_sysattr_list_entry +#undef udev_device_get_property_value +#undef udev_device_get_driver +#undef udev_device_get_devnum +#undef udev_device_get_action +#undef udev_device_get_seqnum +#undef udev_device_get_usec_since_initialized +#undef udev_device_get_sysattr_value +#undef udev_device_set_sysattr_value +#undef udev_device_has_tag +#undef udev_monitor_ref +#undef udev_monitor_unref +#undef udev_monitor_get_udev +#undef udev_monitor_new_from_netlink +#undef udev_monitor_enable_receiving +#undef udev_monitor_set_receive_buffer_size +#undef udev_monitor_get_fd +#undef udev_monitor_receive_device +#undef udev_monitor_filter_add_match_subsystem_devtype +#undef udev_monitor_filter_add_match_tag +#undef udev_monitor_filter_update +#undef udev_monitor_filter_remove +#undef udev_enumerate_ref +#undef udev_enumerate_unref +#undef udev_enumerate_get_udev +#undef udev_enumerate_new +#undef udev_enumerate_add_match_subsystem +#undef udev_enumerate_add_nomatch_subsystem +#undef udev_enumerate_add_match_sysattr +#undef udev_enumerate_add_nomatch_sysattr +#undef udev_enumerate_add_match_property +#undef udev_enumerate_add_match_sysname +#undef udev_enumerate_add_match_tag +#undef udev_enumerate_add_match_parent +#undef udev_enumerate_add_match_is_initialized +#undef udev_enumerate_add_syspath +#undef udev_enumerate_scan_devices +#undef udev_enumerate_scan_subsystems +#undef udev_enumerate_get_list_entry +#undef udev_queue_ref +#undef udev_queue_unref +#undef udev_queue_get_udev +#undef udev_queue_new +#undef udev_queue_get_kernel_seqnum +#undef udev_queue_get_udev_seqnum +#undef udev_queue_get_udev_is_active +#undef udev_queue_get_queue_is_empty +#undef udev_queue_get_seqnum_is_finished +#undef udev_queue_get_seqnum_sequence_is_finished +#undef udev_queue_get_fd +#undef udev_queue_flush +#undef udev_queue_get_queued_list_entry +#undef udev_hwdb_new +#undef udev_hwdb_ref +#undef udev_hwdb_unref +#undef udev_hwdb_get_properties_list_entry +#undef udev_util_encode_string +#include <dlfcn.h> +#include <stdio.h> +struct udev* (*udev_ref_dylibloader_wrapper_libudev)(struct udev*); +struct udev* (*udev_unref_dylibloader_wrapper_libudev)(struct udev*); +struct udev* (*udev_new_dylibloader_wrapper_libudev)( void); +void (*udev_set_log_fn_dylibloader_wrapper_libudev)(struct udev*, void*); +int (*udev_get_log_priority_dylibloader_wrapper_libudev)(struct udev*); +void (*udev_set_log_priority_dylibloader_wrapper_libudev)(struct udev*, int); +void* (*udev_get_userdata_dylibloader_wrapper_libudev)(struct udev*); +void (*udev_set_userdata_dylibloader_wrapper_libudev)(struct udev*, void*); +struct udev_list_entry* (*udev_list_entry_get_next_dylibloader_wrapper_libudev)(struct udev_list_entry*); +struct udev_list_entry* (*udev_list_entry_get_by_name_dylibloader_wrapper_libudev)(struct udev_list_entry*,const char*); +const char* (*udev_list_entry_get_name_dylibloader_wrapper_libudev)(struct udev_list_entry*); +const char* (*udev_list_entry_get_value_dylibloader_wrapper_libudev)(struct udev_list_entry*); +struct udev_device* (*udev_device_ref_dylibloader_wrapper_libudev)(struct udev_device*); +struct udev_device* (*udev_device_unref_dylibloader_wrapper_libudev)(struct udev_device*); +struct udev* (*udev_device_get_udev_dylibloader_wrapper_libudev)(struct udev_device*); +struct udev_device* (*udev_device_new_from_syspath_dylibloader_wrapper_libudev)(struct udev*,const char*); +struct udev_device* (*udev_device_new_from_devnum_dylibloader_wrapper_libudev)(struct udev*, char, dev_t); +struct udev_device* (*udev_device_new_from_subsystem_sysname_dylibloader_wrapper_libudev)(struct udev*,const char*,const char*); +struct udev_device* (*udev_device_new_from_device_id_dylibloader_wrapper_libudev)(struct udev*,const char*); +struct udev_device* (*udev_device_new_from_environment_dylibloader_wrapper_libudev)(struct udev*); +struct udev_device* (*udev_device_get_parent_dylibloader_wrapper_libudev)(struct udev_device*); +struct udev_device* (*udev_device_get_parent_with_subsystem_devtype_dylibloader_wrapper_libudev)(struct udev_device*,const char*,const char*); +const char* (*udev_device_get_devpath_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_subsystem_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_devtype_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_syspath_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_sysname_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_sysnum_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_devnode_dylibloader_wrapper_libudev)(struct udev_device*); +int (*udev_device_get_is_initialized_dylibloader_wrapper_libudev)(struct udev_device*); +struct udev_list_entry* (*udev_device_get_devlinks_list_entry_dylibloader_wrapper_libudev)(struct udev_device*); +struct udev_list_entry* (*udev_device_get_properties_list_entry_dylibloader_wrapper_libudev)(struct udev_device*); +struct udev_list_entry* (*udev_device_get_tags_list_entry_dylibloader_wrapper_libudev)(struct udev_device*); +struct udev_list_entry* (*udev_device_get_sysattr_list_entry_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_property_value_dylibloader_wrapper_libudev)(struct udev_device*,const char*); +const char* (*udev_device_get_driver_dylibloader_wrapper_libudev)(struct udev_device*); +dev_t (*udev_device_get_devnum_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_action_dylibloader_wrapper_libudev)(struct udev_device*); +unsigned long long int (*udev_device_get_seqnum_dylibloader_wrapper_libudev)(struct udev_device*); +unsigned long long int (*udev_device_get_usec_since_initialized_dylibloader_wrapper_libudev)(struct udev_device*); +const char* (*udev_device_get_sysattr_value_dylibloader_wrapper_libudev)(struct udev_device*,const char*); +int (*udev_device_set_sysattr_value_dylibloader_wrapper_libudev)(struct udev_device*,const char*,const char*); +int (*udev_device_has_tag_dylibloader_wrapper_libudev)(struct udev_device*,const char*); +struct udev_monitor* (*udev_monitor_ref_dylibloader_wrapper_libudev)(struct udev_monitor*); +struct udev_monitor* (*udev_monitor_unref_dylibloader_wrapper_libudev)(struct udev_monitor*); +struct udev* (*udev_monitor_get_udev_dylibloader_wrapper_libudev)(struct udev_monitor*); +struct udev_monitor* (*udev_monitor_new_from_netlink_dylibloader_wrapper_libudev)(struct udev*,const char*); +int (*udev_monitor_enable_receiving_dylibloader_wrapper_libudev)(struct udev_monitor*); +int (*udev_monitor_set_receive_buffer_size_dylibloader_wrapper_libudev)(struct udev_monitor*, int); +int (*udev_monitor_get_fd_dylibloader_wrapper_libudev)(struct udev_monitor*); +struct udev_device* (*udev_monitor_receive_device_dylibloader_wrapper_libudev)(struct udev_monitor*); +int (*udev_monitor_filter_add_match_subsystem_devtype_dylibloader_wrapper_libudev)(struct udev_monitor*,const char*,const char*); +int (*udev_monitor_filter_add_match_tag_dylibloader_wrapper_libudev)(struct udev_monitor*,const char*); +int (*udev_monitor_filter_update_dylibloader_wrapper_libudev)(struct udev_monitor*); +int (*udev_monitor_filter_remove_dylibloader_wrapper_libudev)(struct udev_monitor*); +struct udev_enumerate* (*udev_enumerate_ref_dylibloader_wrapper_libudev)(struct udev_enumerate*); +struct udev_enumerate* (*udev_enumerate_unref_dylibloader_wrapper_libudev)(struct udev_enumerate*); +struct udev* (*udev_enumerate_get_udev_dylibloader_wrapper_libudev)(struct udev_enumerate*); +struct udev_enumerate* (*udev_enumerate_new_dylibloader_wrapper_libudev)(struct udev*); +int (*udev_enumerate_add_match_subsystem_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +int (*udev_enumerate_add_nomatch_subsystem_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +int (*udev_enumerate_add_match_sysattr_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*,const char*); +int (*udev_enumerate_add_nomatch_sysattr_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*,const char*); +int (*udev_enumerate_add_match_property_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*,const char*); +int (*udev_enumerate_add_match_sysname_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +int (*udev_enumerate_add_match_tag_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +int (*udev_enumerate_add_match_parent_dylibloader_wrapper_libudev)(struct udev_enumerate*,struct udev_device*); +int (*udev_enumerate_add_match_is_initialized_dylibloader_wrapper_libudev)(struct udev_enumerate*); +int (*udev_enumerate_add_syspath_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +int (*udev_enumerate_scan_devices_dylibloader_wrapper_libudev)(struct udev_enumerate*); +int (*udev_enumerate_scan_subsystems_dylibloader_wrapper_libudev)(struct udev_enumerate*); +struct udev_list_entry* (*udev_enumerate_get_list_entry_dylibloader_wrapper_libudev)(struct udev_enumerate*); +struct udev_queue* (*udev_queue_ref_dylibloader_wrapper_libudev)(struct udev_queue*); +struct udev_queue* (*udev_queue_unref_dylibloader_wrapper_libudev)(struct udev_queue*); +struct udev* (*udev_queue_get_udev_dylibloader_wrapper_libudev)(struct udev_queue*); +struct udev_queue* (*udev_queue_new_dylibloader_wrapper_libudev)(struct udev*); +unsigned long long int (*udev_queue_get_kernel_seqnum_dylibloader_wrapper_libudev)(struct udev_queue*); +unsigned long long int (*udev_queue_get_udev_seqnum_dylibloader_wrapper_libudev)(struct udev_queue*); +int (*udev_queue_get_udev_is_active_dylibloader_wrapper_libudev)(struct udev_queue*); +int (*udev_queue_get_queue_is_empty_dylibloader_wrapper_libudev)(struct udev_queue*); +int (*udev_queue_get_seqnum_is_finished_dylibloader_wrapper_libudev)(struct udev_queue*, unsigned long long int); +int (*udev_queue_get_seqnum_sequence_is_finished_dylibloader_wrapper_libudev)(struct udev_queue*, unsigned long long int, unsigned long long int); +int (*udev_queue_get_fd_dylibloader_wrapper_libudev)(struct udev_queue*); +int (*udev_queue_flush_dylibloader_wrapper_libudev)(struct udev_queue*); +struct udev_list_entry* (*udev_queue_get_queued_list_entry_dylibloader_wrapper_libudev)(struct udev_queue*); +struct udev_hwdb* (*udev_hwdb_new_dylibloader_wrapper_libudev)(struct udev*); +struct udev_hwdb* (*udev_hwdb_ref_dylibloader_wrapper_libudev)(struct udev_hwdb*); +struct udev_hwdb* (*udev_hwdb_unref_dylibloader_wrapper_libudev)(struct udev_hwdb*); +struct udev_list_entry* (*udev_hwdb_get_properties_list_entry_dylibloader_wrapper_libudev)(struct udev_hwdb*,const char*, unsigned); +int (*udev_util_encode_string_dylibloader_wrapper_libudev)(const char*, char*, size_t); +int initialize_libudev(int verbose) { + void *handle; + char *error; + handle = dlopen("libudev.so.1", RTLD_LAZY); + if (!handle) { + if (verbose) { + fprintf(stderr, "%s\n", dlerror()); + } + return(1); + } + dlerror(); +// udev_ref + *(void **) (&udev_ref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_ref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_unref + *(void **) (&udev_unref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_unref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_new + *(void **) (&udev_new_dylibloader_wrapper_libudev) = dlsym(handle, "udev_new"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_set_log_fn + *(void **) (&udev_set_log_fn_dylibloader_wrapper_libudev) = dlsym(handle, "udev_set_log_fn"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_get_log_priority + *(void **) (&udev_get_log_priority_dylibloader_wrapper_libudev) = dlsym(handle, "udev_get_log_priority"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_set_log_priority + *(void **) (&udev_set_log_priority_dylibloader_wrapper_libudev) = dlsym(handle, "udev_set_log_priority"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_get_userdata + *(void **) (&udev_get_userdata_dylibloader_wrapper_libudev) = dlsym(handle, "udev_get_userdata"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_set_userdata + *(void **) (&udev_set_userdata_dylibloader_wrapper_libudev) = dlsym(handle, "udev_set_userdata"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_list_entry_get_next + *(void **) (&udev_list_entry_get_next_dylibloader_wrapper_libudev) = dlsym(handle, "udev_list_entry_get_next"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_list_entry_get_by_name + *(void **) (&udev_list_entry_get_by_name_dylibloader_wrapper_libudev) = dlsym(handle, "udev_list_entry_get_by_name"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_list_entry_get_name + *(void **) (&udev_list_entry_get_name_dylibloader_wrapper_libudev) = dlsym(handle, "udev_list_entry_get_name"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_list_entry_get_value + *(void **) (&udev_list_entry_get_value_dylibloader_wrapper_libudev) = dlsym(handle, "udev_list_entry_get_value"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_ref + *(void **) (&udev_device_ref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_ref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_unref + *(void **) (&udev_device_unref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_unref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_udev + *(void **) (&udev_device_get_udev_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_udev"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_new_from_syspath + *(void **) (&udev_device_new_from_syspath_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_new_from_syspath"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_new_from_devnum + *(void **) (&udev_device_new_from_devnum_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_new_from_devnum"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_new_from_subsystem_sysname + *(void **) (&udev_device_new_from_subsystem_sysname_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_new_from_subsystem_sysname"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_new_from_device_id + *(void **) (&udev_device_new_from_device_id_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_new_from_device_id"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_new_from_environment + *(void **) (&udev_device_new_from_environment_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_new_from_environment"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_parent + *(void **) (&udev_device_get_parent_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_parent"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_parent_with_subsystem_devtype + *(void **) (&udev_device_get_parent_with_subsystem_devtype_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_parent_with_subsystem_devtype"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_devpath + *(void **) (&udev_device_get_devpath_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_devpath"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_subsystem + *(void **) (&udev_device_get_subsystem_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_subsystem"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_devtype + *(void **) (&udev_device_get_devtype_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_devtype"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_syspath + *(void **) (&udev_device_get_syspath_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_syspath"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_sysname + *(void **) (&udev_device_get_sysname_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_sysname"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_sysnum + *(void **) (&udev_device_get_sysnum_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_sysnum"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_devnode + *(void **) (&udev_device_get_devnode_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_devnode"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_is_initialized + *(void **) (&udev_device_get_is_initialized_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_is_initialized"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_devlinks_list_entry + *(void **) (&udev_device_get_devlinks_list_entry_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_devlinks_list_entry"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_properties_list_entry + *(void **) (&udev_device_get_properties_list_entry_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_properties_list_entry"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_tags_list_entry + *(void **) (&udev_device_get_tags_list_entry_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_tags_list_entry"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_sysattr_list_entry + *(void **) (&udev_device_get_sysattr_list_entry_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_sysattr_list_entry"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_property_value + *(void **) (&udev_device_get_property_value_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_property_value"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_driver + *(void **) (&udev_device_get_driver_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_driver"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_devnum + *(void **) (&udev_device_get_devnum_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_devnum"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_action + *(void **) (&udev_device_get_action_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_action"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_seqnum + *(void **) (&udev_device_get_seqnum_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_seqnum"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_usec_since_initialized + *(void **) (&udev_device_get_usec_since_initialized_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_usec_since_initialized"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_get_sysattr_value + *(void **) (&udev_device_get_sysattr_value_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_get_sysattr_value"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_set_sysattr_value + *(void **) (&udev_device_set_sysattr_value_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_set_sysattr_value"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_device_has_tag + *(void **) (&udev_device_has_tag_dylibloader_wrapper_libudev) = dlsym(handle, "udev_device_has_tag"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_ref + *(void **) (&udev_monitor_ref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_ref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_unref + *(void **) (&udev_monitor_unref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_unref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_get_udev + *(void **) (&udev_monitor_get_udev_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_get_udev"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_new_from_netlink + *(void **) (&udev_monitor_new_from_netlink_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_new_from_netlink"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_enable_receiving + *(void **) (&udev_monitor_enable_receiving_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_enable_receiving"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_set_receive_buffer_size + *(void **) (&udev_monitor_set_receive_buffer_size_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_set_receive_buffer_size"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_get_fd + *(void **) (&udev_monitor_get_fd_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_get_fd"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_receive_device + *(void **) (&udev_monitor_receive_device_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_receive_device"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_filter_add_match_subsystem_devtype + *(void **) (&udev_monitor_filter_add_match_subsystem_devtype_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_filter_add_match_subsystem_devtype"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_filter_add_match_tag + *(void **) (&udev_monitor_filter_add_match_tag_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_filter_add_match_tag"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_filter_update + *(void **) (&udev_monitor_filter_update_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_filter_update"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_monitor_filter_remove + *(void **) (&udev_monitor_filter_remove_dylibloader_wrapper_libudev) = dlsym(handle, "udev_monitor_filter_remove"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_ref + *(void **) (&udev_enumerate_ref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_ref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_unref + *(void **) (&udev_enumerate_unref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_unref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_get_udev + *(void **) (&udev_enumerate_get_udev_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_get_udev"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_new + *(void **) (&udev_enumerate_new_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_new"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_match_subsystem + *(void **) (&udev_enumerate_add_match_subsystem_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_match_subsystem"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_nomatch_subsystem + *(void **) (&udev_enumerate_add_nomatch_subsystem_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_nomatch_subsystem"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_match_sysattr + *(void **) (&udev_enumerate_add_match_sysattr_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_match_sysattr"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_nomatch_sysattr + *(void **) (&udev_enumerate_add_nomatch_sysattr_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_nomatch_sysattr"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_match_property + *(void **) (&udev_enumerate_add_match_property_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_match_property"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_match_sysname + *(void **) (&udev_enumerate_add_match_sysname_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_match_sysname"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_match_tag + *(void **) (&udev_enumerate_add_match_tag_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_match_tag"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_match_parent + *(void **) (&udev_enumerate_add_match_parent_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_match_parent"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_match_is_initialized + *(void **) (&udev_enumerate_add_match_is_initialized_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_match_is_initialized"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_add_syspath + *(void **) (&udev_enumerate_add_syspath_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_add_syspath"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_scan_devices + *(void **) (&udev_enumerate_scan_devices_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_scan_devices"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_scan_subsystems + *(void **) (&udev_enumerate_scan_subsystems_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_scan_subsystems"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_enumerate_get_list_entry + *(void **) (&udev_enumerate_get_list_entry_dylibloader_wrapper_libudev) = dlsym(handle, "udev_enumerate_get_list_entry"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_ref + *(void **) (&udev_queue_ref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_ref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_unref + *(void **) (&udev_queue_unref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_unref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_udev + *(void **) (&udev_queue_get_udev_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_udev"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_new + *(void **) (&udev_queue_new_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_new"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_kernel_seqnum + *(void **) (&udev_queue_get_kernel_seqnum_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_kernel_seqnum"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_udev_seqnum + *(void **) (&udev_queue_get_udev_seqnum_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_udev_seqnum"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_udev_is_active + *(void **) (&udev_queue_get_udev_is_active_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_udev_is_active"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_queue_is_empty + *(void **) (&udev_queue_get_queue_is_empty_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_queue_is_empty"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_seqnum_is_finished + *(void **) (&udev_queue_get_seqnum_is_finished_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_seqnum_is_finished"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_seqnum_sequence_is_finished + *(void **) (&udev_queue_get_seqnum_sequence_is_finished_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_seqnum_sequence_is_finished"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_fd + *(void **) (&udev_queue_get_fd_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_fd"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_flush + *(void **) (&udev_queue_flush_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_flush"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_queue_get_queued_list_entry + *(void **) (&udev_queue_get_queued_list_entry_dylibloader_wrapper_libudev) = dlsym(handle, "udev_queue_get_queued_list_entry"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_hwdb_new + *(void **) (&udev_hwdb_new_dylibloader_wrapper_libudev) = dlsym(handle, "udev_hwdb_new"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_hwdb_ref + *(void **) (&udev_hwdb_ref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_hwdb_ref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_hwdb_unref + *(void **) (&udev_hwdb_unref_dylibloader_wrapper_libudev) = dlsym(handle, "udev_hwdb_unref"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_hwdb_get_properties_list_entry + *(void **) (&udev_hwdb_get_properties_list_entry_dylibloader_wrapper_libudev) = dlsym(handle, "udev_hwdb_get_properties_list_entry"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// udev_util_encode_string + *(void **) (&udev_util_encode_string_dylibloader_wrapper_libudev) = dlsym(handle, "udev_util_encode_string"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +return 0; +} diff --git a/platform/linuxbsd/libudev-so_wrap.h b/platform/linuxbsd/libudev-so_wrap.h new file mode 100644 index 0000000000..dd43fd1191 --- /dev/null +++ b/platform/linuxbsd/libudev-so_wrap.h @@ -0,0 +1,378 @@ +#ifndef DYLIBLOAD_WRAPPER_LIBUDEV +#define DYLIBLOAD_WRAPPER_LIBUDEV +// This file is generated. Do not edit! +// see https://github.com/hpvb/dynload-wrapper for details +// generated by /home/hp/Projects/godot/pulse/generate-wrapper.py 0.3 on 2021-02-20 00:08:59 +// flags: /home/hp/Projects/godot/pulse/generate-wrapper.py --include /usr/include/libudev.h --sys-include <libudev.h> --soname libudev.so.1 --init-name libudev --omit-prefix gnu_ --output-header libudev-so_wrap.h --output-implementation libudev-so_wrap.c +// +#include <stdint.h> + +#define udev_ref udev_ref_dylibloader_orig_libudev +#define udev_unref udev_unref_dylibloader_orig_libudev +#define udev_new udev_new_dylibloader_orig_libudev +#define udev_set_log_fn udev_set_log_fn_dylibloader_orig_libudev +#define udev_get_log_priority udev_get_log_priority_dylibloader_orig_libudev +#define udev_set_log_priority udev_set_log_priority_dylibloader_orig_libudev +#define udev_get_userdata udev_get_userdata_dylibloader_orig_libudev +#define udev_set_userdata udev_set_userdata_dylibloader_orig_libudev +#define udev_list_entry_get_next udev_list_entry_get_next_dylibloader_orig_libudev +#define udev_list_entry_get_by_name udev_list_entry_get_by_name_dylibloader_orig_libudev +#define udev_list_entry_get_name udev_list_entry_get_name_dylibloader_orig_libudev +#define udev_list_entry_get_value udev_list_entry_get_value_dylibloader_orig_libudev +#define udev_device_ref udev_device_ref_dylibloader_orig_libudev +#define udev_device_unref udev_device_unref_dylibloader_orig_libudev +#define udev_device_get_udev udev_device_get_udev_dylibloader_orig_libudev +#define udev_device_new_from_syspath udev_device_new_from_syspath_dylibloader_orig_libudev +#define udev_device_new_from_devnum udev_device_new_from_devnum_dylibloader_orig_libudev +#define udev_device_new_from_subsystem_sysname udev_device_new_from_subsystem_sysname_dylibloader_orig_libudev +#define udev_device_new_from_device_id udev_device_new_from_device_id_dylibloader_orig_libudev +#define udev_device_new_from_environment udev_device_new_from_environment_dylibloader_orig_libudev +#define udev_device_get_parent udev_device_get_parent_dylibloader_orig_libudev +#define udev_device_get_parent_with_subsystem_devtype udev_device_get_parent_with_subsystem_devtype_dylibloader_orig_libudev +#define udev_device_get_devpath udev_device_get_devpath_dylibloader_orig_libudev +#define udev_device_get_subsystem udev_device_get_subsystem_dylibloader_orig_libudev +#define udev_device_get_devtype udev_device_get_devtype_dylibloader_orig_libudev +#define udev_device_get_syspath udev_device_get_syspath_dylibloader_orig_libudev +#define udev_device_get_sysname udev_device_get_sysname_dylibloader_orig_libudev +#define udev_device_get_sysnum udev_device_get_sysnum_dylibloader_orig_libudev +#define udev_device_get_devnode udev_device_get_devnode_dylibloader_orig_libudev +#define udev_device_get_is_initialized udev_device_get_is_initialized_dylibloader_orig_libudev +#define udev_device_get_devlinks_list_entry udev_device_get_devlinks_list_entry_dylibloader_orig_libudev +#define udev_device_get_properties_list_entry udev_device_get_properties_list_entry_dylibloader_orig_libudev +#define udev_device_get_tags_list_entry udev_device_get_tags_list_entry_dylibloader_orig_libudev +#define udev_device_get_sysattr_list_entry udev_device_get_sysattr_list_entry_dylibloader_orig_libudev +#define udev_device_get_property_value udev_device_get_property_value_dylibloader_orig_libudev +#define udev_device_get_driver udev_device_get_driver_dylibloader_orig_libudev +#define udev_device_get_devnum udev_device_get_devnum_dylibloader_orig_libudev +#define udev_device_get_action udev_device_get_action_dylibloader_orig_libudev +#define udev_device_get_seqnum udev_device_get_seqnum_dylibloader_orig_libudev +#define udev_device_get_usec_since_initialized udev_device_get_usec_since_initialized_dylibloader_orig_libudev +#define udev_device_get_sysattr_value udev_device_get_sysattr_value_dylibloader_orig_libudev +#define udev_device_set_sysattr_value udev_device_set_sysattr_value_dylibloader_orig_libudev +#define udev_device_has_tag udev_device_has_tag_dylibloader_orig_libudev +#define udev_monitor_ref udev_monitor_ref_dylibloader_orig_libudev +#define udev_monitor_unref udev_monitor_unref_dylibloader_orig_libudev +#define udev_monitor_get_udev udev_monitor_get_udev_dylibloader_orig_libudev +#define udev_monitor_new_from_netlink udev_monitor_new_from_netlink_dylibloader_orig_libudev +#define udev_monitor_enable_receiving udev_monitor_enable_receiving_dylibloader_orig_libudev +#define udev_monitor_set_receive_buffer_size udev_monitor_set_receive_buffer_size_dylibloader_orig_libudev +#define udev_monitor_get_fd udev_monitor_get_fd_dylibloader_orig_libudev +#define udev_monitor_receive_device udev_monitor_receive_device_dylibloader_orig_libudev +#define udev_monitor_filter_add_match_subsystem_devtype udev_monitor_filter_add_match_subsystem_devtype_dylibloader_orig_libudev +#define udev_monitor_filter_add_match_tag udev_monitor_filter_add_match_tag_dylibloader_orig_libudev +#define udev_monitor_filter_update udev_monitor_filter_update_dylibloader_orig_libudev +#define udev_monitor_filter_remove udev_monitor_filter_remove_dylibloader_orig_libudev +#define udev_enumerate_ref udev_enumerate_ref_dylibloader_orig_libudev +#define udev_enumerate_unref udev_enumerate_unref_dylibloader_orig_libudev +#define udev_enumerate_get_udev udev_enumerate_get_udev_dylibloader_orig_libudev +#define udev_enumerate_new udev_enumerate_new_dylibloader_orig_libudev +#define udev_enumerate_add_match_subsystem udev_enumerate_add_match_subsystem_dylibloader_orig_libudev +#define udev_enumerate_add_nomatch_subsystem udev_enumerate_add_nomatch_subsystem_dylibloader_orig_libudev +#define udev_enumerate_add_match_sysattr udev_enumerate_add_match_sysattr_dylibloader_orig_libudev +#define udev_enumerate_add_nomatch_sysattr udev_enumerate_add_nomatch_sysattr_dylibloader_orig_libudev +#define udev_enumerate_add_match_property udev_enumerate_add_match_property_dylibloader_orig_libudev +#define udev_enumerate_add_match_sysname udev_enumerate_add_match_sysname_dylibloader_orig_libudev +#define udev_enumerate_add_match_tag udev_enumerate_add_match_tag_dylibloader_orig_libudev +#define udev_enumerate_add_match_parent udev_enumerate_add_match_parent_dylibloader_orig_libudev +#define udev_enumerate_add_match_is_initialized udev_enumerate_add_match_is_initialized_dylibloader_orig_libudev +#define udev_enumerate_add_syspath udev_enumerate_add_syspath_dylibloader_orig_libudev +#define udev_enumerate_scan_devices udev_enumerate_scan_devices_dylibloader_orig_libudev +#define udev_enumerate_scan_subsystems udev_enumerate_scan_subsystems_dylibloader_orig_libudev +#define udev_enumerate_get_list_entry udev_enumerate_get_list_entry_dylibloader_orig_libudev +#define udev_queue_ref udev_queue_ref_dylibloader_orig_libudev +#define udev_queue_unref udev_queue_unref_dylibloader_orig_libudev +#define udev_queue_get_udev udev_queue_get_udev_dylibloader_orig_libudev +#define udev_queue_new udev_queue_new_dylibloader_orig_libudev +#define udev_queue_get_kernel_seqnum udev_queue_get_kernel_seqnum_dylibloader_orig_libudev +#define udev_queue_get_udev_seqnum udev_queue_get_udev_seqnum_dylibloader_orig_libudev +#define udev_queue_get_udev_is_active udev_queue_get_udev_is_active_dylibloader_orig_libudev +#define udev_queue_get_queue_is_empty udev_queue_get_queue_is_empty_dylibloader_orig_libudev +#define udev_queue_get_seqnum_is_finished udev_queue_get_seqnum_is_finished_dylibloader_orig_libudev +#define udev_queue_get_seqnum_sequence_is_finished udev_queue_get_seqnum_sequence_is_finished_dylibloader_orig_libudev +#define udev_queue_get_fd udev_queue_get_fd_dylibloader_orig_libudev +#define udev_queue_flush udev_queue_flush_dylibloader_orig_libudev +#define udev_queue_get_queued_list_entry udev_queue_get_queued_list_entry_dylibloader_orig_libudev +#define udev_hwdb_new udev_hwdb_new_dylibloader_orig_libudev +#define udev_hwdb_ref udev_hwdb_ref_dylibloader_orig_libudev +#define udev_hwdb_unref udev_hwdb_unref_dylibloader_orig_libudev +#define udev_hwdb_get_properties_list_entry udev_hwdb_get_properties_list_entry_dylibloader_orig_libudev +#define udev_util_encode_string udev_util_encode_string_dylibloader_orig_libudev +#include <libudev.h> +#undef udev_ref +#undef udev_unref +#undef udev_new +#undef udev_set_log_fn +#undef udev_get_log_priority +#undef udev_set_log_priority +#undef udev_get_userdata +#undef udev_set_userdata +#undef udev_list_entry_get_next +#undef udev_list_entry_get_by_name +#undef udev_list_entry_get_name +#undef udev_list_entry_get_value +#undef udev_device_ref +#undef udev_device_unref +#undef udev_device_get_udev +#undef udev_device_new_from_syspath +#undef udev_device_new_from_devnum +#undef udev_device_new_from_subsystem_sysname +#undef udev_device_new_from_device_id +#undef udev_device_new_from_environment +#undef udev_device_get_parent +#undef udev_device_get_parent_with_subsystem_devtype +#undef udev_device_get_devpath +#undef udev_device_get_subsystem +#undef udev_device_get_devtype +#undef udev_device_get_syspath +#undef udev_device_get_sysname +#undef udev_device_get_sysnum +#undef udev_device_get_devnode +#undef udev_device_get_is_initialized +#undef udev_device_get_devlinks_list_entry +#undef udev_device_get_properties_list_entry +#undef udev_device_get_tags_list_entry +#undef udev_device_get_sysattr_list_entry +#undef udev_device_get_property_value +#undef udev_device_get_driver +#undef udev_device_get_devnum +#undef udev_device_get_action +#undef udev_device_get_seqnum +#undef udev_device_get_usec_since_initialized +#undef udev_device_get_sysattr_value +#undef udev_device_set_sysattr_value +#undef udev_device_has_tag +#undef udev_monitor_ref +#undef udev_monitor_unref +#undef udev_monitor_get_udev +#undef udev_monitor_new_from_netlink +#undef udev_monitor_enable_receiving +#undef udev_monitor_set_receive_buffer_size +#undef udev_monitor_get_fd +#undef udev_monitor_receive_device +#undef udev_monitor_filter_add_match_subsystem_devtype +#undef udev_monitor_filter_add_match_tag +#undef udev_monitor_filter_update +#undef udev_monitor_filter_remove +#undef udev_enumerate_ref +#undef udev_enumerate_unref +#undef udev_enumerate_get_udev +#undef udev_enumerate_new +#undef udev_enumerate_add_match_subsystem +#undef udev_enumerate_add_nomatch_subsystem +#undef udev_enumerate_add_match_sysattr +#undef udev_enumerate_add_nomatch_sysattr +#undef udev_enumerate_add_match_property +#undef udev_enumerate_add_match_sysname +#undef udev_enumerate_add_match_tag +#undef udev_enumerate_add_match_parent +#undef udev_enumerate_add_match_is_initialized +#undef udev_enumerate_add_syspath +#undef udev_enumerate_scan_devices +#undef udev_enumerate_scan_subsystems +#undef udev_enumerate_get_list_entry +#undef udev_queue_ref +#undef udev_queue_unref +#undef udev_queue_get_udev +#undef udev_queue_new +#undef udev_queue_get_kernel_seqnum +#undef udev_queue_get_udev_seqnum +#undef udev_queue_get_udev_is_active +#undef udev_queue_get_queue_is_empty +#undef udev_queue_get_seqnum_is_finished +#undef udev_queue_get_seqnum_sequence_is_finished +#undef udev_queue_get_fd +#undef udev_queue_flush +#undef udev_queue_get_queued_list_entry +#undef udev_hwdb_new +#undef udev_hwdb_ref +#undef udev_hwdb_unref +#undef udev_hwdb_get_properties_list_entry +#undef udev_util_encode_string +#ifdef __cplusplus +extern "C" { +#endif +#define udev_ref udev_ref_dylibloader_wrapper_libudev +#define udev_unref udev_unref_dylibloader_wrapper_libudev +#define udev_new udev_new_dylibloader_wrapper_libudev +#define udev_set_log_fn udev_set_log_fn_dylibloader_wrapper_libudev +#define udev_get_log_priority udev_get_log_priority_dylibloader_wrapper_libudev +#define udev_set_log_priority udev_set_log_priority_dylibloader_wrapper_libudev +#define udev_get_userdata udev_get_userdata_dylibloader_wrapper_libudev +#define udev_set_userdata udev_set_userdata_dylibloader_wrapper_libudev +#define udev_list_entry_get_next udev_list_entry_get_next_dylibloader_wrapper_libudev +#define udev_list_entry_get_by_name udev_list_entry_get_by_name_dylibloader_wrapper_libudev +#define udev_list_entry_get_name udev_list_entry_get_name_dylibloader_wrapper_libudev +#define udev_list_entry_get_value udev_list_entry_get_value_dylibloader_wrapper_libudev +#define udev_device_ref udev_device_ref_dylibloader_wrapper_libudev +#define udev_device_unref udev_device_unref_dylibloader_wrapper_libudev +#define udev_device_get_udev udev_device_get_udev_dylibloader_wrapper_libudev +#define udev_device_new_from_syspath udev_device_new_from_syspath_dylibloader_wrapper_libudev +#define udev_device_new_from_devnum udev_device_new_from_devnum_dylibloader_wrapper_libudev +#define udev_device_new_from_subsystem_sysname udev_device_new_from_subsystem_sysname_dylibloader_wrapper_libudev +#define udev_device_new_from_device_id udev_device_new_from_device_id_dylibloader_wrapper_libudev +#define udev_device_new_from_environment udev_device_new_from_environment_dylibloader_wrapper_libudev +#define udev_device_get_parent udev_device_get_parent_dylibloader_wrapper_libudev +#define udev_device_get_parent_with_subsystem_devtype udev_device_get_parent_with_subsystem_devtype_dylibloader_wrapper_libudev +#define udev_device_get_devpath udev_device_get_devpath_dylibloader_wrapper_libudev +#define udev_device_get_subsystem udev_device_get_subsystem_dylibloader_wrapper_libudev +#define udev_device_get_devtype udev_device_get_devtype_dylibloader_wrapper_libudev +#define udev_device_get_syspath udev_device_get_syspath_dylibloader_wrapper_libudev +#define udev_device_get_sysname udev_device_get_sysname_dylibloader_wrapper_libudev +#define udev_device_get_sysnum udev_device_get_sysnum_dylibloader_wrapper_libudev +#define udev_device_get_devnode udev_device_get_devnode_dylibloader_wrapper_libudev +#define udev_device_get_is_initialized udev_device_get_is_initialized_dylibloader_wrapper_libudev +#define udev_device_get_devlinks_list_entry udev_device_get_devlinks_list_entry_dylibloader_wrapper_libudev +#define udev_device_get_properties_list_entry udev_device_get_properties_list_entry_dylibloader_wrapper_libudev +#define udev_device_get_tags_list_entry udev_device_get_tags_list_entry_dylibloader_wrapper_libudev +#define udev_device_get_sysattr_list_entry udev_device_get_sysattr_list_entry_dylibloader_wrapper_libudev +#define udev_device_get_property_value udev_device_get_property_value_dylibloader_wrapper_libudev +#define udev_device_get_driver udev_device_get_driver_dylibloader_wrapper_libudev +#define udev_device_get_devnum udev_device_get_devnum_dylibloader_wrapper_libudev +#define udev_device_get_action udev_device_get_action_dylibloader_wrapper_libudev +#define udev_device_get_seqnum udev_device_get_seqnum_dylibloader_wrapper_libudev +#define udev_device_get_usec_since_initialized udev_device_get_usec_since_initialized_dylibloader_wrapper_libudev +#define udev_device_get_sysattr_value udev_device_get_sysattr_value_dylibloader_wrapper_libudev +#define udev_device_set_sysattr_value udev_device_set_sysattr_value_dylibloader_wrapper_libudev +#define udev_device_has_tag udev_device_has_tag_dylibloader_wrapper_libudev +#define udev_monitor_ref udev_monitor_ref_dylibloader_wrapper_libudev +#define udev_monitor_unref udev_monitor_unref_dylibloader_wrapper_libudev +#define udev_monitor_get_udev udev_monitor_get_udev_dylibloader_wrapper_libudev +#define udev_monitor_new_from_netlink udev_monitor_new_from_netlink_dylibloader_wrapper_libudev +#define udev_monitor_enable_receiving udev_monitor_enable_receiving_dylibloader_wrapper_libudev +#define udev_monitor_set_receive_buffer_size udev_monitor_set_receive_buffer_size_dylibloader_wrapper_libudev +#define udev_monitor_get_fd udev_monitor_get_fd_dylibloader_wrapper_libudev +#define udev_monitor_receive_device udev_monitor_receive_device_dylibloader_wrapper_libudev +#define udev_monitor_filter_add_match_subsystem_devtype udev_monitor_filter_add_match_subsystem_devtype_dylibloader_wrapper_libudev +#define udev_monitor_filter_add_match_tag udev_monitor_filter_add_match_tag_dylibloader_wrapper_libudev +#define udev_monitor_filter_update udev_monitor_filter_update_dylibloader_wrapper_libudev +#define udev_monitor_filter_remove udev_monitor_filter_remove_dylibloader_wrapper_libudev +#define udev_enumerate_ref udev_enumerate_ref_dylibloader_wrapper_libudev +#define udev_enumerate_unref udev_enumerate_unref_dylibloader_wrapper_libudev +#define udev_enumerate_get_udev udev_enumerate_get_udev_dylibloader_wrapper_libudev +#define udev_enumerate_new udev_enumerate_new_dylibloader_wrapper_libudev +#define udev_enumerate_add_match_subsystem udev_enumerate_add_match_subsystem_dylibloader_wrapper_libudev +#define udev_enumerate_add_nomatch_subsystem udev_enumerate_add_nomatch_subsystem_dylibloader_wrapper_libudev +#define udev_enumerate_add_match_sysattr udev_enumerate_add_match_sysattr_dylibloader_wrapper_libudev +#define udev_enumerate_add_nomatch_sysattr udev_enumerate_add_nomatch_sysattr_dylibloader_wrapper_libudev +#define udev_enumerate_add_match_property udev_enumerate_add_match_property_dylibloader_wrapper_libudev +#define udev_enumerate_add_match_sysname udev_enumerate_add_match_sysname_dylibloader_wrapper_libudev +#define udev_enumerate_add_match_tag udev_enumerate_add_match_tag_dylibloader_wrapper_libudev +#define udev_enumerate_add_match_parent udev_enumerate_add_match_parent_dylibloader_wrapper_libudev +#define udev_enumerate_add_match_is_initialized udev_enumerate_add_match_is_initialized_dylibloader_wrapper_libudev +#define udev_enumerate_add_syspath udev_enumerate_add_syspath_dylibloader_wrapper_libudev +#define udev_enumerate_scan_devices udev_enumerate_scan_devices_dylibloader_wrapper_libudev +#define udev_enumerate_scan_subsystems udev_enumerate_scan_subsystems_dylibloader_wrapper_libudev +#define udev_enumerate_get_list_entry udev_enumerate_get_list_entry_dylibloader_wrapper_libudev +#define udev_queue_ref udev_queue_ref_dylibloader_wrapper_libudev +#define udev_queue_unref udev_queue_unref_dylibloader_wrapper_libudev +#define udev_queue_get_udev udev_queue_get_udev_dylibloader_wrapper_libudev +#define udev_queue_new udev_queue_new_dylibloader_wrapper_libudev +#define udev_queue_get_kernel_seqnum udev_queue_get_kernel_seqnum_dylibloader_wrapper_libudev +#define udev_queue_get_udev_seqnum udev_queue_get_udev_seqnum_dylibloader_wrapper_libudev +#define udev_queue_get_udev_is_active udev_queue_get_udev_is_active_dylibloader_wrapper_libudev +#define udev_queue_get_queue_is_empty udev_queue_get_queue_is_empty_dylibloader_wrapper_libudev +#define udev_queue_get_seqnum_is_finished udev_queue_get_seqnum_is_finished_dylibloader_wrapper_libudev +#define udev_queue_get_seqnum_sequence_is_finished udev_queue_get_seqnum_sequence_is_finished_dylibloader_wrapper_libudev +#define udev_queue_get_fd udev_queue_get_fd_dylibloader_wrapper_libudev +#define udev_queue_flush udev_queue_flush_dylibloader_wrapper_libudev +#define udev_queue_get_queued_list_entry udev_queue_get_queued_list_entry_dylibloader_wrapper_libudev +#define udev_hwdb_new udev_hwdb_new_dylibloader_wrapper_libudev +#define udev_hwdb_ref udev_hwdb_ref_dylibloader_wrapper_libudev +#define udev_hwdb_unref udev_hwdb_unref_dylibloader_wrapper_libudev +#define udev_hwdb_get_properties_list_entry udev_hwdb_get_properties_list_entry_dylibloader_wrapper_libudev +#define udev_util_encode_string udev_util_encode_string_dylibloader_wrapper_libudev +extern struct udev* (*udev_ref_dylibloader_wrapper_libudev)(struct udev*); +extern struct udev* (*udev_unref_dylibloader_wrapper_libudev)(struct udev*); +extern struct udev* (*udev_new_dylibloader_wrapper_libudev)( void); +extern void (*udev_set_log_fn_dylibloader_wrapper_libudev)(struct udev*, void*); +extern int (*udev_get_log_priority_dylibloader_wrapper_libudev)(struct udev*); +extern void (*udev_set_log_priority_dylibloader_wrapper_libudev)(struct udev*, int); +extern void* (*udev_get_userdata_dylibloader_wrapper_libudev)(struct udev*); +extern void (*udev_set_userdata_dylibloader_wrapper_libudev)(struct udev*, void*); +extern struct udev_list_entry* (*udev_list_entry_get_next_dylibloader_wrapper_libudev)(struct udev_list_entry*); +extern struct udev_list_entry* (*udev_list_entry_get_by_name_dylibloader_wrapper_libudev)(struct udev_list_entry*,const char*); +extern const char* (*udev_list_entry_get_name_dylibloader_wrapper_libudev)(struct udev_list_entry*); +extern const char* (*udev_list_entry_get_value_dylibloader_wrapper_libudev)(struct udev_list_entry*); +extern struct udev_device* (*udev_device_ref_dylibloader_wrapper_libudev)(struct udev_device*); +extern struct udev_device* (*udev_device_unref_dylibloader_wrapper_libudev)(struct udev_device*); +extern struct udev* (*udev_device_get_udev_dylibloader_wrapper_libudev)(struct udev_device*); +extern struct udev_device* (*udev_device_new_from_syspath_dylibloader_wrapper_libudev)(struct udev*,const char*); +extern struct udev_device* (*udev_device_new_from_devnum_dylibloader_wrapper_libudev)(struct udev*, char, dev_t); +extern struct udev_device* (*udev_device_new_from_subsystem_sysname_dylibloader_wrapper_libudev)(struct udev*,const char*,const char*); +extern struct udev_device* (*udev_device_new_from_device_id_dylibloader_wrapper_libudev)(struct udev*,const char*); +extern struct udev_device* (*udev_device_new_from_environment_dylibloader_wrapper_libudev)(struct udev*); +extern struct udev_device* (*udev_device_get_parent_dylibloader_wrapper_libudev)(struct udev_device*); +extern struct udev_device* (*udev_device_get_parent_with_subsystem_devtype_dylibloader_wrapper_libudev)(struct udev_device*,const char*,const char*); +extern const char* (*udev_device_get_devpath_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_subsystem_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_devtype_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_syspath_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_sysname_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_sysnum_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_devnode_dylibloader_wrapper_libudev)(struct udev_device*); +extern int (*udev_device_get_is_initialized_dylibloader_wrapper_libudev)(struct udev_device*); +extern struct udev_list_entry* (*udev_device_get_devlinks_list_entry_dylibloader_wrapper_libudev)(struct udev_device*); +extern struct udev_list_entry* (*udev_device_get_properties_list_entry_dylibloader_wrapper_libudev)(struct udev_device*); +extern struct udev_list_entry* (*udev_device_get_tags_list_entry_dylibloader_wrapper_libudev)(struct udev_device*); +extern struct udev_list_entry* (*udev_device_get_sysattr_list_entry_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_property_value_dylibloader_wrapper_libudev)(struct udev_device*,const char*); +extern const char* (*udev_device_get_driver_dylibloader_wrapper_libudev)(struct udev_device*); +extern dev_t (*udev_device_get_devnum_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_action_dylibloader_wrapper_libudev)(struct udev_device*); +extern unsigned long long int (*udev_device_get_seqnum_dylibloader_wrapper_libudev)(struct udev_device*); +extern unsigned long long int (*udev_device_get_usec_since_initialized_dylibloader_wrapper_libudev)(struct udev_device*); +extern const char* (*udev_device_get_sysattr_value_dylibloader_wrapper_libudev)(struct udev_device*,const char*); +extern int (*udev_device_set_sysattr_value_dylibloader_wrapper_libudev)(struct udev_device*,const char*,const char*); +extern int (*udev_device_has_tag_dylibloader_wrapper_libudev)(struct udev_device*,const char*); +extern struct udev_monitor* (*udev_monitor_ref_dylibloader_wrapper_libudev)(struct udev_monitor*); +extern struct udev_monitor* (*udev_monitor_unref_dylibloader_wrapper_libudev)(struct udev_monitor*); +extern struct udev* (*udev_monitor_get_udev_dylibloader_wrapper_libudev)(struct udev_monitor*); +extern struct udev_monitor* (*udev_monitor_new_from_netlink_dylibloader_wrapper_libudev)(struct udev*,const char*); +extern int (*udev_monitor_enable_receiving_dylibloader_wrapper_libudev)(struct udev_monitor*); +extern int (*udev_monitor_set_receive_buffer_size_dylibloader_wrapper_libudev)(struct udev_monitor*, int); +extern int (*udev_monitor_get_fd_dylibloader_wrapper_libudev)(struct udev_monitor*); +extern struct udev_device* (*udev_monitor_receive_device_dylibloader_wrapper_libudev)(struct udev_monitor*); +extern int (*udev_monitor_filter_add_match_subsystem_devtype_dylibloader_wrapper_libudev)(struct udev_monitor*,const char*,const char*); +extern int (*udev_monitor_filter_add_match_tag_dylibloader_wrapper_libudev)(struct udev_monitor*,const char*); +extern int (*udev_monitor_filter_update_dylibloader_wrapper_libudev)(struct udev_monitor*); +extern int (*udev_monitor_filter_remove_dylibloader_wrapper_libudev)(struct udev_monitor*); +extern struct udev_enumerate* (*udev_enumerate_ref_dylibloader_wrapper_libudev)(struct udev_enumerate*); +extern struct udev_enumerate* (*udev_enumerate_unref_dylibloader_wrapper_libudev)(struct udev_enumerate*); +extern struct udev* (*udev_enumerate_get_udev_dylibloader_wrapper_libudev)(struct udev_enumerate*); +extern struct udev_enumerate* (*udev_enumerate_new_dylibloader_wrapper_libudev)(struct udev*); +extern int (*udev_enumerate_add_match_subsystem_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +extern int (*udev_enumerate_add_nomatch_subsystem_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +extern int (*udev_enumerate_add_match_sysattr_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*,const char*); +extern int (*udev_enumerate_add_nomatch_sysattr_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*,const char*); +extern int (*udev_enumerate_add_match_property_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*,const char*); +extern int (*udev_enumerate_add_match_sysname_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +extern int (*udev_enumerate_add_match_tag_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +extern int (*udev_enumerate_add_match_parent_dylibloader_wrapper_libudev)(struct udev_enumerate*,struct udev_device*); +extern int (*udev_enumerate_add_match_is_initialized_dylibloader_wrapper_libudev)(struct udev_enumerate*); +extern int (*udev_enumerate_add_syspath_dylibloader_wrapper_libudev)(struct udev_enumerate*,const char*); +extern int (*udev_enumerate_scan_devices_dylibloader_wrapper_libudev)(struct udev_enumerate*); +extern int (*udev_enumerate_scan_subsystems_dylibloader_wrapper_libudev)(struct udev_enumerate*); +extern struct udev_list_entry* (*udev_enumerate_get_list_entry_dylibloader_wrapper_libudev)(struct udev_enumerate*); +extern struct udev_queue* (*udev_queue_ref_dylibloader_wrapper_libudev)(struct udev_queue*); +extern struct udev_queue* (*udev_queue_unref_dylibloader_wrapper_libudev)(struct udev_queue*); +extern struct udev* (*udev_queue_get_udev_dylibloader_wrapper_libudev)(struct udev_queue*); +extern struct udev_queue* (*udev_queue_new_dylibloader_wrapper_libudev)(struct udev*); +extern unsigned long long int (*udev_queue_get_kernel_seqnum_dylibloader_wrapper_libudev)(struct udev_queue*); +extern unsigned long long int (*udev_queue_get_udev_seqnum_dylibloader_wrapper_libudev)(struct udev_queue*); +extern int (*udev_queue_get_udev_is_active_dylibloader_wrapper_libudev)(struct udev_queue*); +extern int (*udev_queue_get_queue_is_empty_dylibloader_wrapper_libudev)(struct udev_queue*); +extern int (*udev_queue_get_seqnum_is_finished_dylibloader_wrapper_libudev)(struct udev_queue*, unsigned long long int); +extern int (*udev_queue_get_seqnum_sequence_is_finished_dylibloader_wrapper_libudev)(struct udev_queue*, unsigned long long int, unsigned long long int); +extern int (*udev_queue_get_fd_dylibloader_wrapper_libudev)(struct udev_queue*); +extern int (*udev_queue_flush_dylibloader_wrapper_libudev)(struct udev_queue*); +extern struct udev_list_entry* (*udev_queue_get_queued_list_entry_dylibloader_wrapper_libudev)(struct udev_queue*); +extern struct udev_hwdb* (*udev_hwdb_new_dylibloader_wrapper_libudev)(struct udev*); +extern struct udev_hwdb* (*udev_hwdb_ref_dylibloader_wrapper_libudev)(struct udev_hwdb*); +extern struct udev_hwdb* (*udev_hwdb_unref_dylibloader_wrapper_libudev)(struct udev_hwdb*); +extern struct udev_list_entry* (*udev_hwdb_get_properties_list_entry_dylibloader_wrapper_libudev)(struct udev_hwdb*,const char*, unsigned); +extern int (*udev_util_encode_string_dylibloader_wrapper_libudev)(const char*, char*, size_t); +int initialize_libudev(int verbose); +#ifdef __cplusplus +} +#endif +#endif diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 68290bb4ec..09e1f9461c 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -128,7 +128,7 @@ Error OS_LinuxBSD::shell_open(String p_uri) { args.push_back(p_uri); // Agnostic - ok = execute("xdg-open", args, true, nullptr, nullptr, &err_code); + ok = execute("xdg-open", args, nullptr, &err_code); if (ok == OK && !err_code) { return OK; } else if (err_code == 2) { @@ -136,25 +136,25 @@ Error OS_LinuxBSD::shell_open(String p_uri) { } // GNOME args.push_front("open"); // The command is `gio open`, so we need to add it to args - ok = execute("gio", args, true, nullptr, nullptr, &err_code); + ok = execute("gio", args, nullptr, &err_code); if (ok == OK && !err_code) { return OK; } else if (err_code == 2) { return ERR_FILE_NOT_FOUND; } args.pop_front(); - ok = execute("gvfs-open", args, true, nullptr, nullptr, &err_code); + ok = execute("gvfs-open", args, nullptr, &err_code); if (ok == OK && !err_code) { return OK; } else if (err_code == 2) { return ERR_FILE_NOT_FOUND; } // KDE - ok = execute("kde-open5", args, true, nullptr, nullptr, &err_code); + ok = execute("kde-open5", args, nullptr, &err_code); if (ok == OK && !err_code) { return OK; } - ok = execute("kde-open", args, true, nullptr, nullptr, &err_code); + ok = execute("kde-open", args, nullptr, &err_code); return !err_code ? ok : FAILED; } @@ -232,7 +232,7 @@ String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const { String pipe; List<String> arg; arg.push_back(xdgparam); - Error err = const_cast<OS_LinuxBSD *>(this)->execute("xdg-user-dir", arg, true, nullptr, &pipe); + Error err = const_cast<OS_LinuxBSD *>(this)->execute("xdg-user-dir", arg, &pipe); if (err != OK) { return "."; } @@ -307,7 +307,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { List<String> args; args.push_back(p_path); args.push_front("trash"); // The command is `gio trash <file_name>` so we need to add it to args. - Error result = execute("gio", args, true, nullptr, nullptr, &err_code); // For GNOME based machines. + Error result = execute("gio", args, nullptr, &err_code); // For GNOME based machines. if (result == OK && !err_code) { return OK; } else if (err_code == 2) { @@ -317,7 +317,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { args.pop_front(); args.push_front("move"); args.push_back("trash:/"); // The command is `kioclient5 move <file_name> trash:/`. - result = execute("kioclient5", args, true, nullptr, nullptr, &err_code); // For KDE based machines. + result = execute("kioclient5", args, nullptr, &err_code); // For KDE based machines. if (result == OK && !err_code) { return OK; } else if (err_code == 2) { @@ -326,7 +326,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { args.pop_front(); args.pop_back(); - result = execute("gvfs-trash", args, true, nullptr, nullptr, &err_code); // For older Linux machines. + result = execute("gvfs-trash", args, nullptr, &err_code); // For older Linux machines. if (result == OK && !err_code) { return OK; } else if (err_code == 2) { @@ -410,7 +410,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { OS::Time time = OS::get_singleton()->get_time(false); String timestamp = vformat("%04d-%02d-%02dT%02d:%02d:", date.year, date.month, date.day, time.hour, time.min); timestamp = vformat("%s%02d", timestamp, time.sec); // vformat only supports up to 6 arguments. - String trash_info = "[Trash Info]\nPath=" + p_path.http_escape() + "\nDeletionDate=" + timestamp + "\n"; + String trash_info = "[Trash Info]\nPath=" + p_path.uri_encode() + "\nDeletionDate=" + timestamp + "\n"; { Error err; FileAccess *file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err); @@ -432,7 +432,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { mv_args.push_back(trash_path + "/files"); { int retval; - Error err = execute("mv", mv_args, true, nullptr, nullptr, &retval); + Error err = execute("mv", mv_args, nullptr, &retval); // Issue an error if "mv" failed to move the given resource to the trash can. if (err != OK || retval != 0) { diff --git a/platform/osx/SCsub b/platform/osx/SCsub index aa95a89444..46c13d8550 100644 --- a/platform/osx/SCsub +++ b/platform/osx/SCsub @@ -18,5 +18,5 @@ files = [ prog = env.add_program("#bin/godot", files) -if env["debug_symbols"] == "yes" and env["separate_debug_symbols"]: +if env["debug_symbols"] and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_osx_builders.make_debug_osx)) diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 4d6ed41a73..0f128d504f 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -70,7 +70,7 @@ static uint64_t load_address() { } static void handle_crash(int sig) { - if (OS::get_singleton() == NULL) { + if (OS::get_singleton() == nullptr) { abort(); } @@ -105,7 +105,7 @@ static void handle_crash(int sig) { if (dladdr(bt_buffer[i], &info) && info.dli_sname) { if (info.dli_sname[0] == '_') { int status; - char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status); + char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status); if (status == 0 && demangled) { snprintf(fname, 1024, "%s", demangled); @@ -135,7 +135,7 @@ static void handle_crash(int sig) { int ret; String out = ""; - Error err = OS::get_singleton()->execute(String("atos"), args, true, NULL, &out, &ret); + Error err = OS::get_singleton()->execute(String("atos"), args, &out, &ret); if (err == OK && out.substr(0, 2) != "0x") { out.erase(out.length() - 1, 1); output = out; @@ -167,9 +167,9 @@ void CrashHandler::disable() { return; #ifdef CRASH_HANDLER_ENABLED - signal(SIGSEGV, NULL); - signal(SIGFPE, NULL); - signal(SIGILL, NULL); + signal(SIGSEGV, nullptr); + signal(SIGFPE, nullptr); + signal(SIGILL, nullptr); #endif disabled = true; diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 466f68d269..317e79d0ea 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -31,11 +31,11 @@ def get_opts(): False, ), EnumVariable("macports_clang", "Build using Clang from MacPorts", "no", ("no", "5.0", "devel")), - EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), + BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False), - BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False), - BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), + BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN)", False), + BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), ] @@ -49,21 +49,21 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) if env["arch"] != "arm64": env.Prepend(CCFLAGS=["-msse2"]) - if env["debug_symbols"] == "yes": + if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) - if env["debug_symbols"] == "yes": + if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "debug": @@ -135,11 +135,16 @@ def configure(env): env.extra_suffix += "s" if env["use_ubsan"]: - env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append( + CCFLAGS=[ + "-fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin" + ] + ) env.Append(LINKFLAGS=["-fsanitize=undefined"]) + env.Append(CCFLAGS=["-fsanitize=nullability-return,nullability-arg,function,nullability-assign"]) if env["use_asan"]: - env.Append(CCFLAGS=["-fsanitize=address"]) + env.Append(CCFLAGS=["-fsanitize=address,pointer-subtract,pointer-compare"]) env.Append(LINKFLAGS=["-fsanitize=address"]) if env["use_tsan"]: diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index 597ebce6ff..9fac99810b 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -93,6 +93,7 @@ public: List<WarpEvent> warp_events; NSTimeInterval last_warp = 0; + bool ignore_warp = false; Vector<KeyEvent> key_event_buffer; int key_event_pos; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index bb3c1d47b7..473ae95036 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -256,9 +256,7 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { List<String> args; args.push_back(((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename); String exec = OS::get_singleton()->get_executable_path(); - - OS::ProcessID pid = 0; - OS::get_singleton()->execute(exec, args, false, &pid); + OS::get_singleton()->create_process(exec, args); } #endif return YES; @@ -831,8 +829,8 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i mb->set_position(pos); mb->set_global_position(pos); mb->set_button_mask(DS_OSX->last_button_state); - if (index == BUTTON_LEFT && pressed) { - mb->set_doubleclick([event clickCount] == 2); + if (index == MOUSE_BUTTON_LEFT && pressed) { + mb->set_double_click([event clickCount] == 2); } Input::get_singleton()->accumulate_input_event(mb); @@ -844,10 +842,10 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i if (([event modifierFlags] & NSEventModifierFlagControl)) { wd.mouse_down_control = true; - _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MASK_RIGHT, true); } else { wd.mouse_down_control = false; - _mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MASK_LEFT, true); } } @@ -860,9 +858,9 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; if (wd.mouse_down_control) { - _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MASK_RIGHT, false); } else { - _mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MASK_LEFT, false); } } @@ -873,6 +871,15 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); NSPoint mpos = [event locationInWindow]; + if (DS_OSX->ignore_warp) { + // Discard late events, before warp + if (([event timestamp]) < DS_OSX->last_warp) { + return; + } + DS_OSX->ignore_warp = false; + return; + } + if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED) { // Discard late events if (([event timestamp]) < DS_OSX->last_warp) { @@ -939,7 +946,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i } - (void)rightMouseDown:(NSEvent *)event { - _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MASK_RIGHT, true); } - (void)rightMouseDragged:(NSEvent *)event { @@ -947,16 +954,16 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i } - (void)rightMouseUp:(NSEvent *)event { - _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MASK_RIGHT, false); } - (void)otherMouseDown:(NSEvent *)event { if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_MASK_MIDDLE, true); } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_XBUTTON1, MOUSE_BUTTON_MASK_XBUTTON1, true); } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_XBUTTON2, MOUSE_BUTTON_MASK_XBUTTON2, true); } else { return; } @@ -968,11 +975,11 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i - (void)otherMouseUp:(NSEvent *)event { if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_MASK_MIDDLE, false); } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_XBUTTON1, MOUSE_BUTTON_MASK_XBUTTON1, false); } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_XBUTTON2, MOUSE_BUTTON_MASK_XBUTTON2, false); } else { return; } @@ -1551,10 +1558,10 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]); } else { if (fabs(deltaX)) { - sendScrollEvent(window_id, 0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); + sendScrollEvent(window_id, 0 > deltaX ? MOUSE_BUTTON_WHEEL_RIGHT : MOUSE_BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); } if (fabs(deltaY)) { - sendScrollEvent(window_id, 0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); + sendScrollEvent(window_id, 0 < deltaY ? MOUSE_BUTTON_WHEEL_UP : MOUSE_BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); } } } @@ -1633,7 +1640,7 @@ String DisplayServerOSX::get_name() const { } const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const { - const NSMenu *menu = NULL; + const NSMenu *menu = nullptr; if (p_menu_root == "") { // Main menu.x menu = [NSApp mainMenu]; @@ -1648,13 +1655,13 @@ const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const } if (menu == apple_menu) { // Do not allow to change Apple menu. - return NULL; + return nullptr; } return menu; } NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { - NSMenu *menu = NULL; + NSMenu *menu = nullptr; if (p_menu_root == "") { // Main menu. menu = [NSApp mainMenu]; @@ -1671,7 +1678,7 @@ NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { } if (menu == apple_menu) { // Do not allow to change Apple menu. - return NULL; + return nullptr; } return menu; } @@ -2100,6 +2107,8 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { CGAssociateMouseAndMouseCursorPosition(true); } + last_warp = [[NSProcessInfo processInfo] systemUptime]; + ignore_warp = true; warp_events.clear(); mouse_mode = p_mode; } @@ -3020,7 +3029,7 @@ void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { return; } - if (cursors[p_shape] != NULL) { + if (cursors[p_shape] != nullptr) { [cursors[p_shape] set]; } else { switch (p_shape) { @@ -3108,7 +3117,7 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape Rect2 atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -3131,7 +3140,7 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape 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(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -3193,9 +3202,9 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape [nsimage release]; } else { // Reset to default system cursor - if (cursors[p_shape] != NULL) { + if (cursors[p_shape] != nullptr) { [cursors[p_shape] release]; - cursors[p_shape] = NULL; + cursors[p_shape] = nullptr; } CursorShape c = cursor_shape; @@ -3750,12 +3759,12 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode // Register to be notified on keyboard layout changes CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), - NULL, keyboard_layout_changed, - kTISNotifySelectedKeyboardInputSourceChanged, NULL, + nullptr, keyboard_layout_changed, + kTISNotifySelectedKeyboardInputSourceChanged, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); // Register to be notified on displays arrangement changes - CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, NULL); + CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, nullptr); // Menu bar setup must go between sharedApplication above and // finishLaunching below, in order to properly emulate the behavior @@ -3845,7 +3854,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode context_vulkan = memnew(VulkanContextOSX); if (context_vulkan->initialize() != OK) { memdelete(context_vulkan); - context_vulkan = NULL; + context_vulkan = nullptr; r_error = ERR_CANT_CREATE; ERR_FAIL_MSG("Could not initialize Vulkan"); } @@ -3917,8 +3926,8 @@ DisplayServerOSX::~DisplayServerOSX() { } #endif - CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL); - CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); + CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), nullptr, kTISNotifySelectedKeyboardInputSourceChanged, nullptr); + CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, nullptr); cursors_cache.clear(); } diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index 752b119958..51204bc8f6 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -56,7 +56,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { 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 _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_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); @@ -114,7 +114,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("pc"); r_features->push_back("s3tc"); - r_features->push_back("OSX"); + r_features->push_back("macOS"); } virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { @@ -155,11 +155,35 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); #ifdef OSX_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); 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::BOOL, "codesign/replace_existing_signature"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/disable_library_validation"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/audio_input"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/camera"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/location"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/address_book"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/calendars"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/photos_library"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/apple_events"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_server"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_client"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_usb"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_bluetooth"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_downloads", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + 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)); @@ -191,7 +215,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, 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); - copymem(&result.write[res_size], &buf, buf_size); + memcpy(&result.write[res_size], &buf, buf_size); res_size += buf_size; buf_size = 0; } @@ -217,7 +241,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, buf[buf_size++] = cur; if (buf_size == 128) { result.write[res_size++] = (uint8_t)(buf_size - 1); - copymem(&result.write[res_size], &buf, buf_size); + memcpy(&result.write[res_size], &buf, buf_size); res_size += buf_size; buf_size = 0; } @@ -225,7 +249,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, } else { buf[buf_size++] = cur; result.write[res_size++] = (uint8_t)(buf_size - 1); - copymem(&result.write[res_size], &buf, buf_size); + memcpy(&result.write[res_size], &buf, buf_size); res_size += buf_size; buf_size = 0; } @@ -235,7 +259,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, int ofs = p_dest.size(); p_dest.resize(p_dest.size() + res_size); - copymem(&p_dest.write[ofs], result.ptr(), res_size); + memcpy(&p_dest.write[ofs], result.ptr(), res_size); } void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data) { @@ -294,7 +318,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ memdelete(f); len += 8; len = BSWAP32(len); - copymem(&data.write[ofs], icon_infos[i].name, 4); + memcpy(&data.write[ofs], icon_infos[i].name, 4); encode_uint32(len, &data.write[ofs + 4]); // Clean up generated file. @@ -314,7 +338,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ int len = data.size() - ofs; len = BSWAP32(len); - copymem(&data.write[ofs], icon_infos[i].name, 4); + memcpy(&data.write[ofs], icon_infos[i].name, 4); encode_uint32(len, &data.write[ofs + 4]); } @@ -329,7 +353,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ } len += 8; len = BSWAP32(len); - copymem(&data.write[ofs], icon_infos[i].mask_name, 4); + memcpy(&data.write[ofs], icon_infos[i].mask_name, 4); encode_uint32(len, &data.write[ofs + 4]); } } @@ -419,7 +443,7 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset args.push_back(p_path); String str; - Error err = OS::get_singleton()->execute("xcrun", args, true, nullptr, &str, nullptr, true); + Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true); ERR_FAIL_COND_V(err != OK, err); print_line("altool (" + p_path + "):\n" + str); @@ -437,7 +461,7 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset return OK; } -Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) { #ifdef OSX_ENABLED List<String> args; @@ -449,9 +473,9 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese args.push_back("runtime"); } - if ((p_preset->get("codesign/entitlements") != "") && (p_path.get_extension() != "dmg")) { + if (p_path.get_extension() != "dmg") { args.push_back("--entitlements"); - args.push_back(p_preset->get("codesign/entitlements")); + args.push_back(p_ent_path); } PackedStringArray user_args = p_preset->get("codesign/custom_options"); @@ -463,14 +487,22 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese } args.push_back("-s"); - args.push_back(p_preset->get("codesign/identity")); + if (p_preset->get("codesign/identity") == "") { + args.push_back("-"); + } else { + args.push_back(p_preset->get("codesign/identity")); + } args.push_back("-v"); /* provide some more feedback */ + if (p_preset->get("codesign/replace_existing_signature")) { + args.push_back("-f"); + } + args.push_back(p_path); String str; - Error err = OS::get_singleton()->execute("codesign", args, true, nullptr, &str, nullptr, true); + Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true); ERR_FAIL_COND_V(err != OK, err); print_line("codesign (" + p_path + "):\n" + str); @@ -504,7 +536,7 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin args.push_back(p_app_path_name); String str; - Error err = OS::get_singleton()->execute("hdiutil", args, true, nullptr, &str, nullptr, true); + Error err = OS::get_singleton()->execute("hdiutil", args, &str, nullptr, true); ERR_FAIL_COND_V(err != OK, err); print_line("hdiutil returned: " + str); @@ -607,6 +639,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // Now process our template. bool found_binary = false; int total_size = 0; + Vector<String> dylibs_found; while (ret == UNZ_OK && err == OK) { bool is_execute = false; @@ -678,14 +711,18 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ret = unzGoToNextFile(src_pkg_zip); continue; // skip } - file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name + "/"); + file = file.replace("/data.mono.osx.64.release_debug/", "/GodotSharp/"); } if (file.find("/data.mono.osx.64.release/") != -1) { if (p_debug) { ret = unzGoToNextFile(src_pkg_zip); continue; // skip } - file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name + "/"); + file = file.replace("/data.mono.osx.64.release/", "/GodotSharp/"); + } + + if (file.ends_with(".dylib")) { + dylibs_found.push_back(file); } print_line("ADDING: " + file + " size: " + itos(data.size())); @@ -735,22 +772,149 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // See if we can code sign our new package. bool sign_enabled = p_preset->get("codesign/enable"); + String ent_path = p_preset->get("codesign/entitlements/custom_file"); + if (sign_enabled && (ent_path == "")) { + ent_path = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements"); + + FileAccess *ent_f = FileAccess::open(ent_path, FileAccess::WRITE); + if (ent_f) { + ent_f->store_line("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); + ent_f->store_line("<plist version=\"1.0\">"); + ent_f->store_line("<dict>"); + if ((bool)p_preset->get("codesign/entitlements/allow_jit_code_execution")) { + ent_f->store_line("<key>com.apple.security.cs.allow-jit</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/allow_unsigned_executable_memory")) { + ent_f->store_line("<key>com.apple.security.cs.allow-unsigned-executable-memory</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/allow_dyld_environment_variables")) { + ent_f->store_line("<key>com.apple.security.cs.allow-dyld-environment-variables</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/disable_library_validation")) { + ent_f->store_line("<key>com.apple.security.cs.disable-library-validation</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/audio_input")) { + ent_f->store_line("<key>com.apple.security.device.audio-input</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/camera")) { + ent_f->store_line("<key>com.apple.security.device.camera</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/location")) { + ent_f->store_line("<key>com.apple.security.personal-information.location</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/address_book")) { + ent_f->store_line("<key>com.apple.security.personal-information.addressbook</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/calendars")) { + ent_f->store_line("<key>com.apple.security.personal-information.calendars</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/photos_library")) { + ent_f->store_line("<key>com.apple.security.personal-information.photos-library</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/apple_events")) { + ent_f->store_line("<key>com.apple.security.automation.apple-events</key>"); + ent_f->store_line("<true/>"); + } + + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/enabled")) { + ent_f->store_line("<key>com.apple.security.app-sandbox</key>"); + ent_f->store_line("<true/>"); + + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/network_server")) { + ent_f->store_line("<key>com.apple.security.network.server</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/network_client")) { + ent_f->store_line("<key>com.apple.security.network.client</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/device_usb")) { + ent_f->store_line("<key>com.apple.security.device.usb</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/device_bluetooth")) { + ent_f->store_line("<key>com.apple.security.device.bluetooth</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_downloads") == 1) { + ent_f->store_line("<key>com.apple.security.files.downloads.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_downloads") == 2) { + ent_f->store_line("<key>com.apple.security.files.downloads.read-write</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_pictures") == 1) { + ent_f->store_line("<key>com.apple.security.files.pictures.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_pictures") == 2) { + ent_f->store_line("<key>com.apple.security.files.pictures.read-write</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_music") == 1) { + ent_f->store_line("<key>com.apple.security.files.music.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_music") == 2) { + ent_f->store_line("<key>com.apple.security.files.music.read-write</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_movies") == 1) { + ent_f->store_line("<key>com.apple.security.files.movies.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_movies") == 2) { + ent_f->store_line("<key>com.apple.security.files.movies.read-write</key>"); + ent_f->store_line("<true/>"); + } + } + + ent_f->store_line("</dict>"); + ent_f->store_line("</plist>"); + + ent_f->close(); + memdelete(ent_f); + } else { + err = ERR_CANT_CREATE; + } + } + 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()); + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file(), ent_path); } } memdelete(da); } + if (sign_enabled) { + for (int i = 0; i < dylibs_found.size(); i++) { + if (err == OK) { + err = _code_sign(p_preset, tmp_app_path_name + "/" + dylibs_found[i], ent_path); + } + } + } + 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); + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name, ent_path); } if (export_format == "dmg") { @@ -766,7 +930,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (ep.step("Code signing DMG", 3)) { return ERR_SKIP; } - err = _code_sign(p_preset, p_path); + err = _code_sign(p_preset, p_path, ent_path); } } else { // Create ZIP. @@ -900,12 +1064,6 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset } 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) { diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index 0b6a0e20a6..b12526915f 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -433,8 +433,8 @@ void JoypadOSX::poll_joypads() const { } } -static const Input::JoyAxis axis_correct(int p_value, int p_min, int p_max) { - Input::JoyAxis jx; +static const Input::JoyAxisValue axis_correct(int p_value, int p_min, int p_max) { + Input::JoyAxisValue jx; if (p_min < 0) { jx.min = -1; if (p_value < 0) { diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 7b5daf5bfb..e6feda5a9b 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -101,7 +101,7 @@ String OS_OSX::get_unique_id() const { if (serial_number.is_empty()) { io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); - CFStringRef serialNumberAsCFString = NULL; + CFStringRef serialNumberAsCFString = nullptr; if (platformExpert) { serialNumberAsCFString = (CFStringRef)IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); IOObjectRelease(platformExpert); @@ -158,7 +158,7 @@ void OS_OSX::delete_main_loop() { if (!main_loop) return; memdelete(main_loop); - main_loop = NULL; + main_loop = nullptr; } String OS_OSX::get_name() const { @@ -346,7 +346,7 @@ Error OS_OSX::move_to_trash(const String &p_path) { } OS_OSX::OS_OSX() { - main_loop = NULL; + main_loop = nullptr; force_quit = false; Vector<Logger *> loggers; diff --git a/platform/osx/vulkan_context_osx.mm b/platform/osx/vulkan_context_osx.mm index 75a4fc990f..6b87fbd489 100644 --- a/platform/osx/vulkan_context_osx.mm +++ b/platform/osx/vulkan_context_osx.mm @@ -38,12 +38,12 @@ 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; + createInfo.pNext = nullptr; createInfo.flags = 0; createInfo.pView = p_window; VkSurfaceKHR surface; - VkResult err = vkCreateMacOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); + VkResult err = vkCreateMacOSSurfaceMVK(_get_instance(), &createInfo, nullptr, &surface); ERR_FAIL_COND_V(err, ERR_CANT_CREATE); return _window_create(p_window_id, surface, p_width, p_height); } diff --git a/platform/server/detect.py b/platform/server/detect.py index db503584d3..478bcad212 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -32,13 +32,14 @@ def get_opts(): return [ BoolVariable("use_llvm", "Use the LLVM compiler", False), - BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", False), + BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", True), BoolVariable("use_coverage", "Test Godot coverage", False), BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False), - BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False), - BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN))", False), - BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), - EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), + BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN)", False), + BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN)", False), + BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), + BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False), + BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False), ] @@ -55,20 +56,20 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O3"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) - if env["debug_symbols"] == "yes": + if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) - if env["debug_symbols"] == "yes": + if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "debug": @@ -93,20 +94,33 @@ def configure(env): env["CC"] = "clang" env["CXX"] = "clang++" env.extra_suffix = ".llvm" + env.extra_suffix + env.Append(LIBS=["atomic"]) if env["use_coverage"]: env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"]) env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"]) - if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"]: + if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"] or env["use_msan"]: env.extra_suffix += "s" if env["use_ubsan"]: - env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append( + CCFLAGS=[ + "-fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin" + ] + ) env.Append(LINKFLAGS=["-fsanitize=undefined"]) + if env["use_llvm"]: + env.Append( + CCFLAGS=[ + "-fsanitize=nullability-return,nullability-arg,function,nullability-assign,implicit-integer-sign-change" + ] + ) + else: + env.Append(CCFLAGS=["-fsanitize=bounds-strict"]) if env["use_asan"]: - env.Append(CCFLAGS=["-fsanitize=address"]) + env.Append(CCFLAGS=["-fsanitize=address,pointer-subtract,pointer-compare"]) env.Append(LINKFLAGS=["-fsanitize=address"]) if env["use_lsan"]: @@ -117,6 +131,12 @@ def configure(env): env.Append(CCFLAGS=["-fsanitize=thread"]) env.Append(LINKFLAGS=["-fsanitize=thread"]) + if env["use_msan"] and env["use_llvm"]: + env.Append(CCFLAGS=["-fsanitize=memory"]) + env.Append(CCFLAGS=["-fsanitize-memory-track-origins"]) + env.Append(CCFLAGS=["-fsanitize-recover=memory"]) + env.Append(LINKFLAGS=["-fsanitize=memory"]) + if env["use_lto"]: env.Append(CCFLAGS=["-flto"]) if not env["use_llvm"] and env.GetOption("num_jobs") > 1: diff --git a/platform/uwp/app.cpp b/platform/uwp/app.cpp index dc4238bdd4..b7e4361831 100644 --- a/platform/uwp/app.cpp +++ b/platform/uwp/app.cpp @@ -149,28 +149,28 @@ static int _get_button(Windows::UI::Input::PointerPoint ^ pt) { using namespace Windows::UI::Input; #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP - return BUTTON_LEFT; + return MOUSE_BUTTON_LEFT; #else switch (pt->Properties->PointerUpdateKind) { case PointerUpdateKind::LeftButtonPressed: case PointerUpdateKind::LeftButtonReleased: - return BUTTON_LEFT; + return MOUSE_BUTTON_LEFT; case PointerUpdateKind::RightButtonPressed: case PointerUpdateKind::RightButtonReleased: - return BUTTON_RIGHT; + return MOUSE_BUTTON_RIGHT; case PointerUpdateKind::MiddleButtonPressed: case PointerUpdateKind::MiddleButtonReleased: - return BUTTON_MIDDLE; + return MOUSE_BUTTON_MIDDLE; case PointerUpdateKind::XButton1Pressed: case PointerUpdateKind::XButton1Released: - return BUTTON_WHEEL_UP; + return MOUSE_BUTTON_WHEEL_UP; case PointerUpdateKind::XButton2Pressed: case PointerUpdateKind::XButton2Released: - return BUTTON_WHEEL_DOWN; + return MOUSE_BUTTON_WHEEL_DOWN; default: break; @@ -265,9 +265,9 @@ void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Cor if (p_is_wheel) { if (point->Properties->MouseWheelDelta > 0) { - mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_UP); + mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? MOUSE_BUTTON_WHEEL_RIGHT : MOUSE_BUTTON_WHEEL_UP); } else if (point->Properties->MouseWheelDelta < 0) { - mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? BUTTON_WHEEL_LEFT : BUTTON_WHEEL_DOWN); + mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? MOUSE_BUTTON_WHEEL_LEFT : MOUSE_BUTTON_WHEEL_DOWN); } } diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index fda8fdec66..28922a4f59 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -54,16 +54,19 @@ def configure(env): ## Build type if env["target"] == "release": - env.Append(CCFLAGS=["/O2", "/GL"]) env.Append(CCFLAGS=["/MD"]) - env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS", "/LTCG"]) + env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["/O2", "/GL"]) + env.Append(LINKFLAGS=["/LTCG"]) elif env["target"] == "release_debug": - env.Append(CCFLAGS=["/O2", "/Zi"]) env.Append(CCFLAGS=["/MD"]) - env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["/O2", "/Zi"]) elif env["target"] == "debug": env.Append(CCFLAGS=["/Zi"]) diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 860448ceac..217c119978 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -760,7 +760,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform { result = result.replace("$version_string$", version); Platform arch = (Platform)(int)p_preset->get("architecture/target"); - String architecture = arch == ARM ? "arm" : arch == X86 ? "x86" : "x64"; + String architecture = arch == ARM ? "arm" : (arch == X86 ? "x86" : "x64"); result = result.replace("$architecture$", architecture); result = result.replace("$display_name$", String(p_preset->get("package/display_name")).is_empty() ? (String)ProjectSettings::get_singleton()->get("application/config/name") : String(p_preset->get("package/display_name"))); @@ -855,33 +855,33 @@ class EditorExportPlatformUWP : public EditorExportPlatform { Vector<uint8_t> _get_image_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) { Vector<uint8_t> data; - StreamTexture2D *image = nullptr; + StreamTexture2D *texture = nullptr; if (p_path.find("StoreLogo") != -1) { - image = p_preset->get("images/store_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/store_logo"))); + texture = 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<StreamTexture2D>(((Object *)p_preset->get("images/square44x44_logo"))); + texture = 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<StreamTexture2D>(((Object *)p_preset->get("images/square71x71_logo"))); + texture = 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<StreamTexture2D>(((Object *)p_preset->get("images/square150x150_logo"))); + texture = 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<StreamTexture2D>(((Object *)p_preset->get("images/square310x310_logo"))); + texture = 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<StreamTexture2D>(((Object *)p_preset->get("images/wide310x150_logo"))); + texture = 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<StreamTexture2D>(((Object *)p_preset->get("images/splash_screen"))); + texture = 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) { + if (!texture) { 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); + Error err = texture->get_image()->save_png(tmp_path); if (err != OK) { String err_string = "Couldn't save temp logo file."; @@ -1177,6 +1177,8 @@ public: } virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + String src_appx; EditorProgress ep("export", "Exporting for UWP", 7, true); @@ -1334,7 +1336,7 @@ public: int base = clf.size(); clf.resize(base + 4 + txt.length()); encode_uint32(txt.length(), &clf.write[base]); - copymem(&clf.write[base + 4], txt.ptr(), txt.length()); + memcpy(&clf.write[base + 4], txt.ptr(), txt.length()); print_line(itos(i) + " param: " + cl[i]); } @@ -1411,7 +1413,7 @@ public: args.push_back(cert_pass); args.push_back(p_path); - OS::get_singleton()->execute(signtool_path, args, true); + OS::get_singleton()->execute(signtool_path, args); #endif // WINDOWS_ENABLED return OK; diff --git a/platform/uwp/joypad_uwp.cpp b/platform/uwp/joypad_uwp.cpp index 5da90db49d..b419fb4fae 100644 --- a/platform/uwp/joypad_uwp.cpp +++ b/platform/uwp/joypad_uwp.cpp @@ -134,8 +134,8 @@ void JoypadUWP::OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Inp input->joy_connection_changed(idx, false, "Xbox Controller"); } -InputDefault::JoyAxis JoypadUWP::axis_correct(double p_val, bool p_negate, bool p_trigger) const { - InputDefault::JoyAxis jx; +InputDefault::JoyAxisValue JoypadUWP::axis_correct(double p_val, bool p_negate, bool p_trigger) const { + InputDefault::JoyAxisValue jx; jx.min = p_trigger ? 0 : -1; jx.value = (float)(p_negate ? -p_val : p_val); diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h index 5df4a211ac..d760d9e2fe 100644 --- a/platform/uwp/joypad_uwp.h +++ b/platform/uwp/joypad_uwp.h @@ -73,7 +73,7 @@ private: void OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value); void OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value); - InputDefault::JoyAxis axis_correct(double p_val, bool p_negate = false, bool p_trigger = false) const; + InputDefault::JoyAxisValue axis_correct(double p_val, bool p_negate = false, bool p_trigger = false) const; void joypad_vibration_start(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop(int p_device, uint64_t p_timestamp); }; diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index 18d5d7e08d..2314a392cc 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -39,14 +39,11 @@ #include "drivers/windows/dir_access_windows.h" #include "drivers/windows/file_access_windows.h" #include "drivers/windows/mutex_windows.h" -#include "drivers/windows/rw_lock_windows.h" #include "drivers/windows/semaphore_windows.h" #include "main/main.h" #include "platform/windows/windows_terminal_logger.h" #include "servers/audio_server.h" #include "servers/rendering/rendering_server_default.h" -#include "servers/rendering/rendering_server_wrap_mt.h" -#include "thread_uwp.h" #include <ppltasks.h> #include <wrl.h> @@ -65,6 +62,8 @@ using namespace Windows::Devices::Sensors; using namespace Windows::ApplicationModel::DataTransfer; using namespace concurrency; +static const float earth_gravity = 9.80665; + int OS_UWP::get_video_driver_count() const { return 2; } @@ -131,9 +130,6 @@ void OS_UWP::initialize_core() { //RedirectIOToConsole(); - ThreadUWP::make_default(); - RWLockWindows::make_default(); - FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM); @@ -151,7 +147,7 @@ void OS_UWP::initialize_core() { ticks_start = 0; ticks_start = get_ticks_usec(); - IP_Unix::make_default(); + IPUnix::make_default(); cursor_shape = CURSOR_ARROW; } @@ -378,9 +374,9 @@ void OS_UWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sende AccelerometerReading ^ reading = args->Reading; os->input->set_accelerometer(Vector3( - reading->AccelerationX, - reading->AccelerationY, - reading->AccelerationZ)); + reading->AccelerationX * earth_gravity, + reading->AccelerationY * earth_gravity, + reading->AccelerationZ * earth_gravity)); } void OS_UWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, MagnetometerReadingChangedEventArgs ^ args) { @@ -638,7 +634,11 @@ void OS_UWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c // TODO } -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) { +Error OS_UWP::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { + return FAILED; +}; + +Error OS_UWP::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) { return FAILED; }; diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index edc197ad08..a4d3d6d52a 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -199,7 +199,8 @@ public: virtual void delay_usec(uint32_t p_usec) const; virtual uint64_t get_ticks_usec() const; - 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 execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr); + virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr); virtual Error kill(const ProcessID &p_pid); virtual bool has_environment(const String &p_var) const; diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 0c9aa77803..47d8e14680 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -32,5 +32,5 @@ if env["vsproj"]: env.vs_srcs += ["platform/windows/" + str(x)] if not os.getenv("VCINSTALLDIR"): - if env["debug_symbols"] == "yes" and env["separate_debug_symbols"]: + if env["debug_symbols"] and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_windows_builders.make_debug_mingw)) diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index e24e466f88..e2d507eddd 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -36,7 +36,7 @@ #ifdef CRASH_HANDLER_EXCEPTION -// Backtrace code code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app +// Backtrace code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app #include <algorithm> #include <iterator> diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 5216fca2ca..7772ba2dbe 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -64,7 +64,7 @@ def get_opts(): # XP support dropped after EOL due to missing API for IPv6 and other issues # Vista support dropped after EOL due to GH-10243 ("target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601"), - EnumVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", "yes", ("yes", "no")), + BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), EnumVariable("windows_subsystem", "Windows subsystem", "default", ("default", "console", "gui")), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), ("msvc_version", "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", None), @@ -72,6 +72,7 @@ def get_opts(): BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_thinlto", "Use ThinLTO", False), BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True), + BoolVariable("use_asan", "Use address sanitizer (ASAN)", False), ] @@ -190,26 +191,28 @@ def configure_msvc(env, manual_msvc_config): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) - else: # optimize for size + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["/O1"]) + env.Append(LINKFLAGS=["/OPT:REF"]) env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"]) - env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) - else: # optimize for size + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["/O1"]) + env.Append(LINKFLAGS=["/OPT:REF"]) env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"]) - env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "debug": - env.AppendUnique(CCFLAGS=["/Z7", "/Od", "/EHsc"]) + env.AppendUnique(CCFLAGS=["/Zi", "/FS", "/Od", "/EHsc"]) env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["/DEBUG"]) - if env["debug_symbols"] == "yes": - env.AppendUnique(CCFLAGS=["/Z7"]) + if env["debug_symbols"]: + env.AppendUnique(CCFLAGS=["/Zi", "/FS"]) env.AppendUnique(LINKFLAGS=["/DEBUG"]) if env["windows_subsystem"] == "gui": @@ -224,6 +227,7 @@ def configure_msvc(env, manual_msvc_config): env.AppendUnique(CCFLAGS=["/MT"]) else: env.AppendUnique(CCFLAGS=["/MD"]) + env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"]) # Force to use Unicode encoding env.AppendUnique(CCFLAGS=["/utf-8"]) @@ -305,6 +309,12 @@ def configure_msvc(env, manual_msvc_config): env.Prepend(CPPPATH=[p for p in os.getenv("INCLUDE").split(";")]) env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")]) + # Sanitizers + if env["use_asan"]: + env.extra_suffix += ".s" + env.Append(LINKFLAGS=["/INFERASANLIBS"]) + env.Append(CCFLAGS=["/fsanitize=address"]) + # Incremental linking fix env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"] env["BUILDERS"]["Program"] = methods.precious_program @@ -339,13 +349,13 @@ def configure_mingw(env): else: # optimize for size env.Prepend(CCFLAGS=["-Os"]) - if env["debug_symbols"] == "yes": + if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": env.Append(CCFLAGS=["-O2"]) env.Append(CPPDEFINES=["DEBUG_ENABLED"]) - if env["debug_symbols"] == "yes": + if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["-O2"]) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 98ca724655..4b859da340 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -332,7 +332,7 @@ static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonito EnumRectData *data = (EnumRectData *)dwData; if (data->count == data->screen) { MONITORINFO minfo; - zeromem(&minfo, sizeof(MONITORINFO)); + memset(&minfo, 0, sizeof(MONITORINFO)); minfo.cbSize = sizeof(MONITORINFO); GetMonitorInfoA(hMonitor, &minfo); @@ -535,7 +535,7 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) { } #endif - if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[p_window].wtctx) { + if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window].wtctx) { wintab_WTClose(windows[p_window].wtctx); windows[p_window].wtctx = 0; } @@ -1253,12 +1253,12 @@ void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTra HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); - // Assign the monochrome AND mask bitmap pixels so that a pixels of the source bitmap + // Assign the monochrome AND mask bitmap pixels so that the pixels of the source bitmap // with 'clrTransparent' will be white pixels of the monochrome bitmap SetBkColor(hMainDC, clrTransparent); BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY); - // Assign the color XOR mask bitmap pixels so that a pixels of the source bitmap + // Assign the color XOR mask bitmap pixels so that the pixels of the source bitmap // with 'clrTransparent' will be black and rest the pixels same as corresponding // pixels of the source bitmap SetBkColor(hXorMaskDC, RGB(0, 0, 0)); @@ -1299,7 +1299,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh Rect2 atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -1322,7 +1322,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh 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(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -1419,13 +1419,13 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) { } int DisplayServerWindows::keyboard_get_layout_count() const { - return GetKeyboardLayoutList(0, NULL); + return GetKeyboardLayoutList(0, nullptr); } int DisplayServerWindows::keyboard_get_current_layout() const { HKL cur_layout = GetKeyboardLayout(0); - int layout_count = GetKeyboardLayoutList(0, NULL); + int layout_count = GetKeyboardLayoutList(0, nullptr); HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL)); GetKeyboardLayoutList(layout_count, layouts); @@ -1440,7 +1440,7 @@ int DisplayServerWindows::keyboard_get_current_layout() const { } void DisplayServerWindows::keyboard_set_current_layout(int p_index) { - int layout_count = GetKeyboardLayoutList(0, NULL); + int layout_count = GetKeyboardLayoutList(0, nullptr); ERR_FAIL_INDEX(p_index, layout_count); @@ -1451,7 +1451,7 @@ void DisplayServerWindows::keyboard_set_current_layout(int p_index) { } String DisplayServerWindows::keyboard_get_layout_language(int p_index) const { - int layout_count = GetKeyboardLayoutList(0, NULL); + int layout_count = GetKeyboardLayoutList(0, nullptr); ERR_FAIL_INDEX_V(p_index, layout_count, ""); @@ -1481,7 +1481,7 @@ String _get_full_layout_name_from_registry(HKL p_layout) { DWORD buffer = 1024; DWORD vtype = REG_SZ; - if (RegQueryValueExW(hkey, L"Layout Text", NULL, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) { + if (RegQueryValueExW(hkey, L"Layout Text", nullptr, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) { ret = String::utf16((const char16_t *)layout_text); } RegCloseKey(hkey); @@ -1489,7 +1489,7 @@ String _get_full_layout_name_from_registry(HKL p_layout) { } String DisplayServerWindows::keyboard_get_layout_name(int p_index) const { - int layout_count = GetKeyboardLayoutList(0, NULL); + int layout_count = GetKeyboardLayoutList(0, nullptr); ERR_FAIL_INDEX_V(p_index, layout_count, ""); @@ -1784,7 +1784,10 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) Ref<InputEventFromWindow> event_from_window = p_event; if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { //send to a window - ERR_FAIL_COND(!windows.has(event_from_window->get_window_id())); + if (!windows.has(event_from_window->get_window_id())) { + in_dispatch_input_event = false; + ERR_FAIL_MSG("DisplayServerWindows: Invalid window id in input event."); + } Callable callable = windows[event_from_window->get_window_id()].input_event_callback; if (callable.is_null()) { in_dispatch_input_event = false; @@ -1877,11 +1880,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA break; } case WM_ACTIVATE: { // Watch For Window Activate Message - saved_wparam = wParam; - saved_lparam = lParam; + if (!windows[window_id].window_focused) { + _process_activate_event(window_id, wParam, lParam); + } else { + windows[window_id].saved_wparam = wParam; + windows[window_id].saved_lparam = lParam; - // Run a timer to prevent event catching warning if the window is closing. - focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); + // Run a timer to prevent event catching warning if the focused window is closing. + windows[window_id].focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); + } return 0; // Return To The Message Loop } case WM_GETMINMAXINFO: { @@ -1922,8 +1929,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_CLOSE: // Did We Receive A Close Message? { - if (focus_timer_id != 0U) { - KillTimer(windows[window_id].hWnd, focus_timer_id); + if (windows[window_id].focus_timer_id != 0U) { + KillTimer(windows[window_id].hWnd, windows[window_id].focus_timer_id); } _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); @@ -2014,7 +2021,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WT_CSRCHANGE: case WT_PROXIMITY: { - if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) { + if ((tablet_get_current_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); @@ -2028,7 +2035,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } break; case WT_PACKET: { - if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available && windows[window_id].wtctx) { + if ((tablet_get_current_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); @@ -2107,7 +2114,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA break; } - if ((OS::get_singleton()->get_current_tablet_driver() != "winink") || !winink_available) { + if ((tablet_get_current_driver() != "winink") || !winink_available) { break; } @@ -2133,7 +2140,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA break; } - if ((OS::get_singleton()->get_current_tablet_driver() != "winink") || !winink_available) { + if ((tablet_get_current_driver() != "winink") || !winink_available) { break; } @@ -2297,8 +2304,8 @@ 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 ((tablet_get_current_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 updated recently. if (windows[window_id].last_pressure_update < 10) { windows[window_id].last_pressure_update++; } else { @@ -2405,17 +2412,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_LBUTTONDBLCLK: { mb->set_pressed(true); mb->set_button_index(1); - mb->set_doubleclick(true); + mb->set_double_click(true); } break; case WM_RBUTTONDBLCLK: { mb->set_pressed(true); mb->set_button_index(2); - mb->set_doubleclick(true); + mb->set_double_click(true); } break; case WM_MBUTTONDBLCLK: { mb->set_pressed(true); mb->set_button_index(3); - mb->set_doubleclick(true); + mb->set_double_click(true); } break; case WM_MOUSEWHEEL: { mb->set_pressed(true); @@ -2424,9 +2431,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA return 0; if (motion > 0) - mb->set_button_index(BUTTON_WHEEL_UP); + mb->set_button_index(MOUSE_BUTTON_WHEEL_UP); else - mb->set_button_index(BUTTON_WHEEL_DOWN); + mb->set_button_index(MOUSE_BUTTON_WHEEL_DOWN); } break; case WM_MOUSEHWHEEL: { @@ -2436,34 +2443,34 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA return 0; if (motion < 0) { - mb->set_button_index(BUTTON_WHEEL_LEFT); + mb->set_button_index(MOUSE_BUTTON_WHEEL_LEFT); mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA)); } else { - mb->set_button_index(BUTTON_WHEEL_RIGHT); + mb->set_button_index(MOUSE_BUTTON_WHEEL_RIGHT); mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA)); } } break; case WM_XBUTTONDOWN: { mb->set_pressed(true); if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); + mb->set_button_index(MOUSE_BUTTON_XBUTTON1); else - mb->set_button_index(BUTTON_XBUTTON2); + mb->set_button_index(MOUSE_BUTTON_XBUTTON2); } break; case WM_XBUTTONUP: { mb->set_pressed(false); if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); + mb->set_button_index(MOUSE_BUTTON_XBUTTON1); else - mb->set_button_index(BUTTON_XBUTTON2); + mb->set_button_index(MOUSE_BUTTON_XBUTTON2); } break; case WM_XBUTTONDBLCLK: { mb->set_pressed(true); if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); + mb->set_button_index(MOUSE_BUTTON_XBUTTON1); else - mb->set_button_index(BUTTON_XBUTTON2); - mb->set_doubleclick(true); + mb->set_button_index(MOUSE_BUTTON_XBUTTON2); + mb->set_double_click(true); } break; default: { return 0; @@ -2559,6 +2566,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].preserve_window_size = false; window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id); } + } else { + windows[window_id].preserve_window_size = true; } if (!windows[window_id].rect_changed_callback.is_null()) { @@ -2607,39 +2616,21 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); - move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); + windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); } break; case WM_EXITSIZEMOVE: { - KillTimer(windows[window_id].hWnd, move_timer_id); + KillTimer(windows[window_id].hWnd, windows[window_id].move_timer_id); } break; case WM_TIMER: { - if (wParam == move_timer_id) { + if (wParam == windows[window_id].move_timer_id) { _process_key_events(); if (!Main::is_iterating()) { Main::iteration(); } - } else if (wParam == focus_timer_id) { - windows[window_id].minimized = HIWORD(saved_wparam) != 0; - - if (LOWORD(saved_wparam) == WA_ACTIVE || LOWORD(saved_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 - 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; - }; - - 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(saved_wparam, saved_lparam)); - } - - KillTimer(windows[window_id].hWnd, focus_timer_id); - focus_timer_id = 0U; + } else if (wParam == windows[window_id].focus_timer_id) { + _process_activate_event(window_id, windows[window_id].saved_wparam, windows[window_id].saved_lparam); + KillTimer(windows[window_id].hWnd, wParam); + windows[window_id].focus_timer_id = 0U; } } break; @@ -2796,6 +2787,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return DefWindowProcW(hWnd, uMsg, wParam, lParam); } +void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM wParam, LPARAM lParam) { + if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) { + _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN); + windows[p_window_id].window_focused = true; + alt_mem = false; + control_mem = false; + shift_mem = false; + } else { // WM_INACTIVE + Input::get_singleton()->release_pressed_events(); + _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); + windows[p_window_id].window_focused = false; + alt_mem = false; + } + + if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window_id].wtctx) { + wintab_WTEnable(windows[p_window_id].wtctx, GET_WM_ACTIVATE_STATE(wParam, lParam)); + } +} + void DisplayServerWindows::_process_key_events() { for (int i = 0; i < key_event_pos; i++) { KeyEvent &ke = key_event_buffer[i]; @@ -3029,7 +3039,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DragAcceptFiles(wd.hWnd, true); - if ((OS::get_singleton()->get_current_tablet_driver() == "wintab") && wintab_available) { + if ((tablet_get_current_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; @@ -3096,6 +3106,40 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS { SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2 } SHC_PROCESS_DPI_AWARENESS; +int DisplayServerWindows::tablet_get_driver_count() const { + return tablet_drivers.size(); +} + +String DisplayServerWindows::tablet_get_driver_name(int p_driver) const { + if (p_driver < 0 || p_driver >= tablet_drivers.size()) { + return ""; + } else { + return tablet_drivers[p_driver]; + } +} + +String DisplayServerWindows::tablet_get_current_driver() const { + return tablet_driver; +} + +void DisplayServerWindows::tablet_set_current_driver(const String &p_driver) { + if (tablet_get_driver_count() == 0) { + return; + } + bool found = false; + for (int i = 0; i < tablet_get_driver_count(); i++) { + if (p_driver == tablet_get_driver_name(i)) { + found = true; + } + } + if (found) { + _update_tablet_ctx(tablet_driver, p_driver); + tablet_driver = p_driver; + } else { + ERR_PRINT("Unknown tablet driver " + p_driver + "."); + } +} + DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { drop_events = false; key_event_pos = 0; @@ -3114,6 +3158,35 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win outside = true; + //Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. + HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); + if (wintab_lib) { + wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW"); + wintab_WTClose = (WTClosePtr)GetProcAddress(wintab_lib, "WTClose"); + wintab_WTInfo = (WTInfoPtr)GetProcAddress(wintab_lib, "WTInfoW"); + wintab_WTPacket = (WTPacketPtr)GetProcAddress(wintab_lib, "WTPacket"); + wintab_WTEnable = (WTEnablePtr)GetProcAddress(wintab_lib, "WTEnable"); + + wintab_available = wintab_WTOpen && wintab_WTClose && wintab_WTInfo && wintab_WTPacket && wintab_WTEnable; + } + + if (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) { + win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); + win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); + + winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo; + } + + if (winink_available) { + tablet_drivers.push_back("winink"); + } + if (OS::get_singleton()->is_hidpi_allowed()) { HMODULE Shcore = LoadLibraryW(L"Shcore.dll"); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 722854c538..a734077e59 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -264,7 +264,6 @@ class DisplayServerWindows : public DisplayServer { _THREAD_SAFE_CLASS_ -public: // WinTab API static bool wintab_available; static WTOpenPtr wintab_WTOpen; @@ -279,8 +278,9 @@ public: static GetPointerPenInfoPtr win8p_GetPointerPenInfo; void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver); + String tablet_driver; + Vector<String> tablet_drivers; -private: void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap); enum { @@ -339,6 +339,14 @@ private: bool no_focus = false; bool window_has_focus = false; + // Used to transfer data between events using timer. + WPARAM saved_wparam; + LPARAM saved_lparam; + + // Timers. + uint32_t move_timer_id = 0U; + uint32_t focus_timer_id = 0U; + HANDLE wtctx; LOGCONTEXTW wtlc; int min_pressure; @@ -387,9 +395,6 @@ private: WindowID last_focused_window = INVALID_WINDOW_ID; - uint32_t move_timer_id = 0U; - uint32_t focus_timer_id = 0U; - HCURSOR hCursor; WNDPROC user_proc = nullptr; @@ -409,13 +414,10 @@ private: bool in_dispatch_input_event = false; bool console_visible = false; - WPARAM saved_wparam; - LPARAM saved_lparam; - WNDCLASSEXW wc; HCURSOR cursors[CURSOR_MAX] = { nullptr }; - CursorShape cursor_shape; + CursorShape cursor_shape = CursorShape::CURSOR_ARROW; Map<CursorShape, Vector<Variant>> cursors_cache; void _drag_event(WindowID p_window, float p_x, float p_y, int idx); @@ -428,6 +430,7 @@ private: void _set_mouse_mode_impl(MouseMode p_mode); + void _process_activate_event(WindowID p_window_id, WPARAM wParam, LPARAM lParam); void _process_key_events(); static void _dispatch_input_events(const Ref<InputEvent> &p_event); @@ -539,6 +542,11 @@ public: virtual String keyboard_get_layout_language(int p_index) const; virtual String keyboard_get_layout_name(int p_index) const; + virtual int tablet_get_driver_count() const; + virtual String tablet_get_driver_name(int p_driver) const; + virtual String tablet_get_current_driver() const; + virtual void tablet_set_current_driver(const String &p_driver); + virtual void process_events(); virtual void force_process_and_drop_events(); diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 084a5bee1d..222597b3ff 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -173,11 +173,11 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> } #ifdef WINDOWS_ENABLED - OS::get_singleton()->execute(rcedit_path, args, true); + OS::get_singleton()->execute(rcedit_path, args); #else // On non-Windows we need WINE to run rcedit args.push_front(rcedit_path); - OS::get_singleton()->execute(wine_path, args, true); + OS::get_singleton()->execute(wine_path, args); #endif } @@ -314,7 +314,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p #endif String str; - Error err = OS::get_singleton()->execute(signtool_path, args, true, nullptr, &str, nullptr, true); + Error err = OS::get_singleton()->execute(signtool_path, args, &str, nullptr, true); ERR_FAIL_COND_V(err != OK, err); print_line("codesign (" + p_path + "): " + str); diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis index d85dfbc3d3..857c6a88f1 100644 --- a/platform/windows/godot.natvis +++ b/platform/windows/godot.natvis @@ -10,6 +10,16 @@ </Expand> </Type> + <Type Name="LocalVector<*>"> + <Expand> + <Item Name="[size]">count</Item> + <ArrayItems> + <Size>count</Size> + <ValuePointer>data</ValuePointer> + </ArrayItems> + </Expand> + </Type> + <Type Name="List<*>"> <Expand> <Item Name="[size]">_data ? (_data->size_cache) : 0</Item> diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index f46a0dbe2e..da36dc1f2b 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -110,12 +110,11 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) { if (GetRawInputDeviceList(nullptr, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) { return false; } - dev_list = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count); - if (!dev_list) - return false; + dev_list = (PRAWINPUTDEVICELIST)memalloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count); + ERR_FAIL_NULL_V_MSG(dev_list, false, "Out of memory."); if (GetRawInputDeviceList(dev_list, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) { - free(dev_list); + memfree(dev_list); return false; } for (unsigned int i = 0; i < dev_list_count; i++) { @@ -130,11 +129,11 @@ 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); + memfree(dev_list); return true; } } - free(dev_list); + memfree(dev_list); return false; } @@ -447,8 +446,8 @@ void JoypadWindows::post_hat(int p_device, DWORD p_dpad) { input->joy_hat(p_device, dpad_val); }; -Input::JoyAxis JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { - Input::JoyAxis jx; +Input::JoyAxisValue JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { + Input::JoyAxisValue jx; if (Math::abs(p_val) < MIN_JOY_AXIS) { jx.min = p_trigger ? 0 : -1; jx.value = 0.0f; diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h index 4727b4a14c..757fb54fb3 100644 --- a/platform/windows/joypad_windows.h +++ b/platform/windows/joypad_windows.h @@ -132,7 +132,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); - Input::JoyAxis axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; + Input::JoyAxisValue 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/os_windows.cpp b/platform/windows/os_windows.cpp index 051b69e8d9..f517190a89 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -39,15 +39,12 @@ #include "core/version_generated.gen.h" #include "drivers/windows/dir_access_windows.h" #include "drivers/windows/file_access_windows.h" -#include "drivers/windows/rw_lock_windows.h" -#include "drivers/windows/thread_windows.h" #include "joypad_windows.h" #include "lang_table.h" #include "main/main.h" #include "platform/windows/display_server_windows.h" #include "servers/audio_server.h" #include "servers/rendering/rendering_server_default.h" -#include "servers/rendering/rendering_server_wrap_mt.h" #include "windows_terminal_logger.h" #include <avrt.h> @@ -177,9 +174,6 @@ void OS_Windows::initialize() { //RedirectIOToConsole(); - ThreadWindows::make_default(); - RWLockWindows::make_default(); - FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM); @@ -210,7 +204,7 @@ void OS_Windows::initialize() { current_pi.pi.hProcess = GetCurrentProcess(); process_map->insert(GetCurrentProcessId(), current_pi); - IP_Unix::make_default(); + IPUnix::make_default(); main_loop = nullptr; } @@ -340,7 +334,7 @@ OS::TimeZoneInfo OS_Windows::get_time_zone_info() const { } // Bias value returned by GetTimeZoneInformation is inverted of what we expect - // For example on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 + // For example, on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 ret.bias = -info.Bias; return ret; } @@ -410,24 +404,23 @@ String OS_Windows::_quote_command_line_argument(const String &p_text) const { 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) { +Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { String path = p_path.replace("/", "\\"); + String command = _quote_command_line_argument(path); + for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) { + command += " " + _quote_command_line_argument(E->get()); + } - if (p_blocking && r_pipe) { - String argss = _quote_command_line_argument(path); - for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) { - argss += " " + _quote_command_line_argument(E->get()); - } - + if (r_pipe) { if (read_stderr) { - argss += " 2>&1"; // Read stderr too + command += " 2>&1"; // Include stderr } - // 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((LPCWSTR)(argss.utf16().get_data()), L"r"); - ERR_FAIL_COND_V(!f, ERR_CANT_OPEN); + // Add extra quotes around the full command, to prevent it from stripping quotes in the command, + // because _wpopen calls command as "cmd.exe /c command", instead of executing it directly + command = _quote_command_line_argument(command); + FILE *f = _wpopen((LPCWSTR)(command.utf16().get_data()), L"r"); + ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, "Cannot create pipe from command: " + command); char buf[65535]; while (fgets(buf, 65535, f)) { if (p_pipe_mutex) { @@ -438,20 +431,40 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, p_pipe_mutex->unlock(); } } - int rv = _pclose(f); + if (r_exitcode) { *r_exitcode = rv; } - return OK; } - String cmdline = _quote_command_line_argument(path); - const List<String>::Element *I = p_arguments.front(); - while (I) { - cmdline += " " + _quote_command_line_argument(I->get()); - I = I->next(); + ProcessInfo pi; + ZeroMemory(&pi.si, sizeof(pi.si)); + pi.si.cb = sizeof(pi.si); + ZeroMemory(&pi.pi, sizeof(pi.pi)); + LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; + + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi); + ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command); + + WaitForSingleObject(pi.pi.hProcess, INFINITE); + if (r_exitcode) { + DWORD ret2; + GetExitCodeProcess(pi.pi.hProcess, &ret2); + *r_exitcode = ret2; + } + CloseHandle(pi.pi.hProcess); + CloseHandle(pi.pi.hThread); + + return OK; +}; + +Error OS_Windows::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) { + String path = p_path.replace("/", "\\"); + String command = _quote_command_line_argument(path); + for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) { + command += " " + _quote_command_line_argument(E->get()); } ProcessInfo pi; @@ -460,27 +473,15 @@ 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; - Char16String modstr = cmdline.utf16(); // Windows wants to change this no idea why. - int ret = CreateProcessW(nullptr, (LPWSTR)(modstr.ptrw()), nullptr, nullptr, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi); - ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK); + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi); + ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command); - if (p_blocking) { - WaitForSingleObject(pi.pi.hProcess, INFINITE); - if (r_exitcode) { - DWORD ret2; - GetExitCodeProcess(pi.pi.hProcess, &ret2); - *r_exitcode = ret2; - } - - CloseHandle(pi.pi.hProcess); - CloseHandle(pi.pi.hThread); - } else { - ProcessID pid = pi.pi.dwProcessId; - if (r_child_id) { - *r_child_id = pid; - } - process_map->insert(pid, pi); + ProcessID pid = pi.pi.dwProcessId; + if (r_child_id) { + *r_child_id = pid; } + process_map->insert(pid, pi); + return OK; }; @@ -764,77 +765,12 @@ 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) { ticks_per_second = 0; ticks_start = 0; main_loop = nullptr; process_map = nullptr; - //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; hInstance = _hInstance; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 78258f132b..8f9ef254f1 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -73,9 +73,6 @@ class OS_Windows : public OS { HINSTANCE hInstance; MainLoop *main_loop; - String tablet_driver; - Vector<String> tablet_drivers; - #ifdef WASAPI_ENABLED AudioDriverWASAPI driver_wasapi; #endif @@ -119,11 +116,6 @@ public: virtual String get_name() const override; - virtual int get_tablet_driver_count() const override; - virtual String get_tablet_driver_name(int p_driver) const override; - virtual String get_current_tablet_driver() const override; - virtual void set_current_tablet_driver(const String &p_driver) override; - virtual void initialize_joypads() override {} virtual Date get_date(bool utc) const override; @@ -136,7 +128,8 @@ public: virtual void delay_usec(uint32_t p_usec) const override; virtual uint64_t get_ticks_usec() const override; - 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) override; + virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override; + virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override; virtual Error kill(const ProcessID &p_pid) override; virtual int get_process_id() const override; diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index 56b620a6d9..c1f3827d15 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -53,7 +53,8 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er if (wlen < 0) return; - wchar_t *wbuf = (wchar_t *)malloc((len + 1) * sizeof(wchar_t)); + wchar_t *wbuf = (wchar_t *)memalloc((len + 1) * sizeof(wchar_t)); + ERR_FAIL_NULL_MSG(wbuf, "Out of memory."); MultiByteToWideChar(CP_UTF8, 0, buf, len, wbuf, wlen); wbuf[wlen] = 0; @@ -62,7 +63,7 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er else wprintf(L"%ls", wbuf); - free(wbuf); + memfree(wbuf); #ifdef DEBUG_ENABLED fflush(stdout); |