diff options
Diffstat (limited to 'platform')
81 files changed, 1853 insertions, 1241 deletions
diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h index e0ee2888c0..6d25a366a4 100644 --- a/platform/android/android_keys_utils.h +++ b/platform/android/android_keys_utils.h @@ -127,8 +127,8 @@ static _WinTranslatePair _ak_to_keycode[] = { { KEY_BACKSLASH, AKEYCODE_BACKSLASH }, { KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET }, { KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET }, - { KEY_CONTROL, AKEYCODE_CTRL_LEFT }, - { KEY_CONTROL, AKEYCODE_CTRL_RIGHT }, + { KEY_CTRL, AKEYCODE_CTRL_LEFT }, + { KEY_CTRL, AKEYCODE_CTRL_RIGHT }, { KEY_UNKNOWN, 0 } }; /* diff --git a/platform/android/detect.py b/platform/android/detect.py index 996b6dcf41..1b6af8662e 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -250,7 +250,7 @@ def configure(env): env["RANLIB"] = tools_path + "/ranlib" env["AS"] = tools_path + "/as" - common_opts = ["-fno-integrated-as", "-gcc-toolchain", gcc_toolchain_path] + common_opts = ["-gcc-toolchain", gcc_toolchain_path] # Compile flags @@ -258,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. @@ -283,6 +285,9 @@ def configure(env): ) env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"]) + if get_platform(env["ndk_platform"]) >= 24: + env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)]) + env["neon_enabled"] = False if env["android_arch"] == "x86": target_opts = ["-target", "i686-none-linux-android"] diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index f8ac29c738..0bae090702 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -201,8 +201,7 @@ String DirAccessJAndroid::get_filesystem_type() const { return "APK"; } -//FileType get_file_type() const; -size_t DirAccessJAndroid::get_space_left() { +uint64_t DirAccessJAndroid::get_space_left() { return 0; } diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index fed468d051..fe87644fe2 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -76,8 +76,7 @@ public: virtual String get_filesystem_type() const; - //virtual FileType get_file_type() const; - size_t get_space_left(); + uint64_t get_space_left(); static void setup(jobject p_io); diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index dd001baba9..f46dcf58dc 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -36,8 +36,6 @@ #include "java_godot_wrapper.h" #include "os_android.h" -#include <android/input.h> - #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_device_vulkan.h" #include "platform/android/vulkan/vulkan_context_android.h" @@ -51,7 +49,7 @@ DisplayServerAndroid *DisplayServerAndroid::get_singleton() { bool DisplayServerAndroid::has_feature(Feature p_feature) const { switch (p_feature) { //case FEATURE_CONSOLE_WINDOW: - //case FEATURE_CURSOR_SHAPE: + case FEATURE_CURSOR_SHAPE: //case FEATURE_CUSTOM_CURSOR_SHAPE: //case FEATURE_GLOBAL_MENU: //case FEATURE_HIDPI: @@ -61,7 +59,6 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const { //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: //case FEATURE_NATIVE_ICON: - //case FEATURE_NATIVE_VIDEO: //case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_CLIPBOARD: case FEATURE_KEEP_SCREEN_ON: @@ -491,10 +488,10 @@ void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p } void DisplayServerAndroid::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) { - ev->set_shift(shift_mem); - ev->set_alt(alt_mem); - ev->set_metakey(meta_mem); - ev->set_control(control_mem); + ev->set_shift_pressed(shift_mem); + ev->set_alt_pressed(alt_mem); + ev->set_meta_pressed(meta_mem); + ev->set_ctrl_pressed(control_mem); } void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) { @@ -529,7 +526,7 @@ void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int if (keycode == KEY_ALT) { alt_mem = p_pressed; } - if (keycode == KEY_CONTROL) { + if (keycode == KEY_CTRL) { control_mem = p_pressed; } if (keycode == KEY_META) { @@ -778,7 +775,7 @@ 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); } @@ -830,6 +827,12 @@ void DisplayServerAndroid::mouse_set_mode(MouseMode p_mode) { return; } + if (p_mode == MouseMode::MOUSE_MODE_HIDDEN) { + OS_Android::get_singleton()->get_godot_java()->get_godot_view()->set_pointer_icon(CURSOR_TYPE_NULL); + } else { + cursor_set_shape(cursor_shape); + } + if (p_mode == MouseMode::MOUSE_MODE_CAPTURED) { OS_Android::get_singleton()->get_godot_java()->get_godot_view()->request_pointer_capture(); } else { @@ -871,3 +874,19 @@ int DisplayServerAndroid::_android_button_mask_to_godot_button_mask(int android_ return godot_button_mask; } + +void DisplayServerAndroid::cursor_set_shape(DisplayServer::CursorShape p_shape) { + if (cursor_shape == p_shape) { + return; + } + + cursor_shape = p_shape; + + if (mouse_mode == MouseMode::MOUSE_MODE_VISIBLE || mouse_mode == MouseMode::MOUSE_MODE_CONFINED) { + OS_Android::get_singleton()->get_godot_java()->get_godot_view()->set_pointer_icon(android_cursors[cursor_shape]); + } +} + +DisplayServer::CursorShape DisplayServerAndroid::cursor_get_shape() const { + return cursor_shape; +} diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index b9d1641656..53c768f406 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -70,6 +70,28 @@ private: int buttons_state; + // https://developer.android.com/reference/android/view/PointerIcon + // mapping between Godot's cursor shape to Android's' + int android_cursors[CURSOR_MAX] = { + 1000, //CURSOR_ARROW + 1008, //CURSOR_IBEAM + 1002, //CURSOR_POINTIN + 1007, //CURSOR_CROSS + 1004, //CURSOR_WAIT + 1004, //CURSOR_BUSY + 1021, //CURSOR_DRAG + 1021, //CURSOR_CAN_DRO + 1000, //CURSOR_FORBIDD (no corresponding icon in Android's icon so fallback to default) + 1015, //CURSOR_VSIZE + 1014, //CURSOR_HSIZE + 1017, //CURSOR_BDIAGSI + 1016, //CURSOR_FDIAGSI + 1020, //CURSOR_MOVE + 1015, //CURSOR_VSPLIT + 1014, //CURSOR_HSPLIT + 1003, //CURSOR_HELP + }; + const int CURSOR_TYPE_NULL = 0; MouseMode mouse_mode; bool keep_screen_on; @@ -78,6 +100,8 @@ private: Point2 hover_prev_pos; // needed to calculate the relative position on hover events Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events + CursorShape cursor_shape = CursorShape::CURSOR_ARROW; + #if defined(VULKAN_ENABLED) VulkanContextAndroid *context_vulkan; RenderingDeviceVulkan *rendering_device_vulkan; @@ -180,6 +204,9 @@ public: void process_joy_event(JoypadEvent p_event); void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed); + virtual void cursor_set_shape(CursorShape p_shape); + virtual CursorShape cursor_get_shape() const; + void mouse_set_mode(MouseMode p_mode); MouseMode mouse_get_mode() const; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 5f9f420b59..cd3f00f935 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -201,8 +201,10 @@ 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"?> @@ -210,7 +212,7 @@ const String SPLASH_CONFIG_XML_CONTENT = R"SPLASH(<?xml version="1.0" encoding=" <item android:drawable="@drawable/splash_bg_color" /> <item> <bitmap - android:gravity="%s" + android:gravity="center" android:filter="%s" android:src="@drawable/splash" /> </item> @@ -853,7 +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"); Vector<String> perms; // Write permissions into the perms variable. @@ -919,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]); @@ -971,28 +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]); - } - - is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware"; iofs += 20; } @@ -1008,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) { @@ -1502,6 +1471,21 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { 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); @@ -1514,8 +1498,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { 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 gravity = scale_splash ? "fill" : "center"; - String processed_splash_config_xml = vformat(SPLASH_CONFIG_XML_CONTENT, gravity, bool_to_string(apply_filter)); + String processed_splash_config_xml = vformat(SPLASH_CONFIG_XML_CONTENT, bool_to_string(apply_filter)); return processed_splash_config_xml; } @@ -1692,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)); @@ -1803,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) \ { \ @@ -1820,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"); @@ -1837,7 +1819,9 @@ public: args.push_back("uninstall"); args.push_back(get_package_name(package_name)); - err = OS::get_singleton()->execute(adb, args, 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); @@ -1852,7 +1836,9 @@ public: args.push_back("-r"); args.push_back(tmp_export_path); - err = OS::get_singleton()->execute(adb, args, 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); @@ -1869,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, 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"); @@ -1880,7 +1868,9 @@ public: args.push_back("tcp:" + itos(dbg_port)); args.push_back("tcp:" + itos(dbg_port)); - OS::get_singleton()->execute(adb, args, nullptr, &rv); + output.clear(); + OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); print_line("Reverse result: " + itos(rv)); } @@ -1894,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, nullptr, &rv); + output.clear(); + err = OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); print_line("Reverse result2: " + itos(rv)); } } else { @@ -1922,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, 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); @@ -2130,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 && @@ -2267,7 +2247,7 @@ public: } 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); } } } @@ -2319,6 +2299,7 @@ public: return ERR_FILE_CANT_OPEN; } + String output; List<String> args; args.push_back("sign"); args.push_back("--verbose"); @@ -2334,7 +2315,9 @@ public: print_verbose("Signing debug binary using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); } int retval; - OS::get_singleton()->execute(apksigner, args, nullptr, &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; @@ -2352,7 +2335,9 @@ public: print_verbose("Verifying signed build using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); } - OS::get_singleton()->execute(apksigner, args, nullptr, &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; @@ -2657,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) \ { \ @@ -2702,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); } diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 40b8e90c6f..bbbb526af9 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -241,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"; @@ -278,10 +272,7 @@ 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"; @@ -289,16 +280,11 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { } String _get_application_tag(const Ref<EditorExportPreset> &p_preset) { - bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; String manifest_application_text = " <application android:label=\"@string/godot_project_name_string\"\n" " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n" - " android:icon=\"@mipmap/icon\">\n\n" - " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n"; + " android:icon=\"@mipmap/icon\">\n\n"; - 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 e288c16777..900d4d9b20 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -71,8 +71,9 @@ bool FileAccessAndroid::is_open() const { return a != nullptr; } -void FileAccessAndroid::seek(size_t p_position) { +void FileAccessAndroid::seek(uint64_t p_position) { ERR_FAIL_COND(!a); + AAsset_seek(a, p_position, SEEK_SET); pos = p_position; if (pos > len) { @@ -89,11 +90,11 @@ void FileAccessAndroid::seek_end(int64_t p_position) { pos = len + p_position; } -size_t FileAccessAndroid::get_position() const { +uint64_t FileAccessAndroid::get_position() const { return pos; } -size_t FileAccessAndroid::get_len() const { +uint64_t FileAccessAndroid::get_len() const { return len; } @@ -113,11 +114,10 @@ uint8_t FileAccessAndroid::get_8() const { return byte; } -int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { - ERR_FAIL_COND_V(!p_dst, -1); - ERR_FAIL_COND_V(p_length < 0, -1); +uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); - off_t r = AAsset_read(a, p_dst, p_length); + int r = AAsset_read(a, p_dst, p_length); if (pos + p_length > len) { eof = true; diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index 56010c918a..9b0f85089d 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -40,8 +40,8 @@ class FileAccessAndroid : public FileAccess { static FileAccess *create_android(); mutable AAsset *a = nullptr; - mutable size_t len = 0; - mutable size_t pos = 0; + mutable uint64_t len = 0; + mutable uint64_t pos = 0; mutable bool eof = false; public: @@ -51,15 +51,15 @@ public: virtual void close(); ///< close a file virtual bool is_open() const; ///< true when file is open - virtual void seek(size_t p_position); ///< seek to a given position + virtual void seek(uint64_t p_position); ///< seek to a given position virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file + virtual uint64_t get_position() const; ///< get position in the file + virtual uint64_t get_len() const; ///< get size of the file virtual bool eof_reached() const; ///< reading passed EOF virtual uint8_t get_8() const; ///< get a byte - virtual int get_buffer(uint8_t *p_dst, int p_length) const; + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; virtual Error get_error() const; ///< get last error diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 948fa8c00b..15feea15a4 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -30,11 +30,6 @@ <!-- 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. --> - <meta-data - android:name="xr_mode_metadata_name" - android:value="xr_mode_metadata_value" /> - <activity android:name=".GodotApp" android:label="@string/godot_project_name_string" @@ -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/build.gradle b/platform/android/java/app/build.gradle index f103f22db2..1b1fb47bd8 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -157,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 c0ae4007d2..b278d15bdf 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -3,7 +3,7 @@ ext.versions = [ 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', @@ -239,5 +239,5 @@ ext.shouldSign = { -> } ext.shouldNotStrip = { -> - return isAndroidStudio() + 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/build.gradle b/platform/android/java/build.gradle index ec02b0fc7a..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' } 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/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java index 4eac2d08d1..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; @@ -68,6 +69,24 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God } } + @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/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 63c91561ff..b3ee55ea64 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -44,11 +44,15 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PixelFormat; import android.opengl.GLSurfaceView; +import android.os.Build; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.PointerIcon; import android.view.SurfaceView; +import androidx.annotation.Keep; + /** * A simple GLSurfaceView sub-class that demonstrate how to perform * OpenGL ES 2.0 rendering into a GL Surface. Note the following important @@ -72,6 +76,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView private final GodotInputHandler inputHandler; private final GestureDetector detector; private final GodotRenderer godotRenderer; + private PointerIcon pointerIcon; public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_32_bits, boolean p_use_debug_opengl) { @@ -83,6 +88,9 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView this.inputHandler = new GodotInputHandler(this); this.detector = new GestureDetector(context, new GodotGestureHandler(this)); this.godotRenderer = new GodotRenderer(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + pointerIcon = PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT); + } init(xrMode, false, 16, 0); } @@ -149,6 +157,21 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView return inputHandler.onGenericMotionEvent(event); } + /** + * called from JNI to change pointer icon + */ + @Keep + public void setPointerIcon(int pointerType) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); + } + } + + @Override + public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) { + return pointerIcon; + } + private void init(XRMode xrMode, boolean translucent, int depth, int stencil) { setPreserveEGLContextOnPause(true); setFocusableInTouchMode(true); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index 2047c88070..ac333dd827 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -47,4 +47,6 @@ public interface GodotRenderView { abstract public void onBackPressed(); abstract public GodotInputHandler getInputHandler(); + + abstract public void setPointerIcon(int pointerType); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index 2e59dbc0d0..169c6cf770 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -37,17 +37,22 @@ import org.godotengine.godot.vulkan.VkSurfaceView; import android.annotation.SuppressLint; import android.content.Context; +import android.os.Build; import android.view.GestureDetector; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.PointerIcon; import android.view.SurfaceView; +import androidx.annotation.Keep; + public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { private final Godot godot; private final GodotInputHandler mInputHandler; private final GestureDetector mGestureDetector; private final VkRenderer mRenderer; + private PointerIcon pointerIcon; public GodotVulkanRenderView(Context context, Godot godot) { super(context); @@ -56,7 +61,9 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV mInputHandler = new GodotInputHandler(this); mGestureDetector = new GestureDetector(context, new GodotGestureHandler(this)); mRenderer = new VkRenderer(); - + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + pointerIcon = PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT); + } setFocusableInTouchMode(true); startRenderer(mRenderer); } @@ -124,6 +131,21 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV return mInputHandler.onGenericMotionEvent(event); } + /** + * called from JNI to change pointer icon + */ + @Keep + public void setPointerIcon(int pointerType) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); + } + } + + @Override + public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) { + return pointerIcon; + } + @Override public void onResume() { super.onResume(); diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index 6b5e44f371..837d2aeced 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -43,6 +43,7 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { if (android_get_device_api_level() >= __ANDROID_API_O__) { _request_pointer_capture = env->GetMethodID(_cls, "requestPointerCapture", "()V"); _release_pointer_capture = env->GetMethodID(_cls, "releasePointerCapture", "()V"); + _set_pointer_icon = env->GetMethodID(_cls, "setPointerIcon", "(I)V"); } } @@ -64,6 +65,15 @@ void GodotJavaViewWrapper::release_pointer_capture() { } } +void GodotJavaViewWrapper::set_pointer_icon(int pointer_type) { + if (_set_pointer_icon != 0) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + + env->CallVoidMethod(_godot_view, _set_pointer_icon, pointer_type); + } +} + GodotJavaViewWrapper::~GodotJavaViewWrapper() { JNIEnv *env = get_jni_env(); ERR_FAIL_COND(env == nullptr); diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h index bfb4369fb8..da547d8118 100644 --- a/platform/android/java_godot_view_wrapper.h +++ b/platform/android/java_godot_view_wrapper.h @@ -45,12 +45,14 @@ private: jmethodID _request_pointer_capture = 0; jmethodID _release_pointer_capture = 0; + jmethodID _set_pointer_icon = 0; public: GodotJavaViewWrapper(jobject godot_view); void request_pointer_capture(); void release_pointer_capture(); + void set_pointer_icon(int pointer_type); ~GodotJavaViewWrapper(); }; diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index ddc2368793..dbd1870ee6 100644 --- a/platform/android/net_socket_android.cpp +++ b/platform/android/net_socket_android.cpp @@ -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/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/SCsub b/platform/iphone/SCsub index ee7b2f4ab5..58b574a72f 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -19,7 +19,6 @@ iphone_lib = [ "godot_view_gesture_recognizer.mm", "device_metrics.m", "keyboard_input_view.mm", - "native_video_view.m", ] env_ios = env.Clone() diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h index 31a44723a5..34c56382a4 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); @@ -190,12 +190,6 @@ public: virtual void screen_set_keep_on(bool p_enable) override; virtual bool screen_is_kept_on() const override; - virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override; - virtual bool native_video_is_playing() const override; - virtual void native_video_pause() override; - virtual void native_video_unpause() override; - virtual void native_video_stop() override; - void resize_window(CGSize size); }; diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index 05dc78bb4d..9e74de0842 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -36,7 +36,6 @@ #import "godot_view.h" #include "ios.h" #import "keyboard_input_view.h" -#import "native_video_view.h" #include "os_iphone.h" #import "view_controller.h" @@ -136,13 +135,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 +221,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(); @@ -305,7 +304,6 @@ bool DisplayServerIPhone::has_feature(Feature p_feature) const { // case FEATURE_MOUSE_WARP: // case FEATURE_NATIVE_DIALOG: // case FEATURE_NATIVE_ICON: - // case FEATURE_NATIVE_VIDEO: // case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_CLIPBOARD: case FEATURE_KEEP_SCREEN_ON: @@ -569,69 +567,6 @@ bool DisplayServerIPhone::screen_is_kept_on() const { return [UIApplication sharedApplication].idleTimerDisabled; } -Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) { - FileAccess *f = FileAccess::open(p_path, FileAccess::READ); - bool exists = f && f->is_open(); - - String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir(); - - if (!exists) { - return FAILED; - } - - String tempFile = OSIPhone::get_singleton()->get_user_data_dir(); - - if (p_path.begins_with("res://")) { - if (PackedData::get_singleton()->has_path(p_path)) { - printf("Unable to play %s using the native player as it resides in a .pck file\n", p_path.utf8().get_data()); - return ERR_INVALID_PARAMETER; - } else { - p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path()); - } - } else if (p_path.begins_with("user://")) { - p_path = p_path.replace("user:/", user_data_dir); - } - - memdelete(f); - - printf("Playing video: %s\n", p_path.utf8().get_data()); - - String file_path = ProjectSettings::get_singleton()->globalize_path(p_path); - - NSString *filePath = [[NSString alloc] initWithUTF8String:file_path.utf8().get_data()]; - NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()]; - NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()]; - - if (![AppDelegate.viewController playVideoAtPath:filePath - volume:p_volume - audio:audioTrack - subtitle:subtitleTrack]) { - return OK; - } - - return FAILED; -} - -bool DisplayServerIPhone::native_video_is_playing() const { - return [AppDelegate.viewController.videoView isVideoPlaying]; -} - -void DisplayServerIPhone::native_video_pause() { - if (native_video_is_playing()) { - [AppDelegate.viewController.videoView pauseVideo]; - } -} - -void DisplayServerIPhone::native_video_unpause() { - [AppDelegate.viewController.videoView unpauseVideo]; -}; - -void DisplayServerIPhone::native_video_stop() { - if (native_video_is_playing()) { - [AppDelegate.viewController.videoView stopVideo]; - } -} - void DisplayServerIPhone::resize_window(CGSize viewSize) { Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m index 3ce9bffc79..6c433c5c3c 100644 --- a/platform/iphone/godot_app_delegate.m +++ b/platform/iphone/godot_app_delegate.m @@ -56,7 +56,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil; [services addObject:service]; } -// UIApplicationDelegate documantation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate +// UIApplicationDelegate documentation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate // MARK: Window 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.mm b/platform/iphone/godot_view.mm index 468fa2928a..00a88d79c5 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -291,14 +291,14 @@ static const float earth_gravity = 9.80665; - (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; } @@ -318,11 +318,11 @@ static const float earth_gravity = 9.80665; - (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; } @@ -332,7 +332,7 @@ static const float earth_gravity = 9.80665; - (void)clearTouches { for (int i = 0; i < max_touches; i++) { - godot_touches[i] = NULL; + godot_touches[i] = nullptr; } } 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/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/native_video_view.h b/platform/iphone/native_video_view.h deleted file mode 100644 index 2df5e27fa4..0000000000 --- a/platform/iphone/native_video_view.h +++ /dev/null @@ -1,41 +0,0 @@ -/*************************************************************************/ -/* native_video_view.h */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -#import <UIKit/UIKit.h> - -@interface GodotNativeVideoView : UIView - -- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; -- (BOOL)isVideoPlaying; -- (void)pauseVideo; -- (void)unpauseVideo; -- (void)stopVideo; - -@end diff --git a/platform/iphone/native_video_view.m b/platform/iphone/native_video_view.m deleted file mode 100644 index f126749600..0000000000 --- a/platform/iphone/native_video_view.m +++ /dev/null @@ -1,266 +0,0 @@ -/*************************************************************************/ -/* native_video_view.m */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -#import "native_video_view.h" -#import <AVFoundation/AVFoundation.h> - -@interface GodotNativeVideoView () - -@property(strong, nonatomic) AVAsset *avAsset; -@property(strong, nonatomic) AVPlayerItem *avPlayerItem; -@property(strong, nonatomic) AVPlayer *avPlayer; -@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; -@property(assign, nonatomic) CMTime videoCurrentTime; -@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying; - -@end - -@implementation GodotNativeVideoView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - - if (self) { - [self godot_commonInit]; - } - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - - if (self) { - [self godot_commonInit]; - } - - return self; -} - -- (void)godot_commonInit { - self.isVideoCurrentlyPlaying = NO; - self.videoCurrentTime = kCMTimeZero; - - [self observeVideoAudio]; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - self.avPlayerLayer.frame = self.bounds; -} - -- (void)observeVideoAudio { - printf("******** adding observer for sound routing changes\n"); - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(audioRouteChangeListenerCallback:) - name:AVAudioSessionRouteChangeNotification - object:nil]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) { - [self handleVideoOrPlayerStatus]; - } - - if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) { - [self handleVideoPlayRate]; - } -} - -// MARK: Video Audio - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { - printf("*********** route changed!\n"); - NSDictionary *interuptionDict = notification.userInfo; - - NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; - - switch (routeChangeReason) { - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { - NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); - NSLog(@"Headphone/Line plugged in"); - } break; - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { - NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); - NSLog(@"Headphone/Line was pulled. Resuming video play...."); - if ([self isVideoPlaying]) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self.avPlayer play]; // NOTE: change this line according your current player implementation - NSLog(@"resumed play"); - }); - } - } break; - case AVAudioSessionRouteChangeReasonCategoryChange: { - // called at start - also when other audio wants to play - NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); - } break; - } -} - -// MARK: Native Video Player - -- (void)handleVideoOrPlayerStatus { - if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) { - [self stopVideo]; - } - - if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) { - // NSLog(@"time: %@", self.video_current_time); - [self.avPlayer seekToTime:self.videoCurrentTime]; - self.videoCurrentTime = kCMTimeZero; - } -} - -- (void)handleVideoPlayRate { - NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate); - if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self.avPlayer play]; // NOTE: change this line according your current player implementation - NSLog(@"resumed play"); - }); - - NSLog(@" . . . PAUSED (or just started)"); - } -} - -- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { - self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]; - - self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset]; - [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; - - self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; - self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; - - [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(playerItemDidReachEnd:) - name:AVPlayerItemDidPlayToEndTimeNotification - object:[self.avPlayer currentItem]]; - - [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; - - [self.avPlayerLayer setFrame:self.bounds]; - [self.layer addSublayer:self.avPlayerLayer]; - [self.avPlayer play]; - - AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - - NSMutableArray *allAudioParams = [NSMutableArray array]; - for (id track in audioGroup.options) { - NSString *language = [[track locale] localeIdentifier]; - NSLog(@"subtitle lang: %@", language); - - if ([language isEqualToString:audioTrack]) { - AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; - [audioInputParams setVolume:videoVolume atTime:kCMTimeZero]; - [audioInputParams setTrackID:[track trackID]]; - [allAudioParams addObject:audioInputParams]; - - AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; - [audioMix setInputParameters:allAudioParams]; - - [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; - [self.avPlayer.currentItem setAudioMix:audioMix]; - - break; - } - } - - AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; - NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; - - for (id track in useableTracks) { - NSString *language = [[track locale] localeIdentifier]; - NSLog(@"subtitle lang: %@", language); - - if ([language isEqualToString:subtitleTrack]) { - [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; - break; - } - } - - self.isVideoCurrentlyPlaying = YES; - - return true; -} - -- (BOOL)isVideoPlaying { - if (self.avPlayer.error) { - printf("Error during playback\n"); - } - return (self.avPlayer.rate > 0 && !self.avPlayer.error); -} - -- (void)pauseVideo { - self.videoCurrentTime = self.avPlayer.currentTime; - [self.avPlayer pause]; - self.isVideoCurrentlyPlaying = NO; -} - -- (void)unpauseVideo { - [self.avPlayer play]; - self.isVideoCurrentlyPlaying = YES; -} - -- (void)playerItemDidReachEnd:(NSNotification *)notification { - [self stopVideo]; -} - -- (void)finishPlayingVideo { - [self.avPlayer pause]; - [self.avPlayerLayer removeFromSuperlayer]; - self.avPlayerLayer = nil; - - if (self.avPlayerItem) { - [self.avPlayerItem removeObserver:self forKeyPath:@"status"]; - self.avPlayerItem = nil; - } - - if (self.avPlayer) { - [self.avPlayer removeObserver:self forKeyPath:@"status"]; - self.avPlayer = nil; - } - - self.avAsset = nil; - - self.isVideoCurrentlyPlaying = NO; -} - -- (void)stopVideo { - [self finishPlayingVideo]; - - [self removeFromSuperview]; -} - -@end diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 51c4da2960..458834ce3a 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -301,10 +301,6 @@ void OSIPhone::on_focus_out() { [AppDelegate.viewController.godotView stopRendering]; - if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) { - DisplayServerIPhone::get_singleton()->native_video_pause(); - } - audio_driver.stop(); } } @@ -319,10 +315,6 @@ void OSIPhone::on_focus_in() { [AppDelegate.viewController.godotView startRendering]; - if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) { - DisplayServerIPhone::get_singleton()->native_video_unpause(); - } - audio_driver.start(); } } diff --git a/platform/iphone/plugin/godot_plugin_config.h b/platform/iphone/plugin/godot_plugin_config.h index f4e30c8349..e2546e733c 100644 --- a/platform/iphone/plugin/godot_plugin_config.h +++ b/platform/iphone/plugin/godot_plugin_config.h @@ -218,8 +218,9 @@ static inline uint64_t get_plugin_modification_time(const PluginConfigIOS &plugi } 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)); diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index 52fb6fbbf2..400145b8b7 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -37,11 +37,6 @@ @interface ViewController : UIViewController @property(nonatomic, readonly, strong) GodotView *godotView; -@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView; @property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView; -// MARK: Native Video Player - -- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; - @end diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 6cef244567..2723ac4706 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -34,7 +34,6 @@ #import "godot_view.h" #import "godot_view_renderer.h" #import "keyboard_input_view.h" -#import "native_video_view.h" #include "os_iphone.h" #import <AVFoundation/AVFoundation.h> @@ -43,7 +42,6 @@ @interface ViewController () <GodotViewDelegate> @property(strong, nonatomic) GodotViewRenderer *renderer; -@property(strong, nonatomic) GodotNativeVideoView *videoView; @property(strong, nonatomic) GodotKeyboardInputView *keyboardView; @property(strong, nonatomic) UIView *godotLoadingOverlay; @@ -151,10 +149,6 @@ } - (void)dealloc { - [self.videoView stopVideo]; - - self.videoView = nil; - self.keyboardView = nil; self.renderer = nil; @@ -243,22 +237,4 @@ } } -// MARK: Native Video Player - -- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { - // If we are showing some video already, reuse existing view for new video. - if (self.videoView) { - return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack]; - } else { - // Create autoresizing view for video playback. - GodotNativeVideoView *videoView = [[GodotNativeVideoView alloc] initWithFrame:self.view.bounds]; - videoView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - [self.view addSubview:videoView]; - - self.videoView = videoView; - - return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack]; - } -} - @end 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/SCsub b/platform/javascript/SCsub index a760e36982..62a8660ae4 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -6,7 +6,7 @@ javascript_files = [ "audio_driver_javascript.cpp", "display_server_javascript.cpp", "http_client_javascript.cpp", - "javascript_eval.cpp", + "javascript_singleton.cpp", "javascript_main.cpp", "os_javascript.cpp", "api/javascript_tools_editor_plugin.cpp", @@ -23,10 +23,8 @@ sys_env.AddJSLibraries( ] ) -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"]) + sys_env.AddJSLibraries(["js/libs/library_godot_javascript_singleton.js"]) for lib in sys_env["JS_LIBS"]: sys_env.Append(LINKFLAGS=["--js-library", lib]) @@ -47,6 +45,7 @@ if env["gdnative_enabled"]: sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"]) sys_env.Append(CCFLAGS=["-s", "EXPORT_ALL=1"]) sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"]) + sys_env.Append(LINKFLAGS=["-s", "WARN_ON_UNDEFINED_SYMBOLS=0"]) # Force exporting the standard library (printf, malloc, etc.) sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi" # The main emscripten runtime, with exported standard libraries. diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 2f7bde065f..5ad2bf56cf 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -30,13 +30,14 @@ #include "api.h" #include "core/config/engine.h" -#include "javascript_eval.h" +#include "javascript_singleton.h" #include "javascript_tools_editor_plugin.h" static JavaScript *javascript_eval; void register_javascript_api() { JavaScriptToolsEditorPlugin::initialize(); + ClassDB::register_virtual_class<JavaScriptObject>(); ClassDB::register_virtual_class<JavaScript>(); javascript_eval = memnew(JavaScript); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval)); @@ -61,10 +62,46 @@ JavaScript::~JavaScript() {} void JavaScript::_bind_methods() { ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScript::eval, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_interface", "interface"), &JavaScript::get_interface); + ClassDB::bind_method(D_METHOD("create_callback", "callable"), &JavaScript::create_callback); + { + MethodInfo mi; + mi.name = "create_object"; + mi.arguments.push_back(PropertyInfo(Variant::STRING, "object")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "create_object", &JavaScript::_create_object_bind, mi); + } + ClassDB::bind_method(D_METHOD("download_buffer", "buffer", "name", "mime"), &JavaScript::download_buffer, DEFVAL("application/octet-stream")); } #if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED) Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { return Variant(); } + +Ref<JavaScriptObject> JavaScript::get_interface(const String &p_interface) { + return Ref<JavaScriptObject>(); +} + +Ref<JavaScriptObject> JavaScript::create_callback(const Callable &p_callable) { + return Ref<JavaScriptObject>(); +} + +Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Ref<JavaScriptObject>(); + } + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + return Ref<JavaScriptObject>(); + } + return Ref<JavaScriptObject>(); +} +#endif +#if !defined(JAVASCRIPT_ENABLED) +void JavaScript::download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime) { +} #endif diff --git a/platform/javascript/api/javascript_eval.h b/platform/javascript/api/javascript_singleton.h index 24f7648ed9..1615efa87e 100644 --- a/platform/javascript/api/javascript_eval.h +++ b/platform/javascript/api/javascript_singleton.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* javascript_eval.h */ +/* javascript_singleton.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,10 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef JAVASCRIPT_EVAL_H -#define JAVASCRIPT_EVAL_H +#ifndef JAVASCRIPT_SINGLETON_H +#define JAVASCRIPT_SINGLETON_H #include "core/object/class_db.h" +#include "core/object/reference.h" + +class JavaScriptObject : public Reference { +private: + GDCLASS(JavaScriptObject, Reference); + +protected: + virtual bool _set(const StringName &p_name, const Variant &p_value) { return false; } + virtual bool _get(const StringName &p_name, Variant &r_ret) const { return false; } + virtual void _get_property_list(List<PropertyInfo> *p_list) const {} +}; class JavaScript : public Object { private: @@ -44,10 +55,14 @@ protected: public: Variant eval(const String &p_code, bool p_use_global_exec_context = false); + Ref<JavaScriptObject> get_interface(const String &p_interface); + Ref<JavaScriptObject> create_callback(const Callable &p_callable); + Variant _create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime = "application/octet-stream"); static JavaScript *get_singleton(); JavaScript(); ~JavaScript(); }; -#endif // JAVASCRIPT_EVAL_H +#endif // JAVASCRIPT_SINGLETON_H diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index 8355faccc2..ac4e6a1256 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -41,7 +41,7 @@ // JavaScript functions defined in library_godot_editor_tools.js extern "C" { -extern void godot_js_editor_download_file(const char *p_path, const char *p_name, const char *p_mime); +extern int godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, const char *p_name, const char *p_mime); } static void _javascript_editor_init_callback() { @@ -65,11 +65,16 @@ 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); - godot_js_editor_download_file("/tmp/project.zip", "project.zip", "application/zip"); + zipClose(zip, nullptr); + FileAccess *f = FileAccess::open("/tmp/project.zip", FileAccess::READ); + ERR_FAIL_COND_MSG(!f, "Unable to create zip file"); + Vector<uint8_t> buf; + buf.resize(f->get_len()); + f->get_buffer(buf.ptrw(), buf.size()); + godot_js_os_download_buffer(buf.ptr(), buf.size(), "project.zip", "application/zip"); } void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { @@ -79,7 +84,7 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z return; } Vector<uint8_t> data; - int len = f->get_len(); + uint64_t len = f->get_len(); data.resize(len); f->get_buffer(data.ptrw(), len); f->close(); @@ -88,12 +93,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 +121,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/detect.py b/platform/javascript/detect.py index ac8d8de7e0..d01e8a8bd4 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -95,8 +95,9 @@ def configure(env): if env["initial_memory"] < 64: print("Editor build requires at least 64MiB of initial memory. Forcing it.") env["initial_memory"] = 64 - elif env["builtin_icu"]: 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. @@ -174,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"] @@ -228,8 +229,8 @@ 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','cwrap']"]) + # 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"]) @@ -238,6 +239,6 @@ def configure(env): env.Append( LINKFLAGS=[ "-s", - "EXPORTED_FUNCTIONS=['_main', '_emscripten_webgl_get_current_context', '_emscripten_webgl_commit_frame', '_emscripten_webgl_create_context']", + "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 fa6f5c1e9e..dae0b5f7e7 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -120,10 +120,10 @@ void DisplayServerJavaScript::request_quit_callback() { template <typename T> void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { - godot_event->set_shift(emscripten_event_ptr->shiftKey); - godot_event->set_alt(emscripten_event_ptr->altKey); - godot_event->set_control(emscripten_event_ptr->ctrlKey); - godot_event->set_metakey(emscripten_event_ptr->metaKey); + godot_event->set_shift_pressed(emscripten_event_ptr->shiftKey); + godot_event->set_alt_pressed(emscripten_event_ptr->altKey); + godot_event->set_ctrl_pressed(emscripten_event_ptr->ctrlKey); + godot_event->set_meta_pressed(emscripten_event_ptr->metaKey); } Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { @@ -215,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(); } @@ -399,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,10 +455,10 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript ev->set_position(input->get_mouse_position()); ev->set_global_position(ev->get_position()); - ev->set_shift(input->is_key_pressed(KEY_SHIFT)); - ev->set_alt(input->is_key_pressed(KEY_ALT)); - ev->set_control(input->is_key_pressed(KEY_CONTROL)); - ev->set_metakey(input->is_key_pressed(KEY_META)); + ev->set_shift_pressed(input->is_key_pressed(KEY_SHIFT)); + ev->set_alt_pressed(input->is_key_pressed(KEY_ALT)); + ev->set_ctrl_pressed(input->is_key_pressed(KEY_CTRL)); + ev->set_meta_pressed(input->is_key_pressed(KEY_META)); if (p_event->deltaY < 0) ev->set_button_index(MOUSE_BUTTON_WHEEL_UP); @@ -536,7 +536,7 @@ bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { return godot_js_display_touchscreen_is_available(); } -// Virtual Keybaord +// Virtual Keyboard 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()) { @@ -771,8 +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); \ +#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. @@ -827,7 +827,6 @@ bool DisplayServerJavaScript::has_feature(Feature p_feature) const { //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: //case FEATURE_NATIVE_ICON: - //case FEATURE_NATIVE_VIDEO: //case FEATURE_WINDOW_TRANSPARENCY: //case FEATURE_KEEP_SCREEN_ON: //case FEATURE_ORIENTATION: diff --git a/platform/javascript/dom_keys.inc b/platform/javascript/dom_keys.inc index 7902efafe0..69340ff58c 100644 --- a/platform/javascript/dom_keys.inc +++ b/platform/javascript/dom_keys.inc @@ -159,8 +159,8 @@ int dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b DOM2GODOT("Backspace", BACKSPACE); DOM2GODOT("CapsLock", CAPSLOCK); DOM2GODOT("ContextMenu", MENU); - DOM2GODOT("ControlLeft", CONTROL); - DOM2GODOT("ControlRight", CONTROL); + DOM2GODOT("ControlLeft", CTRL); + DOM2GODOT("ControlRight", CTRL); DOM2GODOT("Enter", ENTER); DOM2GODOT("MetaLeft", SUPER_L); DOM2GODOT("MetaRight", SUPER_R); diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py index b3b15a1574..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 @@ -59,7 +59,23 @@ def create_template_zip(env, js, wasm, extra): if env["tools"]: # HTML html = "#misc/dist/html/editor.html" - subst_dict = {"@GODOT_VERSION@": get_build_version(), "@GODOT_NAME@": "GodotEngine"} + 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")) @@ -82,6 +98,10 @@ def create_template_zip(env, js, wasm, extra): # 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( diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 3f04bedea2..e398072312 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"; @@ -138,7 +162,7 @@ public: 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(); @@ -146,11 +170,11 @@ public: while (true) { uint8_t bytes[4096]; - int read = f->get_buffer(bytes, 4096); - if (read < 1) { + uint64_t read = f->get_buffer(bytes, 4096); + if (read == 0) { break; } - err = connection->put_data(bytes, read); + err = peer->put_data(bytes, read); if (err != OK) { memdelete(f); ERR_FAIL(); @@ -163,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; @@ -189,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(); @@ -242,7 +288,32 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { return name; } + 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); @@ -281,10 +352,90 @@ 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, const Dictionary &p_file_sizes) { - String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size()); - String str_export; +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]; + 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++) { + 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()); @@ -295,29 +446,172 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re for (int i = 0; i < flags.size(); i++) { args.push_back(flags[i]); } - Dictionary config; 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); +} - for (int i = 0; i < lines.size(); i++) { - String current_line = lines[i]; - current_line = current_line.replace("$GODOT_URL", p_name + ".js"); - 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_CONFIG", str_config); - str_export += current_line + "\n"; +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; +} - CharString cs = str_export.utf8(); - p_html.resize(cs.length()); - for (int i = 0; i < cs.length(); i++) { - p_html.write[i] = cs[i]; +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) { @@ -350,10 +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::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 { @@ -419,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; } @@ -441,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); @@ -450,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()); @@ -459,124 +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; } - Vector<uint8_t> html; + // Parse generated file sizes (pck and wasm, to help show a meaningful loading bar). Dictionary file_sizes; - do { - //get filename - unz_file_info info; - char fname[16384]; - unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); - - String file = fname; - - // HTML is handled later - if (file == "godot.html") { - if (custom_html.is_empty()) { - html.resize(info.uncompressed_size); - unzOpenCurrentFile(pkg); - unzReadCurrentFile(pkg, html.ptrw(), html.size()); - unzCloseCurrentFile(pkg); - } - continue; - } - Vector<uint8_t> data; - data.resize(info.uncompressed_size); - - //read - unzOpenCurrentFile(pkg); - unzReadCurrentFile(pkg, data.ptrw(), data.size()); - unzCloseCurrentFile(pkg); - - //write - - 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"; - file_sizes[file.get_file()] = (uint64_t)info.uncompressed_size; - } - - 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; - } - html.resize(f->get_len()); - f->get_buffer(html.ptrw(), html.size()); + 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); + f = nullptr; } - { - FileAccess *f = FileAccess::open(pck_path, FileAccess::READ); - if (f) { - file_sizes[pck_path.get_file()] = (uint64_t)f->get_len(); - memdelete(f); - f = nullptr; - } - _fix_html(html, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects, file_sizes); - 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(html.ptr(), html.size()); + f = FileAccess::open(base_path + ".wasm", FileAccess::READ); + if (f) { + file_sizes[base_name + ".wasm"] = (uint64_t)f->get_len(); memdelete(f); - html.resize(0); + 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(); - } - } - if (splash.is_null()) { - splash = Ref<Image>(memnew(Image(boot_splash_png))); + // 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; + } + 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; @@ -584,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; @@ -644,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 { @@ -671,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; @@ -730,7 +993,12 @@ EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() { 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/http_client.h.inc b/platform/javascript/http_client.h.inc index 842a93fcba..6544d41c98 100644 --- a/platform/javascript/http_client.h.inc +++ b/platform/javascript/http_client.h.inc @@ -34,7 +34,8 @@ Error make_request(Method p_method, const String &p_url, const Vector<String> &p static void _parse_headers(int p_len, const char **p_headers, void *p_ref); int js_id = 0; -int read_limit = 4096; +// 64 KiB by default (favors fast download speeds at the cost of memory usage). +int read_limit = 65536; Status status = STATUS_DISCONNECTED; String host; diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index b79c965854..a6cf4b0eb8 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -209,7 +209,7 @@ PackedByteArray HTTPClient::read_response_body_chunk() { return chunk; } chunk.resize(read); - copymem(chunk.ptrw(), response_buffer.ptr(), read); + memcpy(chunk.ptrw(), response_buffer.ptr(), read); return chunk; } diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp deleted file mode 100644 index cb19dd20d4..0000000000 --- a/platform/javascript/javascript_eval.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/*************************************************************************/ -/* javascript_eval.cpp */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -#ifdef JAVASCRIPT_EVAL_ENABLED - -#include "api/javascript_eval.h" -#include "emscripten.h" - -extern "C" { -union js_eval_ret { - uint32_t b; - double d; - char *s; -}; - -extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len)); -} - -void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) { - PackedByteArray *arr = (PackedByteArray *)p_arr; - VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write; - arr->resize(p_len); - *write = arr->write; - return arr->ptrw(); -} - -Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { - union js_eval_ret js_data; - PackedByteArray arr; - VectorWriteProxy<uint8_t> arr_write; - - Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write)); - - switch (return_type) { - case Variant::BOOL: - return js_data.b; - case Variant::FLOAT: - return js_data.d; - case Variant::STRING: { - String str = String::utf8(js_data.s); - free(js_data.s); // Must free the string allocated in JS. - return str; - } - case Variant::PACKED_BYTE_ARRAY: - arr_write = VectorWriteProxy<uint8_t>(); - return arr; - default: - return Variant(); - } -} - -#endif // JAVASCRIPT_EVAL_ENABLED diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 0fe95b0a8f..40771d1882 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -66,6 +66,11 @@ void main_loop_callback() { int target_fps = Engine::get_singleton()->get_target_fps(); if (target_fps > 0) { + if (current_ticks - target_ticks > 1000000) { + // When the window loses focus, we stop getting updates and accumulate delay. + // For this reason, if the difference is too big, we reset target ticks to the current ticks. + target_ticks = current_ticks; + } target_ticks += (uint64_t)(1000000 / target_fps); } if (os->main_loop_iterate()) { diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp new file mode 100644 index 0000000000..5ef67c0cdd --- /dev/null +++ b/platform/javascript/javascript_singleton.cpp @@ -0,0 +1,343 @@ +/*************************************************************************/ +/* javascript_singleton.cpp */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifdef JAVASCRIPT_EVAL_ENABLED + +#include "api/javascript_singleton.h" +#include "emscripten.h" + +extern "C" { +typedef union { + int64_t i; + double r; + void *p; +} godot_js_wrapper_ex; + +typedef int (*GodotJSWrapperVariant2JSCallback)(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock); +typedef void (*GodotJSWrapperFreeLockCallback)(void **p_lock, int p_type); +extern int godot_js_wrapper_interface_get(const char *p_name); +extern int godot_js_wrapper_object_call(int p_id, const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback); +extern int godot_js_wrapper_object_get(int p_id, godot_js_wrapper_ex *p_val, const char *p_prop); +extern int godot_js_wrapper_object_getvar(int p_id, int p_type, godot_js_wrapper_ex *p_val); +extern int godot_js_wrapper_object_setvar(int p_id, int p_key_type, godot_js_wrapper_ex *p_key_ex, int p_val_type, godot_js_wrapper_ex *p_val_ex); +extern void godot_js_wrapper_object_set(int p_id, const char *p_name, int p_type, godot_js_wrapper_ex *p_val); +extern void godot_js_wrapper_object_unref(int p_id); +extern int godot_js_wrapper_create_cb(void *p_ref, void (*p_callback)(void *p_ref, int p_arg_id, int p_argc)); +extern int godot_js_wrapper_create_object(const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback); +}; + +class JavaScriptObjectImpl : public JavaScriptObject { +private: + friend class JavaScript; + + int _js_id = 0; + Callable _callable; + + static int _variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock); + static void _free_lock(void **p_lock, int p_type); + static Variant _js2variant(int p_type, godot_js_wrapper_ex *p_val); + static void *_alloc_variants(int p_size); + static void _callback(void *p_ref, int p_arg_id, int p_argc); + +protected: + bool _set(const StringName &p_name, const Variant &p_value) override; + bool _get(const StringName &p_name, Variant &r_ret) const override; + void _get_property_list(List<PropertyInfo> *p_list) const override; + +public: + Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override; + void setvar(const Variant &p_key, const Variant &p_value, bool *r_valid = nullptr) override; + Variant call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) override; + JavaScriptObjectImpl() {} + JavaScriptObjectImpl(int p_id) { _js_id = p_id; } + ~JavaScriptObjectImpl() { + if (_js_id) { + godot_js_wrapper_object_unref(_js_id); + } + } +}; + +bool JavaScriptObjectImpl::_set(const StringName &p_name, const Variant &p_value) { + ERR_FAIL_COND_V_MSG(!_js_id, false, "Invalid JS instance"); + const String name = p_name; + godot_js_wrapper_ex exchange; + void *lock = nullptr; + const Variant *v = &p_value; + int type = _variant2js((const void **)&v, 0, &exchange, &lock); + godot_js_wrapper_object_set(_js_id, name.utf8().get_data(), type, &exchange); + if (lock) { + _free_lock(&lock, type); + } + return true; +} + +bool JavaScriptObjectImpl::_get(const StringName &p_name, Variant &r_ret) const { + ERR_FAIL_COND_V_MSG(!_js_id, false, "Invalid JS instance"); + const String name = p_name; + godot_js_wrapper_ex exchange; + int type = godot_js_wrapper_object_get(_js_id, &exchange, name.utf8().get_data()); + r_ret = _js2variant(type, &exchange); + return true; +} + +Variant JavaScriptObjectImpl::getvar(const Variant &p_key, bool *r_valid) const { + if (r_valid) { + *r_valid = false; + } + godot_js_wrapper_ex exchange; + void *lock = nullptr; + const Variant *v = &p_key; + int prop_type = _variant2js((const void **)&v, 0, &exchange, &lock); + int type = godot_js_wrapper_object_getvar(_js_id, prop_type, &exchange); + if (lock) { + _free_lock(&lock, prop_type); + } + if (type < 0) { + return Variant(); + } + if (r_valid) { + *r_valid = true; + } + return _js2variant(type, &exchange); +} + +void JavaScriptObjectImpl::setvar(const Variant &p_key, const Variant &p_value, bool *r_valid) { + if (r_valid) { + *r_valid = false; + } + godot_js_wrapper_ex kex, vex; + void *klock = nullptr; + void *vlock = nullptr; + const Variant *kv = &p_key; + const Variant *vv = &p_value; + int ktype = _variant2js((const void **)&kv, 0, &kex, &klock); + int vtype = _variant2js((const void **)&vv, 0, &vex, &vlock); + int ret = godot_js_wrapper_object_setvar(_js_id, ktype, &kex, vtype, &vex); + if (klock) { + _free_lock(&klock, ktype); + } + if (vlock) { + _free_lock(&vlock, vtype); + } + if (ret == 0 && r_valid) { + *r_valid = true; + } +} + +void JavaScriptObjectImpl::_get_property_list(List<PropertyInfo> *p_list) const { +} + +void JavaScriptObjectImpl::_free_lock(void **p_lock, int p_type) { + ERR_FAIL_COND_MSG(*p_lock == nullptr, "No lock to free!"); + const Variant::Type type = (Variant::Type)p_type; + switch (type) { + case Variant::STRING: { + CharString *cs = (CharString *)(*p_lock); + memdelete(cs); + *p_lock = nullptr; + } break; + default: + ERR_FAIL_MSG("Unknown lock type to free. Likely a bug."); + } +} + +Variant JavaScriptObjectImpl::_js2variant(int p_type, godot_js_wrapper_ex *p_val) { + Variant::Type type = (Variant::Type)p_type; + switch (type) { + case Variant::BOOL: + return Variant((bool)p_val->i); + case Variant::INT: + return p_val->i; + case Variant::FLOAT: + return p_val->r; + case Variant::STRING: { + String out((const char *)p_val->p); + free(p_val->p); + return out; + } + case Variant::OBJECT: { + return memnew(JavaScriptObjectImpl(p_val->i)); + } + default: + return Variant(); + } +} + +int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock) { + const Variant **args = (const Variant **)p_args; + const Variant *v = args[p_pos]; + Variant::Type type = v->get_type(); + switch (type) { + case Variant::BOOL: + r_val->i = v->operator bool() ? 1 : 0; + break; + case Variant::INT: { + const int64_t tmp = v->operator int64_t(); + if (tmp >= 1 << 31) { + r_val->r = (double)tmp; + return Variant::FLOAT; + } + r_val->i = v->operator int64_t(); + } break; + case Variant::FLOAT: + r_val->r = v->operator real_t(); + break; + case Variant::STRING: { + CharString *cs = memnew(CharString(v->operator String().utf8())); + r_val->p = (void *)cs->get_data(); + *p_lock = (void *)cs; + } break; + case Variant::OBJECT: { + JavaScriptObject *js_obj = Object::cast_to<JavaScriptObject>(v->operator Object *()); + r_val->i = js_obj != nullptr ? ((JavaScriptObjectImpl *)js_obj)->_js_id : 0; + } break; + default: + break; + } + return type; +} + +Variant JavaScriptObjectImpl::call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) { + godot_js_wrapper_ex exchange; + const String method = p_method; + void *lock = nullptr; + const int type = godot_js_wrapper_object_call(_js_id, method.utf8().get_data(), (void **)p_args, p_argc, &_variant2js, &exchange, &lock, &_free_lock); + r_error.error = Callable::CallError::CALL_OK; + if (type < 0) { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return Variant(); + } + return _js2variant(type, &exchange); +} + +void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) { + const JavaScriptObjectImpl *obj = (JavaScriptObjectImpl *)p_ref; + ERR_FAIL_COND_MSG(obj->_callable.is_null(), "JavaScript callback failed."); + Vector<const Variant *> argp; + Array arg_arr; + for (int i = 0; i < p_argc; i++) { + godot_js_wrapper_ex exchange; + exchange.i = i; + int type = godot_js_wrapper_object_getvar(p_args_id, Variant::INT, &exchange); + arg_arr.push_back(_js2variant(type, &exchange)); + } + Variant arg = arg_arr; + const Variant *argv[1] = { &arg }; + Callable::CallError err; + Variant ret; + obj->_callable.call(argv, 1, ret, err); +} + +Ref<JavaScriptObject> JavaScript::create_callback(const Callable &p_callable) { + Ref<JavaScriptObjectImpl> out = memnew(JavaScriptObjectImpl); + out->_callable = p_callable; + out->_js_id = godot_js_wrapper_create_cb(out.ptr(), JavaScriptObjectImpl::_callback); + return out; +} + +Ref<JavaScriptObject> JavaScript::get_interface(const String &p_interface) { + int js_id = godot_js_wrapper_interface_get(p_interface.utf8().get_data()); + ERR_FAIL_COND_V_MSG(!js_id, Ref<JavaScriptObject>(), "No interface '" + p_interface + "' registered."); + return Ref<JavaScriptObject>(memnew(JavaScriptObjectImpl(js_id))); +} + +Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Ref<JavaScriptObject>(); + } + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + return Ref<JavaScriptObject>(); + } + godot_js_wrapper_ex exchange; + const String object = *p_args[0]; + void *lock = nullptr; + const Variant **args = p_argcount > 1 ? &p_args[1] : nullptr; + const int type = godot_js_wrapper_create_object(object.utf8().get_data(), (void **)args, p_argcount - 1, &JavaScriptObjectImpl::_variant2js, &exchange, &lock, &JavaScriptObjectImpl::_free_lock); + r_error.error = Callable::CallError::CALL_OK; + if (type < 0) { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return Ref<JavaScriptObject>(); + } + return JavaScriptObjectImpl::_js2variant(type, &exchange); +} + +extern "C" { +union js_eval_ret { + uint32_t b; + double d; + char *s; +}; + +extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len)); +extern int godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, const char *p_name, const char *p_mime); +} + +void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) { + PackedByteArray *arr = (PackedByteArray *)p_arr; + VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write; + arr->resize(p_len); + *write = arr->write; + return arr->ptrw(); +} + +Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { + union js_eval_ret js_data; + PackedByteArray arr; + VectorWriteProxy<uint8_t> arr_write; + + Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write)); + + switch (return_type) { + case Variant::BOOL: + return js_data.b; + case Variant::FLOAT: + return js_data.d; + case Variant::STRING: { + String str = String::utf8(js_data.s); + free(js_data.s); // Must free the string allocated in JS. + return str; + } + case Variant::PACKED_BYTE_ARRAY: + arr_write = VectorWriteProxy<uint8_t>(); + return arr; + default: + return Variant(); + } +} +#endif // JAVASCRIPT_EVAL_ENABLED + +void JavaScript::download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime) { + godot_js_os_download_buffer(p_arr.ptr(), p_arr.size(), p_name.utf8().get_data(), p_mime.utf8().get_data()); +} diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index ac4055516c..45c3a3fe2e 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -59,7 +59,7 @@ const GodotAudio = { } onstatechange(state); }; - ctx.onstatechange(); // Immeditately notify state. + ctx.onstatechange(); // Immediately notify state. // Update computed latency GodotAudio.interval = setInterval(function () { let computed_latency = 0; diff --git a/platform/javascript/js/libs/library_godot_editor_tools.js b/platform/javascript/js/libs/library_godot_editor_tools.js deleted file mode 100644 index d7f1ad5ea1..0000000000 --- a/platform/javascript/js/libs/library_godot_editor_tools.js +++ /dev/null @@ -1,57 +0,0 @@ -/*************************************************************************/ -/* library_godot_editor_tools.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 GodotEditorTools = { - godot_js_editor_download_file__deps: ['$FS'], - godot_js_editor_download_file__sig: 'viii', - godot_js_editor_download_file: function (p_path, p_name, p_mime) { - const path = GodotRuntime.parseString(p_path); - const name = GodotRuntime.parseString(p_name); - const mime = GodotRuntime.parseString(p_mime); - const size = FS.stat(path)['size']; - const buf = new Uint8Array(size); - const fd = FS.open(path, 'r'); - FS.read(fd, buf, 0, size); - FS.close(fd); - FS.unlink(path); - const blob = new Blob([buf], { type: mime }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = name; - a.style.display = 'none'; - document.body.appendChild(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - }, -}; - -mergeInto(LibraryManager.library, GodotEditorTools); diff --git a/platform/javascript/js/libs/library_godot_eval.js b/platform/javascript/js/libs/library_godot_eval.js deleted file mode 100644 index 9ab392b813..0000000000 --- a/platform/javascript/js/libs/library_godot_eval.js +++ /dev/null @@ -1,86 +0,0 @@ -/*************************************************************************/ -/* library_godot_eval.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 GodotEval = { - godot_js_eval__deps: ['$GodotRuntime'], - godot_js_eval__sig: 'iiiiiii', - godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { - const js_code = GodotRuntime.parseString(p_js); - let eval_ret = null; - try { - if (p_use_global_ctx) { - // indirect eval call grants global execution context - const global_eval = eval; // eslint-disable-line no-eval - eval_ret = global_eval(js_code); - } else { - eval_ret = eval(js_code); // eslint-disable-line no-eval - } - } catch (e) { - GodotRuntime.error(e); - } - - switch (typeof eval_ret) { - case 'boolean': - GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32'); - return 1; // BOOL - - case 'number': - GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double'); - return 3; // REAL - - case 'string': - GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*'); - return 4; // STRING - - case 'object': - if (eval_ret === null) { - break; - } - - if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { - eval_ret = new Uint8Array(eval_ret.buffer); - } else if (eval_ret instanceof ArrayBuffer) { - eval_ret = new Uint8Array(eval_ret); - } - if (eval_ret instanceof Uint8Array) { - const func = GodotRuntime.get_func(p_callback); - const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length); - HEAPU8.set(eval_ret, bytes_ptr); - return 20; // POOL_BYTE_ARRAY - } - break; - - // no default - } - return 0; // NIL - }, -}; - -mergeInto(LibraryManager.library, GodotEval); diff --git a/platform/javascript/js/libs/library_godot_javascript_singleton.js b/platform/javascript/js/libs/library_godot_javascript_singleton.js new file mode 100644 index 0000000000..09ef4a1a5d --- /dev/null +++ b/platform/javascript/js/libs/library_godot_javascript_singleton.js @@ -0,0 +1,333 @@ +/*************************************************************************/ +/* library_godot_eval.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 GodotJSWrapper = { + + $GodotJSWrapper__deps: ['$GodotRuntime', '$IDHandler'], + $GodotJSWrapper__postset: 'GodotJSWrapper.proxies = new Map();', + $GodotJSWrapper: { + proxies: null, + + MyProxy: function (val) { + const id = IDHandler.add(this); + GodotJSWrapper.proxies.set(val, id); + let refs = 1; + this.ref = function () { + refs++; + }; + this.unref = function () { + refs--; + if (refs === 0) { + IDHandler.remove(id); + GodotJSWrapper.proxies.delete(val); + } + }; + this.get_val = function () { + return val; + }; + this.get_id = function () { + return id; + }; + }, + + get_proxied: function (val) { + const id = GodotJSWrapper.proxies.get(val); + if (id === undefined) { + const proxy = new GodotJSWrapper.MyProxy(val); + return proxy.get_id(); + } + IDHandler.get(id).ref(); + return id; + }, + + get_proxied_value: function (id) { + const proxy = IDHandler.get(id); + if (proxy === undefined) { + return undefined; + } + return proxy.get_val(); + }, + + variant2js: function (type, val) { + switch (type) { + case 0: + return null; + case 1: + return !!GodotRuntime.getHeapValue(val, 'i64'); + case 2: + return GodotRuntime.getHeapValue(val, 'i64'); + case 3: + return GodotRuntime.getHeapValue(val, 'double'); + case 4: + return GodotRuntime.parseString(GodotRuntime.getHeapValue(val, '*')); + case 21: // OBJECT + return GodotJSWrapper.get_proxied_value(GodotRuntime.getHeapValue(val, 'i64')); + default: + return undefined; + } + }, + + js2variant: function (p_val, p_exchange) { + if (p_val === undefined || p_val === null) { + return 0; // NIL + } + const type = typeof (p_val); + if (type === 'boolean') { + GodotRuntime.setHeapValue(p_exchange, p_val, 'i64'); + return 1; // BOOL + } else if (type === 'number') { + if (Number.isInteger(p_val)) { + GodotRuntime.setHeapValue(p_exchange, p_val, 'i64'); + return 2; // INT + } + GodotRuntime.setHeapValue(p_exchange, p_val, 'double'); + return 3; // REAL + } else if (type === 'string') { + const c_str = GodotRuntime.allocString(p_val); + GodotRuntime.setHeapValue(p_exchange, c_str, '*'); + return 4; // STRING + } + const id = GodotJSWrapper.get_proxied(p_val); + GodotRuntime.setHeapValue(p_exchange, id, 'i64'); + return 21; + }, + }, + + godot_js_wrapper_interface_get__sig: 'ii', + godot_js_wrapper_interface_get: function (p_name) { + const name = GodotRuntime.parseString(p_name); + if (typeof (window[name]) !== 'undefined') { + return GodotJSWrapper.get_proxied(window[name]); + } + return 0; + }, + + godot_js_wrapper_object_get__sig: 'iiii', + godot_js_wrapper_object_get: function (p_id, p_exchange, p_prop) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return 0; + } + if (p_prop) { + const prop = GodotRuntime.parseString(p_prop); + try { + return GodotJSWrapper.js2variant(obj[prop], p_exchange); + } catch (e) { + GodotRuntime.error(`Error getting variable ${prop} on object`, obj); + return 0; // NIL + } + } + return GodotJSWrapper.js2variant(obj, p_exchange); + }, + + godot_js_wrapper_object_set__sig: 'viiii', + godot_js_wrapper_object_set: function (p_id, p_name, p_type, p_exchange) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return; + } + const name = GodotRuntime.parseString(p_name); + try { + obj[name] = GodotJSWrapper.variant2js(p_type, p_exchange); + } catch (e) { + GodotRuntime.error(`Error setting variable ${name} on object`, obj); + } + }, + + godot_js_wrapper_object_call__sig: 'iiiiiiiii', + godot_js_wrapper_object_call: function (p_id, p_method, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const method = GodotRuntime.parseString(p_method); + const convert = GodotRuntime.get_func(p_convert_callback); + const freeLock = GodotRuntime.get_func(p_free_lock_callback); + const args = new Array(p_argc); + for (let i = 0; i < p_argc; i++) { + const type = convert(p_args, i, p_exchange, p_lock); + const lock = GodotRuntime.getHeapValue(p_lock, '*'); + args[i] = GodotJSWrapper.variant2js(type, p_exchange); + if (lock) { + freeLock(p_lock, type); + } + } + try { + const res = obj[method](...args); + return GodotJSWrapper.js2variant(res, p_exchange); + } catch (e) { + GodotRuntime.error(`Error calling method ${method} on:`, obj, 'error:', e); + return -1; + } + }, + + godot_js_wrapper_object_unref__sig: 'vi', + godot_js_wrapper_object_unref: function (p_id) { + const proxy = IDHandler.get(p_id); + if (proxy !== undefined) { + proxy.unref(); + } + }, + + godot_js_wrapper_create_cb__sig: 'vii', + godot_js_wrapper_create_cb: function (p_ref, p_func) { + const func = GodotRuntime.get_func(p_func); + let id = 0; + const cb = function () { + if (!GodotJSWrapper.get_proxied_value(id)) { + return; + } + const args = Array.from(arguments); + func(p_ref, GodotJSWrapper.get_proxied(args), args.length); + }; + id = GodotJSWrapper.get_proxied(cb); + return id; + }, + + godot_js_wrapper_object_getvar__sig: 'iiii', + godot_js_wrapper_object_getvar: function (p_id, p_type, p_exchange) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const prop = GodotJSWrapper.variant2js(p_type, p_exchange); + if (prop === undefined || prop === null) { + return -1; + } + try { + return GodotJSWrapper.js2variant(obj[prop], p_exchange); + } catch (e) { + GodotRuntime.error(`Error getting variable ${prop} on object`, obj, e); + return -1; + } + }, + + godot_js_wrapper_object_setvar__sig: 'iiiiii', + godot_js_wrapper_object_setvar: function (p_id, p_key_type, p_key_ex, p_val_type, p_val_ex) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const key = GodotJSWrapper.variant2js(p_key_type, p_key_ex); + try { + obj[key] = GodotJSWrapper.variant2js(p_val_type, p_val_ex); + return 0; + } catch (e) { + GodotRuntime.error(`Error setting variable ${key} on object`, obj); + return -1; + } + }, + + godot_js_wrapper_create_object__sig: 'iiiiiiii', + godot_js_wrapper_create_object: function (p_object, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) { + const name = GodotRuntime.parseString(p_object); + if (typeof (window[name]) === 'undefined') { + return -1; + } + const convert = GodotRuntime.get_func(p_convert_callback); + const freeLock = GodotRuntime.get_func(p_free_lock_callback); + const args = new Array(p_argc); + for (let i = 0; i < p_argc; i++) { + const type = convert(p_args, i, p_exchange, p_lock); + const lock = GodotRuntime.getHeapValue(p_lock, '*'); + args[i] = GodotJSWrapper.variant2js(type, p_exchange); + if (lock) { + freeLock(p_lock, type); + } + } + try { + const res = new window[name](...args); + return GodotJSWrapper.js2variant(res, p_exchange); + } catch (e) { + GodotRuntime.error(`Error calling constructor ${name} with args:`, args, 'error:', e); + return -1; + } + }, +}; + +autoAddDeps(GodotJSWrapper, '$GodotJSWrapper'); +mergeInto(LibraryManager.library, GodotJSWrapper); + +const GodotEval = { + godot_js_eval__deps: ['$GodotRuntime'], + godot_js_eval__sig: 'iiiiiii', + godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { + const js_code = GodotRuntime.parseString(p_js); + let eval_ret = null; + try { + if (p_use_global_ctx) { + // indirect eval call grants global execution context + const global_eval = eval; // eslint-disable-line no-eval + eval_ret = global_eval(js_code); + } else { + eval_ret = eval(js_code); // eslint-disable-line no-eval + } + } catch (e) { + GodotRuntime.error(e); + } + + switch (typeof eval_ret) { + case 'boolean': + GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32'); + return 1; // BOOL + + case 'number': + GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double'); + return 3; // REAL + + case 'string': + GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*'); + return 4; // STRING + + case 'object': + if (eval_ret === null) { + break; + } + + if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { + eval_ret = new Uint8Array(eval_ret.buffer); + } else if (eval_ret instanceof ArrayBuffer) { + eval_ret = new Uint8Array(eval_ret); + } + if (eval_ret instanceof Uint8Array) { + const func = GodotRuntime.get_func(p_callback); + const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length); + HEAPU8.set(eval_ret, bytes_ptr); + return 20; // POOL_BYTE_ARRAY + } + break; + + // no default + } + return 0; // NIL + }, +}; + +mergeInto(LibraryManager.library, GodotEval); diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 1d9f889bce..7414b8cc47 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -304,6 +304,23 @@ const GodotOS = { godot_js_os_hw_concurrency_get: function () { return navigator.hardwareConcurrency || 1; }, + + godot_js_os_download_buffer__sig: 'viiii', + godot_js_os_download_buffer: function (p_ptr, p_size, p_name, p_mime) { + const buf = GodotRuntime.heapSlice(HEAP8, p_ptr, p_size); + const name = GodotRuntime.parseString(p_name); + const mime = GodotRuntime.parseString(p_mime); + const blob = new Blob([buf], { type: mime }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = name; + a.style.display = 'none'; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + }, }; autoAddDeps(GodotOS, '$GodotOS'); diff --git a/platform/javascript/package-lock.json b/platform/javascript/package-lock.json index b8c434b3dd..8bf5c52ff6 100644 --- a/platform/javascript/package-lock.json +++ b/platform/javascript/package-lock.json @@ -44,9 +44,9 @@ } }, "@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz", + "integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==", "dev": true }, "@eslint/eslintrc": { @@ -812,9 +812,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "ignore": { @@ -962,9 +962,8 @@ } }, "jsdoc": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.6.tgz", - "integrity": "sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ==", + "version": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560", + "from": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560", "dev": true, "requires": { "@babel/parser": "^7.9.4", @@ -975,12 +974,12 @@ "klaw": "^3.0.0", "markdown-it": "^10.0.0", "markdown-it-anchor": "^5.2.7", - "marked": "^0.8.2", + "marked": "^2.0.3", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", "taffydb": "2.6.2", - "underscore": "~1.10.2" + "underscore": "~1.12.1" }, "dependencies": { "escape-string-regexp": { @@ -1069,9 +1068,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "markdown-it": { @@ -1094,9 +1093,9 @@ "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==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.3.tgz", + "integrity": "sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA==", "dev": true }, "mdurl": { @@ -1689,9 +1688,9 @@ "dev": true }, "underscore": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", - "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", "dev": true }, "uri-js": { diff --git a/platform/javascript/package.json b/platform/javascript/package.json index d9d272923e..53748503f9 100644 --- a/platform/javascript/package.json +++ b/platform/javascript/package.json @@ -23,6 +23,6 @@ "eslint": "^7.9.0", "eslint-config-airbnb-base": "^14.2.0", "eslint-plugin-import": "^2.22.0", - "jsdoc": "^3.6.6" + "jsdoc": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560" } } diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 6b527c6fb5..ee59537ee0 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -67,10 +67,10 @@ def get_opts(): 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_msan", "Use LLVM/GCC compiler memory sanitizer (MSAN))", 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", True), BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), @@ -147,11 +147,23 @@ 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"]) + 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"]: @@ -162,8 +174,10 @@ def configure(env): env.Append(CCFLAGS=["-fsanitize=thread"]) env.Append(LINKFLAGS=["-fsanitize=thread"]) - if env["use_msan"]: + 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"]: @@ -311,6 +325,10 @@ 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 @@ -345,6 +363,7 @@ def configure(env): env.Prepend(CPPPATH=["#platform/linuxbsd"]) env.Append(CPPDEFINES=["X11_ENABLED", "UNIX_ENABLED"]) + env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)]) env.Append(CPPDEFINES=["VULKAN_ENABLED"]) if not env["builtin_vulkan"]: @@ -394,10 +413,7 @@ def configure(env): # Link those statically for portability if env["use_static_cpp"]: - # Workaround for GH-31743, Ubuntu 18.04 i386 crashes when it's used. - # That doesn't make any sense but it's likely a Ubuntu bug? - if is64 or env["bits"] == "64": - env.Append(LINKFLAGS=["-static-libgcc", "-static-libstdc++"]) + env.Append(LINKFLAGS=["-static-libgcc", "-static-libstdc++"]) if env["use_llvm"]: env["LINKCOM"] = env["LINKCOM"] + " -l:libatomic.a" diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 8dc1b41702..b50b5f3479 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -2166,10 +2166,10 @@ static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p } void DisplayServerX11::_get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state) { - state->set_shift((p_x11_state & ShiftMask)); - state->set_control((p_x11_state & ControlMask)); - state->set_alt((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt - state->set_metakey((p_x11_state & Mod4Mask)); + state->set_shift_pressed((p_x11_state & ShiftMask)); + state->set_ctrl_pressed((p_x11_state & ControlMask)); + state->set_alt_pressed((p_x11_state & Mod1Mask /*|| p_x11_state&Mod5Mask*/)); //altgr should not count as alt + state->set_meta_pressed((p_x11_state & Mod4Mask)); } unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) { @@ -2281,7 +2281,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, //make it consistent across platforms. k->set_keycode(KEY_TAB); k->set_physical_keycode(KEY_TAB); - k->set_shift(true); + k->set_shift_pressed(true); } Input::get_singleton()->accumulate_input_event(k); @@ -2409,20 +2409,20 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, //make it consistent across platforms. k->set_keycode(KEY_TAB); k->set_physical_keycode(KEY_TAB); - k->set_shift(true); + k->set_shift_pressed(true); } //don't set mod state if modifier keys are released by themselves //else event.is_action() will not work correctly here if (!k->is_pressed()) { if (k->get_keycode() == KEY_SHIFT) { - k->set_shift(false); - } else if (k->get_keycode() == KEY_CONTROL) { - k->set_control(false); + k->set_shift_pressed(false); + } else if (k->get_keycode() == KEY_CTRL) { + k->set_ctrl_pressed(false); } else if (k->get_keycode() == KEY_ALT) { - k->set_alt(false); + k->set_alt_pressed(false); } else if (k->get_keycode() == KEY_META) { - k->set_metakey(false); + k->set_meta_pressed(false); } } @@ -2606,7 +2606,6 @@ void DisplayServerX11::_window_changed(XEvent *event) { } #endif - print_line("DisplayServer::_window_changed: " + itos(window_id) + " rect: " + new_rect); if (!wd.rect_changed_callback.is_null()) { Rect2i r = new_rect; @@ -3162,14 +3161,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); } @@ -3833,8 +3832,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u wd.position.y = xwa.y; wd.size.width = xwa.width; wd.size.height = xwa.height; - - print_line("DisplayServer::_create_window " + itos(id) + " want rect: " + p_rect + " got rect " + Rect2i(xwa.x, xwa.y, xwa.width, xwa.height)); } //set cursor diff --git a/platform/linuxbsd/key_mapping_x11.cpp b/platform/linuxbsd/key_mapping_x11.cpp index f9f612fa74..74257a7e61 100644 --- a/platform/linuxbsd/key_mapping_x11.cpp +++ b/platform/linuxbsd/key_mapping_x11.cpp @@ -61,8 +61,8 @@ static _XTranslatePair _xkeysym_to_keycode[] = { { XK_Shift_L, KEY_SHIFT }, { XK_Shift_R, KEY_SHIFT }, { XK_Shift_Lock, KEY_SHIFT }, - { XK_Control_L, KEY_CONTROL }, - { XK_Control_R, KEY_CONTROL }, + { XK_Control_L, KEY_CTRL }, + { XK_Control_R, KEY_CTRL }, { XK_Meta_L, KEY_META }, { XK_Meta_R, KEY_META }, { XK_Alt_L, KEY_ALT }, @@ -213,7 +213,7 @@ static _TranslatePair _scancode_to_keycode[] = { { KEY_BRACELEFT, 0x22 }, { KEY_BRACERIGHT, 0x23 }, { KEY_ENTER, 0x24 }, - { KEY_CONTROL, 0x25 }, + { KEY_CTRL, 0x25 }, { KEY_A, 0x26 }, { KEY_S, 0x27 }, { KEY_D, 0x28 }, @@ -272,7 +272,7 @@ static _TranslatePair _scancode_to_keycode[] = { { KEY_F11, 0x5F }, { KEY_F12, 0x60 }, { KEY_KP_ENTER, 0x68 }, - { KEY_CONTROL, 0x69 }, + { KEY_CTRL, 0x69 }, { KEY_KP_DIVIDE, 0x6A }, { KEY_PRINT, 0x6B }, { KEY_ALT, 0x6C }, diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 09e1f9461c..23e448fbd7 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -116,6 +116,8 @@ String OS_LinuxBSD::get_name() const { return "FreeBSD"; #elif defined(__NetBSD__) return "NetBSD"; +#elif defined(__OpenBSD__) + return "OpenBSD"; #else return "BSD"; #endif @@ -164,7 +166,12 @@ bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) { String OS_LinuxBSD::get_config_path() const { if (has_environment("XDG_CONFIG_HOME")) { - return get_environment("XDG_CONFIG_HOME"); + if (get_environment("XDG_CONFIG_HOME").is_abs_path()) { + return get_environment("XDG_CONFIG_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.config` or `.` per the XDG Base Directory specification."); + return has_environment("HOME") ? get_environment("HOME").plus_file(".config") : "."; + } } else if (has_environment("HOME")) { return get_environment("HOME").plus_file(".config"); } else { @@ -174,7 +181,12 @@ String OS_LinuxBSD::get_config_path() const { String OS_LinuxBSD::get_data_path() const { if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); + if (get_environment("XDG_DATA_HOME").is_abs_path()) { + return get_environment("XDG_DATA_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.local/share` or `get_config_path()` per the XDG Base Directory specification."); + return has_environment("HOME") ? get_environment("HOME").plus_file(".local/share") : get_config_path(); + } } else if (has_environment("HOME")) { return get_environment("HOME").plus_file(".local/share"); } else { @@ -184,7 +196,12 @@ String OS_LinuxBSD::get_data_path() const { String OS_LinuxBSD::get_cache_path() const { if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); + if (get_environment("XDG_CACHE_HOME").is_abs_path()) { + return get_environment("XDG_CACHE_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.cache` or `get_config_path()` per the XDG Base Directory specification."); + return has_environment("HOME") ? get_environment("HOME").plus_file(".cache") : get_config_path(); + } } else if (has_environment("HOME")) { return get_environment("HOME").plus_file(".cache"); } else { diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 147ce26779..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); @@ -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 5b320da82f..317e79d0ea 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -34,9 +34,8 @@ def get_opts(): 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_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_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), ] @@ -132,21 +131,22 @@ def configure(env): env["AS"] = basecmd + "as" env.Append(CPPDEFINES=["__MACPORTS__"]) # hack to fix libvpx MM256_BROADCASTSI128_SI256 define - 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_tsan"]: 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_lsan"]: - env.Append(CCFLAGS=["-fsanitize=leak"]) - env.Append(LINKFLAGS=["-fsanitize=leak"]) - if env["use_tsan"]: env.Append(CCFLAGS=["-fsanitize=thread"]) env.Append(LINKFLAGS=["-fsanitize=thread"]) diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 6b838b6d14..4c761e77b5 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -66,10 +66,10 @@ static bool ignore_momentum_scroll = false; static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) { - r_state->set_shift((p_osx_state & NSEventModifierFlagShift)); - r_state->set_control((p_osx_state & NSEventModifierFlagControl)); - r_state->set_alt((p_osx_state & NSEventModifierFlagOption)); - r_state->set_metakey((p_osx_state & NSEventModifierFlagCommand)); + r_state->set_shift_pressed((p_osx_state & NSEventModifierFlagShift)); + r_state->set_ctrl_pressed((p_osx_state & NSEventModifierFlagControl)); + r_state->set_alt_pressed((p_osx_state & NSEventModifierFlagOption)); + r_state->set_meta_pressed((p_osx_state & NSEventModifierFlagCommand)); } static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow) { @@ -368,6 +368,10 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { if (wd.resize_disabled) { [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; } + + if (wd.on_top) { + [wd.window_object setLevel:NSFloatingWindowLevel]; + } } - (void)windowDidChangeBackingProperties:(NSNotification *)notification { @@ -830,7 +834,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i mb->set_global_position(pos); mb->set_button_mask(DS_OSX->last_button_state); if (index == MOUSE_BUTTON_LEFT && pressed) { - mb->set_doubleclick([event clickCount] == 2); + mb->set_double_click([event clickCount] == 2); } Input::get_singleton()->accumulate_input_event(mb); @@ -1133,10 +1137,10 @@ static int translateKey(unsigned int key) { /* 38 */ KEY_SHIFT, /* 39 */ KEY_CAPSLOCK, /* 3a */ KEY_ALT, - /* 3b */ KEY_CONTROL, + /* 3b */ KEY_CTRL, /* 3c */ KEY_SHIFT, /* 3d */ KEY_ALT, - /* 3e */ KEY_CONTROL, + /* 3e */ KEY_CTRL, /* 3f */ KEY_UNKNOWN, /* Function */ /* 40 */ KEY_UNKNOWN, /* F17 */ /* 41 */ KEY_KP_PERIOD, @@ -1640,7 +1644,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]; @@ -1655,13 +1659,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]; @@ -1678,7 +1682,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; } @@ -2437,7 +2441,7 @@ void DisplayServerOSX::_update_window(WindowData p_wd) { [p_wd.window_object setHidesOnDeactivate:YES]; } else { // Reset these when our window is not a borderless window that covers up the screen - if (p_wd.on_top) { + if (p_wd.on_top && !p_wd.fullscreen) { [p_wd.window_object setLevel:NSFloatingWindowLevel]; } else { [p_wd.window_object setLevel:NSNormalWindowLevel]; @@ -2786,6 +2790,7 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { [wd.window_object deminiaturize:nil]; } break; case WINDOW_MODE_FULLSCREEN: { + [wd.window_object setLevel:NSNormalWindowLevel]; if (wd.layered_window) { _set_window_per_pixel_transparency_enabled(true, p_window); } @@ -2903,6 +2908,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { wd.on_top = p_enabled; + if (wd.fullscreen) { + return; + } if (p_enabled) { [wd.window_object setLevel:NSFloatingWindowLevel]; } else { @@ -2940,7 +2948,11 @@ bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) co return [wd.window_object styleMask] == NSWindowStyleMaskBorderless; } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { - return [wd.window_object level] == NSFloatingWindowLevel; + if (wd.fullscreen) { + return wd.on_top; + } else { + return [wd.window_object level] == NSFloatingWindowLevel; + } } break; case WINDOW_FLAG_TRANSPARENT: { return wd.layered_window; @@ -3029,7 +3041,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) { @@ -3202,9 +3214,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; @@ -3467,7 +3479,7 @@ void DisplayServerOSX::set_native_icon(const String &p_filename) { ERR_FAIL_COND(!f); Vector<uint8_t> data; - uint32_t len = f->get_len(); + uint64_t len = f->get_len(); data.resize(len); f->get_buffer((uint8_t *)&data.write[0], len); memdelete(f); @@ -3759,12 +3771,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 @@ -3854,7 +3866,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"); } @@ -3926,8 +3938,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 aca9471849..a7868efaa8 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -215,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; } @@ -241,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; } @@ -249,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; } @@ -259,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) { @@ -312,13 +312,13 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ } int ofs = data.size(); - uint32_t len = f->get_len(); + uint64_t len = f->get_len(); data.resize(data.size() + len + 8); f->get_buffer(&data.write[ofs + 8], len); 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. @@ -338,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]); } @@ -353,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]); } } diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 7b5daf5bfb..9e3f0350e9 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 { @@ -188,31 +188,45 @@ MainLoop *OS_OSX::get_main_loop() const { } String OS_OSX::get_config_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. if (has_environment("XDG_CONFIG_HOME")) { - return get_environment("XDG_CONFIG_HOME"); - } else if (has_environment("HOME")) { + if (get_environment("XDG_CONFIG_HOME").is_abs_path()) { + return get_environment("XDG_CONFIG_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/Library/Application Support` or `.` per the XDG Base Directory specification."); + } + } + if (has_environment("HOME")) { return get_environment("HOME").plus_file("Library/Application Support"); - } else { - return "."; } + return "."; } String OS_OSX::get_data_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); - } else { - return get_config_path(); + if (get_environment("XDG_DATA_HOME").is_abs_path()) { + return get_environment("XDG_DATA_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `get_config_path()` per the XDG Base Directory specification."); + } } + return get_config_path(); } String OS_OSX::get_cache_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on macOS as well. if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); - } else if (has_environment("HOME")) { + if (get_environment("XDG_CACHE_HOME").is_abs_path()) { + return get_environment("XDG_CACHE_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/Libary/Caches` or `get_config_path()` per the XDG Base Directory specification."); + } + } + if (has_environment("HOME")) { return get_environment("HOME").plus_file("Library/Caches"); - } else { - return get_config_path(); } + return get_config_path(); } String OS_OSX::get_bundle_resource_dir() const { @@ -346,7 +360,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 16ddbe1768..478bcad212 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -35,11 +35,11 @@ def get_opts(): 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("debug_symbols", "Add debugging symbols to release/release_debug builds", True), - BoolVariable("use_msan", "Use LLVM/GCC compiler memory sanitizer (MSAN))", False), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False), ] @@ -104,11 +104,23 @@ 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"]) + 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"]: @@ -119,8 +131,10 @@ def configure(env): env.Append(CCFLAGS=["-fsanitize=thread"]) env.Append(LINKFLAGS=["-fsanitize=thread"]) - if env["use_msan"]: + 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"]: diff --git a/platform/uwp/SCsub b/platform/uwp/SCsub index 4358b0eead..71c402358f 100644 --- a/platform/uwp/SCsub +++ b/platform/uwp/SCsub @@ -3,7 +3,6 @@ Import("env") files = [ - "thread_uwp.cpp", "#platform/windows/key_mapping_windows.cpp", "#platform/windows/windows_terminal_logger.cpp", "joypad_uwp.cpp", diff --git a/platform/uwp/app.cpp b/platform/uwp/app.cpp index b7e4361831..9d6ff10483 100644 --- a/platform/uwp/app.cpp +++ b/platform/uwp/app.cpp @@ -383,7 +383,7 @@ void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Wind ke.type = OS_UWP::KeyEvent::MessageType::KEY_EVENT_MESSAGE; ke.unicode = 0; ke.keycode = KeyMappingWindows::get_keysym((unsigned int)key_args->VirtualKey); - ke.physical_keycode = KeyMappingWindows::get_scansym((unsigned int)key_args->KeyStatus.ScanCode); + ke.physical_keycode = KeyMappingWindows::get_scansym((unsigned int)key_args->KeyStatus.ScanCode, key_args->KeyStatus.IsExtendedKey); ke.echo = (!p_pressed && !key_args->KeyStatus.IsKeyReleased) || (p_pressed && key_args->KeyStatus.WasKeyDown); } else { diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 2a0bc78440..217c119978 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -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]); } diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index 33992069f9..435f829c9b 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -147,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; } @@ -565,9 +565,9 @@ void OS_UWP::process_key_events() { Ref<InputEventKey> key_event; key_event.instance(); - key_event->set_alt(kev.alt); - key_event->set_shift(kev.shift); - key_event->set_control(kev.control); + key_event->set_alt_pressed(kev.alt); + key_event->set_shift_pressed(kev.shift); + key_event->set_ctrl_pressed(kev.control); key_event->set_echo(kev.echo); key_event->set_keycode(kev.keycode); key_event->set_physical_keycode(kev.physical_keycode); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 86f20f1dd7..0dd02b4888 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); @@ -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, ""); @@ -1966,9 +1966,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm.instance(); mm->set_window_id(window_id); - mm->set_control(control_mem); - mm->set_shift(shift_mem); - mm->set_alt(alt_mem); + mm->set_ctrl_pressed(control_mem); + mm->set_shift_pressed(shift_mem); + mm->set_alt_pressed(alt_mem); mm->set_pressure((raw->data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN) ? 1.0f : 0.0f); @@ -2062,9 +2062,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Ref<InputEventMouseMotion> mm; mm.instance(); mm->set_window_id(window_id); - mm->set_control(GetKeyState(VK_CONTROL) < 0); - mm->set_shift(GetKeyState(VK_SHIFT) < 0); - mm->set_alt(alt_mem); + mm->set_ctrl_pressed(GetKeyState(VK_CONTROL) < 0); + mm->set_shift_pressed(GetKeyState(VK_SHIFT) < 0); + mm->set_alt_pressed(alt_mem); mm->set_pressure(windows[window_id].last_pressure); mm->set_tilt(windows[window_id].last_tilt); @@ -2205,9 +2205,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_tilt(Vector2((float)pen_info.tiltX / 90, (float)pen_info.tiltY / 90)); } - mm->set_control(GetKeyState(VK_CONTROL) < 0); - mm->set_shift(GetKeyState(VK_SHIFT) < 0); - mm->set_alt(alt_mem); + mm->set_ctrl_pressed(GetKeyState(VK_CONTROL) < 0); + mm->set_shift_pressed(GetKeyState(VK_SHIFT) < 0); + mm->set_alt_pressed(alt_mem); mm->set_button_mask(last_button_state); @@ -2300,9 +2300,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA Ref<InputEventMouseMotion> mm; mm.instance(); mm->set_window_id(window_id); - mm->set_control((wParam & MK_CONTROL) != 0); - mm->set_shift((wParam & MK_SHIFT) != 0); - mm->set_alt(alt_mem); + mm->set_ctrl_pressed((wParam & MK_CONTROL) != 0); + mm->set_shift_pressed((wParam & MK_SHIFT) != 0); + mm->set_alt_pressed(alt_mem); 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. @@ -2412,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); @@ -2470,17 +2470,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_button_index(MOUSE_BUTTON_XBUTTON1); else mb->set_button_index(MOUSE_BUTTON_XBUTTON2); - mb->set_doubleclick(true); + mb->set_double_click(true); } break; default: { return 0; } } - mb->set_control((wParam & MK_CONTROL) != 0); - mb->set_shift((wParam & MK_SHIFT) != 0); - mb->set_alt(alt_mem); - //mb->get_alt()=(wParam&MK_MENU)!=0; + mb->set_ctrl_pressed((wParam & MK_CONTROL) != 0); + mb->set_shift_pressed((wParam & MK_SHIFT) != 0); + mb->set_alt_pressed(alt_mem); + //mb->is_alt_pressed()=(wParam&MK_MENU)!=0; if (mb->is_pressed()) last_button_state |= (1 << (mb->get_button_index() - 1)); else @@ -2835,17 +2835,17 @@ void DisplayServerWindows::_process_key_events() { k.instance(); k->set_window_id(ke.window_id); - k->set_shift(ke.shift); - k->set_alt(ke.alt); - k->set_control(ke.control); - k->set_metakey(ke.meta); + k->set_shift_pressed(ke.shift); + k->set_alt_pressed(ke.alt); + k->set_ctrl_pressed(ke.control); + k->set_meta_pressed(ke.meta); k->set_pressed(true); k->set_keycode(KeyMappingWindows::get_keysym(ke.wParam)); k->set_physical_keycode(KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24))); k->set_unicode(unicode); if (k->get_unicode() && gr_mem) { - k->set_alt(false); - k->set_control(false); + k->set_alt_pressed(false); + k->set_ctrl_pressed(false); } if (k->get_unicode() < 32) @@ -2862,10 +2862,10 @@ void DisplayServerWindows::_process_key_events() { k.instance(); k->set_window_id(ke.window_id); - k->set_shift(ke.shift); - k->set_alt(ke.alt); - k->set_control(ke.control); - k->set_metakey(ke.meta); + k->set_shift_pressed(ke.shift); + k->set_alt_pressed(ke.alt); + k->set_ctrl_pressed(ke.control); + k->set_meta_pressed(ke.meta); k->set_pressed(ke.uMsg == WM_KEYDOWN); @@ -2900,8 +2900,8 @@ void DisplayServerWindows::_process_key_events() { k->set_unicode(unicode); } if (k->get_unicode() && gr_mem) { - k->set_alt(false); - k->set_control(false); + k->set_alt_pressed(false); + k->set_ctrl_pressed(false); } if (k->get_unicode() < 32) diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp index 3312c91932..c367c69826 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -47,7 +47,7 @@ static _WinTranslatePair _vk_to_keycode[] = { { KEY_SHIFT, VK_SHIFT }, //(0x10) - { KEY_CONTROL, VK_CONTROL }, //(0x11) + { KEY_CTRL, VK_CONTROL }, //(0x11) { KEY_ALT, VK_MENU }, //(0x12) @@ -166,8 +166,8 @@ static _WinTranslatePair _vk_to_keycode[] = { { KEY_SCROLLLOCK, VK_SCROLL }, // (0x91) { KEY_SHIFT, VK_LSHIFT }, // (0xA0) { KEY_SHIFT, VK_RSHIFT }, // (0xA1) - { KEY_CONTROL, VK_LCONTROL }, // (0xA2) - { KEY_CONTROL, VK_RCONTROL }, // (0xA3) + { KEY_CTRL, VK_LCONTROL }, // (0xA2) + { KEY_CTRL, VK_RCONTROL }, // (0xA3) { KEY_MENU, VK_LMENU }, // (0xA4) { KEY_MENU, VK_RMENU }, // (0xA5) @@ -265,7 +265,7 @@ static _WinTranslatePair _scancode_to_keycode[] = { { KEY_BRACELEFT, 0x1A }, { KEY_BRACERIGHT, 0x1B }, { KEY_ENTER, 0x1C }, - { KEY_CONTROL, 0x1D }, + { KEY_CTRL, 0x1D }, { KEY_A, 0x1E }, { KEY_S, 0x1F }, { KEY_D, 0x20 }, diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 1e9cdd241d..ccf13488ab 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -204,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; } @@ -631,31 +631,45 @@ MainLoop *OS_Windows::get_main_loop() const { } String OS_Windows::get_config_path() const { - if (has_environment("XDG_CONFIG_HOME")) { // unlikely, but after all why not? - return get_environment("XDG_CONFIG_HOME"); - } else if (has_environment("APPDATA")) { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. + if (has_environment("XDG_CONFIG_HOME")) { + if (get_environment("XDG_CONFIG_HOME").is_abs_path()) { + return get_environment("XDG_CONFIG_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `%APPDATA%` or `.` per the XDG Base Directory specification."); + } + } + if (has_environment("APPDATA")) { return get_environment("APPDATA"); - } else { - return "."; } + return "."; } String OS_Windows::get_data_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); - } else { - return get_config_path(); + if (get_environment("XDG_DATA_HOME").is_abs_path()) { + return get_environment("XDG_DATA_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `get_config_path()` per the XDG Base Directory specification."); + } } + return get_config_path(); } String OS_Windows::get_cache_path() const { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); - } else if (has_environment("TEMP")) { + if (get_environment("XDG_CACHE_HOME").is_abs_path()) { + return get_environment("XDG_CACHE_HOME"); + } else { + WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `%TEMP%` or `get_config_path()` per the XDG Base Directory specification."); + } + } + if (has_environment("TEMP")) { return get_environment("TEMP"); - } else { - return get_config_path(); } + return get_config_path(); } // Get properly capitalized engine name for system paths |