diff options
Diffstat (limited to 'platform')
127 files changed, 3570 insertions, 2741 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 5f0fcc9b77..1b6af8662e 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -197,12 +197,11 @@ def configure(env): if env["optimize"] == "speed": # optimize for speed (default) env.Append(LINKFLAGS=["-O2"]) env.Append(CCFLAGS=["-O2", "-fomit-frame-pointer"]) - env.Append(CPPDEFINES=["NDEBUG"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["-Os"]) - env.Append(CPPDEFINES=["NDEBUG"]) env.Append(LINKFLAGS=["-Os"]) + env.Append(CPPDEFINES=["NDEBUG"]) if can_vectorize: env.Append(CCFLAGS=["-ftree-vectorize"]) if env["target"] == "release_debug": @@ -251,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 @@ -259,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. @@ -284,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..7b9cbeea47 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -74,10 +74,13 @@ public: virtual Error rename(String p_from, String p_to); virtual Error remove(String p_name); + virtual bool is_link(String p_file) { return false; } + virtual String read_link(String p_file) { return p_file; } + virtual Error create_link(String p_source, String p_target) { return FAILED; } + 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 5f7e5eaa83..ff61eeaee1 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: @@ -199,7 +196,7 @@ void DisplayServerAndroid::window_set_input_text_callback(const Callable &p_call } void DisplayServerAndroid::window_set_rect_changed_callback(const Callable &p_callable, DisplayServer::WindowID p_window) { - // Not supported on Android. + rect_changed_callback = p_callable; } void DisplayServerAndroid::window_set_drop_files_callback(const Callable &p_callable, DisplayServer::WindowID p_window) { @@ -392,6 +389,19 @@ void DisplayServerAndroid::reset_window() { #endif } +void DisplayServerAndroid::notify_surface_changed(int p_width, int p_height) { + if (rect_changed_callback.is_null()) { + return; + } + + const Variant size = Rect2i(0, 0, p_width, p_height); + const Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + + rect_changed_callback.call(reinterpret_cast<const Variant **>(&sizep), 1, ret, ce); +} + DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { rendering_driver = p_rendering_driver; @@ -477,7 +487,7 @@ void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p Input::get_singleton()->joy_button(p_event.device, p_event.index, p_event.pressed); break; case JOY_EVENT_AXIS: - Input::JoyAxis value; + Input::JoyAxisValue value; value.min = -1; value.value = p_event.value; Input::get_singleton()->joy_axis(p_event.device, p_event.index, value); @@ -491,10 +501,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 +539,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) { @@ -741,15 +751,15 @@ void DisplayServerAndroid::process_mouse_event(int input_device, int event_actio ev->set_pressed(true); buttons_state = event_buttons_mask; if (event_vertical_factor > 0) { - _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_UP, event_vertical_factor); + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_UP, event_vertical_factor); } else if (event_vertical_factor < 0) { - _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_DOWN, -event_vertical_factor); + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_DOWN, -event_vertical_factor); } if (event_horizontal_factor > 0) { - _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_RIGHT, event_horizontal_factor); + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_RIGHT, event_horizontal_factor); } else if (event_horizontal_factor < 0) { - _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_LEFT, -event_horizontal_factor); + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_LEFT, -event_horizontal_factor); } } break; } @@ -778,22 +788,22 @@ void DisplayServerAndroid::process_double_tap(int event_android_button_mask, Poi ev->set_pressed(event_button_mask != 0); ev->set_button_index(_button_index_from_mask(event_button_mask)); ev->set_button_mask(event_button_mask); - ev->set_doubleclick(true); + ev->set_double_click(true); Input::get_singleton()->accumulate_input_event(ev); } int DisplayServerAndroid::_button_index_from_mask(int button_mask) { switch (button_mask) { - case BUTTON_MASK_LEFT: - return BUTTON_LEFT; - case BUTTON_MASK_RIGHT: - return BUTTON_RIGHT; - case BUTTON_MASK_MIDDLE: - return BUTTON_MIDDLE; - case BUTTON_MASK_XBUTTON1: - return BUTTON_XBUTTON1; - case BUTTON_MASK_XBUTTON2: - return BUTTON_XBUTTON2; + case MOUSE_BUTTON_MASK_LEFT: + return MOUSE_BUTTON_LEFT; + case MOUSE_BUTTON_MASK_RIGHT: + return MOUSE_BUTTON_RIGHT; + case MOUSE_BUTTON_MASK_MIDDLE: + return MOUSE_BUTTON_MIDDLE; + case MOUSE_BUTTON_MASK_XBUTTON1: + return MOUSE_BUTTON_XBUTTON1; + case MOUSE_BUTTON_MASK_XBUTTON2: + return MOUSE_BUTTON_XBUTTON2; default: return 0; } @@ -830,6 +840,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 { @@ -854,20 +870,36 @@ int DisplayServerAndroid::mouse_get_button_state() const { int DisplayServerAndroid::_android_button_mask_to_godot_button_mask(int android_button_mask) { int godot_button_mask = 0; if (android_button_mask & AMOTION_EVENT_BUTTON_PRIMARY) { - godot_button_mask |= BUTTON_MASK_LEFT; + godot_button_mask |= MOUSE_BUTTON_MASK_LEFT; } if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { - godot_button_mask |= BUTTON_MASK_RIGHT; + godot_button_mask |= MOUSE_BUTTON_MASK_RIGHT; } if (android_button_mask & AMOTION_EVENT_BUTTON_TERTIARY) { - godot_button_mask |= BUTTON_MASK_MIDDLE; + godot_button_mask |= MOUSE_BUTTON_MASK_MIDDLE; } if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) { - godot_button_mask |= BUTTON_MASK_XBUTTON1; + godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1; } if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { - godot_button_mask |= BUTTON_MASK_XBUTTON2; + godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2; } return godot_button_mask; } + +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..1379baf154 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; @@ -88,6 +112,7 @@ private: Callable window_event_callback; Callable input_event_callback; Callable input_text_callback; + Callable rect_changed_callback; void _window_callback(const Callable &p_callable, const Variant &p_arg) const; @@ -180,6 +205,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; @@ -188,6 +216,7 @@ public: static void register_android_driver(); void reset_window(); + void notify_surface_changed(int p_width, int p_height); virtual Point2i mouse_get_position() const; virtual int mouse_get_button_state() const; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 326e513261..c45828e194 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> @@ -845,7 +847,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int version_code = p_preset->get("version/code"); String package_name = p_preset->get("package/unique_name"); - const int screen_orientation = _get_android_orientation_value(_get_screen_orientation()); + const int screen_orientation = + _get_android_orientation_value(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation")))); bool screen_support_small = p_preset->get("screen/support_small"); bool screen_support_normal = p_preset->get("screen/support_normal"); @@ -853,7 +856,8 @@ 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"); + + bool backup_allowed = p_preset->get("user_data_backup/allow"); Vector<String> perms; // Write permissions into the perms variable. @@ -919,7 +923,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]); @@ -948,6 +951,10 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } + if (tname == "application" && attrname == "allowBackup") { + encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]); + } + if (tname == "instrumentation" && attrname == "targetPackage") { string_table.write[attr_value] = get_package_name(package_name); } @@ -971,28 +978,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 +993,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 +1478,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 +1505,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 +1682,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)); @@ -1702,6 +1690,8 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_xlarge"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data_backup/allow"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false)); @@ -1803,7 +1793,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 = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); #define CLEANUP_AND_RETURN(m_err) \ { \ @@ -1820,6 +1810,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 +1828,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 +1845,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 +1864,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 +1877,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 +1893,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 +1923,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); @@ -2034,6 +2037,13 @@ public: // Validate the rest of the configuration. String dk = p_preset->get("keystore/debug"); + String dk_user = p_preset->get("keystore/debug_user"); + String dk_password = p_preset->get("keystore/debug_password"); + + if ((dk.is_empty() || dk_user.is_empty() || dk_password.is_empty()) && (!dk.is_empty() || !dk_user.is_empty() || !dk_password.is_empty())) { + valid = false; + err += TTR("Either Debug Keystore, Debug User AND Debug Password settings must be configured OR none of them.") + "\n"; + } if (!FileAccess::exists(dk)) { dk = EditorSettings::get_singleton()->get("export/android/debug_keystore"); @@ -2044,6 +2054,13 @@ public: } String rk = p_preset->get("keystore/release"); + String rk_user = p_preset->get("keystore/release_user"); + String rk_password = p_preset->get("keystore/release_password"); + + if ((rk.is_empty() || rk_user.is_empty() || rk_password.is_empty()) && (!rk.is_empty() || !rk_user.is_empty() || !rk_password.is_empty())) { + valid = false; + err += TTR("Either Release Keystore, Release User AND Release Password settings must be configured OR none of them.") + "\n"; + } if (!rk.is_empty() && !FileAccess::exists(rk)) { valid = false; @@ -2130,27 +2147,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 && @@ -2262,11 +2265,12 @@ public: CharString command_line_argument = command_line_strings[i].utf8(); int base = r_command_line_flags.size(); int length = command_line_argument.length(); - if (length == 0) + if (length == 0) { continue; + } r_command_line_flags.resize(base + 4 + length); encode_uint32(length, &r_command_line_flags.write[base]); - copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length); + memcpy(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length); } } } @@ -2318,6 +2322,7 @@ public: return ERR_FILE_CANT_OPEN; } + String output; List<String> args; args.push_back("sign"); args.push_back("--verbose"); @@ -2333,7 +2338,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; @@ -2351,7 +2358,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; @@ -2484,7 +2493,7 @@ public: _clear_assets_directory(); if (!apk_expansion) { print_verbose("Exporting project files.."); - err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file); + err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, nullptr, ignore_so_file); if (err != OK) { EditorNode::add_io_error("Could not export project files to gradle project\n"); return err; @@ -2615,10 +2624,11 @@ public: } // This is the start of the Legacy build system print_verbose("Starting legacy build system.."); - if (p_debug) + if (p_debug) { src_apk = p_preset->get("custom_template/debug"); - else + } else { src_apk = p_preset->get("custom_template/release"); + } src_apk = src_apk.strip_edges(); if (src_apk == "") { if (p_debug) { @@ -2655,7 +2665,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 = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); #define CLEANUP_AND_RETURN(m_err) \ { \ @@ -2700,12 +2710,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); } @@ -2813,11 +2823,11 @@ public: zipOpenNewFileInZip(unaligned_apk, "assets/_cl_", &zipfi, - NULL, + nullptr, 0, - NULL, + nullptr, 0, - NULL, + nullptr, 0, // No compress (little size gain and potentially slower startup) Z_DEFAULT_COMPRESSION); zipWriteInFileInZip(unaligned_apk, command_line_flags.ptr(), command_line_flags.size()); diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 097a2391ee..0bb94dcc97 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -44,27 +44,6 @@ const String godot_project_name_xml_string = R"(<?xml version="1.0" encoding="ut </resources> )"; -DisplayServer::ScreenOrientation _get_screen_orientation() { - String orientation_settings = ProjectSettings::get_singleton()->get("display/window/handheld/orientation"); - DisplayServer::ScreenOrientation screen_orientation; - if (orientation_settings == "portrait") - screen_orientation = DisplayServer::SCREEN_PORTRAIT; - else if (orientation_settings == "reverse_landscape") - screen_orientation = DisplayServer::SCREEN_REVERSE_LANDSCAPE; - else if (orientation_settings == "reverse_portrait") - screen_orientation = DisplayServer::SCREEN_REVERSE_PORTRAIT; - else if (orientation_settings == "sensor_landscape") - screen_orientation = DisplayServer::SCREEN_SENSOR_LANDSCAPE; - else if (orientation_settings == "sensor_portrait") - screen_orientation = DisplayServer::SCREEN_SENSOR_PORTRAIT; - else if (orientation_settings == "sensor") - screen_orientation = DisplayServer::SCREEN_SENSOR; - else - screen_orientation = DisplayServer::SCREEN_LANDSCAPE; - - return screen_orientation; -} - int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation) { switch (screen_orientation) { case DisplayServer::SCREEN_PORTRAIT: @@ -240,12 +219,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"; @@ -271,16 +244,13 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) { String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; - String orientation = _get_android_orientation_label(_get_screen_orientation()); + String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation")))); String manifest_activity_text = vformat( " <activity android:name=\"com.godot.game.GodotApp\" " "tools:replace=\"android:screenOrientation\" " "android:screenOrientation=\"%s\">\n", orientation); - if (uses_xr) { - String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness")); - manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness); - } else { + if (!uses_xr) { manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n"; } manifest_activity_text += " </activity>\n"; @@ -288,16 +258,12 @@ 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 = + String manifest_application_text = vformat( " <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:allowBackup=\"%s\" tools:ignore=\"GoogleAppIndexingWarning\"\n" + " android:icon=\"@mipmap/icon\">\n\n", + bool_to_string(p_preset->get("user_data_backup/allow"))); - if (uses_xr) { - manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n"; - } manifest_application_text += _get_activity_tag(p_preset); manifest_application_text += " </application>\n"; return manifest_application_text; diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index 165d5da3ae..90370878b7 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_length() const { return len; } @@ -113,8 +114,10 @@ uint8_t FileAccessAndroid::get_8() const { return byte; } -int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { - off_t r = AAsset_read(a, p_dst, p_length); +uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + + 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..8890e0f645 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_length() 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/assets/.gitignore b/platform/android/java/app/assets/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/platform/android/java/app/assets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 934c4bf441..1b1fb47bd8 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -77,7 +77,7 @@ android { defaultConfig { // The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects. aaptOptions { - ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~" + ignoreAssetsPattern "!.svn:!.git:!.gitignore:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~" } ndk { @@ -106,8 +106,10 @@ android { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' - // Should be uncommented for development purpose within Android Studio - // doNotStrip '**/*.so' + // 'doNotStrip' is enabled for development within Android Studio + if (shouldNotStrip()) { + doNotStrip '**/*.so' + } } signingConfigs { @@ -155,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 585e517631..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', @@ -209,10 +209,19 @@ ext.getReleaseKeyAlias = { -> return keyAlias } +ext.isAndroidStudio = { -> + def sysProps = System.getProperties() + return sysProps != null && sysProps['idea.platform.prefix'] != null +} + ext.shouldZipAlign = { -> String zipAlignFlag = project.hasProperty("perform_zipalign") ? project.property("perform_zipalign") : "" if (zipAlignFlag == null || zipAlignFlag.isEmpty()) { - zipAlignFlag = "false" + if (isAndroidStudio()) { + zipAlignFlag = "true" + } else { + zipAlignFlag = "false" + } } return Boolean.parseBoolean(zipAlignFlag) } @@ -220,7 +229,15 @@ ext.shouldZipAlign = { -> ext.shouldSign = { -> String signFlag = project.hasProperty("perform_signing") ? project.property("perform_signing") : "" if (signFlag == null || signFlag.isEmpty()) { - signFlag = "false" + if (isAndroidStudio()) { + signFlag = "true" + } else { + signFlag = "false" + } } return Boolean.parseBoolean(signFlag) } + +ext.shouldNotStrip = { -> + return isAndroidStudio() || project.hasProperty("doNotStrip") +} diff --git a/platform/android/java/app/res/drawable/splash.png b/platform/android/java/app/res/drawable-nodpi/splash.png Binary files differindex 7bddd4325a..7bddd4325a 100644 --- a/platform/android/java/app/res/drawable/splash.png +++ b/platform/android/java/app/res/drawable-nodpi/splash.png diff --git a/platform/android/java/app/res/drawable/splash_bg_color.png b/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png Binary files differindex 004b6fd508..004b6fd508 100644 --- a/platform/android/java/app/res/drawable/splash_bg_color.png +++ b/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png diff --git a/platform/android/java/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/build.gradle b/platform/android/java/lib/build.gradle index 6fc9a11a08..663ba73d40 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -36,8 +36,10 @@ android { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' - // Should be uncommented for development purpose within Android Studio - // doNotStrip '**/*.so' + // 'doNotStrip' is enabled for development within Android Studio + if (shouldNotStrip()) { + doNotStrip '**/*.so' + } } sourceSets { diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java index 4e67402c63..1ed16e04ca 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -34,6 +34,7 @@ import android.content.Intent; import android.os.Bundle; import android.view.KeyEvent; +import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; @@ -44,7 +45,7 @@ import androidx.fragment.app.FragmentActivity; * It's also a reference implementation for how to setup and use the {@link Godot} fragment * within an Android app. */ -public abstract class FullScreenGodotApp extends FragmentActivity { +public abstract class FullScreenGodotApp extends FragmentActivity implements GodotHost { @Nullable private Godot godotFragment; @@ -62,11 +63,30 @@ public abstract class FullScreenGodotApp extends FragmentActivity { @Override public void onNewIntent(Intent intent) { + super.onNewIntent(intent); if (godotFragment != null) { godotFragment.onNewIntent(intent); } } + @CallSuper + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (godotFragment != null) { + godotFragment.onActivityResult(requestCode, resultCode, data); + } + } + + @CallSuper + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (godotFragment != null) { + godotFragment.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + @Override public void onBackPressed() { if (godotFragment != null) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 0891904dff..0c16214c8a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -103,6 +103,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.security.MessageDigest; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -131,6 +132,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC private boolean activityResumed; private int mState; + private GodotHost godotHost; private GodotPluginRegistry pluginRegistry; static private Intent mCurrentIntent; @@ -178,7 +180,25 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC public ResultCallback result_callback; @Override + public void onAttach(Context context) { + super.onAttach(context); + if (getParentFragment() instanceof GodotHost) { + godotHost = (GodotHost)getParentFragment(); + } else if (getActivity() instanceof GodotHost) { + godotHost = (GodotHost)getActivity(); + } + } + + @Override + public void onDetach() { + super.onDetach(); + godotHost = null; + } + + @CallSuper + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); if (result_callback != null) { result_callback.callback(requestCode, resultCode, data); result_callback = null; @@ -189,8 +209,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC } } + @CallSuper @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults); } @@ -201,6 +223,20 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC }; /** + * Invoked on the render thread when the Godot setup is complete. + */ + @CallSuper + protected void onGodotSetupCompleted() { + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGodotSetupCompleted(); + } + + if (godotHost != null) { + godotHost.onGodotSetupCompleted(); + } + } + + /** * Invoked on the render thread when the Godot main loop has started. */ @CallSuper @@ -208,6 +244,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onGodotMainLoopStarted(); } + + if (godotHost != null) { + godotHost.onGodotMainLoopStarted(); + } } /** @@ -228,7 +268,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC GodotLib.setup(command_line); - final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name"); + final String videoDriver = GodotLib.getGlobal("rendering/driver/driver_name"); if (videoDriver.equals("Vulkan")) { mRenderView = new GodotVulkanRenderView(activity, this); } else { @@ -301,7 +341,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)); } else { - //deprecated in API 26 + // deprecated in API 26 v.vibrate(durationMs); } } @@ -356,6 +396,21 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC @CallSuper protected String[] getCommandLine() { + String[] original = parseCommandLine(); + String[] updated; + List<String> hostCommandLine = godotHost != null ? godotHost.getCommandLine() : null; + if (hostCommandLine == null || hostCommandLine.isEmpty()) { + updated = original; + } else { + updated = Arrays.copyOf(original, original.length + hostCommandLine.size()); + for (int i = 0; i < hostCommandLine.size(); i++) { + updated[original.length + i] = hostCommandLine.get(i); + } + } + return updated; + } + + private String[] parseCommandLine() { InputStream is; try { is = getActivity().getAssets().open("_cl_"); @@ -473,7 +528,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE); pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); - //check for apk expansion API + // check for apk expansion API boolean md5mismatch = false; command_line = getCommandLine(); String main_pack_md5 = null; @@ -529,9 +584,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC command_line = new_args.toArray(new String[new_args.size()]); } if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { - //check that environment is ok! + // check that environment is ok! if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - //show popup and die + // show popup and die } // Build the full path to the app's expansion files 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/server/godot_server.cpp b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index 1ced95fcbc..317fd13535 100644 --- a/platform/server/godot_server.cpp +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -1,5 +1,5 @@ /*************************************************************************/ -/* godot_server.cpp */ +/* GodotHost.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,22 +28,29 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "main/main.h" -#include "os_server.h" +package org.godotengine.godot; -int main(int argc, char *argv[]) { - OS_Server os; +import java.util.Collections; +import java.util.List; - // We must override main when testing is enabled - TEST_MAIN_OVERRIDE +/** + * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} fragment. + */ +public interface GodotHost { + /** + * Provides a set of command line parameters to setup the engine. + */ + default List<String> getCommandLine() { + return Collections.emptyList(); + } - Error err = Main::setup(argv[0], argc - 1, &argv[1]); - if (err != OK) - return 255; + /** + * Invoked on the render thread when the Godot setup is complete. + */ + default void onGodotSetupCompleted() {} - if (Main::start()) - os.run(); // it is actually the OS that decides how to run - Main::cleanup(); - - return os.get_exit_code(); + /** + * Invoked on the render thread when the Godot main loop has started. + */ + default void onGodotMainLoopStarted() {} } diff --git a/platform/android/java/lib/src/org/godotengine/godot/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/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java index 993f0e9127..6c8a3d4219 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -47,6 +47,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -109,31 +110,9 @@ public abstract class GodotPlugin { * This method is invoked on the render thread. */ public final void onRegisterPluginWithGodotNative() { - registeredSignals.putAll(registerPluginWithGodotNative(this, new GodotPluginInfoProvider() { - @NonNull - @Override - public String getPluginName() { - return GodotPlugin.this.getPluginName(); - } - - @NonNull - @Override - public List<String> getPluginMethods() { - return GodotPlugin.this.getPluginMethods(); - } - - @NonNull - @Override - public Set<SignalInfo> getPluginSignals() { - return GodotPlugin.this.getPluginSignals(); - } - - @NonNull - @Override - public Set<String> getPluginGDNativeLibrariesPaths() { - return GodotPlugin.this.getPluginGDNativeLibrariesPaths(); - } - })); + registeredSignals.putAll( + registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals(), + getPluginGDNativeLibrariesPaths())); } /** @@ -141,23 +120,41 @@ public abstract class GodotPlugin { * * This method must be invoked on the render thread. */ - public static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject, GodotPluginInfoProvider pluginInfoProvider) { - nativeRegisterSingleton(pluginInfoProvider.getPluginName(), pluginObject); + public static void registerPluginWithGodotNative(Object pluginObject, + GodotPluginInfoProvider pluginInfoProvider) { + registerPluginWithGodotNative(pluginObject, pluginInfoProvider.getPluginName(), + Collections.emptyList(), pluginInfoProvider.getPluginSignals(), + pluginInfoProvider.getPluginGDNativeLibrariesPaths()); + + // Notify that registration is complete. + pluginInfoProvider.onPluginRegistered(); + } + private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject, + String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals, + Set<String> pluginGDNativeLibrariesPaths) { + nativeRegisterSingleton(pluginName, pluginObject); + + Set<Method> filteredMethods = new HashSet<>(); Class clazz = pluginObject.getClass(); + Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { - boolean found = false; - - for (String s : pluginInfoProvider.getPluginMethods()) { - if (s.equals(method.getName())) { - found = true; - break; + // Check if the method is annotated with {@link UsedByGodot}. + if (method.getAnnotation(UsedByGodot.class) != null) { + filteredMethods.add(method); + } else { + // For backward compatibility, process the methods from the given <pluginMethods> argument. + for (String methodName : pluginMethods) { + if (methodName.equals(method.getName())) { + filteredMethods.add(method); + break; + } } } - if (!found) - continue; + } + for (Method method : filteredMethods) { List<String> ptr = new ArrayList<>(); Class[] paramTypes = method.getParameterTypes(); @@ -168,21 +165,20 @@ public abstract class GodotPlugin { String[] pt = new String[ptr.size()]; ptr.toArray(pt); - nativeRegisterMethod(pluginInfoProvider.getPluginName(), method.getName(), method.getReturnType().getName(), pt); + nativeRegisterMethod(pluginName, method.getName(), method.getReturnType().getName(), pt); } // Register the signals for this plugin. Map<String, SignalInfo> registeredSignals = new HashMap<>(); - for (SignalInfo signalInfo : pluginInfoProvider.getPluginSignals()) { + for (SignalInfo signalInfo : pluginSignals) { String signalName = signalInfo.getName(); - nativeRegisterSignal(pluginInfoProvider.getPluginName(), signalName, signalInfo.getParamTypesNames()); + nativeRegisterSignal(pluginName, signalName, signalInfo.getParamTypesNames()); registeredSignals.put(signalName, signalInfo); } // Get the list of gdnative libraries to register. - Set<String> gdnativeLibrariesPaths = pluginInfoProvider.getPluginGDNativeLibrariesPaths(); - if (!gdnativeLibrariesPaths.isEmpty()) { - nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0])); + if (!pluginGDNativeLibrariesPaths.isEmpty()) { + nativeRegisterGDNativeLibraries(pluginGDNativeLibrariesPaths.toArray(new String[0])); } return registeredSignals; @@ -236,6 +232,11 @@ public abstract class GodotPlugin { public boolean onMainBackPressed() { return false; } /** + * Invoked on the render thread when the Godot setup is complete. + */ + public void onGodotSetupCompleted() {} + + /** * Invoked on the render thread when the Godot main loop has started. */ public void onGodotMainLoopStarted() {} @@ -282,8 +283,11 @@ public abstract class GodotPlugin { /** * Returns the list of methods to be exposed to Godot. + * + * @deprecated Used the {@link UsedByGodot} annotation instead. */ @NonNull + @Deprecated public List<String> getPluginMethods() { return Collections.emptyList(); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java index c3084b036e..09366384c2 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java @@ -32,7 +32,7 @@ package org.godotengine.godot.plugin; import androidx.annotation.NonNull; -import java.util.List; +import java.util.Collections; import java.util.Set; /** @@ -46,16 +46,12 @@ public interface GodotPluginInfoProvider { String getPluginName(); /** - * Returns the list of methods to be exposed to Godot. - */ - @NonNull - List<String> getPluginMethods(); - - /** * Returns the list of signals to be exposed to Godot. */ @NonNull - Set<SignalInfo> getPluginSignals(); + default Set<SignalInfo> getPluginSignals() { + return Collections.emptySet(); + } /** * Returns the paths for the plugin's gdnative libraries (if any). @@ -63,5 +59,14 @@ public interface GodotPluginInfoProvider { * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file. */ @NonNull - Set<String> getPluginGDNativeLibrariesPaths(); + default Set<String> getPluginGDNativeLibrariesPaths() { + return Collections.emptySet(); + } + + /** + * This is invoked on the render thread when the plugin described by this instance has been + * registered. + */ + default void onPluginRegistered() { + } } diff --git a/platform/javascript/api/javascript_eval.h b/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java index 24f7648ed9..04c091d944 100644 --- a/platform/javascript/api/javascript_eval.h +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java @@ -1,5 +1,5 @@ /*************************************************************************/ -/* javascript_eval.h */ +/* UsedByGodot.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,26 +28,18 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef JAVASCRIPT_EVAL_H -#define JAVASCRIPT_EVAL_H - -#include "core/object/class_db.h" - -class JavaScript : public Object { -private: - GDCLASS(JavaScript, Object); - - static JavaScript *singleton; - -protected: - static void _bind_methods(); - -public: - Variant eval(const String &p_code, bool p_use_global_exec_context = false); - - static JavaScript *get_singleton(); - JavaScript(); - ~JavaScript(); -}; - -#endif // JAVASCRIPT_EVAL_H +package org.godotengine.godot.plugin; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to indicate a method is being invoked from the Godot game logic. + * + * At runtime, annotated plugin methods are detected and automatically registered. + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface UsedByGodot {} diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt index b967fd5f24..6e59268076 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt @@ -61,6 +61,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk private var rendererInitialized = false private var rendererResumed = false private var resumed = false + private var surfaceChanged = false private var hasSurface = false private var width = 0 private var height = 0 @@ -141,8 +142,10 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk fun onSurfaceChanged(width: Int, height: Int) { lock.withLock { hasSurface = true + surfaceChanged = true; this.width = width this.height = height + lockCondition.signalAll() } } @@ -188,8 +191,11 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk rendererInitialized = true vkRenderer.onVkSurfaceCreated(vkSurfaceView.holder.surface) } + } + if (surfaceChanged) { vkRenderer.onVkSurfaceChanged(vkSurfaceView.holder.surface, width, height) + surfaceChanged = false } // Break out of the loop so drawing can occur without holding onto the lock. diff --git a/platform/android/java/nativeSrcsConfigs/README.md b/platform/android/java/nativeSrcsConfigs/README.md index e48505ccda..9d884415cc 100644 --- a/platform/android/java/nativeSrcsConfigs/README.md +++ b/platform/android/java/nativeSrcsConfigs/README.md @@ -1,4 +1,4 @@ ## Native sources configs -This is a non functional Android library used to provide Android Studio editor support to the Godot project native files. +This is a non-functional Android library used to provide Android Studio editor support to the Godot project native files. Nothing else should be added to this library. diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle index 66077060ea..158bb2b98e 100644 --- a/platform/android/java/nativeSrcsConfigs/build.gradle +++ b/platform/android/java/nativeSrcsConfigs/build.gradle @@ -20,9 +20,6 @@ android { packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' - - // Should be uncommented for development purpose within Android Studio - // doNotStrip '**/*.so' } sourceSets { diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index ab03599dc3..f49b0e843a 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -38,6 +38,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, return false; JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); MethodInfo *method = nullptr; for (List<MethodInfo>::Element *E = M->get().front(); E; E = E->next()) { @@ -965,6 +966,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { return class_cache[p_class]; JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, Ref<JavaClass>()); jclass bclass = env->FindClass(p_class.utf8().get_data()); ERR_FAIL_COND_V(!bclass, Ref<JavaClass>()); @@ -1149,6 +1151,7 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) { singleton = this; JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jclass activityClass = env->FindClass("android/app/Activity"); jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 41201db32b..ec3b6f8ac0 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -73,6 +73,7 @@ jobject GodotIOJavaWrapper::get_instance() { Error GodotIOJavaWrapper::open_uri(const String &p_uri) { if (_open_URI) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, ERR_UNAVAILABLE); jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; } else { @@ -83,6 +84,7 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { String GodotIOJavaWrapper::get_user_data_dir() { if (_get_data_dir) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); return jstring_to_string(s, env); } else { @@ -93,6 +95,7 @@ String GodotIOJavaWrapper::get_user_data_dir() { String GodotIOJavaWrapper::get_locale() { if (_get_locale) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_locale); return jstring_to_string(s, env); } else { @@ -103,6 +106,7 @@ String GodotIOJavaWrapper::get_locale() { String GodotIOJavaWrapper::get_model() { if (_get_model) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_model); return jstring_to_string(s, env); } else { @@ -113,6 +117,7 @@ String GodotIOJavaWrapper::get_model() { int GodotIOJavaWrapper::get_screen_dpi() { if (_get_screen_DPI) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 160); return env->CallIntMethod(godot_io_instance, _get_screen_DPI); } else { return 160; @@ -122,6 +127,7 @@ int GodotIOJavaWrapper::get_screen_dpi() { void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { if (_screen_get_usable_rect) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _screen_get_usable_rect); ERR_FAIL_COND(env->GetArrayLength(returnArray) != 4); jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE); @@ -135,6 +141,7 @@ void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { String GodotIOJavaWrapper::get_unique_id() { if (_get_unique_id) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_id); return jstring_to_string(s, env); } else { @@ -149,6 +156,7 @@ bool GodotIOJavaWrapper::has_vk() { void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { if (_show_keyboard) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end); } @@ -157,6 +165,7 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int void GodotIOJavaWrapper::hide_vk() { if (_hide_keyboard) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); env->CallVoidMethod(godot_io_instance, _hide_keyboard); } } @@ -164,6 +173,7 @@ void GodotIOJavaWrapper::hide_vk() { void GodotIOJavaWrapper::set_screen_orientation(int p_orient) { if (_set_screen_orientation) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); env->CallVoidMethod(godot_io_instance, _set_screen_orientation, p_orient); } } @@ -171,6 +181,7 @@ void GodotIOJavaWrapper::set_screen_orientation(int p_orient) { int GodotIOJavaWrapper::get_screen_orientation() { if (_get_screen_orientation) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 0); return env->CallIntMethod(godot_io_instance, _get_screen_orientation); } else { return 0; @@ -180,6 +191,7 @@ int GodotIOJavaWrapper::get_screen_orientation() { String GodotIOJavaWrapper::get_system_dir(int p_dir) { if (_get_system_dir) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String(".")); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir); return jstring_to_string(s, env); } else { diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index bb22162879..c7ff6cb2c0 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -127,9 +127,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc if (p_cmdline) { cmdlen = env->GetArrayLength(p_cmdline); if (cmdlen) { - cmdline = (const char **)malloc((cmdlen + 1) * sizeof(const char *)); + cmdline = (const char **)memalloc((cmdlen + 1) * sizeof(const char *)); + ERR_FAIL_NULL_MSG(cmdline, "Out of memory."); cmdline[cmdlen] = nullptr; - j_cmdline = (jstring *)malloc(cmdlen * sizeof(jstring)); + j_cmdline = (jstring *)memalloc(cmdlen * sizeof(jstring)); + ERR_FAIL_NULL_MSG(j_cmdline, "Out of memory."); for (int i = 0; i < cmdlen; i++) { jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i); @@ -147,13 +149,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc for (int i = 0; i < cmdlen; ++i) { env->ReleaseStringUTFChars(j_cmdline[i], cmdline[i]); } - free(j_cmdline); + memfree(j_cmdline); } - free(cmdline); + memfree(cmdline); } if (err != OK) { - return; //should exit instead and print the error + return; // should exit instead and print the error } java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity())); @@ -171,6 +173,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j os_android->set_native_window(native_window); DisplayServerAndroid::get_singleton()->reset_window(); + DisplayServerAndroid::get_singleton()->notify_surface_changed(p_width, p_height); } } } @@ -215,9 +218,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl if (step == 1) { if (!Main::start()) { - return; //should exit instead and print the error + return; // should exit instead and print the error } + godot_java->on_godot_setup_completed(env); os_android->main_loop_begin(); godot_java->on_godot_main_loop_started(env); ++step; diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index 5b638300ef..837d2aeced 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -34,6 +34,7 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); _godot_view = env->NewGlobalRef(godot_view); @@ -42,12 +43,15 @@ 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"); } } void GodotJavaViewWrapper::request_pointer_capture() { if (_request_pointer_capture != 0) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(_godot_view, _request_pointer_capture); } } @@ -55,12 +59,25 @@ void GodotJavaViewWrapper::request_pointer_capture() { void GodotJavaViewWrapper::release_pointer_capture() { if (_request_pointer_capture != 0) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(_godot_view, _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); + env->DeleteGlobalRef(_godot_view); env->DeleteGlobalRef(_cls); } diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h index 548c278292..da547d8118 100644 --- a/platform/android/java_godot_view_wrapper.h +++ b/platform/android/java_godot_view_wrapper.h @@ -34,6 +34,8 @@ #include <android/log.h> #include <jni.h> +#include "string_android.h" + // Class that makes functions in java/src/org/godotengine/godot/GodotView.java callable from C++ class GodotJavaViewWrapper { private: @@ -43,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/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 759980373a..bfd93345f3 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -74,6 +74,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z"); _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V"); _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;"); + _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V"); _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); // get some Activity method pointers... @@ -93,6 +94,8 @@ jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_cl if (p_env == nullptr) p_env = get_jni_env(); + ERR_FAIL_COND_V(p_env == nullptr, nullptr); + jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class); return p_env->GetStaticObjectField(godot_class, fid); } else { @@ -103,6 +106,8 @@ jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_cl jobject GodotJavaWrapper::get_class_loader() { if (_get_class_loader) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + return env->CallObjectMethod(activity, _get_class_loader); } else { return nullptr; @@ -114,17 +119,30 @@ GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { return _godot_view; } JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + jmethodID godot_view_getter = env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); _godot_view = new GodotJavaViewWrapper(env->CallObjectMethod(godot_instance, godot_view_getter)); return _godot_view; } void GodotJavaWrapper::on_video_init(JNIEnv *p_env) { - if (_on_video_init) + if (_on_video_init) { if (p_env == nullptr) p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); - p_env->CallVoidMethod(godot_instance, _on_video_init); + p_env->CallVoidMethod(godot_instance, _on_video_init); + } +} + +void GodotJavaWrapper::on_godot_setup_completed(JNIEnv *p_env) { + if (_on_godot_setup_completed) { + if (p_env == nullptr) { + p_env = get_jni_env(); + } + p_env->CallVoidMethod(godot_instance, _on_godot_setup_completed); + } } void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) { @@ -132,29 +150,36 @@ void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) { if (p_env == nullptr) { p_env = get_jni_env(); } + ERR_FAIL_COND(p_env == nullptr); + p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started); } - p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started); } void GodotJavaWrapper::restart(JNIEnv *p_env) { - if (_restart) + if (_restart) { if (p_env == nullptr) p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); - p_env->CallVoidMethod(godot_instance, _restart); + p_env->CallVoidMethod(godot_instance, _restart); + } } void GodotJavaWrapper::force_quit(JNIEnv *p_env) { - if (_finish) + if (_finish) { if (p_env == nullptr) p_env = get_jni_env(); + ERR_FAIL_COND(p_env == nullptr); - p_env->CallVoidMethod(godot_instance, _finish); + p_env->CallVoidMethod(godot_instance, _finish); + } } void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) { if (_set_keep_screen_on) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _set_keep_screen_on, p_enabled); } } @@ -162,6 +187,8 @@ void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) { void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { if (_alert) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle); @@ -170,6 +197,8 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { int GodotJavaWrapper::get_gles_version_code() { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, 0); + if (_get_GLES_version_code) { return env->CallIntMethod(godot_instance, _get_GLES_version_code); } @@ -184,6 +213,8 @@ bool GodotJavaWrapper::has_get_clipboard() { String GodotJavaWrapper::get_clipboard() { if (_get_clipboard) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); + jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_clipboard); return jstring_to_string(s, env); } else { @@ -194,6 +225,8 @@ String GodotJavaWrapper::get_clipboard() { String GodotJavaWrapper::get_input_fallback_mapping() { if (_get_input_fallback_mapping) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, String()); + jstring fallback_mapping = (jstring)env->CallObjectMethod(godot_instance, _get_input_fallback_mapping); return jstring_to_string(fallback_mapping, env); } else { @@ -208,6 +241,8 @@ bool GodotJavaWrapper::has_set_clipboard() { void GodotJavaWrapper::set_clipboard(const String &p_text) { if (_set_clipboard) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); env->CallVoidMethod(godot_instance, _set_clipboard, jStr); } @@ -216,6 +251,8 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) { bool GodotJavaWrapper::request_permission(const String &p_name) { if (_request_permission) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + jstring jStrName = env->NewStringUTF(p_name.utf8().get_data()); return env->CallBooleanMethod(godot_instance, _request_permission, jStrName); } else { @@ -226,6 +263,8 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { bool GodotJavaWrapper::request_permissions() { if (_request_permissions) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + return env->CallBooleanMethod(godot_instance, _request_permissions); } else { return false; @@ -236,6 +275,8 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const { Vector<String> permissions_list; if (_get_granted_permissions) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, permissions_list); + jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions); jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object); @@ -254,6 +295,8 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const { void GodotJavaWrapper::init_input_devices() { if (_init_input_devices) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _init_input_devices); } } @@ -261,6 +304,8 @@ void GodotJavaWrapper::init_input_devices() { jobject GodotJavaWrapper::get_surface() { if (_get_surface) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, nullptr); + return env->CallObjectMethod(godot_instance, _get_surface); } else { return nullptr; @@ -270,6 +315,8 @@ jobject GodotJavaWrapper::get_surface() { bool GodotJavaWrapper::is_activity_resumed() { if (_is_activity_resumed) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND_V(env == nullptr, false); + return env->CallBooleanMethod(godot_instance, _is_activity_resumed); } else { return false; @@ -279,6 +326,8 @@ bool GodotJavaWrapper::is_activity_resumed() { void GodotJavaWrapper::vibrate(int p_duration_ms) { if (_vibrate) { JNIEnv *env = get_jni_env(); + ERR_FAIL_COND(env == nullptr); + env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms); } } diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 99a60dffa1..0e20747a16 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -66,6 +66,7 @@ private: jmethodID _is_activity_resumed = 0; jmethodID _vibrate = 0; jmethodID _get_input_fallback_mapping = 0; + jmethodID _on_godot_setup_completed = 0; jmethodID _on_godot_main_loop_started = 0; jmethodID _get_class_loader = 0; @@ -80,6 +81,7 @@ public: GodotJavaViewWrapper *get_godot_view(); void on_video_init(JNIEnv *p_env = nullptr); + void on_godot_setup_completed(JNIEnv *p_env = nullptr); void on_godot_main_loop_started(JNIEnv *p_env = nullptr); void restart(JNIEnv *p_env = nullptr); void force_quit(JNIEnv *p_env = nullptr); diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index 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/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index afcc7294f2..ba379c8d43 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -30,17 +30,34 @@ #include "thread_jandroid.h" +#include <android/log.h> + #include "core/os/thread.h" static JavaVM *java_vm = nullptr; static thread_local JNIEnv *env = nullptr; +// The logic here need to improve, init_thread/term_tread are designed to work with Thread::callback +// Calling init_thread from setup_android_thread and get_jni_env to setup an env we're keeping and not detaching +// could cause issues on app termination. +// +// We should be making sure that any thread started calls a nice cleanup function when it's done, +// especially now that we use many more threads. + static void init_thread() { + if (env) { + // thread never detached! just keep using... + return; + } + java_vm->AttachCurrentThread(&env, nullptr); } static void term_thread() { java_vm->DetachCurrentThread(); + + // this is no longer valid, must called init_thread to re-establish + env = nullptr; } void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) { @@ -50,9 +67,17 @@ void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) { } void setup_android_thread() { - init_thread(); + if (!env) { + // !BAS! see remarks above + init_thread(); + } } JNIEnv *get_jni_env() { + if (!env) { + // !BAS! see remarks above + init_thread(); + } + return env; } 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/detect.py b/platform/iphone/detect.py index 17796beb6f..cf358e0878 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -54,7 +54,7 @@ def configure(env): if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["-O2", "-ftree-vectorize", "-fomit-frame-pointer"]) env.Append(LINKFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["-Os", "-ftree-vectorize"]) env.Append(LINKFLAGS=["-Os"]) 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/export/export.cpp b/platform/iphone/export/export.cpp index c585c2afbe..73723b98a0 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -353,6 +353,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); @@ -378,11 +380,6 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/landscape_left"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/landscape_right"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait_upside_down"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "icons/generate_missing"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with retina display @@ -453,6 +450,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; } else if (lines[i].find("$team_id") != -1) { strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n"; + } else if (lines[i].find("$default_build_config") != -1) { + strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n"; } else if (lines[i].find("$export_method") != -1) { int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release"); strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n"; @@ -473,6 +472,20 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n"; } else if (lines[i].find("$linker_flags") != -1) { strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n"; + } else if (lines[i].find("$targeted_device_family") != -1) { + String xcode_value; + switch ((int)p_preset->get("application/targeted_device_family")) { + case 0: // iPhone + xcode_value = "1"; + break; + case 1: // iPad + xcode_value = "2"; + break; + case 2: // iPhone & iPad + xcode_value = "1,2"; + break; + } + strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n"; } else if (lines[i].find("$cpp_code") != -1) { strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n"; } else if (lines[i].find("$docs_in_place") != -1) { @@ -501,18 +514,39 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$required_device_capabilities", capabilities); } else if (lines[i].find("$interface_orientations") != -1) { String orientations; + const DisplayServer::ScreenOrientation screen_orientation = + DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))); - if ((bool)p_preset->get("orientation/portrait")) { - orientations += "<string>UIInterfaceOrientationPortrait</string>\n"; - } - if ((bool)p_preset->get("orientation/landscape_left")) { - orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n"; - } - if ((bool)p_preset->get("orientation/landscape_right")) { - orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n"; - } - if ((bool)p_preset->get("orientation/portrait_upside_down")) { - orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n"; + switch (screen_orientation) { + case DisplayServer::SCREEN_LANDSCAPE: + orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n"; + break; + case DisplayServer::SCREEN_PORTRAIT: + orientations += "<string>UIInterfaceOrientationPortrait</string>\n"; + break; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n"; + break; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n"; + break; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + // Allow both landscape orientations depending on sensor direction. + orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n"; + orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n"; + break; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + // Allow both portrait orientations depending on sensor direction. + orientations += "<string>UIInterfaceOrientationPortrait</string>\n"; + orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n"; + break; + case DisplayServer::SCREEN_SENSOR: + // Allow all screen orientations depending on sensor direction. + orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n"; + orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n"; + orientations += "<string>UIInterfaceOrientationPortrait</string>\n"; + orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n"; + break; } strnew += lines[i].replace("$interface_orientations", orientations); 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 887297848e..00a88d79c5 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -39,6 +39,7 @@ #import <CoreMotion/CoreMotion.h> static const int max_touches = 8; +static const float earth_gravity = 9.80665; @interface GodotView () { UITouch *godot_touches[max_touches]; @@ -290,14 +291,14 @@ static const int max_touches = 8; - (void)initTouches { for (int i = 0; i < max_touches; i++) { - godot_touches[i] = NULL; + godot_touches[i] = nullptr; } } - (int)getTouchIDForTouch:(UITouch *)p_touch { int first = -1; for (int i = 0; i < max_touches; i++) { - if (first == -1 && godot_touches[i] == NULL) { + if (first == -1 && godot_touches[i] == nullptr) { first = i; continue; } @@ -317,11 +318,11 @@ static const int max_touches = 8; - (int)removeTouch:(UITouch *)p_touch { int remaining = 0; for (int i = 0; i < max_touches; i++) { - if (godot_touches[i] == NULL) { + if (godot_touches[i] == nullptr) { continue; } if (godot_touches[i] == p_touch) { - godot_touches[i] = NULL; + godot_touches[i] = nullptr; } else { ++remaining; } @@ -331,7 +332,7 @@ static const int max_touches = 8; - (void)clearTouches { for (int i = 0; i < max_touches; i++) { - godot_touches[i] = NULL; + godot_touches[i] = nullptr; } } @@ -402,10 +403,19 @@ static const int max_touches = 8; // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc // Apple splits our accelerometer date into a gravity and user movement - // component. We add them back together + // component. We add them back together. CMAcceleration gravity = self.motionManager.deviceMotion.gravity; CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration; + // To be consistent with Android we convert the unit of measurement from g (Earth's gravity) + // to m/s^2. + gravity.x *= earth_gravity; + gravity.y *= earth_gravity; + gravity.z *= earth_gravity; + acceleration.x *= earth_gravity; + acceleration.y *= earth_gravity; + acceleration.z *= earth_gravity; + ///@TODO We don't seem to be getting data here, is my device broken or /// is this code incorrect? CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field; diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index cef03534c4..3430a9cba7 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -56,16 +56,16 @@ void iOS::alert(const char *p_alert, const char *p_title) { String iOS::get_model() const { // [[UIDevice currentDevice] model] only returns "iPad" or "iPhone". size_t size; - sysctlbyname("hw.machine", NULL, &size, NULL, 0); + sysctlbyname("hw.machine", nullptr, &size, nullptr, 0); char *model = (char *)malloc(size); - if (model == NULL) { + if (model == nullptr) { return ""; } - sysctlbyname("hw.machine", model, &size, NULL, 0); + sysctlbyname("hw.machine", model, &size, nullptr, 0); NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding]; free(model); const char *str = [platform UTF8String]; - return String(str != NULL ? str : ""); + return String(str != nullptr ? str : ""); } String iOS::get_rate_url(int p_app_id) const { diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm index a0f0eee5d3..45842b38aa 100644 --- a/platform/iphone/joypad_iphone.mm +++ b/platform/iphone/joypad_iphone.mm @@ -287,7 +287,7 @@ void JoypadIPhone::start_processing() { gamepad.dpad.right.isPressed); }; - Input::JoyAxis jx; + Input::JoyAxisValue jx; jx.min = -1; if (element == gamepad.leftThumbstick) { jx.value = gamepad.leftThumbstick.xAxis.value; diff --git a/platform/iphone/keyboard_input_view.mm b/platform/iphone/keyboard_input_view.mm index 1408f78e90..0e5a98a3e6 100644 --- a/platform/iphone/keyboard_input_view.mm +++ b/platform/iphone/keyboard_input_view.mm @@ -88,13 +88,15 @@ self.text = existingString; self.previousText = existingString; + NSInteger safeStartIndex = MAX(start, 0); + NSRange textRange; // Either a simple cursor or a selection. if (end > 0) { - textRange = NSMakeRange(start, end - start); + textRange = NSMakeRange(safeStartIndex, end - start); } else { - textRange = NSMakeRange(start, 0); + textRange = NSMakeRange(safeStartIndex, 0); } self.selectedRange = textRange; diff --git a/platform/iphone/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 50b61c0db3..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", @@ -17,16 +17,14 @@ sys_env.AddJSLibraries( [ "js/libs/library_godot_audio.js", "js/libs/library_godot_display.js", - "js/libs/library_godot_http_request.js", + "js/libs/library_godot_fetch.js", "js/libs/library_godot_os.js", "js/libs/library_godot_runtime.js", ] ) -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. @@ -86,40 +85,6 @@ wrap_list = [ ] js_wrapped = env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFILESUFFIX="${PROGSUFFIX}.wrapped.js") -zip_dir = env.Dir("#bin/.javascript_zip") -binary_name = "godot.tools" if env["tools"] else "godot" -out_files = [ - zip_dir.File(binary_name + ".js"), - zip_dir.File(binary_name + ".wasm"), - zip_dir.File(binary_name + ".html"), - zip_dir.File(binary_name + ".audio.worklet.js"), -] -html_file = "#misc/dist/html/full-size.html" -if env["tools"]: - subst_dict = {"@GODOT_VERSION@": env.GetBuildVersion()} - html_file = env.Substfile( - target="#bin/godot${PROGSUFFIX}.html", source="#misc/dist/html/editor.html", SUBST_DICT=subst_dict - ) - -in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"] -if env["gdnative_enabled"]: - in_files.append(build[2]) # Runtime - out_files.append(zip_dir.File(binary_name + ".side.wasm")) -elif env["threads_enabled"]: - in_files.append(build[2]) # Worker - out_files.append(zip_dir.File(binary_name + ".worker.js")) - -if env["tools"]: - in_files.append("#misc/dist/html/logo.svg") - out_files.append(zip_dir.File("logo.svg")) - in_files.append("#icon.png") - out_files.append(zip_dir.File("favicon.png")) - -zip_files = env.InstallAs(out_files, in_files) -env.Zip( - "#bin/godot", - zip_files, - ZIPROOT=zip_dir, - ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}", - ZIPCOMSTR="Archiving $SOURCES as $TARGET", -) +# Extra will be the thread worker, or the GDNative side, or None +extra = build[2] if len(build) > 2 else None +env.CreateTemplateZip(js_wrapped, build[1], extra) diff --git a/platform/javascript/api/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/js/libs/library_godot_editor_tools.js b/platform/javascript/api/javascript_singleton.h index d7f1ad5ea1..1615efa87e 100644 --- a/platform/javascript/js/libs/library_godot_editor_tools.js +++ b/platform/javascript/api/javascript_singleton.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* library_godot_editor_tools.js */ +/* javascript_singleton.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,30 +28,41 @@ /* 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); - }, +#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: + GDCLASS(JavaScript, Object); + + static JavaScript *singleton; + +protected: + static void _bind_methods(); + +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(); }; -mergeInto(LibraryManager.library, GodotEditorTools); +#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..b35ccd087f 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_length()); + 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_length(); 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 4297088c09..da6adc4cd8 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -7,7 +7,7 @@ from emscripten_helpers import ( add_js_libraries, add_js_pre, add_js_externs, - get_build_version, + create_template_zip, ) from methods import get_compiler_version from SCons.Util import WhereIs @@ -53,6 +53,7 @@ def get_flags(): # in this platform. For the available networking methods, the browser # manages TLS. ("module_mbedtls_enabled", False), + ("vulkan", False), ] @@ -64,21 +65,21 @@ def configure(env): sys.exit(255) ## Build type - if env["target"] == "release": + if env["target"].startswith("release"): # Use -Os to prioritize optimizing for reduced file size. This is # particularly valuable for the web platform because it directly # decreases download time. # -Os reduces file size by around 5 MiB over -O3. -Oz only saves about # 100 KiB over -Os, which does not justify the negative impact on # run-time performance. - env.Append(CCFLAGS=["-Os"]) - env.Append(LINKFLAGS=["-Os"]) - elif env["target"] == "release_debug": - env.Append(CCFLAGS=["-Os"]) - env.Append(LINKFLAGS=["-Os"]) - env.Append(CPPDEFINES=["DEBUG_ENABLED"]) - # Retain function names for backtraces at the cost of file size. - env.Append(LINKFLAGS=["--profiling-funcs"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["-Os"]) + env.Append(LINKFLAGS=["-Os"]) + + if env["target"] == "release_debug": + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + # Retain function names for backtraces at the cost of file size. + env.Append(LINKFLAGS=["--profiling-funcs"]) else: # "debug" env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(CCFLAGS=["-O1", "-g"]) @@ -95,8 +96,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. @@ -147,12 +149,12 @@ def configure(env): env.AddMethod(add_js_pre, "AddJSPre") env.AddMethod(add_js_externs, "AddJSExterns") - # Add method for getting build version string. - env.AddMethod(get_build_version, "GetBuildVersion") - # Add method that joins/compiles our Engine files. env.AddMethod(create_engine_file, "CreateEngineFile") + # Add method for creating the final zip file + env.AddMethod(create_template_zip, "CreateTemplateZip") + # Closure compiler extern and support for ecmascript specs (const, let, etc). env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT6" @@ -174,7 +176,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 +230,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 +240,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 a605f22e16..cce1f8dca7 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -30,8 +30,8 @@ #include "platform/javascript/display_server_javascript.h" -#include "drivers/dummy/rasterizer_dummy.h" #include "platform/javascript/os_javascript.h" +#include "servers/rendering/rasterizer_dummy.h" #include <emscripten.h> #include <png.h> @@ -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) { @@ -189,19 +189,19 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E switch (p_event->button) { case DOM_BUTTON_LEFT: - ev->set_button_index(BUTTON_LEFT); + ev->set_button_index(MOUSE_BUTTON_LEFT); break; case DOM_BUTTON_MIDDLE: - ev->set_button_index(BUTTON_MIDDLE); + ev->set_button_index(MOUSE_BUTTON_MIDDLE); break; case DOM_BUTTON_RIGHT: - ev->set_button_index(BUTTON_RIGHT); + ev->set_button_index(MOUSE_BUTTON_RIGHT); break; case DOM_BUTTON_XBUTTON1: - ev->set_button_index(BUTTON_XBUTTON1); + ev->set_button_index(MOUSE_BUTTON_XBUTTON1); break; case DOM_BUTTON_XBUTTON2: - ev->set_button_index(BUTTON_XBUTTON2); + ev->set_button_index(MOUSE_BUTTON_XBUTTON2); break; default: return false; @@ -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(); } @@ -341,7 +341,7 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso Rect2 atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -364,7 +364,7 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - image = texture->get_data(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -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,19 +455,19 @@ 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(BUTTON_WHEEL_UP); + ev->set_button_index(MOUSE_BUTTON_WHEEL_UP); else if (p_event->deltaY > 0) - ev->set_button_index(BUTTON_WHEEL_DOWN); + ev->set_button_index(MOUSE_BUTTON_WHEEL_DOWN); else if (p_event->deltaX > 0) - ev->set_button_index(BUTTON_WHEEL_LEFT); + ev->set_button_index(MOUSE_BUTTON_WHEEL_LEFT); else if (p_event->deltaX < 0) - ev->set_button_index(BUTTON_WHEEL_RIGHT); + ev->set_button_index(MOUSE_BUTTON_WHEEL_RIGHT); else return false; @@ -536,6 +536,43 @@ bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { return godot_js_display_touchscreen_is_available(); } +// 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()) { + return; + } + // Call input_text + Variant event = String(p_text); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + ds->input_text_callback.call((const Variant **)&eventp, 1, ret, ce); + // Insert key right to reach position. + Input *input = Input::get_singleton(); + Ref<InputEventKey> k; + for (int i = 0; i < p_cursor; i++) { + k.instance(); + k->set_pressed(true); + k->set_echo(false); + k->set_keycode(KEY_RIGHT); + input->parse_input_event(k); + k.instance(); + k->set_pressed(false); + k->set_echo(false); + k->set_keycode(KEY_RIGHT); + input->parse_input_event(k); + } +} + +void DisplayServerJavaScript::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { + godot_js_display_vk_show(p_existing_text.utf8().get_data(), p_multiline, p_cursor_start, p_cursor_end); +} + +void DisplayServerJavaScript::virtual_keyboard_hide() { + godot_js_display_vk_hide(); +} + // Gamepad void DisplayServerJavaScript::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) { Input *input = Input::get_singleton(); @@ -564,7 +601,7 @@ void DisplayServerJavaScript::process_joypads() { // Buttons 6 and 7 in the standard mapping need to be // axis to be handled as JOY_AXIS_TRIGGER by Godot. if (s_standard && (b == 6 || b == 7)) { - Input::JoyAxis joy_axis; + Input::JoyAxisValue joy_axis; joy_axis.min = 0; joy_axis.value = value; int a = b == 6 ? JOY_AXIS_TRIGGER_LEFT : JOY_AXIS_TRIGGER_RIGHT; @@ -574,7 +611,7 @@ void DisplayServerJavaScript::process_joypads() { } } for (int a = 0; a < s_axes_num; a++) { - Input::JoyAxis joy_axis; + Input::JoyAxisValue joy_axis; joy_axis.min = -1; joy_axis.value = s_axes[a]; input->joy_axis(idx, a, joy_axis); @@ -683,7 +720,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive godot_js_config_canvas_id_get(canvas_id, 256); // Handle contextmenu, webglcontextlost - godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, p_mode == WINDOW_MODE_FULLSCREEN); + godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, p_mode == WINDOW_MODE_FULLSCREEN, OS::get_singleton()->is_hidpi_allowed() ? 1 : 0); // Check if it's windows. swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1; @@ -734,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. @@ -764,6 +801,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive godot_js_display_paste_cb(update_clipboard_callback); godot_js_display_drop_files_cb(drop_files_js_callback); godot_js_display_gamepad_cb(&DisplayServerJavaScript::gamepad_callback); + godot_js_display_vk_cb(&vk_input_text_callback); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event); } @@ -789,11 +827,11 @@ 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: - //case FEATURE_VIRTUAL_KEYBOARD: + case FEATURE_VIRTUAL_KEYBOARD: + return godot_js_display_vk_available() != 0; default: return false; } @@ -866,7 +904,7 @@ void DisplayServerJavaScript::window_set_input_event_callback(const Callable &p_ } void DisplayServerJavaScript::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { - input_text_callback = p_callable; // TODO unused... do I need this? + input_text_callback = p_callable; } void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index 47e25ab2a0..ece38f1a95 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -75,6 +75,8 @@ private: static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); + static void vk_input_text_callback(const char *p_text, int p_cursor); + static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data); static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data); @@ -135,6 +137,9 @@ public: int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override; + void virtual_keyboard_hide() override; + // windows Vector<DisplayServer::WindowID> get_window_list() const override; WindowID get_window_at_screen_position(const Point2i &p_position) const override; 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 d08555916b..ab98838e20 100644 --- a/platform/javascript/emscripten_helpers.py +++ b/platform/javascript/emscripten_helpers.py @@ -1,4 +1,4 @@ -import os +import os, json from SCons.Util import WhereIs @@ -15,13 +15,17 @@ def run_closure_compiler(target, source, env, for_signature): return " ".join(cmd) -def get_build_version(env): +def get_build_version(): import version name = "custom_build" if os.getenv("BUILD_NAME") != None: name = os.getenv("BUILD_NAME") - return "%d.%d.%d.%s.%s" % (version.major, version.minor, version.patch, version.status, name) + v = "%d.%d" % (version.major, version.minor) + if version.patch > 0: + v += ".%d" % version.patch + v += ".%s.%s" % (version.status, name) + return v def create_engine_file(env, target, source, externs): @@ -30,6 +34,85 @@ def create_engine_file(env, target, source, externs): return env.Textfile(target, [env.File(s) for s in source]) +def create_template_zip(env, js, wasm, extra): + binary_name = "godot.tools" if env["tools"] else "godot" + zip_dir = env.Dir("#bin/.javascript_zip") + in_files = [ + js, + wasm, + "#platform/javascript/js/libs/audio.worklet.js", + ] + out_files = [ + zip_dir.File(binary_name + ".js"), + zip_dir.File(binary_name + ".wasm"), + zip_dir.File(binary_name + ".audio.worklet.js"), + ] + # GDNative/Threads specific + if env["gdnative_enabled"]: + in_files.append(extra) # Runtime + out_files.append(zip_dir.File(binary_name + ".side.wasm")) + elif env["threads_enabled"]: + in_files.append(extra) # Worker + out_files.append(zip_dir.File(binary_name + ".worker.js")) + + service_worker = "#misc/dist/html/service-worker.js" + if env["tools"]: + # HTML + html = "#misc/dist/html/editor.html" + cache = [ + "godot.tools.html", + "offline.html", + "godot.tools.js", + "godot.tools.worker.js", + "godot.tools.audio.worklet.js", + "logo.svg", + "favicon.png", + ] + opt_cache = ["godot.tools.wasm"] + subst_dict = { + "@GODOT_VERSION@": get_build_version(), + "@GODOT_NAME@": "GodotEngine", + "@GODOT_CACHE@": json.dumps(cache), + "@GODOT_OPT_CACHE@": json.dumps(opt_cache), + "@GODOT_OFFLINE_PAGE@": "offline.html", + } + html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict) + in_files.append(html) + out_files.append(zip_dir.File(binary_name + ".html")) + # And logo/favicon + in_files.append("#misc/dist/html/logo.svg") + out_files.append(zip_dir.File("logo.svg")) + in_files.append("#icon.png") + out_files.append(zip_dir.File("favicon.png")) + # PWA + service_worker = env.Substfile( + target="#bin/godot${PROGSUFFIX}.service.worker.js", source=service_worker, SUBST_DICT=subst_dict + ) + in_files.append(service_worker) + out_files.append(zip_dir.File("service.worker.js")) + in_files.append("#misc/dist/html/manifest.json") + out_files.append(zip_dir.File("manifest.json")) + in_files.append("#misc/dist/html/offline.html") + out_files.append(zip_dir.File("offline.html")) + else: + # HTML + in_files.append("#misc/dist/html/full-size.html") + out_files.append(zip_dir.File(binary_name + ".html")) + in_files.append(service_worker) + out_files.append(zip_dir.File(binary_name + ".service.worker.js")) + in_files.append("#misc/dist/html/offline-export.html") + out_files.append(zip_dir.File("godot.offline.html")) + + zip_files = env.InstallAs(out_files, in_files) + env.Zip( + "#bin/godot", + zip_files, + ZIPROOT=zip_dir, + ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}", + ZIPCOMSTR="Archiving $SOURCES as $TARGET", + ) + + def add_js_libraries(env, libraries): env.Append(JS_LIBS=env.File(libraries)) diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 353cc49ef8..8ce294f31b 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 = EditorPaths::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 = EditorPaths::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; } - void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects); + Ref<Image> _get_project_icon() const { + Ref<Image> icon; + icon.instance(); + const String icon_path = String(GLOBAL_GET("application/config/icon")).strip_edges(); + if (icon_path.is_empty() || ImageLoader::load_image(icon_path, icon) != OK) { + return EditorNode::get_singleton()->get_editor_theme()->get_icon("DefaultProjectIcon", "EditorIcons")->get_image(); + } + return icon; + } + + Ref<Image> _get_project_splash() const { + Ref<Image> splash; + splash.instance(); + const String splash_path = String(GLOBAL_GET("application/boot_splash/image")).strip_edges(); + if (splash_path.is_empty() || ImageLoader::load_image(splash_path, splash) != OK) { + return Ref<Image>(memnew(Image(boot_splash_png))); + } + return splash; + } + + Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa); + void _replace_strings(Map<String, String> p_replaces, Vector<uint8_t> &r_template); + void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); + Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr); + Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); + Error _write_or_error(const uint8_t *p_content, int p_len, String p_path); static void _server_thread_poll(void *data); @@ -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) { - 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,27 +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_length()); + 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) { @@ -348,9 +644,19 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/export_icon"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::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 { @@ -416,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; } @@ -438,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); @@ -447,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()); @@ -456,111 +768,54 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese } } memdelete(da); + da = nullptr; - FileAccess *src_f = nullptr; - zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - unzFile pkg = unzOpen2(template_path.utf8().get_data(), &io); - - if (!pkg) { - EditorNode::get_singleton()->show_warning(TTR("Could not open template for export:") + "\n" + template_path); - return ERR_FILE_NOT_FOUND; - } - - if (unzGoToFirstFile(pkg) != UNZ_OK) { - EditorNode::get_singleton()->show_warning(TTR("Invalid export template:") + "\n" + template_path); - unzClose(pkg); - return ERR_FILE_CORRUPT; + // Extract templates. + error = _extract_template(template_path, base_dir, base_name, pwa); + if (error) { + return error; } - do { - //get filename - unz_file_info info; - char fname[16384]; - unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); - - String file = fname; - - Vector<uint8_t> data; - data.resize(info.uncompressed_size); - - //read - unzOpenCurrentFile(pkg); - unzReadCurrentFile(pkg, data.ptrw(), data.size()); - unzCloseCurrentFile(pkg); - - //write - - if (file == "godot.html") { - if (!custom_html.is_empty()) { - continue; - } - _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); - file = p_path.get_file(); - - } else if (file == "godot.js") { - file = p_path.get_file().get_basename() + ".js"; - - } else if (file == "godot.worker.js") { - file = p_path.get_file().get_basename() + ".worker.js"; - - } else if (file == "godot.side.wasm") { - file = p_path.get_file().get_basename() + ".side.wasm"; - - } else if (file == "godot.audio.worklet.js") { - file = p_path.get_file().get_basename() + ".audio.worklet.js"; - - } else if (file == "godot.wasm") { - file = p_path.get_file().get_basename() + ".wasm"; - } - - String dst = p_path.get_base_dir().plus_file(file); - FileAccess *f = FileAccess::open(dst, FileAccess::WRITE); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + dst); - unzClose(pkg); - return ERR_FILE_CANT_WRITE; - } - f->store_buffer(data.ptr(), data.size()); - memdelete(f); - - } while (unzGoToNextFile(pkg) == UNZ_OK); - unzClose(pkg); - - if (!custom_html.is_empty()) { - FileAccess *f = FileAccess::open(custom_html, FileAccess::READ); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not read custom HTML shell:") + "\n" + custom_html); - return ERR_FILE_CANT_READ; - } - Vector<uint8_t> buf; - buf.resize(f->get_len()); - f->get_buffer(buf.ptrw(), buf.size()); + // Parse generated file sizes (pck and wasm, to help show a meaningful loading bar). + Dictionary file_sizes; + FileAccess *f = nullptr; + f = FileAccess::open(pck_path, FileAccess::READ); + if (f) { + file_sizes[pck_path.get_file()] = (uint64_t)f->get_length(); memdelete(f); - _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); - - f = FileAccess::open(p_path, FileAccess::WRITE); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path); - return ERR_FILE_CANT_WRITE; - } - f->store_buffer(buf.ptr(), buf.size()); + f = nullptr; + } + f = FileAccess::open(base_path + ".wasm", FileAccess::READ); + if (f) { + file_sizes[base_name + ".wasm"] = (uint64_t)f->get_length(); memdelete(f); + f = nullptr; } - Ref<Image> splash; - const String splash_path = String(GLOBAL_GET("application/boot_splash/image")).strip_edges(); - if (!splash_path.is_empty()) { - splash.instance(); - const Error err = splash->load(splash_path); - if (err) { - EditorNode::get_singleton()->show_warning(TTR("Could not read boot splash image file:") + "\n" + splash_path + "\n" + TTR("Using default boot splash image.")); - splash.unref(); - } + // Read the HTML shell file (custom or from template). + const String html_path = custom_html.is_empty() ? base_path + ".html" : custom_html; + Vector<uint8_t> html; + f = FileAccess::open(html_path, FileAccess::READ); + if (!f) { + EditorNode::get_singleton()->show_warning(TTR("Could not read HTML shell:") + "\n" + html_path); + return ERR_FILE_CANT_READ; } - if (splash.is_null()) { - splash = Ref<Image>(memnew(Image(boot_splash_png))); + html.resize(f->get_length()); + 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; @@ -568,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; @@ -628,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 = EditorPaths::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 { @@ -655,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; @@ -714,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/godot_js.h b/platform/javascript/godot_js.h index 5aa8677a54..8927a83cb3 100644 --- a/platform/javascript/godot_js.h +++ b/platform/javascript/godot_js.h @@ -71,6 +71,7 @@ extern int godot_js_display_fullscreen_exit(); extern void godot_js_display_compute_position(int p_x, int p_y, int32_t *r_x, int32_t *r_y); extern void godot_js_display_window_title_set(const char *p_text); extern void godot_js_display_window_icon_set(const uint8_t *p_ptr, int p_len); +extern int godot_js_display_has_webgl(int p_version); // Display clipboard extern int godot_js_display_clipboard_set(const char *p_text); @@ -92,7 +93,14 @@ extern int godot_js_display_gamepad_sample_get(int p_idx, float r_btns[16], int3 extern void godot_js_display_notification_cb(void (*p_callback)(int p_notification), int p_enter, int p_exit, int p_in, int p_out); extern void godot_js_display_paste_cb(void (*p_callback)(const char *p_text)); extern void godot_js_display_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec)); -extern void godot_js_display_setup_canvas(int p_width, int p_height, int p_fullscreen); +extern void godot_js_display_setup_canvas(int p_width, int p_height, int p_fullscreen, int p_hidpi); + +// Display Virtual Keyboard +extern int godot_js_display_vk_available(); +extern void godot_js_display_vk_cb(void (*p_input)(const char *p_text, int p_cursor)); +extern void godot_js_display_vk_show(const char *p_text, int p_multiline, int p_start, int p_end); +extern void godot_js_display_vk_hide(); + #ifdef __cplusplus } #endif diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc index 2ef1ad5b83..6544d41c98 100644 --- a/platform/javascript/http_client.h.inc +++ b/platform/javascript/http_client.h.inc @@ -30,24 +30,22 @@ // HTTPClient's additional private members in the javascript platform -Error prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers); +Error make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len); +static void _parse_headers(int p_len, const char **p_headers, void *p_ref); -int xhr_id; -int read_limit = 4096; -int response_read_offset = 0; +int js_id = 0; +// 64 KiB by default (favors fast download speeds at the cost of memory usage). +int read_limit = 65536; Status status = STATUS_DISCONNECTED; String host; int port = -1; bool use_tls = false; -String username; -String password; int polled_response_code = 0; -String polled_response_header; -PackedByteArray polled_response; +Vector<String> response_headers; +Vector<uint8_t> response_buffer; #ifdef DEBUG_ENABLED -bool has_polled = false; uint64_t last_polling_frame = 0; #endif diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index 1136ea299d..a6cf4b0eb8 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -30,7 +30,38 @@ #include "core/io/http_client.h" -#include "http_request.h" +#ifdef __cplusplus +extern "C" { +#endif + +#include "stddef.h" + +typedef enum { + GODOT_JS_FETCH_STATE_REQUESTING = 0, + GODOT_JS_FETCH_STATE_BODY = 1, + GODOT_JS_FETCH_STATE_DONE = 2, + GODOT_JS_FETCH_STATE_ERROR = -1, +} godot_js_fetch_state_t; + +extern int godot_js_fetch_create(const char *p_method, const char *p_url, const char **p_headers, int p_headers_len, const uint8_t *p_body, int p_body_len); +extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_size, const char **p_headers, void *p_ref), void *p_ref); +extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size); +extern void godot_js_fetch_free(int p_id); +extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id); +extern int godot_js_fetch_body_length_get(int p_id); +extern int godot_js_fetch_http_status_get(int p_id); +extern int godot_js_fetch_is_chunked(int p_id); + +#ifdef __cplusplus +} +#endif + +void HTTPClient::_parse_headers(int p_len, const char **p_headers, void *p_ref) { + HTTPClient *client = static_cast<HTTPClient *>(p_ref); + for (int i = 0; i < p_len; i++) { + client->response_headers.push_back(String::utf8(p_headers[i])); + } +} Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { close(); @@ -74,7 +105,7 @@ Ref<StreamPeer> HTTPClient::get_connection() const { ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform."); } -Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers) { +Error HTTPClient::make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) { ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the HTML5 platform."); ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); @@ -83,46 +114,33 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER); String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url; - godot_xhr_reset(xhr_id); - godot_xhr_open(xhr_id, _methods[p_method], url.utf8().get_data(), - username.is_empty() ? nullptr : username.utf8().get_data(), - password.is_empty() ? nullptr : password.utf8().get_data()); - + Vector<CharString> keeper; + Vector<const char *> c_strings; for (int i = 0; i < p_headers.size(); i++) { - int header_separator = p_headers[i].find(": "); - ERR_FAIL_COND_V(header_separator < 0, ERR_INVALID_PARAMETER); - godot_xhr_set_request_header(xhr_id, - p_headers[i].left(header_separator).utf8().get_data(), - p_headers[i].right(header_separator + 2).utf8().get_data()); + keeper.push_back(p_headers[i].utf8()); + c_strings.push_back(keeper[i].get_data()); + } + if (js_id) { + godot_js_fetch_free(js_id); } - response_read_offset = 0; + js_id = godot_js_fetch_create(_methods[p_method], url.utf8().get_data(), c_strings.ptrw(), c_strings.size(), p_body, p_body_len); status = STATUS_REQUESTING; return OK; } Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) { - Error err = prepare_request(p_method, p_url, p_headers); - if (err != OK) - return err; if (p_body.is_empty()) { - godot_xhr_send(xhr_id, nullptr, 0); - } else { - godot_xhr_send(xhr_id, p_body.ptr(), p_body.size()); + return make_request(p_method, p_url, p_headers, nullptr, 0); } - return OK; + return make_request(p_method, p_url, p_headers, p_body.ptr(), p_body.size()); } Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) { - Error err = prepare_request(p_method, p_url, p_headers); - if (err != OK) - return err; if (p_body.is_empty()) { - godot_xhr_send(xhr_id, nullptr, 0); - } else { - const CharString cs = p_body.utf8(); - godot_xhr_send(xhr_id, cs.get_data(), cs.length()); + return make_request(p_method, p_url, p_headers, nullptr, 0); } - return OK; + const CharString cs = p_body.utf8(); + return make_request(p_method, p_url, p_headers, (const uint8_t *)cs.get_data(), cs.size() - 1); } void HTTPClient::close() { @@ -130,10 +148,13 @@ void HTTPClient::close() { port = -1; use_tls = false; status = STATUS_DISCONNECTED; - polled_response.resize(0); polled_response_code = 0; - polled_response_header = String(); - godot_xhr_reset(xhr_id); + response_headers.resize(0); + response_buffer.resize(0); + if (js_id) { + godot_js_fetch_free(js_id); + js_id = 0; + } } HTTPClient::Status HTTPClient::get_status() const { @@ -141,12 +162,11 @@ HTTPClient::Status HTTPClient::get_status() const { } bool HTTPClient::has_response() const { - return !polled_response_header.is_empty(); + return response_headers.size() > 0; } bool HTTPClient::is_response_chunked() const { - // TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream - return false; + return godot_js_fetch_is_chunked(js_id); } int HTTPClient::get_response_code() const { @@ -154,36 +174,42 @@ int HTTPClient::get_response_code() const { } Error HTTPClient::get_response_headers(List<String> *r_response) { - if (polled_response_header.is_empty()) + if (!response_headers.size()) { return ERR_INVALID_PARAMETER; - - Vector<String> header_lines = polled_response_header.split("\r\n", false); - for (int i = 0; i < header_lines.size(); ++i) { - r_response->push_back(header_lines[i]); } - polled_response_header = String(); + for (int i = 0; i < response_headers.size(); i++) { + r_response->push_back(response_headers[i]); + } + response_headers.clear(); return OK; } int HTTPClient::get_response_body_length() const { - return polled_response.size(); + return godot_js_fetch_body_length_get(js_id); } PackedByteArray HTTPClient::read_response_body_chunk() { ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); - int to_read = MIN(read_limit, polled_response.size() - response_read_offset); - PackedByteArray chunk; - chunk.resize(to_read); - memcpy(chunk.ptrw(), polled_response.ptr() + response_read_offset, to_read); - response_read_offset += to_read; - - if (response_read_offset == polled_response.size()) { - status = STATUS_CONNECTED; - polled_response.resize(0); - godot_xhr_reset(xhr_id); + if (response_buffer.size() != read_limit) { + response_buffer.resize(read_limit); + } + int read = godot_js_fetch_read_chunk(js_id, response_buffer.ptrw(), read_limit); + + // Check if the stream is over. + godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id); + if (state == GODOT_JS_FETCH_STATE_DONE) { + status = STATUS_DISCONNECTED; + } else if (state != GODOT_JS_FETCH_STATE_BODY) { + status = STATUS_CONNECTION_ERROR; } + PackedByteArray chunk; + if (!read) { + return chunk; + } + chunk.resize(read); + memcpy(chunk.ptrw(), response_buffer.ptr(), read); return chunk; } @@ -217,48 +243,48 @@ Error HTTPClient::poll() { return OK; case STATUS_CONNECTED: - case STATUS_BODY: return OK; + case STATUS_BODY: { + godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id); + if (state == GODOT_JS_FETCH_STATE_DONE) { + status = STATUS_DISCONNECTED; + } else if (state != GODOT_JS_FETCH_STATE_BODY) { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + return OK; + } + case STATUS_CONNECTION_ERROR: return ERR_CONNECTION_ERROR; case STATUS_REQUESTING: { #ifdef DEBUG_ENABLED - if (!has_polled) { - has_polled = true; - } else { - // forcing synchronous requests is not possible on the web - if (last_polling_frame == Engine::get_singleton()->get_process_frames()) { - WARN_PRINT("HTTPClient polled multiple times in one frame, " - "but request cannot progress more than once per " - "frame on the HTML5 platform."); - } + // forcing synchronous requests is not possible on the web + if (last_polling_frame == Engine::get_singleton()->get_process_frames()) { + WARN_PRINT("HTTPClient polled multiple times in one frame, " + "but request cannot progress more than once per " + "frame on the HTML5 platform."); } last_polling_frame = Engine::get_singleton()->get_process_frames(); #endif - polled_response_code = godot_xhr_get_status(xhr_id); - if (godot_xhr_get_ready_state(xhr_id) != XHR_READY_STATE_DONE) { + polled_response_code = godot_js_fetch_http_status_get(js_id); + godot_js_fetch_state_t js_state = godot_js_fetch_state_get(js_id); + if (js_state == GODOT_JS_FETCH_STATE_REQUESTING) { return OK; - } else if (!polled_response_code) { + } else if (js_state == GODOT_JS_FETCH_STATE_ERROR) { + // Fetch is in error state. + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + if (godot_js_fetch_read_headers(js_id, &_parse_headers, this)) { + // Failed to parse headers. status = STATUS_CONNECTION_ERROR; return ERR_CONNECTION_ERROR; } - status = STATUS_BODY; - - PackedByteArray bytes; - int len = godot_xhr_get_response_headers_length(xhr_id); - bytes.resize(len + 1); - - godot_xhr_get_response_headers(xhr_id, reinterpret_cast<char *>(bytes.ptrw()), len); - bytes.ptrw()[len] = 0; - - polled_response_header = String::utf8(reinterpret_cast<const char *>(bytes.ptr())); - - polled_response.resize(godot_xhr_get_response_length(xhr_id)); - godot_xhr_get_response(xhr_id, polled_response.ptrw(), polled_response.size()); break; } @@ -269,9 +295,8 @@ Error HTTPClient::poll() { } HTTPClient::HTTPClient() { - xhr_id = godot_xhr_new(); } HTTPClient::~HTTPClient() { - godot_xhr_free(xhr_id); + close(); } diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h deleted file mode 100644 index a9d6faade2..0000000000 --- a/platform/javascript/http_request.h +++ /dev/null @@ -1,73 +0,0 @@ -/*************************************************************************/ -/* http_request.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. */ -/*************************************************************************/ - -#ifndef HTTP_REQUEST_H -#define HTTP_REQUEST_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "stddef.h" - -typedef enum { - XHR_READY_STATE_UNSENT = 0, - XHR_READY_STATE_OPENED = 1, - XHR_READY_STATE_HEADERS_RECEIVED = 2, - XHR_READY_STATE_LOADING = 3, - XHR_READY_STATE_DONE = 4, -} godot_xhr_ready_state_t; - -extern int godot_xhr_new(); -extern void godot_xhr_reset(int p_xhr_id); -extern void godot_xhr_free(int p_xhr_id); - -extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = nullptr, const char *p_password = nullptr); - -extern void godot_xhr_set_request_header(int p_xhr_id, const char *p_header, const char *p_value); - -extern void godot_xhr_send(int p_xhr_id, const void *p_data, int p_len); -extern void godot_xhr_abort(int p_xhr_id); - -/* this is an HTTPClient::ResponseCode, not ::Status */ -extern int godot_xhr_get_status(int p_xhr_id); -extern godot_xhr_ready_state_t godot_xhr_get_ready_state(int p_xhr_id); - -extern int godot_xhr_get_response_headers_length(int p_xhr_id); -extern void godot_xhr_get_response_headers(int p_xhr_id, char *r_dst, int p_len); - -extern int godot_xhr_get_response_length(int p_xhr_id); -extern void godot_xhr_get_response(int p_xhr_id, void *r_dst, int p_len); - -#ifdef __cplusplus -} -#endif - -#endif /* HTTP_REQUEST_H */ 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/engine/config.js b/platform/javascript/js/engine/config.js index bba20dc360..6072782875 100644 --- a/platform/javascript/js/engine/config.js +++ b/platform/javascript/js/engine/config.js @@ -91,16 +91,34 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- */ args: [], /** + * When enabled, this will turn on experimental virtual keyboard support on mobile. + * + * @memberof EngineConfig + * @type {boolean} + * @default + */ + experimentalVK: false, + /** * @ignore * @type {Array.<string>} */ persistentPaths: ['/userfs'], /** * @ignore + * @type {boolean} + */ + persistentDrops: false, + /** + * @ignore * @type {Array.<string>} */ gdnativeLibs: [], /** + * @ignore + * @type {Array.<string>} + */ + fileSizes: [], + /** * A callback function for handling Godot's ``OS.execute`` calls. * * This is for example used in the Web Editor template to switch between project manager and editor, and for running the game. @@ -218,7 +236,10 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- this.locale = parse('locale', this.locale); this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy); this.persistentPaths = parse('persistentPaths', this.persistentPaths); + this.persistentDrops = parse('persistentDrops', this.persistentDrops); + this.experimentalVK = parse('experimentalVK', this.experimentalVK); this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs); + this.fileSizes = parse('fileSizes', this.fileSizes); this.args = parse('args', this.args); this.onExecute = parse('onExecute', this.onExecute); this.onExit = parse('onExit', this.onExit); @@ -227,10 +248,10 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- /** * @ignore * @param {string} loadPath - * @param {Promise} loadPromise + * @param {Response} response */ - Config.prototype.getModuleConfig = function (loadPath, loadPromise) { - let loader = loadPromise; + Config.prototype.getModuleConfig = function (loadPath, response) { + let r = response; return { 'print': this.onPrint, 'printErr': this.onPrintError, @@ -238,12 +259,17 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- 'noExitRuntime': true, 'dynamicLibraries': [`${loadPath}.side.wasm`], 'instantiateWasm': function (imports, onSuccess) { - loader.then(function (xhr) { - WebAssembly.instantiate(xhr.response, imports).then(function (result) { - onSuccess(result['instance'], result['module']); + function done(result) { + onSuccess(result['instance'], result['module']); + } + if (typeof (WebAssembly.instantiateStreaming) !== 'undefined') { + WebAssembly.instantiateStreaming(Promise.resolve(r), imports).then(done); + } else { + r.arrayBuffer().then(function (buffer) { + WebAssembly.instantiate(buffer, imports).then(done); }); - }); - loader = null; + } + r = null; return {}; }, 'locateFile': function (path) { @@ -296,6 +322,8 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- 'canvas': this.canvas, 'canvasResizePolicy': this.canvasResizePolicy, 'locale': locale, + 'persistentDrops': this.persistentDrops, + 'virtualKeyboard': this.experimentalVK, 'onExecute': this.onExecute, 'onExit': function (p_code) { cleanup(); // We always need to call the cleanup callback to free memory. diff --git a/platform/javascript/js/engine/engine.externs.js b/platform/javascript/js/engine/engine.externs.js index 1a94dd15ec..35a66a93ae 100644 --- a/platform/javascript/js/engine/engine.externs.js +++ b/platform/javascript/js/engine/engine.externs.js @@ -1,3 +1,4 @@ var Godot; var WebAssembly = {}; WebAssembly.instantiate = function(buffer, imports) {}; +WebAssembly.instantiateStreaming = function(response, imports) {}; diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js index c51955ed3d..17a8df9e29 100644 --- a/platform/javascript/js/engine/engine.js +++ b/platform/javascript/js/engine/engine.js @@ -35,14 +35,15 @@ const Engine = (function () { * Load the engine from the specified base path. * * @param {string} basePath Base path of the engine to load. + * @param {number=} [size=0] The file size if known. * @returns {Promise} A Promise that resolves once the engine is loaded. * * @function Engine.load */ - Engine.load = function (basePath) { + Engine.load = function (basePath, size) { if (loadPromise == null) { loadPath = basePath; - loadPromise = preloader.loadPromise(`${loadPath}.wasm`); + loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true); requestAnimationFrame(preloader.animateProgress); } return loadPromise; @@ -96,23 +97,31 @@ const Engine = (function () { initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.')); return initPromise; } - Engine.load(basePath); + Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]); } - preloader.setProgressFunc(this.config.onProgress); - let config = this.config.getModuleConfig(loadPath, loadPromise); const me = this; - initPromise = new Promise(function (resolve, reject) { - Godot(config).then(function (module) { - module['initFS'](me.config.persistentPaths).then(function (fs_err) { - me.rtenv = module; - if (me.config.unloadAfterInit) { - Engine.unload(); - } - resolve(); - config = null; + function doInit(promise) { + // Care! Promise chaining is bogus with old emscripten versions. + // This caused a regression with the Mono build (which uses an older emscripten version). + // Make sure to test that when refactoring. + return new Promise(function (resolve, reject) { + promise.then(function (response) { + const cloned = new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] }); + Godot(me.config.getModuleConfig(loadPath, cloned)).then(function (module) { + const paths = me.config.persistentPaths; + module['initFS'](paths).then(function (err) { + me.rtenv = module; + if (me.config.unloadAfterInit) { + Engine.unload(); + } + resolve(); + }); + }); }); }); - }); + } + preloader.setProgressFunc(this.config.onProgress); + initPromise = doInit(loadPromise); return initPromise; }, @@ -133,7 +142,7 @@ const Engine = (function () { * @returns {Promise} A Promise that resolves once the file is loaded. */ preloadFile: function (file, path) { - return preloader.preload(file, path); + return preloader.preload(file, path, this.config.fileSizes[file]); }, /** diff --git a/platform/javascript/js/engine/preloader.js b/platform/javascript/js/engine/preloader.js index ec34fb93f2..564c68d264 100644 --- a/platform/javascript/js/engine/preloader.js +++ b/platform/javascript/js/engine/preloader.js @@ -1,54 +1,62 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars - const loadXHR = function (resolve, reject, file, tracker, attempts) { - const xhr = new XMLHttpRequest(); + function getTrackedResponse(response, load_status) { + function onloadprogress(reader, controller) { + return reader.read().then(function (result) { + if (load_status.done) { + return Promise.resolve(); + } + if (result.value) { + controller.enqueue(result.value); + load_status.loaded += result.value.length; + } + if (!result.done) { + return onloadprogress(reader, controller); + } + load_status.done = true; + return Promise.resolve(); + }); + } + const reader = response.body.getReader(); + return new Response(new ReadableStream({ + start: function (controller) { + onloadprogress(reader, controller).then(function () { + controller.close(); + }); + }, + }), { headers: response.headers }); + } + + function loadFetch(file, tracker, fileSize, raw) { tracker[file] = { - total: 0, + total: fileSize || 0, loaded: 0, - final: false, + done: false, }; - xhr.onerror = function () { + return fetch(file).then(function (response) { + if (!response.ok) { + return Promise.reject(new Error(`Failed loading file '${file}'`)); + } + const tr = getTrackedResponse(response, tracker[file]); + if (raw) { + return Promise.resolve(tr); + } + return tr.arrayBuffer(); + }); + } + + function retry(func, attempts = 1) { + function onerror(err) { if (attempts <= 1) { - reject(new Error(`Failed loading file '${file}'`)); - } else { + return Promise.reject(err); + } + return new Promise(function (resolve, reject) { setTimeout(function () { - loadXHR(resolve, reject, file, tracker, attempts - 1); + retry(func, attempts - 1).then(resolve).catch(reject); }, 1000); - } - }; - xhr.onabort = function () { - tracker[file].final = true; - reject(new Error(`Loading file '${file}' was aborted.`)); - }; - xhr.onloadstart = function (ev) { - tracker[file].total = ev.total; - tracker[file].loaded = ev.loaded; - }; - xhr.onprogress = function (ev) { - tracker[file].loaded = ev.loaded; - tracker[file].total = ev.total; - }; - xhr.onload = function () { - if (xhr.status >= 400) { - if (xhr.status < 500 || attempts <= 1) { - reject(new Error(`Failed loading file '${file}': ${xhr.statusText}`)); - xhr.abort(); - } else { - setTimeout(function () { - loadXHR(resolve, reject, file, tracker, attempts - 1); - }, 1000); - } - } else { - tracker[file].final = true; - resolve(xhr); - } - }; - // Make request. - xhr.open('GET', file); - if (!file.endsWith('.js')) { - xhr.responseType = 'arraybuffer'; + }); } - xhr.send(); - }; + return func().catch(onerror); + } const DOWNLOAD_ATTEMPTS_MAX = 4; const loadingFiles = {}; @@ -63,7 +71,7 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un Object.keys(loadingFiles).forEach(function (file) { const stat = loadingFiles[file]; - if (!stat.final) { + if (!stat.done) { progressIsFinal = false; } if (!totalIsValid || stat.total === 0) { @@ -92,21 +100,19 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un progressFunc = callback; }; - this.loadPromise = function (file) { - return new Promise(function (resolve, reject) { - loadXHR(resolve, reject, file, loadingFiles, DOWNLOAD_ATTEMPTS_MAX); - }); + this.loadPromise = function (file, fileSize, raw = false) { + return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX); }; this.preloadedFiles = []; - this.preload = function (pathOrBuffer, destPath) { + this.preload = function (pathOrBuffer, destPath, fileSize) { let buffer = null; if (typeof pathOrBuffer === 'string') { const me = this; - return this.loadPromise(pathOrBuffer).then(function (xhr) { + return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) { me.preloadedFiles.push({ path: destPath || pathOrBuffer, - buffer: xhr.response, + buffer: buf, }); return Promise.resolve(); }); diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index 8e385e9176..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; @@ -238,6 +238,9 @@ const GodotAudioWorklet = { close: function () { return new Promise(function (resolve, reject) { + if (GodotAudioWorklet.promise === null) { + return; + } GodotAudioWorklet.promise.then(function () { GodotAudioWorklet.worklet.port.postMessage({ 'cmd': 'stop', @@ -247,7 +250,7 @@ const GodotAudioWorklet = { GodotAudioWorklet.worklet = null; GodotAudioWorklet.promise = null; resolve(); - }); + }).catch(function (err) { /* aborted? */ }); }); }, }, diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js index fdb5cc0ec2..91cab5eacc 100644 --- a/platform/javascript/js/libs/library_godot_display.js +++ b/platform/javascript/js/libs/library_godot_display.js @@ -192,33 +192,45 @@ const GodotDisplayDragDrop = { GodotDisplayDragDrop.promises = []; GodotDisplayDragDrop.pending_files = []; callback(drops); - const dirs = [DROP.substr(0, DROP.length - 1)]; - // Remove temporary files - files.forEach(function (file) { - FS.unlink(file); - let dir = file.replace(DROP, ''); - let idx = dir.lastIndexOf('/'); - while (idx > 0) { - dir = dir.substr(0, idx); - if (dirs.indexOf(DROP + dir) === -1) { - dirs.push(DROP + dir); - } - idx = dir.lastIndexOf('/'); - } - }); - // Remove dirs. - dirs.sort(function (a, b) { - const al = (a.match(/\//g) || []).length; - const bl = (b.match(/\//g) || []).length; - if (al > bl) { - return -1; - } else if (al < bl) { - return 1; + if (GodotConfig.persistent_drops) { + // Delay removal at exit. + GodotOS.atexit(function (resolve, reject) { + GodotDisplayDragDrop.remove_drop(files, DROP); + resolve(); + }); + } else { + GodotDisplayDragDrop.remove_drop(files, DROP); + } + }); + }, + + remove_drop: function (files, drop_path) { + const dirs = [drop_path.substr(0, drop_path.length - 1)]; + // Remove temporary files + files.forEach(function (file) { + FS.unlink(file); + let dir = file.replace(drop_path, ''); + let idx = dir.lastIndexOf('/'); + while (idx > 0) { + dir = dir.substr(0, idx); + if (dirs.indexOf(drop_path + dir) === -1) { + dirs.push(drop_path + dir); } - return 0; - }).forEach(function (dir) { - FS.rmdir(dir); - }); + idx = dir.lastIndexOf('/'); + } + }); + // Remove dirs. + dirs.sort(function (a, b) { + const al = (a.match(/\//g) || []).length; + const bl = (b.match(/\//g) || []).length; + if (al > bl) { + return -1; + } else if (al < bl) { + return 1; + } + return 0; + }).forEach(function (dir) { + FS.rmdir(dir); }); }, @@ -231,6 +243,105 @@ const GodotDisplayDragDrop = { }; mergeInto(LibraryManager.library, GodotDisplayDragDrop); +const GodotDisplayVK = { + + $GodotDisplayVK__deps: ['$GodotRuntime', '$GodotConfig', '$GodotDisplayListeners'], + $GodotDisplayVK__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayVK.clear(); resolve(); });', + $GodotDisplayVK: { + textinput: null, + textarea: null, + + available: function () { + return GodotConfig.virtual_keyboard && 'ontouchstart' in window; + }, + + init: function (input_cb) { + function create(what) { + const elem = document.createElement(what); + elem.style.display = 'none'; + elem.style.position = 'absolute'; + elem.style.zIndex = '-1'; + elem.style.background = 'transparent'; + elem.style.padding = '0px'; + elem.style.margin = '0px'; + elem.style.overflow = 'hidden'; + elem.style.width = '0px'; + elem.style.height = '0px'; + elem.style.border = '0px'; + elem.style.outline = 'none'; + elem.readonly = true; + elem.disabled = true; + GodotDisplayListeners.add(elem, 'input', function (evt) { + const c_str = GodotRuntime.allocString(elem.value); + input_cb(c_str, elem.selectionEnd); + GodotRuntime.free(c_str); + }, false); + GodotDisplayListeners.add(elem, 'blur', function (evt) { + elem.style.display = 'none'; + elem.readonly = true; + elem.disabled = true; + }, false); + GodotConfig.canvas.insertAdjacentElement('beforebegin', elem); + return elem; + } + GodotDisplayVK.textinput = create('input'); + GodotDisplayVK.textarea = create('textarea'); + GodotDisplayVK.updateSize(); + }, + show: function (text, multiline, start, end) { + if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) { + return; + } + if (GodotDisplayVK.textinput.style.display !== '' || GodotDisplayVK.textarea.style.display !== '') { + GodotDisplayVK.hide(); + } + GodotDisplayVK.updateSize(); + const elem = multiline ? GodotDisplayVK.textarea : GodotDisplayVK.textinput; + elem.readonly = false; + elem.disabled = false; + elem.value = text; + elem.style.display = 'block'; + elem.focus(); + elem.setSelectionRange(start, end); + }, + hide: function () { + if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) { + return; + } + [GodotDisplayVK.textinput, GodotDisplayVK.textarea].forEach(function (elem) { + elem.blur(); + elem.style.display = 'none'; + elem.value = ''; + }); + }, + updateSize: function () { + if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) { + return; + } + const rect = GodotConfig.canvas.getBoundingClientRect(); + function update(elem) { + elem.style.left = `${rect.left}px`; + elem.style.top = `${rect.top}px`; + elem.style.width = `${rect.width}px`; + elem.style.height = `${rect.height}px`; + } + update(GodotDisplayVK.textinput); + update(GodotDisplayVK.textarea); + }, + clear: function () { + if (GodotDisplayVK.textinput) { + GodotDisplayVK.textinput.remove(); + GodotDisplayVK.textinput = null; + } + if (GodotDisplayVK.textarea) { + GodotDisplayVK.textarea.remove(); + GodotDisplayVK.textarea = null; + } + }, + }, +}; +mergeInto(LibraryManager.library, GodotDisplayVK); + /* * Display server cursor helper. * Keeps track of cursor status and custom shapes. @@ -400,6 +511,10 @@ const GodotDisplayScreen = { $GodotDisplayScreen__deps: ['$GodotConfig', '$GodotOS', '$GL', 'emscripten_webgl_get_current_context'], $GodotDisplayScreen: { desired_size: [0, 0], + hidpi: true, + getPixelRatio: function () { + return GodotDisplayScreen.hidpi ? window.devicePixelRatio || 1 : 1; + }, isFullscreen: function () { const elem = document.fullscreenElement || document.mozFullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement; @@ -477,7 +592,7 @@ const GodotDisplayScreen = { } return 0; } - const scale = window.devicePixelRatio || 1; + const scale = GodotDisplayScreen.getPixelRatio(); if (isFullscreen || wantsFullWindow) { // We need to match screen size. width = window.innerWidth * scale; @@ -507,7 +622,7 @@ mergeInto(LibraryManager.library, GodotDisplayScreen); * Exposes all the functions needed by DisplayServer implementation. */ const GodotDisplay = { - $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop', '$GodotDisplayGamepads', '$GodotDisplayScreen'], + $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop', '$GodotDisplayGamepads', '$GodotDisplayScreen', '$GodotDisplayVK'], $GodotDisplay: { window_icon: '', findDPI: function () { @@ -555,7 +670,7 @@ const GodotDisplay = { godot_js_display_pixel_ratio_get__sig: 'f', godot_js_display_pixel_ratio_get: function () { - return window.devicePixelRatio || 1; + return GodotDisplayScreen.getPixelRatio(); }, godot_js_display_fullscreen_request__sig: 'i', @@ -576,12 +691,16 @@ const GodotDisplay = { godot_js_display_size_update__sig: 'i', godot_js_display_size_update: function () { - return GodotDisplayScreen.updateSize(); + const updated = GodotDisplayScreen.updateSize(); + if (updated) { + GodotDisplayVK.updateSize(); + } + return updated; }, godot_js_display_screen_size_get__sig: 'vii', godot_js_display_screen_size_get: function (width, height) { - const scale = window.devicePixelRatio || 1; + const scale = GodotDisplayScreen.getPixelRatio(); GodotRuntime.setHeapValue(width, window.screen.width * scale, 'i32'); GodotRuntime.setHeapValue(height, window.screen.height * scale, 'i32'); }, @@ -600,6 +719,17 @@ const GodotDisplay = { GodotRuntime.setHeapValue(r_y, (y - rect.y) * rh, 'i32'); }, + godot_js_display_has_webgl__sig: 'ii', + godot_js_display_has_webgl: function (p_version) { + if (p_version !== 1 && p_version !== 2) { + return false; + } + try { + return !!document.createElement('canvas').getContext(p_version === 2 ? 'webgl2' : 'webgl'); + } catch (e) { /* Not available */ } + return false; + }, + /* * Canvas */ @@ -671,7 +801,7 @@ const GodotDisplay = { document.head.appendChild(link); } const old_icon = GodotDisplay.window_icon; - const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); + const png = new Blob([GodotRuntime.heapSlice(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); GodotDisplay.window_icon = URL.createObjectURL(png); link.href = GodotDisplay.window_icon; if (old_icon) { @@ -711,7 +841,7 @@ const GodotDisplay = { const shape = GodotRuntime.parseString(p_shape); const old_shape = GodotDisplayCursor.cursors[shape]; if (p_len > 0) { - const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); + const png = new Blob([GodotRuntime.heapSlice(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); const url = URL.createObjectURL(png); GodotDisplayCursor.cursors[shape] = { url: url, @@ -739,7 +869,7 @@ const GodotDisplay = { const notif = [p_enter, p_exit, p_in, p_out]; ['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function (evt_name, idx) { GodotDisplayListeners.add(canvas, evt_name, function () { - func.bind(null, notif[idx]); + func(notif[idx]); }, true); }); }, @@ -776,8 +906,8 @@ const GodotDisplay = { GodotDisplayListeners.add(canvas, 'drop', GodotDisplayDragDrop.handler(dropFiles)); }, - godot_js_display_setup_canvas__sig: 'viii', - godot_js_display_setup_canvas: function (p_width, p_height, p_fullscreen) { + godot_js_display_setup_canvas__sig: 'viiii', + godot_js_display_setup_canvas: function (p_width, p_height, p_fullscreen, p_hidpi) { const canvas = GodotConfig.canvas; GodotDisplayListeners.add(canvas, 'contextmenu', function (ev) { ev.preventDefault(); @@ -786,6 +916,7 @@ const GodotDisplay = { alert('WebGL context lost, please reload the page'); // eslint-disable-line no-alert ev.preventDefault(); }, false); + GodotDisplayScreen.hidpi = !!p_hidpi; switch (GodotConfig.canvas_resize_policy) { case 0: // None GodotDisplayScreen.desired_size = [canvas.width, canvas.height]; @@ -800,12 +931,42 @@ const GodotDisplay = { canvas.style.left = 0; break; } + GodotDisplayScreen.updateSize(); if (p_fullscreen) { GodotDisplayScreen.requestFullscreen(); } }, /* + * Virtual Keyboard + */ + godot_js_display_vk_show__sig: 'viiii', + godot_js_display_vk_show: function (p_text, p_multiline, p_start, p_end) { + const text = GodotRuntime.parseString(p_text); + const start = p_start > 0 ? p_start : 0; + const end = p_end > 0 ? p_end : start; + GodotDisplayVK.show(text, p_multiline, start, end); + }, + + godot_js_display_vk_hide__sig: 'v', + godot_js_display_vk_hide: function () { + GodotDisplayVK.hide(); + }, + + godot_js_display_vk_available__sig: 'i', + godot_js_display_vk_available: function () { + return GodotDisplayVK.available(); + }, + + godot_js_display_vk_cb__sig: 'vi', + godot_js_display_vk_cb: function (p_input_cb) { + const input_cb = GodotRuntime.get_func(p_input_cb); + if (GodotDisplayVK.available()) { + GodotDisplayVK.init(input_cb); + } + }, + + /* * Gamepads */ godot_js_display_gamepad_cb__sig: 'vi', 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_fetch.js b/platform/javascript/js/libs/library_godot_fetch.js new file mode 100644 index 0000000000..de5ae2b1ae --- /dev/null +++ b/platform/javascript/js/libs/library_godot_fetch.js @@ -0,0 +1,247 @@ +/*************************************************************************/ +/* library_godot_fetch.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +const GodotFetch = { + $GodotFetch__deps: ['$GodotRuntime'], + $GodotFetch: { + + onread: function (id, result) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + if (result.value) { + obj.chunks.push(result.value); + } + obj.reading = false; + obj.done = result.done; + }, + + onresponse: function (id, response) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + let chunked = false; + response.headers.forEach(function (value, header) { + const v = value.toLowerCase().trim(); + const h = header.toLowerCase().trim(); + if (h === 'transfer-encoding' && v === 'chunked') { + chunked = true; + } + }); + obj.status = response.status; + obj.response = response; + obj.reader = response.body.getReader(); + obj.chunked = chunked; + }, + + onerror: function (id, err) { + GodotRuntime.error(err); + const obj = IDHandler.get(id); + if (!obj) { + return; + } + obj.error = err; + }, + + create: function (method, url, headers, body) { + const obj = { + request: null, + response: null, + reader: null, + error: null, + done: false, + reading: false, + status: 0, + chunks: [], + bodySize: -1, + }; + const id = IDHandler.add(obj); + const init = { + method: method, + headers: headers, + body: body, + }; + obj.request = fetch(url, init); + obj.request.then(GodotFetch.onresponse.bind(null, id)).catch(GodotFetch.onerror.bind(null, id)); + return id; + }, + + free: function (id) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + IDHandler.remove(id); + if (!obj.request) { + return; + } + // Try to abort + obj.request.then(function (response) { + response.abort(); + }).catch(function (e) { /* nothing to do */ }); + }, + + read: function (id) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + if (obj.reader && !obj.reading) { + if (obj.done) { + obj.reader = null; + return; + } + obj.reading = true; + obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id)); + } + }, + }, + + godot_js_fetch_create__sig: 'iii', + godot_js_fetch_create: function (p_method, p_url, p_headers, p_headers_size, p_body, p_body_size) { + const method = GodotRuntime.parseString(p_method); + const url = GodotRuntime.parseString(p_url); + const headers = GodotRuntime.parseStringArray(p_headers, p_headers_size); + const body = p_body_size ? GodotRuntime.heapSlice(HEAP8, p_body, p_body_size) : null; + return GodotFetch.create(method, url, headers.map(function (hv) { + const idx = hv.indexOf(':'); + if (idx <= 0) { + return []; + } + return [ + hv.slice(0, idx).trim(), + hv.slice(idx + 1).trim(), + ]; + }).filter(function (v) { + return v.length === 2; + }), body); + }, + + godot_js_fetch_state_get__sig: 'ii', + godot_js_fetch_state_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj) { + return -1; + } + if (obj.error) { + return -1; + } + if (!obj.response) { + return 0; + } + if (obj.reader) { + return 1; + } + if (obj.done) { + return 2; + } + return -1; + }, + + godot_js_fetch_http_status_get__sig: 'ii', + godot_js_fetch_http_status_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 0; + } + return obj.status; + }, + + godot_js_fetch_read_headers__sig: 'iii', + godot_js_fetch_read_headers: function (p_id, p_parse_cb, p_ref) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 1; + } + const cb = GodotRuntime.get_func(p_parse_cb); + const arr = []; + obj.response.headers.forEach(function (v, h) { + arr.push(`${h}:${v}`); + }); + const c_ptr = GodotRuntime.allocStringArray(arr); + cb(arr.length, c_ptr, p_ref); + GodotRuntime.freeStringArray(c_ptr, arr.length); + return 0; + }, + + godot_js_fetch_read_chunk__sig: 'ii', + godot_js_fetch_read_chunk: function (p_id, p_buf, p_buf_size) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 0; + } + let to_read = p_buf_size; + const chunks = obj.chunks; + while (to_read && chunks.length) { + const chunk = obj.chunks[0]; + if (chunk.length > to_read) { + GodotRuntime.heapCopy(HEAP8, chunk.slice(0, to_read), p_buf); + chunks[0] = chunk.slice(to_read); + to_read = 0; + } else { + GodotRuntime.heapCopy(HEAP8, chunk, p_buf); + to_read -= chunk.length; + chunks.pop(); + } + } + if (!chunks.length) { + GodotFetch.read(p_id); + } + return p_buf_size - to_read; + }, + + godot_js_fetch_body_length_get__sig: 'ii', + godot_js_fetch_body_length_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return -1; + } + return obj.bodySize; + }, + + godot_js_fetch_is_chunked__sig: 'ii', + godot_js_fetch_is_chunked: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return -1; + } + return obj.chunked ? 1 : 0; + }, + + godot_js_fetch_free__sig: 'vi', + godot_js_fetch_free: function (id) { + GodotFetch.free(id); + }, +}; + +autoAddDeps(GodotFetch, '$GodotFetch'); +mergeInto(LibraryManager.library, GodotFetch); diff --git a/platform/javascript/js/libs/library_godot_http_request.js b/platform/javascript/js/libs/library_godot_http_request.js deleted file mode 100644 index 7dc2a2df29..0000000000 --- a/platform/javascript/js/libs/library_godot_http_request.js +++ /dev/null @@ -1,142 +0,0 @@ -/*************************************************************************/ -/* http_request.js */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -const GodotHTTPRequest = { - $GodotHTTPRequest__deps: ['$GodotRuntime'], - $GodotHTTPRequest: { - requests: [], - - getUnusedRequestId: function () { - const idMax = GodotHTTPRequest.requests.length; - for (let potentialId = 0; potentialId < idMax; ++potentialId) { - if (GodotHTTPRequest.requests[potentialId] instanceof XMLHttpRequest) { - continue; - } - return potentialId; - } - GodotHTTPRequest.requests.push(null); - return idMax; - }, - - setupRequest: function (xhr) { - xhr.responseType = 'arraybuffer'; - }, - }, - - godot_xhr_new__sig: 'i', - godot_xhr_new: function () { - const newId = GodotHTTPRequest.getUnusedRequestId(); - GodotHTTPRequest.requests[newId] = new XMLHttpRequest(); - GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]); - return newId; - }, - - godot_xhr_reset__sig: 'vi', - godot_xhr_reset: function (xhrId) { - GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest(); - GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); - }, - - godot_xhr_free__sig: 'vi', - godot_xhr_free: function (xhrId) { - GodotHTTPRequest.requests[xhrId].abort(); - GodotHTTPRequest.requests[xhrId] = null; - }, - - godot_xhr_open__sig: 'viiiii', - godot_xhr_open: function (xhrId, method, url, p_user, p_password) { - const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null; - const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null; - GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password); - }, - - godot_xhr_set_request_header__sig: 'viii', - godot_xhr_set_request_header: function (xhrId, header, value) { - GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value)); - }, - - godot_xhr_send__sig: 'viii', - godot_xhr_send: function (xhrId, p_ptr, p_len) { - let data = null; - if (p_ptr && p_len) { - data = GodotRuntime.heapCopy(HEAP8, p_ptr, p_len); - } - GodotHTTPRequest.requests[xhrId].send(data); - }, - - godot_xhr_abort__sig: 'vi', - godot_xhr_abort: function (xhrId) { - GodotHTTPRequest.requests[xhrId].abort(); - }, - - godot_xhr_get_status__sig: 'ii', - godot_xhr_get_status: function (xhrId) { - return GodotHTTPRequest.requests[xhrId].status; - }, - - godot_xhr_get_ready_state__sig: 'ii', - godot_xhr_get_ready_state: function (xhrId) { - return GodotHTTPRequest.requests[xhrId].readyState; - }, - - godot_xhr_get_response_headers_length__sig: 'ii', - godot_xhr_get_response_headers_length: function (xhrId) { - const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); - return headers === null ? 0 : GodotRuntime.strlen(headers); - }, - - godot_xhr_get_response_headers__sig: 'viii', - godot_xhr_get_response_headers: function (xhrId, dst, len) { - const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); - if (str === null) { - return; - } - GodotRuntime.stringToHeap(str, dst, len); - }, - - godot_xhr_get_response_length__sig: 'ii', - godot_xhr_get_response_length: function (xhrId) { - const body = GodotHTTPRequest.requests[xhrId].response; - return body === null ? 0 : body.byteLength; - }, - - godot_xhr_get_response__sig: 'viii', - godot_xhr_get_response: function (xhrId, dst, len) { - let buf = GodotHTTPRequest.requests[xhrId].response; - if (buf === null) { - return; - } - buf = new Uint8Array(buf).subarray(0, len); - HEAPU8.set(buf, dst); - }, -}; - -autoAddDeps(GodotHTTPRequest, '$GodotHTTPRequest'); -mergeInto(LibraryManager.library, GodotHTTPRequest); diff --git a/platform/javascript/js/libs/library_godot_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 0f189b013c..7414b8cc47 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -59,6 +59,8 @@ const GodotConfig = { canvas: null, locale: 'en', canvas_resize_policy: 2, // Adaptive + virtual_keyboard: false, + persistent_drops: false, on_execute: null, on_exit: null, @@ -66,6 +68,8 @@ const GodotConfig = { GodotConfig.canvas_resize_policy = p_opts['canvasResizePolicy']; GodotConfig.canvas = p_opts['canvas']; GodotConfig.locale = p_opts['locale'] || GodotConfig.locale; + GodotConfig.virtual_keyboard = p_opts['virtualKeyboard']; + GodotConfig.persistent_drops = !!p_opts['persistentDrops']; GodotConfig.on_execute = p_opts['onExecute']; GodotConfig.on_exit = p_opts['onExit']; }, @@ -77,6 +81,8 @@ const GodotConfig = { GodotConfig.canvas = null; GodotConfig.locale = 'en'; GodotConfig.canvas_resize_policy = 2; + GodotConfig.virtual_keyboard = false; + GodotConfig.persistent_drops = false; GodotConfig.on_execute = null; GodotConfig.on_exit = null; }, @@ -298,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/js/libs/library_godot_runtime.js b/platform/javascript/js/libs/library_godot_runtime.js index 7e36ff8ab5..3da1ed8f06 100644 --- a/platform/javascript/js/libs/library_godot_runtime.js +++ b/platform/javascript/js/libs/library_godot_runtime.js @@ -72,11 +72,16 @@ const GodotRuntime = { return p_heap.subarray(p_ptr / bytes, p_ptr / bytes + p_len); }, - heapCopy: function (p_heap, p_ptr, p_len) { + heapSlice: function (p_heap, p_ptr, p_len) { const bytes = p_heap.BYTES_PER_ELEMENT; return p_heap.slice(p_ptr / bytes, p_ptr / bytes + p_len); }, + heapCopy: function (p_dst, p_src, p_ptr) { + const bytes = p_src.BYTES_PER_ELEMENT; + return p_dst.set(p_src, p_ptr / bytes); + }, + /* * Strings */ @@ -84,6 +89,15 @@ const GodotRuntime = { return UTF8ToString(p_ptr); // eslint-disable-line no-undef }, + parseStringArray: function (p_ptr, p_size) { + const strings = []; + const ptrs = GodotRuntime.heapSub(HEAP32, p_ptr, p_size); // TODO wasm64 + ptrs.forEach(function (ptr) { + strings.push(GodotRuntime.parseString(ptr)); + }); + return strings; + }, + strlen: function (p_str) { return lengthBytesUTF8(p_str); // eslint-disable-line no-undef }, diff --git a/platform/javascript/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/SCsub b/platform/linuxbsd/SCsub index 46714e9502..1751d56e71 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -5,21 +5,27 @@ Import("env") from platform_methods import run_in_subprocess import platform_linuxbsd_builders -common_x11 = [ +common_linuxbsd = [ "crash_handler_linuxbsd.cpp", "os_linuxbsd.cpp", "joypad_linux.cpp", - "context_gl_x11.cpp", - "detect_prime_x11.cpp", - "display_server_x11.cpp", - "vulkan_context_x11.cpp", - "key_mapping_x11.cpp", ] +if "x11" in env and env["x11"]: + common_linuxbsd += [ + "context_gl_x11.cpp", + "detect_prime_x11.cpp", + "display_server_x11.cpp", + "key_mapping_x11.cpp", + ] + +if "vulkan" in env and env["vulkan"]: + common_linuxbsd.append("vulkan_context_x11.cpp") + if "udev" in env and env["udev"]: - common_x11.append("libudev-so_wrap.c") + common_linuxbsd.append("libudev-so_wrap.c") -prog = env.add_program("#bin/godot", ["godot_linuxbsd.cpp"] + common_x11) +prog = env.add_program("#bin/godot", ["godot_linuxbsd.cpp"] + common_linuxbsd) if env["debug_symbols"] and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_linuxbsd_builders.make_debug_linuxbsd)) diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 8f1afe0e66..1487210174 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -67,11 +67,13 @@ 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("pulseaudio", "Detect and use PulseAudio", True), BoolVariable("udev", "Use udev for gamepad connection callbacks", True), + BoolVariable("x11", "Enable X11 display", True), BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), BoolVariable("touch", "Enable touch events", True), @@ -89,7 +91,7 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O3"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) if env["debug_symbols"]: @@ -98,7 +100,7 @@ def configure(env): elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) @@ -142,15 +144,27 @@ def configure(env): env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"]) env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"]) - if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"]: + if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"] or env["use_msan"]: env.extra_suffix += "s" if env["use_ubsan"]: - env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append( + CCFLAGS=[ + "-fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin" + ] + ) env.Append(LINKFLAGS=["-fsanitize=undefined"]) + if env["use_llvm"]: + env.Append( + CCFLAGS=[ + "-fsanitize=nullability-return,nullability-arg,function,nullability-assign,implicit-integer-sign-change" + ] + ) + else: + env.Append(CCFLAGS=["-fsanitize=bounds-strict"]) if env["use_asan"]: - env.Append(CCFLAGS=["-fsanitize=address"]) + env.Append(CCFLAGS=["-fsanitize=address,pointer-subtract,pointer-compare"]) env.Append(LINKFLAGS=["-fsanitize=address"]) if env["use_lsan"]: @@ -161,6 +175,12 @@ def configure(env): env.Append(CCFLAGS=["-fsanitize=thread"]) env.Append(LINKFLAGS=["-fsanitize=thread"]) + if env["use_msan"] and env["use_llvm"]: + env.Append(CCFLAGS=["-fsanitize=memory"]) + env.Append(CCFLAGS=["-fsanitize-memory-track-origins"]) + env.Append(CCFLAGS=["-fsanitize-recover=memory"]) + env.Append(LINKFLAGS=["-fsanitize=memory"]) + if env["use_lto"]: if not env["use_llvm"] and env.GetOption("num_jobs") > 1: env.Append(CCFLAGS=["-flto"]) @@ -306,6 +326,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 @@ -339,17 +363,26 @@ def configure(env): env.ParseConfig("pkg-config zlib --cflags --libs") env.Prepend(CPPPATH=["#platform/linuxbsd"]) - env.Append(CPPDEFINES=["X11_ENABLED", "UNIX_ENABLED"]) - env.Append(CPPDEFINES=["VULKAN_ENABLED"]) - if not env["builtin_vulkan"]: - env.ParseConfig("pkg-config vulkan --cflags --libs") - if not env["builtin_glslang"]: - # No pkgconfig file for glslang so far - env.Append(LIBS=["glslang", "SPIRV"]) + if env["x11"]: + if not env["vulkan"]: + print("Error: X11 support requires vulkan=yes") + env.Exit(255) + env.Append(CPPDEFINES=["X11_ENABLED"]) + + env.Append(CPPDEFINES=["UNIX_ENABLED"]) + env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)]) + + if env["vulkan"]: + env.Append(CPPDEFINES=["VULKAN_ENABLED"]) + if not env["builtin_vulkan"]: + env.ParseConfig("pkg-config vulkan --cflags --libs") + if not env["builtin_glslang"]: + # No pkgconfig file for glslang so far + env.Append(LIBS=["glslang", "SPIRV"]) - # env.Append(CPPDEFINES=['OPENGL_ENABLED']) - env.Append(LIBS=["GL"]) + # env.Append(CPPDEFINES=['OPENGL_ENABLED']) + env.Append(LIBS=["GL"]) env.Append(LIBS=["pthread"]) @@ -389,10 +422,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 fceeb82325..b50b5f3479 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -727,9 +727,9 @@ Point2i DisplayServerX11::screen_get_position(int p_screen) const { int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - if (p_screen >= count) { - return Point2i(0, 0); - } + + // Check if screen is valid + ERR_FAIL_INDEX_V(p_screen, count, Point2i(0, 0)); Point2i position = Point2i(xsi[p_screen].x_org, xsi[p_screen].y_org); @@ -758,9 +758,9 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); - if (p_screen >= count) { - return Rect2i(0, 0, 0, 0); - } + + // Check if screen is valid + ERR_FAIL_INDEX_V(p_screen, count, Rect2i(0, 0, 0, 0)); Rect2i rect = Rect2i(xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height); XFree(xsi); @@ -1041,11 +1041,13 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - int count = get_screen_count(); - if (p_screen >= count) { - return; + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); } + // Check if screen is valid + ERR_FAIL_INDEX(p_screen, get_screen_count()); + if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) { Point2i position = screen_get_position(p_screen); Size2i size = screen_get_size(p_screen); @@ -1915,7 +1917,7 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape Rect2i atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -1938,7 +1940,7 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - image = texture->get_data(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -2010,7 +2012,7 @@ int DisplayServerX11::keyboard_get_layout_count() const { XkbGetNames(x11_display, XkbSymbolsNameMask, kbd); const Atom *groups = kbd->names->groups; - if (kbd->ctrls != NULL) { + if (kbd->ctrls != nullptr) { _group_count = kbd->ctrls->num_groups; } else { while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { @@ -2044,7 +2046,7 @@ String DisplayServerX11::keyboard_get_layout_language(int p_index) const { int _group_count = 0; const Atom *groups = kbd->names->groups; - if (kbd->ctrls != NULL) { + if (kbd->ctrls != nullptr) { _group_count = kbd->ctrls->num_groups; } else { while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { @@ -2083,7 +2085,7 @@ String DisplayServerX11::keyboard_get_layout_name(int p_index) const { int _group_count = 0; const Atom *groups = kbd->names->groups; - if (kbd->ctrls != NULL) { + if (kbd->ctrls != nullptr) { _group_count = kbd->ctrls->num_groups; } else { while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) { @@ -2164,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) { @@ -2279,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); @@ -2407,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); } } @@ -2604,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; @@ -2682,7 +2683,7 @@ bool DisplayServerX11::_wait_for_events() const { tv.tv_sec = 1; // Wait for next event or timeout. - int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); + int num_ready_fds = select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv); if (num_ready_fds > 0) { // Event received. @@ -3160,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); } @@ -3296,7 +3297,7 @@ void DisplayServerX11::process_events() { if (xi.pressure_supported) { mm->set_pressure(xi.pressure); } else { - mm->set_pressure((mouse_get_button_state() & (1 << (BUTTON_LEFT - 1))) ? 1.0f : 0.0f); + mm->set_pressure((mouse_get_button_state() & (1 << (MOUSE_BUTTON_LEFT - 1))) ? 1.0f : 0.0f); } mm->set_tilt(xi.tilt); @@ -3831,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 @@ -4028,7 +4027,10 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode use_prime = 0; } - if (getenv("LD_LIBRARY_PATH")) { + // Some tools use fake libGL libraries and have them override the real one using + // LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its + // runtime and includes system `/lib` and `/lib64`... so ignore Steam. + if (use_prime == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) { String ld_library_path(getenv("LD_LIBRARY_PATH")); Vector<String> libraries = ld_library_path.split(":"); diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 471259e50f..e8f4352dff 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -62,7 +62,7 @@ void JoypadLinux::Joypad::reset() { dpad = 0; fd = -1; - Input::JoyAxis jx; + Input::JoyAxisValue jx; jx.min = -1; jx.value = 0.0f; for (int i = 0; i < MAX_ABS; i++) { @@ -178,17 +178,18 @@ void JoypadLinux::monitor_joypads(udev *p_udev) { select() ensured that this will not block. */ dev = udev_monitor_receive_device(mon); - if (dev && udev_device_get_devnode(dev) != 0) { + if (dev && udev_device_get_devnode(dev) != nullptr) { MutexLock lock(joy_mutex); String action = udev_device_get_action(dev); const char *devnode = udev_device_get_devnode(dev); if (devnode) { String devnode_str = devnode; if (devnode_str.find(ignore_str) == -1) { - if (action == "add") + if (action == "add") { open_joypad(devnode); - else if (String(action) == "remove") + } else if (String(action) == "remove") { close_joypad(get_joy_from_path(devnode)); + } } } @@ -212,7 +213,7 @@ void JoypadLinux::monitor_joypads() { struct dirent *current; char fname[64]; - while ((current = readdir(input_directory)) != NULL) { + while ((current = readdir(input_directory)) != nullptr) { if (strncmp(current->d_name, "event", 5) != 0) { continue; } @@ -428,10 +429,10 @@ void JoypadLinux::joypad_vibration_stop(int p_id, uint64_t p_timestamp) { joy.ff_effect_timestamp = p_timestamp; } -Input::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { +Input::JoyAxisValue JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const { int min = p_abs->minimum; int max = p_abs->maximum; - Input::JoyAxis jx; + Input::JoyAxisValue jx; if (min < 0) { jx.min = -1; @@ -513,7 +514,7 @@ void JoypadLinux::process_joypads() { return; } if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) { - Input::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value); + Input::JoyAxisValue value = axis_correct(joy->abs_info[ev.code], ev.value); joy->curr_axis[joy->abs_map[ev.code]] = value; } break; diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index b0d0db047b..177d7a51ce 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -53,7 +53,7 @@ private: }; struct Joypad { - Input::JoyAxis curr_axis[MAX_ABS]; + Input::JoyAxisValue curr_axis[MAX_ABS]; int key_map[MAX_KEY]; int abs_map[MAX_ABS]; int dpad = 0; @@ -97,7 +97,7 @@ private: void joypad_vibration_start(int p_id, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop(int p_id, uint64_t p_timestamp); - Input::JoyAxis axis_correct(const input_absinfo *p_abs, int p_value) const; + Input::JoyAxisValue axis_correct(const input_absinfo *p_abs, int p_value) const; }; #endif diff --git a/platform/linuxbsd/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 acea00c5ac..317e79d0ea 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -34,8 +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_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), + BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN)", False), + BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), ] @@ -49,7 +49,7 @@ def configure(env): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) if env["arch"] != "arm64": env.Prepend(CCFLAGS=["-msse2"]) @@ -60,7 +60,7 @@ def configure(env): elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size + elif env["optimize"] == "size": # optimize for size env.Prepend(CCFLAGS=["-Os"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) if env["debug_symbols"]: @@ -135,11 +135,16 @@ def configure(env): env.extra_suffix += "s" if env["use_ubsan"]: - env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append( + CCFLAGS=[ + "-fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin" + ] + ) env.Append(LINKFLAGS=["-fsanitize=undefined"]) + env.Append(CCFLAGS=["-fsanitize=nullability-return,nullability-arg,function,nullability-assign"]) if env["use_asan"]: - env.Append(CCFLAGS=["-fsanitize=address"]) + env.Append(CCFLAGS=["-fsanitize=address,pointer-subtract,pointer-compare"]) env.Append(LINKFLAGS=["-fsanitize=address"]) if env["use_tsan"]: diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index ed7d89009f..f53b60891f 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 { @@ -829,8 +833,8 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i mb->set_position(pos); mb->set_global_position(pos); mb->set_button_mask(DS_OSX->last_button_state); - if (index == BUTTON_LEFT && pressed) { - mb->set_doubleclick([event clickCount] == 2); + if (index == MOUSE_BUTTON_LEFT && pressed) { + mb->set_double_click([event clickCount] == 2); } Input::get_singleton()->accumulate_input_event(mb); @@ -842,10 +846,10 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i if (([event modifierFlags] & NSEventModifierFlagControl)) { wd.mouse_down_control = true; - _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MASK_RIGHT, true); } else { wd.mouse_down_control = false; - _mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MASK_LEFT, true); } } @@ -858,9 +862,9 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; if (wd.mouse_down_control) { - _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MASK_RIGHT, false); } else { - _mouseDownEvent(window_id, event, BUTTON_LEFT, BUTTON_MASK_LEFT, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MASK_LEFT, false); } } @@ -946,7 +950,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i } - (void)rightMouseDown:(NSEvent *)event { - _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MASK_RIGHT, true); } - (void)rightMouseDragged:(NSEvent *)event { @@ -954,16 +958,16 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i } - (void)rightMouseUp:(NSEvent *)event { - _mouseDownEvent(window_id, event, BUTTON_RIGHT, BUTTON_MASK_RIGHT, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MASK_RIGHT, false); } - (void)otherMouseDown:(NSEvent *)event { if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_MASK_MIDDLE, true); } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_XBUTTON1, MOUSE_BUTTON_MASK_XBUTTON1, true); } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, true); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_XBUTTON2, MOUSE_BUTTON_MASK_XBUTTON2, true); } else { return; } @@ -975,11 +979,11 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i - (void)otherMouseUp:(NSEvent *)event { if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(window_id, event, BUTTON_MIDDLE, BUTTON_MASK_MIDDLE, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_MASK_MIDDLE, false); } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(window_id, event, BUTTON_XBUTTON1, BUTTON_MASK_XBUTTON1, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_XBUTTON1, MOUSE_BUTTON_MASK_XBUTTON1, false); } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(window_id, event, BUTTON_XBUTTON2, BUTTON_MASK_XBUTTON2, false); + _mouseDownEvent(window_id, event, MOUSE_BUTTON_XBUTTON2, MOUSE_BUTTON_MASK_XBUTTON2, false); } else { return; } @@ -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, @@ -1558,10 +1562,10 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]); } else { if (fabs(deltaX)) { - sendScrollEvent(window_id, 0 > deltaX ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); + sendScrollEvent(window_id, 0 > deltaX ? MOUSE_BUTTON_WHEEL_RIGHT : MOUSE_BUTTON_WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); } if (fabs(deltaY)) { - sendScrollEvent(window_id, 0 < deltaY ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); + sendScrollEvent(window_id, 0 < deltaY ? MOUSE_BUTTON_WHEEL_UP : MOUSE_BUTTON_WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); } } } @@ -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) { @@ -3117,7 +3129,7 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape Rect2 atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -3140,7 +3152,7 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - image = texture->get_data(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -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_length(); 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 337cfd6808..6d995412ab 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -56,7 +56,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { void _make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data); Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path); - Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path); + Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path); Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); @@ -114,7 +114,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("pc"); r_features->push_back("s3tc"); - r_features->push_back("OSX"); + r_features->push_back("macOS"); } virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { @@ -155,11 +155,35 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); #ifdef OSX_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/replace_existing_signature"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/disable_library_validation"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/audio_input"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/camera"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/location"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/address_book"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/calendars"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/photos_library"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/apple_events"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_server"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_client"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_usb"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_bluetooth"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_downloads", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false)); @@ -191,7 +215,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, if ((p_source.ptr()[(i + 1) * 4 + p_ch] == cur) && (p_source.ptr()[(i + 2) * 4 + p_ch] == cur)) { if (buf_size > 0) { result.write[res_size++] = (uint8_t)(buf_size - 1); - copymem(&result.write[res_size], &buf, buf_size); + memcpy(&result.write[res_size], &buf, buf_size); res_size += buf_size; buf_size = 0; } @@ -217,7 +241,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, buf[buf_size++] = cur; if (buf_size == 128) { result.write[res_size++] = (uint8_t)(buf_size - 1); - copymem(&result.write[res_size], &buf, buf_size); + memcpy(&result.write[res_size], &buf, buf_size); res_size += buf_size; buf_size = 0; } @@ -225,7 +249,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, } else { buf[buf_size++] = cur; result.write[res_size++] = (uint8_t)(buf_size - 1); - copymem(&result.write[res_size], &buf, buf_size); + memcpy(&result.write[res_size], &buf, buf_size); res_size += buf_size; buf_size = 0; } @@ -235,7 +259,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, Vector<uint8_t> &p_source, int ofs = p_dest.size(); p_dest.resize(p_dest.size() + res_size); - copymem(&p_dest.write[ofs], result.ptr(), res_size); + memcpy(&p_dest.write[ofs], result.ptr(), res_size); } void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data) { @@ -277,7 +301,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ if (icon_infos[i].is_png) { // Encode PNG icon. it->create_from_image(copy); - String path = EditorSettings::get_singleton()->get_cache_dir().plus_file("icon.png"); + String path = EditorPaths::get_singleton()->get_cache_dir().plus_file("icon.png"); ResourceSaver::save(path, it); FileAccess *f = FileAccess::open(path, FileAccess::READ); @@ -288,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_length(); 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. @@ -314,7 +338,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ int len = data.size() - ofs; len = BSWAP32(len); - copymem(&data.write[ofs], icon_infos[i].name, 4); + memcpy(&data.write[ofs], icon_infos[i].name, 4); encode_uint32(len, &data.write[ofs + 4]); } @@ -329,7 +353,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ } len += 8; len = BSWAP32(len); - copymem(&data.write[ofs], icon_infos[i].mask_name, 4); + memcpy(&data.write[ofs], icon_infos[i].mask_name, 4); encode_uint32(len, &data.write[ofs + 4]); } } @@ -437,7 +461,7 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset return OK; } -Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) { #ifdef OSX_ENABLED List<String> args; @@ -449,9 +473,9 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese args.push_back("runtime"); } - if ((p_preset->get("codesign/entitlements") != "") && (p_path.get_extension() != "dmg")) { + if (p_path.get_extension() != "dmg") { args.push_back("--entitlements"); - args.push_back(p_preset->get("codesign/entitlements")); + args.push_back(p_ent_path); } PackedStringArray user_args = p_preset->get("codesign/custom_options"); @@ -463,10 +487,18 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese } args.push_back("-s"); - args.push_back(p_preset->get("codesign/identity")); + if (p_preset->get("codesign/identity") == "") { + args.push_back("-"); + } else { + args.push_back(p_preset->get("codesign/identity")); + } args.push_back("-v"); /* provide some more feedback */ + if (p_preset->get("codesign/replace_existing_signature")) { + args.push_back("-f"); + } + args.push_back(p_path); String str; @@ -578,7 +610,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // Create our application bundle. String tmp_app_dir_name = pkg_name + ".app"; - String tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name); + String tmp_app_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name); print_line("Exporting to " + tmp_app_path_name); Error err = OK; @@ -607,6 +639,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // Now process our template. bool found_binary = false; int total_size = 0; + Vector<String> dylibs_found; while (ret == UNZ_OK && err == OK) { bool is_execute = false; @@ -656,8 +689,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (iconpath.get_extension() == "icns") { FileAccess *icon = FileAccess::open(iconpath, FileAccess::READ); if (icon) { - data.resize(icon->get_len()); - icon->get_buffer(&data.write[0], icon->get_len()); + data.resize(icon->get_length()); + icon->get_buffer(&data.write[0], icon->get_length()); icon->close(); memdelete(icon); } @@ -678,14 +711,18 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ret = unzGoToNextFile(src_pkg_zip); continue; // skip } - file = file.replace("/data.mono.osx.64.release_debug/", "/data_" + pkg_name + "/"); + file = file.replace("/data.mono.osx.64.release_debug/", "/GodotSharp/"); } if (file.find("/data.mono.osx.64.release/") != -1) { if (p_debug) { ret = unzGoToNextFile(src_pkg_zip); continue; // skip } - file = file.replace("/data.mono.osx.64.release/", "/data_" + pkg_name + "/"); + file = file.replace("/data.mono.osx.64.release/", "/GodotSharp/"); + } + + if (file.ends_with(".dylib")) { + dylibs_found.push_back(file); } print_line("ADDING: " + file + " size: " + itos(data.size())); @@ -735,22 +772,162 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // See if we can code sign our new package. bool sign_enabled = p_preset->get("codesign/enable"); + String ent_path = p_preset->get("codesign/entitlements/custom_file"); + if (sign_enabled && (ent_path == "")) { + ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements"); + + FileAccess *ent_f = FileAccess::open(ent_path, FileAccess::WRITE); + if (ent_f) { + ent_f->store_line("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); + ent_f->store_line("<plist version=\"1.0\">"); + ent_f->store_line("<dict>"); + if ((bool)p_preset->get("codesign/entitlements/allow_jit_code_execution")) { + ent_f->store_line("<key>com.apple.security.cs.allow-jit</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/allow_unsigned_executable_memory")) { + ent_f->store_line("<key>com.apple.security.cs.allow-unsigned-executable-memory</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/allow_dyld_environment_variables")) { + ent_f->store_line("<key>com.apple.security.cs.allow-dyld-environment-variables</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/disable_library_validation")) { + ent_f->store_line("<key>com.apple.security.cs.disable-library-validation</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/audio_input")) { + ent_f->store_line("<key>com.apple.security.device.audio-input</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/camera")) { + ent_f->store_line("<key>com.apple.security.device.camera</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/location")) { + ent_f->store_line("<key>com.apple.security.personal-information.location</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/address_book")) { + ent_f->store_line("<key>com.apple.security.personal-information.addressbook</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/calendars")) { + ent_f->store_line("<key>com.apple.security.personal-information.calendars</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/photos_library")) { + ent_f->store_line("<key>com.apple.security.personal-information.photos-library</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/apple_events")) { + ent_f->store_line("<key>com.apple.security.automation.apple-events</key>"); + ent_f->store_line("<true/>"); + } + + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/enabled")) { + ent_f->store_line("<key>com.apple.security.app-sandbox</key>"); + ent_f->store_line("<true/>"); + + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/network_server")) { + ent_f->store_line("<key>com.apple.security.network.server</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/network_client")) { + ent_f->store_line("<key>com.apple.security.network.client</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/device_usb")) { + ent_f->store_line("<key>com.apple.security.device.usb</key>"); + ent_f->store_line("<true/>"); + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/device_bluetooth")) { + ent_f->store_line("<key>com.apple.security.device.bluetooth</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_downloads") == 1) { + ent_f->store_line("<key>com.apple.security.files.downloads.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_downloads") == 2) { + ent_f->store_line("<key>com.apple.security.files.downloads.read-write</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_pictures") == 1) { + ent_f->store_line("<key>com.apple.security.files.pictures.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_pictures") == 2) { + ent_f->store_line("<key>com.apple.security.files.pictures.read-write</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_music") == 1) { + ent_f->store_line("<key>com.apple.security.files.music.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_music") == 2) { + ent_f->store_line("<key>com.apple.security.files.music.read-write</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_movies") == 1) { + ent_f->store_line("<key>com.apple.security.files.movies.read-only</key>"); + ent_f->store_line("<true/>"); + } + if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_movies") == 2) { + ent_f->store_line("<key>com.apple.security.files.movies.read-write</key>"); + ent_f->store_line("<true/>"); + } + } + + ent_f->store_line("</dict>"); + ent_f->store_line("</plist>"); + + ent_f->close(); + memdelete(ent_f); + } else { + err = ERR_CANT_CREATE; + } + } + if (err == OK) { DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { - err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); + String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); + if (da->dir_exists(src_path)) { +#ifndef UNIX_ENABLED + WARN_PRINT("Relative symlinks are not supported, exported " + src_path.get_file() + " might be broken!"); +#endif + print_verbose("export framework: " + src_path + " -> " + tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file()); + err = da->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file()); + if (err == OK) { + err = da->copy_dir(src_path, tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(), -1, true); + } + } else { + print_verbose("export dylib: " + src_path + " -> " + tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file()); + err = da->copy(src_path, tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file()); + } if (err == OK && sign_enabled) { - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(), ent_path); } } memdelete(da); } + if (sign_enabled) { + for (int i = 0; i < dylibs_found.size(); i++) { + if (err == OK) { + err = _code_sign(p_preset, tmp_app_path_name + "/" + dylibs_found[i], ent_path); + } + } + } + if (err == OK && sign_enabled) { if (ep.step("Code signing bundle", 2)) { return ERR_SKIP; } - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name); + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name, ent_path); } if (export_format == "dmg") { @@ -766,7 +943,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (ep.step("Code signing DMG", 3)) { return ERR_SKIP; } - err = _code_sign(p_preset, p_path); + err = _code_sign(p_preset, p_path, ent_path); } } else { // Create ZIP. @@ -782,7 +959,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f); zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); - _zip_folder_recursive(zip, EditorSettings::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name); + _zip_folder_recursive(zip, EditorPaths::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name); zipClose(zip, nullptr); } @@ -816,7 +993,48 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String if (f == "." || f == "..") { continue; } - if (da->current_is_dir()) { + if (da->is_link(f)) { + OS::Time time = OS::get_singleton()->get_time(); + OS::Date date = OS::get_singleton()->get_date(); + + zip_fileinfo zipfi; + zipfi.tmz_date.tm_hour = time.hour; + zipfi.tmz_date.tm_mday = date.day; + zipfi.tmz_date.tm_min = time.min; + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_sec = time.sec; + zipfi.tmz_date.tm_year = date.year; + zipfi.dosDate = 0; + // 0120000: symbolic link type + // 0000755: permissions rwxr-xr-x + // 0000644: permissions rw-r--r-- + uint32_t _mode = 0120644; + zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); + zipfi.internal_fa = 0; + + zipOpenNewFileInZip4(p_zip, + p_folder.plus_file(f).utf8().get_data(), + &zipfi, + nullptr, + 0, + nullptr, + 0, + nullptr, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION, + 0, + -MAX_WBITS, + DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, + nullptr, + 0, + 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions + 0); + + String target = da->read_link(f); + zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); + zipCloseFileInZip(p_zip); + } else if (da->current_is_dir()) { _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name); } else { bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)); @@ -900,12 +1118,6 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset } bool sign_enabled = p_preset->get("codesign/enable"); - if (sign_enabled) { - if (p_preset->get("codesign/identity") == "") { - err += TTR("Codesign: identity not specified.") + "\n"; - valid = false; - } - } bool noto_enabled = p_preset->get("notarization/enable"); if (noto_enabled) { if (!sign_enabled) { diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index 0b6a0e20a6..b12526915f 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -433,8 +433,8 @@ void JoypadOSX::poll_joypads() const { } } -static const Input::JoyAxis axis_correct(int p_value, int p_min, int p_max) { - Input::JoyAxis jx; +static const Input::JoyAxisValue axis_correct(int p_value, int p_min, int p_max) { + Input::JoyAxisValue jx; if (p_min < 0) { jx.min = -1; if (p_value < 0) { diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 7b5daf5bfb..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/SCsub b/platform/server/SCsub deleted file mode 100644 index 15b9af4d25..0000000000 --- a/platform/server/SCsub +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python - -import sys - -Import("env") - -common_server = [ - "os_server.cpp", -] - -if sys.platform == "darwin": - common_server.append("#platform/osx/crash_handler_osx.mm") -else: - common_server.append("#platform/x11/crash_handler_x11.cpp") - -prog = env.add_program("#bin/godot_server", ["godot_server.cpp"] + common_server) diff --git a/platform/server/detect.py b/platform/server/detect.py deleted file mode 100644 index 5be7e81e7a..0000000000 --- a/platform/server/detect.py +++ /dev/null @@ -1,277 +0,0 @@ -import os -import platform -import sys - -# This file is mostly based on platform/x11/detect.py. -# If editing this file, make sure to apply relevant changes here too. - - -def is_active(): - return True - - -def get_name(): - return "Server" - - -def get_program_suffix(): - if sys.platform == "darwin": - return "osx" - return "linuxbsd" - - -def can_build(): - if os.name != "posix": - return False - - return True - - -def get_opts(): - from SCons.Variables import BoolVariable, EnumVariable - - return [ - BoolVariable("use_llvm", "Use the LLVM compiler", False), - BoolVariable("use_static_cpp", "Link libgcc and libstdc++ statically for better portability", True), - BoolVariable("use_coverage", "Test Godot coverage", False), - BoolVariable("use_ubsan", "Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)", False), - BoolVariable("use_asan", "Use LLVM/GCC compiler address sanitizer (ASAN))", False), - BoolVariable("use_lsan", "Use LLVM/GCC compiler leak sanitizer (LSAN))", False), - BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN))", False), - BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), - BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), - BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False), - ] - - -def get_flags(): - return [] - - -def configure(env): - - ## Build type - - if env["target"] == "release": - if env["optimize"] == "speed": # optimize for speed (default) - env.Prepend(CCFLAGS=["-O3"]) - else: # optimize for size - env.Prepend(CCFLAGS=["-Os"]) - - if env["debug_symbols"]: - env.Prepend(CCFLAGS=["-g2"]) - - elif env["target"] == "release_debug": - if env["optimize"] == "speed": # optimize for speed (default) - env.Prepend(CCFLAGS=["-O2"]) - else: # optimize for size - env.Prepend(CCFLAGS=["-Os"]) - env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) - - if env["debug_symbols"]: - env.Prepend(CCFLAGS=["-g2"]) - - elif env["target"] == "debug": - env.Prepend(CCFLAGS=["-g3"]) - env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) - env.Append(LINKFLAGS=["-rdynamic"]) - - ## Architecture - - is64 = sys.maxsize > 2 ** 32 - if env["bits"] == "default": - env["bits"] = "64" if is64 else "32" - - ## Compiler configuration - - if "CXX" in env and "clang" in os.path.basename(env["CXX"]): - # Convenience check to enforce the use_llvm overrides when CXX is clang(++) - env["use_llvm"] = True - - if env["use_llvm"]: - if "clang++" not in os.path.basename(env["CXX"]): - env["CC"] = "clang" - env["CXX"] = "clang++" - env.extra_suffix = ".llvm" + env.extra_suffix - env.Append(LIBS=["atomic"]) - - if env["use_coverage"]: - env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"]) - env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"]) - - if env["use_ubsan"] or env["use_asan"] or env["use_lsan"] or env["use_tsan"]: - env.extra_suffix += "s" - - if env["use_ubsan"]: - env.Append(CCFLAGS=["-fsanitize=undefined"]) - env.Append(LINKFLAGS=["-fsanitize=undefined"]) - - if env["use_asan"]: - env.Append(CCFLAGS=["-fsanitize=address"]) - 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"]) - - if env["use_lto"]: - env.Append(CCFLAGS=["-flto"]) - if not env["use_llvm"] and env.GetOption("num_jobs") > 1: - env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))]) - else: - env.Append(LINKFLAGS=["-flto"]) - if not env["use_llvm"]: - env["RANLIB"] = "gcc-ranlib" - env["AR"] = "gcc-ar" - - env.Append(CCFLAGS=["-pipe"]) - env.Append(LINKFLAGS=["-pipe"]) - - ## Dependencies - - # FIXME: Check for existence of the libs before parsing their flags with pkg-config - - # freetype depends on libpng and zlib, so bundling one of them while keeping others - # as shared libraries leads to weird issues - if ( - env["builtin_freetype"] - or env["builtin_libpng"] - or env["builtin_zlib"] - or env["builtin_graphite"] - or env["builtin_harfbuzz"] - ): - env["builtin_freetype"] = True - env["builtin_libpng"] = True - env["builtin_zlib"] = True - env["builtin_graphite"] = True - env["builtin_harfbuzz"] = True - - if not env["builtin_freetype"]: - env.ParseConfig("pkg-config freetype2 --cflags --libs") - - if not env["builtin_graphite"]: - env.ParseConfig("pkg-config graphite2 --cflags --libs") - - if not env["builtin_icu"]: - env.ParseConfig("pkg-config icu-uc --cflags --libs") - - if not env["builtin_harfbuzz"]: - env.ParseConfig("pkg-config harfbuzz harfbuzz-icu --cflags --libs") - - if not env["builtin_libpng"]: - env.ParseConfig("pkg-config libpng16 --cflags --libs") - - if not env["builtin_bullet"]: - # We need at least version 2.89 - import subprocess - - bullet_version = subprocess.check_output(["pkg-config", "bullet", "--modversion"]).strip() - if str(bullet_version) < "2.89": - # Abort as system bullet was requested but too old - print( - "Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format( - bullet_version, "2.89" - ) - ) - sys.exit(255) - env.ParseConfig("pkg-config bullet --cflags --libs") - - if False: # not env['builtin_assimp']: - # FIXME: Add min version check - env.ParseConfig("pkg-config assimp --cflags --libs") - - if not env["builtin_enet"]: - env.ParseConfig("pkg-config libenet --cflags --libs") - - if not env["builtin_squish"]: - env.ParseConfig("pkg-config libsquish --cflags --libs") - - if not env["builtin_zstd"]: - env.ParseConfig("pkg-config libzstd --cflags --libs") - - # Sound and video libraries - # Keep the order as it triggers chained dependencies (ogg needed by others, etc.) - - if not env["builtin_libtheora"]: - env["builtin_libogg"] = False # Needed to link against system libtheora - env["builtin_libvorbis"] = False # Needed to link against system libtheora - env.ParseConfig("pkg-config theora theoradec --cflags --libs") - else: - list_of_x86 = ["x86_64", "x86", "i386", "i586"] - if any(platform.machine() in s for s in list_of_x86): - env["x86_libtheora_opt_gcc"] = True - - if not env["builtin_libvpx"]: - env.ParseConfig("pkg-config vpx --cflags --libs") - - if not env["builtin_libvorbis"]: - env["builtin_libogg"] = False # Needed to link against system libvorbis - env.ParseConfig("pkg-config vorbis vorbisfile --cflags --libs") - - if not env["builtin_opus"]: - env["builtin_libogg"] = False # Needed to link against system opus - env.ParseConfig("pkg-config opus opusfile --cflags --libs") - - if not env["builtin_libogg"]: - env.ParseConfig("pkg-config ogg --cflags --libs") - - if not env["builtin_libwebp"]: - env.ParseConfig("pkg-config libwebp --cflags --libs") - - if not env["builtin_mbedtls"]: - # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228 - env.Append(LIBS=["mbedtls", "mbedcrypto", "mbedx509"]) - - if not env["builtin_wslay"]: - env.ParseConfig("pkg-config libwslay --cflags --libs") - - if not env["builtin_miniupnpc"]: - # No pkgconfig file so far, hardcode default paths. - env.Prepend(CPPPATH=["/usr/include/miniupnpc"]) - env.Append(LIBS=["miniupnpc"]) - - # On Linux wchar_t should be 32-bits - # 16-bit library shouldn't be required due to compiler optimisations - if not env["builtin_pcre2"]: - env.ParseConfig("pkg-config libpcre2-32 --cflags --libs") - - ## Flags - - # Linkflags below this line should typically stay the last ones - if not env["builtin_zlib"]: - env.ParseConfig("pkg-config zlib --cflags --libs") - - env.Prepend(CPPPATH=["#platform/server"]) - env.Append(CPPDEFINES=["SERVER_ENABLED", "UNIX_ENABLED"]) - - if platform.system() == "Darwin": - env.Append( - LINKFLAGS=[ - "-framework", - "Cocoa", - "-framework", - "Carbon", - "-lz", - "-framework", - "IOKit", - ] - ) - - env.Append(LIBS=["pthread"]) - - if platform.system() == "Linux": - env.Append(LIBS=["dl"]) - - if platform.system().find("BSD") >= 0: - env["execinfo"] = True - - if env["execinfo"]: - env.Append(LIBS=["execinfo"]) - - # Link those statically for portability - if env["use_static_cpp"]: - env.Append(LINKFLAGS=["-static-libgcc", "-static-libstdc++"]) diff --git a/platform/server/logo.png b/platform/server/logo.png Binary files differdeleted file mode 100644 index 8666ada9ca..0000000000 --- a/platform/server/logo.png +++ /dev/null diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp deleted file mode 100644 index 852ec7c4ef..0000000000 --- a/platform/server/os_server.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/*************************************************************************/ -/* os_server.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. */ -/*************************************************************************/ - -#include "os_server.h" - -#include "core/string/print_string.h" -#include "drivers/dummy/rasterizer_dummy.h" -#include "drivers/dummy/texture_loader_dummy.h" -#include "servers/rendering/rendering_server_default.h" - -#include "main/main.h" - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> - -int OS_Server::get_video_driver_count() const { - return 1; -} - -const char *OS_Server::get_video_driver_name(int p_driver) const { - return "Dummy"; -} - -int OS_Server::get_current_video_driver() const { - return video_driver_index; -} - -void OS_Server::initialize_core() { - crash_handler.initialize(); - - OS_Unix::initialize_core(); -} - -Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - args = OS::get_singleton()->get_cmdline_args(); - current_videomode = p_desired; - main_loop = nullptr; - - RasterizerDummy::make_current(); - - video_driver_index = p_video_driver; // unused in server platform, but should still be initialized - - rendering_server = memnew(RenderingServerDefault); - rendering_server->init(); - - AudioDriverManager::initialize(p_audio_driver); - - input = memnew(InputDefault); - - _ensure_user_data_dir(); - - resource_loader_dummy.instance(); - ResourceLoader::add_resource_format_loader(resource_loader_dummy); - - return OK; -} - -void OS_Server::finalize() { - if (main_loop) - memdelete(main_loop); - main_loop = nullptr; - - rendering_server->finish(); - memdelete(rendering_server); - - memdelete(input); - - ResourceLoader::remove_resource_format_loader(resource_loader_dummy); - resource_loader_dummy.unref(); - - args.clear(); -} - -void OS_Server::set_mouse_show(bool p_show) { -} - -void OS_Server::set_mouse_grab(bool p_grab) { - grab = p_grab; -} - -bool OS_Server::is_mouse_grab_enabled() const { - return grab; -} - -int OS_Server::get_mouse_button_state() const { - return 0; -} - -Point2 OS_Server::get_mouse_position() const { - return Point2(); -} - -void OS_Server::set_window_title(const String &p_title) { -} - -void OS_Server::set_video_mode(const VideoMode &p_video_mode, int p_screen) { -} - -OS::VideoMode OS_Server::get_video_mode(int p_screen) const { - return current_videomode; -} - -Size2 OS_Server::get_window_size() const { - return Vector2(current_videomode.width, current_videomode.height); -} - -void OS_Server::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { -} - -MainLoop *OS_Server::get_main_loop() const { - return main_loop; -} - -void OS_Server::delete_main_loop() { - if (main_loop) - memdelete(main_loop); - main_loop = nullptr; -} - -void OS_Server::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; - input->set_main_loop(p_main_loop); -} - -String OS_Server::get_name() const { - return "Server"; -} - -void OS_Server::move_window_to_foreground() { -} - -bool OS_Server::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; -} - -void OS_Server::run() { - force_quit = false; - - if (!main_loop) - return; - - main_loop->init(); - - while (!force_quit) { - if (Main::iteration()) - break; - }; - - main_loop->finish(); -} - -String OS_Server::get_config_path() const { - if (has_environment("XDG_CONFIG_HOME")) { - return get_environment("XDG_CONFIG_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".config"); - } else { - return "."; - } -} - -String OS_Server::get_data_path() const { - if (has_environment("XDG_DATA_HOME")) { - return get_environment("XDG_DATA_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".local/share"); - } else { - return get_config_path(); - } -} - -String OS_Server::get_cache_path() const { - if (has_environment("XDG_CACHE_HOME")) { - return get_environment("XDG_CACHE_HOME"); - } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".cache"); - } else { - return get_config_path(); - } -} - -String OS_Server::get_system_dir(SystemDir p_dir) const { - String xdgparam; - - switch (p_dir) { - case SYSTEM_DIR_DESKTOP: { - xdgparam = "DESKTOP"; - } break; - case SYSTEM_DIR_DCIM: { - xdgparam = "PICTURES"; - - } break; - case SYSTEM_DIR_DOCUMENTS: { - xdgparam = "DOCUMENTS"; - - } break; - case SYSTEM_DIR_DOWNLOADS: { - xdgparam = "DOWNLOAD"; - - } break; - case SYSTEM_DIR_MOVIES: { - xdgparam = "VIDEOS"; - - } break; - case SYSTEM_DIR_MUSIC: { - xdgparam = "MUSIC"; - - } break; - case SYSTEM_DIR_PICTURES: { - xdgparam = "PICTURES"; - - } break; - case SYSTEM_DIR_RINGTONES: { - xdgparam = "MUSIC"; - - } break; - } - - String pipe; - List<String> arg; - arg.push_back(xdgparam); - Error err = const_cast<OS_Server *>(this)->execute("xdg-user-dir", arg, true, nullptr, &pipe); - if (err != OK) - return "."; - return pipe.strip_edges(); -} - -void OS_Server::disable_crash_handler() { - crash_handler.disable(); -} - -bool OS_Server::is_disable_crash_handler() const { - return crash_handler.is_disabled(); -} - -OS_Server::OS_Server() { - //adriver here - grab = false; -}; diff --git a/platform/server/os_server.h b/platform/server/os_server.h deleted file mode 100644 index 61025fa14b..0000000000 --- a/platform/server/os_server.h +++ /dev/null @@ -1,116 +0,0 @@ -/*************************************************************************/ -/* os_server.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. */ -/*************************************************************************/ - -#ifndef OS_SERVER_H -#define OS_SERVER_H - -#include "core/input/input.h" -#include "drivers/dummy/texture_loader_dummy.h" -#include "drivers/unix/os_unix.h" -#ifdef __APPLE__ -#include "platform/osx/crash_handler_osx.h" -#include "platform/osx/semaphore_osx.h" -#else -#include "platform/x11/crash_handler_linuxbsd.h" -#endif -#include "servers/audio_server.h" -#include "servers/rendering/renderer_compositor.h" -#include "servers/rendering_server.h" - -#undef CursorShape - -class OS_Server : public OS_Unix { - RenderingServer *rendering_server = nullptr; - VideoMode current_videomode; - List<String> args; - MainLoop *main_loop = nullptr; - - bool grab = false; - - virtual void delete_main_loop(); - - bool force_quit = false; - - InputDefault *input = nullptr; - - CrashHandler crash_handler; - - int video_driver_index = 0; - - Ref<ResourceFormatDummyTexture> resource_loader_dummy; - -protected: - virtual int get_video_driver_count() const; - virtual const char *get_video_driver_name(int p_driver) const; - virtual int get_current_video_driver() const; - - virtual void initialize_core(); - virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); - virtual void finalize(); - - virtual void set_main_loop(MainLoop *p_main_loop); - -public: - virtual String get_name() const; - - virtual void set_mouse_show(bool p_show); - virtual void set_mouse_grab(bool p_grab); - virtual bool is_mouse_grab_enabled() const; - virtual Point2 get_mouse_position() const; - virtual int get_mouse_button_state() const; - virtual void set_window_title(const String &p_title); - - virtual MainLoop *get_main_loop() const; - - virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); - virtual VideoMode get_video_mode(int p_screen = 0) const; - virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const; - - virtual Size2 get_window_size() const; - - virtual void move_window_to_foreground(); - - void run(); - - virtual bool _check_internal_feature_support(const String &p_feature); - - virtual String get_config_path() const; - virtual String get_data_path() const; - virtual String get_cache_path() const; - - virtual String get_system_dir(SystemDir p_dir) const; - - void disable_crash_handler(); - bool is_disable_crash_handler() const; - - OS_Server(); -}; - -#endif diff --git a/platform/server/platform_config.h b/platform/server/platform_config.h deleted file mode 100644 index 32a19d811b..0000000000 --- a/platform/server/platform_config.h +++ /dev/null @@ -1,49 +0,0 @@ -/*************************************************************************/ -/* platform_config.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. */ -/*************************************************************************/ - -#if defined(__linux__) || defined(__APPLE__) -#include <alloca.h> -#endif - -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) -#include <stdlib.h> // alloca -// FreeBSD and OpenBSD use pthread_set_name_np, while other platforms, -// include NetBSD, use pthread_setname_np. NetBSD's version however requires -// a different format, we handle this directly in thread_posix. -#ifdef __NetBSD__ -#define PTHREAD_NETBSD_SET_NAME -#else -#define PTHREAD_BSD_SET_NAME -#endif -#endif - -#ifdef __APPLE__ -#define PTHREAD_RENAME_SELF -#endif diff --git a/platform/uwp/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 dc4238bdd4..9d6ff10483 100644 --- a/platform/uwp/app.cpp +++ b/platform/uwp/app.cpp @@ -149,28 +149,28 @@ static int _get_button(Windows::UI::Input::PointerPoint ^ pt) { using namespace Windows::UI::Input; #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP - return BUTTON_LEFT; + return MOUSE_BUTTON_LEFT; #else switch (pt->Properties->PointerUpdateKind) { case PointerUpdateKind::LeftButtonPressed: case PointerUpdateKind::LeftButtonReleased: - return BUTTON_LEFT; + return MOUSE_BUTTON_LEFT; case PointerUpdateKind::RightButtonPressed: case PointerUpdateKind::RightButtonReleased: - return BUTTON_RIGHT; + return MOUSE_BUTTON_RIGHT; case PointerUpdateKind::MiddleButtonPressed: case PointerUpdateKind::MiddleButtonReleased: - return BUTTON_MIDDLE; + return MOUSE_BUTTON_MIDDLE; case PointerUpdateKind::XButton1Pressed: case PointerUpdateKind::XButton1Released: - return BUTTON_WHEEL_UP; + return MOUSE_BUTTON_WHEEL_UP; case PointerUpdateKind::XButton2Pressed: case PointerUpdateKind::XButton2Released: - return BUTTON_WHEEL_DOWN; + return MOUSE_BUTTON_WHEEL_DOWN; default: break; @@ -265,9 +265,9 @@ void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Cor if (p_is_wheel) { if (point->Properties->MouseWheelDelta > 0) { - mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_UP); + mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? MOUSE_BUTTON_WHEEL_RIGHT : MOUSE_BUTTON_WHEEL_UP); } else if (point->Properties->MouseWheelDelta < 0) { - mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? BUTTON_WHEEL_LEFT : BUTTON_WHEEL_DOWN); + mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? MOUSE_BUTTON_WHEEL_LEFT : MOUSE_BUTTON_WHEEL_DOWN); } } @@ -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/detect.py b/platform/uwp/detect.py index fda8fdec66..28922a4f59 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -54,16 +54,19 @@ def configure(env): ## Build type if env["target"] == "release": - env.Append(CCFLAGS=["/O2", "/GL"]) env.Append(CCFLAGS=["/MD"]) - env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS", "/LTCG"]) + env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["/O2", "/GL"]) + env.Append(LINKFLAGS=["/LTCG"]) elif env["target"] == "release_debug": - env.Append(CCFLAGS=["/O2", "/Zi"]) env.Append(CCFLAGS=["/MD"]) - env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["/O2", "/Zi"]) elif env["target"] == "debug": env.Append(CCFLAGS=["/Zi"]) diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 1aad2bfa1a..351aaa5957 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -567,12 +567,12 @@ void AppxPackager::finish() { // Create and add block map file EditorNode::progress_task_step("export", "Creating block map...", 4); - const String &tmp_blockmap_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml"); + const String &tmp_blockmap_file_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml"); make_block_map(tmp_blockmap_file_path); FileAccess *blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ); Vector<uint8_t> blockmap_buffer; - blockmap_buffer.resize(blockmap_file->get_len()); + blockmap_buffer.resize(blockmap_file->get_length()); blockmap_file->get_buffer(blockmap_buffer.ptrw(), blockmap_buffer.size()); @@ -585,12 +585,12 @@ void AppxPackager::finish() { EditorNode::progress_task_step("export", "Setting content types...", 5); - const String &tmp_content_types_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml"); + const String &tmp_content_types_file_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml"); make_content_types(tmp_content_types_file_path); FileAccess *types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ); Vector<uint8_t> types_buffer; - types_buffer.resize(types_file->get_len()); + types_buffer.resize(types_file->get_length()); types_file->get_buffer(types_buffer.ptrw(), types_buffer.size()); @@ -855,33 +855,33 @@ class EditorExportPlatformUWP : public EditorExportPlatform { Vector<uint8_t> _get_image_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) { Vector<uint8_t> data; - StreamTexture2D *image = nullptr; + StreamTexture2D *texture = nullptr; if (p_path.find("StoreLogo") != -1) { - image = p_preset->get("images/store_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/store_logo"))); + texture = p_preset->get("images/store_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/store_logo"))); } else if (p_path.find("Square44x44Logo") != -1) { - image = p_preset->get("images/square44x44_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square44x44_logo"))); + texture = p_preset->get("images/square44x44_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square44x44_logo"))); } else if (p_path.find("Square71x71Logo") != -1) { - image = p_preset->get("images/square71x71_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square71x71_logo"))); + texture = p_preset->get("images/square71x71_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square71x71_logo"))); } else if (p_path.find("Square150x150Logo") != -1) { - image = p_preset->get("images/square150x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square150x150_logo"))); + texture = p_preset->get("images/square150x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square150x150_logo"))); } else if (p_path.find("Square310x310Logo") != -1) { - image = p_preset->get("images/square310x310_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square310x310_logo"))); + texture = p_preset->get("images/square310x310_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/square310x310_logo"))); } else if (p_path.find("Wide310x150Logo") != -1) { - image = p_preset->get("images/wide310x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/wide310x150_logo"))); + texture = p_preset->get("images/wide310x150_logo").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/wide310x150_logo"))); } else if (p_path.find("SplashScreen") != -1) { - image = p_preset->get("images/splash_screen").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/splash_screen"))); + texture = p_preset->get("images/splash_screen").is_zero() ? nullptr : Object::cast_to<StreamTexture2D>(((Object *)p_preset->get("images/splash_screen"))); } else { ERR_PRINT("Unable to load logo"); } - if (!image) { + if (!texture) { return data; } - String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("uwp_tmp_logo.png"); + String tmp_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("uwp_tmp_logo.png"); - Error err = image->get_data()->save_png(tmp_path); + Error err = texture->get_image()->save_png(tmp_path); if (err != OK) { String err_string = "Couldn't save temp logo file."; @@ -900,7 +900,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform { ERR_FAIL_V_MSG(data, err_string); } - data.resize(f->get_len()); + data.resize(f->get_length()); f->get_buffer(data.ptrw(), data.size()); f->close(); @@ -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/joypad_uwp.cpp b/platform/uwp/joypad_uwp.cpp index 5da90db49d..b419fb4fae 100644 --- a/platform/uwp/joypad_uwp.cpp +++ b/platform/uwp/joypad_uwp.cpp @@ -134,8 +134,8 @@ void JoypadUWP::OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Inp input->joy_connection_changed(idx, false, "Xbox Controller"); } -InputDefault::JoyAxis JoypadUWP::axis_correct(double p_val, bool p_negate, bool p_trigger) const { - InputDefault::JoyAxis jx; +InputDefault::JoyAxisValue JoypadUWP::axis_correct(double p_val, bool p_negate, bool p_trigger) const { + InputDefault::JoyAxisValue jx; jx.min = p_trigger ? 0 : -1; jx.value = (float)(p_negate ? -p_val : p_val); diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h index 5df4a211ac..d760d9e2fe 100644 --- a/platform/uwp/joypad_uwp.h +++ b/platform/uwp/joypad_uwp.h @@ -73,7 +73,7 @@ private: void OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value); void OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value); - InputDefault::JoyAxis axis_correct(double p_val, bool p_negate = false, bool p_trigger = false) const; + InputDefault::JoyAxisValue axis_correct(double p_val, bool p_negate = false, bool p_trigger = false) const; void joypad_vibration_start(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop(int p_device, uint64_t p_timestamp); }; diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index fa97948395..435f829c9b 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -62,6 +62,8 @@ using namespace Windows::Devices::Sensors; using namespace Windows::ApplicationModel::DataTransfer; using namespace concurrency; +static const float earth_gravity = 9.80665; + int OS_UWP::get_video_driver_count() const { return 2; } @@ -145,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; } @@ -372,9 +374,9 @@ void OS_UWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sende AccelerometerReading ^ reading = args->Reading; os->input->set_accelerometer(Vector3( - reading->AccelerationX, - reading->AccelerationY, - reading->AccelerationZ)); + reading->AccelerationX * earth_gravity, + reading->AccelerationY * earth_gravity, + reading->AccelerationZ * earth_gravity)); } void OS_UWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, MagnetometerReadingChangedEventArgs ^ args) { @@ -563,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/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index e24e466f88..e2d507eddd 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -36,7 +36,7 @@ #ifdef CRASH_HANDLER_EXCEPTION -// Backtrace code code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app +// Backtrace code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app #include <algorithm> #include <iterator> diff --git a/platform/windows/detect.py b/platform/windows/detect.py index f26dea8d35..7772ba2dbe 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -72,6 +72,7 @@ def get_opts(): BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_thinlto", "Use ThinLTO", False), BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True), + BoolVariable("use_asan", "Use address sanitizer (ASAN)", False), ] @@ -190,18 +191,20 @@ def configure_msvc(env, manual_msvc_config): if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) - else: # optimize for size + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["/O1"]) + env.Append(LINKFLAGS=["/OPT:REF"]) env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"]) - env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) - else: # optimize for size + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["/O1"]) + env.Append(LINKFLAGS=["/OPT:REF"]) env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"]) - env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "debug": env.AppendUnique(CCFLAGS=["/Zi", "/FS", "/Od", "/EHsc"]) @@ -306,6 +309,12 @@ def configure_msvc(env, manual_msvc_config): env.Prepend(CPPPATH=[p for p in os.getenv("INCLUDE").split(";")]) env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")]) + # Sanitizers + if env["use_asan"]: + env.extra_suffix += ".s" + env.Append(LINKFLAGS=["/INFERASANLIBS"]) + env.Append(CCFLAGS=["/fsanitize=address"]) + # Incremental linking fix env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"] env["BUILDERS"]["Program"] = methods.precious_program diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index b9b78f7bd4..4dd3151eb3 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); @@ -1119,7 +1119,7 @@ bool DisplayServerWindows::window_can_draw(WindowID p_window) const { ERR_FAIL_COND_V(!windows.has(p_window), false); const WindowData &wd = windows[p_window]; - return wd.minimized; + return !wd.minimized; } bool DisplayServerWindows::can_any_window_draw() const { @@ -1253,12 +1253,12 @@ void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTra HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); - // Assign the monochrome AND mask bitmap pixels so that a pixels of the source bitmap + // Assign the monochrome AND mask bitmap pixels so that the pixels of the source bitmap // with 'clrTransparent' will be white pixels of the monochrome bitmap SetBkColor(hMainDC, clrTransparent); BitBlt(hAndMaskDC, 0, 0, bm.bmWidth, bm.bmHeight, hMainDC, 0, 0, SRCCOPY); - // Assign the color XOR mask bitmap pixels so that a pixels of the source bitmap + // Assign the color XOR mask bitmap pixels so that the pixels of the source bitmap // with 'clrTransparent' will be black and rest the pixels same as corresponding // pixels of the source bitmap SetBkColor(hXorMaskDC, RGB(0, 0, 0)); @@ -1299,7 +1299,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh Rect2 atlas_rect; if (texture.is_valid()) { - image = texture->get_data(); + image = texture->get_image(); } if (!image.is_valid() && atlas_texture.is_valid()) { @@ -1322,7 +1322,7 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - image = texture->get_data(); + image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); @@ -1419,13 +1419,13 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) { } int DisplayServerWindows::keyboard_get_layout_count() const { - return GetKeyboardLayoutList(0, NULL); + return GetKeyboardLayoutList(0, nullptr); } int DisplayServerWindows::keyboard_get_current_layout() const { HKL cur_layout = GetKeyboardLayout(0); - int layout_count = GetKeyboardLayoutList(0, NULL); + int layout_count = GetKeyboardLayoutList(0, nullptr); HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL)); GetKeyboardLayoutList(layout_count, layouts); @@ -1440,7 +1440,7 @@ int DisplayServerWindows::keyboard_get_current_layout() const { } void DisplayServerWindows::keyboard_set_current_layout(int p_index) { - int layout_count = GetKeyboardLayoutList(0, NULL); + int layout_count = GetKeyboardLayoutList(0, nullptr); ERR_FAIL_INDEX(p_index, layout_count); @@ -1451,7 +1451,7 @@ void DisplayServerWindows::keyboard_set_current_layout(int p_index) { } String DisplayServerWindows::keyboard_get_layout_language(int p_index) const { - int layout_count = GetKeyboardLayoutList(0, NULL); + int layout_count = GetKeyboardLayoutList(0, nullptr); ERR_FAIL_INDEX_V(p_index, layout_count, ""); @@ -1481,7 +1481,7 @@ String _get_full_layout_name_from_registry(HKL p_layout) { DWORD buffer = 1024; DWORD vtype = REG_SZ; - if (RegQueryValueExW(hkey, L"Layout Text", NULL, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) { + if (RegQueryValueExW(hkey, L"Layout Text", nullptr, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) { ret = String::utf16((const char16_t *)layout_text); } RegCloseKey(hkey); @@ -1489,7 +1489,7 @@ String _get_full_layout_name_from_registry(HKL p_layout) { } String DisplayServerWindows::keyboard_get_layout_name(int p_index) const { - int layout_count = GetKeyboardLayoutList(0, NULL); + int layout_count = GetKeyboardLayoutList(0, nullptr); ERR_FAIL_INDEX_V(p_index, layout_count, ""); @@ -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,12 +2300,12 @@ 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 update recently. + // Note: WinTab sends both WT_PACKET and WM_xBUTTONDOWN/UP/MOUSEMOVE events, use mouse 1/0 pressure only when last_pressure was not updated recently. if (windows[window_id].last_pressure_update < 10) { windows[window_id].last_pressure_update++; } else { @@ -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); @@ -2431,9 +2431,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA return 0; if (motion > 0) - mb->set_button_index(BUTTON_WHEEL_UP); + mb->set_button_index(MOUSE_BUTTON_WHEEL_UP); else - mb->set_button_index(BUTTON_WHEEL_DOWN); + mb->set_button_index(MOUSE_BUTTON_WHEEL_DOWN); } break; case WM_MOUSEHWHEEL: { @@ -2443,44 +2443,44 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA return 0; if (motion < 0) { - mb->set_button_index(BUTTON_WHEEL_LEFT); + mb->set_button_index(MOUSE_BUTTON_WHEEL_LEFT); mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA)); } else { - mb->set_button_index(BUTTON_WHEEL_RIGHT); + mb->set_button_index(MOUSE_BUTTON_WHEEL_RIGHT); mb->set_factor(fabs((double)motion / (double)WHEEL_DELTA)); } } break; case WM_XBUTTONDOWN: { mb->set_pressed(true); if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); + mb->set_button_index(MOUSE_BUTTON_XBUTTON1); else - mb->set_button_index(BUTTON_XBUTTON2); + mb->set_button_index(MOUSE_BUTTON_XBUTTON2); } break; case WM_XBUTTONUP: { mb->set_pressed(false); if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); + mb->set_button_index(MOUSE_BUTTON_XBUTTON1); else - mb->set_button_index(BUTTON_XBUTTON2); + mb->set_button_index(MOUSE_BUTTON_XBUTTON2); } break; case WM_XBUTTONDBLCLK: { mb->set_pressed(true); if (HIWORD(wParam) == XBUTTON1) - mb->set_button_index(BUTTON_XBUTTON1); + mb->set_button_index(MOUSE_BUTTON_XBUTTON1); else - mb->set_button_index(BUTTON_XBUTTON2); - mb->set_doubleclick(true); + mb->set_button_index(MOUSE_BUTTON_XBUTTON2); + mb->set_double_click(true); } break; default: { return 0; } } - 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 @@ -2566,6 +2566,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].preserve_window_size = false; window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id); } + } else { + windows[window_id].preserve_window_size = true; } if (!windows[window_id].rect_changed_callback.is_null()) { @@ -2833,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) @@ -2860,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); @@ -2898,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/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index f46a0dbe2e..da36dc1f2b 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -110,12 +110,11 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) { if (GetRawInputDeviceList(nullptr, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) { return false; } - dev_list = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count); - if (!dev_list) - return false; + dev_list = (PRAWINPUTDEVICELIST)memalloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count); + ERR_FAIL_NULL_V_MSG(dev_list, false, "Out of memory."); if (GetRawInputDeviceList(dev_list, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) { - free(dev_list); + memfree(dev_list); return false; } for (unsigned int i = 0; i < dev_list_count; i++) { @@ -130,11 +129,11 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) { (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == (LONG)p_guid->Data1) && (GetRawInputDeviceInfoA(dev_list[i].hDevice, RIDI_DEVICENAME, &dev_name, &nameSize) != (UINT)-1) && (strstr(dev_name, "IG_") != nullptr)) { - free(dev_list); + memfree(dev_list); return true; } } - free(dev_list); + memfree(dev_list); return false; } @@ -447,8 +446,8 @@ void JoypadWindows::post_hat(int p_device, DWORD p_dpad) { input->joy_hat(p_device, dpad_val); }; -Input::JoyAxis JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { - Input::JoyAxis jx; +Input::JoyAxisValue JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const { + Input::JoyAxisValue jx; if (Math::abs(p_val) < MIN_JOY_AXIS) { jx.min = p_trigger ? 0 : -1; jx.value = 0.0f; diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h index 4727b4a14c..757fb54fb3 100644 --- a/platform/windows/joypad_windows.h +++ b/platform/windows/joypad_windows.h @@ -132,7 +132,7 @@ private: void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); void joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp); - Input::JoyAxis axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; + Input::JoyAxisValue axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; XInputGetState_t xinput_get_state; XInputSetState_t xinput_set_state; }; diff --git a/platform/windows/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 3280a36e9b..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; } @@ -334,7 +334,7 @@ OS::TimeZoneInfo OS_Windows::get_time_zone_info() const { } // Bias value returned by GetTimeZoneInformation is inverted of what we expect - // For example on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 + // For example, on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 ret.bias = -info.Bias; return ret; } @@ -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 diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index 56b620a6d9..c1f3827d15 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -53,7 +53,8 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er if (wlen < 0) return; - wchar_t *wbuf = (wchar_t *)malloc((len + 1) * sizeof(wchar_t)); + wchar_t *wbuf = (wchar_t *)memalloc((len + 1) * sizeof(wchar_t)); + ERR_FAIL_NULL_MSG(wbuf, "Out of memory."); MultiByteToWideChar(CP_UTF8, 0, buf, len, wbuf, wlen); wbuf[wlen] = 0; @@ -62,7 +63,7 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er else wprintf(L"%ls", wbuf); - free(wbuf); + memfree(wbuf); #ifdef DEBUG_ENABLED fflush(stdout); |