summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/android_keys_utils.h4
-rw-r--r--platform/android/detect.py14
-rw-r--r--platform/android/dir_access_jandroid.cpp3
-rw-r--r--platform/android/dir_access_jandroid.h7
-rw-r--r--platform/android/display_server_android.cpp94
-rw-r--r--platform/android/display_server_android.h29
-rw-r--r--platform/android/export/export.cpp160
-rw-r--r--platform/android/export/gradle_export_util.h46
-rw-r--r--platform/android/file_access_android.cpp13
-rw-r--r--platform/android/file_access_android.h12
-rw-r--r--platform/android/java/app/AndroidManifest.xml9
-rw-r--r--platform/android/java/app/assets/.gitignore2
-rw-r--r--platform/android/java/app/build.gradle10
-rw-r--r--platform/android/java/app/config.gradle23
-rw-r--r--platform/android/java/app/res/drawable-nodpi/splash.png (renamed from platform/android/java/app/res/drawable/splash.png)bin14766 -> 14766 bytes
-rw-r--r--platform/android/java/app/res/drawable-nodpi/splash_bg_color.png (renamed from platform/android/java/app/res/drawable/splash_bg_color.png)bin1360 -> 1360 bytes
-rw-r--r--platform/android/java/build.gradle38
-rw-r--r--platform/android/java/gradle.properties2
-rw-r--r--platform/android/java/lib/build.gradle6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java22
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java65
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java23
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java (renamed from platform/server/godot_server.cpp)37
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java24
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java86
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java23
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java (renamed from platform/javascript/api/javascript_eval.h)40
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt6
-rw-r--r--platform/android/java/nativeSrcsConfigs/README.md2
-rw-r--r--platform/android/java/nativeSrcsConfigs/build.gradle3
-rw-r--r--platform/android/java_class_wrapper.cpp3
-rw-r--r--platform/android/java_godot_io_wrapper.cpp12
-rw-r--r--platform/android/java_godot_lib_jni.cpp16
-rw-r--r--platform/android/java_godot_view_wrapper.cpp17
-rw-r--r--platform/android/java_godot_view_wrapper.h4
-rw-r--r--platform/android/java_godot_wrapper.cpp63
-rw-r--r--platform/android/java_godot_wrapper.h2
-rw-r--r--platform/android/net_socket_android.cpp4
-rw-r--r--platform/android/net_socket_android.h4
-rw-r--r--platform/android/thread_jandroid.cpp27
-rw-r--r--platform/android/vulkan/vulkan_context_android.cpp10
-rw-r--r--platform/android/vulkan/vulkan_context_android.h9
-rw-r--r--platform/iphone/SCsub1
-rw-r--r--platform/iphone/detect.py2
-rw-r--r--platform/iphone/display_server_iphone.h8
-rw-r--r--platform/iphone/display_server_iphone.mm71
-rw-r--r--platform/iphone/export/export.cpp66
-rw-r--r--platform/iphone/godot_app_delegate.m2
-rw-r--r--platform/iphone/godot_iphone.mm4
-rw-r--r--platform/iphone/godot_view.mm22
-rw-r--r--platform/iphone/ios.mm8
-rw-r--r--platform/iphone/joypad_iphone.mm2
-rw-r--r--platform/iphone/keyboard_input_view.mm6
-rw-r--r--platform/iphone/native_video_view.h41
-rw-r--r--platform/iphone/native_video_view.m266
-rw-r--r--platform/iphone/os_iphone.mm8
-rw-r--r--platform/iphone/plugin/godot_plugin_config.h5
-rw-r--r--platform/iphone/view_controller.h5
-rw-r--r--platform/iphone/view_controller.mm24
-rw-r--r--platform/iphone/vulkan_context_iphone.mm4
-rw-r--r--platform/javascript/SCsub49
-rw-r--r--platform/javascript/api/api.cpp39
-rw-r--r--platform/javascript/api/javascript_singleton.h (renamed from platform/javascript/js/libs/library_godot_editor_tools.js)63
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.cpp31
-rw-r--r--platform/javascript/detect.py38
-rw-r--r--platform/javascript/display_server_javascript.cpp100
-rw-r--r--platform/javascript/display_server_javascript.h5
-rw-r--r--platform/javascript/dom_keys.inc4
-rw-r--r--platform/javascript/emscripten_helpers.py89
-rw-r--r--platform/javascript/export/export.cpp662
-rw-r--r--platform/javascript/godot_js.h10
-rw-r--r--platform/javascript/http_client.h.inc16
-rw-r--r--platform/javascript/http_client_javascript.cpp183
-rw-r--r--platform/javascript/http_request.h73
-rw-r--r--platform/javascript/javascript_eval.cpp79
-rw-r--r--platform/javascript/javascript_main.cpp5
-rw-r--r--platform/javascript/javascript_singleton.cpp343
-rw-r--r--platform/javascript/js/engine/config.js44
-rw-r--r--platform/javascript/js/engine/engine.externs.js1
-rw-r--r--platform/javascript/js/engine/engine.js41
-rw-r--r--platform/javascript/js/engine/preloader.js108
-rw-r--r--platform/javascript/js/libs/library_godot_audio.js7
-rw-r--r--platform/javascript/js/libs/library_godot_display.js233
-rw-r--r--platform/javascript/js/libs/library_godot_eval.js86
-rw-r--r--platform/javascript/js/libs/library_godot_fetch.js247
-rw-r--r--platform/javascript/js/libs/library_godot_http_request.js142
-rw-r--r--platform/javascript/js/libs/library_godot_javascript_singleton.js333
-rw-r--r--platform/javascript/js/libs/library_godot_os.js23
-rw-r--r--platform/javascript/js/libs/library_godot_runtime.js16
-rw-r--r--platform/javascript/package-lock.json39
-rw-r--r--platform/javascript/package.json2
-rw-r--r--platform/linuxbsd/SCsub22
-rw-r--r--platform/linuxbsd/detect.py72
-rw-r--r--platform/linuxbsd/display_server_x11.cpp68
-rw-r--r--platform/linuxbsd/joypad_linux.cpp17
-rw-r--r--platform/linuxbsd/joypad_linux.h4
-rw-r--r--platform/linuxbsd/key_mapping_x11.cpp8
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp23
-rw-r--r--platform/osx/crash_handler_osx.mm10
-rw-r--r--platform/osx/detect.py17
-rw-r--r--platform/osx/display_server_osx.mm92
-rw-r--r--platform/osx/export/export.cpp280
-rw-r--r--platform/osx/joypad_osx.cpp4
-rw-r--r--platform/osx/os_osx.mm42
-rw-r--r--platform/osx/vulkan_context_osx.mm4
-rw-r--r--platform/server/SCsub16
-rw-r--r--platform/server/detect.py277
-rw-r--r--platform/server/logo.pngbin2016 -> 0 bytes
-rw-r--r--platform/server/os_server.cpp267
-rw-r--r--platform/server/os_server.h116
-rw-r--r--platform/server/platform_config.h49
-rw-r--r--platform/uwp/SCsub1
-rw-r--r--platform/uwp/app.cpp18
-rw-r--r--platform/uwp/detect.py11
-rw-r--r--platform/uwp/export/export.cpp36
-rw-r--r--platform/uwp/joypad_uwp.cpp4
-rw-r--r--platform/uwp/joypad_uwp.h2
-rw-r--r--platform/uwp/os_uwp.cpp16
-rw-r--r--platform/windows/crash_handler_windows.cpp2
-rw-r--r--platform/windows/detect.py17
-rw-r--r--platform/windows/display_server_windows.cpp112
-rw-r--r--platform/windows/joypad_windows.cpp15
-rw-r--r--platform/windows/joypad_windows.h2
-rw-r--r--platform/windows/key_mapping_windows.cpp8
-rw-r--r--platform/windows/os_windows.cpp42
-rw-r--r--platform/windows/windows_terminal_logger.cpp5
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
index 7bddd4325a..7bddd4325a 100644
--- a/platform/android/java/app/res/drawable/splash.png
+++ b/platform/android/java/app/res/drawable-nodpi/splash.png
Binary files differ
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
index 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
Binary files differ
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
deleted file mode 100644
index 8666ada9ca..0000000000
--- a/platform/server/logo.png
+++ /dev/null
Binary files differ
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);