summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/SCsub1
-rw-r--r--platform/android/android_input_handler.cpp10
-rw-r--r--platform/android/api/api.cpp4
-rw-r--r--platform/android/api/java_class_wrapper.h4
-rw-r--r--platform/android/api/jni_singleton.h28
-rw-r--r--platform/android/audio_driver_opensl.cpp9
-rw-r--r--platform/android/dir_access_jandroid.cpp50
-rw-r--r--platform/android/display_server_android.cpp40
-rw-r--r--platform/android/display_server_android.h5
-rw-r--r--platform/android/export/export.cpp3
-rw-r--r--platform/android/export/export_plugin.cpp78
-rw-r--r--platform/android/export/export_plugin.h24
-rw-r--r--platform/android/export/gradle_export_util.cpp16
-rw-r--r--platform/android/file_access_android.cpp25
-rw-r--r--platform/android/java/app/AndroidManifest.xml11
-rw-r--r--platform/android/java/app/config.gradle15
-rw-r--r--platform/android/java/build.gradle4
-rw-r--r--platform/android/java/lib/build.gradle17
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java2
-rw-r--r--platform/android/java/nativeSrcsConfigs/CMakeLists.txt3
-rw-r--r--platform/android/java/scripts/publish-module.gradle74
-rw-r--r--platform/android/java/scripts/publish-root.gradle39
-rw-r--r--platform/android/java_class_wrapper.cpp96
-rw-r--r--platform/android/java_godot_io_wrapper.cpp15
-rw-r--r--platform/android/java_godot_io_wrapper.h2
-rw-r--r--platform/android/java_godot_lib_jni.cpp58
-rw-r--r--platform/android/java_godot_wrapper.cpp28
-rw-r--r--platform/android/java_godot_wrapper.h3
-rw-r--r--platform/android/jni_utils.cpp44
-rw-r--r--platform/android/net_socket_android.cpp21
-rw-r--r--platform/android/os_android.cpp30
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp7
-rw-r--r--platform/iphone/app_delegate.mm11
-rw-r--r--platform/iphone/detect.py1
-rw-r--r--platform/iphone/display_layer.mm12
-rw-r--r--platform/iphone/display_server_iphone.h3
-rw-r--r--platform/iphone/display_server_iphone.mm46
-rw-r--r--platform/iphone/export/export_plugin.cpp424
-rw-r--r--platform/iphone/export/export_plugin.h3
-rw-r--r--platform/iphone/godot_iphone.mm12
-rw-r--r--platform/iphone/godot_view.h5
-rw-r--r--platform/iphone/godot_view.mm16
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.mm32
-rw-r--r--platform/iphone/godot_view_renderer.mm3
-rw-r--r--platform/iphone/ios.mm5
-rw-r--r--platform/iphone/joypad_iphone.mm53
-rw-r--r--platform/iphone/os_iphone.h1
-rw-r--r--platform/iphone/os_iphone.mm26
-rw-r--r--platform/iphone/view_controller.mm2
-rw-r--r--platform/javascript/api/api.cpp13
-rw-r--r--platform/javascript/api/javascript_singleton.h2
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.cpp6
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.h2
-rw-r--r--platform/javascript/detect.py6
-rw-r--r--platform/javascript/display_server_javascript.cpp40
-rw-r--r--platform/javascript/display_server_javascript.h1
-rw-r--r--platform/javascript/export/export_plugin.cpp88
-rw-r--r--platform/javascript/export/export_plugin.h3
-rw-r--r--platform/javascript/export/export_server.h2
-rw-r--r--platform/javascript/godot_js.h2
-rw-r--r--platform/javascript/http_client_javascript.cpp7
-rw-r--r--platform/javascript/http_client_javascript.h2
-rw-r--r--platform/javascript/javascript_main.cpp3
-rw-r--r--platform/javascript/javascript_singleton.cpp13
-rw-r--r--platform/javascript/js/engine/config.js10
-rw-r--r--platform/javascript/js/engine/engine.js3
-rw-r--r--platform/javascript/js/libs/library_godot_input.js2
-rw-r--r--platform/javascript/js/libs/library_godot_os.js55
-rw-r--r--platform/javascript/os_javascript.cpp22
-rw-r--r--platform/javascript/os_javascript.h9
-rw-r--r--platform/javascript/package-lock.json3544
-rw-r--r--platform/javascript/package.json9
-rw-r--r--platform/javascript/serve.json21
-rw-r--r--platform/linuxbsd/crash_handler_linuxbsd.cpp7
-rw-r--r--platform/linuxbsd/detect.py21
-rw-r--r--platform/linuxbsd/display_server_x11.cpp359
-rw-r--r--platform/linuxbsd/display_server_x11.h28
-rw-r--r--platform/linuxbsd/export/export.cpp7
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp74
-rw-r--r--platform/linuxbsd/export/export_plugin.h47
-rw-r--r--platform/linuxbsd/gl_manager_x11.cpp44
-rw-r--r--platform/linuxbsd/gl_manager_x11.h2
-rw-r--r--platform/linuxbsd/joypad_linux.cpp37
-rw-r--r--platform/linuxbsd/joypad_linux.h8
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp67
-rw-r--r--platform/linuxbsd/os_linuxbsd.h3
-rw-r--r--platform/linuxbsd/vulkan_context_x11.cpp1
-rw-r--r--platform/osx/SCsub11
-rw-r--r--platform/osx/crash_handler_osx.mm16
-rw-r--r--platform/osx/detect.py10
-rw-r--r--platform/osx/display_server_osx.h172
-rw-r--r--platform/osx/display_server_osx.mm2945
-rw-r--r--platform/osx/export/codesign.cpp1569
-rw-r--r--platform/osx/export/codesign.h368
-rw-r--r--platform/osx/export/export.cpp3
-rw-r--r--platform/osx/export/export_plugin.cpp647
-rw-r--r--platform/osx/export/export_plugin.h12
-rw-r--r--platform/osx/export/lipo.cpp243
-rw-r--r--platform/osx/export/lipo.h76
-rw-r--r--platform/osx/export/macho.cpp556
-rw-r--r--platform/osx/export/macho.h217
-rw-r--r--platform/osx/export/plist.cpp570
-rw-r--r--platform/osx/export/plist.h116
-rw-r--r--platform/osx/gl_manager_osx_legacy.h (renamed from platform/osx/gl_manager_osx.h)39
-rw-r--r--platform/osx/gl_manager_osx_legacy.mm (renamed from platform/osx/gl_manager_osx.mm)121
-rw-r--r--platform/osx/godot_application.h42
-rw-r--r--platform/osx/godot_application.mm53
-rw-r--r--platform/osx/godot_application_delegate.h45
-rw-r--r--platform/osx/godot_application_delegate.mm136
-rw-r--r--platform/osx/godot_content_view.h65
-rw-r--r--platform/osx/godot_content_view.mm764
-rw-r--r--platform/osx/godot_main_osx.mm27
-rw-r--r--platform/osx/godot_menu_item.h52
-rw-r--r--platform/osx/godot_window.h47
-rw-r--r--platform/osx/godot_window.mm69
-rw-r--r--platform/osx/godot_window_delegate.h47
-rw-r--r--platform/osx/godot_window_delegate.mm270
-rw-r--r--platform/osx/joypad_osx.cpp43
-rw-r--r--platform/osx/joypad_osx.h2
-rw-r--r--platform/osx/key_mapping_osx.h52
-rw-r--r--platform/osx/key_mapping_osx.mm477
-rw-r--r--platform/osx/os_osx.h30
-rw-r--r--platform/osx/os_osx.mm452
-rw-r--r--platform/osx/osx_terminal_logger.h44
-rw-r--r--platform/osx/osx_terminal_logger.mm82
-rw-r--r--platform/osx/vulkan_context_osx.mm2
-rw-r--r--platform/uwp/app_uwp.cpp15
-rw-r--r--platform/uwp/context_egl_uwp.cpp20
-rw-r--r--platform/uwp/export/app_packager.cpp3
-rw-r--r--platform/uwp/export/app_packager.h1
-rw-r--r--platform/uwp/export/export_plugin.cpp30
-rw-r--r--platform/uwp/export/export_plugin.h21
-rw-r--r--platform/uwp/joypad_uwp.cpp23
-rw-r--r--platform/uwp/joypad_uwp.h2
-rw-r--r--platform/uwp/os_uwp.cpp91
-rw-r--r--platform/uwp/os_uwp.h4
-rw-r--r--platform/windows/crash_handler_windows.cpp30
-rw-r--r--platform/windows/detect.py20
-rw-r--r--platform/windows/display_server_windows.cpp620
-rw-r--r--platform/windows/display_server_windows.h29
-rw-r--r--platform/windows/export/export_plugin.cpp90
-rw-r--r--platform/windows/export/export_plugin.h10
-rw-r--r--platform/windows/gl_manager_windows.cpp39
-rw-r--r--platform/windows/godot.icobin110755 -> 359559 bytes
-rw-r--r--platform/windows/godot_windows.cpp12
-rw-r--r--platform/windows/joypad_windows.cpp90
-rw-r--r--platform/windows/joypad_windows.h5
-rw-r--r--platform/windows/key_mapping_windows.cpp317
-rw-r--r--platform/windows/os_windows.cpp299
-rw-r--r--platform/windows/os_windows.h10
-rw-r--r--platform/windows/windows_terminal_logger.cpp14
154 files changed, 14152 insertions, 4145 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub
index ecc72019e5..d031d14499 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -18,6 +18,7 @@ android_files = [
"jni_utils.cpp",
"android_keys_utils.cpp",
"display_server_android.cpp",
+ "plugin/godot_plugin_jni.cpp",
"vulkan/vulkan_context_android.cpp",
]
diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp
index 7efdc620df..10f23b320b 100644
--- a/platform/android/android_input_handler.cpp
+++ b/platform/android/android_input_handler.cpp
@@ -39,10 +39,7 @@ void AndroidInputHandler::process_joy_event(AndroidInputHandler::JoypadEvent p_e
Input::get_singleton()->joy_button(p_event.device, (JoyButton)p_event.index, p_event.pressed);
break;
case JOY_EVENT_AXIS:
- Input::JoyAxisValue value;
- value.min = -1;
- value.value = p_event.value;
- Input::get_singleton()->joy_axis(p_event.device, (JoyAxis)p_event.index, value);
+ Input::get_singleton()->joy_axis(p_event.device, (JoyAxis)p_event.index, p_event.value);
break;
case JOY_EVENT_HAT:
Input::get_singleton()->joy_hat(p_event.device, p_event.hat);
@@ -168,8 +165,9 @@ void AndroidInputHandler::process_touch(int p_event, int p_pointer, const Vector
ERR_CONTINUE(idx == -1);
- if (touch[i].pos == p_points[idx].pos)
- continue; //no move unncesearily
+ if (touch[i].pos == p_points[idx].pos) {
+ continue; // Don't move unnecessarily.
+ }
Ref<InputEventScreenDrag> ev;
ev.instantiate();
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp
index f544f29b10..f80f1e3051 100644
--- a/platform/android/api/api.cpp
+++ b/platform/android/api/api.cpp
@@ -64,14 +64,14 @@ void JavaClassWrapper::_bind_methods() {
#if !defined(ANDROID_ENABLED)
-Variant JavaClass::call(const StringName &, const Variant **, int, Callable::CallError &) {
+Variant JavaClass::callp(const StringName &, const Variant **, int, Callable::CallError &) {
return Variant();
}
JavaClass::JavaClass() {
}
-Variant JavaObject::call(const StringName &, const Variant **, int, Callable::CallError &) {
+Variant JavaObject::callp(const StringName &, const Variant **, int, Callable::CallError &) {
return Variant();
}
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index d4d1208757..96b7b48e48 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -179,7 +179,7 @@ class JavaClass : public RefCounted {
#endif
public:
- virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
+ virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
JavaClass();
};
@@ -195,7 +195,7 @@ class JavaObject : public RefCounted {
#endif
public:
- virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
+ virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
#ifdef ANDROID_ENABLED
JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance);
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
index d8503b6caf..74ca10e5e2 100644
--- a/platform/android/api/jni_singleton.h
+++ b/platform/android/api/jni_singleton.h
@@ -31,10 +31,10 @@
#ifndef JNI_SINGLETON_H
#define JNI_SINGLETON_H
-#include <core/config/engine.h>
-#include <core/variant/variant.h>
+#include "core/config/engine.h"
+#include "core/variant/variant.h"
#ifdef ANDROID_ENABLED
-#include <platform/android/jni_utils.h>
+#include "platform/android/jni_utils.h"
#endif
class JNISingleton : public Object {
@@ -52,7 +52,7 @@ class JNISingleton : public Object {
#endif
public:
- virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
+ virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
#ifdef ANDROID_ENABLED
Map<StringName, MethodData>::Element *E = method_map.find(p_method);
@@ -70,7 +70,7 @@ public:
if (call_error) {
// The method is not in this map, defaulting to the regular instance calls.
- return Object::call(p_method, p_args, p_argcount, r_error);
+ return Object::callp(p_method, p_args, p_argcount, r_error);
}
ERR_FAIL_COND_V(!instance, Variant());
@@ -93,8 +93,9 @@ public:
for (int i = 0; i < p_argcount; i++) {
jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]);
v[i] = vr.val;
- if (vr.obj)
+ if (vr.obj) {
to_erase.push_back(vr.obj);
+ }
}
Variant ret;
@@ -175,7 +176,7 @@ public:
#else // ANDROID_ENABLED
// Defaulting to the regular instance calls.
- return Object::call(p_method, p_args, p_argcount, r_error);
+ return Object::callp(p_method, p_args, p_argcount, r_error);
#endif
}
@@ -197,18 +198,19 @@ public:
}
void add_signal(const StringName &p_name, const Vector<Variant::Type> &p_args) {
- if (p_args.size() == 0)
+ if (p_args.size() == 0) {
ADD_SIGNAL(MethodInfo(p_name));
- else if (p_args.size() == 1)
+ } else if (p_args.size() == 1) {
ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1")));
- else if (p_args.size() == 2)
+ } else if (p_args.size() == 2) {
ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2")));
- else if (p_args.size() == 3)
+ } else if (p_args.size() == 3) {
ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3")));
- else if (p_args.size() == 4)
+ } else if (p_args.size() == 4) {
ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"), PropertyInfo(p_args[3], "arg4")));
- else if (p_args.size() == 5)
+ } else if (p_args.size() == 5) {
ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"), PropertyInfo(p_args[3], "arg4"), PropertyInfo(p_args[4], "arg5")));
+ }
}
#endif
diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp
index cd478bb90f..8495d2cc18 100644
--- a/platform/android/audio_driver_opensl.cpp
+++ b/platform/android/audio_driver_opensl.cpp
@@ -56,8 +56,9 @@ void AudioDriverOpenSL::_buffer_callback(
}
}
- if (mix)
+ if (mix) {
mutex.unlock();
+ }
const int32_t *src_buff = mixdown_buffer;
@@ -312,13 +313,15 @@ AudioDriver::SpeakerMode AudioDriverOpenSL::get_speaker_mode() const {
}
void AudioDriverOpenSL::lock() {
- if (active)
+ if (active) {
mutex.lock();
+ }
}
void AudioDriverOpenSL::unlock() {
- if (active)
+ if (active) {
mutex.unlock();
+ }
}
void AudioDriverOpenSL::finish() {
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 5461a3aefa..7fb4f54fca 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "dir_access_jandroid.h"
+
#include "core/string/print_string.h"
#include "file_access_android.h"
#include "string_android.h"
@@ -51,8 +52,9 @@ Error DirAccessJAndroid::list_dir_begin() {
jstring js = env->NewStringUTF(current_dir.utf8().get_data());
int res = env->CallIntMethod(io, _dir_open, js);
- if (res <= 0)
+ if (res <= 0) {
return ERR_CANT_OPEN;
+ }
id = res;
@@ -64,8 +66,9 @@ String DirAccessJAndroid::get_next() {
JNIEnv *env = get_jni_env();
jstring str = (jstring)env->CallObjectMethod(io, _dir_next, id);
- if (!str)
+ if (!str) {
return "";
+ }
String ret = jstring_to_string((jstring)str, env);
env->DeleteLocalRef((jobject)str);
@@ -83,8 +86,9 @@ bool DirAccessJAndroid::current_is_hidden() const {
}
void DirAccessJAndroid::list_dir_end() {
- if (id == 0)
+ if (id == 0) {
return;
+ }
JNIEnv *env = get_jni_env();
env->CallVoidMethod(io, _dir_close, id);
@@ -102,22 +106,25 @@ String DirAccessJAndroid::get_drive(int p_drive) {
Error DirAccessJAndroid::change_dir(String p_dir) {
JNIEnv *env = get_jni_env();
- if (p_dir.is_empty() || p_dir == "." || (p_dir == ".." && current_dir.is_empty()))
+ if (p_dir.is_empty() || p_dir == "." || (p_dir == ".." && current_dir.is_empty())) {
return OK;
+ }
String new_dir;
- if (p_dir != "res://" && p_dir.length() > 1 && p_dir.ends_with("/"))
+ if (p_dir != "res://" && p_dir.length() > 1 && p_dir.ends_with("/")) {
p_dir = p_dir.substr(0, p_dir.length() - 1);
+ }
- if (p_dir.begins_with("/"))
+ if (p_dir.begins_with("/")) {
new_dir = p_dir.substr(1, p_dir.length());
- else if (p_dir.begins_with("res://"))
+ } else if (p_dir.begins_with("res://")) {
new_dir = p_dir.substr(6, p_dir.length());
- else if (current_dir.is_empty())
+ } else if (current_dir.is_empty()) {
new_dir = p_dir;
- else
+ } else {
new_dir = current_dir.plus_file(p_dir);
+ }
//test if newdir exists
new_dir = new_dir.simplify_path();
@@ -125,8 +132,9 @@ Error DirAccessJAndroid::change_dir(String p_dir) {
jstring js = env->NewStringUTF(new_dir.utf8().get_data());
int res = env->CallIntMethod(io, _dir_open, js);
env->DeleteLocalRef(js);
- if (res <= 0)
+ if (res <= 0) {
return ERR_INVALID_PARAMETER;
+ }
env->CallVoidMethod(io, _dir_close, res);
@@ -141,10 +149,11 @@ String DirAccessJAndroid::get_current_dir(bool p_include_drive) {
bool DirAccessJAndroid::file_exists(String p_file) {
String sd;
- if (current_dir.is_empty())
+ if (current_dir.is_empty()) {
sd = p_file;
- else
+ } else {
sd = current_dir.plus_file(p_file);
+ }
FileAccessAndroid *f = memnew(FileAccessAndroid);
bool exists = f->file_exists(sd);
@@ -158,27 +167,30 @@ bool DirAccessJAndroid::dir_exists(String p_dir) {
String sd;
- if (current_dir.is_empty())
+ if (current_dir.is_empty()) {
sd = p_dir;
- else {
- if (p_dir.is_relative_path())
+ } else {
+ if (p_dir.is_relative_path()) {
sd = current_dir.plus_file(p_dir);
- else
+ } else {
sd = fix_path(p_dir);
+ }
}
String path = sd.simplify_path();
- if (path.begins_with("/"))
+ if (path.begins_with("/")) {
path = path.substr(1, path.length());
- else if (path.begins_with("res://"))
+ } else if (path.begins_with("res://")) {
path = path.substr(6, path.length());
+ }
jstring js = env->NewStringUTF(path.utf8().get_data());
int res = env->CallIntMethod(io, _dir_open, js);
env->DeleteLocalRef(js);
- if (res <= 0)
+ if (res <= 0) {
return false;
+ }
env->CallVoidMethod(io, _dir_close, res);
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 5b9ca44180..a7a8801bdc 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -47,7 +47,6 @@ 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_CUSTOM_CURSOR_SHAPE:
//case FEATURE_GLOBAL_MENU:
@@ -96,6 +95,17 @@ String DisplayServerAndroid::clipboard_get() const {
}
}
+bool DisplayServerAndroid::clipboard_has() const {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_COND_V(!godot_java, false);
+
+ if (godot_java->has_has_clipboard()) {
+ return godot_java->has_clipboard();
+ } else {
+ return DisplayServer::clipboard_has();
+ }
+}
+
void DisplayServerAndroid::screen_set_keep_on(bool p_enable) {
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
ERR_FAIL_COND(!godot_java);
@@ -151,6 +161,16 @@ int DisplayServerAndroid::screen_get_dpi(int p_screen) const {
return godot_io_java->get_screen_dpi();
}
+float DisplayServerAndroid::screen_get_refresh_rate(int p_screen) const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ if (!godot_io_java) {
+ ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+ }
+
+ return godot_io_java->get_screen_refresh_rate(SCREEN_REFRESH_RATE_FALLBACK);
+}
+
bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const {
return true;
}
@@ -243,6 +263,24 @@ DisplayServer::WindowID DisplayServerAndroid::get_window_at_screen_position(cons
return MAIN_WINDOW_ID;
}
+int64_t DisplayServerAndroid::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+ ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
+ switch (p_handle_type) {
+ case DISPLAY_HANDLE: {
+ return 0; // Not supported.
+ }
+ case WINDOW_HANDLE: {
+ return (int64_t)((OS_Android *)OS::get_singleton())->get_godot_java()->get_activity();
+ }
+ case WINDOW_VIEW: {
+ return 0; // Not supported.
+ }
+ default: {
+ return 0;
+ }
+ }
+}
+
void DisplayServerAndroid::window_attach_instance_id(ObjectID p_instance, DisplayServer::WindowID p_window) {
window_attached_instance_id = p_instance;
}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 8538b6e660..23077a6529 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -93,6 +93,7 @@ public:
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
+ virtual bool clipboard_has() const override;
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;
@@ -105,6 +106,7 @@ public:
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override;
@@ -123,6 +125,9 @@ public:
virtual Vector<WindowID> get_window_list() const override;
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+
+ virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 23b5c5e4e5..aa4b394965 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -32,6 +32,9 @@
#include "export_plugin.h"
+#include "core/os/os.h"
+#include "editor/editor_settings.h"
+
void register_android_exporter() {
String exe_ext;
if (OS::get_singleton()->get_name() == "Windows") {
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index cbecde787f..df3693ba61 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -30,6 +30,26 @@
#include "export_plugin.h"
+#include "gradle_export_util.h"
+
+#include "core/config/project_settings.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/io/image_loader.h"
+#include "core/io/json.h"
+#include "core/io/marshalls.h"
+#include "core/version.h"
+#include "drivers/png/png_driver_common.h"
+#include "editor/editor_log.h"
+#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
+#include "editor/editor_settings.h"
+#include "main/splash.gen.h"
+#include "platform/android/logo.gen.h"
+#include "platform/android/run_icon.gen.h"
+
+#include <string.h>
+
static const char *android_perms[] = {
"ACCESS_CHECKIN_PROPERTIES",
"ACCESS_COARSE_LOCATION",
@@ -416,10 +436,10 @@ String EditorExportPlatformAndroid::get_package_name(const String &p_package) co
bool first = true;
for (int i = 0; i < basename.length(); i++) {
char32_t c = basename[i];
- if (c >= '0' && c <= '9' && first) {
+ if (is_digit(c) && first) {
continue;
}
- if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
+ if (is_ascii_alphanumeric_char(c)) {
name += String::chr(c);
first = false;
}
@@ -462,19 +482,19 @@ bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package,
first = true;
continue;
}
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) {
+ if (!is_ascii_identifier_char(c)) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Android application package names."), String::chr(c));
}
return false;
}
- if (first && (c >= '0' && c <= '9')) {
+ if (first && is_digit(c)) {
if (r_error) {
*r_error = TTR("A digit cannot be the first character in a package segment.");
}
return false;
}
- if (first && c == '_') {
+ if (first && is_underscore(c)) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' cannot be the first character in a package segment."), String::chr(c));
}
@@ -523,7 +543,7 @@ bool EditorExportPlatformAndroid::_should_compress_asset(const String &p_path, c
".webp", // Same reasoning as .png
".cfb", // Don't let small config files slow-down startup
".scn", // Binary scenes are usually already compressed
- ".stex", // Streamable textures are usually already compressed
+ ".ctex", // Streamable textures are usually already compressed
// Trailer for easier processing
nullptr
};
@@ -975,20 +995,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
}
}
- 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 == XR_MODE_OPENXR) {
- 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 == XR_MODE_OPENXR) {
- string_table.write[attr_value] = "vr_only";
- }
- }
-
if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_metadata_name") {
if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) {
string_table.write[attr_value] = "com.oculus.handtracking.frequency";
@@ -1381,6 +1387,7 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> &
Vector<String> string_table;
String package_name = p_preset->get("package/name");
+ Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized");
for (uint32_t i = 0; i < string_count; i++) {
uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]);
@@ -1395,9 +1402,8 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> &
} else {
String lang = str.substr(str.rfind("-") + 1, str.length()).replace("-", "_");
- String prop = "application/config/name_" + lang;
- if (ProjectSettings::get_singleton()->has_setting(prop)) {
- str = ProjectSettings::get_singleton()->get(prop);
+ if (appnames.has(lang)) {
+ str = appnames[lang];
} else {
str = get_project_name(package_name);
}
@@ -1514,7 +1520,7 @@ String EditorExportPlatformAndroid::load_splash_refs(Ref<Image> &splash_image, R
}
if (scale_splash) {
- Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
+ Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height"));
int width, height;
if (screen_size.width > screen_size.height) {
// scale horizontally
@@ -2250,9 +2256,9 @@ String EditorExportPlatformAndroid::get_apk_expansion_fullpath(const Ref<EditorE
return fullpath;
}
-Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
- Error err = save_pack(p_preset, fullpath);
+ Error err = save_pack(p_preset, p_debug, fullpath);
return err;
}
@@ -2570,7 +2576,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
CustomExportData user_data;
user_data.assets_directory = assets_directory;
user_data.debug = p_debug;
- err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
+ err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
if (err != OK) {
EditorNode::add_io_error(TTR("Could not export project files to gradle project\n"));
return err;
@@ -2583,7 +2589,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
}
} else {
print_verbose("Saving apk expansion file..");
- err = save_apk_expansion_file(p_preset, p_path);
+ err = save_apk_expansion_file(p_preset, p_debug, p_path);
if (err != OK) {
EditorNode::add_io_error(TTR("Could not write expansion package file!"));
return err;
@@ -2666,6 +2672,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
debug_password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
debug_user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
}
+ if (debug_keystore.is_relative_path()) {
+ debug_keystore = OS::get_singleton()->get_resource_dir().plus_file(debug_keystore).simplify_path();
+ }
+ if (!FileAccess::exists(debug_keystore)) {
+ EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
+ return ERR_FILE_CANT_OPEN;
+ }
cmdline.push_back("-Pdebug_keystore_file=" + debug_keystore); // argument to specify the debug keystore file.
cmdline.push_back("-Pdebug_keystore_alias=" + debug_user); // argument to specify the debug keystore alias.
@@ -2675,6 +2688,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
String release_keystore = p_preset->get("keystore/release");
String release_username = p_preset->get("keystore/release_user");
String release_password = p_preset->get("keystore/release_password");
+ if (release_keystore.is_relative_path()) {
+ release_keystore = OS::get_singleton()->get_resource_dir().plus_file(release_keystore).simplify_path();
+ }
if (!FileAccess::exists(release_keystore)) {
EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
return ERR_FILE_CANT_OPEN;
@@ -2899,10 +2915,10 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
- err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so);
+ err = export_project_files(p_preset, p_debug, ignore_apk_file, &ed, save_apk_so);
} else {
if (apk_expansion) {
- err = save_apk_expansion_file(p_preset, p_path);
+ err = save_apk_expansion_file(p_preset, p_debug, p_path);
if (err != OK) {
EditorNode::add_io_error(TTR("Could not write expansion package file!"));
return err;
@@ -2911,7 +2927,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
- err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so);
+ err = export_project_files(p_preset, p_debug, save_apk_file, &ed, save_apk_so);
}
}
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 9e952ecb8f..0f267cf13a 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -28,29 +28,11 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "core/config/project_settings.h"
-#include "core/io/dir_access.h"
-#include "core/io/file_access.h"
-#include "core/io/image_loader.h"
-#include "core/io/json.h"
-#include "core/io/marshalls.h"
+#include "godot_plugin_config.h"
+
#include "core/io/zip_io.h"
#include "core/os/os.h"
-#include "core/templates/safe_refcount.h"
-#include "core/version.h"
-#include "drivers/png/png_driver_common.h"
#include "editor/editor_export.h"
-#include "editor/editor_log.h"
-#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
-#include "main/splash.gen.h"
-#include "platform/android/logo.gen.h"
-#include "platform/android/run_icon.gen.h"
-
-#include "godot_plugin_config.h"
-#include "gradle_export_util.h"
-
-#include <string.h>
const String SPLASH_CONFIG_XML_CONTENT = R"SPLASH(<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
@@ -228,7 +210,7 @@ public:
String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path);
- Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path);
+ Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags);
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index 287b669fd1..ab915a5f85 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -30,6 +30,8 @@
#include "gradle_export_util.h"
+#include "core/config/project_settings.h"
+
int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation) {
switch (screen_orientation) {
case DisplayServer::SCREEN_PORTRAIT:
@@ -73,11 +75,10 @@ String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_or
// Utility method used to create a directory.
Error create_directory(const String &p_dir) {
if (!DirAccess::exists(p_dir)) {
- DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ DirAccessRef filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
Error err = filesystem_da->make_dir_recursive(p_dir);
ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
- memdelete(filesystem_da);
}
return OK;
}
@@ -159,6 +160,7 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
return ERR_CANT_OPEN;
}
da->list_dir_begin();
+ Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized");
while (true) {
String file = da->get_next();
if (file.is_empty()) {
@@ -169,10 +171,9 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
continue;
}
String locale = file.replace("values-", "").replace("-r", "_");
- String property_name = "application/config/name_" + locale;
String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml";
- if (ProjectSettings::get_singleton()->has_setting(property_name)) {
- String locale_project_name = ProjectSettings::get_singleton()->get(property_name);
+ if (appnames.has(locale)) {
+ String locale_project_name = appnames[locale];
String processed_xml_string = vformat(godot_project_name_xml_string, _android_xml_escape(locale_project_name));
print_verbose("Storing project name for locale " + locale + " under " + locale_directory);
store_string_at_path(locale_directory, processed_xml_string);
@@ -278,7 +279,6 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
" android:requestLegacyExternalStorage=\"%s\"\n"
" tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n"
" tools:ignore=\"GoogleAppIndexingWarning\">\n\n"
- " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n"
" <meta-data tools:node=\"remove\" android:name=\"xr_hand_tracking_metadata_name\" />\n",
bool_to_string(p_preset->get("user_data_backup/allow")),
bool_to_string(p_preset->get("package/classify_as_game")),
@@ -286,8 +286,6 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
bool_to_string(p_has_storage_permission));
if (uses_xr) {
- manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n";
-
bool hand_tracking_enabled = (int)(p_preset->get("xr_features/hand_tracking")) > XR_HAND_TRACKING_NONE;
if (hand_tracking_enabled) {
int hand_tracking_frequency_index = p_preset->get("xr_features/hand_tracking_frequency");
@@ -296,6 +294,8 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
" <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.frequency\" android:value=\"%s\" />\n",
hand_tracking_frequency);
}
+ } else {
+ manifest_application_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.supportedDevices\" />\n";
}
manifest_application_text += _get_activity_tag(p_preset);
manifest_application_text += " </application>\n";
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index 26bdcb9520..c84a919b6b 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -29,30 +29,28 @@
/*************************************************************************/
#include "file_access_android.h"
+
#include "core/string/print_string.h"
AAssetManager *FileAccessAndroid::asset_manager = nullptr;
-/*void FileAccessAndroid::make_default() {
- create_func=create_android;
-}*/
-
FileAccess *FileAccessAndroid::create_android() {
return memnew(FileAccessAndroid);
}
Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) {
String path = fix_path(p_path).simplify_path();
- if (path.begins_with("/"))
+ if (path.begins_with("/")) {
path = path.substr(1, path.length());
- else if (path.begins_with("res://"))
+ } else if (path.begins_with("res://")) {
path = path.substr(6, path.length());
+ }
ERR_FAIL_COND_V(p_mode_flags & FileAccess::WRITE, ERR_UNAVAILABLE); //can't write on android..
a = AAssetManager_open(asset_manager, path.utf8().get_data(), AASSET_MODE_STREAMING);
- if (!a)
+ if (!a) {
return ERR_CANT_OPEN;
- //ERR_FAIL_COND_V(!a,ERR_FILE_NOT_FOUND);
+ }
len = AAsset_getLength(a);
pos = 0;
eof = false;
@@ -61,8 +59,9 @@ Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) {
}
void FileAccessAndroid::close() {
- if (!a)
+ if (!a) {
return;
+ }
AAsset_close(a);
a = nullptr;
}
@@ -146,15 +145,17 @@ void FileAccessAndroid::store_8(uint8_t p_dest) {
bool FileAccessAndroid::file_exists(const String &p_path) {
String path = fix_path(p_path).simplify_path();
- if (path.begins_with("/"))
+ if (path.begins_with("/")) {
path = path.substr(1, path.length());
- else if (path.begins_with("res://"))
+ } else if (path.begins_with("res://")) {
path = path.substr(6, path.length());
+ }
AAsset *at = AAssetManager_open(asset_manager, path.utf8().get_data(), AASSET_MODE_STREAMING);
- if (!at)
+ if (!at) {
return false;
+ }
AAsset_close(at);
return true;
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 3924aacccd..4c4501729d 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -33,11 +33,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" />
-
<!-- XR hand tracking metadata -->
<!-- This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. -->
<!-- Removed at export time if the xr mode is not VR or hand tracking is disabled. -->
@@ -45,6 +40,12 @@
android:name="xr_hand_tracking_metadata_name"
android:value="xr_hand_tracking_metadata_value"/>
+ <!-- Supported Meta devices -->
+ <!-- This is removed by the exporter if the xr mode is not VR. -->
+ <meta-data
+ android:name="com.oculus.supportedDevices"
+ android:value="all" />
+
<activity
android:name=".GodotApp"
android:label="@string/godot_project_name_string"
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 32e03998da..c238d1b361 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -4,7 +4,7 @@ ext.versions = [
minSdk : 19, // Also update 'platform/android/java/lib/AndroidManifest.xml#minSdkVersion' & 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION'
targetSdk : 30, // Also update 'platform/android/java/lib/AndroidManifest.xml#targetSdkVersion' & 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
buildTools : '30.0.3',
- kotlinVersion : '1.5.10',
+ kotlinVersion : '1.6.10',
fragmentVersion : '1.3.6',
javaVersion : 11,
ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated.
@@ -86,13 +86,12 @@ ext.getGodotEditorVersion = { ->
return editorVersion
}
-ext.getGodotLibraryVersion = { ->
+ext.generateGodotLibraryVersion = { List<String> requiredKeys ->
// Attempt to read the version from the `version.py` file.
String libraryVersion = ""
File versionFile = new File("../../../version.py")
if (versionFile.isFile()) {
- List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"]
def map = [:]
List<String> lines = versionFile.readLines()
@@ -121,6 +120,16 @@ ext.getGodotLibraryVersion = { ->
return libraryVersion
}
+ext.getGodotLibraryVersion = { ->
+ List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"]
+ return generateGodotLibraryVersion(requiredKeys)
+}
+
+ext.getGodotPublishVersion = { ->
+ List<String> requiredKeys = ["major", "minor", "patch", "status"]
+ return generateGodotLibraryVersion(requiredKeys)
+}
+
final String VALUE_SEPARATOR_REGEX = "\\|"
// get the list of ABIs the project should be exported to
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index ac008edbed..83bc68c992 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -1,4 +1,6 @@
+apply plugin: 'io.github.gradle-nexus.publish-plugin'
apply from: 'app/config.gradle'
+apply from: 'scripts/publish-root.gradle'
buildscript {
apply from: 'app/config.gradle'
@@ -6,10 +8,12 @@ buildscript {
repositories {
google()
mavenCentral()
+ maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath libraries.androidGradlePlugin
classpath libraries.kotlinGradlePlugin
+ classpath 'io.github.gradle-nexus:publish-plugin:1.1.0'
}
}
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index fbed4ed078..120a40a31d 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -1,6 +1,13 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
+ext {
+ PUBLISH_VERSION = getGodotPublishVersion()
+ PUBLISH_ARTIFACT_ID = 'godot'
+}
+
+apply from: "../scripts/publish-module.gradle"
+
dependencies {
implementation libraries.kotlinStdLib
implementation libraries.androidxFragment
@@ -21,6 +28,8 @@ android {
manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersion()]
}
+ namespace = "org.godotengine.godot"
+
compileOptions {
sourceCompatibility versions.javaVersion
targetCompatibility versions.javaVersion
@@ -111,4 +120,12 @@ android {
// Schedule the tasks so the generated libs are present before the aar file is packaged.
tasks["merge${buildType}JniLibFolders"].dependsOn taskName
}
+
+ // TODO: Enable when issues with AGP 7.1+ are resolved (https://github.com/GodotVR/godot_openxr/issues/187).
+// publishing {
+// singleVariant("release") {
+// withSourcesJar()
+// withJavadocJar()
+// }
+// }
}
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 3fbbd8fbcc..78848c109a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -660,10 +660,14 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
}
+ public boolean hasClipboard() {
+ return mClipboard.hasPrimaryClip();
+ }
+
public String getClipboard() {
String copiedText = "";
- if (mClipboard.getPrimaryClip() != null) {
+ if (mClipboard.hasPrimaryClip()) {
ClipData.Item item = mClipboard.getPrimaryClip().getItemAt(0);
copiedText = item.getText().toString();
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index d679fd92c0..b151e7eec1 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -226,6 +226,14 @@ public class GodotIO {
return (int)(metrics.density * 160f);
}
+ public double getScreenRefreshRate(double fallback) {
+ Display display = activity.getWindowManager().getDefaultDisplay();
+ if (display != null) {
+ return display.getRefreshRate();
+ }
+ return fallback;
+ }
+
public int[] screenGetUsableRect() {
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
Display display = activity.getWindowManager().getDefaultDisplay();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
index 8fc16ab7ba..4525c5c212 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
@@ -147,8 +147,9 @@ public class GLUtils {
Log.i(TAG, String.format(" %s: %d\n", name, value[0]));
} else {
// Log.w(TAG, String.format(" %s: failed\n", name));
- while (egl.eglGetError() != EGL10.EGL_SUCCESS)
- ;
+ while (egl.eglGetError() != EGL10.EGL_SUCCESS) {
+ // Continue.
+ }
}
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index 9ea787ac86..e5b4f41153 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -45,8 +45,8 @@ import java.util.List;
/**
* This class includes utility functions for Android permissions related operations.
- * @author Cagdas Caglak <cagdascaglak@gmail.com>
*/
+
public final class PermissionsUtil {
private static final String TAG = PermissionsUtil.class.getSimpleName();
diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index 34925684da..966c02f7d7 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -15,5 +15,4 @@ file(GLOB_RECURSE HEADERS ${GODOT_ROOT_DIR}/*.h**)
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_include_directories(${PROJECT_NAME}
SYSTEM PUBLIC
- ${GODOT_ROOT_DIR}
- ${GODOT_ROOT_DIR}/modules/gdnative/include)
+ ${GODOT_ROOT_DIR})
diff --git a/platform/android/java/scripts/publish-module.gradle b/platform/android/java/scripts/publish-module.gradle
new file mode 100644
index 0000000000..6b2aea5caf
--- /dev/null
+++ b/platform/android/java/scripts/publish-module.gradle
@@ -0,0 +1,74 @@
+apply plugin: 'maven-publish'
+apply plugin: 'signing'
+
+group = ossrhGroupId
+version = PUBLISH_VERSION
+
+afterEvaluate {
+ publishing {
+ publications {
+ release(MavenPublication) {
+ // The coordinates of the library, being set from variables that
+ // we'll set up later
+ groupId ossrhGroupId
+ artifactId PUBLISH_ARTIFACT_ID
+ version PUBLISH_VERSION
+
+ // Two artifacts, the `aar` (or `jar`) and the sources
+ if (project.plugins.findPlugin("com.android.library")) {
+ from components.release
+ } else {
+ from components.java
+ }
+
+ // Mostly self-explanatory metadata
+ pom {
+ name = PUBLISH_ARTIFACT_ID
+ description = 'Godot Engine Android Library'
+ url = 'https://godotengine.org/'
+ licenses {
+ license {
+ name = 'MIT License'
+ url = 'https://github.com/godotengine/godot/blob/master/LICENSE.txt'
+ }
+ }
+ developers {
+ developer {
+ id = 'm4gr3d'
+ name = 'Fredia Huya-Kouadio'
+ email = 'fhuyakou@gmail.com'
+ }
+ developer {
+ id = 'reduz'
+ name = 'Juan Linietsky'
+ email = 'reduzio@gmail.com'
+ }
+ developer {
+ id = 'akien-mga'
+ name = 'Rémi Verschelde'
+ email = 'rverschelde@gmail.com'
+ }
+ // Add all other devs here...
+ }
+
+ // Version control info - if you're using GitHub, follow the
+ // format as seen here
+ scm {
+ connection = 'scm:git:github.com/godotengine/godot.git'
+ developerConnection = 'scm:git:ssh://github.com/godotengine/godot.git'
+ url = 'https://github.com/godotengine/godot/tree/master'
+ }
+ }
+ }
+ }
+ }
+}
+
+signing {
+ useInMemoryPgpKeys(
+ rootProject.ext["signing.keyId"],
+ rootProject.ext["signing.key"],
+ rootProject.ext["signing.password"],
+ )
+ sign publishing.publications
+}
diff --git a/platform/android/java/scripts/publish-root.gradle b/platform/android/java/scripts/publish-root.gradle
new file mode 100644
index 0000000000..ae88487c34
--- /dev/null
+++ b/platform/android/java/scripts/publish-root.gradle
@@ -0,0 +1,39 @@
+// Create variables with empty default values
+ext["signing.keyId"] = ''
+ext["signing.password"] = ''
+ext["signing.key"] = ''
+ext["ossrhGroupId"] = ''
+ext["ossrhUsername"] = ''
+ext["ossrhPassword"] = ''
+ext["sonatypeStagingProfileId"] = ''
+
+File secretPropsFile = project.rootProject.file('local.properties')
+if (secretPropsFile.exists()) {
+ // Read local.properties file first if it exists
+ Properties p = new Properties()
+ new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
+ p.each { name, value -> ext[name] = value }
+} else {
+ // Use system environment variables
+ ext["ossrhGroupId"] = System.getenv('OSSRH_GROUP_ID')
+ ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME')
+ ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD')
+ ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
+ ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
+ ext["signing.password"] = System.getenv('SIGNING_PASSWORD')
+ ext["signing.key"] = System.getenv('SIGNING_KEY')
+}
+
+// Set up Sonatype repository
+nexusPublishing {
+ repositories {
+ sonatype {
+ stagingProfileId = sonatypeStagingProfileId
+ username = ossrhUsername
+ password = ossrhPassword
+ nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
+ snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
+ }
+ }
+}
+
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index f823e2c27f..1805807f90 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -29,13 +29,15 @@
/*************************************************************************/
#include "api/java_class_wrapper.h"
+
#include "string_android.h"
#include "thread_jandroid.h"
bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret) {
Map<StringName, List<MethodInfo>>::Element *M = methods.find(p_method);
- if (!M)
+ if (!M) {
return false;
+ }
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, false);
@@ -68,8 +70,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
//bug?
} break;
case ARG_TYPE_BOOLEAN: {
- if (p_args[i]->get_type() != Variant::BOOL)
+ if (p_args[i]->get_type() != Variant::BOOL) {
arg_expected = Variant::BOOL;
+ }
} break;
case ARG_NUMBER_CLASS_BIT | ARG_TYPE_BYTE:
case ARG_NUMBER_CLASS_BIT | ARG_TYPE_CHAR:
@@ -81,27 +84,27 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
case ARG_TYPE_SHORT:
case ARG_TYPE_INT:
case ARG_TYPE_LONG: {
- if (!p_args[i]->is_num())
+ if (!p_args[i]->is_num()) {
arg_expected = Variant::INT;
-
+ }
} break;
case ARG_NUMBER_CLASS_BIT | ARG_TYPE_FLOAT:
case ARG_NUMBER_CLASS_BIT | ARG_TYPE_DOUBLE:
case ARG_TYPE_FLOAT:
case ARG_TYPE_DOUBLE: {
- if (!p_args[i]->is_num())
+ if (!p_args[i]->is_num()) {
arg_expected = Variant::FLOAT;
-
+ }
} break;
case ARG_TYPE_STRING: {
- if (p_args[i]->get_type() != Variant::STRING)
+ if (p_args[i]->get_type() != Variant::STRING) {
arg_expected = Variant::STRING;
-
+ }
} break;
case ARG_TYPE_CLASS: {
- if (p_args[i]->get_type() != Variant::OBJECT)
+ if (p_args[i]->get_type() != Variant::OBJECT) {
arg_expected = Variant::OBJECT;
- else {
+ } else {
Ref<RefCounted> ref = *p_args[i];
if (!ref.is_null()) {
if (Object::cast_to<JavaObject>(ref.ptr())) {
@@ -118,12 +121,11 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
}
}
}
-
} break;
default: {
- if (p_args[i]->get_type() != Variant::ARRAY)
+ if (p_args[i]->get_type() != Variant::ARRAY) {
arg_expected = Variant::ARRAY;
-
+ }
} break;
}
@@ -135,15 +137,17 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
break;
}
}
- if (!valid)
+ if (!valid) {
continue;
+ }
method = &E;
break;
}
- if (!method)
+ if (!method) {
return true; //no version convinces
+ }
r_error.error = Callable::CallError::CALL_OK;
@@ -481,14 +485,14 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
return success;
}
-Variant JavaClass::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
Variant ret;
bool found = _call_method(nullptr, p_method, p_args, p_argcount, r_error, ret);
if (found) {
return ret;
}
- return RefCounted::call(p_method, p_args, p_argcount, r_error);
+ return RefCounted::callp(p_method, p_args, p_argcount, r_error);
}
JavaClass::JavaClass() {
@@ -496,7 +500,7 @@ JavaClass::JavaClass() {
/////////////////////
-Variant JavaObject::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
return Variant();
}
@@ -780,9 +784,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
bool val = env->CallBooleanMethod(o, JavaClassWrapper::singleton->Boolean_booleanValue);
ret.push_back(val);
}
@@ -801,9 +805,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
int val = env->CallByteMethod(o, JavaClassWrapper::singleton->Byte_byteValue);
ret.push_back(val);
}
@@ -821,9 +825,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
int val = env->CallCharMethod(o, JavaClassWrapper::singleton->Character_characterValue);
ret.push_back(val);
}
@@ -841,9 +845,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
int val = env->CallShortMethod(o, JavaClassWrapper::singleton->Short_shortValue);
ret.push_back(val);
}
@@ -861,9 +865,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
int val = env->CallIntMethod(o, JavaClassWrapper::singleton->Integer_integerValue);
ret.push_back(val);
}
@@ -881,9 +885,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
int64_t val = env->CallLongMethod(o, JavaClassWrapper::singleton->Long_longValue);
ret.push_back(val);
}
@@ -901,9 +905,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
float val = env->CallFloatMethod(o, JavaClassWrapper::singleton->Float_floatValue);
ret.push_back(val);
}
@@ -921,9 +925,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
double val = env->CallDoubleMethod(o, JavaClassWrapper::singleton->Double_doubleValue);
ret.push_back(val);
}
@@ -942,9 +946,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
for (int i = 0; i < count; i++) {
jobject o = env->GetObjectArrayElement(arr, i);
- if (!o)
+ if (!o) {
ret.push_back(Variant());
- else {
+ } else {
String val = jstring_to_string((jstring)o, env);
ret.push_back(val);
}
@@ -962,8 +966,9 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
}
Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
- if (class_cache.has(p_class))
+ if (class_cache.has(p_class)) {
return class_cache[p_class];
+ }
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, Ref<JavaClass>());
@@ -971,10 +976,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
jclass bclass = env->FindClass(p_class.utf8().get_data());
ERR_FAIL_COND_V(!bclass, Ref<JavaClass>());
- //jmethodID getDeclaredMethods = env->GetMethodID(bclass,"getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
-
- //ERR_FAIL_COND_V(!getDeclaredMethods,Ref<JavaClass>());
-
jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, getDeclaredMethods);
ERR_FAIL_COND_V(!methods, Ref<JavaClass>());
@@ -1057,8 +1058,9 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
float new_likeliness = 0;
float existing_likeliness = 0;
- if (E->get().param_types.size() != mi.param_types.size())
+ if (E->get().param_types.size() != mi.param_types.size()) {
continue;
+ }
bool valid = true;
for (int j = 0; j < E->get().param_types.size(); j++) {
Variant::Type _new;
@@ -1075,8 +1077,9 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
existing_likeliness = existing_l;
}
- if (!valid)
+ if (!valid) {
continue;
+ }
if (new_likeliness > existing_likeliness) {
java_class->methods[str_method].erase(E);
@@ -1087,10 +1090,11 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
}
if (!discard) {
- if (mi._static)
+ if (mi._static) {
mi.method = env->GetStaticMethodID(bclass, str_method.utf8().get_data(), signature.utf8().get_data());
- else
+ } else {
mi.method = env->GetMethodID(bclass, str_method.utf8().get_data(), signature.utf8().get_data());
+ }
ERR_CONTINUE(!mi.method);
@@ -1100,7 +1104,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
env->DeleteLocalRef(obj);
env->DeleteLocalRef(param_types);
env->DeleteLocalRef(return_type);
- };
+ }
env->DeleteLocalRef(methods);
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index e0a535f16e..d6e3ad90b1 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "java_godot_io_wrapper.h"
+
#include "core/error/error_list.h"
// JNIEnv is only valid within the thread it belongs to, in a multi threading environment
@@ -53,6 +54,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
+ _get_screen_refresh_rate = p_env->GetMethodID(cls, "getScreenRefreshRate", "(D)D");
_screen_get_usable_rect = p_env->GetMethodID(cls, "screenGetUsableRect", "()[I"),
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");
@@ -136,6 +138,19 @@ int GodotIOJavaWrapper::get_screen_dpi() {
}
}
+float GodotIOJavaWrapper::get_screen_refresh_rate(float fallback) {
+ if (_get_screen_refresh_rate) {
+ JNIEnv *env = get_jni_env();
+ if (env == nullptr) {
+ ERR_PRINT("An error occurred while trying to get screen refresh rate.");
+ return fallback;
+ }
+ return (float)env->CallDoubleMethod(godot_io_instance, _get_screen_refresh_rate, (double)fallback);
+ }
+ ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
+ return fallback;
+}
+
void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) {
if (_screen_get_usable_rect) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index c96abf1101..38a2b710a9 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -51,6 +51,7 @@ private:
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
jmethodID _get_screen_DPI = 0;
+ jmethodID _get_screen_refresh_rate = 0;
jmethodID _screen_get_usable_rect = 0;
jmethodID _get_unique_id = 0;
jmethodID _show_keyboard = 0;
@@ -71,6 +72,7 @@ public:
String get_locale();
String get_model();
int get_screen_dpi();
+ float get_screen_refresh_rate(float fallback);
void screen_get_usable_rect(int (&p_rect_xywh)[4]);
String get_unique_id();
bool has_vk();
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index e7ab0ef7ed..249717921f 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -200,8 +200,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) {
- if (step.get() == 0)
+ if (step.get() == 0) {
return;
+ }
if (DisplayServerAndroid *dsa = Object::cast_to<DisplayServerAndroid>(DisplayServer::get_singleton())) {
dsa->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST);
@@ -209,8 +210,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jcl
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) {
- if (step.get() == -1)
+ if (step.get() == -1) {
return;
+ }
if (step.get() == 0) {
// Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id,
@@ -243,8 +245,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl
}
void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
Vector<AndroidInputHandler::TouchPos> points;
for (int i = 0; i < pointer_count; i++) {
@@ -279,32 +282,36 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FIFF(JNI
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
input_handler->process_hover(p_type, Point2(p_x, p_y));
}
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, jint p_x, jint p_y) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
input_handler->process_double_tap(p_button_mask, Point2(p_x, p_y));
}
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
input_handler->process_scroll(Point2(p_x, p_y));
}
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
AndroidInputHandler::JoypadEvent jevent;
jevent.device = p_device;
@@ -369,8 +376,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
input_handler->process_key_event(p_keycode, p_scancode, p_unicode_char, p_pressed);
}
@@ -392,15 +400,17 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
os_android->main_loop_focusin();
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
os_android->main_loop_focusout();
}
@@ -426,16 +436,17 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en
for (int i = 0; i < count; i++) {
jobject obj = env->GetObjectArrayElement(params, i);
Variant v;
- if (obj)
+ if (obj) {
v = _jobject_to_variant(env, obj);
+ }
memnew_placement(&vlist[i], Variant);
vlist[i] = v;
vptr[i] = &vlist[i];
env->DeleteLocalRef(obj);
- };
+ }
Callable::CallError err;
- obj->call(str_method, (const Variant **)vptr, count, err);
+ obj->callp(str_method, (const Variant **)vptr, count, err);
// something
env->PopLocalFrame(nullptr);
@@ -451,17 +462,20 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *
String str_method = jstring_to_string(method, env);
int count = env->GetArrayLength(params);
- Variant args[VARIANT_ARG_MAX];
- for (int i = 0; i < MIN(count, VARIANT_ARG_MAX); i++) {
+ Variant *args = (Variant *)alloca(sizeof(Variant) * count);
+ const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * count);
+
+ for (int i = 0; i < count; i++) {
jobject obj = env->GetObjectArrayElement(params, i);
- if (obj)
+ if (obj) {
args[i] = _jobject_to_variant(env, obj);
+ }
env->DeleteLocalRef(obj);
- };
+ argptrs[i] = &args[i];
+ }
- static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8");
- obj->call_deferred(str_method, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
+ MessageQueue::get_singleton()->push_callp(obj, str_method, (const Variant **)argptrs, count);
// something
env->PopLocalFrame(nullptr);
}
@@ -478,8 +492,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
if (os_android->get_main_loop()) {
os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);
@@ -487,8 +502,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz) {
- if (step.get() <= 0)
+ if (step.get() <= 0) {
return;
+ }
if (os_android->get_main_loop()) {
os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 115264d7ee..754267c834 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -66,6 +66,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_get_GLES_version_code = p_env->GetMethodID(godot_class, "getGLESVersionCode", "()I");
_get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;");
_set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
+ _has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z");
_request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z");
_request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z");
_get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;");
@@ -91,8 +92,9 @@ jobject GodotJavaWrapper::get_activity() {
jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) {
if (godot_class) {
- if (p_env == nullptr)
+ if (p_env == nullptr) {
p_env = get_jni_env();
+ }
ERR_FAIL_COND_V(p_env == nullptr, nullptr);
@@ -128,8 +130,9 @@ GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() {
void GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
if (_on_video_init) {
- if (p_env == nullptr)
+ if (p_env == nullptr) {
p_env = get_jni_env();
+ }
ERR_FAIL_COND(p_env == nullptr);
p_env->CallVoidMethod(godot_instance, _on_video_init);
@@ -157,8 +160,9 @@ void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
void GodotJavaWrapper::restart(JNIEnv *p_env) {
if (_restart) {
- if (p_env == nullptr)
+ if (p_env == nullptr) {
p_env = get_jni_env();
+ }
ERR_FAIL_COND(p_env == nullptr);
p_env->CallVoidMethod(godot_instance, _restart);
@@ -167,8 +171,9 @@ void GodotJavaWrapper::restart(JNIEnv *p_env) {
void GodotJavaWrapper::force_quit(JNIEnv *p_env) {
if (_finish) {
- if (p_env == nullptr)
+ if (p_env == nullptr) {
p_env = get_jni_env();
+ }
ERR_FAIL_COND(p_env == nullptr);
p_env->CallVoidMethod(godot_instance, _finish);
@@ -248,6 +253,21 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) {
}
}
+bool GodotJavaWrapper::has_has_clipboard() {
+ return _has_clipboard != 0;
+}
+
+bool GodotJavaWrapper::has_clipboard() {
+ if (_has_clipboard) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
+
+ return env->CallBooleanMethod(godot_instance, _has_clipboard);
+ } else {
+ return false;
+ }
+}
+
bool GodotJavaWrapper::request_permission(const String &p_name) {
if (_request_permission) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 4d058ac426..42ae91480f 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -58,6 +58,7 @@ private:
jmethodID _get_GLES_version_code = 0;
jmethodID _get_clipboard = 0;
jmethodID _set_clipboard = 0;
+ jmethodID _has_clipboard = 0;
jmethodID _request_permission = 0;
jmethodID _request_permissions = 0;
jmethodID _get_granted_permissions = 0;
@@ -92,6 +93,8 @@ public:
String get_clipboard();
bool has_set_clipboard();
void set_clipboard(const String &p_text);
+ bool has_has_clipboard();
+ bool has_clipboard();
bool request_permission(const String &p_name);
bool request_permissions();
Vector<String> get_granted_permissions() const;
diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp
index 7e81565d9d..e2573d10f8 100644
--- a/platform/android/jni_utils.cpp
+++ b/platform/android/jni_utils.cpp
@@ -46,7 +46,7 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a
env->DeleteLocalRef(bclass);
} else {
v.val.z = *p_arg;
- };
+ }
} break;
case Variant::INT: {
if (force_jobject) {
@@ -61,7 +61,7 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a
} else {
v.val.i = *p_arg;
- };
+ }
} break;
case Variant::FLOAT: {
if (force_jobject) {
@@ -76,7 +76,7 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a
} else {
v.val.f = *p_arg;
- };
+ }
} break;
case Variant::STRING: {
String s = *p_arg;
@@ -111,7 +111,7 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a
jstring str = env->NewStringUTF(String(keys[j]).utf8().get_data());
env->SetObjectArrayElement(jkeys, j, str);
env->DeleteLocalRef(str);
- };
+ }
jmethodID set_keys = env->GetMethodID(dclass, "set_keys", "([Ljava/lang/String;)V");
jvalue val;
@@ -128,7 +128,7 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a
if (v.obj) {
env->DeleteLocalRef(v.obj);
}
- };
+ }
jmethodID set_values = env->GetMethodID(dclass, "set_values", "([Ljava/lang/Object;)V");
val.l = jvalues;
@@ -205,7 +205,7 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
if (name == "java.lang.String") {
return jstring_to_string((jstring)obj, env);
- };
+ }
if (name == "[Ljava.lang.String;") {
jobjectArray arr = (jobjectArray)obj;
@@ -219,20 +219,20 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
}
return sarr;
- };
+ }
if (name == "java.lang.Boolean") {
jmethodID boolValue = env->GetMethodID(c, "booleanValue", "()Z");
bool ret = env->CallBooleanMethod(obj, boolValue);
return ret;
- };
+ }
if (name == "java.lang.Integer" || name == "java.lang.Long") {
jclass nclass = env->FindClass("java/lang/Number");
jmethodID longValue = env->GetMethodID(nclass, "longValue", "()J");
jlong ret = env->CallLongMethod(obj, longValue);
return ret;
- };
+ }
if (name == "[I") {
jintArray arr = (jintArray)obj;
@@ -243,7 +243,7 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
int *w = sarr.ptrw();
env->GetIntArrayRegion(arr, 0, fCount, w);
return sarr;
- };
+ }
if (name == "[B") {
jbyteArray arr = (jbyteArray)obj;
@@ -254,14 +254,14 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
uint8_t *w = sarr.ptrw();
env->GetByteArrayRegion(arr, 0, fCount, reinterpret_cast<signed char *>(w));
return sarr;
- };
+ }
if (name == "java.lang.Float" || name == "java.lang.Double") {
jclass nclass = env->FindClass("java/lang/Number");
jmethodID doubleValue = env->GetMethodID(nclass, "doubleValue", "()D");
double ret = env->CallDoubleMethod(obj, doubleValue);
return ret;
- };
+ }
if (name == "[D") {
jdoubleArray arr = (jdoubleArray)obj;
@@ -275,9 +275,9 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
double n;
env->GetDoubleArrayRegion(arr, i, 1, &n);
w[i] = n;
- };
+ }
return sarr;
- };
+ }
if (name == "[F") {
jfloatArray arr = (jfloatArray)obj;
@@ -291,9 +291,9 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
float n;
env->GetFloatArrayRegion(arr, i, 1, &n);
w[i] = n;
- };
+ }
return sarr;
- };
+ }
if (name == "[Ljava.lang.Object;") {
jobjectArray arr = (jobjectArray)obj;
@@ -308,7 +308,7 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
}
return varr;
- };
+ }
if (name == "java.util.HashMap" || name == "org.godotengine.godot.Dictionary") {
Dictionary ret;
@@ -327,10 +327,10 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
for (int i = 0; i < keys.size(); i++) {
ret[keys[i]] = vals[i];
- };
+ }
return ret;
- };
+ }
env->DeleteLocalRef(c);
@@ -359,8 +359,9 @@ Variant::Type get_jni_type(const String &p_type) {
int idx = 0;
while (_type_to_vtype[idx].name) {
- if (p_type == _type_to_vtype[idx].name)
+ if (p_type == _type_to_vtype[idx].name) {
return _type_to_vtype[idx].type;
+ }
idx++;
}
@@ -390,8 +391,9 @@ const char *get_jni_sig(const String &p_type) {
int idx = 0;
while (_type_to_vtype[idx].name) {
- if (p_type == _type_to_vtype[idx].name)
+ if (p_type == _type_to_vtype[idx].name) {
return _type_to_vtype[idx].sig;
+ }
idx++;
}
diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp
index 620df539e4..a65e7c6724 100644
--- a/platform/android/net_socket_android.cpp
+++ b/platform/android/net_socket_android.cpp
@@ -77,18 +77,21 @@ NetSocketAndroid::~NetSocketAndroid() {
void NetSocketAndroid::close() {
NetSocketPosix::close();
- if (wants_broadcast)
+ if (wants_broadcast) {
multicast_lock_release();
- if (multicast_groups)
+ }
+ if (multicast_groups) {
multicast_lock_release();
+ }
wants_broadcast = false;
multicast_groups = 0;
}
Error NetSocketAndroid::set_broadcasting_enabled(bool p_enabled) {
Error err = NetSocketPosix::set_broadcasting_enabled(p_enabled);
- if (err != OK)
+ if (err != OK) {
return err;
+ }
if (p_enabled != wants_broadcast) {
if (p_enabled) {
@@ -105,11 +108,13 @@ Error NetSocketAndroid::set_broadcasting_enabled(bool p_enabled) {
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)
+ if (err != OK) {
return err;
+ }
- if (!multicast_groups)
+ if (!multicast_groups) {
multicast_lock_acquire();
+ }
multicast_groups++;
return OK;
@@ -117,14 +122,16 @@ Error NetSocketAndroid::join_multicast_group(const IPAddress &p_multi_address, S
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)
+ if (err != OK) {
return err;
+ }
ERR_FAIL_COND_V(multicast_groups == 0, ERR_BUG);
multicast_groups--;
- if (!multicast_groups)
+ if (!multicast_groups) {
multicast_lock_release();
+ }
return OK;
}
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index d1672d6ea3..b17b0f3139 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -81,17 +81,18 @@ void OS_Android::alert(const String &p_alert, const String &p_title) {
void OS_Android::initialize_core() {
OS_Unix::initialize_core();
- if (use_apk_expansion)
+ if (use_apk_expansion) {
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
- else {
+ } else {
FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES);
}
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
- if (use_apk_expansion)
+ if (use_apk_expansion) {
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
- else
+ } else {
DirAccess::make_default<DirAccessJAndroid>(DirAccess::ACCESS_RESOURCES);
+ }
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
@@ -162,20 +163,23 @@ MainLoop *OS_Android::get_main_loop() const {
}
void OS_Android::main_loop_begin() {
- if (main_loop)
+ if (main_loop) {
main_loop->initialize();
+ }
}
bool OS_Android::main_loop_iterate() {
- if (!main_loop)
+ if (!main_loop) {
return false;
+ }
DisplayServerAndroid::get_singleton()->process_events();
return Main::iteration();
}
void OS_Android::main_loop_end() {
- if (main_loop)
+ if (main_loop) {
main_loop->finalize();
+ }
}
void OS_Android::main_loop_focusout() {
@@ -207,8 +211,9 @@ String OS_Android::get_locale() const {
String OS_Android::get_model_name() const {
String model = godot_io_java->get_model();
- if (!model.is_empty())
+ if (!model.is_empty()) {
return model;
+ }
return OS_Unix::get_model_name();
}
@@ -218,8 +223,9 @@ String OS_Android::get_data_path() const {
}
String OS_Android::get_user_data_dir() const {
- if (!data_dir_cache.is_empty())
+ if (!data_dir_cache.is_empty()) {
return data_dir_cache;
+ }
String data_dir = godot_io_java->get_user_data_dir();
if (!data_dir.is_empty()) {
@@ -230,8 +236,9 @@ String OS_Android::get_user_data_dir() const {
}
String OS_Android::get_cache_path() const {
- if (!cache_dir_cache.is_empty())
+ if (!cache_dir_cache.is_empty()) {
return cache_dir_cache;
+ }
String cache_dir = godot_io_java->get_cache_dir();
if (!cache_dir.is_empty()) {
@@ -243,8 +250,9 @@ String OS_Android::get_cache_path() const {
String OS_Android::get_unique_id() const {
String unique_id = godot_io_java->get_unique_id();
- if (!unique_id.is_empty())
+ if (!unique_id.is_empty()) {
return unique_id;
+ }
return OS::get_unique_id();
}
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
index 2207eec18d..158512803a 100644
--- a/platform/android/plugin/godot_plugin_jni.cpp
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -114,10 +114,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS
String signal_name = jstring_to_string(j_signal_name, env);
int count = env->GetArrayLength(j_signal_params);
- ERR_FAIL_COND_MSG(count > VARIANT_ARG_MAX, "Maximum argument count exceeded!");
- Variant variant_params[VARIANT_ARG_MAX];
- const Variant *args[VARIANT_ARG_MAX];
+ Variant *variant_params = (Variant *)alloca(sizeof(Variant) * count);
+ const Variant **args = (const Variant **)alloca(sizeof(Variant *) * count);
for (int i = 0; i < count; i++) {
jobject j_param = env->GetObjectArrayElement(j_signal_params, i);
@@ -126,7 +125,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS
env->DeleteLocalRef(j_param);
};
- singleton->emit_signal(SNAME(signal_name), args, count);
+ singleton->emit_signalp(StringName(signal_name), args, count);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths) {
diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm
index 5130a26f15..c5c9b5a5f9 100644
--- a/platform/iphone/app_delegate.mm
+++ b/platform/iphone/app_delegate.mm
@@ -29,6 +29,7 @@
/*************************************************************************/
#import "app_delegate.h"
+
#include "core/config/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#import "godot_view.h"
@@ -76,7 +77,7 @@ static ViewController *mainViewController = nil;
// bail, things did not go very well for us, should probably output a message on screen with our error code...
exit(0);
return NO;
- };
+ }
ViewController *viewController = [[ViewController alloc] init];
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
@@ -99,7 +100,7 @@ static ViewController *mainViewController = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
return YES;
-};
+}
- (void)onAudioInterruption:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
@@ -111,17 +112,17 @@ static ViewController *mainViewController = nil;
OSIPhone::get_singleton()->on_focus_in();
}
}
-};
+}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
}
-};
+}
- (void)applicationWillTerminate:(UIApplication *)application {
iphone_finish();
-};
+}
// When application goes to background (e.g. user switches to another app or presses Home),
// then applicationWillResignActive -> applicationDidEnterBackground are called.
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index 4e4b0d81c3..f442235e7c 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -120,7 +120,6 @@ def configure(env):
)
)
env.Append(CPPDEFINES=["NEED_LONG_INT"])
- env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
# Disable exceptions on non-tools (template) builds
if not env["tools"]:
diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm
index 7448dfed4a..92e81448ac 100644
--- a/platform/iphone/display_layer.mm
+++ b/platform/iphone/display_layer.mm
@@ -29,6 +29,7 @@
/*************************************************************************/
#import "display_layer.h"
+
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "display_server_iphone.h"
@@ -153,17 +154,6 @@
return NO;
}
- // if (OS::get_singleton()) {
- // OS::VideoMode vm;
- // vm.fullscreen = true;
- // vm.width = backingWidth;
- // vm.height = backingHeight;
- // vm.resizable = false;
- // OS::get_singleton()->set_video_mode(vm);
- // OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
- // };
- // gl_view_base_fb = viewFramebuffer;
-
return YES;
}
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
index de04bc88e3..7441550f67 100644
--- a/platform/iphone/display_server_iphone.h
+++ b/platform/iphone/display_server_iphone.h
@@ -129,12 +129,15 @@ public:
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
virtual WindowID
get_window_at_screen_position(const Point2i &p_position) const override;
+ virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
index 3047d91a4d..a0f8daf5a0 100644
--- a/platform/iphone/display_server_iphone.mm
+++ b/platform/iphone/display_server_iphone.mm
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "display_server_iphone.h"
+
#import "app_delegate.h"
#include "core/config/project_settings.h"
#include "core/io/file_access_pack.h"
@@ -231,7 +232,7 @@ void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_presse
ev->set_position(Vector2(p_x, p_y));
perform_event(ev);
}
-};
+}
void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
if (!GLOBAL_DEF("debug/disable_touch", false)) {
@@ -241,16 +242,16 @@ void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int
ev->set_position(Vector2(p_x, p_y));
ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
perform_event(ev);
- };
-};
+ }
+}
void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) {
Input::get_singleton()->parse_input_event(p_event);
-};
+}
void DisplayServerIPhone::touches_cancelled(int p_idx) {
touch_press(p_idx, -1, -1, false, false);
-};
+}
// MARK: Keyboard
@@ -263,13 +264,13 @@ void DisplayServerIPhone::key(Key p_key, bool p_pressed) {
ev->set_physical_keycode(p_key);
ev->set_unicode((char32_t)p_key);
perform_event(ev);
-};
+}
// MARK: Motion
void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
-};
+}
void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
// Found out the Z should not be negated! Pass as is!
@@ -279,21 +280,20 @@ void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, float p_z)
p_z / kDisplayServerIPhoneAcceleration);
Input::get_singleton()->set_accelerometer(v_accelerometer);
-};
+}
void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
-};
+}
void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
-};
+}
// MARK: -
bool DisplayServerIPhone::has_feature(Feature p_feature) const {
switch (p_feature) {
- // case FEATURE_CONSOLE_WINDOW:
// case FEATURE_CURSOR_SHAPE:
// case FEATURE_CUSTOM_CURSOR_SHAPE:
// case FEATURE_GLOBAL_MENU:
@@ -394,6 +394,10 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
}
}
+float DisplayServerIPhone::screen_get_refresh_rate(int p_screen) const {
+ return [UIScreen mainScreen].maximumFramesPerSecond;
+}
+
float DisplayServerIPhone::screen_get_scale(int p_screen) const {
return [UIScreen mainScreen].nativeScale;
}
@@ -408,6 +412,24 @@ DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const
return MAIN_WINDOW_ID;
}
+int64_t DisplayServerIPhone::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+ ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
+ switch (p_handle_type) {
+ case DISPLAY_HANDLE: {
+ return 0; // Not supported.
+ }
+ case WINDOW_HANDLE: {
+ return (int64_t)AppDelegate.viewController;
+ }
+ case WINDOW_VIEW: {
+ return (int64_t)AppDelegate.viewController.godotView;
+ }
+ default: {
+ return 0;
+ }
+ }
+}
+
void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
window_attached_instance_id = p_instance;
}
@@ -499,7 +521,7 @@ void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) {
float DisplayServerIPhone::screen_get_max_scale() const {
return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
-};
+}
void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
screen_orientation = p_orientation;
diff --git a/platform/iphone/export/export_plugin.cpp b/platform/iphone/export/export_plugin.cpp
index 0ad68086a7..ac5886e620 100644
--- a/platform/iphone/export/export_plugin.cpp
+++ b/platform/iphone/export/export_plugin.cpp
@@ -30,13 +30,12 @@
#include "export_plugin.h"
+#include "editor/editor_node.h"
+
void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name");
- r_features->push_back("pvrtc");
- if (driver == "vulkan") {
- // FIXME: Review if this is correct.
- r_features->push_back("etc2");
- }
+ // Vulkan and OpenGL ES 3.0 both mandate ETC2 support.
+ r_features->push_back("etc2");
Vector<String> architectures = _get_preset_architectures(p_preset);
for (int i = 0; i < architectures.size(); ++i) {
@@ -46,7 +45,6 @@ void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset>
Vector<EditorExportPlatformIOS::ExportArchitecture> EditorExportPlatformIOS::_get_supported_architectures() {
Vector<ExportArchitecture> archs;
- archs.push_back(ExportArchitecture("armv7", false)); // Disabled by default, not included in official templates.
archs.push_back(ExportArchitecture("arm64", true));
return archs;
}
@@ -86,7 +84,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "iPhone Developer"));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), ""));
@@ -94,13 +92,10 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
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"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0"));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
Vector<PluginConfigIOS> found_plugins = get_plugins();
for (int i = 0; i < found_plugins.size(); i++) {
@@ -141,20 +136,23 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
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::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
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::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "icons/generate_missing"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with Retina display
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with Retina HD display
- 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
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with Retina display
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with retina HD display
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with retina display
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with retina display
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with Retina display
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0));
@@ -163,8 +161,6 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "launch_screens/generate_missing"), false));
-
for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) {
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png"), ""));
}
@@ -183,6 +179,10 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
"scaleAspectFill",
"scaleToFill"
};
+ String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : p_preset->get("application/code_sign_identity_debug");
+ String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release");
+ bool dbg_manual = !p_preset->get("application/provisioning_profile_uuid_debug").operator String().is_empty() || (dbg_sign_id != "iPhone Developer");
+ bool rel_manual = !p_preset->get("application/provisioning_profile_uuid_release").operator String().is_empty() || (rel_sign_id != "iPhone Distribution");
String str;
String strnew;
str.parse_utf8((const char *)pfile.ptr(), pfile.size());
@@ -200,8 +200,6 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n";
} else if (lines[i].find("$name") != -1) {
strnew += lines[i].replace("$name", p_config.pkg_name) + "\n";
- } else if (lines[i].find("$info") != -1) {
- strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n";
} else if (lines[i].find("$bundle_identifier") != -1) {
strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n";
} else if (lines[i].find("$short_version") != -1) {
@@ -210,8 +208,6 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
strnew += lines[i].replace("$version", p_preset->get("application/version")) + "\n";
} else if (lines[i].find("$signature") != -1) {
strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n";
- } else if (lines[i].find("$copyright") != -1) {
- 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) {
@@ -223,13 +219,25 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get("application/provisioning_profile_uuid_release")) + "\n";
} else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) {
strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get("application/provisioning_profile_uuid_debug")) + "\n";
+ } else if (lines[i].find("$code_sign_style_debug") != -1) {
+ if (dbg_manual) {
+ strnew += lines[i].replace("$code_sign_style_debug", "Manual") + "\n";
+ } else {
+ strnew += lines[i].replace("$code_sign_style_debug", "Automatic") + "\n";
+ }
+ } else if (lines[i].find("$code_sign_style_release") != -1) {
+ if (rel_manual) {
+ strnew += lines[i].replace("$code_sign_style_release", "Manual") + "\n";
+ } else {
+ strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n";
+ }
} else if (lines[i].find("$provisioning_profile_uuid") != -1) {
String uuid = p_debug ? p_preset->get("application/provisioning_profile_uuid_debug") : p_preset->get("application/provisioning_profile_uuid_release");
strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n";
} else if (lines[i].find("$code_sign_identity_debug") != -1) {
- strnew += lines[i].replace("$code_sign_identity_debug", p_preset->get("application/code_sign_identity_debug")) + "\n";
+ strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n";
} else if (lines[i].find("$code_sign_identity_release") != -1) {
- strnew += lines[i].replace("$code_sign_identity_release", p_preset->get("application/code_sign_identity_release")) + "\n";
+ strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n";
} else if (lines[i].find("$additional_plist_content") != -1) {
strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n";
} else if (lines[i].find("$godot_archs") != -1) {
@@ -377,6 +385,36 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
String value = value_format.format(value_dictionary, "$_");
strnew += lines[i].replace("$launch_screen_background_color", value) + "\n";
+ } else if (lines[i].find("$pbx_locale_file_reference") != -1) {
+ String locale_files;
+ Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations");
+ if (translations.size() > 0) {
+ int index = 0;
+ for (const String &E : translations) {
+ Ref<Translation> tr = ResourceLoader::load(E);
+ if (tr.is_valid()) {
+ String lang = tr->get_locale();
+ locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"<group>\"; };";
+ }
+ index++;
+ }
+ }
+ strnew += lines[i].replace("$pbx_locale_file_reference", locale_files);
+ } else if (lines[i].find("$pbx_locale_build_reference") != -1) {
+ String locale_files;
+ Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations");
+ if (translations.size() > 0) {
+ int index = 0;
+ for (const String &E : translations) {
+ Ref<Translation> tr = ResourceLoader::load(E);
+ if (tr.is_valid()) {
+ String lang = tr->get_locale();
+ locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,";
+ }
+ index++;
+ }
+ }
+ strnew += lines[i].replace("$pbx_locale_build_reference", locale_files);
} else {
strnew += lines[i] + "\n";
}
@@ -466,33 +504,33 @@ struct IconInfo {
const char *actual_size_side;
const char *scale;
const char *unscaled_size;
- bool is_required = false;
};
static const IconInfo icon_infos[] = {
- { "required_icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60", true },
- { "required_icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40", true },
-
- { "required_icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76", true },
- { "required_icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", true },
-
- { "optional_icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60", false },
-
- { "optional_icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76", false },
-
- { "optional_icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5", false },
-
- { "optional_icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40", false },
-
- { "optional_icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40", false },
- { "optional_icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40", false }
+ // Home screen on iPhone
+ { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60" },
+ { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40" },
+ { "icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60" },
+
+ // Home screen on iPad
+ { "icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76" },
+ { "icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76" },
+ { "icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5" },
+
+ // App Store
+ { "icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024" },
+
+ // Spotlight
+ { "icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40" },
+ { "icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40" },
+ { "icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40" }
};
Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) {
String json_description = "{\"images\":[";
String sizes;
- DirAccess *da = DirAccess::open(p_iconset_dir);
+ DirAccessRef da = DirAccess::open(p_iconset_dir);
ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'.");
for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) {
@@ -500,35 +538,23 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
int side_size = String(info.actual_size_side).to_int();
String icon_path = p_preset->get(info.preset_key);
if (icon_path.length() == 0) {
- if ((bool)p_preset->get("icons/generate_missing")) {
- // Resize main app icon
- icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
- Ref<Image> img = memnew(Image);
- Error err = ImageLoader::load_image(icon_path, img);
- if (err != OK) {
- ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'.");
- return ERR_UNCONFIGURED;
- }
- img->resize(side_size, side_size);
- err = img->save_png(p_iconset_dir + info.export_name);
- if (err) {
- String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
- ERR_PRINT(err_str.utf8().get_data());
- return err;
- }
- } else {
- if (info.is_required) {
- String err_str = String("Required icon (") + info.preset_key + ") is not specified in the preset.";
- ERR_PRINT(err_str);
- return ERR_UNCONFIGURED;
- } else {
- String err_str = String("Icon (") + info.preset_key + ") is not specified in the preset.";
- WARN_PRINT(err_str);
- }
- continue;
+ // Resize main app icon
+ icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
+ Ref<Image> img = memnew(Image);
+ Error err = ImageLoader::load_image(icon_path, img);
+ if (err != OK) {
+ ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'.");
+ return ERR_UNCONFIGURED;
+ }
+ img->resize(side_size, side_size);
+ err = img->save_png(p_iconset_dir + info.export_name);
+ if (err) {
+ String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
+ ERR_PRINT(err_str.utf8().get_data());
+ return err;
}
} else {
- // Load custom icon
+ // Load custom icon and resize if required
Ref<Image> img = memnew(Image);
Error err = ImageLoader::load_image(icon_path, img);
if (err != OK) {
@@ -536,13 +562,14 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
return ERR_UNCONFIGURED;
}
if (img->get_width() != side_size || img->get_height() != side_size) {
- ERR_PRINT("Invalid icon size (" + String(info.preset_key) + "): '" + icon_path + "'.");
- return ERR_UNCONFIGURED;
+ WARN_PRINT("Icon (" + String(info.preset_key) + "): '" + icon_path + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(side_size) + "x" + String::num_int64(side_size) + ".");
+ img->resize(side_size, side_size);
+ err = img->save_png(p_iconset_dir + info.export_name);
+ } else {
+ err = da->copy(icon_path, p_iconset_dir + info.export_name);
}
- err = da->copy(icon_path, p_iconset_dir + info.export_name);
if (err) {
- memdelete(da);
String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
ERR_PRINT(err_str.utf8().get_data());
return err;
@@ -560,7 +587,6 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
json_description += String("}");
}
json_description += "]}";
- memdelete(da);
FileAccess *json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE);
ERR_FAIL_COND_V(!json_file, ERR_CANT_CREATE);
@@ -646,14 +672,19 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor
}
Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
- DirAccess *da = DirAccess::open(p_dest_dir);
+ DirAccessRef da = DirAccess::open(p_dest_dir);
ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'.");
for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) {
LoadingScreenInfo info = loading_screen_infos[i];
String loading_screen_file = p_preset->get(info.preset_key);
+
+ Color boot_bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color");
+ String boot_logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
+ bool boot_logo_scale = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize");
+
if (loading_screen_file.size() > 0) {
- // Load custom loading screens
+ // Load custom loading screens, and resize if required.
Ref<Image> img = memnew(Image);
Error err = ImageLoader::load_image(loading_screen_file, img);
if (err != OK) {
@@ -661,22 +692,30 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExp
return ERR_UNCONFIGURED;
}
if (img->get_width() != info.width || img->get_height() != info.height) {
- ERR_PRINT("Invalid loading screen size (" + String(info.preset_key) + "): '" + loading_screen_file + "'.");
- return ERR_UNCONFIGURED;
+ WARN_PRINT("Loading screen (" + String(info.preset_key) + "): '" + loading_screen_file + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(info.width) + "x" + String::num_int64(info.height) + ".");
+ float aspect_ratio = (float)img->get_width() / (float)img->get_height();
+ if (boot_logo_scale) {
+ if (info.height * aspect_ratio <= info.width) {
+ img->resize(info.height * aspect_ratio, info.height);
+ } else {
+ img->resize(info.width, info.width / aspect_ratio);
+ }
+ }
+ Ref<Image> new_img = memnew(Image);
+ new_img->create(info.width, info.height, false, Image::FORMAT_RGBA8);
+ new_img->fill(boot_bg_color);
+ _blend_and_rotate(new_img, img, false);
+ err = new_img->save_png(p_dest_dir + info.export_name);
+ } else {
+ err = da->copy(loading_screen_file, p_dest_dir + info.export_name);
}
- err = da->copy(loading_screen_file, p_dest_dir + info.export_name);
if (err) {
- memdelete(da);
String err_str = String("Failed to export loading screen (") + info.preset_key + ") from path '" + loading_screen_file + "'.";
ERR_PRINT(err_str.utf8().get_data());
return err;
}
- } else if ((bool)p_preset->get("launch_screens/generate_missing")) {
+ } else {
// Generate loading screen from the splash screen
- Color boot_bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color");
- String boot_logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
- bool boot_logo_scale = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize");
-
Ref<Image> img = memnew(Image);
img->create(info.width, info.height, false, Image::FORMAT_RGBA8);
img->fill(boot_bg_color);
@@ -716,12 +755,8 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExp
String err_str = String("Failed to export loading screen (") + info.preset_key + ") from splash screen.";
WARN_PRINT(err_str.utf8().get_data());
}
- } else {
- String err_str = String("No loading screen (") + info.preset_key + ") specified.";
- WARN_PRINT(err_str.utf8().get_data());
}
}
- memdelete(da);
return OK;
}
@@ -774,10 +809,18 @@ Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) {
if (p_file.ends_with(".dylib")) {
CodesignData *data = (CodesignData *)p_userdata;
print_line(String("Signing ") + p_file);
+
+ String sign_id;
+ if (data->debug) {
+ sign_id = data->preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : data->preset->get("application/code_sign_identity_debug");
+ } else {
+ sign_id = data->preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : data->preset->get("application/code_sign_identity_release");
+ }
+
List<String> codesign_args;
codesign_args.push_back("-f");
codesign_args.push_back("-s");
- codesign_args.push_back(data->preset->get(data->debug ? "application/code_sign_identity_debug" : "application/code_sign_identity_release"));
+ codesign_args.push_back(sign_id);
codesign_args.push_back(p_file);
return OS::get_singleton()->execute("codesign", codesign_args);
}
@@ -919,21 +962,15 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
}
Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
- DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
-
String binary_name = p_out_dir.get_file().get_basename();
- DirAccess *da = DirAccess::create_for_path(p_asset);
+ DirAccessRef da = DirAccess::create_for_path(p_asset);
if (!da) {
- memdelete(filesystem_da);
ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + ".");
}
bool file_exists = da->file_exists(p_asset);
bool dir_exists = da->dir_exists(p_asset);
if (!file_exists && !dir_exists) {
- memdelete(da);
- memdelete(filesystem_da);
return ERR_FILE_NOT_FOUND;
}
@@ -993,19 +1030,18 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String
destination = p_out_dir.plus_file(asset_path);
}
+ DirAccessRef filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
+
if (!filesystem_da->dir_exists(destination_dir)) {
Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
if (make_dir_err) {
- memdelete(da);
- memdelete(filesystem_da);
return make_dir_err;
}
}
Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
- memdelete(da);
if (err) {
- memdelete(filesystem_da);
return err;
}
IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed };
@@ -1070,8 +1106,6 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String
}
}
- memdelete(filesystem_da);
-
return OK;
}
@@ -1376,29 +1410,29 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
return ERR_FILE_BAD_PATH;
}
- DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (da) {
- String current_dir = da->get_current_dir();
-
- // remove leftovers from last export so they don't interfere
- // in case some files are no longer needed
- if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) {
- da->erase_contents_recursive();
- }
- if (da->change_dir(dest_dir + binary_name) == OK) {
- da->erase_contents_recursive();
- }
+ {
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (da) {
+ String current_dir = da->get_current_dir();
+
+ // remove leftovers from last export so they don't interfere
+ // in case some files are no longer needed
+ if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) {
+ da->erase_contents_recursive();
+ }
+ if (da->change_dir(dest_dir + binary_name) == OK) {
+ da->erase_contents_recursive();
+ }
- da->change_dir(current_dir);
+ da->change_dir(current_dir);
- if (!da->dir_exists(dest_dir + binary_name)) {
- Error err = da->make_dir(dest_dir + binary_name);
- if (err) {
- memdelete(da);
- return err;
+ if (!da->dir_exists(dest_dir + binary_name)) {
+ Error err = da->make_dir(dest_dir + binary_name);
+ if (err) {
+ return err;
+ }
}
}
- memdelete(da);
}
if (ep.step("Making .pck", 0)) {
@@ -1406,7 +1440,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
}
String pack_path = dest_dir + binary_name + ".pck";
Vector<SharedObject> libraries;
- Error err = save_pack(p_preset, pack_path, &libraries);
+ Error err = save_pack(p_preset, p_debug, pack_path, &libraries);
if (err) {
return err;
}
@@ -1419,9 +1453,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
print_line("Static framework: " + library_to_use);
String pkg_name;
- if (p_preset->get("application/name") != "") {
- pkg_name = p_preset->get("application/name"); // app_name
- } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
+ if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name"));
} else {
pkg_name = "Unnamed";
@@ -1456,7 +1488,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
Vector<IOSExportAsset> assets;
- DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir);
+ DirAccessRef tmp_app_path = DirAccess::create_for_path(dest_dir);
ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE);
print_line("Unzipping...");
@@ -1535,7 +1567,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
if (dir_err) {
ERR_PRINT("Can't create '" + dir_name + "'.");
unzClose(src_pkg_zip);
- memdelete(tmp_app_path);
return ERR_CANT_CREATE;
}
}
@@ -1545,7 +1576,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
if (!f) {
ERR_PRINT("Can't write '" + file + "'.");
unzClose(src_pkg_zip);
- memdelete(tmp_app_path);
return ERR_CANT_CREATE;
};
f->store_buffer(data.ptr(), data.size());
@@ -1568,10 +1598,53 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
if (!found_library) {
ERR_PRINT("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive.");
- memdelete(tmp_app_path);
return ERR_FILE_NOT_FOUND;
}
+ Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized");
+ Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized");
+ Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized");
+ Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized");
+
+ Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations");
+ if (translations.size() > 0) {
+ {
+ String fname = dest_dir + binary_name + "/en.lproj";
+ tmp_app_path->make_dir_recursive(fname);
+ FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ f->store_line("/* Localized versions of Info.plist keys */");
+ f->store_line("");
+ f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";");
+ f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";");
+ f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";");
+ f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";");
+ }
+
+ for (const String &E : translations) {
+ Ref<Translation> tr = ResourceLoader::load(E);
+ if (tr.is_valid()) {
+ String lang = tr->get_locale();
+ String fname = dest_dir + binary_name + "/" + lang + ".lproj";
+ tmp_app_path->make_dir_recursive(fname);
+ FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ f->store_line("/* Localized versions of Info.plist keys */");
+ f->store_line("");
+ if (appnames.has(lang)) {
+ f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
+ }
+ if (camera_usage_descriptions.has(lang)) {
+ f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (microphone_usage_descriptions.has(lang)) {
+ f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (photolibrary_usage_descriptions.has(lang)) {
+ f->store_line("NSPhotoLibraryUsageDescription = \"" + photolibrary_usage_descriptions[lang].operator String() + "\";");
+ }
+ }
+ }
+ }
+
// Copy project static libs to the project
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
for (int i = 0; i < export_plugins.size(); i++) {
@@ -1582,7 +1655,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
Error lib_copy_err = tmp_app_path->copy(static_lib_path, dest_lib_file_path);
if (lib_copy_err != OK) {
ERR_PRINT("Can't copy '" + static_lib_path + "'.");
- memdelete(tmp_app_path);
return lib_copy_err;
}
}
@@ -1593,7 +1665,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
if (!tmp_app_path->dir_exists(iconset_dir)) {
err = tmp_app_path->make_dir_recursive(iconset_dir);
}
- memdelete(tmp_app_path);
if (err) {
return err;
}
@@ -1603,43 +1674,43 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
return err;
}
- bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard");
+ {
+ bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard");
- String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/";
- String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/";
+ String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/";
+ String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/";
- DirAccess *launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ DirAccessRef launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (!launch_screen_da) {
- return ERR_CANT_CREATE;
- }
+ if (!launch_screen_da) {
+ return ERR_CANT_CREATE;
+ }
- if (use_storyboard) {
- print_line("Using Launch Storyboard");
+ if (use_storyboard) {
+ print_line("Using Launch Storyboard");
- if (launch_screen_da->change_dir(launch_image_path) == OK) {
- launch_screen_da->erase_contents_recursive();
- launch_screen_da->remove(launch_image_path);
- }
+ if (launch_screen_da->change_dir(launch_image_path) == OK) {
+ launch_screen_da->erase_contents_recursive();
+ launch_screen_da->remove(launch_image_path);
+ }
- err = _export_loading_screen_file(p_preset, splash_image_path);
- } else {
- print_line("Using Launch Images");
+ err = _export_loading_screen_file(p_preset, splash_image_path);
+ } else {
+ print_line("Using Launch Images");
- const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard";
+ const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard";
- launch_screen_da->remove(launch_screen_path);
+ launch_screen_da->remove(launch_screen_path);
- if (launch_screen_da->change_dir(splash_image_path) == OK) {
- launch_screen_da->erase_contents_recursive();
- launch_screen_da->remove(splash_image_path);
- }
+ if (launch_screen_da->change_dir(splash_image_path) == OK) {
+ launch_screen_da->erase_contents_recursive();
+ launch_screen_da->remove(splash_image_path);
+ }
- err = _export_loading_screen_images(p_preset, launch_image_path);
+ err = _export_loading_screen_images(p_preset, launch_image_path);
+ }
}
- memdelete(launch_screen_da);
-
if (err) {
return err;
}
@@ -1658,15 +1729,17 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
memdelete(f);
#ifdef OSX_ENABLED
- if (ep.step("Code-signing dylibs", 2)) {
- return ERR_SKIP;
+ {
+ if (ep.step("Code-signing dylibs", 2)) {
+ return ERR_SKIP;
+ }
+ DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs");
+ ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN);
+ CodesignData codesign_data(p_preset, p_debug);
+ err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data);
+ memdelete(dylibs_dir);
+ ERR_FAIL_COND_V(err, err);
}
- DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs");
- ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN);
- CodesignData codesign_data(p_preset, p_debug);
- err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data);
- memdelete(dylibs_dir);
- ERR_FAIL_COND_V(err, err);
if (ep.step("Making .xcarchive", 3)) {
return ERR_SKIP;
@@ -1684,6 +1757,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
archive_args.push_back("-destination");
archive_args.push_back("generic/platform=iOS");
archive_args.push_back("archive");
+ archive_args.push_back("-allowProvisioningUpdates");
archive_args.push_back("-archivePath");
archive_args.push_back(archive_path);
String archive_str;
@@ -1754,19 +1828,7 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
valid = false;
}
- for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) {
- IconInfo info = icon_infos[i];
- String icon_path = p_preset->get(info.preset_key);
- if (icon_path.length() == 0) {
- if (info.is_required) {
- err += TTR("Required icon is not specified in the preset.") + "\n";
- valid = false;
- }
- break;
- }
- }
-
- String etc_error = test_etc2_or_pvrtc();
+ const String etc_error = test_etc2();
if (!etc_error.is_empty()) {
valid = false;
err += etc_error;
diff --git a/platform/iphone/export/export_plugin.h b/platform/iphone/export/export_plugin.h
index 756bca14dd..c01983e39f 100644
--- a/platform/iphone/export/export_plugin.h
+++ b/platform/iphone/export/export_plugin.h
@@ -41,7 +41,6 @@
#include "core/templates/safe_refcount.h"
#include "core/version.h"
#include "editor/editor_export.h"
-#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "main/splash.gen.h"
#include "platform/iphone/logo.gen.h"
@@ -130,7 +129,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
for (int i = 0; i < pname.length(); i++) {
char32_t c = pname[i];
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
+ if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
}
diff --git a/platform/iphone/godot_iphone.mm b/platform/iphone/godot_iphone.mm
index a76276e815..49474ef554 100644
--- a/platform/iphone/godot_iphone.mm
+++ b/platform/iphone/godot_iphone.mm
@@ -53,7 +53,7 @@ int add_path(int p_argc, char **p_args) {
p_args[p_argc] = nullptr;
return p_argc;
-};
+}
int add_cmdline(int p_argc, char **p_args) {
NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
@@ -67,12 +67,12 @@ int add_cmdline(int p_argc, char **p_args) {
continue;
}
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
- };
+ }
p_args[p_argc] = nullptr;
return p_argc;
-};
+}
int iphone_main(int argc, char **argv, String data_dir, String cache_dir) {
size_t len = strlen(argv[0]);
@@ -103,7 +103,7 @@ int iphone_main(int argc, char **argv, String data_dir, String cache_dir) {
char *fargv[64];
for (int i = 0; i < argc; i++) {
fargv[i] = argv[i];
- };
+ }
fargv[argc] = nullptr;
argc = add_path(argc, fargv);
argc = add_cmdline(argc, fargv);
@@ -119,10 +119,10 @@ int iphone_main(int argc, char **argv, String data_dir, String cache_dir) {
os->initialize_modules();
return 0;
-};
+}
void iphone_finish() {
printf("iphone_finish\n");
Main::cleanup();
delete os;
-};
+}
diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h
index 1c72a26b4a..fcb97fa63a 100644
--- a/platform/iphone/godot_view.h
+++ b/platform/iphone/godot_view.h
@@ -59,4 +59,9 @@ class String;
- (void)stopRendering;
- (void)startRendering;
+- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
+
@end
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
index b90c10fa84..e48dd2e507 100644
--- a/platform/iphone/godot_view.mm
+++ b/platform/iphone/godot_view.mm
@@ -29,6 +29,7 @@
/*************************************************************************/
#import "godot_view.h"
+
#include "core/os/keyboard.h"
#include "core/string/ustring.h"
#import "display_layer.h"
@@ -153,6 +154,8 @@ static const float earth_gravity = 9.80665;
[self initTouches];
+ self.multipleTouchEnabled = YES;
+
// Configure and start accelerometer
if (!self.motionManager) {
self.motionManager = [[CMMotionManager alloc] init];
@@ -226,8 +229,9 @@ static const float earth_gravity = 9.80665;
[self.displayLink setPaused:YES];
// Process all input events
- while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
- ;
+ while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) {
+ // Continue.
+ }
// We are good to go, resume the CADisplayLink
[self.displayLink setPaused:NO];
@@ -336,7 +340,7 @@ static const float earth_gravity = 9.80665;
}
}
-- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
+- (void)godotTouchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
@@ -349,7 +353,7 @@ static const float earth_gravity = 9.80665;
}
}
-- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
@@ -363,7 +367,7 @@ static const float earth_gravity = 9.80665;
}
}
-- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
@@ -377,7 +381,7 @@ static const float earth_gravity = 9.80665;
}
}
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm
index b50ba5f942..c8137f35ff 100644
--- a/platform/iphone/godot_view_gesture_recognizer.mm
+++ b/platform/iphone/godot_view_gesture_recognizer.mm
@@ -30,6 +30,8 @@
#import "godot_view_gesture_recognizer.h"
+#import "godot_view.h"
+
#include "core/config/project_settings.h"
// Minimum distance for touches to move to fire
@@ -58,6 +60,10 @@ const CGFloat kGLGestureMovementDistance = 0.5;
@implementation GodotViewGestureRecognizer
+- (GodotView *)godotView {
+ return (GodotView *)self.view;
+}
+
- (instancetype)init {
self = [super init];
@@ -104,7 +110,7 @@ const CGFloat kGLGestureMovementDistance = 0.5;
self.delayTimer = nil;
if (self.delayedTouches) {
- [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent];
+ [self.godotView godotTouchesBegan:self.delayedTouches withEvent:self.delayedEvent];
}
self.delayedTouches = nil;
@@ -114,6 +120,8 @@ const CGFloat kGLGestureMovementDistance = 0.5;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
[self delayTouches:cleared andEvent:event];
+
+ [super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
@@ -123,8 +131,8 @@ const CGFloat kGLGestureMovementDistance = 0.5;
// We should check if movement was significant enough to fire an event
// for dragging to work correctly.
for (UITouch *touch in cleared) {
- CGPoint from = [touch locationInView:self.view];
- CGPoint to = [touch previousLocationInView:self.view];
+ CGPoint from = [touch locationInView:self.godotView];
+ CGPoint to = [touch previousLocationInView:self.godotView];
CGFloat xDistance = from.x - to.x;
CGFloat yDistance = from.y - to.y;
@@ -133,7 +141,7 @@ const CGFloat kGLGestureMovementDistance = 0.5;
// Early exit, since one of touches has moved enough to fire a drag event.
if (distance > kGLGestureMovementDistance) {
[self.delayTimer fire];
- [self.view touchesMoved:cleared withEvent:event];
+ [self.godotView godotTouchesMoved:cleared withEvent:event];
return;
}
}
@@ -141,26 +149,32 @@ const CGFloat kGLGestureMovementDistance = 0.5;
return;
}
- [self.view touchesMoved:cleared withEvent:event];
+ [self.godotView godotTouchesMoved:cleared withEvent:event];
+
+ [super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self.delayTimer fire];
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
- [self.view touchesEnded:cleared withEvent:event];
+ [self.godotView godotTouchesEnded:cleared withEvent:event];
+
+ [super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self.delayTimer fire];
- [self.view touchesCancelled:touches withEvent:event];
-};
+ [self.godotView godotTouchesCancelled:touches withEvent:event];
+
+ [super touchesCancelled:touches withEvent:event];
+}
- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
NSMutableSet *cleared = [touches mutableCopy];
for (UITouch *touch in touches) {
- if (touch.phase != phaseToSave) {
+ if (touch.view != self.view || touch.phase != phaseToSave) {
[cleared removeObject:touch];
}
}
diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm
index e49edf5b74..32477ae41c 100644
--- a/platform/iphone/godot_view_renderer.mm
+++ b/platform/iphone/godot_view_renderer.mm
@@ -29,6 +29,7 @@
/*************************************************************************/
#import "godot_view_renderer.h"
+
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#import "display_server_iphone.h"
@@ -101,7 +102,7 @@
double dval = [n doubleValue];
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
- };
+ }
// do stuff
}
}
diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm
index da21ad0ace..ad1ea70c10 100644
--- a/platform/iphone/ios.mm
+++ b/platform/iphone/ios.mm
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "ios.h"
+
#import "app_delegate.h"
#import "view_controller.h"
#import <UIKit/UIKit.h>
@@ -36,7 +37,7 @@
void iOS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
-};
+}
void iOS::alert(const char *p_alert, const char *p_title) {
NSString *title = [NSString stringWithUTF8String:p_title];
@@ -75,6 +76,6 @@ String iOS::get_rate_url(int p_app_id) const {
printf("returning rate url %s\n", ret.utf8().get_data());
return ret;
-};
+}
iOS::iOS() {}
diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm
index 2630b42da0..9c2feeaaca 100644
--- a/platform/iphone/joypad_iphone.mm
+++ b/platform/iphone/joypad_iphone.mm
@@ -29,6 +29,7 @@
/*************************************************************************/
#import "joypad_iphone.h"
+
#include "core/config/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "main/main.h"
@@ -139,10 +140,10 @@ void JoypadIPhone::start_processing() {
for (NSNumber *key in keys) {
int joy_id = [key intValue];
return joy_id;
- };
+ }
return -1;
-};
+}
- (void)addiOSJoypad:(GCController *)controller {
// get a new id for our controller
@@ -156,7 +157,7 @@ void JoypadIPhone::start_processing() {
// assign our player index
if (controller.playerIndex == GCControllerPlayerIndexUnset) {
controller.playerIndex = [self getFreePlayerIndex];
- };
+ }
// tell Godot about our new controller
Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8([controller.vendorName UTF8String]));
@@ -202,8 +203,8 @@ void JoypadIPhone::start_processing() {
// and remove it from our dictionary
[self.connectedJoypads removeObjectForKey:key];
- };
-};
+ }
+}
- (GCControllerPlayerIndex)getFreePlayerIndex {
bool have_player_1 = false;
@@ -223,9 +224,9 @@ void JoypadIPhone::start_processing() {
have_player_3 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex4) {
have_player_4 = true;
- };
- };
- };
+ }
+ }
+ }
if (!have_player_1) {
return GCControllerPlayerIndex1;
@@ -237,7 +238,7 @@ void JoypadIPhone::start_processing() {
return GCControllerPlayerIndex4;
} else {
return GCControllerPlayerIndexUnset;
- };
+ }
}
- (void)setControllerInputHandler:(GCController *)controller {
@@ -285,27 +286,25 @@ void JoypadIPhone::start_processing() {
gamepad.dpad.left.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT,
gamepad.dpad.right.isPressed);
- };
+ }
- Input::JoyAxisValue jx;
- jx.min = -1;
if (element == gamepad.leftThumbstick) {
- jx.value = gamepad.leftThumbstick.xAxis.value;
- Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, jx);
- jx.value = -gamepad.leftThumbstick.yAxis.value;
- Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, jx);
+ float value = gamepad.leftThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value);
+ value = -gamepad.leftThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value);
} else if (element == gamepad.rightThumbstick) {
- jx.value = gamepad.rightThumbstick.xAxis.value;
- Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, jx);
- jx.value = -gamepad.rightThumbstick.yAxis.value;
- Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, jx);
+ float value = gamepad.rightThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, value);
+ value = -gamepad.rightThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, value);
} else if (element == gamepad.leftTrigger) {
- jx.value = gamepad.leftTrigger.value;
- Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, jx);
+ float value = gamepad.leftTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, value);
} else if (element == gamepad.rightTrigger) {
- jx.value = gamepad.rightTrigger.value;
- Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, jx);
- };
+ float value = gamepad.rightTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value);
+ }
};
} else if (controller.microGamepad != nil) {
// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
@@ -331,7 +330,7 @@ void JoypadIPhone::start_processing() {
gamepad.dpad.down.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT, gamepad.dpad.left.isPressed);
Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT, gamepad.dpad.right.isPressed);
- };
+ }
};
}
@@ -340,6 +339,6 @@ void JoypadIPhone::start_processing() {
///@TODO need to add support for controllerPausedHandler which should be a
/// toggle
-};
+}
@end
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index aca6f5fe2b..3281ff0cdb 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -109,6 +109,7 @@ public:
virtual String get_locale() const override;
virtual String get_unique_id() const override;
+ virtual String get_processor_name() const override;
virtual void vibrate_handheld(int p_duration_ms = 500) override;
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
index 8350365d88..1d990b5625 100644
--- a/platform/iphone/os_iphone.mm
+++ b/platform/iphone/os_iphone.mm
@@ -31,6 +31,7 @@
#ifdef IPHONE_ENABLED
#include "os_iphone.h"
+
#import "app_delegate.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
@@ -45,6 +46,7 @@
#import <AudioToolbox/AudioServices.h>
#import <UIKit/UIKit.h>
#import <dlfcn.h>
+#include <sys/sysctl.h>
#if defined(VULKAN_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
@@ -168,7 +170,7 @@ void OSIPhone::delete_main_loop() {
if (main_loop) {
main_loop->finalize();
memdelete(main_loop);
- };
+ }
main_loop = nullptr;
}
@@ -197,7 +199,7 @@ void OSIPhone::finalize() {
deinitialize_modules();
// Already gets called
- // delete_main_loop();
+ //delete_main_loop();
}
// MARK: Dynamic Libraries
@@ -230,12 +232,13 @@ Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const
String OSIPhone::get_name() const {
return "iOS";
-};
+}
String OSIPhone::get_model_name() const {
String model = ios->get_model();
- if (model != "")
+ if (model != "") {
return model;
+ }
return OS_Unix::get_model_name();
}
@@ -253,14 +256,12 @@ Error OSIPhone::shell_open(String p_uri) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
return OK;
-};
+}
void OSIPhone::set_user_data_dir(String p_dir) {
- DirAccess *da = DirAccess::open(p_dir);
-
+ DirAccessRef da = DirAccess::open(p_dir);
user_data_dir = da->get_current_dir();
printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data());
- memdelete(da);
}
String OSIPhone::get_user_data_dir() const {
@@ -287,6 +288,15 @@ String OSIPhone::get_unique_id() const {
return String::utf8([uuid UTF8String]);
}
+String OSIPhone::get_processor_name() const {
+ char buffer[256];
+ size_t buffer_len = 256;
+ if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) {
+ return String::utf8(buffer, buffer_len);
+ }
+ ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
+}
+
void OSIPhone::vibrate_handheld(int p_duration_ms) {
// iOS does not support duration for vibration
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm
index e1fc645c13..4f4ef4f046 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/view_controller.mm
@@ -203,7 +203,7 @@
case DisplayServer::SCREEN_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeLeft;
}
-};
+}
- (BOOL)prefersStatusBarHidden {
return YES;
diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp
index 0c4accccc3..46a0a816bf 100644
--- a/platform/javascript/api/api.cpp
+++ b/platform/javascript/api/api.cpp
@@ -37,8 +37,8 @@ static JavaScript *javascript_eval;
void register_javascript_api() {
JavaScriptToolsEditorPlugin::initialize();
- GDREGISTER_VIRTUAL_CLASS(JavaScriptObject);
- GDREGISTER_VIRTUAL_CLASS(JavaScript);
+ GDREGISTER_ABSTRACT_CLASS(JavaScriptObject);
+ GDREGISTER_ABSTRACT_CLASS(JavaScript);
javascript_eval = memnew(JavaScript);
Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval));
}
@@ -71,6 +71,9 @@ void JavaScript::_bind_methods() {
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"));
+ ClassDB::bind_method(D_METHOD("pwa_needs_update"), &JavaScript::pwa_needs_update);
+ ClassDB::bind_method(D_METHOD("pwa_update"), &JavaScript::pwa_update);
+ ADD_SIGNAL(MethodInfo("pwa_update_available"));
}
#if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED)
@@ -102,6 +105,12 @@ Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount,
}
#endif
#if !defined(JAVASCRIPT_ENABLED)
+bool JavaScript::pwa_needs_update() const {
+ return false;
+}
+Error JavaScript::pwa_update() {
+ return ERR_UNAVAILABLE;
+}
void JavaScript::download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime) {
}
#endif
diff --git a/platform/javascript/api/javascript_singleton.h b/platform/javascript/api/javascript_singleton.h
index 63f1aec624..e93b0a18a1 100644
--- a/platform/javascript/api/javascript_singleton.h
+++ b/platform/javascript/api/javascript_singleton.h
@@ -59,6 +59,8 @@ public:
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");
+ bool pwa_needs_update() const;
+ Error pwa_update();
static JavaScript *get_singleton();
JavaScript();
diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp
index c68ac655a8..0442a1eaeb 100644
--- a/platform/javascript/api/javascript_tools_editor_plugin.cpp
+++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp
@@ -46,14 +46,14 @@ extern void godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, co
}
static void _javascript_editor_init_callback() {
- EditorNode::get_singleton()->add_editor_plugin(memnew(JavaScriptToolsEditorPlugin(EditorNode::get_singleton())));
+ EditorNode::get_singleton()->add_editor_plugin(memnew(JavaScriptToolsEditorPlugin));
}
void JavaScriptToolsEditorPlugin::initialize() {
EditorNode::add_init_callback(_javascript_editor_init_callback);
}
-JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) {
+JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin() {
add_tool_menu_item("Download Project Source", callable_mp(this, &JavaScriptToolsEditorPlugin::_download_zip));
}
@@ -124,7 +124,7 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z
}
void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) {
- DirAccess *dir = DirAccess::open(p_path);
+ DirAccessRef dir = DirAccess::open(p_path);
if (!dir) {
WARN_PRINT("Unable to open directory for zipping: " + p_path);
return;
diff --git a/platform/javascript/api/javascript_tools_editor_plugin.h b/platform/javascript/api/javascript_tools_editor_plugin.h
index 08f10b01dc..cbf5f49497 100644
--- a/platform/javascript/api/javascript_tools_editor_plugin.h
+++ b/platform/javascript/api/javascript_tools_editor_plugin.h
@@ -46,7 +46,7 @@ private:
public:
static void initialize();
- JavaScriptToolsEditorPlugin(EditorNode *p_editor);
+ JavaScriptToolsEditorPlugin();
};
#else
class JavaScriptToolsEditorPlugin {
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index b57f3b3f16..b6be44fbb2 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -89,10 +89,10 @@ def configure(env):
if env["tools"]:
if not env["threads_enabled"]:
- print("Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option")
- sys.exit(255)
+ print('Note: Forcing "threads_enabled=yes" as it is required for the web editor.')
+ env["threads_enabled"] = "yes"
if env["initial_memory"] < 64:
- print("Editor build requires at least 64MiB of initial memory. Forcing it.")
+ print('Note: Forcing "initial_memory=64" as it is required for the web editor.')
env["initial_memory"] = 64
env.Append(CCFLAGS=["-frtti"])
elif env["builtin_icu"]:
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index 5d960ef80c..a38040922d 100644
--- a/platform/javascript/display_server_javascript.cpp
+++ b/platform/javascript/display_server_javascript.cpp
@@ -28,13 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "platform/javascript/display_server_javascript.h"
+#include "display_server_javascript.h"
#ifdef GLES3_ENABLED
#include "drivers/gles3/rasterizer_gles3.h"
#endif
#include "platform/javascript/os_javascript.h"
-#include "servers/rendering/rasterizer_dummy.h"
+#include "servers/rendering/dummy/rasterizer_dummy.h"
#include <emscripten.h>
#include <png.h>
@@ -137,7 +137,6 @@ int DisplayServerJavaScript::mouse_button_callback(int p_pressed, int p_button,
DisplayServerJavaScript *ds = get_singleton();
Point2 pos(p_x, p_y);
- Input::get_singleton()->set_mouse_position(pos);
Ref<InputEventMouseButton> ev;
ev.instantiate();
ev->set_position(pos);
@@ -219,7 +218,6 @@ void DisplayServerJavaScript::mouse_move_callback(double p_x, double p_y, double
}
Point2 pos(p_x, p_y);
- Input::get_singleton()->set_mouse_position(pos);
Ref<InputEventMouseMotion> ev;
ev.instantiate();
dom2godot_mod(ev, p_modifiers);
@@ -229,8 +227,7 @@ void DisplayServerJavaScript::mouse_move_callback(double p_x, double p_y, double
ev->set_global_position(pos);
ev->set_relative(Vector2(p_rel_x, p_rel_y));
- Input::get_singleton()->set_mouse_position(ev->get_position());
- ev->set_speed(Input::get_singleton()->get_last_mouse_speed());
+ ev->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
Input::get_singleton()->parse_input_event(ev);
}
@@ -328,12 +325,13 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso
image = image->duplicate();
- if (atlas_texture.is_valid())
+ if (atlas_texture.is_valid()) {
image->crop_from_point(
atlas_rect.position.x,
atlas_rect.position.y,
texture_size.width,
texture_size.height);
+ }
if (image->get_format() != Image::FORMAT_RGBA8) {
image->convert(Image::FORMAT_RGBA8);
@@ -558,24 +556,16 @@ void DisplayServerJavaScript::process_joypads() {
continue;
}
for (int b = 0; b < s_btns_num; b++) {
- float value = s_btns[b];
// Buttons 6 and 7 in the standard mapping need to be
// axis to be handled as JoyAxis::TRIGGER by Godot.
if (s_standard && (b == 6 || b == 7)) {
- Input::JoyAxisValue joy_axis;
- joy_axis.min = 0;
- joy_axis.value = value;
- JoyAxis a = b == 6 ? JoyAxis::TRIGGER_LEFT : JoyAxis::TRIGGER_RIGHT;
- input->joy_axis(idx, a, joy_axis);
+ input->joy_axis(idx, (JoyAxis)b, s_btns[b]);
} else {
- input->joy_button(idx, (JoyButton)b, value);
+ input->joy_button(idx, (JoyButton)b, s_btns[b]);
}
}
for (int a = 0; a < s_axes_num; a++) {
- Input::JoyAxisValue joy_axis;
- joy_axis.min = -1;
- joy_axis.value = s_axes[a];
- input->joy_axis(idx, (JoyAxis)a, joy_axis);
+ input->joy_axis(idx, (JoyAxis)a, s_axes[a]);
}
}
}
@@ -629,8 +619,9 @@ void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) {
ERR_FAIL_COND(icon->decompress() != OK);
}
if (icon->get_format() != Image::FORMAT_RGBA8) {
- if (icon == p_icon)
+ if (icon == p_icon) {
icon = icon->duplicate();
+ }
icon->convert(Image::FORMAT_RGBA8);
}
@@ -674,7 +665,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_window_mode == WINDOW_MODE_FULLSCREEN, OS::get_singleton()->is_hidpi_allowed() ? 1 : 0);
+ godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, (p_window_mode == WINDOW_MODE_FULLSCREEN || p_window_mode == WINDOW_MODE_EXCLUSIVE_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;
@@ -746,7 +737,6 @@ DisplayServerJavaScript::~DisplayServerJavaScript() {
bool DisplayServerJavaScript::has_feature(Feature p_feature) const {
switch (p_feature) {
- //case FEATURE_CONSOLE_WINDOW:
//case FEATURE_GLOBAL_MENU:
//case FEATURE_HIDPI:
//case FEATURE_IME:
@@ -806,6 +796,10 @@ float DisplayServerJavaScript::screen_get_scale(int p_screen) const {
return godot_js_display_pixel_ratio_get();
}
+float DisplayServerJavaScript::screen_get_refresh_rate(int p_screen) const {
+ return SCREEN_REFRESH_RATE_FALLBACK; // Javascript doesn't have much of a need for the screen refresh rate, and there's no native way to do so.
+}
+
Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const {
Vector<WindowID> ret;
ret.push_back(MAIN_WINDOW_ID);
@@ -899,8 +893,9 @@ Size2i DisplayServerJavaScript::window_get_real_size(WindowID p_window) const {
}
void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_window) {
- if (window_mode == p_mode)
+ if (window_mode == p_mode) {
return;
+ }
switch (p_mode) {
case WINDOW_MODE_WINDOWED: {
@@ -909,6 +904,7 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind
}
window_mode = WINDOW_MODE_WINDOWED;
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
int result = godot_js_display_fullscreen_request();
ERR_FAIL_COND_MSG(result, "The request was denied. Remember that enabling fullscreen is only possible from an input callback for the HTML5 platform.");
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
index 1ae5d68787..b50956d91c 100644
--- a/platform/javascript/display_server_javascript.h
+++ b/platform/javascript/display_server_javascript.h
@@ -139,6 +139,7 @@ public:
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual 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;
virtual void virtual_keyboard_hide() override;
diff --git a/platform/javascript/export/export_plugin.cpp b/platform/javascript/export/export_plugin.cpp
index 81ffae82bf..4448acccc2 100644
--- a/platform/javascript/export/export_plugin.cpp
+++ b/platform/javascript/export/export_plugin.cpp
@@ -30,6 +30,8 @@
#include "export_plugin.h"
+#include "core/config/project_settings.h"
+
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);
@@ -139,8 +141,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
}
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";
+ config["serviceWorker"] = p_name + ".service.worker.js";
}
// Replaces HTML string
@@ -188,35 +189,46 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c
}
Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects) {
+ String proj_name = ProjectSettings::get_singleton()->get_setting("application/config/name");
+ if (proj_name.is_empty()) {
+ proj_name = "Godot Game";
+ }
+
// 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_VERSION@"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec());
+ replaces["@GODOT_NAME@"] = proj_name.substr(0, 16);
replaces["@GODOT_OFFLINE_PAGE@"] = name + ".offline.html";
- Array files;
- replaces["@GODOT_OPT_CACHE@"] = Variant(files).to_json_string();
- 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");
+
+ // Files cached during worker install.
+ Array cache_files;
+ cache_files.push_back(name + ".html");
+ cache_files.push_back(name + ".js");
+ cache_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");
+ cache_files.push_back(name + ".icon.png");
+ cache_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");
+ cache_files.push_back(name + ".worker.js");
+ cache_files.push_back(name + ".audio.worklet.js");
+ }
+ replaces["@GODOT_CACHE@"] = Variant(cache_files).to_json_string();
+
+ // Heavy files that are cached on demand.
+ Array opt_cache_files;
+ opt_cache_files.push_back(name + ".wasm");
+ opt_cache_files.push_back(name + ".pck");
+ if (mode == EXPORT_MODE_GDNATIVE) {
+ opt_cache_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());
+ opt_cache_files.push_back(p_shared_objects[i].path.get_file());
}
}
- replaces["@GODOT_CACHE@"] = Variant(files).to_json_string();
+ replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string();
const String sw_path = dir.plus_file(name + ".service.worker.js");
Vector<uint8_t> sw;
@@ -240,7 +252,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &
// 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);
+ DirAccessRef 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) {
@@ -256,10 +268,6 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &
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]);
@@ -333,9 +341,9 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op
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::STRING, "progressive_web_app/icon_144x144", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/icon_180x180", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/icon_512x512", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "progressive_web_app/background_color", PROPERTY_HINT_COLOR_NO_ALPHA), Color()));
}
@@ -432,23 +440,23 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
// Export pck and shared objects
Vector<SharedObject> shared_objects;
String pck_path = base_path + ".pck";
- Error error = save_pack(p_preset, pck_path, &shared_objects);
+ Error error = save_pack(p_preset, p_debug, pck_path, &shared_objects);
if (error != OK) {
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path);
return error;
}
- DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- for (int i = 0; i < shared_objects.size(); i++) {
- 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());
- memdelete(da);
- return error;
+
+ {
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ for (int i = 0; i < shared_objects.size(); i++) {
+ 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());
+ return error;
+ }
}
}
- memdelete(da);
- da = nullptr;
// Extract templates.
error = _extract_template(template_path, base_dir, base_name, pwa);
@@ -636,7 +644,7 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_run_icon() const {
void EditorExportPlatformJavaScript::_server_thread_poll(void *data) {
EditorExportPlatformJavaScript *ej = (EditorExportPlatformJavaScript *)data;
while (!ej->server_quit) {
- OS::get_singleton()->delay_usec(1000);
+ OS::get_singleton()->delay_usec(6900);
{
MutexLock lock(ej->server_lock);
ej->server->poll();
@@ -658,7 +666,7 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() {
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
if (theme.is_valid()) {
- stop_icon = theme->get_icon("Stop", "EditorIcons");
+ stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons"));
} else {
stop_icon.instantiate();
}
diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h
index c55a881911..d17fd2f674 100644
--- a/platform/javascript/export/export_plugin.h
+++ b/platform/javascript/export/export_plugin.h
@@ -31,6 +31,7 @@
#ifndef JAVASCRIPT_EXPORT_PLUGIN_H
#define JAVASCRIPT_EXPORT_PLUGIN_H
+#include "core/config/project_settings.h"
#include "core/io/image_loader.h"
#include "core/io/stream_peer_ssl.h"
#include "core/io/tcp_server.h"
@@ -87,7 +88,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
icon.instantiate();
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 EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("DefaultProjectIcon"), SNAME("EditorIcons"))->get_image();
}
return icon;
}
diff --git a/platform/javascript/export/export_server.h b/platform/javascript/export/export_server.h
index 1380f34ad7..1b908ad9b0 100644
--- a/platform/javascript/export/export_server.h
+++ b/platform/javascript/export/export_server.h
@@ -36,7 +36,7 @@
#include "core/io/tcp_server.h"
#include "core/io/zip_io.h"
#include "editor/editor_export.h"
-#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
class EditorHTTPServer : public RefCounted {
private:
diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h
index 15de8b5dc7..2cb5c3025c 100644
--- a/platform/javascript/godot_js.h
+++ b/platform/javascript/godot_js.h
@@ -49,6 +49,8 @@ extern void godot_js_os_fs_sync(void (*p_callback)());
extern int godot_js_os_execute(const char *p_json);
extern void godot_js_os_shell_open(const char *p_uri);
extern int godot_js_os_hw_concurrency_get();
+extern int godot_js_pwa_cb(void (*p_callback)());
+extern int godot_js_pwa_update();
// Input
extern void godot_js_input_mouse_button_cb(int (*p_callback)(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers));
diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp
index 57416ebe48..c946302862 100644
--- a/platform/javascript/http_client_javascript.cpp
+++ b/platform/javascript/http_client_javascript.cpp
@@ -87,6 +87,11 @@ Error HTTPClientJavaScript::request(Method p_method, const String &p_url, const
ERR_FAIL_COND_V(port < 0, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
+ Error err = verify_headers(p_headers);
+ if (err) {
+ return err;
+ }
+
String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url;
Vector<CharString> keeper;
Vector<const char *> c_strings;
@@ -143,7 +148,7 @@ Error HTTPClientJavaScript::get_response_headers(List<String> *r_response) {
return OK;
}
-int HTTPClientJavaScript::get_response_body_length() const {
+int64_t HTTPClientJavaScript::get_response_body_length() const {
return godot_js_fetch_body_length_get(js_id);
}
diff --git a/platform/javascript/http_client_javascript.h b/platform/javascript/http_client_javascript.h
index d8f23fe694..096aa6a153 100644
--- a/platform/javascript/http_client_javascript.h
+++ b/platform/javascript/http_client_javascript.h
@@ -95,7 +95,7 @@ public:
bool is_response_chunked() const override;
int get_response_code() const override;
Error get_response_headers(List<String> *r_response) override;
- int get_response_body_length() const override;
+ int64_t get_response_body_length() const override;
PackedByteArray read_response_body_chunk() override;
void set_blocking_mode(bool p_enable) override;
bool is_blocking_mode_enabled() const override;
diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp
index 5c00476a72..307a80feea 100644
--- a/platform/javascript/javascript_main.cpp
+++ b/platform/javascript/javascript_main.cpp
@@ -28,6 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#include "core/config/engine.h"
#include "core/io/resource_loader.h"
#include "main/main.h"
#include "platform/javascript/display_server_javascript.h"
@@ -94,7 +95,7 @@ extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) {
Main::start();
os->get_main_loop()->initialize();
#ifdef TOOLS_ENABLED
- if (Main::is_project_manager() && FileAccess::exists("/tmp/preload.zip")) {
+ if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) {
PackedStringArray ps;
ps.push_back("/tmp/preload.zip");
os->get_main_loop()->emit_signal(SNAME("files_dropped"), ps, -1);
diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp
index b10e8007c0..c9c4d6e1b9 100644
--- a/platform/javascript/javascript_singleton.cpp
+++ b/platform/javascript/javascript_singleton.cpp
@@ -29,7 +29,9 @@
/*************************************************************************/
#include "api/javascript_singleton.h"
+
#include "emscripten.h"
+#include "os_javascript.h"
extern "C" {
extern void godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, const char *p_name, const char *p_mime);
@@ -79,7 +81,7 @@ protected:
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;
+ Variant callp(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() {
@@ -229,7 +231,7 @@ int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_w
return type;
}
-Variant JavaScriptObjectImpl::call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) {
+Variant JavaScriptObjectImpl::callp(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;
@@ -355,3 +357,10 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
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());
}
+
+bool JavaScript::pwa_needs_update() const {
+ return OS_JavaScript::get_singleton()->pwa_needs_update();
+}
+Error JavaScript::pwa_update() {
+ return OS_JavaScript::get_singleton()->pwa_update();
+}
diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js
index ba61b14eb7..2e5e1ed0d1 100644
--- a/platform/javascript/js/engine/config.js
+++ b/platform/javascript/js/engine/config.js
@@ -107,6 +107,13 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
*/
experimentalVK: false,
/**
+ * The progressive web app service worker to install.
+ * @memberof EngineConfig
+ * @default
+ * @type {string}
+ */
+ serviceWorker: '',
+ /**
* @ignore
* @type {Array.<string>}
*/
@@ -225,6 +232,8 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
*/
Config.prototype.update = function (opts) {
const config = opts || {};
+ // NOTE: We must explicitly pass the default, accessing it via
+ // the key will fail due to closure compiler renames.
function parse(key, def) {
if (typeof (config[key]) === 'undefined') {
return def;
@@ -247,6 +256,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
this.persistentDrops = parse('persistentDrops', this.persistentDrops);
this.experimentalVK = parse('experimentalVK', this.experimentalVK);
this.focusCanvas = parse('focusCanvas', this.focusCanvas);
+ this.serviceWorker = parse('serviceWorker', this.serviceWorker);
this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);
this.fileSizes = parse('fileSizes', this.fileSizes);
this.args = parse('args', this.args);
diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js
index 17a8df9e29..d2ba595083 100644
--- a/platform/javascript/js/engine/engine.js
+++ b/platform/javascript/js/engine/engine.js
@@ -189,6 +189,9 @@ const Engine = (function () {
preloader.preloadedFiles.length = 0; // Clear memory
me.rtenv['callMain'](me.config.args);
initPromise = null;
+ if (me.config.serviceWorker && 'serviceWorker' in navigator) {
+ navigator.serviceWorker.register(me.config.serviceWorker);
+ }
resolve();
});
});
diff --git a/platform/javascript/js/libs/library_godot_input.js b/platform/javascript/js/libs/library_godot_input.js
index 7a4d0d8126..1e64c260f8 100644
--- a/platform/javascript/js/libs/library_godot_input.js
+++ b/platform/javascript/js/libs/library_godot_input.js
@@ -87,7 +87,7 @@ const GodotInputGamepads = {
},
init: function (onchange) {
- GodotEventListeners.samples = [];
+ GodotInputGamepads.samples = [];
function add(pad) {
const guid = GodotInputGamepads.get_guid(pad);
const c_id = GodotRuntime.allocString(pad.id);
diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js
index 662d215443..12d06a8d51 100644
--- a/platform/javascript/js/libs/library_godot_os.js
+++ b/platform/javascript/js/libs/library_godot_os.js
@@ -368,3 +368,58 @@ const GodotEventListeners = {
},
};
mergeInto(LibraryManager.library, GodotEventListeners);
+
+const GodotPWA = {
+
+ $GodotPWA__deps: ['$GodotRuntime', '$GodotEventListeners'],
+ $GodotPWA: {
+ hasUpdate: false,
+
+ updateState: function (cb, reg) {
+ if (!reg) {
+ return;
+ }
+ if (!reg.active) {
+ return;
+ }
+ if (reg.waiting) {
+ GodotPWA.hasUpdate = true;
+ cb();
+ }
+ GodotEventListeners.add(reg, 'updatefound', function () {
+ const installing = reg.installing;
+ GodotEventListeners.add(installing, 'statechange', function () {
+ if (installing.state === 'installed') {
+ GodotPWA.hasUpdate = true;
+ cb();
+ }
+ });
+ });
+ },
+ },
+
+ godot_js_pwa_cb__sig: 'vi',
+ godot_js_pwa_cb: function (p_update_cb) {
+ if ('serviceWorker' in navigator) {
+ const cb = GodotRuntime.get_func(p_update_cb);
+ navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb));
+ }
+ },
+
+ godot_js_pwa_update__sig: 'i',
+ godot_js_pwa_update: function () {
+ if ('serviceWorker' in navigator && GodotPWA.hasUpdate) {
+ navigator.serviceWorker.getRegistration().then(function (reg) {
+ if (!reg || !reg.waiting) {
+ return;
+ }
+ reg.waiting.postMessage('update');
+ });
+ return 0;
+ }
+ return 1;
+ },
+};
+
+autoAddDeps(GodotPWA, '$GodotPWA');
+mergeInto(LibraryManager.library, GodotPWA);
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index b0eea54aa9..da88ea18b0 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -45,6 +45,7 @@
#include <emscripten.h>
#include <stdlib.h>
+#include "api/javascript_singleton.h"
#include "godot_js.h"
void OS_JavaScript::alert(const String &p_alert, const String &p_title) {
@@ -107,11 +108,11 @@ void OS_JavaScript::finalize() {
// Miscellaneous
-Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
+Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
return create_process(p_path, p_arguments);
}
-Error OS_JavaScript::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) {
+Error OS_JavaScript::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
Array args;
for (const String &E : p_arguments) {
args.push_back(E);
@@ -174,7 +175,7 @@ String OS_JavaScript::get_name() const {
String OS_JavaScript::get_user_data_dir() const {
return "/userfs";
-};
+}
String OS_JavaScript::get_cache_path() const {
return "/home/web_user/.cache";
@@ -203,6 +204,19 @@ void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags
}
}
+void OS_JavaScript::update_pwa_state_callback() {
+ if (OS_JavaScript::get_singleton()) {
+ OS_JavaScript::get_singleton()->pwa_is_waiting = true;
+ }
+ if (JavaScript::get_singleton()) {
+ JavaScript::get_singleton()->emit_signal("pwa_update_available");
+ }
+}
+
+Error OS_JavaScript::pwa_update() {
+ return godot_js_pwa_update() ? FAILED : OK;
+}
+
bool OS_JavaScript::is_userfs_persistent() const {
return idb_available;
}
@@ -226,6 +240,8 @@ OS_JavaScript::OS_JavaScript() {
godot_js_config_locale_get(locale_ptr, 16);
setenv("LANG", locale_ptr, true);
+ godot_js_pwa_cb(&OS_JavaScript::update_pwa_state_callback);
+
if (AudioDriverJavaScript::is_available()) {
#ifdef NO_THREADS
audio_drivers.push_back(memnew(AudioDriverScriptProcessor));
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index c12088bfbe..9e272f9aa1 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -45,11 +45,13 @@ class OS_JavaScript : public OS_Unix {
bool idb_is_syncing = false;
bool idb_available = false;
bool idb_needs_sync = false;
+ bool pwa_is_waiting = false;
static void main_loop_callback();
static void file_access_close_callback(const String &p_file, int p_flags);
static void fs_sync_callback();
+ static void update_pwa_state_callback();
protected:
void initialize() override;
@@ -65,13 +67,16 @@ public:
// Override return type to make writing static callbacks less tedious.
static OS_JavaScript *get_singleton();
+ bool pwa_needs_update() const { return pwa_is_waiting; }
+ Error pwa_update();
+
void initialize_joypads() override;
MainLoop *get_main_loop() const override;
bool main_loop_iterate();
- Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override;
- Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
+ Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
+ Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
Error kill(const ProcessID &p_pid) override;
int get_process_id() const override;
int get_processor_count() const override;
diff --git a/platform/javascript/package-lock.json b/platform/javascript/package-lock.json
index 1bc11c7ccf..35f864f01a 100644
--- a/platform/javascript/package-lock.json
+++ b/platform/javascript/package-lock.json
@@ -1,8 +1,3009 @@
{
"name": "godot",
"version": "1.0.0",
- "lockfileVersion": 1,
+ "lockfileVersion": 2,
"requires": true,
+ "packages": {
+ "": {
+ "name": "godot",
+ "version": "1.0.0",
+ "license": "MIT",
+ "devDependencies": {
+ "eslint": "^7.28.0",
+ "eslint-config-airbnb-base": "^14.2.1",
+ "eslint-plugin-import": "^2.23.4",
+ "jsdoc": "^3.6.7",
+ "serve": "^13.0.2"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
+ "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
+ "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
+ "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.14.5",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz",
+ "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz",
+ "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.1.1",
+ "espree": "^7.3.0",
+ "globals": "^13.9.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^3.13.1",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
+ "dev": true
+ },
+ "node_modules/@zeit/schemas": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz",
+ "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==",
+ "dev": true
+ },
+ "node_modules/accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "dev": true,
+ "dependencies": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
+ "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-align": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.1.0"
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/arch": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
+ "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/arg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz",
+ "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==",
+ "dev": true
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz",
+ "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.18.0-next.2",
+ "get-intrinsic": "^1.1.1",
+ "is-string": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz",
+ "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.18.0-next.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true
+ },
+ "node_modules/boxen": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
+ "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-align": "^3.0.0",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.1.0",
+ "cli-boxes": "^2.2.1",
+ "string-width": "^4.2.2",
+ "type-fest": "^0.20.2",
+ "widest-line": "^3.1.0",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/catharsis": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
+ "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.15"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
+ "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/chalk/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/chalk/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-boxes": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
+ "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/clipboardy": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz",
+ "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==",
+ "dev": true,
+ "dependencies": {
+ "arch": "^2.1.1",
+ "execa": "^1.0.0",
+ "is-wsl": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "node_modules/compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": ">= 1.43.0 < 2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/compression": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz",
+ "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==",
+ "dev": true,
+ "dependencies": {
+ "accepts": "~1.3.5",
+ "bytes": "3.0.0",
+ "compressible": "~2.0.14",
+ "debug": "2.6.9",
+ "on-headers": "~1.0.1",
+ "safe-buffer": "5.1.2",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/compression/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/compression/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/confusing-browser-globals": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz",
+ "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==",
+ "dev": true
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "node_modules/define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
+ "dependencies": {
+ "object-keys": "^1.0.12"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/enquirer": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-colors": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/entities": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
+ "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==",
+ "dev": true
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.18.3",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz",
+ "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.2",
+ "is-callable": "^1.2.3",
+ "is-negative-zero": "^2.0.1",
+ "is-regex": "^1.1.3",
+ "is-string": "^1.0.6",
+ "object-inspect": "^1.10.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.2",
+ "string.prototype.trimend": "^1.0.4",
+ "string.prototype.trimstart": "^1.0.4",
+ "unbox-primitive": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz",
+ "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "7.12.11",
+ "@eslint/eslintrc": "^0.4.2",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "enquirer": "^2.3.5",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^2.1.0",
+ "eslint-visitor-keys": "^2.0.0",
+ "espree": "^7.3.1",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.1.2",
+ "globals": "^13.6.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "progress": "^2.0.0",
+ "regexpp": "^3.1.0",
+ "semver": "^7.2.1",
+ "strip-ansi": "^6.0.0",
+ "strip-json-comments": "^3.1.0",
+ "table": "^6.0.9",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-airbnb-base": {
+ "version": "14.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz",
+ "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==",
+ "dev": true,
+ "dependencies": {
+ "confusing-browser-globals": "^1.0.10",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0",
+ "eslint-plugin-import": "^2.22.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz",
+ "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^2.6.9",
+ "resolve": "^1.13.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.1.tgz",
+ "integrity": "sha512-ZXI9B8cxAJIH4nfkhTwcRTEAnrVfobYqwjWy/QMCZ8rHkZHFjf9yO4BzpiF9kCSfNlMG54eKigISHpX0+AaT4A==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7",
+ "pkg-dir": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.23.4",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz",
+ "integrity": "sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.3",
+ "array.prototype.flat": "^1.2.4",
+ "debug": "^2.6.9",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.4",
+ "eslint-module-utils": "^2.6.1",
+ "find-up": "^2.0.0",
+ "has": "^1.0.3",
+ "is-core-module": "^2.4.0",
+ "minimatch": "^3.0.4",
+ "object.values": "^1.1.3",
+ "pkg-up": "^2.0.0",
+ "read-pkg-up": "^3.0.0",
+ "resolve": "^1.20.0",
+ "tsconfig-paths": "^3.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/espree": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
+ "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^7.4.0",
+ "acorn-jsx": "^5.3.1",
+ "eslint-visitor-keys": "^1.3.0"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esquery/node_modules/estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse/node_modules/estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/execa/node_modules/cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "dependencies": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ },
+ "engines": {
+ "node": ">=4.8"
+ }
+ },
+ "node_modules/execa/node_modules/path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/execa/node_modules/semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/execa/node_modules/shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/execa/node_modules/shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/execa/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "node_modules/fast-url-parser": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz",
+ "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^1.3.2"
+ }
+ },
+ "node_modules/fast-url-parser/node_modules/punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz",
+ "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
+ "dev": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
+ "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
+ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.9.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz",
+ "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+ "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
+ "dev": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
+ "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "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
+ },
+ "node_modules/ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz",
+ "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz",
+ "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
+ "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
+ "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz",
+ "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
+ "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz",
+ "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz",
+ "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz",
+ "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/js2xmlparser": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz",
+ "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==",
+ "dev": true,
+ "dependencies": {
+ "xmlcreate": "^2.0.3"
+ }
+ },
+ "node_modules/jsdoc": {
+ "version": "3.6.7",
+ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz",
+ "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.9.4",
+ "bluebird": "^3.7.2",
+ "catharsis": "^0.9.0",
+ "escape-string-regexp": "^2.0.0",
+ "js2xmlparser": "^4.0.1",
+ "klaw": "^3.0.0",
+ "markdown-it": "^10.0.0",
+ "markdown-it-anchor": "^5.2.7",
+ "marked": "^2.0.3",
+ "mkdirp": "^1.0.4",
+ "requizzle": "^0.2.3",
+ "strip-json-comments": "^3.1.0",
+ "taffydb": "2.6.2",
+ "underscore": "~1.13.1"
+ },
+ "bin": {
+ "jsdoc": "jsdoc.js"
+ },
+ "engines": {
+ "node": ">=8.15.0"
+ }
+ },
+ "node_modules/jsdoc/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/klaw": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
+ "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.9"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/linkify-it": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
+ "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
+ "dev": true,
+ "dependencies": {
+ "uc.micro": "^1.0.1"
+ }
+ },
+ "node_modules/load-json-file": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+ "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^4.0.0",
+ "pify": "^3.0.0",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+ "dev": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lodash.truncate": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
+ "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/markdown-it": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz",
+ "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "entities": "~2.0.0",
+ "linkify-it": "^2.0.0",
+ "mdurl": "^1.0.1",
+ "uc.micro": "^1.0.5"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.js"
+ }
+ },
+ "node_modules/markdown-it-anchor": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz",
+ "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==",
+ "dev": true,
+ "peerDependencies": {
+ "markdown-it": "*"
+ }
+ },
+ "node_modules/marked": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.7.tgz",
+ "integrity": "sha512-BJXxkuIfJchcXOJWTT2DOL+yFWifFv2yGYOUzvXg8Qz610QKw+sHCvTMYwA+qWGhlA2uivBezChZ/pBy1tWdkQ==",
+ "dev": true,
+ "bin": {
+ "marked": "bin/marked"
+ },
+ "engines": {
+ "node": ">= 8.16.2"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
+ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
+ "dev": true
+ },
+ "node_modules/mime-db": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+ "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.34",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+ "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "1.51.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
+ "node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz",
+ "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+ "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz",
+ "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.18.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz",
+ "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.18.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "dev": true,
+ "dependencies": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+ "dev": true
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-to-regexp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz",
+ "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+ "dev": true,
+ "dependencies": {
+ "pify": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-up": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
+ "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/rc/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/read-pkg": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+ "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+ "dev": true,
+ "dependencies": {
+ "load-json-file": "^4.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
+ "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/registry-auth-token": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
+ "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
+ "dev": true,
+ "dependencies": {
+ "rc": "^1.1.6",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/registry-url": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+ "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+ "dev": true,
+ "dependencies": {
+ "rc": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/requizzle": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz",
+ "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.14"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/serve": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz",
+ "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==",
+ "dev": true,
+ "dependencies": {
+ "@zeit/schemas": "2.6.0",
+ "ajv": "6.12.6",
+ "arg": "2.0.0",
+ "boxen": "5.1.2",
+ "chalk": "2.4.1",
+ "clipboardy": "2.3.0",
+ "compression": "1.7.3",
+ "serve-handler": "6.1.3",
+ "update-check": "1.5.2"
+ },
+ "bin": {
+ "serve": "bin/serve.js"
+ }
+ },
+ "node_modules/serve-handler": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz",
+ "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==",
+ "dev": true,
+ "dependencies": {
+ "bytes": "3.0.0",
+ "content-disposition": "0.5.2",
+ "fast-url-parser": "1.1.3",
+ "mime-types": "2.1.18",
+ "minimatch": "3.0.4",
+ "path-is-inside": "1.0.2",
+ "path-to-regexp": "2.2.1",
+ "range-parser": "1.2.0"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "~1.33.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve/node_modules/chalk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/serve/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
+ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==",
+ "dev": true
+ },
+ "node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/spdx-correct": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+ "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
+ "dev": true,
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+ "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+ "dev": true
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz",
+ "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==",
+ "dev": true
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "node_modules/string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
+ "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
+ "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/table": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz",
+ "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^8.0.1",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/table/node_modules/ajv": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz",
+ "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/table/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
+ "node_modules/taffydb": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz",
+ "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=",
+ "dev": true
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz",
+ "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==",
+ "dev": true,
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.1",
+ "minimist": "^1.2.0",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/uc.micro": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
+ "dev": true
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
+ "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has-bigints": "^1.0.1",
+ "has-symbols": "^1.0.2",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/underscore": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz",
+ "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==",
+ "dev": true
+ },
+ "node_modules/update-check": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz",
+ "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==",
+ "dev": true,
+ "dependencies": {
+ "registry-auth-token": "3.3.2",
+ "registry-url": "3.1.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "dev": true
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
+ "dependencies": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/widest-line": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
+ "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "node_modules/xmlcreate": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz",
+ "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==",
+ "dev": true
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ },
"dependencies": {
"@babel/code-frame": {
"version": "7.12.11",
@@ -78,6 +3079,22 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true
},
+ "@zeit/schemas": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz",
+ "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==",
+ "dev": true
+ },
+ "accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "dev": true,
+ "requires": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ }
+ },
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@@ -88,7 +3105,8 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"ajv": {
"version": "6.12.6",
@@ -102,6 +3120,15 @@
"uri-js": "^4.2.2"
}
},
+ "ansi-align": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.1.0"
+ }
+ },
"ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
@@ -123,6 +3150,18 @@
"color-convert": "^1.9.0"
}
},
+ "arch": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
+ "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
+ "dev": true
+ },
+ "arg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz",
+ "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==",
+ "dev": true
+ },
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -174,6 +3213,22 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
},
+ "boxen": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
+ "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==",
+ "dev": true,
+ "requires": {
+ "ansi-align": "^3.0.0",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.1.0",
+ "cli-boxes": "^2.2.1",
+ "string-width": "^4.2.2",
+ "type-fest": "^0.20.2",
+ "widest-line": "^3.1.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -184,6 +3239,12 @@
"concat-map": "0.0.1"
}
},
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+ "dev": true
+ },
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -200,6 +3261,12 @@
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
+ "camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true
+ },
"catharsis": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
@@ -260,6 +3327,23 @@
}
}
},
+ "cli-boxes": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
+ "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
+ "dev": true
+ },
+ "clipboardy": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz",
+ "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==",
+ "dev": true,
+ "requires": {
+ "arch": "^2.1.1",
+ "execa": "^1.0.0",
+ "is-wsl": "^2.1.1"
+ }
+ },
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -275,6 +3359,47 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
+ "compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "dev": true,
+ "requires": {
+ "mime-db": ">= 1.43.0 < 2"
+ }
+ },
+ "compression": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz",
+ "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.5",
+ "bytes": "3.0.0",
+ "compressible": "~2.0.14",
+ "debug": "2.6.9",
+ "on-headers": "~1.0.1",
+ "safe-buffer": "5.1.2",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -287,6 +3412,12 @@
"integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==",
"dev": true
},
+ "content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
+ "dev": true
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -307,6 +3438,12 @@
"ms": "2.1.2"
}
},
+ "deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true
+ },
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -337,6 +3474,15 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
+ "end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
"enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
@@ -661,6 +3807,72 @@
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -679,6 +3891,23 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
+ "fast-url-parser": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz",
+ "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=",
+ "dev": true,
+ "requires": {
+ "punycode": "^1.3.2"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ }
+ }
+ },
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -742,6 +3971,15 @@
"has-symbols": "^1.0.1"
}
},
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
"glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
@@ -851,6 +4089,12 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
+ "ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -893,6 +4137,12 @@
"integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==",
"dev": true
},
+ "is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true
+ },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -936,6 +4186,12 @@
"has-symbols": "^1.0.2"
}
},
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true
+ },
"is-string": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz",
@@ -951,6 +4207,15 @@
"has-symbols": "^1.0.2"
}
},
+ "is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "requires": {
+ "is-docker": "^2.0.0"
+ }
+ },
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -1139,7 +4404,8 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz",
"integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"marked": {
"version": "2.0.7",
@@ -1153,6 +4419,21 @@
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
"dev": true
},
+ "mime-db": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+ "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.34",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+ "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.51.0"
+ }
+ },
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -1186,6 +4467,18 @@
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
+ "negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+ "dev": true
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -1206,6 +4499,23 @@
}
}
},
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
+ "requires": {
+ "path-key": "^2.0.0"
+ },
+ "dependencies": {
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ }
+ }
+ },
"object-inspect": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz",
@@ -1252,6 +4562,12 @@
"es-abstract": "^1.18.2"
}
},
+ "on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "dev": true
+ },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -1275,6 +4591,12 @@
"word-wrap": "^1.2.3"
}
},
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "dev": true
+ },
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
@@ -1330,6 +4652,12 @@
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
+ "path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+ "dev": true
+ },
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -1342,6 +4670,12 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
+ "path-to-regexp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz",
+ "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==",
+ "dev": true
+ },
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
@@ -1387,12 +4721,48 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
+ "range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
+ "dev": true
+ },
+ "rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true
+ }
+ }
+ },
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -1420,6 +4790,25 @@
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
+ "registry-auth-token": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
+ "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
+ "dev": true,
+ "requires": {
+ "rc": "^1.1.6",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "registry-url": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+ "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+ "dev": true,
+ "requires": {
+ "rc": "^1.0.1"
+ }
+ },
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -1460,6 +4849,12 @@
"glob": "^7.1.3"
}
},
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@@ -1469,6 +4864,75 @@
"lru-cache": "^6.0.0"
}
},
+ "serve": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz",
+ "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==",
+ "dev": true,
+ "requires": {
+ "@zeit/schemas": "2.6.0",
+ "ajv": "6.12.6",
+ "arg": "2.0.0",
+ "boxen": "5.1.2",
+ "chalk": "2.4.1",
+ "clipboardy": "2.3.0",
+ "compression": "1.7.3",
+ "serve-handler": "6.1.3",
+ "update-check": "1.5.2"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ }
+ }
+ },
+ "serve-handler": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz",
+ "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==",
+ "dev": true,
+ "requires": {
+ "bytes": "3.0.0",
+ "content-disposition": "0.5.2",
+ "fast-url-parser": "1.1.3",
+ "mime-types": "2.1.18",
+ "minimatch": "3.0.4",
+ "path-is-inside": "1.0.2",
+ "path-to-regexp": "2.2.1",
+ "range-parser": "1.2.0"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "dev": true,
+ "requires": {
+ "mime-db": "~1.33.0"
+ }
+ }
+ }
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -1484,6 +4948,12 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
+ "signal-exit": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
+ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==",
+ "dev": true
+ },
"slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
@@ -1605,6 +5075,12 @@
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
},
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
+ },
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -1717,6 +5193,16 @@
"integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==",
"dev": true
},
+ "update-check": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz",
+ "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==",
+ "dev": true,
+ "requires": {
+ "registry-auth-token": "3.3.2",
+ "registry-url": "3.1.0"
+ }
+ },
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -1742,6 +5228,12 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+ "dev": true
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -1764,12 +5256,58 @@
"is-symbol": "^1.0.3"
}
},
+ "widest-line": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
+ "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.0.0"
+ }
+ },
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"dev": true
},
+ "wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ }
+ }
+ },
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/platform/javascript/package.json b/platform/javascript/package.json
index 9dafae30c5..2ff1544837 100644
--- a/platform/javascript/package.json
+++ b/platform/javascript/package.json
@@ -2,9 +2,8 @@
"name": "godot",
"private": true,
"version": "1.0.0",
- "description": "Linting setup for Godot's HTML5 platform code",
+ "description": "Development and linting setup for Godot's HTML5 platform code",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
"docs": "jsdoc --template js/jsdoc2rst/ js/engine/engine.js js/engine/config.js --destination ''",
"lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules && npm run lint:tools",
"lint:engine": "eslint \"js/engine/*.js\" --no-eslintrc -c .eslintrc.engine.js",
@@ -15,7 +14,8 @@
"format:engine": "npm run lint:engine -- --fix",
"format:libs": "npm run lint:libs -- --fix",
"format:modules": "npm run lint:modules -- --fix",
- "format:tools": "npm run lint:tools -- --fix"
+ "format:tools": "npm run lint:tools -- --fix",
+ "serve": "serve"
},
"author": "Godot Engine contributors",
"license": "MIT",
@@ -23,6 +23,7 @@
"eslint": "^7.28.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.23.4",
- "jsdoc": "^3.6.7"
+ "jsdoc": "^3.6.7",
+ "serve": "^13.0.2"
}
}
diff --git a/platform/javascript/serve.json b/platform/javascript/serve.json
new file mode 100644
index 0000000000..f2ef24751f
--- /dev/null
+++ b/platform/javascript/serve.json
@@ -0,0 +1,21 @@
+{
+ "public": "../../bin",
+ "headers": [{
+ "source": "**/*",
+ "headers": [
+ {
+ "key": "Cross-Origin-Embedder-Policy",
+ "value": "require-corp"
+ }, {
+ "key": "Cross-Origin-Opener-Policy",
+ "value": "same-origin"
+ }, {
+ "key": "Access-Control-Allow-Origin",
+ "value": "*"
+ }, {
+ "key": "Cache-Control",
+ "value": "no-store, max-age=0"
+ }
+ ]
+ }]
+}
diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp
index e9369fefdd..b4ec7924f6 100644
--- a/platform/linuxbsd/crash_handler_linuxbsd.cpp
+++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp
@@ -33,7 +33,6 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "main/main.h"
#ifdef DEBUG_ENABLED
@@ -71,10 +70,10 @@ static void handle_crash(int sig) {
}
// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
- if (String(VERSION_HASH).length() != 0) {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n");
+ if (String(VERSION_HASH).is_empty()) {
+ fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
} else {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n");
+ fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
}
fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
char **strings = backtrace_symbols(bt_buffer, size);
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index ab643b254a..03c85d09ad 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -261,27 +261,6 @@ def configure(env):
if not env["builtin_libpng"]:
env.ParseConfig("pkg-config libpng16 --cflags --libs")
- if not env["builtin_bullet"]:
- # We need at least version 2.90
- min_bullet_version = "2.90"
-
- import subprocess
-
- bullet_version = subprocess.check_output(["pkg-config", "bullet", "--modversion"]).strip()
- if str(bullet_version) < min_bullet_version:
- # Abort as system bullet was requested but too old
- print(
- "Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(
- bullet_version, min_bullet_version
- )
- )
- 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")
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 68bd5e8421..07cb6a23e8 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -33,6 +33,7 @@
#ifdef X11_ENABLED
#include "core/config/project_settings.h"
+#include "core/math/math_funcs.h"
#include "core/string/print_string.h"
#include "core/string/ustring.h"
#include "detect_prime_x11.h"
@@ -64,7 +65,6 @@
// EWMH
#define _NET_WM_STATE_REMOVE 0L // remove/unset property
#define _NET_WM_STATE_ADD 1L // add/set property
-#define _NET_WM_STATE_TOGGLE 2L // toggle property
#include <dlfcn.h>
#include <fcntl.h>
@@ -109,6 +109,15 @@ struct Hints {
unsigned long status = 0;
};
+static String get_atom_name(Display *p_disp, Atom p_atom) {
+ char *name = XGetAtomName(p_disp, p_atom);
+ ERR_FAIL_NULL_V_MSG(name, String(), "Atom is invalid.");
+ String ret;
+ ret.parse_utf8(name);
+ XFree(name);
+ return ret;
+}
+
bool DisplayServerX11::has_feature(Feature p_feature) const {
switch (p_feature) {
case FEATURE_SUBWINDOWS:
@@ -324,20 +333,21 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {
if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
//flush pending motion events
_flush_mouse_motion();
- WindowData &main_window = windows[MAIN_WINDOW_ID];
+ WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID;
+ WindowData &window = windows[window_id];
if (XGrabPointer(
- x11_display, main_window.x11_window, True,
+ x11_display, window.x11_window, True,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
- GrabModeAsync, GrabModeAsync, windows[MAIN_WINDOW_ID].x11_window, None, CurrentTime) != GrabSuccess) {
+ GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) {
ERR_PRINT("NO GRAB");
}
if (mouse_mode == MOUSE_MODE_CAPTURED) {
- center.x = main_window.size.width / 2;
- center.y = main_window.size.height / 2;
+ center.x = window.size.width / 2;
+ center.y = window.size.height / 2;
- XWarpPointer(x11_display, None, main_window.x11_window,
+ XWarpPointer(x11_display, None, window.x11_window,
0, 0, 0, 0, (int)center.x, (int)center.y);
Input::get_singleton()->set_mouse_position(center);
@@ -359,27 +369,13 @@ void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) {
if (mouse_mode == MOUSE_MODE_CAPTURED) {
last_mouse_pos = p_to;
} else {
- XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window,
+ WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID;
+ XWarpPointer(x11_display, None, windows[window_id].x11_window,
0, 0, 0, 0, (int)p_to.x, (int)p_to.y);
}
}
Point2i DisplayServerX11::mouse_get_position() const {
- int root_x, root_y;
- int win_x, win_y;
- unsigned int mask_return;
- Window window_returned;
-
- Bool result = XQueryPointer(x11_display, RootWindow(x11_display, DefaultScreen(x11_display)), &window_returned,
- &window_returned, &root_x, &root_y, &win_x, &win_y,
- &mask_return);
- if (result == True) {
- return Point2i(root_x, root_y);
- }
- return Point2i();
-}
-
-Point2i DisplayServerX11::mouse_get_absolute_position() const {
int number_of_screens = XScreenCount(x11_display);
for (int i = 0; i < number_of_screens; i++) {
Window root, child;
@@ -448,7 +444,7 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A
Window selection_owner = XGetSelectionOwner(x11_display, p_source);
if (selection_owner == x11_window) {
static const char *target_type = "PRIMARY";
- if (p_source != None && String(XGetAtomName(x11_display, p_source)) == target_type) {
+ if (p_source != None && get_atom_name(x11_display, p_source) == target_type) {
return internal_clipboard_primary;
} else {
return internal_clipboard;
@@ -1068,6 +1064,67 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const {
return 96;
}
+float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ //invalid screen?
+ ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK);
+
+ //Use xrandr to get screen refresh rate.
+ if (xrandr_ext_ok) {
+ XRRScreenResources *screen_info = XRRGetScreenResources(x11_display, windows[MAIN_WINDOW_ID].x11_window);
+ if (screen_info) {
+ RRMode current_mode = 0;
+ xrr_monitor_info *monitors = nullptr;
+
+ if (xrr_get_monitors) {
+ int count = 0;
+ monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);
+ ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK);
+ } else {
+ ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+ }
+
+ bool found_active_mode = false;
+ for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting.
+ XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]);
+ if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue.
+ continue;
+ }
+
+ if (monitor_info->mode != None) {
+ current_mode = monitor_info->mode;
+ found_active_mode = true;
+ break;
+ }
+ }
+
+ if (found_active_mode) {
+ for (int mode = 0; mode < screen_info->nmode; mode++) {
+ XRRModeInfo m_info = screen_info->modes[mode];
+ if (m_info.id == current_mode) {
+ // Snap to nearest 0.01 to stay consistent with other platforms.
+ return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01);
+ }
+ }
+ }
+
+ ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred.
+ return SCREEN_REFRESH_RATE_FALLBACK;
+ } else {
+ ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+ }
+ }
+ ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+}
+
bool DisplayServerX11::screen_is_touchscreen(int p_screen) const {
_THREAD_SAFE_METHOD_
@@ -1125,6 +1182,7 @@ void DisplayServerX11::show_window(WindowID p_id) {
_THREAD_SAFE_METHOD_
const WindowData &wd = windows[p_id];
+ popup_open(p_id);
DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id);
@@ -1135,7 +1193,9 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_id));
- ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted"); //ma
+ ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted");
+
+ popup_close(p_id);
WindowData &wd = windows[p_id];
@@ -1170,6 +1230,24 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
windows.erase(p_id);
}
+int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+ ERR_FAIL_COND_V(!windows.has(p_window), 0);
+ switch (p_handle_type) {
+ case DISPLAY_HANDLE: {
+ return (int64_t)x11_display;
+ }
+ case WINDOW_HANDLE: {
+ return (int64_t)windows[p_window].x11_window;
+ }
+ case WINDOW_VIEW: {
+ return 0; // Not supported.
+ }
+ default: {
+ return 0;
+ }
+ }
+}
+
void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
@@ -1330,8 +1408,9 @@ int DisplayServerX11::window_get_current_screen(WindowID p_window) const {
void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) {
#if defined(GLES3_ENABLED)
- if (gl_manager)
+ if (gl_manager) {
gl_manager->window_make_current(p_window_id);
+ }
#endif
}
@@ -1391,8 +1470,8 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent
// Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if (wd_window.menu_type && !wd_window.no_focus && wd_window.focused) {
- if (!wd_parent.no_focus) {
+ if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) {
+ if (!wd_parent.no_focus && !wd_window.is_popup) {
XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime);
}
}
@@ -1768,7 +1847,7 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) {
Hints hints;
Atom property;
hints.flags = 2;
- hints.decorations = window_get_flag(WINDOW_FLAG_BORDERLESS, p_window) ? 0 : 1;
+ hints.decorations = wd.borderless ? 0 : 1;
property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
if (property != None) {
XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
@@ -1820,6 +1899,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
//Remove full-screen
wd.fullscreen = false;
@@ -1872,6 +1952,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
wd.last_position_before_fs = wd.position;
@@ -2004,6 +2085,18 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
case WINDOW_FLAG_TRANSPARENT: {
//todo reimplement
} break;
+ case WINDOW_FLAG_NO_FOCUS: {
+ wd.no_focus = p_enabled;
+ } break;
+ case WINDOW_FLAG_POPUP: {
+ XWindowAttributes xwa;
+ XSync(x11_display, False);
+ XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
+
+ ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
+ ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");
+ wd.is_popup = p_enabled;
+ } break;
default: {
}
}
@@ -2045,6 +2138,12 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co
case WINDOW_FLAG_TRANSPARENT: {
//todo reimplement
} break;
+ case WINDOW_FLAG_NO_FOCUS: {
+ return wd.no_focus;
+ } break;
+ case WINDOW_FLAG_POPUP: {
+ return wd.is_popup;
+ } break;
default: {
}
}
@@ -2358,8 +2457,7 @@ String DisplayServerX11::keyboard_get_layout_language(int p_index) const {
Atom names = kbd->names->symbols;
if (names != None) {
- char *name = XGetAtomName(x11_display, names);
- Vector<String> info = String(name).split("+");
+ Vector<String> info = get_atom_name(x11_display, names).split("+");
if (p_index >= 0 && p_index < _group_count) {
if (p_index + 1 < info.size()) {
ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols.
@@ -2369,7 +2467,6 @@ String DisplayServerX11::keyboard_get_layout_language(int p_index) const {
} else {
ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
}
- XFree(name);
}
XkbFreeKeyboard(kbd, 0, true);
}
@@ -2396,9 +2493,7 @@ String DisplayServerX11::keyboard_get_layout_name(int p_index) const {
}
if (p_index >= 0 && p_index < _group_count) {
- char *full_name = XGetAtomName(x11_display, groups[p_index]);
- ret.parse_utf8(full_name);
- XFree(full_name);
+ ret = get_atom_name(x11_display, groups[p_index]);
} else {
ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
}
@@ -2412,7 +2507,7 @@ Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const {
Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;
unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);
KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0);
- if (xkeysym >= 'a' && xkeysym <= 'z') {
+ if (is_ascii_lower_case(xkeysym)) {
xkeysym -= ('a' - 'A');
}
@@ -2461,7 +2556,7 @@ static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count)
for (int i = 0; i < p_count; i++) {
Atom atom = p_list[i];
- if (atom != None && String(XGetAtomName(p_display, atom)) == target_type) {
+ if (atom != None && get_atom_name(p_display, atom) == target_type) {
return atom;
}
}
@@ -2470,15 +2565,15 @@ static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count)
static Atom pick_target_from_atoms(Display *p_disp, Atom p_t1, Atom p_t2, Atom p_t3) {
static const char *target_type = "text/uri-list";
- if (p_t1 != None && String(XGetAtomName(p_disp, p_t1)) == target_type) {
+ if (p_t1 != None && get_atom_name(p_disp, p_t1) == target_type) {
return p_t1;
}
- if (p_t2 != None && String(XGetAtomName(p_disp, p_t2)) == target_type) {
+ if (p_t2 != None && get_atom_name(p_disp, p_t2) == target_type) {
return p_t2;
}
- if (p_t3 != None && String(XGetAtomName(p_disp, p_t3)) == target_type) {
+ if (p_t3 != None && get_atom_name(p_disp, p_t3) == target_type) {
return p_t3;
}
@@ -2800,7 +2895,7 @@ Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p
// is the owner during a selection request.
CharString clip;
static const char *target_type = "PRIMARY";
- if (p_selection != None && String(XGetAtomName(x11_display, p_selection)) == target_type) {
+ if (p_selection != None && get_atom_name(x11_display, p_selection) == target_type) {
clip = internal_clipboard_primary.utf8();
} else {
clip = internal_clipboard.utf8();
@@ -2959,23 +3054,36 @@ void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) {
Variant ret;
Callable::CallError ce;
+ {
+ List<WindowID>::Element *E = popup_list.front();
+ if (E && Object::cast_to<InputEventKey>(*p_event)) {
+ // Redirect keyboard input to active popup.
+ if (windows.has(E->get())) {
+ Callable callable = windows[E->get()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+ return;
+ }
+ }
+
Ref<InputEventFromWindow> event_from_window = p_event;
if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
- //send to a window
- ERR_FAIL_COND(!windows.has(event_from_window->get_window_id()));
- Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
- if (callable.is_null()) {
- return;
+ // Send to a single window.
+ if (windows.has(event_from_window->get_window_id())) {
+ Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
}
- callable.call((const Variant **)&evp, 1, ret, ce);
} else {
- //send to all windows
+ // Send to all windows.
for (KeyValue<WindowID, WindowData> &E : windows) {
Callable callable = E.value.input_event_callback;
- if (callable.is_null()) {
- continue;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
}
- callable.call((const Variant **)&evp, 1, ret, ce);
}
}
}
@@ -3067,6 +3175,108 @@ void DisplayServerX11::_check_pending_events(LocalVector<XEvent> &r_events) {
}
}
+DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const {
+ const List<WindowID>::Element *E = popup_list.back();
+ if (E) {
+ return E->get();
+ } else {
+ return INVALID_WINDOW_ID;
+ }
+}
+
+void DisplayServerX11::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.parent_safe_rect = p_rect;
+}
+
+Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());
+ const WindowData &wd = windows[p_window];
+ return wd.parent_safe_rect;
+}
+
+void DisplayServerX11::popup_open(WindowID p_window) {
+ WindowData &wd = windows[p_window];
+ if (wd.is_popup) {
+ // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ List<WindowID>::Element *E = popup_list.back();
+ while (E) {
+ if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
+ _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ } else {
+ break;
+ }
+ }
+
+ time_since_popup = OS::get_singleton()->get_ticks_msec();
+ popup_list.push_back(p_window);
+ }
+}
+
+void DisplayServerX11::popup_close(WindowID p_window) {
+ List<WindowID>::Element *E = popup_list.find(p_window);
+ while (E) {
+ _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->next();
+ popup_list.erase(E);
+ E = F;
+ }
+}
+
+void DisplayServerX11::mouse_process_popups() {
+ if (popup_list.is_empty()) {
+ return;
+ }
+
+ uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
+ if (delta < 250) {
+ return;
+ }
+
+ int number_of_screens = XScreenCount(x11_display);
+ for (int i = 0; i < number_of_screens; i++) {
+ Window root, child;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask;
+ if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {
+ XWindowAttributes root_attrs;
+ XGetWindowAttributes(x11_display, root, &root_attrs);
+ Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);
+ if ((pos != last_mouse_monitor_pos) || (mask != last_mouse_monitor_mask)) {
+ if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) {
+ List<WindowID>::Element *E = popup_list.back();
+ while (E) {
+ // Popup window area.
+ Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
+ // Area of the parent window, which responsible for opening sub-menu.
+ Rect2i safe_rect = window_get_popup_safe_rect(E->get());
+ if (win_rect.has_point(pos)) {
+ break;
+ } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
+ break;
+ } else {
+ _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ }
+ }
+ }
+ }
+ last_mouse_monitor_mask = mask;
+ last_mouse_monitor_pos = pos;
+ }
+ }
+}
+
void DisplayServerX11::process_events() {
_THREAD_SAFE_METHOD_
@@ -3075,6 +3285,8 @@ void DisplayServerX11::process_events() {
++frame;
#endif
+ mouse_process_popups();
+
if (app_focused) {
//verify that one of the windows has focus, else send focus out notification
bool focus_found = false;
@@ -3305,7 +3517,7 @@ void DisplayServerX11::process_events() {
// Set focus when menu window is started.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if (wd.menu_type && !wd.no_focus) {
+ if (!wd.no_focus && !wd.is_popup) {
XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
}
} break;
@@ -3350,7 +3562,7 @@ void DisplayServerX11::process_events() {
DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode);
WindowData &wd = windows[window_id];
-
+ last_focused_window = window_id;
wd.focused = true;
if (wd.xic) {
@@ -3416,7 +3628,7 @@ void DisplayServerX11::process_events() {
if (mouse_mode_grab) {
for (const KeyValue<WindowID, WindowData> &E : windows) {
- //dear X11, I try, I really try, but you never work, you do whathever you want.
+ //dear X11, I try, I really try, but you never work, you do whatever you want.
if (mouse_mode == MOUSE_MODE_CAPTURED) {
// Show the cursor if we're in captured mode so it doesn't look weird.
XUndefineCursor(x11_display, E.value.x11_window);
@@ -3448,10 +3660,14 @@ void DisplayServerX11::process_events() {
const WindowData &wd = windows[window_id];
+ XWindowAttributes xwa;
+ XSync(x11_display, False);
+ XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
+
// Set focus when menu window is re-used.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if (wd.menu_type && !wd.no_focus) {
+ if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) {
XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
}
@@ -3492,7 +3708,7 @@ void DisplayServerX11::process_events() {
// Ensure window focus on click.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if (!wd.no_focus) {
+ if (!wd.no_focus && !wd.is_popup) {
XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
}
@@ -3550,9 +3766,9 @@ void DisplayServerX11::process_events() {
// The X11 API requires filtering one-by-one through the motion
// notify events, in order to figure out which event is the one
// generated by warping the mouse pointer.
-
+ WindowID focused_window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID;
while (true) {
- if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[MAIN_WINDOW_ID].size.width / 2 && event.xmotion.y == windows[MAIN_WINDOW_ID].size.height / 2) {
+ if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[focused_window_id].size.width / 2 && event.xmotion.y == windows[focused_window_id].size.height / 2) {
//this is likely the warp event since it was warped here
center = Vector2(event.xmotion.x, event.xmotion.y);
break;
@@ -3627,9 +3843,8 @@ void DisplayServerX11::process_events() {
// Reset to prevent lingering motion
xi.relative_motion.x = 0;
xi.relative_motion.y = 0;
-
if (mouse_mode == MOUSE_MODE_CAPTURED) {
- pos = Point2i(windows[MAIN_WINDOW_ID].size.width / 2, windows[MAIN_WINDOW_ID].size.height / 2);
+ pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2);
}
Ref<InputEventMouseMotion> mm;
@@ -3647,8 +3862,7 @@ void DisplayServerX11::process_events() {
mm->set_button_mask((MouseButton)mouse_get_button_state());
mm->set_position(pos);
mm->set_global_position(pos);
- Input::get_singleton()->set_mouse_position(pos);
- mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+ mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
mm->set_relative(rel);
@@ -3678,7 +3892,7 @@ void DisplayServerX11::process_events() {
mm->set_window_id(E.key);
mm->set_position(pos_focused);
mm->set_global_position(pos_focused);
- mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+ mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
Input::get_singleton()->parse_input_event(mm);
break;
@@ -3709,6 +3923,7 @@ void DisplayServerX11::process_events() {
Property p = _read_property(x11_display, windows[window_id].x11_window, XInternAtom(x11_display, "PRIMARY", 0));
Vector<String> files = String((char *)p.data).split("\n", false);
+ XFree(p.data);
for (int i = 0; i < files.size(); i++) {
files.write[i] = files[i].replace("file://", "").uri_decode().strip_edges();
}
@@ -3751,6 +3966,7 @@ void DisplayServerX11::process_events() {
if (more_than_3) {
Property p = _read_property(x11_display, source, XInternAtom(x11_display, "XdndTypeList", False));
requested = pick_target_from_list(x11_display, (Atom *)p.data, p.nitems);
+ XFree(p.data);
} else {
requested = pick_target_from_atoms(x11_display, event.xclient.data.l[2], event.xclient.data.l[3], event.xclient.data.l[4]);
}
@@ -4054,21 +4270,20 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
WindowID id = window_id_counter++;
WindowData &wd = windows[id];
- if ((id != MAIN_WINDOW_ID) && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) {
- wd.menu_type = true;
- }
-
if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
- wd.menu_type = true;
wd.no_focus = true;
}
+ if (p_flags & WINDOW_FLAG_POPUP_BIT) {
+ wd.is_popup = true;
+ }
+
// Setup for menu subwindows:
// - override_redirect forces the WM not to interfere with the window, to avoid delays due to
// handling decorations and placement.
// On the other hand, focus changes need to be handled manually when this is set.
// - save_under is a hint for the WM to keep the content of windows behind to avoid repaint.
- if (wd.menu_type) {
+ if (wd.is_popup || wd.no_focus) {
windowAttributes.override_redirect = True;
windowAttributes.save_under = True;
valuemask |= CWOverrideRedirect | CWSaveUnder;
@@ -4079,7 +4294,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
// Enable receiving notification when the window is initialized (MapNotify)
// so the focus can be set at the right time.
- if (wd.menu_type && !wd.no_focus) {
+ if (!wd.no_focus && !wd.is_popup) {
XSelectInput(x11_display, wd.x11_window, StructureNotifyMask);
}
@@ -4176,7 +4391,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
}
}
- if (wd.menu_type) {
+ if (wd.is_popup || wd.no_focus) {
// Set Utility type to disable fade animations.
Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
@@ -4727,7 +4942,7 @@ DisplayServerX11::~DisplayServerX11() {
if (img[i] != nullptr) {
XcursorImageDestroy(img[i]);
}
- };
+ }
if (xim) {
XCloseIM(xim);
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
index b5fd3664d3..63d32d939d 100644
--- a/platform/linuxbsd/display_server_x11.h
+++ b/platform/linuxbsd/display_server_x11.h
@@ -31,8 +31,6 @@
#ifndef DISPLAY_SERVER_X11_H
#define DISPLAY_SERVER_X11_H
-#include "drivers/gles3/rasterizer_platforms.h"
-
#ifdef X11_ENABLED
#include "servers/display_server.h"
@@ -135,7 +133,6 @@ class DisplayServerX11 : public DisplayServer {
ObjectID instance_id;
- bool menu_type = false;
bool no_focus = false;
//better to guess on the fly, given WM can change it
@@ -145,14 +142,25 @@ class DisplayServerX11 : public DisplayServer {
bool borderless = false;
bool resize_disabled = false;
Vector2i last_position_before_fs;
- bool focused = false;
+ bool focused = true;
bool minimized = false;
+ bool is_popup = false;
+
+ Rect2i parent_safe_rect;
unsigned int focus_order = 0;
};
Map<WindowID, WindowData> windows;
+ unsigned int last_mouse_monitor_mask = 0;
+ Vector2i last_mouse_monitor_pos;
+ uint64_t time_since_popup = 0;
+
+ List<WindowID> popup_list;
+
+ WindowID last_focused_window = INVALID_WINDOW_ID;
+
WindowID window_id_counter = MAIN_WINDOW_ID;
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect);
@@ -283,6 +291,10 @@ protected:
void _window_changed(XEvent *event);
public:
+ void mouse_process_popups();
+ void popup_open(WindowID p_window);
+ void popup_close(WindowID p_window);
+
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
@@ -291,7 +303,6 @@ public:
virtual void mouse_warp_to_position(const Point2i &p_to) override;
virtual Point2i mouse_get_position() const override;
- virtual Point2i mouse_get_absolute_position() const override;
virtual MouseButton mouse_get_button_state() const override;
virtual void clipboard_set(const String &p_text) override;
@@ -304,6 +315,7 @@ public:
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
#if defined(DBUS_ENABLED)
@@ -317,8 +329,14 @@ public:
virtual void show_window(WindowID p_id) override;
virtual void delete_sub_window(WindowID p_id) override;
+ virtual WindowID window_get_active_popup() const override;
+ virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override;
+ virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override;
+
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+ virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp
index f05d2faa11..796e82594a 100644
--- a/platform/linuxbsd/export/export.cpp
+++ b/platform/linuxbsd/export/export.cpp
@@ -30,15 +30,12 @@
#include "export.h"
-#include "core/io/file_access.h"
-#include "editor/editor_export.h"
-#include "platform/linuxbsd/logo.gen.h"
-#include "scene/resources/texture.h"
+#include "export_plugin.h"
static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size);
void register_linuxbsd_exporter() {
- Ref<EditorExportPlatformPC> platform;
+ Ref<EditorExportPlatformLinuxBSD> platform;
platform.instantiate();
Ref<Image> img = memnew(Image(_linuxbsd_logo));
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
new file mode 100644
index 0000000000..99f5b82b34
--- /dev/null
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -0,0 +1,74 @@
+/*************************************************************************/
+/* export_plugin.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "export_plugin.h"
+
+#include "core/config/project_settings.h"
+#include "editor/editor_node.h"
+
+Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
+ FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
+
+ f->store_line("#!/bin/sh");
+ f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'");
+ f->store_line("base_path=\"$(dirname \"$(realpath \"$0\")\")\"");
+ f->store_line("\"$base_path/" + p_pkg_name + "\" \"$@\"");
+
+ return OK;
+}
+
+Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+ Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags);
+
+ if (err != OK) {
+ return err;
+ }
+
+ String app_name;
+ if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
+ app_name = String(ProjectSettings::get_singleton()->get("application/config/name"));
+ } else {
+ app_name = "Unnamed";
+ }
+ app_name = OS::get_singleton()->get_safe_dir_name(app_name);
+
+ // Save console script.
+ if (err == OK) {
+ int con_scr = p_preset->get("debug/export_console_script");
+ if ((con_scr == 1 && p_debug) || (con_scr == 2)) {
+ String scr_path = p_path.get_basename() + ".sh";
+ err = _export_debug_script(p_preset, app_name, p_path.get_file(), scr_path);
+ FileAccess::set_unix_permissions(scr_path, 0755);
+ }
+ }
+
+ return err;
+}
diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h
new file mode 100644
index 0000000000..f482ddce3d
--- /dev/null
+++ b/platform/linuxbsd/export/export_plugin.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* export_plugin.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 LINUXBSD_EXPORT_PLUGIN_H
+#define LINUXBSD_EXPORT_PLUGIN_H
+
+#include "core/io/file_access.h"
+#include "editor/editor_export.h"
+#include "editor/editor_settings.h"
+#include "platform/linuxbsd/logo.gen.h"
+#include "scene/resources/texture.h"
+
+class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC {
+ Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);
+
+public:
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+};
+
+#endif
diff --git a/platform/linuxbsd/gl_manager_x11.cpp b/platform/linuxbsd/gl_manager_x11.cpp
index 1721d0e0b3..d3fb1d6705 100644
--- a/platform/linuxbsd/gl_manager_x11.cpp
+++ b/platform/linuxbsd/gl_manager_x11.cpp
@@ -68,8 +68,9 @@ static int ctxErrorHandler(Display *dpy, XErrorEvent *ev) {
int GLManager_X11::_find_or_create_display(Display *p_x11_display) {
for (unsigned int n = 0; n < _displays.size(); n++) {
const GLDisplay &d = _displays[n];
- if (d.x11_display == p_x11_display)
+ if (d.x11_display == p_x11_display) {
return n;
+ }
}
// create
@@ -82,8 +83,7 @@ int GLManager_X11::_find_or_create_display(Display *p_x11_display) {
GLDisplay &d = _displays[new_display_id];
d.context = memnew(GLManager_X11_Private);
- ;
- d.context->glx_context = 0;
+ d.context->glx_context = nullptr;
//Error err = _create_context(d);
_create_context(d);
@@ -124,7 +124,7 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) {
};
int fbcount;
- GLXFBConfig fbconfig = 0;
+ GLXFBConfig fbconfig = nullptr;
XVisualInfo *vi = nullptr;
gl_display.x_swa.event_mask = StructureNotifyMask;
@@ -137,8 +137,9 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) {
for (int i = 0; i < fbcount; i++) {
vi = (XVisualInfo *)glXGetVisualFromFBConfig(x11_display, fbc[i]);
- if (!vi)
+ if (!vi) {
continue;
+ }
XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi->visual);
if (!pict_format) {
@@ -262,22 +263,26 @@ void GLManager_X11::window_destroy(DisplayServer::WindowID p_window_id) {
}
void GLManager_X11::release_current() {
- if (!_current_window)
+ if (!_current_window) {
return;
+ }
glXMakeCurrent(_x_windisp.x11_display, None, nullptr);
}
void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) {
- if (p_window_id == -1)
+ if (p_window_id == -1) {
return;
+ }
GLWindow &win = _windows[p_window_id];
- if (!win.in_use)
+ if (!win.in_use) {
return;
+ }
// noop
- if (&win == _current_window)
+ if (&win == _current_window) {
return;
+ }
const GLDisplay &disp = get_display(win.gldisplay_id);
@@ -287,8 +292,9 @@ void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) {
}
void GLManager_X11::make_current() {
- if (!_current_window)
+ if (!_current_window) {
return;
+ }
if (!_current_window->in_use) {
WARN_PRINT("current window not in use!");
return;
@@ -301,8 +307,9 @@ void GLManager_X11::swap_buffers() {
// NO NEED TO CALL SWAP BUFFERS for each window...
// see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml
- if (!_current_window)
+ if (!_current_window) {
return;
+ }
if (!_current_window->in_use) {
WARN_PRINT("current window not in use!");
return;
@@ -335,19 +342,23 @@ void GLManager_X11::set_use_vsync(bool p_use) {
}
// we need an active window to get a display to set the vsync
- if (!_current_window)
+ if (!_current_window) {
return;
+ }
const GLDisplay &disp = get_current_display();
if (!setup) {
setup = true;
String extensions = glXQueryExtensionsString(disp.x11_display, DefaultScreen(disp.x11_display));
- if (extensions.find("GLX_EXT_swap_control") != -1)
+ if (extensions.find("GLX_EXT_swap_control") != -1) {
glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT");
- if (extensions.find("GLX_MESA_swap_control") != -1)
+ }
+ if (extensions.find("GLX_MESA_swap_control") != -1) {
glXSwapIntervalMESA = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalMESA");
- if (extensions.find("GLX_SGI_swap_control") != -1)
+ }
+ if (extensions.find("GLX_SGI_swap_control") != -1) {
glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI");
+ }
}
int val = p_use ? 1 : 0;
if (glXSwapIntervalMESA) {
@@ -357,8 +368,9 @@ void GLManager_X11::set_use_vsync(bool p_use) {
} else if (glXSwapIntervalEXT) {
GLXDrawable drawable = glXGetCurrentDrawable();
glXSwapIntervalEXT(disp.x11_display, drawable, val);
- } else
+ } else {
return;
+ }
use_vsync = p_use;
}
diff --git a/platform/linuxbsd/gl_manager_x11.h b/platform/linuxbsd/gl_manager_x11.h
index c83b96395b..0bb0a446ab 100644
--- a/platform/linuxbsd/gl_manager_x11.h
+++ b/platform/linuxbsd/gl_manager_x11.h
@@ -33,8 +33,6 @@
#ifdef X11_ENABLED
-#include "drivers/gles3/rasterizer_platforms.h"
-
#ifdef GLES3_ENABLED
#include "core/os/os.h"
diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp
index 37606de3bc..65d53b266f 100644
--- a/platform/linuxbsd/joypad_linux.cpp
+++ b/platform/linuxbsd/joypad_linux.cpp
@@ -61,13 +61,9 @@ JoypadLinux::Joypad::~Joypad() {
void JoypadLinux::Joypad::reset() {
dpad = HatMask::CENTER;
fd = -1;
-
- Input::JoyAxisValue jx;
- jx.min = -1;
- jx.value = 0.0f;
for (int i = 0; i < MAX_ABS; i++) {
abs_map[i] = -1;
- curr_axis[i] = jx;
+ curr_axis[i] = 0;
}
}
@@ -242,7 +238,7 @@ void JoypadLinux::close_joypad(int p_id) {
if (p_id == -1) {
for (int i = 0; i < JOYPADS_MAX; i++) {
close_joypad(i);
- };
+ }
return;
} else if (p_id < 0) {
return;
@@ -255,7 +251,7 @@ void JoypadLinux::close_joypad(int p_id) {
joy.fd = -1;
attached_devices.remove_at(attached_devices.find(joy.devpath));
input->joy_connection_changed(p_id, false, "");
- };
+ }
}
static String _hex_str(uint8_t p_byte) {
@@ -337,8 +333,9 @@ void JoypadLinux::open_joypad(const char *p_path) {
}
// Check if the device supports basic gamepad events
- if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) &&
- test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) {
+ bool has_abs_left = (test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit));
+ bool has_abs_right = (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit));
+ if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && (has_abs_left || has_abs_right))) {
close(fd);
return;
}
@@ -429,23 +426,11 @@ void JoypadLinux::joypad_vibration_stop(int p_id, uint64_t p_timestamp) {
joy.ff_effect_timestamp = p_timestamp;
}
-Input::JoyAxisValue JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const {
+float JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const {
int min = p_abs->minimum;
int max = p_abs->maximum;
- Input::JoyAxisValue jx;
-
- if (min < 0) {
- jx.min = -1;
- if (p_value < 0) {
- jx.value = (float)-p_value / min;
- } else {
- jx.value = (float)p_value / max;
- }
- } else if (min == 0) {
- jx.min = 0;
- jx.value = 0.0f + (float)p_value / max;
- }
- return jx;
+ // Convert to a value between -1.0f and 1.0f.
+ return 2.0f * (p_value - min) / (max - min) - 1.0f;
}
void JoypadLinux::process_joypads() {
@@ -514,7 +499,7 @@ void JoypadLinux::process_joypads() {
return;
}
if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) {
- Input::JoyAxisValue value = axis_correct(joy->abs_info[ev.code], ev.value);
+ float value = axis_correct(joy->abs_info[ev.code], ev.value);
joy->curr_axis[joy->abs_map[ev.code]] = value;
}
break;
@@ -531,7 +516,7 @@ void JoypadLinux::process_joypads() {
}
if (len == 0 || (len < 0 && errno != EAGAIN)) {
close_joypad(i);
- };
+ }
if (joy->force_feedback) {
uint64_t timestamp = input->get_joy_vibration_timestamp(i);
diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h
index 42797afdfa..9177465547 100644
--- a/platform/linuxbsd/joypad_linux.h
+++ b/platform/linuxbsd/joypad_linux.h
@@ -28,7 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-//author: Andreas Haas <hondres, liugam3@gmail.com>
#ifndef JOYPAD_LINUX_H
#define JOYPAD_LINUX_H
@@ -53,7 +52,7 @@ private:
};
struct Joypad {
- Input::JoyAxisValue curr_axis[MAX_ABS];
+ float curr_axis[MAX_ABS];
int key_map[MAX_KEY];
int abs_map[MAX_ABS];
HatMask dpad = HatMask::CENTER;
@@ -97,8 +96,9 @@ 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::JoyAxisValue axis_correct(const input_absinfo *p_abs, int p_value) const;
+ float axis_correct(const input_absinfo *p_abs, int p_value) const;
};
-#endif
+#endif // JOYDEV_ENABLED
+
#endif // JOYPAD_LINUX_H
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index b5f127bb16..86874687ad 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -32,6 +32,7 @@
#include "core/io/dir_access.h"
#include "main/main.h"
+#include "servers/display_server.h"
#ifdef X11_ENABLED
#include "display_server_x11.h"
@@ -141,6 +142,20 @@ String OS_LinuxBSD::get_unique_id() const {
return machine_id;
}
+String OS_LinuxBSD::get_processor_name() const {
+ FileAccessRef f = FileAccess::open("/proc/cpuinfo", FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!f, "", String("Couldn't open `/proc/cpuinfo` to get the CPU model name. Returning an empty string."));
+
+ while (!f->eof_reached()) {
+ const String line = f->get_line();
+ if (line.find("model name") != -1) {
+ return line.split(":")[1].strip_edges();
+ }
+ }
+
+ ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name from `/proc/cpuinfo`. Returning an empty string."));
+}
+
void OS_LinuxBSD::finalize() {
if (main_loop) {
memdelete(main_loop);
@@ -342,7 +357,7 @@ void OS_LinuxBSD::run() {
if (Main::iteration()) {
break;
}
- };
+ }
main_loop->finalize();
}
@@ -384,9 +399,11 @@ static String get_mountpoint(const String &p_path) {
}
Error OS_LinuxBSD::move_to_trash(const String &p_path) {
+ String path = p_path.rstrip("/"); // Strip trailing slash when path points to a directory
+
int err_code;
List<String> args;
- args.push_back(p_path);
+ args.push_back(path);
args.push_front("trash"); // The command is `gio trash <file_name>` so we need to add it to args.
Error result = execute("gio", args, nullptr, &err_code); // For GNOME based machines.
if (result == OK && !err_code) {
@@ -416,14 +433,14 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
// If the commands `kioclient5`, `gio` or `gvfs-trash` don't exist on the system we do it manually.
String trash_path = "";
- String mnt = get_mountpoint(p_path);
+ String mnt = get_mountpoint(path);
// If there is a directory "[Mountpoint]/.Trash-[UID], use it as the trash can.
if (!mnt.is_empty()) {
- String path(mnt + "/.Trash-" + itos(getuid()));
+ String mountpoint_trash_path(mnt + "/.Trash-" + itos(getuid()));
struct stat s;
- if (!stat(path.utf8().get_data(), &s)) {
- trash_path = path;
+ if (!stat(mountpoint_trash_path.utf8().get_data(), &s)) {
+ trash_path = mountpoint_trash_path;
}
}
@@ -448,25 +465,21 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
// Create needed directories for decided trash can location.
{
- DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error err = dir_access->make_dir_recursive(trash_path);
// Issue an error if trash can is not created properly.
ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"");
err = dir_access->make_dir_recursive(trash_path + "/files");
- ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/files");
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "/files\"");
err = dir_access->make_dir_recursive(trash_path + "/info");
- ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/info");
- memdelete(dir_access);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "/info\"");
}
// The trash can is successfully created, now we check that we don't exceed our file name length limit.
// If the file name is too long trim it so we can add the identifying number and ".trashinfo".
// Assumes that the file name length limit is 255 characters.
- String file_name = p_path.get_file();
- if (file_name.length() == 0) {
- file_name = p_path.get_base_dir().get_file();
- }
+ String file_name = path.get_file();
if (file_name.length() > 240) {
file_name = file_name.substr(0, file_name.length() - 15);
}
@@ -489,30 +502,31 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
}
file_name = fn;
+ String renamed_path = path.get_base_dir() + "/" + file_name;
+
// Generates the .trashinfo file
OS::Date date = OS::get_singleton()->get_date(false);
OS::Time time = OS::get_singleton()->get_time(false);
String timestamp = vformat("%04d-%02d-%02dT%02d:%02d:", date.year, (int)date.month, date.day, time.hour, time.minute);
timestamp = vformat("%s%02d", timestamp, time.second); // vformat only supports up to 6 arguments.
- String trash_info = "[Trash Info]\nPath=" + p_path.uri_encode() + "\nDeletionDate=" + timestamp + "\n";
+ String trash_info = "[Trash Info]\nPath=" + path.uri_encode() + "\nDeletionDate=" + timestamp + "\n";
{
Error err;
- FileAccess *file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file:" + trash_path + "/info/" + file_name + ".trashinfo");
+ FileAccessRef file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file: \"" + trash_path + "/info/" + file_name + ".trashinfo\"");
file->store_string(trash_info);
file->close();
// Rename our resource before moving it to the trash can.
- DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- err = dir_access->rename(p_path, p_path.get_base_dir() + "/" + file_name);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + p_path + "\"");
- memdelete(dir_access);
+ DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ err = dir_access->rename(path, renamed_path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + path + "\" to \"" + renamed_path + "\"");
}
// Move the given resource to the trash can.
// Do not use DirAccess:rename() because it can't move files across multiple mountpoints.
List<String> mv_args;
- mv_args.push_back(p_path.get_base_dir() + "/" + file_name);
+ mv_args.push_back(renamed_path);
mv_args.push_back(trash_path + "/files");
{
int retval;
@@ -520,11 +534,10 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
// Issue an error if "mv" failed to move the given resource to the trash can.
if (err != OK || retval != 0) {
- ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_path + "/files\"");
- DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- err = dir_access->rename(p_path.get_base_dir() + "/" + file_name, p_path);
- memdelete(dir_access);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Could not rename " + p_path.get_base_dir() + "/" + file_name + " back to its original name:" + p_path);
+ ERR_PRINT("move_to_trash: Could not move the resource \"" + path + "\" to the trash can \"" + trash_path + "/files\"");
+ DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ err = dir_access->rename(renamed_path, path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Could not rename \"" + renamed_path + "\" back to its original name: \"" + path + "\"");
return FAILED;
}
}
diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h
index ad6e4cd168..d3857e85f8 100644
--- a/platform/linuxbsd/os_linuxbsd.h
+++ b/platform/linuxbsd/os_linuxbsd.h
@@ -39,8 +39,6 @@
#include "drivers/unix/os_unix.h"
#include "joypad_linux.h"
#include "servers/audio_server.h"
-#include "servers/rendering/renderer_compositor.h"
-#include "servers/rendering_server.h"
class OS_LinuxBSD : public OS_Unix {
virtual void delete_main_loop() override;
@@ -89,6 +87,7 @@ public:
virtual Error shell_open(String p_uri) override;
virtual String get_unique_id() const override;
+ virtual String get_processor_name() const override;
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
diff --git a/platform/linuxbsd/vulkan_context_x11.cpp b/platform/linuxbsd/vulkan_context_x11.cpp
index e2fd8c76d2..b4f585726f 100644
--- a/platform/linuxbsd/vulkan_context_x11.cpp
+++ b/platform/linuxbsd/vulkan_context_x11.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "vulkan_context_x11.h"
+
#ifdef USE_VOLK
#include <volk.h>
#else
diff --git a/platform/osx/SCsub b/platform/osx/SCsub
index 8ba106d1c2..d72a75af04 100644
--- a/platform/osx/SCsub
+++ b/platform/osx/SCsub
@@ -6,14 +6,21 @@ from platform_methods import run_in_subprocess
import platform_osx_builders
files = [
- "crash_handler_osx.mm",
"os_osx.mm",
+ "godot_application.mm",
+ "godot_application_delegate.mm",
+ "crash_handler_osx.mm",
+ "osx_terminal_logger.mm",
"display_server_osx.mm",
+ "godot_content_view.mm",
+ "godot_window_delegate.mm",
+ "godot_window.mm",
+ "key_mapping_osx.mm",
"godot_main_osx.mm",
"dir_access_osx.mm",
"joypad_osx.cpp",
"vulkan_context_osx.mm",
- "gl_manager_osx.mm",
+ "gl_manager_osx_legacy.mm",
]
prog = env.add_program("#bin/godot", files)
diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm
index 16be941308..06ed91907c 100644
--- a/platform/osx/crash_handler_osx.mm
+++ b/platform/osx/crash_handler_osx.mm
@@ -33,7 +33,6 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "main/main.h"
#include <string.h>
@@ -90,14 +89,15 @@ static void handle_crash(int sig) {
fprintf(stderr, "\n================================================================\n");
fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig);
- if (OS::get_singleton()->get_main_loop())
+ if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
+ }
// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
- if (String(VERSION_HASH).length() != 0) {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n");
+ if (String(VERSION_HASH).is_empty()) {
+ fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
} else {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n");
+ fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
}
fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
char **strings = backtrace_symbols(bt_buffer, size);
@@ -120,8 +120,9 @@ static void handle_crash(int sig) {
snprintf(fname, 1024, "%s", demangled);
}
- if (demangled)
+ if (demangled) {
free(demangled);
+ }
}
}
@@ -178,8 +179,9 @@ CrashHandler::~CrashHandler() {
}
void CrashHandler::disable() {
- if (disabled)
+ if (disabled) {
return;
+ }
#ifdef CRASH_HANDLER_ENABLED
signal(SIGSEGV, nullptr);
diff --git a/platform/osx/detect.py b/platform/osx/detect.py
index c67791b340..0ff93bedb4 100644
--- a/platform/osx/detect.py
+++ b/platform/osx/detect.py
@@ -78,14 +78,16 @@ def configure(env):
env["osxcross"] = True
if env["arch"] == "arm64":
- print("Building for macOS 10.15+, platform arm64.")
- env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15"])
- env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15"])
+ print("Building for macOS 11.0+, platform arm64.")
+ env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"])
+ env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"])
else:
print("Building for macOS 10.12+, platform x86_64.")
env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
+ env.Append(CCFLAGS=["-fobjc-arc"])
+
if not "osxcross" in env: # regular native build
if env["macports_clang"] != "no":
mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local")
@@ -188,6 +190,8 @@ def configure(env):
env.Append(CCFLAGS=["-Wno-deprecated-declarations"]) # Disable deprecation warnings
env.Append(LINKFLAGS=["-framework", "OpenGL"])
+ env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"])
+
if env["vulkan"]:
env.Append(CPPDEFINES=["VULKAN_ENABLED"])
env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"])
diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h
index 4b558d973d..cc9ac162ea 100644
--- a/platform/osx/display_server_osx.h
+++ b/platform/osx/display_server_osx.h
@@ -37,13 +37,13 @@
#include "servers/display_server.h"
#if defined(GLES3_ENABLED)
-#include "gl_manager_osx.h"
-#endif
+#include "gl_manager_osx_legacy.h"
+#endif // GLES3_ENABLED
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
#include "platform/osx/vulkan_context_osx.h"
-#endif
+#endif // VULKAN_ENABLED
#include <AppKit/AppKit.h>
#include <AppKit/NSCursor.h>
@@ -59,27 +59,8 @@ class DisplayServerOSX : public DisplayServer {
_THREAD_SAFE_CLASS_
public:
- void _send_event(NSEvent *p_event);
- NSMenu *_get_dock_menu() const;
- void _menu_callback(id p_sender);
-
-#if defined(GLES3_ENABLED)
- GLManager_OSX *gl_manager = nullptr;
-#endif
-#if defined(VULKAN_ENABLED)
- VulkanContextOSX *context_vulkan = nullptr;
- RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
-#endif
-
- const NSMenu *_get_menu_root(const String &p_menu_root) const;
- NSMenu *_get_menu_root(const String &p_menu_root);
-
- NSMenu *apple_menu = nullptr;
- NSMenu *dock_menu = nullptr;
- Map<String, NSMenu *> submenu;
-
struct KeyEvent {
- WindowID window_id;
+ WindowID window_id = INVALID_WINDOW_ID;
unsigned int osx_state = false;
bool pressed = false;
bool echo = false;
@@ -89,18 +70,6 @@ public:
uint32_t unicode = 0;
};
- struct WarpEvent {
- NSTimeInterval timestamp;
- NSPoint delta;
- };
-
- List<WarpEvent> warp_events;
- NSTimeInterval last_warp = 0;
- bool ignore_warp = false;
-
- Vector<KeyEvent> key_event_buffer;
- int key_event_pos;
-
struct WindowData {
id window_delegate;
id window_object;
@@ -114,8 +83,6 @@ public:
Size2i max_size;
Size2i size;
- bool mouse_down_control = false;
-
bool im_active = false;
Size2i im_position;
@@ -128,6 +95,7 @@ public:
ObjectID instance_id;
WindowID transient_parent = INVALID_WINDOW_ID;
+ bool exclusive = false;
Set<WindowID> transient_children;
bool layered_window = false;
@@ -136,49 +104,116 @@ public:
bool borderless = false;
bool resize_disabled = false;
bool no_focus = false;
+ bool is_popup = false;
+
+ Rect2i parent_safe_rect;
};
+ List<WindowID> popup_list;
+ uint64_t time_since_popup = 0;
+
+private:
+#if defined(GLES3_ENABLED)
+ GLManager_OSX *gl_manager = nullptr;
+#endif
+#if defined(VULKAN_ENABLED)
+ VulkanContextOSX *context_vulkan = nullptr;
+ RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
+#endif
+ String rendering_driver;
+
+ NSMenu *apple_menu = nullptr;
+ NSMenu *dock_menu = nullptr;
+ Map<String, NSMenu *> submenu;
+
+ struct WarpEvent {
+ NSTimeInterval timestamp;
+ NSPoint delta;
+ };
+ List<WarpEvent> warp_events;
+ NSTimeInterval last_warp = 0;
+ bool ignore_warp = false;
+
+ Vector<KeyEvent> key_event_buffer;
+ int key_event_pos = 0;
+
Point2i im_selection;
String im_text;
- Map<WindowID, WindowData> windows;
+ CGEventSourceRef event_source;
+ MouseMode mouse_mode = MOUSE_MODE_VISIBLE;
+ MouseButton last_button_state = MouseButton::NONE;
+
+ bool drop_events = false;
+ bool in_dispatch_input_event = false;
+
+ struct LayoutInfo {
+ String name;
+ String code;
+ };
+ Vector<LayoutInfo> kbd_layouts;
+ int current_layout = 0;
+ bool keyboard_layout_dirty = true;
+ WindowID last_focused_window = INVALID_WINDOW_ID;
WindowID window_id_counter = MAIN_WINDOW_ID;
+ float display_max_scale = 1.f;
+ Point2i origin;
+ bool displays_arrangement_dirty = true;
+ bool is_resizing = false;
- WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect);
- void _update_window(WindowData p_wd);
- void _send_window_event(const WindowData &wd, WindowEvent p_event);
- static void _dispatch_input_events(const Ref<InputEvent> &p_event);
- void _dispatch_input_event(const Ref<InputEvent> &p_event);
- WindowID _find_window_id(id p_window);
+ CursorShape cursor_shape = CURSOR_ARROW;
+ NSCursor *cursors[CURSOR_MAX];
+ Map<CursorShape, Vector<Variant>> cursors_cache;
+ Map<WindowID, WindowData> windows;
+
+ const NSMenu *_get_menu_root(const String &p_menu_root) const;
+ NSMenu *_get_menu_root(const String &p_menu_root);
+
+ WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect);
+ void _update_window_style(WindowData p_wd);
void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window);
+ void _update_displays_arrangement();
Point2i _get_screens_origin() const;
Point2i _get_native_screen_position(int p_screen) const;
+ static void _displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info);
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void _dispatch_input_event(const Ref<InputEvent> &p_event);
void _push_input(const Ref<InputEvent> &p_event);
void _process_key_events();
- void _release_pressed_events();
+ void _update_keyboard_layouts();
+ static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info);
- String rendering_driver;
-
- id autoreleasePool;
- CGEventSourceRef eventSource;
-
- CursorShape cursor_shape;
- NSCursor *cursors[CURSOR_MAX];
- Map<CursorShape, Vector<Variant>> cursors_cache;
-
- MouseMode mouse_mode;
- Point2i last_mouse_pos;
- MouseButton last_button_state = MouseButton::NONE;
-
- bool window_focused;
- bool drop_events;
- bool in_dispatch_input_event = false;
+ static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);
public:
+ NSMenu *get_dock_menu() const;
+ void menu_callback(id p_sender);
+
+ bool has_window(WindowID p_window) const;
+ WindowData &get_window(WindowID p_window);
+
+ void send_event(NSEvent *p_event);
+ void send_window_event(const WindowData &p_wd, WindowEvent p_event);
+ void release_pressed_events();
+ void get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) const;
+ void update_mouse_pos(WindowData &p_wd, NSPoint p_location_in_window);
+ void push_to_key_event_buffer(const KeyEvent &p_event);
+ void update_im_text(const Point2i &p_selection, const String &p_text);
+ void set_last_focused_window(WindowID p_window);
+ void mouse_process_popups(bool p_close = false);
+ void popup_open(WindowID p_window);
+ void popup_close(WindowID p_window);
+ void set_is_resizing(bool p_is_resizing);
+ bool get_is_resizing() const;
+
+ void window_update(WindowID p_window);
+ void window_destroy(WindowID p_window);
+ void window_resize(WindowID p_window, int p_width, int p_height);
+
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
@@ -212,9 +247,10 @@ public:
virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const override;
+ bool update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp);
virtual void mouse_warp_to_position(const Point2i &p_to) override;
virtual Point2i mouse_get_position() const override;
- virtual Point2i mouse_get_absolute_position() const override;
+ void mouse_set_button_state(MouseButton p_state);
virtual MouseButton mouse_get_button_state() const override;
virtual void clipboard_set(const String &p_text) override;
@@ -227,6 +263,7 @@ public:
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_max_scale() const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Vector<int> get_window_list() const override;
@@ -234,6 +271,10 @@ public:
virtual void show_window(WindowID p_id) override;
virtual void delete_sub_window(WindowID p_id) override;
+ virtual WindowID window_get_active_popup() const override;
+ virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override;
+ virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override;
+
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
@@ -250,6 +291,7 @@ public:
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+ virtual void window_set_exclusive(WindowID p_window, bool p_exclusive) override;
virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
@@ -281,6 +323,8 @@ public:
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+ virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void gl_window_make_current(DisplayServer::WindowID p_window_id) override;
@@ -291,6 +335,7 @@ public:
virtual Point2i ime_get_selection() const override;
virtual String ime_get_text() const override;
+ void cursor_update_shape();
virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override;
virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
@@ -314,9 +359,6 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
- virtual void console_set_visible(bool p_enabled) override;
- virtual bool is_console_visible() const override;
-
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
static Vector<String> get_rendering_drivers_func();
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index e9cc79f149..89ca6e50ec 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -30,6 +30,11 @@
#include "display_server_osx.h"
+#include "godot_content_view.h"
+#include "godot_menu_item.h"
+#include "godot_window.h"
+#include "godot_window_delegate.h"
+#include "key_mapping_osx.h"
#include "os_osx.h"
#include "core/io/marshalls.h"
@@ -47,222 +52,122 @@
#if defined(GLES3_ENABLED)
#include "drivers/gles3/rasterizer_gles3.h"
-
-#import <AppKit/NSOpenGLView.h>
#endif
#if defined(VULKAN_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
-
-#include <QuartzCore/CAMetalLayer.h>
-#endif
-
-#ifndef NSAppKitVersionNumber10_14
-#define NSAppKitVersionNumber10_14 1671
#endif
-#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton()))
-
-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_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) {
- const NSRect contentRect = [p_wd.window_view frame];
- const float scale = DS_OSX->screen_get_max_scale();
- p_wd.mouse_pos.x = p_locationInWindow.x * scale;
- p_wd.mouse_pos.y = (contentRect.size.height - p_locationInWindow.y) * scale;
- DS_OSX->last_mouse_pos = p_wd.mouse_pos;
- Input::get_singleton()->set_mouse_position(p_wd.mouse_pos);
- return p_wd.mouse_pos;
-}
-
-static void _push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) {
- Vector<DisplayServerOSX::KeyEvent> &buffer = DS_OSX->key_event_buffer;
- if (DS_OSX->key_event_pos >= buffer.size()) {
- buffer.resize(1 + DS_OSX->key_event_pos);
- }
- buffer.write[DS_OSX->key_event_pos++] = p_event;
-}
-
-static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
- if ([NSCursor respondsToSelector:selector]) {
- id object = [NSCursor performSelector:selector];
- if ([object isKindOfClass:[NSCursor class]]) {
- return object;
+const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const {
+ const NSMenu *menu = nullptr;
+ if (p_menu_root == "") {
+ // Main menu.
+ menu = [NSApp mainMenu];
+ } else if (p_menu_root.to_lower() == "_dock") {
+ // macOS dock menu.
+ menu = dock_menu;
+ } else {
+ // Submenu.
+ if (submenu.has(p_menu_root)) {
+ menu = submenu[p_menu_root];
}
}
- if (fallback) {
- // Fallback should be a reasonable default, no need to check.
- return [NSCursor performSelector:fallback];
- }
- return [NSCursor arrowCursor];
-}
-
-/*************************************************************************/
-/* GlobalMenuItem */
-/*************************************************************************/
-
-@interface GlobalMenuItem : NSObject {
-@public
- Callable callback;
- Variant meta;
- bool checkable;
-}
-
-@end
-
-@implementation GlobalMenuItem
-@end
-
-/*************************************************************************/
-/* GodotWindowDelegate */
-/*************************************************************************/
-
-@interface GodotWindowDelegate : NSObject {
- DisplayServerOSX::WindowID window_id;
-}
-
-- (void)windowWillClose:(NSNotification *)notification;
-- (void)setWindowID:(DisplayServerOSX::WindowID)wid;
-
-@end
-
-@implementation GodotWindowDelegate
-
-- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
- window_id = wid;
-}
-
-- (BOOL)windowShouldClose:(id)sender {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return YES;
+ if (menu == apple_menu) {
+ // Do not allow to change Apple menu.
+ return nullptr;
}
- DS_OSX->_send_window_event(DS_OSX->windows[window_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
- return NO;
+ return menu;
}
-- (void)windowWillClose:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return;
- }
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- while (wd.transient_children.size()) {
- DS_OSX->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID);
- }
-
- if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) {
- DisplayServerOSX::WindowData &pwd = DS_OSX->windows[wd.transient_parent];
- [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to parent.
- DS_OSX->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID);
- } else if ((window_id != DisplayServerOSX::MAIN_WINDOW_ID) && (DS_OSX->windows.size() == 1)) {
- DisplayServerOSX::WindowData &pwd = DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID];
- [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to main window if there is no parent or other windows left.
- }
-
-#if defined(GLES3_ENABLED)
- if (DS_OSX->gl_manager) {
- DS_OSX->gl_manager->window_destroy(window_id);
- }
-#endif
-#ifdef VULKAN_ENABLED
- if (DS_OSX->context_vulkan) {
- DS_OSX->context_vulkan->window_destroy(window_id);
+NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) {
+ NSMenu *menu = nullptr;
+ if (p_menu_root == "") {
+ // Main menu.
+ menu = [NSApp mainMenu];
+ } else if (p_menu_root.to_lower() == "_dock") {
+ // macOS dock menu.
+ menu = dock_menu;
+ } else {
+ // Submenu.
+ if (!submenu.has(p_menu_root)) {
+ NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
+ submenu[p_menu_root] = n_menu;
+ }
+ menu = submenu[p_menu_root];
}
-#endif
-
- DS_OSX->windows.erase(window_id);
-}
-
-- (void)windowDidEnterFullScreen:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return;
+ if (menu == apple_menu) {
+ // Do not allow to change Apple menu.
+ return nullptr;
}
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- wd.fullscreen = true;
-
- [wd.window_object setContentMinSize:NSMakeSize(0, 0)];
- [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
- // Force window resize event.
- [self windowDidResize:notification];
+ return menu;
}
-- (void)windowDidExitFullScreen:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return;
- }
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- wd.fullscreen = false;
-
- const float scale = DS_OSX->screen_get_max_scale();
- if (wd.min_size != Size2i()) {
- Size2i size = wd.min_size / scale;
- [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)];
- }
- if (wd.max_size != Size2i()) {
- Size2i size = wd.max_size / scale;
- [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
- }
-
- if (wd.resize_disabled) {
- [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
- }
-
- if (wd.on_top) {
- [wd.window_object setLevel:NSFloatingWindowLevel];
- }
- // Force window resize event.
- [self windowDidResize:notification];
-}
+DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect) {
+ WindowID id;
+ const float scale = screen_get_max_scale();
+ {
+ WindowData wd;
-- (void)windowDidChangeBackingProperties:(NSNotification *)notification {
- if (!DisplayServerOSX::get_singleton()) {
- return;
- }
+ wd.window_delegate = [[GodotWindowDelegate alloc] init];
+ ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate");
+ [wd.window_delegate setWindowID:window_id_counter];
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+ Point2i position = p_rect.position;
+ // OS X native y-coordinate relative to _get_screens_origin() is negative,
+ // Godot passes a positive value.
+ position.y *= -1;
+ position += _get_screens_origin();
- CGFloat newBackingScaleFactor = [wd.window_object backingScaleFactor];
- CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
+ // initWithContentRect uses bottom-left corner of the window’s frame as origin.
+ wd.window_object = [[GodotWindow alloc]
+ initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale)
+ styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window");
+ [wd.window_object setWindowID:window_id_counter];
- if (newBackingScaleFactor != oldBackingScaleFactor) {
- //Set new display scale and window size
- const float scale = DS_OSX->screen_get_max_scale();
- const NSRect contentRect = [wd.window_view frame];
+ wd.window_view = [[GodotContentView alloc] init];
+ ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view");
+ [wd.window_view setWindowID:window_id_counter];
+ [wd.window_view setWantsLayer:TRUE];
- wd.size.width = contentRect.size.width * scale;
- wd.size.height = contentRect.size.height * scale;
+ [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+ [wd.window_object setContentView:wd.window_view];
+ [wd.window_object setDelegate:wd.window_delegate];
+ [wd.window_object setAcceptsMouseMovedEvents:YES];
+ [wd.window_object setRestorable:NO];
+ [wd.window_object setColorSpace:[NSColorSpace sRGBColorSpace]];
- DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE);
+ if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) {
+ [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed];
+ }
CALayer *layer = [wd.window_view layer];
if (layer) {
layer.contentsScale = scale;
}
- //Force window resize event
- [self windowDidResize:notification];
+#if defined(VULKAN_ENABLED)
+ if (context_vulkan) {
+ Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, wd.window_view, p_rect.size.width, p_rect.size.height);
+ ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context");
+ }
+#endif
+#if defined(GLES3_ENABLED)
+ if (gl_manager) {
+ Error err = gl_manager->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height);
+ ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context");
+ }
+#endif
+ id = window_id_counter++;
+ windows[id] = wd;
}
-}
-- (void)windowDidResize:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return;
- }
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+ WindowData &wd = windows[id];
+ window_set_mode(p_mode, id);
const NSRect contentRect = [wd.window_view frame];
-
- const float scale = DS_OSX->screen_get_max_scale();
wd.size.width = contentRect.size.width * scale;
wd.size.height = contentRect.size.height * scale;
@@ -272,1203 +177,471 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {
}
#if defined(GLES3_ENABLED)
- if (DS_OSX->gl_manager) {
- DS_OSX->gl_manager->window_resize(window_id, wd.size.width, wd.size.height);
+ if (gl_manager) {
+ gl_manager->window_resize(id, wd.size.width, wd.size.height);
}
#endif
#if defined(VULKAN_ENABLED)
- if (DS_OSX->context_vulkan) {
- DS_OSX->context_vulkan->window_resize(window_id, wd.size.width, wd.size.height);
+ if (context_vulkan) {
+ context_vulkan->window_resize(id, wd.size.width, wd.size.height);
}
#endif
- if (!wd.rect_changed_callback.is_null()) {
- Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id));
- Variant *sizep = &size;
- Variant ret;
- Callable::CallError ce;
- wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
- }
+ return id;
}
-- (void)windowDidMove:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return;
- }
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- DS_OSX->_release_pressed_events();
+void DisplayServerOSX::_update_window_style(WindowData p_wd) {
+ bool borderless_full = false;
- if (!wd.rect_changed_callback.is_null()) {
- Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id));
- Variant *sizep = &size;
- Variant ret;
- Callable::CallError ce;
- wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
- }
-}
+ if (p_wd.borderless) {
+ NSRect frameRect = [p_wd.window_object frame];
+ NSRect screenRect = [[p_wd.window_object screen] frame];
-- (void)windowDidBecomeKey:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return;
+ // Check if our window covers up the screen.
+ if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y &&
+ frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) {
+ borderless_full = true;
+ }
}
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
- if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CAPTURED) {
- const NSRect contentRect = [wd.window_view frame];
- NSRect pointInWindowRect = NSMakeRect(contentRect.size.width / 2, contentRect.size.height / 2, 0, 0);
- NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin;
- CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y };
- CGWarpMouseCursorPosition(lMouseWarpPos);
+ if (borderless_full) {
+ // If the window covers up the screen set the level to above the main menu and hide on deactivate.
+ [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1];
+ [p_wd.window_object setHidesOnDeactivate:YES];
} else {
- _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
- Input::get_singleton()->set_mouse_position(wd.mouse_pos);
- }
-
- DS_OSX->window_focused = true;
- DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
-}
-
-- (void)windowDidResignKey:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return;
+ // Reset these when our window is not a borderless window that covers up the screen.
+ if (p_wd.on_top && !p_wd.fullscreen) {
+ [p_wd.window_object setLevel:NSFloatingWindowLevel];
+ } else {
+ [p_wd.window_object setLevel:NSNormalWindowLevel];
+ }
+ [p_wd.window_object setHidesOnDeactivate:NO];
}
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- DS_OSX->window_focused = false;
-
- DS_OSX->_release_pressed_events();
- DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
}
-- (void)windowDidMiniaturize:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
- return;
- }
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- DS_OSX->window_focused = false;
-
- DS_OSX->_release_pressed_events();
- DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
-}
+void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) {
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
-- (void)windowDidDeminiaturize:(NSNotification *)notification {
- if (!DS_OSX || !DS_OSX->windows.has(window_id)) {
+ if (!OS::get_singleton()->is_layered_allowed()) {
return;
}
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- DS_OSX->window_focused = true;
- DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
-}
-
-@end
-
-/*************************************************************************/
-/* GodotContentView */
-/*************************************************************************/
-
+ if (wd.layered_window != p_enabled) {
+ if (p_enabled) {
+ [wd.window_object setBackgroundColor:[NSColor clearColor]];
+ [wd.window_object setOpaque:NO];
+ [wd.window_object setHasShadow:NO];
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ [layer setBackgroundColor:[NSColor clearColor].CGColor];
+ [layer setOpaque:NO];
+ }
#if defined(GLES3_ENABLED)
-@interface GodotContentView : NSOpenGLView <NSTextInputClient> {
-#else
-@interface GodotContentView : NSView <NSTextInputClient> {
-#endif
-
- DisplayServerOSX::WindowID window_id;
- NSTrackingArea *trackingArea;
- NSMutableAttributedString *markedText;
- bool imeInputEventInProgress;
-}
-
-- (void)cancelComposition;
-- (CALayer *)makeBackingLayer;
-- (BOOL)wantsUpdateLayer;
-- (void)updateLayer;
-- (void)setWindowID:(DisplayServerOSX::WindowID)wid;
-
-@end
-
-@implementation GodotContentView
-
-- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
- window_id = wid;
-}
-
-+ (void)initialize {
- if (self == [GodotContentView class]) {
- // nothing left to do here at the moment..
- }
-}
-
-- (CALayer *)makeBackingLayer {
-#if defined(VULKAN_ENABLED)
- if (DS_OSX->context_vulkan) {
- CALayer *layer = [[CAMetalLayer class] layer];
- return layer;
- }
+ if (gl_manager) {
+ gl_manager->window_set_per_pixel_transparency_enabled(p_window, true);
+ }
#endif
- return [super makeBackingLayer];
-}
-
-- (void)updateLayer {
+ wd.layered_window = true;
+ } else {
+ [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]];
+ [wd.window_object setOpaque:YES];
+ [wd.window_object setHasShadow:YES];
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ [layer setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1].CGColor];
+ [layer setOpaque:YES];
+ }
#if defined(GLES3_ENABLED)
- if (DS_OSX->gl_manager) {
- DS_OSX->gl_manager->window_update(window_id);
- }
+ if (gl_manager) {
+ gl_manager->window_set_per_pixel_transparency_enabled(p_window, false);
+ }
#endif
-#if defined(VULKAN_ENABLED)
- if (DS_OSX->context_vulkan) {
- [super updateLayer];
+ wd.layered_window = false;
+ }
+ NSRect frameRect = [wd.window_object frame];
+ [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES];
+ [wd.window_object setFrame:frameRect display:YES];
}
-#endif
}
-- (BOOL)wantsUpdateLayer {
- return YES;
-}
-
-- (id)init {
- self = [super init];
- trackingArea = nil;
- imeInputEventInProgress = false;
- [self updateTrackingAreas];
-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
- [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]];
-#else
- [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
-#endif
- markedText = [[NSMutableAttributedString alloc] init];
- return self;
-}
+void DisplayServerOSX::_update_displays_arrangement() {
+ origin = Point2i();
-- (void)dealloc {
- [trackingArea release];
- [markedText release];
- [super dealloc];
+ for (int i = 0; i < get_screen_count(); i++) {
+ Point2i position = _get_native_screen_position(i);
+ if (position.x < origin.x) {
+ origin.x = position.x;
+ }
+ if (position.y > origin.y) {
+ origin.y = position.y;
+ }
+ }
+ displays_arrangement_dirty = false;
}
-static const NSRange kEmptyRange = { NSNotFound, 0 };
-
-- (BOOL)hasMarkedText {
- return (markedText.length > 0);
-}
+Point2i DisplayServerOSX::_get_screens_origin() const {
+ // Returns the native top-left screen coordinate of the smallest rectangle
+ // that encompasses all screens. Needed in get_screen_position(),
+ // window_get_position, and window_set_position()
+ // to convert between OS X native screen coordinates and the ones expected by Godot.
-- (NSRange)markedRange {
- return NSMakeRange(0, markedText.length);
-}
+ if (displays_arrangement_dirty) {
+ const_cast<DisplayServerOSX *>(this)->_update_displays_arrangement();
+ }
-- (NSRange)selectedRange {
- return kEmptyRange;
+ return origin;
}
-- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
- if ([aString isKindOfClass:[NSAttributedString class]]) {
- [markedText initWithAttributedString:aString];
- } else {
- [markedText initWithString:aString];
- }
- if (markedText.length == 0) {
- [self unmarkText];
- return;
+Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const {
+ NSArray *screenArray = [NSScreen screens];
+ if ((NSUInteger)p_screen < [screenArray count]) {
+ NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
+ // Return the top-left corner of the screen, for OS X the y starts at the bottom.
+ return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale();
}
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- if (wd.im_active) {
- imeInputEventInProgress = true;
- DS_OSX->im_text.parse_utf8([[markedText mutableString] UTF8String]);
- DS_OSX->im_selection = Point2i(selectedRange.location, selectedRange.length);
+ return Point2i();
+}
- OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+void DisplayServerOSX::_displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->displays_arrangement_dirty = true;
}
}
-- (void)doCommandBySelector:(SEL)aSelector {
- if ([self respondsToSelector:aSelector]) {
- [self performSelector:aSelector];
- }
+void DisplayServerOSX::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event);
}
-- (void)unmarkText {
- imeInputEventInProgress = false;
- [[markedText mutableString] setString:@""];
+void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ _THREAD_SAFE_METHOD_
+ if (!in_dispatch_input_event) {
+ in_dispatch_input_event = true;
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+ Variant ev = p_event;
+ Variant *evp = &ev;
+ Variant ret;
+ Callable::CallError ce;
- if (wd.im_active) {
- DS_OSX->im_text = String();
- DS_OSX->im_selection = Point2i();
+ {
+ List<WindowID>::Element *E = popup_list.front();
+ if (E && Object::cast_to<InputEventKey>(*p_event)) {
+ // Redirect keyboard input to active popup.
+ if (windows.has(E->get())) {
+ Callable callable = windows[E->get()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+ in_dispatch_input_event = false;
+ return;
+ }
+ }
- OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+ Ref<InputEventFromWindow> event_from_window = p_event;
+ if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
+ // Send to a window.
+ if (windows.has(event_from_window->get_window_id())) {
+ Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+ } else {
+ // Send to all windows.
+ for (KeyValue<WindowID, WindowData> &E : windows) {
+ Callable callable = E.value.input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+ }
+ in_dispatch_input_event = false;
}
}
-- (NSArray *)validAttributesForMarkedText {
- return [NSArray array];
-}
-
-- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
- return nil;
-}
-
-- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint {
- return 0;
-}
-
-- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
- ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NSMakeRect(0, 0, 0, 0));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- const NSRect contentRect = [wd.window_view frame];
- const float scale = DS_OSX->screen_get_max_scale();
- NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / scale, contentRect.size.height - (wd.im_position.y / scale) - 1, 0, 0);
- NSPoint pointOnScreen = [wd.window_object convertRectToScreen:pointInWindowRect].origin;
-
- return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0);
+void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEvent> ev = p_event;
+ Input::get_singleton()->parse_input_event(ev);
}
-- (void)cancelComposition {
- [self unmarkText];
- NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext];
- [currentInputContext discardMarkedText];
-}
+void DisplayServerOSX::_process_key_events() {
+ Ref<InputEventKey> k;
+ for (int i = 0; i < key_event_pos; i++) {
+ const KeyEvent &ke = key_event_buffer[i];
+ if (ke.raw) {
+ // Non IME input - no composite characters, pass events as is.
+ k.instantiate();
-- (void)insertText:(id)aString {
- [self insertText:aString replacementRange:NSMakeRange(0, 0)];
-}
+ k->set_window_id(ke.window_id);
+ get_key_modifier_state(ke.osx_state, k);
+ k->set_pressed(ke.pressed);
+ k->set_echo(ke.echo);
+ k->set_keycode(ke.keycode);
+ k->set_physical_keycode((Key)ke.physical_keycode);
+ k->set_unicode(ke.unicode);
-- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
- NSEvent *event = [NSApp currentEvent];
+ _push_input(k);
+ } else {
+ // IME input.
+ if ((i == 0 && ke.keycode == Key::NONE) || (i > 0 && key_event_buffer[i - 1].keycode == Key::NONE)) {
+ k.instantiate();
- NSString *characters;
- if ([aString isKindOfClass:[NSAttributedString class]]) {
- characters = [aString string];
- } else {
- characters = (NSString *)aString;
- }
+ k->set_window_id(ke.window_id);
+ get_key_modifier_state(ke.osx_state, k);
+ k->set_pressed(ke.pressed);
+ k->set_echo(ke.echo);
+ k->set_keycode(Key::NONE);
+ k->set_physical_keycode(Key::NONE);
+ k->set_unicode(ke.unicode);
- NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet];
- NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
- if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) {
- NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext];
- [currentInputContext discardMarkedText];
- [self cancelComposition];
- return;
- }
+ _push_input(k);
+ }
+ if (ke.keycode != Key::NONE) {
+ k.instantiate();
- Char16String text;
- text.resize([characters length] + 1);
- [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
+ k->set_window_id(ke.window_id);
+ get_key_modifier_state(ke.osx_state, k);
+ k->set_pressed(ke.pressed);
+ k->set_echo(ke.echo);
+ k->set_keycode(ke.keycode);
+ k->set_physical_keycode((Key)ke.physical_keycode);
- String u32text;
- u32text.parse_utf16(text.ptr(), text.length());
+ if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) {
+ k->set_unicode(key_event_buffer[i + 1].unicode);
+ }
- for (int i = 0; i < u32text.length(); i++) {
- const char32_t codepoint = u32text[i];
- if ((codepoint & 0xFF00) == 0xF700) {
- continue;
+ _push_input(k);
+ }
}
-
- DisplayServerOSX::KeyEvent ke;
-
- ke.window_id = window_id;
- ke.osx_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = false;
- ke.raw = false; // IME input event
- ke.keycode = Key::NONE;
- ke.physical_keycode = Key::NONE;
- ke.unicode = codepoint;
-
- _push_to_key_event_buffer(ke);
}
- [self cancelComposition];
-}
-- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
- return NSDragOperationCopy;
+ key_event_pos = 0;
}
-- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
- return NSDragOperationCopy;
-}
+void DisplayServerOSX::_update_keyboard_layouts() {
+ kbd_layouts.clear();
+ current_layout = 0;
-- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
- ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NO);
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+ TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource();
+ NSString *cur_name = (__bridge NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName);
+ CFRelease(cur_source);
- if (!wd.drop_files_callback.is_null()) {
- Vector<String> files;
- NSPasteboard *pboard = [sender draggingPasteboard];
+ // Enum IME layouts.
+ NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode };
+ NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false);
+ for (NSUInteger i = 0; i < [list_ime count]; i++) {
+ LayoutInfo ly;
+ NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName);
+ ly.name.parse_utf8([name UTF8String]);
-#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
- NSArray *items = pboard.pasteboardItems;
- for (NSPasteboardItem *item in items) {
- NSString *path = [item stringForType:NSPasteboardTypeFileURL];
- NSString *ns = [NSURL URLWithString:path].path;
- char *utfs = strdup([ns UTF8String]);
- String ret;
- ret.parse_utf8(utfs);
- free(utfs);
- files.push_back(ret);
- }
-#else
- NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
- for (NSString *ns in filenames) {
- char *utfs = strdup([ns UTF8String]);
- String ret;
- ret.parse_utf8(utfs);
- free(utfs);
- files.push_back(ret);
- }
-#endif
+ NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages);
+ ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]);
+ kbd_layouts.push_back(ly);
- Variant v = files;
- Variant *vp = &v;
- Variant ret;
- Callable::CallError ce;
- wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+ if ([name isEqualToString:cur_name]) {
+ current_layout = kbd_layouts.size() - 1;
+ }
}
- return NO;
-}
+ // Enum plain keyboard layouts.
+ NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout };
+ NSArray *list_kbd = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false);
+ for (NSUInteger i = 0; i < [list_kbd count]; i++) {
+ LayoutInfo ly;
+ NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName);
+ ly.name.parse_utf8([name UTF8String]);
-- (BOOL)isOpaque {
- return YES;
-}
+ NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages);
+ ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]);
+ kbd_layouts.push_back(ly);
-- (BOOL)canBecomeKeyView {
- if (DS_OSX->windows.has(window_id)) {
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
- if (wd.no_focus) {
- return NO;
+ if ([name isEqualToString:cur_name]) {
+ current_layout = kbd_layouts.size() - 1;
}
}
- return YES;
-}
-
-- (BOOL)acceptsFirstResponder {
- return YES;
-}
-- (void)cursorUpdate:(NSEvent *)event {
- DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape;
- DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX;
- DS_OSX->cursor_set_shape(p_shape);
+ keyboard_layout_dirty = false;
}
-static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, MouseButton index, MouseButton mask, bool pressed) {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- if (pressed) {
- DS_OSX->last_button_state |= mask;
- } else {
- DS_OSX->last_button_state &= (MouseButton)~mask;
+void DisplayServerOSX::_keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->keyboard_layout_dirty = true;
}
-
- Ref<InputEventMouseButton> mb;
- mb.instantiate();
- mb->set_window_id(window_id);
- const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow]);
- _get_key_modifier_state([event modifierFlags], mb);
- mb->set_button_index(index);
- mb->set_pressed(pressed);
- mb->set_position(pos);
- mb->set_global_position(pos);
- mb->set_button_mask(DS_OSX->last_button_state);
- if (index == MouseButton::LEFT && pressed) {
- mb->set_double_click([event clickCount] == 2);
- }
-
- Input::get_singleton()->parse_input_event(mb);
}
-- (void)mouseDown:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- if (([event modifierFlags] & NSEventModifierFlagControl)) {
- wd.mouse_down_control = true;
- _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, true);
- } else {
- wd.mouse_down_control = false;
- _mouseDownEvent(window_id, event, MouseButton::LEFT, MouseButton::MASK_LEFT, true);
+NSCursor *DisplayServerOSX::_cursor_from_selector(SEL p_selector, SEL p_fallback) {
+ if ([NSCursor respondsToSelector:p_selector]) {
+ id object = [NSCursor performSelector:p_selector];
+ if ([object isKindOfClass:[NSCursor class]]) {
+ return object;
+ }
}
-}
-
-- (void)mouseDragged:(NSEvent *)event {
- [self mouseMoved:event];
-}
-
-- (void)mouseUp:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- if (wd.mouse_down_control) {
- _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, false);
- } else {
- _mouseDownEvent(window_id, event, MouseButton::LEFT, MouseButton::MASK_LEFT, false);
+ if (p_fallback) {
+ // Fallback should be a reasonable default, no need to check.
+ return [NSCursor performSelector:p_fallback];
}
+ return [NSCursor arrowCursor];
}
-- (void)mouseMoved:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- NSPoint delta = NSMakePoint([event deltaX], [event deltaY]);
- NSPoint mpos = [event locationInWindow];
+NSMenu *DisplayServerOSX::get_dock_menu() const {
+ return dock_menu;
+}
- if (DS_OSX->ignore_warp) {
- // Discard late events, before warp
- if (([event timestamp]) < DS_OSX->last_warp) {
- return;
- }
- DS_OSX->ignore_warp = false;
+void DisplayServerOSX::menu_callback(id p_sender) {
+ if (![p_sender representedObject]) {
return;
}
- if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED || DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED_HIDDEN) {
- // Discard late events
- if (([event timestamp]) < DS_OSX->last_warp) {
- return;
- }
+ GodotMenuItem *value = [p_sender representedObject];
- // Warp affects next event delta, subtract previous warp deltas
- List<DisplayServerOSX::WarpEvent>::Element *F = DS_OSX->warp_events.front();
- while (F) {
- if (F->get().timestamp < [event timestamp]) {
- List<DisplayServerOSX::WarpEvent>::Element *E = F;
- delta.x -= E->get().delta.x;
- delta.y -= E->get().delta.y;
- F = F->next();
- DS_OSX->warp_events.erase(E);
+ if (value) {
+ if (value->checkable) {
+ if ([p_sender state] == NSControlStateValueOff) {
+ [p_sender setState:NSControlStateValueOn];
} else {
- F = F->next();
+ [p_sender setState:NSControlStateValueOff];
}
}
- // Confine mouse position to the window, and update delta
- NSRect frame = [wd.window_object frame];
- NSPoint conf_pos = mpos;
- conf_pos.x = CLAMP(conf_pos.x + delta.x, 0.f, frame.size.width);
- conf_pos.y = CLAMP(conf_pos.y - delta.y, 0.f, frame.size.height);
- delta.x = conf_pos.x - mpos.x;
- delta.y = mpos.y - conf_pos.y;
- mpos = conf_pos;
-
- // Move mouse cursor
- NSRect pointInWindowRect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0);
- conf_pos = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin;
- conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y;
- CGWarpMouseCursorPosition(conf_pos);
-
- // Save warp data
- DS_OSX->last_warp = [[NSProcessInfo processInfo] systemUptime];
- DisplayServerOSX::WarpEvent ev;
- ev.timestamp = DS_OSX->last_warp;
- ev.delta = delta;
- DS_OSX->warp_events.push_back(ev);
- }
-
- Ref<InputEventMouseMotion> mm;
- mm.instantiate();
-
- mm->set_window_id(window_id);
- mm->set_button_mask(DS_OSX->last_button_state);
- const Vector2i pos = _get_mouse_pos(wd, mpos);
- mm->set_position(pos);
- mm->set_pressure([event pressure]);
- if ([event subtype] == NSEventSubtypeTabletPoint) {
- const NSPoint p = [event tilt];
- mm->set_tilt(Vector2(p.x, p.y));
+ if (value->callback != Callable()) {
+ Variant tag = value->meta;
+ Variant *tagp = &tag;
+ Variant ret;
+ Callable::CallError ce;
+ value->callback.call((const Variant **)&tagp, 1, ret, ce);
+ }
}
- mm->set_global_position(pos);
- mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
- const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * DS_OSX->screen_get_max_scale();
- mm->set_relative(relativeMotion);
- _get_key_modifier_state([event modifierFlags], mm);
-
- Input::get_singleton()->set_mouse_position(wd.mouse_pos);
- Input::get_singleton()->parse_input_event(mm);
-}
-
-- (void)rightMouseDown:(NSEvent *)event {
- _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, true);
}
-- (void)rightMouseDragged:(NSEvent *)event {
- [self mouseMoved:event];
+bool DisplayServerOSX::has_window(WindowID p_window) const {
+ return windows.has(p_window);
}
-- (void)rightMouseUp:(NSEvent *)event {
- _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, false);
+DisplayServerOSX::WindowData &DisplayServerOSX::get_window(WindowID p_window) {
+ return windows[p_window];
}
-- (void)otherMouseDown:(NSEvent *)event {
- if ((int)[event buttonNumber] == 2) {
- _mouseDownEvent(window_id, event, MouseButton::MIDDLE, MouseButton::MASK_MIDDLE, true);
- } else if ((int)[event buttonNumber] == 3) {
- _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON1, MouseButton::MASK_XBUTTON1, true);
- } else if ((int)[event buttonNumber] == 4) {
- _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON2, MouseButton::MASK_XBUTTON2, true);
- } else {
- return;
+void DisplayServerOSX::send_event(NSEvent *p_event) {
+ if ([p_event type] == NSEventTypeLeftMouseDown || [p_event type] == NSEventTypeRightMouseDown || [p_event type] == NSEventTypeOtherMouseDown) {
+ mouse_process_popups();
}
-}
+ // Special case handling of command-period, which is traditionally a special
+ // shortcut in macOS and doesn't arrive at our regular keyDown handler.
+ if ([p_event type] == NSEventTypeKeyDown) {
+ if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) {
+ Ref<InputEventKey> k;
+ k.instantiate();
-- (void)otherMouseDragged:(NSEvent *)event {
- [self mouseMoved:event];
-}
+ get_key_modifier_state([p_event modifierFlags], k);
+ k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID);
+ k->set_pressed(true);
+ k->set_keycode(Key::PERIOD);
+ k->set_physical_keycode(Key::PERIOD);
+ k->set_echo([p_event isARepeat]);
-- (void)otherMouseUp:(NSEvent *)event {
- if ((int)[event buttonNumber] == 2) {
- _mouseDownEvent(window_id, event, MouseButton::MIDDLE, MouseButton::MASK_MIDDLE, false);
- } else if ((int)[event buttonNumber] == 3) {
- _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON1, MouseButton::MASK_XBUTTON1, false);
- } else if ((int)[event buttonNumber] == 4) {
- _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON2, MouseButton::MASK_XBUTTON2, false);
- } else {
- return;
+ Input::get_singleton()->parse_input_event(k);
+ }
}
}
-- (void)mouseExited:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
+void DisplayServerOSX::send_window_event(const WindowData &wd, WindowEvent p_event) {
+ _THREAD_SAFE_METHOD_
- if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) {
- DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT);
+ if (!wd.event_callback.is_null()) {
+ Variant event = int(p_event);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ wd.event_callback.call((const Variant **)&eventp, 1, ret, ce);
}
}
-- (void)mouseEntered:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) {
- DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER);
+void DisplayServerOSX::release_pressed_events() {
+ _THREAD_SAFE_METHOD_
+ if (Input::get_singleton()) {
+ Input::get_singleton()->release_pressed_events();
}
-
- DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape;
- DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX;
- DS_OSX->cursor_set_shape(p_shape);
}
-- (void)magnifyWithEvent:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- Ref<InputEventMagnifyGesture> ev;
- ev.instantiate();
- ev->set_window_id(window_id);
- _get_key_modifier_state([event modifierFlags], ev);
- ev->set_position(_get_mouse_pos(wd, [event locationInWindow]));
- ev->set_factor([event magnification] + 1.0);
-
- Input::get_singleton()->parse_input_event(ev);
+void DisplayServerOSX::get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) const {
+ 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));
}
-- (void)viewDidChangeBackingProperties {
- // nothing left to do here
-}
-
-- (void)updateTrackingAreas {
- if (trackingArea != nil) {
- [self removeTrackingArea:trackingArea];
- [trackingArea release];
- }
-
- NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect;
- trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil];
-
- [self addTrackingArea:trackingArea];
- [super updateTrackingAreas];
-}
-
-static bool isNumpadKey(unsigned int key) {
- static const unsigned int table[] = {
- 0x41, /* kVK_ANSI_KeypadDecimal */
- 0x43, /* kVK_ANSI_KeypadMultiply */
- 0x45, /* kVK_ANSI_KeypadPlus */
- 0x47, /* kVK_ANSI_KeypadClear */
- 0x4b, /* kVK_ANSI_KeypadDivide */
- 0x4c, /* kVK_ANSI_KeypadEnter */
- 0x4e, /* kVK_ANSI_KeypadMinus */
- 0x51, /* kVK_ANSI_KeypadEquals */
- 0x52, /* kVK_ANSI_Keypad0 */
- 0x53, /* kVK_ANSI_Keypad1 */
- 0x54, /* kVK_ANSI_Keypad2 */
- 0x55, /* kVK_ANSI_Keypad3 */
- 0x56, /* kVK_ANSI_Keypad4 */
- 0x57, /* kVK_ANSI_Keypad5 */
- 0x58, /* kVK_ANSI_Keypad6 */
- 0x59, /* kVK_ANSI_Keypad7 */
- 0x5b, /* kVK_ANSI_Keypad8 */
- 0x5c, /* kVK_ANSI_Keypad9 */
- 0x5f, /* kVK_JIS_KeypadComma */
- 0x00
- };
- for (int i = 0; table[i] != 0; i++) {
- if (key == table[i]) {
- return true;
- }
- }
- return false;
+void DisplayServerOSX::update_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_location_in_window) {
+ const NSRect content_rect = [p_wd.window_view frame];
+ const float scale = screen_get_max_scale();
+ p_wd.mouse_pos.x = p_location_in_window.x * scale;
+ p_wd.mouse_pos.y = (content_rect.size.height - p_location_in_window.y) * scale;
+ Input::get_singleton()->set_mouse_position(p_wd.mouse_pos);
}
-// Keyboard symbol translation table
-static const Key _osx_to_godot_table[128] = {
- /* 00 */ Key::A,
- /* 01 */ Key::S,
- /* 02 */ Key::D,
- /* 03 */ Key::F,
- /* 04 */ Key::H,
- /* 05 */ Key::G,
- /* 06 */ Key::Z,
- /* 07 */ Key::X,
- /* 08 */ Key::C,
- /* 09 */ Key::V,
- /* 0a */ Key::SECTION, /* ISO Section */
- /* 0b */ Key::B,
- /* 0c */ Key::Q,
- /* 0d */ Key::W,
- /* 0e */ Key::E,
- /* 0f */ Key::R,
- /* 10 */ Key::Y,
- /* 11 */ Key::T,
- /* 12 */ Key::KEY_1,
- /* 13 */ Key::KEY_2,
- /* 14 */ Key::KEY_3,
- /* 15 */ Key::KEY_4,
- /* 16 */ Key::KEY_6,
- /* 17 */ Key::KEY_5,
- /* 18 */ Key::EQUAL,
- /* 19 */ Key::KEY_9,
- /* 1a */ Key::KEY_7,
- /* 1b */ Key::MINUS,
- /* 1c */ Key::KEY_8,
- /* 1d */ Key::KEY_0,
- /* 1e */ Key::BRACERIGHT,
- /* 1f */ Key::O,
- /* 20 */ Key::U,
- /* 21 */ Key::BRACELEFT,
- /* 22 */ Key::I,
- /* 23 */ Key::P,
- /* 24 */ Key::ENTER,
- /* 25 */ Key::L,
- /* 26 */ Key::J,
- /* 27 */ Key::APOSTROPHE,
- /* 28 */ Key::K,
- /* 29 */ Key::SEMICOLON,
- /* 2a */ Key::BACKSLASH,
- /* 2b */ Key::COMMA,
- /* 2c */ Key::SLASH,
- /* 2d */ Key::N,
- /* 2e */ Key::M,
- /* 2f */ Key::PERIOD,
- /* 30 */ Key::TAB,
- /* 31 */ Key::SPACE,
- /* 32 */ Key::QUOTELEFT,
- /* 33 */ Key::BACKSPACE,
- /* 34 */ Key::UNKNOWN,
- /* 35 */ Key::ESCAPE,
- /* 36 */ Key::META,
- /* 37 */ Key::META,
- /* 38 */ Key::SHIFT,
- /* 39 */ Key::CAPSLOCK,
- /* 3a */ Key::ALT,
- /* 3b */ Key::CTRL,
- /* 3c */ Key::SHIFT,
- /* 3d */ Key::ALT,
- /* 3e */ Key::CTRL,
- /* 3f */ Key::UNKNOWN, /* Function */
- /* 40 */ Key::UNKNOWN, /* F17 */
- /* 41 */ Key::KP_PERIOD,
- /* 42 */ Key::UNKNOWN,
- /* 43 */ Key::KP_MULTIPLY,
- /* 44 */ Key::UNKNOWN,
- /* 45 */ Key::KP_ADD,
- /* 46 */ Key::UNKNOWN,
- /* 47 */ Key::NUMLOCK, /* Really KeypadClear... */
- /* 48 */ Key::VOLUMEUP, /* VolumeUp */
- /* 49 */ Key::VOLUMEDOWN, /* VolumeDown */
- /* 4a */ Key::VOLUMEMUTE, /* Mute */
- /* 4b */ Key::KP_DIVIDE,
- /* 4c */ Key::KP_ENTER,
- /* 4d */ Key::UNKNOWN,
- /* 4e */ Key::KP_SUBTRACT,
- /* 4f */ Key::UNKNOWN, /* F18 */
- /* 50 */ Key::UNKNOWN, /* F19 */
- /* 51 */ Key::EQUAL, /* KeypadEqual */
- /* 52 */ Key::KP_0,
- /* 53 */ Key::KP_1,
- /* 54 */ Key::KP_2,
- /* 55 */ Key::KP_3,
- /* 56 */ Key::KP_4,
- /* 57 */ Key::KP_5,
- /* 58 */ Key::KP_6,
- /* 59 */ Key::KP_7,
- /* 5a */ Key::UNKNOWN, /* F20 */
- /* 5b */ Key::KP_8,
- /* 5c */ Key::KP_9,
- /* 5d */ Key::YEN, /* JIS Yen */
- /* 5e */ Key::UNDERSCORE, /* JIS Underscore */
- /* 5f */ Key::COMMA, /* JIS KeypadComma */
- /* 60 */ Key::F5,
- /* 61 */ Key::F6,
- /* 62 */ Key::F7,
- /* 63 */ Key::F3,
- /* 64 */ Key::F8,
- /* 65 */ Key::F9,
- /* 66 */ Key::UNKNOWN, /* JIS Eisu */
- /* 67 */ Key::F11,
- /* 68 */ Key::UNKNOWN, /* JIS Kana */
- /* 69 */ Key::F13,
- /* 6a */ Key::F16,
- /* 6b */ Key::F14,
- /* 6c */ Key::UNKNOWN,
- /* 6d */ Key::F10,
- /* 6e */ Key::MENU,
- /* 6f */ Key::F12,
- /* 70 */ Key::UNKNOWN,
- /* 71 */ Key::F15,
- /* 72 */ Key::INSERT, /* Really Help... */
- /* 73 */ Key::HOME,
- /* 74 */ Key::PAGEUP,
- /* 75 */ Key::KEY_DELETE,
- /* 76 */ Key::F4,
- /* 77 */ Key::END,
- /* 78 */ Key::F2,
- /* 79 */ Key::PAGEDOWN,
- /* 7a */ Key::F1,
- /* 7b */ Key::LEFT,
- /* 7c */ Key::RIGHT,
- /* 7d */ Key::DOWN,
- /* 7e */ Key::UP,
- /* 7f */ Key::UNKNOWN,
-};
-
-// Translates a OS X keycode to a Godot keycode
-static Key translateKey(unsigned int key) {
- if (key >= 128) {
- return Key::UNKNOWN;
- }
-
- return _osx_to_godot_table[key];
-}
-
-// Translates a Godot keycode back to a OSX keycode
-static unsigned int unmapKey(Key key) {
- for (int i = 0; i <= 126; i++) {
- if (_osx_to_godot_table[i] == key) {
- return i;
- }
- }
- return 127;
-}
-
-struct _KeyCodeMap {
- UniChar kchar;
- Key kcode;
-};
-
-static const _KeyCodeMap _keycodes[55] = {
- { '`', Key::QUOTELEFT },
- { '~', Key::ASCIITILDE },
- { '0', Key::KEY_0 },
- { '1', Key::KEY_1 },
- { '2', Key::KEY_2 },
- { '3', Key::KEY_3 },
- { '4', Key::KEY_4 },
- { '5', Key::KEY_5 },
- { '6', Key::KEY_6 },
- { '7', Key::KEY_7 },
- { '8', Key::KEY_8 },
- { '9', Key::KEY_9 },
- { '-', Key::MINUS },
- { '_', Key::UNDERSCORE },
- { '=', Key::EQUAL },
- { '+', Key::PLUS },
- { 'q', Key::Q },
- { 'w', Key::W },
- { 'e', Key::E },
- { 'r', Key::R },
- { 't', Key::T },
- { 'y', Key::Y },
- { 'u', Key::U },
- { 'i', Key::I },
- { 'o', Key::O },
- { 'p', Key::P },
- { '[', Key::BRACELEFT },
- { ']', Key::BRACERIGHT },
- { '{', Key::BRACELEFT },
- { '}', Key::BRACERIGHT },
- { 'a', Key::A },
- { 's', Key::S },
- { 'd', Key::D },
- { 'f', Key::F },
- { 'g', Key::G },
- { 'h', Key::H },
- { 'j', Key::J },
- { 'k', Key::K },
- { 'l', Key::L },
- { ';', Key::SEMICOLON },
- { ':', Key::COLON },
- { '\'', Key::APOSTROPHE },
- { '\"', Key::QUOTEDBL },
- { '\\', Key::BACKSLASH },
- { '#', Key::NUMBERSIGN },
- { 'z', Key::Z },
- { 'x', Key::X },
- { 'c', Key::C },
- { 'v', Key::V },
- { 'b', Key::B },
- { 'n', Key::N },
- { 'm', Key::M },
- { ',', Key::COMMA },
- { '.', Key::PERIOD },
- { '/', Key::SLASH }
-};
-
-static Key remapKey(unsigned int key, unsigned int state) {
- if (isNumpadKey(key)) {
- return translateKey(key);
- }
-
- TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
- if (!currentKeyboard) {
- return translateKey(key);
- }
-
- CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
- if (!layoutData) {
- return translateKey(key);
- }
-
- const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
-
- UInt32 keysDown = 0;
- UniChar chars[4];
- UniCharCount realLength;
-
- OSStatus err = UCKeyTranslate(keyboardLayout,
- key,
- kUCKeyActionDisplay,
- (state >> 8) & 0xFF,
- LMGetKbdType(),
- kUCKeyTranslateNoDeadKeysBit,
- &keysDown,
- sizeof(chars) / sizeof(chars[0]),
- &realLength,
- chars);
-
- if (err != noErr) {
- return translateKey(key);
- }
-
- for (unsigned int i = 0; i < 55; i++) {
- if (_keycodes[i].kchar == chars[0]) {
- return _keycodes[i].kcode;
- }
- }
- return translateKey(key);
-}
-
-- (void)keyDown:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- ignore_momentum_scroll = true;
-
- // Ignore all input if IME input is in progress
- if (!imeInputEventInProgress) {
- NSString *characters = [event characters];
- NSUInteger length = [characters length];
-
- if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
- // Fallback unicode character handler used if IME is not active
- Char16String text;
- text.resize([characters length] + 1);
- [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
-
- String u32text;
- u32text.parse_utf16(text.ptr(), text.length());
-
- for (int i = 0; i < u32text.length(); i++) {
- const char32_t codepoint = u32text[i];
-
- DisplayServerOSX::KeyEvent ke;
-
- ke.window_id = window_id;
- ke.osx_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = [event isARepeat];
- ke.keycode = remapKey([event keyCode], [event modifierFlags]);
- ke.physical_keycode = translateKey([event keyCode]);
- ke.raw = true;
- ke.unicode = codepoint;
-
- _push_to_key_event_buffer(ke);
- }
- } else {
- DisplayServerOSX::KeyEvent ke;
-
- ke.window_id = window_id;
- ke.osx_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = [event isARepeat];
- ke.keycode = remapKey([event keyCode], [event modifierFlags]);
- ke.physical_keycode = translateKey([event keyCode]);
- ke.raw = false;
- ke.unicode = 0;
-
- _push_to_key_event_buffer(ke);
- }
- }
-
- // Pass events to IME handler
- if (wd.im_active) {
- [self interpretKeyEvents:[NSArray arrayWithObject:event]];
+void DisplayServerOSX::push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) {
+ if (key_event_pos >= key_event_buffer.size()) {
+ key_event_buffer.resize(1 + key_event_pos);
}
+ key_event_buffer.write[key_event_pos++] = p_event;
}
-- (void)flagsChanged:(NSEvent *)event {
- ignore_momentum_scroll = true;
-
- // Ignore all input if IME input is in progress
- if (!imeInputEventInProgress) {
- DisplayServerOSX::KeyEvent ke;
-
- ke.window_id = window_id;
- ke.echo = false;
- ke.raw = true;
-
- int key = [event keyCode];
- int mod = [event modifierFlags];
-
- if (key == 0x36 || key == 0x37) {
- if (mod & NSEventModifierFlagCommand) {
- mod &= ~NSEventModifierFlagCommand;
- ke.pressed = true;
- } else {
- ke.pressed = false;
- }
- } else if (key == 0x38 || key == 0x3c) {
- if (mod & NSEventModifierFlagShift) {
- mod &= ~NSEventModifierFlagShift;
- ke.pressed = true;
- } else {
- ke.pressed = false;
- }
- } else if (key == 0x3a || key == 0x3d) {
- if (mod & NSEventModifierFlagOption) {
- mod &= ~NSEventModifierFlagOption;
- ke.pressed = true;
- } else {
- ke.pressed = false;
- }
- } else if (key == 0x3b || key == 0x3e) {
- if (mod & NSEventModifierFlagControl) {
- mod &= ~NSEventModifierFlagControl;
- ke.pressed = true;
- } else {
- ke.pressed = false;
- }
- } else {
- return;
- }
-
- ke.osx_state = mod;
- ke.keycode = remapKey(key, mod);
- ke.physical_keycode = translateKey(key);
- ke.unicode = 0;
+void DisplayServerOSX::update_im_text(const Point2i &p_selection, const String &p_text) {
+ im_selection = p_selection;
+ im_text = p_text;
- _push_to_key_event_buffer(ke);
- }
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
}
-- (void)keyUp:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- // Ignore all input if IME input is in progress
- if (!imeInputEventInProgress) {
- NSString *characters = [event characters];
- NSUInteger length = [characters length];
-
- // Fallback unicode character handler used if IME is not active
- if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
- Char16String text;
- text.resize([characters length] + 1);
- [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
-
- String u32text;
- u32text.parse_utf16(text.ptr(), text.length());
-
- for (int i = 0; i < u32text.length(); i++) {
- const char32_t codepoint = u32text[i];
- DisplayServerOSX::KeyEvent ke;
-
- ke.window_id = window_id;
- ke.osx_state = [event modifierFlags];
- ke.pressed = false;
- ke.echo = [event isARepeat];
- ke.keycode = remapKey([event keyCode], [event modifierFlags]);
- ke.physical_keycode = translateKey([event keyCode]);
- ke.raw = true;
- ke.unicode = codepoint;
-
- _push_to_key_event_buffer(ke);
- }
- } else {
- DisplayServerOSX::KeyEvent ke;
-
- ke.window_id = window_id;
- ke.osx_state = [event modifierFlags];
- ke.pressed = false;
- ke.echo = [event isARepeat];
- ke.keycode = remapKey([event keyCode], [event modifierFlags]);
- ke.physical_keycode = translateKey([event keyCode]);
- ke.raw = true;
- ke.unicode = 0;
-
- _push_to_key_event_buffer(ke);
- }
- }
+void DisplayServerOSX::set_last_focused_window(WindowID p_window) {
+ last_focused_window = p_window;
}
-inline void sendScrollEvent(DisplayServer::WindowID window_id, MouseButton button, double factor, int modifierFlags) {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- MouseButton mask = mouse_button_to_mask(button);
-
- Ref<InputEventMouseButton> sc;
- sc.instantiate();
-
- sc->set_window_id(window_id);
- _get_key_modifier_state(modifierFlags, sc);
- sc->set_button_index(button);
- sc->set_factor(factor);
- sc->set_pressed(true);
- sc->set_position(wd.mouse_pos);
- sc->set_global_position(wd.mouse_pos);
- DS_OSX->last_button_state |= (MouseButton)mask;
- sc->set_button_mask(DS_OSX->last_button_state);
-
- Input::get_singleton()->parse_input_event(sc);
-
- sc.instantiate();
- sc->set_window_id(window_id);
- sc->set_button_index(button);
- sc->set_factor(factor);
- sc->set_pressed(false);
- sc->set_position(wd.mouse_pos);
- sc->set_global_position(wd.mouse_pos);
- DS_OSX->last_button_state &= (MouseButton)~mask;
- sc->set_button_mask(DS_OSX->last_button_state);
-
- Input::get_singleton()->parse_input_event(sc);
+void DisplayServerOSX::set_is_resizing(bool p_is_resizing) {
+ is_resizing = p_is_resizing;
}
-inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy, int modifierFlags) {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- Ref<InputEventPanGesture> pg;
- pg.instantiate();
-
- pg->set_window_id(window_id);
- _get_key_modifier_state(modifierFlags, pg);
- pg->set_position(wd.mouse_pos);
- pg->set_delta(Vector2(-dx, -dy));
-
- Input::get_singleton()->parse_input_event(pg);
+bool DisplayServerOSX::get_is_resizing() const {
+ return is_resizing;
}
-- (void)scrollWheel:(NSEvent *)event {
- ERR_FAIL_COND(!DS_OSX->windows.has(window_id));
- DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id];
-
- double deltaX, deltaY;
-
- _get_mouse_pos(wd, [event locationInWindow]);
-
- deltaX = [event scrollingDeltaX];
- deltaY = [event scrollingDeltaY];
-
- if ([event hasPreciseScrollingDeltas]) {
- deltaX *= 0.03;
- deltaY *= 0.03;
+void DisplayServerOSX::window_update(WindowID p_window) {
+#if defined(GLES3_ENABLED)
+ if (gl_manager) {
+ gl_manager->window_update(p_window);
}
+#endif
+}
- if ([event momentumPhase] != NSEventPhaseNone) {
- if (ignore_momentum_scroll) {
- return;
- }
- } else {
- ignore_momentum_scroll = false;
+void DisplayServerOSX::window_destroy(WindowID p_window) {
+#if defined(GLES3_ENABLED)
+ if (gl_manager) {
+ gl_manager->window_destroy(p_window);
}
-
- if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) {
- sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]);
- } else {
- if (fabs(deltaX)) {
- sendScrollEvent(window_id, 0 > deltaX ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]);
- }
- if (fabs(deltaY)) {
- sendScrollEvent(window_id, 0 < deltaY ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]);
- }
+#endif
+#ifdef VULKAN_ENABLED
+ if (context_vulkan) {
+ context_vulkan->window_destroy(p_window);
}
+#endif
+ windows.erase(p_window);
}
-@end
-
-/*************************************************************************/
-/* GodotWindow */
-/*************************************************************************/
-
-@interface GodotWindow : NSWindow {
-}
-
-@end
-
-@implementation GodotWindow
-
-- (BOOL)canBecomeKeyWindow {
- // Required for NSBorderlessWindowMask windows
- for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) {
- if (E->get().window_object == self) {
- if (E->get().no_focus) {
- return NO;
- }
- }
+void DisplayServerOSX::window_resize(WindowID p_window, int p_width, int p_height) {
+#if defined(GLES3_ENABLED)
+ if (gl_manager) {
+ gl_manager->window_resize(p_window, p_width, p_height);
}
- return YES;
-}
-
-- (BOOL)canBecomeMainWindow {
- // Required for NSBorderlessWindowMask windows
- for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) {
- if (E->get().window_object == self) {
- if (E->get().no_focus) {
- return NO;
- }
- }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (context_vulkan) {
+ context_vulkan->window_resize(p_window, p_width, p_height);
}
- return YES;
+#endif
}
-@end
-
-/*************************************************************************/
-/* DisplayServerOSX */
-/*************************************************************************/
-
bool DisplayServerOSX::has_feature(Feature p_feature) const {
switch (p_feature) {
case FEATURE_GLOBAL_MENU:
@@ -1480,7 +653,6 @@ bool DisplayServerOSX::has_feature(Feature p_feature) const {
case FEATURE_CURSOR_SHAPE:
case FEATURE_CUSTOM_CURSOR_SHAPE:
case FEATURE_NATIVE_DIALOG:
- //case FEATURE_CONSOLE_WINDOW:
case FEATURE_IME:
case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_HIDPI:
@@ -1499,57 +671,13 @@ String DisplayServerOSX::get_name() const {
return "OSX";
}
-const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const {
- const NSMenu *menu = nullptr;
- if (p_menu_root == "") {
- // Main menu.x
- menu = [NSApp mainMenu];
- } else if (p_menu_root.to_lower() == "_dock") {
- // macOS dock menu.
- menu = dock_menu;
- } else {
- // Submenu.
- if (submenu.has(p_menu_root)) {
- menu = submenu[p_menu_root];
- }
- }
- if (menu == apple_menu) {
- // Do not allow to change Apple menu.
- return nullptr;
- }
- return menu;
-}
-
-NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) {
- NSMenu *menu = nullptr;
- if (p_menu_root == "") {
- // Main menu.
- menu = [NSApp mainMenu];
- } else if (p_menu_root.to_lower() == "_dock") {
- // macOS dock menu.
- menu = dock_menu;
- } else {
- // Submenu.
- if (!submenu.has(p_menu_root)) {
- NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
- submenu[p_menu_root] = n_menu;
- }
- menu = submenu[p_menu_root];
- }
- if (menu == apple_menu) {
- // Do not allow to change Apple menu.
- return nullptr;
- }
- return menu;
-}
-
void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
- GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease];
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
obj->checkable = false;
@@ -1563,7 +691,7 @@ void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, con
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
- GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease];
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
obj->checkable = true;
@@ -1619,7 +747,7 @@ bool DisplayServerOSX::global_menu_is_item_checkable(const String &p_menu_root,
if (menu) {
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- GlobalMenuItem *obj = [menu_item representedObject];
+ GodotMenuItem *obj = [menu_item representedObject];
if (obj) {
return obj->checkable;
}
@@ -1635,7 +763,7 @@ Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_ro
if (menu) {
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- GlobalMenuItem *obj = [menu_item representedObject];
+ GodotMenuItem *obj = [menu_item representedObject];
if (obj) {
return obj->callback;
}
@@ -1651,7 +779,7 @@ Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, in
if (menu) {
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- GlobalMenuItem *obj = [menu_item representedObject];
+ GodotMenuItem *obj = [menu_item representedObject];
if (obj) {
return obj->meta;
}
@@ -1667,10 +795,8 @@ String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, in
if (menu) {
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- char *utfs = strdup([[menu_item title] UTF8String]);
String ret;
- ret.parse_utf8(utfs);
- free(utfs);
+ ret.parse_utf8([[menu_item title] UTF8String]);
return ret;
}
}
@@ -1687,8 +813,9 @@ String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root,
const NSMenu *sub_menu = [menu_item submenu];
if (sub_menu) {
for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) {
- if (E->get() == sub_menu)
+ if (E->get() == sub_menu) {
return E->key();
+ }
}
}
}
@@ -1725,7 +852,7 @@ void DisplayServerOSX::global_menu_set_item_checkable(const String &p_menu_root,
}
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- GlobalMenuItem *obj = [menu_item representedObject];
+ GodotMenuItem *obj = [menu_item representedObject];
obj->checkable = p_checkable;
}
}
@@ -1741,7 +868,7 @@ void DisplayServerOSX::global_menu_set_item_callback(const String &p_menu_root,
}
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- GlobalMenuItem *obj = [menu_item representedObject];
+ GodotMenuItem *obj = [menu_item representedObject];
obj->callback = p_callback;
}
}
@@ -1757,7 +884,7 @@ void DisplayServerOSX::global_menu_set_item_tag(const String &p_menu_root, int p
}
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- GlobalMenuItem *obj = [menu_item representedObject];
+ GodotMenuItem *obj = [menu_item representedObject];
obj->meta = p_tag;
}
}
@@ -1874,7 +1001,6 @@ Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector
p_callback.call((const Variant **)&buttonp, 1, ret, ce);
}
- [window release];
return OK;
}
@@ -1896,10 +1022,8 @@ Error DisplayServerOSX::dialog_input_text(String p_title, String p_description,
[window runModal];
- char *utfs = strdup([[input stringValue] UTF8String]);
String ret;
- ret.parse_utf8(utfs);
- free(utfs);
+ ret.parse_utf8([[input stringValue] UTF8String]);
if (!p_callback.is_null()) {
Variant text = ret;
@@ -1909,7 +1033,6 @@ Error DisplayServerOSX::dialog_input_text(String p_title, String p_description,
p_callback.call((const Variant **)&textp, 1, ret, ce);
}
- [window release];
return OK;
}
@@ -1920,7 +1043,8 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) {
return;
}
- WindowData &wd = windows[MAIN_WINDOW_ID];
+ WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID;
+ WindowData &wd = windows[window_id];
if (p_mode == MOUSE_MODE_CAPTURED) {
// Apple Docs state that the display parameter is not used.
// "This parameter is not used. By default, you may pass kCGDirectMainDisplay."
@@ -1963,9 +1087,7 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) {
mouse_mode = p_mode;
if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {
- CursorShape p_shape = cursor_shape;
- cursor_shape = DisplayServer::CURSOR_MAX;
- cursor_set_shape(p_shape);
+ cursor_update_shape();
}
}
@@ -1973,24 +1095,82 @@ DisplayServer::MouseMode DisplayServerOSX::mouse_get_mode() const {
return mouse_mode;
}
+bool DisplayServerOSX::update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp) {
+ _THREAD_SAFE_METHOD_
+
+ if (ignore_warp) {
+ // Discard late events, before warp.
+ if (p_timestamp < last_warp) {
+ return true;
+ }
+ ignore_warp = false;
+ return true;
+ }
+
+ if (mouse_mode == DisplayServer::MOUSE_MODE_CONFINED || mouse_mode == DisplayServer::MOUSE_MODE_CONFINED_HIDDEN) {
+ // Discard late events.
+ if (p_timestamp < last_warp) {
+ return true;
+ }
+
+ // Warp affects next event delta, subtract previous warp deltas.
+ List<WarpEvent>::Element *F = warp_events.front();
+ while (F) {
+ if (F->get().timestamp < p_timestamp) {
+ List<DisplayServerOSX::WarpEvent>::Element *E = F;
+ r_delta.x -= E->get().delta.x;
+ r_delta.y -= E->get().delta.y;
+ F = F->next();
+ warp_events.erase(E);
+ } else {
+ F = F->next();
+ }
+ }
+
+ // Confine mouse position to the window, and update delta.
+ NSRect frame = [p_wd.window_object frame];
+ NSPoint conf_pos = r_mpos;
+ conf_pos.x = CLAMP(conf_pos.x + r_delta.x, 0.f, frame.size.width);
+ conf_pos.y = CLAMP(conf_pos.y - r_delta.y, 0.f, frame.size.height);
+ r_delta.x = conf_pos.x - r_mpos.x;
+ r_delta.y = r_mpos.y - conf_pos.y;
+ r_mpos = conf_pos;
+
+ // Move mouse cursor.
+ NSRect point_in_window_rect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0);
+ conf_pos = [[p_wd.window_view window] convertRectToScreen:point_in_window_rect].origin;
+ conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y;
+ CGWarpMouseCursorPosition(conf_pos);
+
+ // Save warp data.
+ last_warp = [[NSProcessInfo processInfo] systemUptime];
+
+ DisplayServerOSX::WarpEvent ev;
+ ev.timestamp = last_warp;
+ ev.delta = r_delta;
+ warp_events.push_back(ev);
+ }
+
+ return false;
+}
+
void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) {
_THREAD_SAFE_METHOD_
- if (mouse_mode == MOUSE_MODE_CAPTURED) {
- last_mouse_pos = p_to;
- } else {
- WindowData &wd = windows[MAIN_WINDOW_ID];
+ if (mouse_mode != MOUSE_MODE_CAPTURED) {
+ WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID;
+ WindowData &wd = windows[window_id];
- //local point in window coords
+ // Local point in window coords.
const NSRect contentRect = [wd.window_view frame];
const float scale = screen_get_max_scale();
NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0);
NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin;
- //point in scren coords
+ // Point in scren coords.
CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y };
- //do the warping
+ // Do the warping.
CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0);
CGAssociateMouseAndMouseCursorPosition(false);
@@ -2002,10 +1182,6 @@ void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) {
}
Point2i DisplayServerOSX::mouse_get_position() const {
- return last_mouse_pos;
-}
-
-Point2i DisplayServerOSX::mouse_get_absolute_position() const {
_THREAD_SAFE_METHOD_
const NSPoint mouse_pos = [NSEvent mouseLocation];
@@ -2020,6 +1196,10 @@ Point2i DisplayServerOSX::mouse_get_absolute_position() const {
return Vector2i();
}
+void DisplayServerOSX::mouse_set_button_state(MouseButton p_state) {
+ last_button_state = p_state;
+}
+
MouseButton DisplayServerOSX::mouse_get_button_state() const {
return last_button_state;
}
@@ -2051,11 +1231,8 @@ String DisplayServerOSX::clipboard_get() const {
NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
NSString *string = [objectsToPaste objectAtIndex:0];
- char *utfs = strdup([string UTF8String]);
String ret;
- ret.parse_utf8(utfs);
- free(utfs);
-
+ ret.parse_utf8([string UTF8String]);
return ret;
}
@@ -2066,50 +1243,6 @@ int DisplayServerOSX::get_screen_count() const {
return [screenArray count];
}
-// Returns the native top-left screen coordinate of the smallest rectangle
-// that encompasses all screens. Needed in get_screen_position(),
-// window_get_position, and window_set_position()
-// to convert between OS X native screen coordinates and the ones expected by Godot
-
-static bool displays_arrangement_dirty = true;
-static bool displays_scale_dirty = true;
-static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) {
- displays_arrangement_dirty = true;
- displays_scale_dirty = true;
-}
-
-Point2i DisplayServerOSX::_get_screens_origin() const {
- static Point2i origin;
-
- if (displays_arrangement_dirty) {
- origin = Point2i();
-
- for (int i = 0; i < get_screen_count(); i++) {
- Point2i position = _get_native_screen_position(i);
- if (position.x < origin.x) {
- origin.x = position.x;
- }
- if (position.y > origin.y) {
- origin.y = position.y;
- }
- }
- displays_arrangement_dirty = false;
- }
-
- return origin;
-}
-
-Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const {
- NSArray *screenArray = [NSScreen screens];
- if ((NSUInteger)p_screen < [screenArray count]) {
- NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
- // Return the top-left corner of the screen, for OS X the y starts at the bottom
- return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale();
- }
-
- return Point2i();
-}
-
Point2i DisplayServerOSX::screen_get_position(int p_screen) const {
_THREAD_SAFE_METHOD_
@@ -2119,7 +1252,7 @@ Point2i DisplayServerOSX::screen_get_position(int p_screen) const {
Point2i position = _get_native_screen_position(p_screen) - _get_screens_origin();
// OS X native y-coordinate relative to _get_screens_origin() is negative,
- // Godot expects a positive value
+ // Godot expects a positive value.
position.y *= -1;
return position;
}
@@ -2133,7 +1266,7 @@ Size2i DisplayServerOSX::screen_get_size(int p_screen) const {
NSArray *screenArray = [NSScreen screens];
if ((NSUInteger)p_screen < [screenArray count]) {
- // Note: Use frame to get the whole screen size
+ // Note: Use frame to get the whole screen size.
NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame];
return Size2i(nsrect.size.width, nsrect.size.height) * screen_get_max_scale();
}
@@ -2186,15 +1319,8 @@ float DisplayServerOSX::screen_get_scale(int p_screen) const {
float DisplayServerOSX::screen_get_max_scale() const {
_THREAD_SAFE_METHOD_
- static float scale = 1.f;
- if (displays_scale_dirty) {
- int screen_count = get_screen_count();
- for (int i = 0; i < screen_count; i++) {
- scale = fmax(scale, screen_get_scale(i));
- }
- displays_scale_dirty = false;
- }
- return scale;
+ // Note: Do not update max display scale on screen configuration change, existing editor windows can't be rescaled on the fly.
+ return display_max_scale;
}
Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const {
@@ -2219,6 +1345,24 @@ Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const {
return Rect2i();
}
+float DisplayServerOSX::screen_get_refresh_rate(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ NSArray *screenArray = [NSScreen screens];
+ if ((NSUInteger)p_screen < [screenArray count]) {
+ NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription];
+ const CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);
+ const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode);
+ return (float)displayRefreshRate;
+ }
+ ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+}
+
Vector<DisplayServer::WindowID> DisplayServerOSX::get_window_list() const {
_THREAD_SAFE_METHOD_
@@ -2245,63 +1389,14 @@ DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, V
void DisplayServerOSX::show_window(WindowID p_id) {
WindowData &wd = windows[p_id];
- if (wd.no_focus) {
+ popup_open(p_id);
+ if (wd.no_focus || wd.is_popup) {
[wd.window_object orderFront:nil];
} else {
[wd.window_object makeKeyAndOrderFront:nil];
}
}
-void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) {
- _THREAD_SAFE_METHOD_
-
- if (!wd.event_callback.is_null()) {
- Variant event = int(p_event);
- Variant *eventp = &event;
- Variant ret;
- Callable::CallError ce;
- wd.event_callback.call((const Variant **)&eventp, 1, ret, ce);
- }
-}
-
-DisplayServerOSX::WindowID DisplayServerOSX::_find_window_id(id p_window) {
- for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
- if (E->get().window_object == p_window) {
- return E->key();
- }
- }
- return INVALID_WINDOW_ID;
-}
-
-void DisplayServerOSX::_update_window(WindowData p_wd) {
- bool borderless_full = false;
-
- if (p_wd.borderless) {
- NSRect frameRect = [p_wd.window_object frame];
- NSRect screenRect = [[p_wd.window_object screen] frame];
-
- // Check if our window covers up the screen
- if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y &&
- frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) {
- borderless_full = true;
- }
- }
-
- if (borderless_full) {
- // If the window covers up the screen set the level to above the main menu and hide on deactivate
- [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1];
- [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 && !p_wd.fullscreen) {
- [p_wd.window_object setLevel:NSFloatingWindowLevel];
- } else {
- [p_wd.window_object setLevel:NSNormalWindowLevel];
- }
- [p_wd.window_object setHidesOnDeactivate:NO];
- }
-}
-
void DisplayServerOSX::delete_sub_window(WindowID p_id) {
_THREAD_SAFE_METHOD_
@@ -2314,24 +1409,6 @@ void DisplayServerOSX::delete_sub_window(WindowID p_id) {
[wd.window_object close];
}
-void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) {
- _THREAD_SAFE_METHOD_
-
- ERR_FAIL_COND(!windows.has(p_window));
- WindowData &wd = windows[p_window];
-
- [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
-}
-
-void DisplayServerOSX::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
- _THREAD_SAFE_METHOD_
-
- ERR_FAIL_COND(!windows.has(p_window));
- WindowData &wd = windows[p_window];
-
- wd.mpath = p_region;
-}
-
void DisplayServerOSX::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -2370,6 +1447,24 @@ void DisplayServerOSX::window_set_drop_files_callback(const Callable &p_callable
wd.drop_files_callback = p_callable;
}
+void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
+}
+
+void DisplayServerOSX::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ wd.mpath = p_region;
+}
+
int DisplayServerOSX::window_get_current_screen(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), -1);
@@ -2381,40 +1476,41 @@ int DisplayServerOSX::window_get_current_screen(WindowID p_window) const {
void DisplayServerOSX::window_set_current_screen(int p_screen, WindowID p_window) {
_THREAD_SAFE_METHOD_
- Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
- window_set_position(wpos + screen_get_position(p_screen), p_window);
-}
-
-void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) {
- _THREAD_SAFE_METHOD_
- ERR_FAIL_COND(p_window == p_parent);
ERR_FAIL_COND(!windows.has(p_window));
- WindowData &wd_window = windows[p_window];
-
- ERR_FAIL_COND(wd_window.transient_parent == p_parent);
-
- ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");
- if (p_parent == INVALID_WINDOW_ID) {
- //remove transient
- ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID);
- ERR_FAIL_COND(!windows.has(wd_window.transient_parent));
-
- WindowData &wd_parent = windows[wd_window.transient_parent];
+ WindowData &wd = windows[p_window];
- wd_window.transient_parent = INVALID_WINDOW_ID;
- wd_parent.transient_children.erase(p_window);
+ bool was_fullscreen = false;
+ if (wd.fullscreen) {
+ // Temporary exit fullscreen mode to move window.
+ [wd.window_object toggleFullScreen:nil];
+ was_fullscreen = true;
+ }
- [wd_parent.window_object removeChildWindow:wd_window.window_object];
- } else {
- ERR_FAIL_COND(!windows.has(p_parent));
- ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
- WindowData &wd_parent = windows[p_parent];
+ Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
+ window_set_position(wpos + screen_get_position(p_screen), p_window);
- wd_window.transient_parent = p_parent;
- wd_parent.transient_children.insert(p_window);
+ if (was_fullscreen) {
+ // Re-enter fullscreen mode.
+ [wd.window_object toggleFullScreen:nil];
+ }
+}
- [wd_parent.window_object addChildWindow:wd_window.window_object ordered:NSWindowAbove];
+void DisplayServerOSX::window_set_exclusive(WindowID p_window, bool p_exclusive) {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ if (wd.exclusive != p_exclusive) {
+ wd.exclusive = p_exclusive;
+ if (wd.transient_parent != INVALID_WINDOW_ID) {
+ WindowData &wd_parent = windows[wd.transient_parent];
+ if (wd.exclusive) {
+ ERR_FAIL_COND_MSG([[wd_parent.window_object childWindows] count] > 0, "Transient parent has another exclusive child.");
+ [wd_parent.window_object addChildWindow:wd.window_object ordered:NSWindowAbove];
+ } else {
+ [wd_parent.window_object removeChildWindow:wd.window_object];
+ }
+ }
}
}
@@ -2424,17 +1520,19 @@ Point2i DisplayServerOSX::window_get_position(WindowID p_window) const {
ERR_FAIL_COND_V(!windows.has(p_window), Point2i());
const WindowData &wd = windows[p_window];
- NSRect nsrect = [wd.window_object frame];
+ // Use content rect position (without titlebar / window border).
+ const NSRect contentRect = [wd.window_view frame];
+ const NSRect nsrect = [wd.window_object convertRectToScreen:contentRect];
Point2i pos;
- // Return the position of the top-left corner, for OS X the y starts at the bottom
+ // Return the position of the top-left corner, for OS X the y starts at the bottom.
const float scale = screen_get_max_scale();
pos.x = nsrect.origin.x;
pos.y = (nsrect.origin.y + nsrect.size.height);
pos *= scale;
pos -= _get_screens_origin();
// OS X native y-coordinate relative to _get_screens_origin() is negative,
- // Godot expects a positive value
+ // Godot expects a positive value.
pos.y *= -1;
return pos;
}
@@ -2447,15 +1545,63 @@ void DisplayServerOSX::window_set_position(const Point2i &p_position, WindowID p
Point2i position = p_position;
// OS X native y-coordinate relative to _get_screens_origin() is negative,
- // Godot passes a positive value
+ // Godot passes a positive value.
position.y *= -1;
position += _get_screens_origin();
position /= screen_get_max_scale();
- [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x, position.y)];
+ // Remove titlebar / window border size.
+ const NSRect contentRect = [wd.window_view frame];
+ const NSRect windowRect = [wd.window_object frame];
+ const NSRect nsrect = [wd.window_object convertRectToScreen:contentRect];
+ Point2i offset;
+ offset.x = (nsrect.origin.x - windowRect.origin.x);
+ offset.y = (nsrect.origin.y + nsrect.size.height);
+ offset.y -= (windowRect.origin.y + windowRect.size.height);
+
+ [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x - offset.x, position.y - offset.y)];
+
+ _update_window_style(wd);
+ update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+}
+
+void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND(p_window == p_parent);
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd_window = windows[p_window];
+
+ ERR_FAIL_COND(wd_window.transient_parent == p_parent);
+
+ ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");
+ if (p_parent == INVALID_WINDOW_ID) {
+ // Remove transient.
+ ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID);
+ ERR_FAIL_COND(!windows.has(wd_window.transient_parent));
+
+ WindowData &wd_parent = windows[wd_window.transient_parent];
- _update_window(wd);
- _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+ wd_window.transient_parent = INVALID_WINDOW_ID;
+ wd_parent.transient_children.erase(p_window);
+ [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+
+ if (wd_window.exclusive) {
+ [wd_parent.window_object removeChildWindow:wd_window.window_object];
+ }
+ } else {
+ ERR_FAIL_COND(!windows.has(p_parent));
+ ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
+ WindowData &wd_parent = windows[p_parent];
+
+ wd_window.transient_parent = p_parent;
+ wd_parent.transient_children.insert(p_window);
+ [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
+
+ if (wd_window.exclusive) {
+ [wd_parent.window_object addChildWindow:wd_window.window_object ordered:NSWindowAbove];
+ }
+ }
}
void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_window) {
@@ -2536,7 +1682,7 @@ void DisplayServerOSX::window_set_size(const Size2i p_size, WindowID p_window) {
[wd.window_object setFrame:new_frame display:YES];
- _update_window(wd);
+ _update_window_style(wd);
}
Size2i DisplayServerOSX::window_get_size(WindowID p_window) const {
@@ -2556,73 +1702,6 @@ Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const {
return Size2i(frame.size.width, frame.size.height) * screen_get_max_scale();
}
-bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const {
- return true;
-}
-
-void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) {
- ERR_FAIL_COND(!windows.has(p_window));
- WindowData &wd = windows[p_window];
-
- if (!OS_OSX::get_singleton()->is_layered_allowed()) {
- return;
- }
- if (wd.layered_window != p_enabled) {
- if (p_enabled) {
- [wd.window_object setBackgroundColor:[NSColor clearColor]];
- [wd.window_object setOpaque:NO];
- [wd.window_object setHasShadow:NO];
- CALayer *layer = [wd.window_view layer];
- if (layer) {
- [layer setOpaque:NO];
- }
-#if defined(VULKAN_ENABLED)
- if (context_vulkan) {
- //TODO - implement transparency for Vulkan
- }
-#endif
-#if defined(GLES3_ENABLED)
- if (gl_manager) {
- //TODO - reimplement OpenGLES
- }
-#endif
- wd.layered_window = true;
- } else {
- [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]];
- [wd.window_object setOpaque:YES];
- [wd.window_object setHasShadow:YES];
- CALayer *layer = [wd.window_view layer];
- if (layer) {
- [layer setOpaque:YES];
- }
-#if defined(VULKAN_ENABLED)
- if (context_vulkan) {
- //TODO - implement transparency for Vulkan
- }
-#endif
-#if defined(GLES3_ENABLED)
- if (gl_manager) {
- //TODO - reimplement OpenGLES
- }
-#endif
- wd.layered_window = false;
- }
-#if defined(GLES3_ENABLED)
- if (gl_manager) {
- //TODO - reimplement OpenGLES
- }
-#endif
-#if defined(VULKAN_ENABLED)
- if (context_vulkan) {
- //TODO - implement transparency for Vulkan
- }
-#endif
- NSRect frameRect = [wd.window_object frame];
- [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES];
- [wd.window_object setFrame:frameRect display:YES];
- }
-}
-
void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -2631,22 +1710,21 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) {
WindowMode old_mode = window_get_mode(p_window);
if (old_mode == p_mode) {
- return; // do nothing
+ return; // Do nothing.
}
switch (old_mode) {
case WINDOW_MODE_WINDOWED: {
- //do nothing
+ // Do nothing.
} break;
case WINDOW_MODE_MINIMIZED: {
[wd.window_object deminiaturize:nil];
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
[wd.window_object setLevel:NSNormalWindowLevel];
- if (wd.layered_window) {
- _set_window_per_pixel_transparency_enabled(true, p_window);
- }
- if (wd.resize_disabled) { //restore resize disabled
+ _set_window_per_pixel_transparency_enabled(true, p_window);
+ if (wd.resize_disabled) { // Restore resize disabled.
[wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
}
if (wd.min_size != Size2i()) {
@@ -2669,16 +1747,17 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) {
switch (p_mode) {
case WINDOW_MODE_WINDOWED: {
- //do nothing
+ // Do nothing.
} break;
case WINDOW_MODE_MINIMIZED: {
[wd.window_object performMiniaturize:nil];
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
- if (wd.layered_window)
- _set_window_per_pixel_transparency_enabled(false, p_window);
- if (wd.resize_disabled) //fullscreen window should be resizable to work
+ _set_window_per_pixel_transparency_enabled(false, p_window);
+ if (wd.resize_disabled) { // Fullscreen window should be resizable to work.
[wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable];
+ }
[wd.window_object setContentMinSize:NSMakeSize(0, 0)];
[wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
[wd.window_object toggleFullScreen:nil];
@@ -2698,7 +1777,7 @@ DisplayServer::WindowMode DisplayServerOSX::window_get_mode(WindowID p_window) c
ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);
const WindowData &wd = windows[p_window];
- if (wd.fullscreen) { //if fullscreen, it's not in another mode
+ if (wd.fullscreen) { // If fullscreen, it's not in another mode.
return WINDOW_MODE_FULLSCREEN;
}
if ([wd.window_object isZoomed] && !wd.resize_disabled) {
@@ -2710,10 +1789,14 @@ DisplayServer::WindowMode DisplayServerOSX::window_get_mode(WindowID p_window) c
}
}
- // all other discarded, return windowed.
+ // All other discarded, return windowed.
return WINDOW_MODE_WINDOWED;
}
+bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const {
+ return true;
+}
+
void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -2723,7 +1806,7 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
switch (p_flag) {
case WINDOW_FLAG_RESIZE_DISABLED: {
wd.resize_disabled = p_enabled;
- if (wd.fullscreen) { //fullscreen window should be resizable, style will be applied on exiting fs
+ if (wd.fullscreen) { // Fullscreen window should be resizable, style will be applied on exiting fullscreen.
return;
}
if (p_enabled) {
@@ -2733,7 +1816,7 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
}
} break;
case WINDOW_FLAG_BORDERLESS: {
- // OrderOut prevents a lose focus bug with the window
+ // OrderOut prevents a lose focus bug with the window.
if ([wd.window_object isVisible]) {
[wd.window_object orderOut:nil];
}
@@ -2741,17 +1824,16 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
if (p_enabled) {
[wd.window_object setStyleMask:NSWindowStyleMaskBorderless];
} else {
- if (wd.layered_window)
- _set_window_per_pixel_transparency_enabled(false, p_window);
+ _set_window_per_pixel_transparency_enabled(false, p_window);
[wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)];
- // Force update of the window styles
+ // Force update of the window styles.
NSRect frameRect = [wd.window_object frame];
[wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
[wd.window_object setFrame:frameRect display:NO];
}
- _update_window(wd);
+ _update_window_style(wd);
if ([wd.window_object isVisible]) {
- if (wd.no_focus) {
+ if (wd.no_focus || wd.is_popup) {
[wd.window_object orderFront:nil];
} else {
[wd.window_object makeKeyAndOrderFront:nil];
@@ -2770,9 +1852,8 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
}
} break;
case WINDOW_FLAG_TRANSPARENT: {
- wd.layered_window = p_enabled;
if (p_enabled) {
- [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // force borderless
+ [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // Force borderless.
} else if (!wd.borderless) {
[wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)];
}
@@ -2781,6 +1862,11 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
case WINDOW_FLAG_NO_FOCUS: {
wd.no_focus = p_enabled;
} break;
+ case WINDOW_FLAG_POPUP: {
+ ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
+ ERR_FAIL_COND_MSG([wd.window_object isVisible] && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");
+ wd.is_popup = p_enabled;
+ } break;
default: {
}
}
@@ -2812,6 +1898,9 @@ bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) co
case WINDOW_FLAG_NO_FOCUS: {
return wd.no_focus;
} break;
+ case WINDOW_FLAG_POPUP: {
+ return wd.is_popup;
+ } break;
default: {
}
}
@@ -2831,7 +1920,7 @@ void DisplayServerOSX::window_move_to_foreground(WindowID p_window) {
const WindowData &wd = windows[p_window];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
- if (wd.no_focus) {
+ if (wd.no_focus || wd.is_popup) {
[wd.window_object orderFront:nil];
} else {
[wd.window_object makeKeyAndOrderFront:nil];
@@ -2875,28 +1964,103 @@ void DisplayServerOSX::window_set_ime_position(const Point2i &p_pos, WindowID p_
wd.im_position = p_pos;
}
-bool DisplayServerOSX::get_swap_cancel_ok() {
- return false;
+DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const {
+ Point2i position = p_position;
+ position.y *= -1;
+ position += _get_screens_origin();
+ position /= screen_get_max_scale();
+
+ NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/];
+ for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+ if ([E->get().window_object windowNumber] == wnum) {
+ return E->key();
+ }
+ }
+ return INVALID_WINDOW_ID;
}
-void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) {
+int64_t DisplayServerOSX::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+ ERR_FAIL_COND_V(!windows.has(p_window), 0);
+ switch (p_handle_type) {
+ case DISPLAY_HANDLE: {
+ return 0; // Not supported.
+ }
+ case WINDOW_HANDLE: {
+ return (int64_t)windows[p_window].window_object;
+ }
+ case WINDOW_VIEW: {
+ return (int64_t)windows[p_window].window_view;
+ }
+ default: {
+ return 0;
+ }
+ }
+}
+
+void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
_THREAD_SAFE_METHOD_
- ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+ ERR_FAIL_COND(!windows.has(p_window));
+ windows[p_window].instance_id = p_instance;
+}
- if (cursor_shape == p_shape) {
- return;
+ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());
+ return windows[p_window].instance_id;
+}
+
+void DisplayServerOSX::gl_window_make_current(DisplayServer::WindowID p_window_id) {
+#if defined(GLES3_ENABLED)
+ gl_manager->window_make_current(p_window_id);
+#endif
+}
+
+void DisplayServerOSX::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+#if defined(GLES3_ENABLED)
+ if (gl_manager) {
+ gl_manager->set_use_vsync(p_vsync_mode);
+ }
+#endif
+#if defined(VULKAN_ENABLED)
+ if (context_vulkan) {
+ context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
}
+#endif
+}
- if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
- cursor_shape = p_shape;
- return;
+DisplayServer::VSyncMode DisplayServerOSX::window_get_vsync_mode(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+#if defined(GLES3_ENABLED)
+ if (gl_manager) {
+ return (gl_manager->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED);
}
+#endif
+#if defined(VULKAN_ENABLED)
+ if (context_vulkan) {
+ return context_vulkan->get_vsync_mode(p_window);
+ }
+#endif
+ return DisplayServer::VSYNC_ENABLED;
+}
+
+Point2i DisplayServerOSX::ime_get_selection() const {
+ return im_selection;
+}
- if (cursors[p_shape] != nullptr) {
- [cursors[p_shape] set];
+String DisplayServerOSX::ime_get_text() const {
+ return im_text;
+}
+
+void DisplayServerOSX::cursor_update_shape() {
+ _THREAD_SAFE_METHOD_
+
+ if (cursors[cursor_shape] != nullptr) {
+ [cursors[cursor_shape] set];
} else {
- switch (p_shape) {
+ switch (cursor_shape) {
case CURSOR_ARROW:
[[NSCursor arrowCursor] set];
break;
@@ -2925,16 +2089,16 @@ void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) {
[[NSCursor operationNotAllowedCursor] set];
break;
case CURSOR_VSIZE:
- [_cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set];
+ [_cursor_from_selector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set];
break;
case CURSOR_HSIZE:
- [_cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set];
+ [_cursor_from_selector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set];
break;
case CURSOR_BDIAGSIZE:
- [_cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set];
+ [_cursor_from_selector(@selector(_windowResizeNorthEastSouthWestCursor)) set];
break;
case CURSOR_FDIAGSIZE:
- [_cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set];
+ [_cursor_from_selector(@selector(_windowResizeNorthWestSouthEastCursor)) set];
break;
case CURSOR_MOVE:
[[NSCursor arrowCursor] set];
@@ -2946,14 +2110,30 @@ void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) {
[[NSCursor resizeLeftRightCursor] set];
break;
case CURSOR_HELP:
- [_cursorFromSelector(@selector(_helpCursor)) set];
+ [_cursor_from_selector(@selector(_helpCursor)) set];
break;
default: {
}
}
}
+}
+
+void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+ if (cursor_shape == p_shape) {
+ return;
+ }
cursor_shape = p_shape;
+
+ if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
+ return;
+ }
+
+ cursor_update_shape();
}
DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const {
@@ -3048,7 +2228,6 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape
NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)];
- [cursors[p_shape] release];
cursors[p_shape] = cursor;
Vector<Variant> params;
@@ -3061,94 +2240,32 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape
[cursor set];
}
}
-
- [imgrep release];
- [nsimage release];
} else {
- // Reset to default system cursor
+ // Reset to default system cursor.
if (cursors[p_shape] != nullptr) {
- [cursors[p_shape] release];
cursors[p_shape] = nullptr;
}
- CursorShape c = cursor_shape;
- cursor_shape = CURSOR_MAX;
- cursor_set_shape(c);
+ cursor_update_shape();
cursors_cache.erase(p_shape);
}
}
-struct LayoutInfo {
- String name;
- String code;
-};
-
-static Vector<LayoutInfo> kbd_layouts;
-static int current_layout = 0;
-static bool keyboard_layout_dirty = true;
-static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) {
- kbd_layouts.clear();
- current_layout = 0;
- keyboard_layout_dirty = true;
-}
-
-void _update_keyboard_layouts() {
- @autoreleasepool {
- TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource();
- NSString *cur_name = (NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName);
- CFRelease(cur_source);
-
- // Enum IME layouts
- NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode };
- NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false);
- for (NSUInteger i = 0; i < [list_ime count]; i++) {
- LayoutInfo ly;
- NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName);
- ly.name.parse_utf8([name UTF8String]);
-
- NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages);
- ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]);
- kbd_layouts.push_back(ly);
-
- if ([name isEqualToString:cur_name]) {
- current_layout = kbd_layouts.size() - 1;
- }
- }
- [list_ime release];
-
- // Enum plain keyboard layouts
- NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout };
- NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false);
- for (NSUInteger i = 0; i < [list_kbd count]; i++) {
- LayoutInfo ly;
- NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName);
- ly.name.parse_utf8([name UTF8String]);
-
- NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages);
- ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]);
- kbd_layouts.push_back(ly);
-
- if ([name isEqualToString:cur_name]) {
- current_layout = kbd_layouts.size() - 1;
- }
- }
- [list_kbd release];
- }
-
- keyboard_layout_dirty = false;
+bool DisplayServerOSX::get_swap_cancel_ok() {
+ return false;
}
int DisplayServerOSX::keyboard_get_layout_count() const {
if (keyboard_layout_dirty) {
- _update_keyboard_layouts();
+ const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts();
}
return kbd_layouts.size();
}
void DisplayServerOSX::keyboard_set_current_layout(int p_index) {
if (keyboard_layout_dirty) {
- _update_keyboard_layouts();
+ const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts();
}
ERR_FAIL_INDEX(p_index, kbd_layouts.size());
@@ -3156,31 +2273,29 @@ void DisplayServerOSX::keyboard_set_current_layout(int p_index) {
NSString *cur_name = [NSString stringWithUTF8String:kbd_layouts[p_index].name.utf8().get_data()];
NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout };
- NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false);
+ NSArray *list_kbd = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false);
for (NSUInteger i = 0; i < [list_kbd count]; i++) {
- NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName);
+ NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName);
if ([name isEqualToString:cur_name]) {
- TISSelectInputSource((TISInputSourceRef)[list_kbd objectAtIndex:i]);
+ TISSelectInputSource((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i]);
break;
}
}
- [list_kbd release];
NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode };
- NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false);
+ NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false);
for (NSUInteger i = 0; i < [list_ime count]; i++) {
- NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName);
+ NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName);
if ([name isEqualToString:cur_name]) {
- TISSelectInputSource((TISInputSourceRef)[list_ime objectAtIndex:i]);
+ TISSelectInputSource((__bridge TISInputSourceRef)[list_ime objectAtIndex:i]);
break;
}
}
- [list_ime release];
}
int DisplayServerOSX::keyboard_get_current_layout() const {
if (keyboard_layout_dirty) {
- _update_keyboard_layouts();
+ const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts();
}
return current_layout;
@@ -3188,7 +2303,7 @@ int DisplayServerOSX::keyboard_get_current_layout() const {
String DisplayServerOSX::keyboard_get_layout_language(int p_index) const {
if (keyboard_layout_dirty) {
- _update_keyboard_layouts();
+ const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts();
}
ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), "");
@@ -3197,7 +2312,7 @@ String DisplayServerOSX::keyboard_get_layout_language(int p_index) const {
String DisplayServerOSX::keyboard_get_layout_name(int p_index) const {
if (keyboard_layout_dirty) {
- _update_keyboard_layouts();
+ const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts();
}
ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), "");
@@ -3211,124 +2326,8 @@ Key DisplayServerOSX::keyboard_get_keycode_from_physical(Key p_keycode) const {
Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK;
Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;
- unsigned int osx_keycode = unmapKey((Key)keycode_no_mod);
- return (Key)(remapKey(osx_keycode, 0) | modifiers);
-}
-
-void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) {
- Ref<InputEvent> ev = p_event;
- Input::get_singleton()->parse_input_event(ev);
-}
-
-void DisplayServerOSX::_release_pressed_events() {
- _THREAD_SAFE_METHOD_
- if (Input::get_singleton()) {
- Input::get_singleton()->release_pressed_events();
- }
-}
-
-NSMenu *DisplayServerOSX::_get_dock_menu() const {
- return dock_menu;
-}
-
-void DisplayServerOSX::_menu_callback(id p_sender) {
- if (![p_sender representedObject]) {
- return;
- }
-
- GlobalMenuItem *value = [p_sender representedObject];
-
- if (value) {
- if (value->checkable) {
- if ([p_sender state] == NSControlStateValueOff) {
- [p_sender setState:NSControlStateValueOn];
- } else {
- [p_sender setState:NSControlStateValueOff];
- }
- }
-
- if (value->callback != Callable()) {
- Variant tag = value->meta;
- Variant *tagp = &tag;
- Variant ret;
- Callable::CallError ce;
- value->callback.call((const Variant **)&tagp, 1, ret, ce);
- }
- }
-}
-
-void DisplayServerOSX::_send_event(NSEvent *p_event) {
- // special case handling of command-period, which is traditionally a special
- // shortcut in macOS and doesn't arrive at our regular keyDown handler.
- if ([p_event type] == NSEventTypeKeyDown) {
- if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) {
- Ref<InputEventKey> k;
- k.instantiate();
-
- _get_key_modifier_state([p_event modifierFlags], k);
- k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID);
- k->set_pressed(true);
- k->set_keycode(Key::PERIOD);
- k->set_physical_keycode(Key::PERIOD);
- k->set_echo([p_event isARepeat]);
-
- Input::get_singleton()->parse_input_event(k);
- }
- }
-}
-
-void DisplayServerOSX::_process_key_events() {
- Ref<InputEventKey> k;
- for (int i = 0; i < key_event_pos; i++) {
- const KeyEvent &ke = key_event_buffer[i];
- if (ke.raw) {
- // Non IME input - no composite characters, pass events as is
- k.instantiate();
-
- k->set_window_id(ke.window_id);
- _get_key_modifier_state(ke.osx_state, k);
- k->set_pressed(ke.pressed);
- k->set_echo(ke.echo);
- k->set_keycode(ke.keycode);
- k->set_physical_keycode((Key)ke.physical_keycode);
- k->set_unicode(ke.unicode);
-
- _push_input(k);
- } else {
- // IME input
- if ((i == 0 && ke.keycode == Key::NONE) || (i > 0 && key_event_buffer[i - 1].keycode == Key::NONE)) {
- k.instantiate();
-
- k->set_window_id(ke.window_id);
- _get_key_modifier_state(ke.osx_state, k);
- k->set_pressed(ke.pressed);
- k->set_echo(ke.echo);
- k->set_keycode(Key::NONE);
- k->set_physical_keycode(Key::NONE);
- k->set_unicode(ke.unicode);
-
- _push_input(k);
- }
- if (ke.keycode != Key::NONE) {
- k.instantiate();
-
- k->set_window_id(ke.window_id);
- _get_key_modifier_state(ke.osx_state, k);
- k->set_pressed(ke.pressed);
- k->set_echo(ke.echo);
- k->set_keycode(ke.keycode);
- k->set_physical_keycode((Key)ke.physical_keycode);
-
- if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) {
- k->set_unicode(key_event_buffer[i + 1].unicode);
- }
-
- _push_input(k);
- }
- }
- }
-
- key_event_pos = 0;
+ unsigned int osx_keycode = KeyMappingOSX::unmap_key((Key)keycode_no_mod);
+ return (Key)(KeyMappingOSX::remap_key(osx_keycode, 0) | modifiers);
}
void DisplayServerOSX::process_events() {
@@ -3356,8 +2355,8 @@ void DisplayServerOSX::process_events() {
for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
WindowData &wd = E->get();
if (wd.mpath.size() > 0) {
- const Vector2 mpos = _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
- if (Geometry2D::is_point_in_polygon(mpos, wd.mpath)) {
+ update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+ if (Geometry2D::is_point_in_polygon(wd.mouse_pos, wd.mpath)) {
if ([wd.window_object ignoresMouseEvents]) {
[wd.window_object setIgnoresMouseEvents:NO];
}
@@ -3372,9 +2371,6 @@ void DisplayServerOSX::process_events() {
}
}
}
-
- [autoreleasePool drain];
- autoreleasePool = [[NSAutoreleasePool alloc] init];
}
void DisplayServerOSX::force_process_and_drop_events() {
@@ -3385,6 +2381,18 @@ void DisplayServerOSX::force_process_and_drop_events() {
drop_events = false;
}
+void DisplayServerOSX::release_rendering_thread() {
+}
+
+void DisplayServerOSX::make_rendering_thread() {
+}
+
+void DisplayServerOSX::swap_buffers() {
+#if defined(GLES3_ENABLED)
+ gl_manager->swap_buffers();
+#endif
+}
+
void DisplayServerOSX::set_native_icon(const String &p_filename) {
_THREAD_SAFE_METHOD_
@@ -3397,10 +2405,10 @@ void DisplayServerOSX::set_native_icon(const String &p_filename) {
f->get_buffer((uint8_t *)&data.write[0], len);
memdelete(f);
- NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease];
+ NSData *icon_data = [[NSData alloc] initWithBytes:&data.write[0] length:len];
ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data.");
- NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease];
+ NSImage *icon = [[NSImage alloc] initWithData:icon_data];
ERR_FAIL_COND_MSG(!icon, "Error loading icon.");
[NSApp setApplicationIconImage:icon];
@@ -3443,38 +2451,14 @@ void DisplayServerOSX::set_icon(const Ref<Image> &p_icon) {
[nsimg addRepresentation:imgrep];
[NSApp setApplicationIconImage:nsimg];
-
- [imgrep release];
- [nsimg release];
}
-void DisplayServerOSX::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
- _THREAD_SAFE_METHOD_
-#if defined(GLES3_ENABLED)
- if (gl_manager) {
- gl_manager->swap_buffers();
- }
-#endif
-#if defined(VULKAN_ENABLED)
- if (context_vulkan) {
- context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
- }
-#endif
-}
-
-DisplayServer::VSyncMode DisplayServerOSX::window_get_vsync_mode(WindowID p_window) const {
- _THREAD_SAFE_METHOD_
-#if defined(GLES3_ENABLED)
- if (gl_manager) {
- return (gl_manager->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED);
- }
-#endif
-#if defined(VULKAN_ENABLED)
- if (context_vulkan) {
- return context_vulkan->get_vsync_mode(p_window);
+DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ DisplayServer *ds = memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
+ if (r_error != OK) {
+ OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan or OpenGL versions.", "Unable to initialize Video driver");
}
-#endif
- return DisplayServer::VSYNC_ENABLED;
+ return ds;
}
Vector<String> DisplayServerOSX::get_rendering_drivers_func() {
@@ -3490,233 +2474,149 @@ Vector<String> DisplayServerOSX::get_rendering_drivers_func() {
return drivers;
}
-void DisplayServerOSX::gl_window_make_current(DisplayServer::WindowID p_window_id) {
-#if defined(GLES3_ENABLED)
- gl_manager->window_make_current(p_window_id);
-#endif
-}
-
-Point2i DisplayServerOSX::ime_get_selection() const {
- return im_selection;
-}
-
-String DisplayServerOSX::ime_get_text() const {
- return im_text;
+void DisplayServerOSX::register_osx_driver() {
+ register_create_function("osx", create_func, get_rendering_drivers_func);
}
-DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const {
- Point2i position = p_position;
- position.y *= -1;
- position += _get_screens_origin();
- position /= screen_get_max_scale();
-
- NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/];
- for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
- if ([E->get().window_object windowNumber] == wnum) {
- return E->key();
- }
+DisplayServer::WindowID DisplayServerOSX::window_get_active_popup() const {
+ const List<WindowID>::Element *E = popup_list.back();
+ if (E) {
+ return E->get();
+ } else {
+ return INVALID_WINDOW_ID;
}
- return INVALID_WINDOW_ID;
}
-void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+void DisplayServerOSX::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
- windows[p_window].instance_id = p_instance;
+ WindowData &wd = windows[p_window];
+ wd.parent_safe_rect = p_rect;
}
-ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const {
+Rect2i DisplayServerOSX::window_get_popup_safe_rect(WindowID p_window) const {
_THREAD_SAFE_METHOD_
- ERR_FAIL_COND_V(!windows.has(p_window), ObjectID());
- return windows[p_window].instance_id;
-}
-
-DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
- DisplayServer *ds = memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
- if (r_error != OK) {
- OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan or OpenGL versions.", "Unable to initialize Video driver");
- }
- return ds;
+ ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());
+ const WindowData &wd = windows[p_window];
+ return wd.parent_safe_rect;
}
-DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect) {
- WindowID id;
- const float scale = screen_get_max_scale();
- {
- WindowData wd;
-
- wd.window_delegate = [[GodotWindowDelegate alloc] init];
- ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate");
- [wd.window_delegate setWindowID:window_id_counter];
-
- Point2i position = p_rect.position;
- // OS X native y-coordinate relative to _get_screens_origin() is negative,
- // Godot passes a positive value
- position.y *= -1;
- position += _get_screens_origin();
-
- // initWithContentRect uses bottom-left corner of the window’s frame as origin.
- wd.window_object = [[GodotWindow alloc]
- initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale)
- styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
- backing:NSBackingStoreBuffered
- defer:NO];
- ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window");
-
- wd.window_view = [[GodotContentView alloc] init];
- ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view");
- [wd.window_view setWindowID:window_id_counter];
- [wd.window_view setWantsLayer:TRUE];
-
- [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
- [wd.window_object setContentView:wd.window_view];
- [wd.window_object setDelegate:wd.window_delegate];
- [wd.window_object setAcceptsMouseMovedEvents:YES];
- [wd.window_object setRestorable:NO];
-
- if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) {
- [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed];
- }
-
- CALayer *layer = [wd.window_view layer];
- if (layer) {
- layer.contentsScale = scale;
+void DisplayServerOSX::popup_open(WindowID p_window) {
+ WindowData &wd = windows[p_window];
+ if (wd.is_popup) {
+ bool was_empty = popup_list.is_empty();
+ // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ List<WindowID>::Element *E = popup_list.back();
+ while (E) {
+ if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
+ send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ } else {
+ break;
+ }
}
-#if defined(VULKAN_ENABLED)
- if (context_vulkan) {
- Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, wd.window_view, p_rect.size.width, p_rect.size.height);
- ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context");
- }
-#endif
-#if defined(GLES3_ENABLED)
- if (gl_manager) {
- Error err = gl_manager->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height);
- ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context");
+ if (was_empty && popup_list.is_empty()) {
+ // Inform OS that popup was opened, to close other native popups.
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
}
-#endif
- id = window_id_counter++;
- windows[id] = wd;
+ time_since_popup = OS::get_singleton()->get_ticks_msec();
+ popup_list.push_back(p_window);
}
+}
- WindowData &wd = windows[id];
- window_set_mode(p_mode, id);
-
- const NSRect contentRect = [wd.window_view frame];
- wd.size.width = contentRect.size.width * scale;
- wd.size.height = contentRect.size.height * scale;
-
- CALayer *layer = [wd.window_view layer];
- if (layer) {
- layer.contentsScale = scale;
+void DisplayServerOSX::popup_close(WindowID p_window) {
+ bool was_empty = popup_list.is_empty();
+ List<WindowID>::Element *E = popup_list.find(p_window);
+ while (E) {
+ send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->next();
+ popup_list.erase(E);
+ E = F;
}
-
-#if defined(GLES3_ENABLED)
- if (gl_manager) {
- gl_manager->window_resize(id, wd.size.width, wd.size.height);
+ if (!was_empty && popup_list.is_empty()) {
+ // Inform OS that all popups are closed.
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
}
-#endif
-#if defined(VULKAN_ENABLED)
- if (context_vulkan) {
- context_vulkan->window_resize(id, wd.size.width, wd.size.height);
- }
-#endif
-
- return id;
}
-void DisplayServerOSX::_dispatch_input_events(const Ref<InputEvent> &p_event) {
- ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event);
-}
-
-void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+void DisplayServerOSX::mouse_process_popups(bool p_close) {
_THREAD_SAFE_METHOD_
- if (!in_dispatch_input_event) {
- in_dispatch_input_event = true;
- Variant ev = p_event;
- Variant *evp = &ev;
- Variant ret;
- Callable::CallError ce;
+ bool was_empty = popup_list.is_empty();
+ if (p_close) {
+ // Close all popups.
+ List<WindowID>::Element *E = popup_list.front();
+ while (E) {
+ send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->next();
+ popup_list.erase(E);
+ E = F;
+ }
+ if (!was_empty) {
+ // Inform OS that all popups are closed.
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
+ }
+ } else {
+ uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
+ if (delta < 250) {
+ return;
+ }
- Ref<InputEventFromWindow> event_from_window = p_event;
- if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
- //send to a window
- if (windows.has(event_from_window->get_window_id())) {
- Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
- if (callable.is_null()) {
- return;
- }
- callable.call((const Variant **)&evp, 1, ret, ce);
- }
- } else {
- //send to all windows
- for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
- Callable callable = E->get().input_event_callback;
- if (callable.is_null()) {
- continue;
- }
- callable.call((const Variant **)&evp, 1, ret, ce);
+ Point2i pos = mouse_get_position();
+ List<WindowID>::Element *E = popup_list.back();
+ while (E) {
+ // Popup window area.
+ Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
+ // Area of the parent window, which responsible for opening sub-menu.
+ Rect2i safe_rect = window_get_popup_safe_rect(E->get());
+ if (win_rect.has_point(pos)) {
+ break;
+ } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
+ break;
+ } else {
+ send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
}
}
-
- in_dispatch_input_event = false;
+ if (!was_empty && popup_list.is_empty()) {
+ // Inform OS that all popups are closed.
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
+ }
}
}
-void DisplayServerOSX::release_rendering_thread() {
-}
-
-void DisplayServerOSX::make_rendering_thread() {
-}
-
-void DisplayServerOSX::swap_buffers() {
-#if defined(GLES3_ENABLED)
- gl_manager->swap_buffers();
-#endif
-}
-
-void DisplayServerOSX::console_set_visible(bool p_enabled) {
- //TODO - open terminal and redirect
-}
-
-bool DisplayServerOSX::is_console_visible() const {
- return isatty(STDIN_FILENO);
-}
-
DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
r_error = OK;
- drop_events = false;
memset(cursors, 0, sizeof(cursors));
- cursor_shape = CURSOR_ARROW;
-
- key_event_pos = 0;
- mouse_mode = MOUSE_MODE_VISIBLE;
- autoreleasePool = [[NSAutoreleasePool alloc] init];
+ event_source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ ERR_FAIL_COND(!event_source);
- eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
- ERR_FAIL_COND(!eventSource);
+ CGEventSourceSetLocalEventsSuppressionInterval(event_source, 0.0);
- CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0);
-
- keyboard_layout_dirty = true;
- displays_arrangement_dirty = true;
- displays_scale_dirty = true;
+ int screen_count = get_screen_count();
+ for (int i = 0; i < screen_count; i++) {
+ display_max_scale = fmax(display_max_scale, screen_get_scale(i));
+ }
- // Register to be notified on keyboard layout changes
+ // Register to be notified on keyboard layout changes.
CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(),
- nullptr, keyboard_layout_changed,
+ nullptr, _keyboard_layout_changed,
kTISNotifySelectedKeyboardInputSourceChanged, nullptr,
CFNotificationSuspensionBehaviorDeliverImmediately);
- // Register to be notified on displays arrangement changes
- CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, nullptr);
+ // Register to be notified on displays arrangement changes.
+ CGDisplayRegisterReconfigurationCallback(_displays_arrangement_changed, nullptr);
NSMenuItem *menu_item;
NSString *title;
@@ -3726,11 +2626,11 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode
nsappname = [[NSProcessInfo processInfo] processName];
}
- // Setup Dock menu
+ // Setup Dock menu.
dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
- // Setup Apple menu
- apple_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+ // Setup Apple menu.
+ apple_menu = [[NSMenu alloc] initWithTitle:@""];
title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
@@ -3740,7 +2640,6 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode
menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""];
[apple_menu setSubmenu:services forItem:menu_item];
[NSApp setServicesMenu:services];
- [services release];
[apple_menu addItem:[NSMenuItem separatorItem]];
@@ -3757,7 +2656,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode
title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
- // Add items to the menu bar
+ // Add items to the menu bar.
NSMenu *main_menu = [NSApp mainMenu];
menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
[main_menu setSubmenu:apple_menu forItem:menu_item];
@@ -3819,15 +2718,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode
}
DisplayServerOSX::~DisplayServerOSX() {
- if (dock_menu) {
- [dock_menu release];
- }
-
- for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) {
- [E->get() release];
- }
-
- //destroy all windows
+ // Destroy all windows.
for (Map<WindowID, WindowData>::Element *E = windows.front(); E;) {
Map<WindowID, WindowData>::Element *F = E;
E = E->next();
@@ -3835,7 +2726,7 @@ DisplayServerOSX::~DisplayServerOSX() {
[F->get().window_object close];
}
- //destroy drivers
+ // Destroy drivers.
#if defined(GLES3_ENABLED)
if (gl_manager) {
memdelete(gl_manager);
@@ -3856,11 +2747,7 @@ DisplayServerOSX::~DisplayServerOSX() {
#endif
CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), nullptr, kTISNotifySelectedKeyboardInputSourceChanged, nullptr);
- CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, nullptr);
+ CGDisplayRemoveReconfigurationCallback(_displays_arrangement_changed, nullptr);
cursors_cache.clear();
}
-
-void DisplayServerOSX::register_osx_driver() {
- register_create_function("osx", create_func, get_rendering_drivers_func);
-}
diff --git a/platform/osx/export/codesign.cpp b/platform/osx/export/codesign.cpp
new file mode 100644
index 0000000000..b609a21c44
--- /dev/null
+++ b/platform/osx/export/codesign.cpp
@@ -0,0 +1,1569 @@
+/*************************************************************************/
+/* codesign.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "codesign.h"
+
+#include "lipo.h"
+#include "macho.h"
+#include "plist.h"
+
+#include "core/os/os.h"
+#include "editor/editor_paths.h"
+#include "editor/editor_settings.h"
+
+#include "modules/modules_enabled.gen.h" // For regex.
+
+#include <ctime>
+
+#ifdef MODULE_REGEX_ENABLED
+
+/*************************************************************************/
+/* CodeSignCodeResources */
+/*************************************************************************/
+
+String CodeSignCodeResources::hash_sha1_base64(const String &p_path) {
+ FileAccessRef fa = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!fa, String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
+
+ CryptoCore::SHA1Context ctx;
+ ctx.start();
+
+ unsigned char step[4096];
+ while (true) {
+ uint64_t br = fa->get_buffer(step, 4096);
+ if (br > 0) {
+ ctx.update(step, br);
+ }
+ if (br < 4096) {
+ break;
+ }
+ }
+
+ unsigned char hash[0x14];
+ ctx.finish(hash);
+ fa->close();
+
+ return CryptoCore::b64_encode_str(hash, 0x14);
+}
+
+String CodeSignCodeResources::hash_sha256_base64(const String &p_path) {
+ FileAccessRef fa = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!fa, String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
+
+ CryptoCore::SHA256Context ctx;
+ ctx.start();
+
+ unsigned char step[4096];
+ while (true) {
+ uint64_t br = fa->get_buffer(step, 4096);
+ if (br > 0) {
+ ctx.update(step, br);
+ }
+ if (br < 4096) {
+ break;
+ }
+ }
+
+ unsigned char hash[0x20];
+ ctx.finish(hash);
+ fa->close();
+
+ return CryptoCore::b64_encode_str(hash, 0x20);
+}
+
+void CodeSignCodeResources::add_rule1(const String &p_rule, const String &p_key, int p_weight, bool p_store) {
+ rules1.push_back(CRRule(p_rule, p_key, p_weight, p_store));
+}
+
+void CodeSignCodeResources::add_rule2(const String &p_rule, const String &p_key, int p_weight, bool p_store) {
+ rules2.push_back(CRRule(p_rule, p_key, p_weight, p_store));
+}
+
+CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules1(const String &p_path) const {
+ CRMatch found = CRMatch::CR_MATCH_NO;
+ int weight = 0;
+ for (int i = 0; i < rules1.size(); i++) {
+ RegEx regex = RegEx(rules1[i].file_pattern);
+ if (regex.search(p_path).is_valid()) {
+ if (rules1[i].key == "omit") {
+ return CRMatch::CR_MATCH_NO;
+ } else if (rules1[i].key == "nested") {
+ if (weight <= rules1[i].weight) {
+ found = CRMatch::CR_MATCH_NESTED;
+ weight = rules1[i].weight;
+ }
+ } else if (rules1[i].key == "optional") {
+ if (weight <= rules1[i].weight) {
+ found = CRMatch::CR_MATCH_OPTIONAL;
+ weight = rules1[i].weight;
+ }
+ } else {
+ if (weight <= rules1[i].weight) {
+ found = CRMatch::CR_MATCH_YES;
+ weight = rules1[i].weight;
+ }
+ }
+ }
+ }
+ return found;
+}
+
+CodeSignCodeResources::CRMatch CodeSignCodeResources::match_rules2(const String &p_path) const {
+ CRMatch found = CRMatch::CR_MATCH_NO;
+ int weight = 0;
+ for (int i = 0; i < rules2.size(); i++) {
+ RegEx regex = RegEx(rules2[i].file_pattern);
+ if (regex.search(p_path).is_valid()) {
+ if (rules2[i].key == "omit") {
+ return CRMatch::CR_MATCH_NO;
+ } else if (rules2[i].key == "nested") {
+ if (weight <= rules2[i].weight) {
+ found = CRMatch::CR_MATCH_NESTED;
+ weight = rules2[i].weight;
+ }
+ } else if (rules2[i].key == "optional") {
+ if (weight <= rules2[i].weight) {
+ found = CRMatch::CR_MATCH_OPTIONAL;
+ weight = rules2[i].weight;
+ }
+ } else {
+ if (weight <= rules2[i].weight) {
+ found = CRMatch::CR_MATCH_YES;
+ weight = rules2[i].weight;
+ }
+ }
+ }
+ }
+ return found;
+}
+
+bool CodeSignCodeResources::add_file1(const String &p_root, const String &p_path) {
+ CRMatch found = match_rules1(p_path);
+ if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) {
+ return true; // No match.
+ }
+
+ CRFile f;
+ f.name = p_path;
+ f.optional = (found == CRMatch::CR_MATCH_OPTIONAL);
+ f.nested = false;
+ f.hash = hash_sha1_base64(p_root.plus_file(p_path));
+ print_verbose(vformat("CodeSign/CodeResources: File(V1) %s hash1:%s", f.name, f.hash));
+
+ files1.push_back(f);
+ return true;
+}
+
+bool CodeSignCodeResources::add_file2(const String &p_root, const String &p_path) {
+ CRMatch found = match_rules2(p_path);
+ if (found == CRMatch::CR_MATCH_NESTED) {
+ return add_nested_file(p_root, p_path, p_root.plus_file(p_path));
+ }
+ if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) {
+ return true; // No match.
+ }
+
+ CRFile f;
+ f.name = p_path;
+ f.optional = (found == CRMatch::CR_MATCH_OPTIONAL);
+ f.nested = false;
+ f.hash = hash_sha1_base64(p_root.plus_file(p_path));
+ f.hash2 = hash_sha256_base64(p_root.plus_file(p_path));
+
+ print_verbose(vformat("CodeSign/CodeResources: File(V2) %s hash1:%s hash2:%s", f.name, f.hash, f.hash2));
+
+ files2.push_back(f);
+ return true;
+}
+
+bool CodeSignCodeResources::add_nested_file(const String &p_root, const String &p_path, const String &p_exepath) {
+#define CLEANUP() \
+ if (files_to_add.size() > 1) { \
+ for (int j = 0; j < files_to_add.size(); j++) { \
+ da->remove(files_to_add[j]); \
+ } \
+ }
+
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V(!da, false);
+
+ Vector<String> files_to_add;
+ if (LipO::is_lipo(p_exepath)) {
+ String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo");
+ Error err = da->make_dir_recursive(tmp_path_name);
+ if (err != OK) {
+ ERR_FAIL_V_MSG(false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name));
+ }
+ LipO lip;
+ if (lip.open_file(p_exepath)) {
+ for (int i = 0; i < lip.get_arch_count(); i++) {
+ if (!lip.extract_arch(i, tmp_path_name.plus_file("_rqexe_" + itos(i)))) {
+ CLEANUP();
+ ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Failed to extract thin binary.");
+ }
+ files_to_add.push_back(tmp_path_name.plus_file("_rqexe_" + itos(i)));
+ }
+ }
+ } else if (MachO::is_macho(p_exepath)) {
+ files_to_add.push_back(p_exepath);
+ }
+
+ CRFile f;
+ f.name = p_path;
+ f.optional = false;
+ f.nested = true;
+ for (int i = 0; i < files_to_add.size(); i++) {
+ MachO mh;
+ if (!mh.open_file(files_to_add[i])) {
+ CLEANUP();
+ ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid executable file.");
+ }
+ PackedByteArray hash = mh.get_cdhash_sha256(); // Use SHA-256 variant, if available.
+ if (hash.size() != 0x20) {
+ hash = mh.get_cdhash_sha1(); // Use SHA-1 instead.
+ if (hash.size() != 0x14) {
+ CLEANUP();
+ ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Unsigned nested executable file.");
+ }
+ }
+ hash.resize(0x14); // Always clamp to 0x14 size.
+ f.hash = CryptoCore::b64_encode_str(hash.ptr(), hash.size());
+
+ PackedByteArray rq_blob = mh.get_requirements();
+ String req_string;
+ if (rq_blob.size() > 8) {
+ CodeSignRequirements rq = CodeSignRequirements(rq_blob);
+ Vector<String> rqs = rq.parse_requirements();
+ for (int j = 0; j < rqs.size(); j++) {
+ if (rqs[j].begins_with("designated => ")) {
+ req_string = rqs[j].replace("designated => ", "");
+ }
+ }
+ }
+ if (req_string.is_empty()) {
+ req_string = "cdhash H\"" + String::hex_encode_buffer(hash.ptr(), hash.size()) + "\"";
+ }
+ print_verbose(vformat("CodeSign/CodeResources: Nested object %s (cputype: %d) cdhash:%s designated rq:%s", f.name, mh.get_cputype(), f.hash, req_string));
+ if (f.requirements != req_string) {
+ if (i != 0) {
+ f.requirements += " or ";
+ }
+ f.requirements += req_string;
+ }
+ }
+ files2.push_back(f);
+
+ CLEANUP();
+ return true;
+
+#undef CLEANUP
+}
+
+bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const String &p_path, const String &p_main_exe_path) {
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V(!da, false);
+ Error err = da->change_dir(p_root.plus_file(p_path));
+ ERR_FAIL_COND_V(err != OK, false);
+
+ bool ret = true;
+ da->list_dir_begin();
+ String n = da->get_next();
+ while (n != String()) {
+ if (n != "." && n != "..") {
+ String path = p_root.plus_file(p_path).plus_file(n);
+ if (path == p_main_exe_path) {
+ n = da->get_next();
+ continue; // Skip main executable.
+ }
+ if (da->current_is_dir()) {
+ CRMatch found = match_rules2(p_path.plus_file(n));
+ String fmw_ver = "Current"; // Framework version (default).
+ String info_path;
+ String main_exe;
+ bool bundle = false;
+ if (da->file_exists(path.plus_file("Contents/Info.plist"))) {
+ info_path = path.plus_file("Contents/Info.plist");
+ main_exe = path.plus_file("Contents/MacOS");
+ bundle = true;
+ } else if (da->file_exists(path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) {
+ info_path = path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver));
+ main_exe = path.plus_file(vformat("Versions/%s", fmw_ver));
+ bundle = true;
+ } else if (da->file_exists(path.plus_file("Info.plist"))) {
+ info_path = path.plus_file("Info.plist");
+ main_exe = path;
+ bundle = true;
+ }
+ if (bundle && found == CRMatch::CR_MATCH_NESTED && !info_path.is_empty()) {
+ // Read Info.plist.
+ PList info_plist;
+ if (info_plist.load_file(info_path)) {
+ if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) {
+ main_exe = main_exe.plus_file(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data()));
+ } else {
+ ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, no exe name.");
+ }
+ } else {
+ ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, can't load.");
+ }
+ ret = ret && add_nested_file(p_root, p_path.plus_file(n), main_exe);
+ } else {
+ ret = ret && add_folder_recursive(p_root, p_path.plus_file(n), p_main_exe_path);
+ }
+ } else {
+ ret = ret && add_file1(p_root, p_path.plus_file(n));
+ ret = ret && add_file2(p_root, p_path.plus_file(n));
+ }
+ }
+
+ n = da->get_next();
+ }
+
+ da->list_dir_end();
+ return ret;
+}
+
+bool CodeSignCodeResources::save_to_file(const String &p_path) {
+ PList pl;
+
+ print_verbose(vformat("CodeSign/CodeResources: Writing to file: %s", p_path));
+
+ // Write version 1 hashes.
+ Ref<PListNode> files1_dict = PListNode::new_dict();
+ pl.get_root()->push_subnode(files1_dict, "files");
+ for (int i = 0; i < files1.size(); i++) {
+ if (files1[i].optional) {
+ Ref<PListNode> file_dict = PListNode::new_dict();
+ files1_dict->push_subnode(file_dict, files1[i].name);
+
+ file_dict->push_subnode(PListNode::new_data(files1[i].hash), "hash");
+ file_dict->push_subnode(PListNode::new_bool(true), "optional");
+ } else {
+ files1_dict->push_subnode(PListNode::new_data(files1[i].hash), files1[i].name);
+ }
+ }
+
+ // Write version 2 hashes.
+ Ref<PListNode> files2_dict = PListNode::new_dict();
+ pl.get_root()->push_subnode(files2_dict, "files2");
+ for (int i = 0; i < files2.size(); i++) {
+ Ref<PListNode> file_dict = PListNode::new_dict();
+ files2_dict->push_subnode(file_dict, files2[i].name);
+
+ if (files2[i].nested) {
+ file_dict->push_subnode(PListNode::new_data(files2[i].hash), "cdhash");
+ file_dict->push_subnode(PListNode::new_string(files2[i].requirements), "requirement");
+ } else {
+ file_dict->push_subnode(PListNode::new_data(files2[i].hash), "hash");
+ file_dict->push_subnode(PListNode::new_data(files2[i].hash2), "hash2");
+ if (files2[i].optional) {
+ file_dict->push_subnode(PListNode::new_bool(true), "optional");
+ }
+ }
+ }
+
+ // Write version 1 rules.
+ Ref<PListNode> rules1_dict = PListNode::new_dict();
+ pl.get_root()->push_subnode(rules1_dict, "rules");
+ for (int i = 0; i < rules1.size(); i++) {
+ if (rules1[i].store) {
+ if (rules1[i].key.is_empty() && rules1[i].weight <= 0) {
+ rules1_dict->push_subnode(PListNode::new_bool(true), rules1[i].file_pattern);
+ } else {
+ Ref<PListNode> rule_dict = PListNode::new_dict();
+ rules1_dict->push_subnode(rule_dict, rules1[i].file_pattern);
+ if (!rules1[i].key.is_empty()) {
+ rule_dict->push_subnode(PListNode::new_bool(true), rules1[i].key);
+ }
+ if (rules1[i].weight != 1) {
+ rule_dict->push_subnode(PListNode::new_real(rules1[i].weight), "weight");
+ }
+ }
+ }
+ }
+
+ // Write version 2 rules.
+ Ref<PListNode> rules2_dict = PListNode::new_dict();
+ pl.get_root()->push_subnode(rules2_dict, "rules2");
+ for (int i = 0; i < rules2.size(); i++) {
+ if (rules2[i].store) {
+ if (rules2[i].key.is_empty() && rules2[i].weight <= 0) {
+ rules2_dict->push_subnode(PListNode::new_bool(true), rules2[i].file_pattern);
+ } else {
+ Ref<PListNode> rule_dict = PListNode::new_dict();
+ rules2_dict->push_subnode(rule_dict, rules2[i].file_pattern);
+ if (!rules2[i].key.is_empty()) {
+ rule_dict->push_subnode(PListNode::new_bool(true), rules2[i].key);
+ }
+ if (rules2[i].weight != 1) {
+ rule_dict->push_subnode(PListNode::new_real(rules2[i].weight), "weight");
+ }
+ }
+ }
+ }
+ String text = pl.save_text();
+ ERR_FAIL_COND_V_MSG(text.is_empty(), false, "CodeSign/CodeResources: Generating resources PList failed.");
+
+ FileAccessRef fa = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(!fa, false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
+
+ CharString cs = text.utf8();
+ fa->store_buffer((const uint8_t *)cs.ptr(), cs.length());
+ fa->close();
+ return true;
+}
+
+/*************************************************************************/
+/* CodeSignRequirements */
+/*************************************************************************/
+
+CodeSignRequirements::CodeSignRequirements() {
+ blob.append_array({ 0xFA, 0xDE, 0x0C, 0x01 }); // Requirement set magic.
+ blob.append_array({ 0x00, 0x00, 0x00, 0x0C }); // Length of requirements set (12 bytes).
+ blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Empty.
+}
+
+CodeSignRequirements::CodeSignRequirements(const PackedByteArray &p_data) {
+ blob = p_data;
+}
+
+_FORCE_INLINE_ void CodeSignRequirements::_parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
+#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
+ ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ r_out += "certificate ";
+ uint32_t tag_slot = _R(r_pos);
+ if (tag_slot == 0x00000000) {
+ r_out += "leaf";
+ } else if (tag_slot == 0xffffffff) {
+ r_out += "root";
+ } else {
+ r_out += itos((int32_t)tag_slot);
+ }
+ r_pos += 4;
+#undef _R
+}
+
+_FORCE_INLINE_ void CodeSignRequirements::_parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
+#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
+ ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ uint32_t key_size = _R(r_pos);
+ ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ CharString key;
+ key.resize(key_size);
+ memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size);
+ r_pos += 4 + key_size + PAD(key_size, 4);
+ r_out += "[" + String::utf8(key, key_size) + "]";
+#undef _R
+}
+
+_FORCE_INLINE_ void CodeSignRequirements::_parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
+#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
+ ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ uint32_t key_size = _R(r_pos);
+ ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ r_out += "[field.";
+ r_out += itos(blob[r_pos + 4] / 40) + ".";
+ r_out += itos(blob[r_pos + 4] % 40);
+ uint32_t spos = r_pos + 5;
+ while (spos < r_pos + 4 + key_size) {
+ r_out += ".";
+ if (blob[spos] <= 127) {
+ r_out += itos(blob[spos]);
+ spos += 1;
+ } else {
+ uint32_t x = (0x7F & blob[spos]) << 7;
+ spos += 1;
+ while (blob[spos] > 127) {
+ x = (x + (0x7F & blob[spos])) << 7;
+ spos += 1;
+ }
+ x = (x + (0x7F & blob[spos]));
+ r_out += itos(x);
+ spos += 1;
+ }
+ }
+ r_out += "]";
+ r_pos += 4 + key_size + PAD(key_size, 4);
+#undef _R
+}
+
+_FORCE_INLINE_ void CodeSignRequirements::_parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
+#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
+ ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ uint32_t tag_size = _R(r_pos);
+ ERR_FAIL_COND_MSG(r_pos + tag_size > p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ PackedByteArray data;
+ data.resize(tag_size);
+ memcpy(data.ptrw(), blob.ptr() + r_pos + 4, tag_size);
+ r_out += "H\"" + String::hex_encode_buffer(data.ptr(), data.size()) + "\"";
+ r_pos += 4 + tag_size + PAD(tag_size, 4);
+#undef _R
+}
+
+_FORCE_INLINE_ void CodeSignRequirements::_parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
+#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
+ ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ uint32_t key_size = _R(r_pos);
+ ERR_FAIL_COND_MSG(r_pos + key_size > p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ CharString key;
+ key.resize(key_size);
+ memcpy(key.ptrw(), blob.ptr() + r_pos + 4, key_size);
+ r_pos += 4 + key_size + PAD(key_size, 4);
+ r_out += "\"" + String::utf8(key, key_size) + "\"";
+#undef _R
+}
+
+_FORCE_INLINE_ void CodeSignRequirements::_parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
+#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
+ ERR_FAIL_COND_MSG(r_pos >= p_rq_size, "CodeSign/Requirements: Out of bounds.");
+ uint32_t date = _R(r_pos);
+ time_t t = 978307200 + date;
+ struct tm lt;
+#ifdef WINDOWS_ENABLED
+ gmtime_s(&lt, &t);
+#else
+ gmtime_r(&t, &lt);
+#endif
+ r_out += vformat("<%04d-%02d-%02d ", (int)(1900 + lt.tm_year), (int)(lt.tm_mon + 1), (int)(lt.tm_mday)) + vformat("%02d:%02d:%02d +0000>", (int)(lt.tm_hour), (int)(lt.tm_min), (int)(lt.tm_sec));
+#undef _R
+}
+
+_FORCE_INLINE_ bool CodeSignRequirements::_parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const {
+#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
+ ERR_FAIL_COND_V_MSG(r_pos >= p_rq_size, false, "CodeSign/Requirements: Out of bounds.");
+ uint32_t match = _R(r_pos);
+ r_pos += 4;
+ switch (match) {
+ case 0x00000000: {
+ r_out += "exists";
+ } break;
+ case 0x00000001: {
+ r_out += "= ";
+ _parse_value(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x00000002: {
+ r_out += "~ ";
+ _parse_value(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x00000003: {
+ r_out += "= *";
+ _parse_value(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x00000004: {
+ r_out += "= ";
+ _parse_value(r_pos, r_out, p_rq_size);
+ r_out += "*";
+ } break;
+ case 0x00000005: {
+ r_out += "< ";
+ _parse_value(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x00000006: {
+ r_out += "> ";
+ _parse_value(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x00000007: {
+ r_out += "<= ";
+ _parse_value(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x00000008: {
+ r_out += ">= ";
+ _parse_value(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x00000009: {
+ r_out += "= ";
+ _parse_date(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x0000000A: {
+ r_out += "< ";
+ _parse_date(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x0000000B: {
+ r_out += "> ";
+ _parse_date(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x0000000C: {
+ r_out += "<= ";
+ _parse_date(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x0000000D: {
+ r_out += ">= ";
+ _parse_date(r_pos, r_out, p_rq_size);
+ } break;
+ case 0x0000000E: {
+ r_out += "absent";
+ } break;
+ default: {
+ return false;
+ }
+ }
+ return true;
+#undef _R
+}
+
+Vector<String> CodeSignRequirements::parse_requirements() const {
+#define _R(x) BSWAP32(*(uint32_t *)(blob.ptr() + x))
+ Vector<String> list;
+
+ // Read requirements set header.
+ ERR_FAIL_COND_V_MSG(blob.size() < 12, list, "CodeSign/Requirements: Blob is too small.");
+ uint32_t magic = _R(0);
+ ERR_FAIL_COND_V_MSG(magic != 0xfade0c01, list, "CodeSign/Requirements: Invalid set magic.");
+ uint32_t size = _R(4);
+ ERR_FAIL_COND_V_MSG(size != (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid set size.");
+ uint32_t count = _R(8);
+
+ for (uint32_t i = 0; i < count; i++) {
+ String out;
+
+ // Read requirement header.
+ uint32_t rq_type = _R(12 + i * 8);
+ uint32_t rq_offset = _R(12 + i * 8 + 4);
+ ERR_FAIL_COND_V_MSG(rq_offset + 12 >= (uint32_t)blob.size(), list, "CodeSign/Requirements: Invalid requirement offset.");
+ switch (rq_type) {
+ case 0x00000001: {
+ out += "host => ";
+ } break;
+ case 0x00000002: {
+ out += "guest => ";
+ } break;
+ case 0x00000003: {
+ out += "designated => ";
+ } break;
+ case 0x00000004: {
+ out += "library => ";
+ } break;
+ case 0x00000005: {
+ out += "plugin => ";
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement type.");
+ }
+ }
+ uint32_t rq_magic = _R(rq_offset);
+ uint32_t rq_size = _R(rq_offset + 4);
+ uint32_t rq_ver = _R(rq_offset + 8);
+ uint32_t pos = rq_offset + 12;
+ ERR_FAIL_COND_V_MSG(rq_magic != 0xfade0c00, list, "CodeSign/Requirements: Invalid requirement magic.");
+ ERR_FAIL_COND_V_MSG(rq_ver != 0x00000001, list, "CodeSign/Requirements: Invalid requirement version.");
+
+ // Read requirement tokens.
+ List<String> tokens;
+ while (pos < rq_offset + rq_size) {
+ uint32_t rq_tag = _R(pos);
+ pos += 4;
+ String token;
+ switch (rq_tag) {
+ case 0x00000000: {
+ token = "false";
+ } break;
+ case 0x00000001: {
+ token = "true";
+ } break;
+ case 0x00000002: {
+ token = "identifier ";
+ _parse_value(pos, token, rq_offset + rq_size);
+ } break;
+ case 0x00000003: {
+ token = "anchor apple";
+ } break;
+ case 0x00000004: {
+ _parse_certificate_slot(pos, token, rq_offset + rq_size);
+ token += " ";
+ _parse_hash_string(pos, token, rq_offset + rq_size);
+ } break;
+ case 0x00000005: {
+ token = "info";
+ _parse_key(pos, token, rq_offset + rq_size);
+ token += " = ";
+ _parse_value(pos, token, rq_offset + rq_size);
+ } break;
+ case 0x00000006: {
+ token = "and";
+ } break;
+ case 0x00000007: {
+ token = "or";
+ } break;
+ case 0x00000008: {
+ token = "cdhash ";
+ _parse_hash_string(pos, token, rq_offset + rq_size);
+ } break;
+ case 0x00000009: {
+ token = "!";
+ } break;
+ case 0x0000000A: {
+ token = "info";
+ _parse_key(pos, token, rq_offset + rq_size);
+ token += " ";
+ ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix.");
+ } break;
+ case 0x0000000B: {
+ _parse_certificate_slot(pos, token, rq_offset + rq_size);
+ _parse_key(pos, token, rq_offset + rq_size);
+ token += " ";
+ ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix.");
+ } break;
+ case 0x0000000C: {
+ _parse_certificate_slot(pos, token, rq_offset + rq_size);
+ token += " trusted";
+ } break;
+ case 0x0000000D: {
+ token = "anchor trusted";
+ } break;
+ case 0x0000000E: {
+ _parse_certificate_slot(pos, token, rq_offset + rq_size);
+ _parse_oid_key(pos, token, rq_offset + rq_size);
+ token += " ";
+ ERR_FAIL_COND_V_MSG(!_parse_match(pos, token, rq_offset + rq_size), list, "CodeSign/Requirements: Unsupported match suffix.");
+ } break;
+ case 0x0000000F: {
+ token = "anchor apple generic";
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid requirement token.");
+ } break;
+ }
+ tokens.push_back(token);
+ }
+
+ // Polish to infix notation (w/o bracket optimization).
+ for (List<String>::Element *E = tokens.back(); E; E = E->prev()) {
+ if (E->get() == "and") {
+ ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence.");
+ String token = "(" + E->next()->get() + " and " + E->next()->next()->get() + ")";
+ tokens.erase(E->next()->next());
+ tokens.erase(E->next());
+ E->get() = token;
+ } else if (E->get() == "or") {
+ ERR_FAIL_COND_V_MSG(!E->next() || !E->next()->next(), list, "CodeSign/Requirements: Invalid token sequence.");
+ String token = "(" + E->next()->get() + " or " + E->next()->next()->get() + ")";
+ tokens.erase(E->next()->next());
+ tokens.erase(E->next());
+ E->get() = token;
+ }
+ }
+
+ if (tokens.size() == 1) {
+ list.push_back(out + tokens.front()->get());
+ } else {
+ ERR_FAIL_V_MSG(list, "CodeSign/Requirements: Invalid token sequence.");
+ }
+ }
+
+ return list;
+#undef _R
+}
+
+PackedByteArray CodeSignRequirements::get_hash_sha1() const {
+ PackedByteArray hash;
+ hash.resize(0x14);
+
+ CryptoCore::SHA1Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+PackedByteArray CodeSignRequirements::get_hash_sha256() const {
+ PackedByteArray hash;
+ hash.resize(0x20);
+
+ CryptoCore::SHA256Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+int CodeSignRequirements::get_size() const {
+ return blob.size();
+}
+
+void CodeSignRequirements::write_to_file(FileAccess *p_file) const {
+ ERR_FAIL_COND_MSG(!p_file, "CodeSign/Requirements: Invalid file handle.");
+ p_file->store_buffer(blob.ptr(), blob.size());
+}
+
+/*************************************************************************/
+/* CodeSignEntitlementsText */
+/*************************************************************************/
+
+CodeSignEntitlementsText::CodeSignEntitlementsText() {
+ blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic.
+ blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes).
+}
+
+CodeSignEntitlementsText::CodeSignEntitlementsText(const String &p_string) {
+ CharString utf8 = p_string.utf8();
+ blob.append_array({ 0xFA, 0xDE, 0x71, 0x71 }); // Text Entitlements set magic.
+ for (int i = 3; i >= 0; i--) {
+ uint8_t x = ((utf8.length() + 8) >> i * 8) & 0xFF; // Size.
+ blob.push_back(x);
+ }
+ for (int i = 0; i < utf8.length(); i++) { // Write data.
+ blob.push_back(utf8[i]);
+ }
+}
+
+PackedByteArray CodeSignEntitlementsText::get_hash_sha1() const {
+ PackedByteArray hash;
+ hash.resize(0x14);
+
+ CryptoCore::SHA1Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+PackedByteArray CodeSignEntitlementsText::get_hash_sha256() const {
+ PackedByteArray hash;
+ hash.resize(0x20);
+
+ CryptoCore::SHA256Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+int CodeSignEntitlementsText::get_size() const {
+ return blob.size();
+}
+
+void CodeSignEntitlementsText::write_to_file(FileAccess *p_file) const {
+ ERR_FAIL_COND_MSG(!p_file, "CodeSign/EntitlementsText: Invalid file handle.");
+ p_file->store_buffer(blob.ptr(), blob.size());
+}
+
+/*************************************************************************/
+/* CodeSignEntitlementsBinary */
+/*************************************************************************/
+
+CodeSignEntitlementsBinary::CodeSignEntitlementsBinary() {
+ blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic.
+ blob.append_array({ 0x00, 0x00, 0x00, 0x08 }); // Length (8 bytes).
+}
+
+CodeSignEntitlementsBinary::CodeSignEntitlementsBinary(const String &p_string) {
+ PList pl = PList(p_string);
+
+ PackedByteArray asn1 = pl.save_asn1();
+ blob.append_array({ 0xFA, 0xDE, 0x71, 0x72 }); // Binary Entitlements magic.
+ uint32_t size = asn1.size() + 8;
+ for (int i = 3; i >= 0; i--) {
+ uint8_t x = (size >> i * 8) & 0xFF; // Size.
+ blob.push_back(x);
+ }
+ blob.append_array(asn1); // Write data.
+}
+
+PackedByteArray CodeSignEntitlementsBinary::get_hash_sha1() const {
+ PackedByteArray hash;
+ hash.resize(0x14);
+
+ CryptoCore::SHA1Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+PackedByteArray CodeSignEntitlementsBinary::get_hash_sha256() const {
+ PackedByteArray hash;
+ hash.resize(0x20);
+
+ CryptoCore::SHA256Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+int CodeSignEntitlementsBinary::get_size() const {
+ return blob.size();
+}
+
+void CodeSignEntitlementsBinary::write_to_file(FileAccess *p_file) const {
+ ERR_FAIL_COND_MSG(!p_file, "CodeSign/EntitlementsBinary: Invalid file handle.");
+ p_file->store_buffer(blob.ptr(), blob.size());
+}
+
+/*************************************************************************/
+/* CodeSignCodeDirectory */
+/*************************************************************************/
+
+CodeSignCodeDirectory::CodeSignCodeDirectory() {
+ blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic.
+ blob.append_array({ 0x00, 0x00, 0x00, 0x00 }); // Size (8 bytes).
+}
+
+CodeSignCodeDirectory::CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash_type, bool p_main, const CharString &p_id, const CharString &p_team_id, uint32_t p_page_size, uint64_t p_exe_limit, uint64_t p_code_limit) {
+ pages = p_code_limit / (uint64_t(1) << p_page_size);
+ remain = p_code_limit % (uint64_t(1) << p_page_size);
+ code_slots = pages + (remain > 0 ? 1 : 0);
+ special_slots = 7;
+
+ int cd_size = 8 + sizeof(CodeDirectoryHeader) + (code_slots + special_slots) * p_hash_size + p_id.size() + p_team_id.size();
+ int cd_off = 8 + sizeof(CodeDirectoryHeader);
+ blob.append_array({ 0xFA, 0xDE, 0x0C, 0x02 }); // Code Directory magic.
+ for (int i = 3; i >= 0; i--) {
+ uint8_t x = (cd_size >> i * 8) & 0xFF; // Size.
+ blob.push_back(x);
+ }
+ blob.resize(cd_size);
+ memset(blob.ptrw() + 8, 0x00, cd_size - 8);
+ CodeDirectoryHeader *cd = (CodeDirectoryHeader *)(blob.ptrw() + 8);
+
+ bool is_64_cl = (p_code_limit >= std::numeric_limits<uint32_t>::max());
+
+ // Version and options.
+ cd->version = BSWAP32(0x20500);
+ cd->flags = BSWAP32(SIGNATURE_ADHOC | SIGNATURE_RUNTIME);
+ cd->special_slots = BSWAP32(special_slots);
+ cd->code_slots = BSWAP32(code_slots);
+ if (is_64_cl) {
+ cd->code_limit_64 = BSWAP64(p_code_limit);
+ } else {
+ cd->code_limit = BSWAP32(p_code_limit);
+ }
+ cd->hash_size = p_hash_size;
+ cd->hash_type = p_hash_type;
+ cd->page_size = p_page_size;
+ cd->exec_seg_base = 0x00;
+ cd->exec_seg_limit = BSWAP64(p_exe_limit);
+ cd->exec_seg_flags = 0;
+ if (p_main) {
+ cd->exec_seg_flags |= EXECSEG_MAIN_BINARY;
+ }
+ cd->exec_seg_flags = BSWAP64(cd->exec_seg_flags);
+ uint32_t version = (11 << 16) + (3 << 8) + 0; // Version 11.3.0
+ cd->runtime = BSWAP32(version);
+
+ // Copy ID.
+ cd->ident_offset = BSWAP32(cd_off);
+ memcpy(blob.ptrw() + cd_off, p_id.get_data(), p_id.size());
+ cd_off += p_id.size();
+
+ // Copy Team ID.
+ if (p_team_id.length() > 0) {
+ cd->team_offset = BSWAP32(cd_off);
+ memcpy(blob.ptrw() + cd_off, p_team_id.get_data(), p_team_id.size());
+ cd_off += p_team_id.size();
+ } else {
+ cd->team_offset = 0;
+ }
+
+ // Scatter vector.
+ cd->scatter_vector_offset = 0; // Not used.
+
+ // Executable hashes offset.
+ cd->hash_offset = BSWAP32(cd_off + special_slots * cd->hash_size);
+}
+
+bool CodeSignCodeDirectory::set_hash_in_slot(const PackedByteArray &p_hash, int p_slot) {
+ ERR_FAIL_COND_V_MSG((p_slot < -special_slots) || (p_slot >= code_slots), false, vformat("CodeSign/CodeDirectory: Invalid hash slot index: %d.", p_slot));
+ CodeDirectoryHeader *cd = reinterpret_cast<CodeDirectoryHeader *>(blob.ptrw() + 8);
+ for (int i = 0; i < cd->hash_size; i++) {
+ blob.write[BSWAP32(cd->hash_offset) + p_slot * cd->hash_size + i] = p_hash[i];
+ }
+ return true;
+}
+
+int32_t CodeSignCodeDirectory::get_page_count() {
+ return pages;
+}
+
+int32_t CodeSignCodeDirectory::get_page_remainder() {
+ return remain;
+}
+
+PackedByteArray CodeSignCodeDirectory::get_hash_sha1() const {
+ PackedByteArray hash;
+ hash.resize(0x14);
+
+ CryptoCore::SHA1Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+PackedByteArray CodeSignCodeDirectory::get_hash_sha256() const {
+ PackedByteArray hash;
+ hash.resize(0x20);
+
+ CryptoCore::SHA256Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+int CodeSignCodeDirectory::get_size() const {
+ return blob.size();
+}
+
+void CodeSignCodeDirectory::write_to_file(FileAccess *p_file) const {
+ ERR_FAIL_COND_MSG(!p_file, "CodeSign/CodeDirectory: Invalid file handle.");
+ p_file->store_buffer(blob.ptr(), blob.size());
+}
+
+/*************************************************************************/
+/* CodeSignSignature */
+/*************************************************************************/
+
+CodeSignSignature::CodeSignSignature() {
+ blob.append_array({ 0xFA, 0xDE, 0x0B, 0x01 }); // Signature magic.
+ uint32_t sign_size = 8; // Ad-hoc signature is empty.
+ for (int i = 3; i >= 0; i--) {
+ uint8_t x = (sign_size >> i * 8) & 0xFF; // Size.
+ blob.push_back(x);
+ }
+}
+
+PackedByteArray CodeSignSignature::get_hash_sha1() const {
+ PackedByteArray hash;
+ hash.resize(0x14);
+
+ CryptoCore::SHA1Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+PackedByteArray CodeSignSignature::get_hash_sha256() const {
+ PackedByteArray hash;
+ hash.resize(0x20);
+
+ CryptoCore::SHA256Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+}
+
+int CodeSignSignature::get_size() const {
+ return blob.size();
+}
+
+void CodeSignSignature::write_to_file(FileAccess *p_file) const {
+ ERR_FAIL_COND_MSG(!p_file, "CodeSign/Signature: Invalid file handle.");
+ p_file->store_buffer(blob.ptr(), blob.size());
+}
+
+/*************************************************************************/
+/* CodeSignSuperBlob */
+/*************************************************************************/
+
+bool CodeSignSuperBlob::add_blob(const Ref<CodeSignBlob> &p_blob) {
+ if (p_blob.is_valid()) {
+ blobs.push_back(p_blob);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+int CodeSignSuperBlob::get_size() const {
+ int size = 12 + blobs.size() * 8;
+ for (int i = 0; i < blobs.size(); i++) {
+ if (blobs[i].is_null()) {
+ return 0;
+ }
+ size += blobs[i]->get_size();
+ }
+ return size;
+}
+
+void CodeSignSuperBlob::write_to_file(FileAccess *p_file) const {
+ ERR_FAIL_COND_MSG(!p_file, "CodeSign/SuperBlob: Invalid file handle.");
+ uint32_t size = get_size();
+ uint32_t data_offset = 12 + blobs.size() * 8;
+
+ // Write header.
+ p_file->store_32(BSWAP32(0xfade0cc0));
+ p_file->store_32(BSWAP32(size));
+ p_file->store_32(BSWAP32(blobs.size()));
+
+ // Write index.
+ for (int i = 0; i < blobs.size(); i++) {
+ if (blobs[i].is_null()) {
+ return;
+ }
+ p_file->store_32(BSWAP32(blobs[i]->get_index_type()));
+ p_file->store_32(BSWAP32(data_offset));
+ data_offset += blobs[i]->get_size();
+ }
+
+ // Write blobs.
+ for (int i = 0; i < blobs.size(); i++) {
+ blobs[i]->write_to_file(p_file);
+ }
+}
+
+/*************************************************************************/
+/* CodeSign */
+/*************************************************************************/
+
+PackedByteArray CodeSign::file_hash_sha1(const String &p_path) {
+ PackedByteArray file_hash;
+ FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!f, PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path));
+
+ CryptoCore::SHA1Context ctx;
+ ctx.start();
+
+ unsigned char step[4096];
+ while (true) {
+ uint64_t br = f->get_buffer(step, 4096);
+ if (br > 0) {
+ ctx.update(step, br);
+ }
+ if (br < 4096) {
+ break;
+ }
+ }
+
+ file_hash.resize(0x14);
+ ctx.finish(file_hash.ptrw());
+ return file_hash;
+}
+
+PackedByteArray CodeSign::file_hash_sha256(const String &p_path) {
+ PackedByteArray file_hash;
+ FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!f, PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path));
+
+ CryptoCore::SHA256Context ctx;
+ ctx.start();
+
+ unsigned char step[4096];
+ while (true) {
+ uint64_t br = f->get_buffer(step, 4096);
+ if (br > 0) {
+ ctx.update(step, br);
+ }
+ if (br < 4096) {
+ break;
+ }
+ }
+
+ file_hash.resize(0x20);
+ ctx.finish(file_hash.ptrw());
+ return file_hash;
+}
+
+Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const String &p_info, const String &p_exe_path, const String &p_bundle_path, const String &p_ent_path, bool p_ios_bundle, String &r_error_msg) {
+#define CLEANUP() \
+ if (files_to_sign.size() > 1) { \
+ for (int j = 0; j < files_to_sign.size(); j++) { \
+ da->remove(files_to_sign[j]); \
+ } \
+ }
+
+ print_verbose(vformat("CodeSign: Signing executable: %s, bundle: %s with entitlements %s", p_exe_path, p_bundle_path, p_ent_path));
+
+ PackedByteArray info_hash1, info_hash2;
+ PackedByteArray res_hash1, res_hash2;
+ String id;
+ String main_exe = p_exe_path;
+
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (!da) {
+ r_error_msg = TTR("Can't get filesystem access.");
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access.");
+ }
+
+ // Read Info.plist.
+ if (!p_info.is_empty()) {
+ print_verbose(vformat("CodeSign: Reading bundle info..."));
+ PList info_plist;
+ if (info_plist.load_file(p_info)) {
+ info_hash1 = file_hash_sha1(p_info);
+ info_hash2 = file_hash_sha256(p_info);
+ if (info_hash1.is_empty() || info_hash2.is_empty()) {
+ r_error_msg = TTR("Failed to get Info.plist hash.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get Info.plist hash.");
+ }
+
+ if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) {
+ main_exe = p_exe_path.plus_file(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data()));
+ } else {
+ r_error_msg = TTR("Invalid Info.plist, no exe name.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no exe name.");
+ }
+
+ if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleIdentifier")) {
+ id = info_plist.get_root()->data_dict["CFBundleIdentifier"]->data_string.get_data();
+ } else {
+ r_error_msg = TTR("Invalid Info.plist, no bundle id.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no bundle id.");
+ }
+ } else {
+ r_error_msg = TTR("Invalid Info.plist, can't load.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, can't load.");
+ }
+ }
+
+ // Extract fat binary.
+ Vector<String> files_to_sign;
+ if (LipO::is_lipo(main_exe)) {
+ print_verbose(vformat("CodeSign: Executable is fat, extracting..."));
+ String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo");
+ Error err = da->make_dir_recursive(tmp_path_name);
+ if (err != OK) {
+ r_error_msg = vformat(TTR("Failed to create \"%s\" subfolder."), tmp_path_name);
+ ERR_FAIL_V_MSG(FAILED, vformat("CodeSign: Failed to create \"%s\" subfolder.", tmp_path_name));
+ }
+ LipO lip;
+ if (lip.open_file(main_exe)) {
+ for (int i = 0; i < lip.get_arch_count(); i++) {
+ if (!lip.extract_arch(i, tmp_path_name.plus_file("_exe_" + itos(i)))) {
+ CLEANUP();
+ r_error_msg = TTR("Failed to extract thin binary.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to extract thin binary.");
+ }
+ files_to_sign.push_back(tmp_path_name.plus_file("_exe_" + itos(i)));
+ }
+ }
+ } else if (MachO::is_macho(main_exe)) {
+ print_verbose(vformat("CodeSign: Executable is thin..."));
+ files_to_sign.push_back(main_exe);
+ } else {
+ r_error_msg = TTR("Invalid binary format.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid binary format.");
+ }
+
+ // Check if it's already signed.
+ if (!p_force) {
+ for (int i = 0; i < files_to_sign.size(); i++) {
+ MachO mh;
+ mh.open_file(files_to_sign[i]);
+ if (mh.is_signed()) {
+ CLEANUP();
+ r_error_msg = TTR("Already signed!");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Already signed!");
+ }
+ }
+ }
+
+ // Generate core resources.
+ if (!p_bundle_path.is_empty()) {
+ print_verbose(vformat("CodeSign: Generating bundle CodeResources..."));
+ CodeSignCodeResources cr;
+
+ if (p_ios_bundle) {
+ cr.add_rule1("^.*");
+ cr.add_rule1("^.*\\.lproj/", "optional", 100);
+ cr.add_rule1("^.*\\.lproj/locversion.plist$", "omit", 1100);
+ cr.add_rule1("^Base\\.lproj/", "", 1010);
+ cr.add_rule1("^version.plist$");
+
+ cr.add_rule2(".*\\.dSYM($|/)", "", 11);
+ cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000);
+ cr.add_rule2("^.*");
+ cr.add_rule2("^.*\\.lproj/", "optional", 1000);
+ cr.add_rule2("^.*\\.lproj/locversion.plist$", "omit", 1100);
+ cr.add_rule2("^Base\\.lproj/", "", 1010);
+ cr.add_rule2("^Info\\.plist$", "omit", 20);
+ cr.add_rule2("^PkgInfo$", "omit", 20);
+ cr.add_rule2("^embedded\\.provisionprofile$", "", 10);
+ cr.add_rule2("^version\\.plist$", "", 20);
+
+ cr.add_rule2("^_MASReceipt", "omit", 2000, false);
+ cr.add_rule2("^_CodeSignature", "omit", 2000, false);
+ cr.add_rule2("^CodeResources", "omit", 2000, false);
+ } else {
+ cr.add_rule1("^Resources/");
+ cr.add_rule1("^Resources/.*\\.lproj/", "optional", 1000);
+ cr.add_rule1("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100);
+ cr.add_rule1("^Resources/Base\\.lproj/", "", 1010);
+ cr.add_rule1("^version.plist$");
+
+ cr.add_rule2(".*\\.dSYM($|/)", "", 11);
+ cr.add_rule2("^(.*/)?\\.DS_Store$", "omit", 2000);
+ cr.add_rule2("^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/", "nested", 10);
+ cr.add_rule2("^.*");
+ cr.add_rule2("^Info\\.plist$", "omit", 20);
+ cr.add_rule2("^PkgInfo$", "omit", 20);
+ cr.add_rule2("^Resources/", "", 20);
+ cr.add_rule2("^Resources/.*\\.lproj/", "optional", 1000);
+ cr.add_rule2("^Resources/.*\\.lproj/locversion.plist$", "omit", 1100);
+ cr.add_rule2("^Resources/Base\\.lproj/", "", 1010);
+ cr.add_rule2("^[^/]+$", "nested", 10);
+ cr.add_rule2("^embedded\\.provisionprofile$", "", 10);
+ cr.add_rule2("^version\\.plist$", "", 20);
+ cr.add_rule2("^_MASReceipt", "omit", 2000, false);
+ cr.add_rule2("^_CodeSignature", "omit", 2000, false);
+ cr.add_rule2("^CodeResources", "omit", 2000, false);
+ }
+
+ if (!cr.add_folder_recursive(p_bundle_path, "", main_exe)) {
+ CLEANUP();
+ r_error_msg = TTR("Failed to process nested resources.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to process nested resources.");
+ }
+ Error err = da->make_dir_recursive(p_bundle_path.plus_file("_CodeSignature"));
+ if (err != OK) {
+ CLEANUP();
+ r_error_msg = TTR("Failed to create _CodeSignature subfolder.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create _CodeSignature subfolder.");
+ }
+ cr.save_to_file(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources"));
+ res_hash1 = file_hash_sha1(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources"));
+ res_hash2 = file_hash_sha256(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources"));
+ if (res_hash1.is_empty() || res_hash2.is_empty()) {
+ CLEANUP();
+ r_error_msg = TTR("Failed to get CodeResources hash.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to get CodeResources hash.");
+ }
+ }
+
+ // Generate common signature structures.
+ if (id.is_empty()) {
+ CryptoCore::RandomGenerator rng;
+ ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
+ uint8_t uuid[16];
+ Error err = rng.get_random_bytes(uuid, 16);
+ ERR_FAIL_COND_V_MSG(err, err, "Failed to generate UUID.");
+ id = (String("a-55554944") /*a-UUID*/ + String::hex_encode_buffer(uuid, 16));
+ }
+ CharString uuid_str = id.utf8();
+ print_verbose(vformat("CodeSign: Used bundle ID: %s", id));
+
+ print_verbose(vformat("CodeSign: Processing entitlements..."));
+
+ Ref<CodeSignEntitlementsText> cet;
+ Ref<CodeSignEntitlementsBinary> ceb;
+ if (!p_ent_path.is_empty()) {
+ String entitlements = FileAccess::get_file_as_string(p_ent_path);
+ if (entitlements.is_empty()) {
+ CLEANUP();
+ r_error_msg = TTR("Invalid entitlements file.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid entitlements file.");
+ }
+ cet = Ref<CodeSignEntitlementsText>(memnew(CodeSignEntitlementsText(entitlements)));
+ ceb = Ref<CodeSignEntitlementsBinary>(memnew(CodeSignEntitlementsBinary(entitlements)));
+ }
+
+ print_verbose(vformat("CodeSign: Generating requirements..."));
+ Ref<CodeSignRequirements> rq;
+ String team_id = "";
+ rq = Ref<CodeSignRequirements>(memnew(CodeSignRequirements()));
+
+ // Sign executables.
+ for (int i = 0; i < files_to_sign.size(); i++) {
+ MachO mh;
+ if (!mh.open_file(files_to_sign[i])) {
+ CLEANUP();
+ r_error_msg = TTR("Invalid executable file.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid executable file.");
+ }
+ print_verbose(vformat("CodeSign: Signing executable for cputype: %d ...", mh.get_cputype()));
+
+ print_verbose(vformat("CodeSign: Generating CodeDirectory..."));
+ Ref<CodeSignCodeDirectory> cd1 = memnew(CodeSignCodeDirectory(0x14, 0x01, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit()));
+ Ref<CodeSignCodeDirectory> cd2 = memnew(CodeSignCodeDirectory(0x20, 0x02, true, uuid_str, team_id.utf8(), 12, mh.get_exe_limit(), mh.get_code_limit()));
+ print_verbose(vformat("CodeSign: Calculating special slot hashes..."));
+ if (info_hash2.size() == 0x20) {
+ cd2->set_hash_in_slot(info_hash2, CodeSignCodeDirectory::SLOT_INFO_PLIST);
+ }
+ if (info_hash1.size() == 0x14) {
+ cd1->set_hash_in_slot(info_hash1, CodeSignCodeDirectory::SLOT_INFO_PLIST);
+ }
+ cd1->set_hash_in_slot(rq->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS);
+ cd2->set_hash_in_slot(rq->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_REQUIREMENTS);
+ if (res_hash2.size() == 0x20) {
+ cd2->set_hash_in_slot(res_hash2, CodeSignCodeDirectory::SLOT_RESOURCES);
+ }
+ if (res_hash1.size() == 0x14) {
+ cd1->set_hash_in_slot(res_hash1, CodeSignCodeDirectory::SLOT_RESOURCES);
+ }
+ if (cet.is_valid()) {
+ cd1->set_hash_in_slot(cet->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS); //Text variant.
+ cd2->set_hash_in_slot(cet->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_ENTITLEMENTS);
+ }
+ if (ceb.is_valid()) {
+ cd1->set_hash_in_slot(ceb->get_hash_sha1(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS); //ASN.1 variant.
+ cd2->set_hash_in_slot(ceb->get_hash_sha256(), CodeSignCodeDirectory::Slot::SLOT_DER_ENTITLEMENTS);
+ }
+
+ // Calculate signature size.
+ int sign_size = 12; // SuperBlob header.
+ sign_size += cd1->get_size() + 8;
+ sign_size += cd2->get_size() + 8;
+ sign_size += rq->get_size() + 8;
+ if (cet.is_valid()) {
+ sign_size += cet->get_size() + 8;
+ }
+ if (ceb.is_valid()) {
+ sign_size += ceb->get_size() + 8;
+ }
+ sign_size += 16; // Empty signature size.
+
+ // Alloc/resize signature load command.
+ print_verbose(vformat("CodeSign: Reallocating space for the signature superblob (%d)...", sign_size));
+ if (!mh.set_signature_size(sign_size)) {
+ CLEANUP();
+ r_error_msg = TTR("Can't resize signature load command.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Can't resize signature load command.");
+ }
+
+ print_verbose(vformat("CodeSign: Calculating executable code hashes..."));
+ // Calculate executable code hashes.
+ PackedByteArray buffer;
+ PackedByteArray hash1, hash2;
+ hash1.resize(0x14);
+ hash2.resize(0x20);
+ buffer.resize(1 << 12);
+ mh.get_file()->seek(0);
+ for (int32_t j = 0; j < cd2->get_page_count(); j++) {
+ mh.get_file()->get_buffer(buffer.ptrw(), (1 << 12));
+ CryptoCore::SHA256Context ctx2;
+ ctx2.start();
+ ctx2.update(buffer.ptr(), (1 << 12));
+ ctx2.finish(hash2.ptrw());
+ cd2->set_hash_in_slot(hash2, j);
+
+ CryptoCore::SHA1Context ctx1;
+ ctx1.start();
+ ctx1.update(buffer.ptr(), (1 << 12));
+ ctx1.finish(hash1.ptrw());
+ cd1->set_hash_in_slot(hash1, j);
+ }
+ if (cd2->get_page_remainder() > 0) {
+ mh.get_file()->get_buffer(buffer.ptrw(), cd2->get_page_remainder());
+ CryptoCore::SHA256Context ctx2;
+ ctx2.start();
+ ctx2.update(buffer.ptr(), cd2->get_page_remainder());
+ ctx2.finish(hash2.ptrw());
+ cd2->set_hash_in_slot(hash2, cd2->get_page_count());
+
+ CryptoCore::SHA1Context ctx1;
+ ctx1.start();
+ ctx1.update(buffer.ptr(), cd1->get_page_remainder());
+ ctx1.finish(hash1.ptrw());
+ cd1->set_hash_in_slot(hash1, cd1->get_page_count());
+ }
+
+ print_verbose(vformat("CodeSign: Generating signature..."));
+ Ref<CodeSignSignature> cs;
+ cs = Ref<CodeSignSignature>(memnew(CodeSignSignature()));
+
+ print_verbose(vformat("CodeSign: Writing signature superblob..."));
+ // Write signature data to the executable.
+ CodeSignSuperBlob sb = CodeSignSuperBlob();
+ sb.add_blob(cd2);
+ sb.add_blob(cd1);
+ sb.add_blob(rq);
+ if (cet.is_valid()) {
+ sb.add_blob(cet);
+ }
+ if (ceb.is_valid()) {
+ sb.add_blob(ceb);
+ }
+ sb.add_blob(cs);
+ mh.get_file()->seek(mh.get_signature_offset());
+ sb.write_to_file(mh.get_file());
+ }
+ if (files_to_sign.size() > 1) {
+ print_verbose(vformat("CodeSign: Rebuilding fat executable..."));
+ LipO lip;
+ if (!lip.create_file(main_exe, files_to_sign)) {
+ CLEANUP();
+ r_error_msg = TTR("Failed to create fat binary.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create fat binary.");
+ }
+ CLEANUP();
+ }
+ FileAccess::set_unix_permissions(main_exe, 0755); // Restore unix permissions.
+ return OK;
+#undef CLEANUP
+}
+
+Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg) {
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (!da) {
+ r_error_msg = TTR("Can't get filesystem access.");
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access.");
+ }
+
+ if (da->dir_exists(p_path)) {
+ String fmw_ver = "Current"; // Framework version (default).
+ String info_path;
+ String main_exe;
+ String bundle_path;
+ bool bundle = false;
+ bool ios_bundle = false;
+ if (da->file_exists(p_path.plus_file("Contents/Info.plist"))) {
+ info_path = p_path.plus_file("Contents/Info.plist");
+ main_exe = p_path.plus_file("Contents/MacOS");
+ bundle_path = p_path.plus_file("Contents");
+ bundle = true;
+ } else if (da->file_exists(p_path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) {
+ info_path = p_path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver));
+ main_exe = p_path.plus_file(vformat("Versions/%s", fmw_ver));
+ bundle_path = p_path.plus_file(vformat("Versions/%s", fmw_ver));
+ bundle = true;
+ } else if (da->file_exists(p_path.plus_file("Info.plist"))) {
+ info_path = p_path.plus_file("Info.plist");
+ main_exe = p_path;
+ bundle_path = p_path;
+ bundle = true;
+ ios_bundle = true;
+ }
+ if (bundle) {
+ return _codesign_file(p_use_hardened_runtime, p_force, info_path, main_exe, bundle_path, p_ent_path, ios_bundle, r_error_msg);
+ } else {
+ r_error_msg = TTR("Unknown bundle type.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown bundle type.");
+ }
+ } else if (da->file_exists(p_path)) {
+ return _codesign_file(p_use_hardened_runtime, p_force, "", p_path, "", p_ent_path, false, r_error_msg);
+ } else {
+ r_error_msg = TTR("Unknown object type.");
+ ERR_FAIL_V_MSG(FAILED, "CodeSign: Unknown object type.");
+ }
+}
+
+#endif // MODULE_REGEX_ENABLED
diff --git a/platform/osx/export/codesign.h b/platform/osx/export/codesign.h
new file mode 100644
index 0000000000..e5e9be5c28
--- /dev/null
+++ b/platform/osx/export/codesign.h
@@ -0,0 +1,368 @@
+/*************************************************************************/
+/* codesign.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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. */
+/*************************************************************************/
+
+// macOS code signature creation utility.
+//
+// Current implementation has the following limitation:
+// - Only version 11.3.0 signatures are supported.
+// - Only "framework" and "app" bundle types are supported.
+// - Page hash array scattering is not supported.
+// - Reading and writing binary property lists i snot supported (third-party frameworks with binary Info.plist will not work unless .plist is converted to text format).
+// - Requirements code generator is not implemented (only hard-coded requirements for the ad-hoc signing is supported).
+// - RFC5652/CMS blob generation is not implemented, supports ad-hoc signing only.
+
+#ifndef CODESIGN_H
+#define CODESIGN_H
+
+#include "core/crypto/crypto_core.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/object/ref_counted.h"
+
+#include "modules/modules_enabled.gen.h" // For regex.
+#ifdef MODULE_REGEX_ENABLED
+#include "modules/regex/regex.h"
+#endif
+
+#include "plist.h"
+
+#ifdef MODULE_REGEX_ENABLED
+
+/*************************************************************************/
+/* CodeSignCodeResources */
+/*************************************************************************/
+
+class CodeSignCodeResources {
+public:
+ enum class CRMatch {
+ CR_MATCH_NO,
+ CR_MATCH_YES,
+ CR_MATCH_NESTED,
+ CR_MATCH_OPTIONAL,
+ };
+
+private:
+ struct CRFile {
+ String name;
+ String hash;
+ String hash2;
+ bool optional;
+ bool nested;
+ String requirements;
+ };
+
+ struct CRRule {
+ String file_pattern;
+ String key;
+ int weight;
+ bool store;
+ CRRule() {
+ weight = 1;
+ store = true;
+ }
+ CRRule(const String &p_file_pattern, const String &p_key, int p_weight, bool p_store) {
+ file_pattern = p_file_pattern;
+ key = p_key;
+ weight = p_weight;
+ store = p_store;
+ }
+ };
+
+ Vector<CRRule> rules1;
+ Vector<CRRule> rules2;
+
+ Vector<CRFile> files1;
+ Vector<CRFile> files2;
+
+ String hash_sha1_base64(const String &p_path);
+ String hash_sha256_base64(const String &p_path);
+
+public:
+ void add_rule1(const String &p_rule, const String &p_key = "", int p_weight = 0, bool p_store = true);
+ void add_rule2(const String &p_rule, const String &p_key = "", int p_weight = 0, bool p_store = true);
+
+ CRMatch match_rules1(const String &p_path) const;
+ CRMatch match_rules2(const String &p_path) const;
+
+ bool add_file1(const String &p_root, const String &p_path);
+ bool add_file2(const String &p_root, const String &p_path);
+ bool add_nested_file(const String &p_root, const String &p_path, const String &p_exepath);
+
+ bool add_folder_recursive(const String &p_root, const String &p_path = "", const String &p_main_exe_path = "");
+
+ bool save_to_file(const String &p_path);
+};
+
+/*************************************************************************/
+/* CodeSignBlob */
+/*************************************************************************/
+
+class CodeSignBlob : public RefCounted {
+public:
+ virtual PackedByteArray get_hash_sha1() const = 0;
+ virtual PackedByteArray get_hash_sha256() const = 0;
+
+ virtual int get_size() const = 0;
+ virtual uint32_t get_index_type() const = 0;
+
+ virtual void write_to_file(FileAccess *p_file) const = 0;
+};
+
+/*************************************************************************/
+/* CodeSignRequirements */
+/*************************************************************************/
+
+// Note: Proper code generator is not implemented (any we probably won't ever need it), just a hardcoded bytecode for the limited set of cases.
+
+class CodeSignRequirements : public CodeSignBlob {
+ PackedByteArray blob;
+
+ static inline size_t PAD(size_t s, size_t a) {
+ return (s % a == 0) ? 0 : (a - s % a);
+ }
+
+ _FORCE_INLINE_ void _parse_certificate_slot(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
+ _FORCE_INLINE_ void _parse_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
+ _FORCE_INLINE_ void _parse_oid_key(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
+ _FORCE_INLINE_ void _parse_hash_string(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
+ _FORCE_INLINE_ void _parse_value(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
+ _FORCE_INLINE_ void _parse_date(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
+ _FORCE_INLINE_ bool _parse_match(uint32_t &r_pos, String &r_out, uint32_t p_rq_size) const;
+
+public:
+ CodeSignRequirements();
+ CodeSignRequirements(const PackedByteArray &p_data);
+
+ Vector<String> parse_requirements() const;
+
+ virtual PackedByteArray get_hash_sha1() const override;
+ virtual PackedByteArray get_hash_sha256() const override;
+
+ virtual int get_size() const override;
+
+ virtual uint32_t get_index_type() const override { return 0x00000002; };
+ virtual void write_to_file(FileAccess *p_file) const override;
+};
+
+/*************************************************************************/
+/* CodeSignEntitlementsText */
+/*************************************************************************/
+
+// PList formatted entitlements.
+
+class CodeSignEntitlementsText : public CodeSignBlob {
+ PackedByteArray blob;
+
+public:
+ CodeSignEntitlementsText();
+ CodeSignEntitlementsText(const String &p_string);
+
+ virtual PackedByteArray get_hash_sha1() const override;
+ virtual PackedByteArray get_hash_sha256() const override;
+
+ virtual int get_size() const override;
+
+ virtual uint32_t get_index_type() const override { return 0x00000005; };
+ virtual void write_to_file(FileAccess *p_file) const override;
+};
+
+/*************************************************************************/
+/* CodeSignEntitlementsBinary */
+/*************************************************************************/
+
+// ASN.1 serialized entitlements.
+
+class CodeSignEntitlementsBinary : public CodeSignBlob {
+ PackedByteArray blob;
+
+public:
+ CodeSignEntitlementsBinary();
+ CodeSignEntitlementsBinary(const String &p_string);
+
+ virtual PackedByteArray get_hash_sha1() const override;
+ virtual PackedByteArray get_hash_sha256() const override;
+
+ virtual int get_size() const override;
+
+ virtual uint32_t get_index_type() const override { return 0x00000007; };
+ virtual void write_to_file(FileAccess *p_file) const override;
+};
+
+/*************************************************************************/
+/* CodeSignCodeDirectory */
+/*************************************************************************/
+
+// Code Directory, runtime options, code segment and special structure hashes.
+
+class CodeSignCodeDirectory : public CodeSignBlob {
+public:
+ enum Slot {
+ SLOT_INFO_PLIST = -1,
+ SLOT_REQUIREMENTS = -2,
+ SLOT_RESOURCES = -3,
+ SLOT_APP_SPECIFIC = -4, // Unused.
+ SLOT_ENTITLEMENTS = -5,
+ SLOT_RESERVER1 = -6, // Unused.
+ SLOT_DER_ENTITLEMENTS = -7,
+ };
+
+ enum CodeSignExecSegFlags {
+ EXECSEG_MAIN_BINARY = 0x1,
+ EXECSEG_ALLOW_UNSIGNED = 0x10,
+ EXECSEG_DEBUGGER = 0x20,
+ EXECSEG_JIT = 0x40,
+ EXECSEG_SKIP_LV = 0x80,
+ EXECSEG_CAN_LOAD_CDHASH = 0x100,
+ EXECSEG_CAN_EXEC_CDHASH = 0x200,
+ };
+
+ enum CodeSignatureFlags {
+ SIGNATURE_HOST = 0x0001,
+ SIGNATURE_ADHOC = 0x0002,
+ SIGNATURE_TASK_ALLOW = 0x0004,
+ SIGNATURE_INSTALLER = 0x0008,
+ SIGNATURE_FORCED_LV = 0x0010,
+ SIGNATURE_INVALID_ALLOWED = 0x0020,
+ SIGNATURE_FORCE_HARD = 0x0100,
+ SIGNATURE_FORCE_KILL = 0x0200,
+ SIGNATURE_FORCE_EXPIRATION = 0x0400,
+ SIGNATURE_RESTRICT = 0x0800,
+ SIGNATURE_ENFORCEMENT = 0x1000,
+ SIGNATURE_LIBRARY_VALIDATION = 0x2000,
+ SIGNATURE_ENTITLEMENTS_VALIDATED = 0x4000,
+ SIGNATURE_NVRAM_UNRESTRICTED = 0x8000,
+ SIGNATURE_RUNTIME = 0x10000,
+ SIGNATURE_LINKER_SIGNED = 0x20000,
+ };
+
+private:
+ PackedByteArray blob;
+
+ struct CodeDirectoryHeader {
+ uint32_t version; // Using version 0x0020500.
+ uint32_t flags; // // Option flags.
+ uint32_t hash_offset; // Slot zero offset.
+ uint32_t ident_offset; // Identifier string offset.
+ uint32_t special_slots; // Nr. of slots with negative index.
+ uint32_t code_slots; // Nr. of slots with index >= 0, (code_limit / page_size).
+ uint32_t code_limit; // Everything before code signature load command offset.
+ uint8_t hash_size; // 20 (SHA-1) or 32 (SHA-256).
+ uint8_t hash_type; // 1 (SHA-1) or 2 (SHA-256).
+ uint8_t platform; // Not used.
+ uint8_t page_size; // Page size, power of two, 2^12 (4096).
+ uint32_t spare2; // Not used.
+ // Version 0x20100
+ uint32_t scatter_vector_offset; // Set to 0 and ignore.
+ // Version 0x20200
+ uint32_t team_offset; // Team id string offset.
+ // Version 0x20300
+ uint32_t spare3; // Not used.
+ uint64_t code_limit_64; // Set to 0 and ignore.
+ // Version 0x20400
+ uint64_t exec_seg_base; // Start of the signed code segmet.
+ uint64_t exec_seg_limit; // Code segment (__TEXT) vmsize.
+ uint64_t exec_seg_flags; // Executable segment flags.
+ // Version 0x20500
+ uint32_t runtime; // Runtime version.
+ uint32_t pre_encrypt_offset; // Set to 0 and ignore.
+ };
+
+ int32_t pages = 0;
+ int32_t remain = 0;
+ int32_t code_slots = 0;
+ int32_t special_slots = 0;
+
+public:
+ CodeSignCodeDirectory();
+ CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash_type, bool p_main, const CharString &p_id, const CharString &p_team_id, uint32_t p_page_size, uint64_t p_exe_limit, uint64_t p_code_limit);
+
+ int32_t get_page_count();
+ int32_t get_page_remainder();
+
+ bool set_hash_in_slot(const PackedByteArray &p_hash, int p_slot);
+
+ virtual PackedByteArray get_hash_sha1() const override;
+ virtual PackedByteArray get_hash_sha256() const override;
+
+ virtual int get_size() const override;
+ virtual uint32_t get_index_type() const override { return 0x00000000; };
+
+ virtual void write_to_file(FileAccess *p_file) const override;
+};
+
+/*************************************************************************/
+/* CodeSignSignature */
+/*************************************************************************/
+
+class CodeSignSignature : public CodeSignBlob {
+ PackedByteArray blob;
+
+public:
+ CodeSignSignature();
+
+ virtual PackedByteArray get_hash_sha1() const override;
+ virtual PackedByteArray get_hash_sha256() const override;
+
+ virtual int get_size() const override;
+ virtual uint32_t get_index_type() const override { return 0x00010000; };
+
+ virtual void write_to_file(FileAccess *p_file) const override;
+};
+
+/*************************************************************************/
+/* CodeSignSuperBlob */
+/*************************************************************************/
+
+class CodeSignSuperBlob {
+ Vector<Ref<CodeSignBlob>> blobs;
+
+public:
+ bool add_blob(const Ref<CodeSignBlob> &p_blob);
+
+ int get_size() const;
+ void write_to_file(FileAccess *p_file) const;
+};
+
+/*************************************************************************/
+/* CodeSign */
+/*************************************************************************/
+
+class CodeSign {
+ static PackedByteArray file_hash_sha1(const String &p_path);
+ static PackedByteArray file_hash_sha256(const String &p_path);
+ static Error _codesign_file(bool p_use_hardened_runtime, bool p_force, const String &p_info, const String &p_exe_path, const String &p_bundle_path, const String &p_ent_path, bool p_ios_bundle, String &r_error_msg);
+
+public:
+ static Error codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg);
+};
+
+#endif // MODULE_REGEX_ENABLED
+
+#endif // CODESIGN_H
diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp
index afc0d63338..bd35b39e9e 100644
--- a/platform/osx/export/export.cpp
+++ b/platform/osx/export/export.cpp
@@ -33,6 +33,9 @@
#include "export_plugin.h"
void register_osx_exporter() {
+ EDITOR_DEF("export/macos/force_builtin_codesign", false);
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::BOOL, "export/macos/force_builtin_codesign", PROPERTY_HINT_NONE));
+
Ref<EditorExportPlatformOSX> platform;
platform.instantiate();
diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp
index ab50144303..4f06342fac 100644
--- a/platform/osx/export/export_plugin.cpp
+++ b/platform/osx/export/export_plugin.cpp
@@ -30,6 +30,13 @@
#include "export_plugin.h"
+#include "codesign.h"
+
+#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
+
+#include "modules/modules_enabled.gen.h" // For regex.
+
void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
if (p_preset->get("texture_format/s3tc")) {
r_features->push_back("s3tc");
@@ -44,12 +51,28 @@ void EditorExportPlatformOSX::get_preset_features(const Ref<EditorExportPreset>
r_features->push_back("64");
}
+bool EditorExportPlatformOSX::get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
+ // These options are not supported by built-in codesign, used on non macOS host.
+ if (!OS::get_singleton()->has_feature("macos")) {
+ if (p_option == "codesign/identity" || p_option == "codesign/timestamp" || p_option == "codesign/hardened_runtime" || p_option == "codesign/custom_options" || p_option.begins_with("notarization/")) {
+ return false;
+ }
+ }
+
+ // These entitlements are required to run managed code, and are always enabled in Mono builds.
+ if (Engine::get_singleton()->has_singleton("GodotSharp")) {
+ if (p_option == "codesign/entitlements/allow_jit_code_execution" || p_option == "codesign/entitlements/allow_unsigned_executable_memory" || p_option == "codesign/entitlements/allow_dyld_environment_variables") {
+ return false;
+ }
+ }
+ return true;
+}
+
void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) {
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
- 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::INT, "debug/export_console_script", PROPERTY_HINT_ENUM, "No,Debug Only,Debug and Release"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
@@ -57,24 +80,41 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), ""));
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::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/location_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/address_book_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/calendar_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photos_library_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/desktop_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/documents_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/downloads_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/network_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/removable_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
-#ifdef OSX_ENABLED
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::BOOL, "codesign/replace_existing_signature"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
- if (!Engine::get_singleton()->has_singleton("GodotSharp")) {
- // These entitlements are required to run managed code, and are always enabled in Mono builds.
- 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/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));
@@ -103,7 +143,6 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), ""));
-#endif
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false));
@@ -288,9 +327,7 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
if (lines[i].find("$binary") != -1) {
strnew += lines[i].replace("$binary", p_binary) + "\n";
} else if (lines[i].find("$name") != -1) {
- strnew += lines[i].replace("$name", p_binary) + "\n";
- } else if (lines[i].find("$info") != -1) {
- strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n";
+ strnew += lines[i].replace("$name", ProjectSettings::get_singleton()->get("application/config/name")) + "\n";
} else if (lines[i].find("$bundle_identifier") != -1) {
strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n";
} else if (lines[i].find("$short_version") != -1) {
@@ -305,13 +342,56 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
} else if (lines[i].find("$copyright") != -1) {
strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n";
} else if (lines[i].find("$highres") != -1) {
- strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "<true/>" : "<false/>") + "\n";
- } else if (lines[i].find("$camera_usage_description") != -1) {
- String description = p_preset->get("privacy/camera_usage_description");
- strnew += lines[i].replace("$camera_usage_description", description) + "\n";
- } else if (lines[i].find("$microphone_usage_description") != -1) {
- String description = p_preset->get("privacy/microphone_usage_description");
- strnew += lines[i].replace("$microphone_usage_description", description) + "\n";
+ strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n";
+ } else if (lines[i].find("$usage_descriptions") != -1) {
+ String descriptions;
+ if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSMicrophoneUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/microphone_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/camera_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSCameraUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/camera_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/location_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSLocationUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/location_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSContactsUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/address_book_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSCalendarsUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/calendar_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSPhotoLibraryUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/photos_library_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/desktop_folder_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSDesktopFolderUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/desktop_folder_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/documents_folder_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSDocumentsFolderUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/documents_folder_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/downloads_folder_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSDownloadsFolderUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/downloads_folder_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/network_volumes_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSNetworkVolumesUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/network_volumes_usage_description") + "</string>\n";
+ }
+ if (!((String)p_preset->get("privacy/removable_volumes_usage_description")).is_empty()) {
+ descriptions += "\t<key>NSRemovableVolumesUsageDescription</key>\n";
+ descriptions += "\t<string>" + (String)p_preset->get("privacy/removable_volumes_usage_description") + "</string>\n";
+ }
+ if (!descriptions.is_empty()) {
+ strnew += lines[i].replace("$usage_descriptions", descriptions);
+ }
} else {
strnew += lines[i] + "\n";
}
@@ -362,14 +442,16 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset
Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true);
ERR_FAIL_COND_V(err != OK, err);
- print_line("altool (" + p_path + "):\n" + str);
+ print_verbose("altool (" + p_path + "):\n" + str);
if (str.find("RequestUUID") == -1) {
EditorNode::add_io_error("altool: " + str);
return FAILED;
} else {
- print_line("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.");
- print_line(" You can check progress manually by opening a Terminal and running the following command:");
- print_line(" \"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
+ print_line(TTR("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."));
+ print_line(" " + TTR("You can check progress manually by opening a Terminal and running the following command:"));
+ print_line(" \"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
+ print_line(" " + TTR("Run the following command to staple the notarization ticket to the exported application (optional):"));
+ print_line(" \"xcrun stapler staple <app path>\"");
}
#endif
@@ -377,62 +459,99 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset
return OK;
}
-Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) {
+Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn) {
+ bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign");
+ bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
+
+ if ((!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) || force_builtin_codesign) {
+ print_verbose("using built-in codesign...");
+#ifdef MODULE_REGEX_ENABLED
+
#ifdef OSX_ENABLED
- List<String> args;
+ if (p_preset->get("codesign/timestamp") && p_warn) {
+ WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!");
+ }
+ if (p_preset->get("codesign/hardened_runtime") && p_warn) {
+ WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!");
+ }
+#endif
- if (p_preset->get("codesign/timestamp")) {
- args.push_back("--timestamp");
- }
- if (p_preset->get("codesign/hardened_runtime")) {
- args.push_back("--options");
- args.push_back("runtime");
- }
+ String error_msg;
+ Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg);
+ if (err != OK) {
+ EditorNode::add_io_error("Built-in CodeSign: " + error_msg);
+ return FAILED;
+ }
+#else
+ ERR_FAIL_V_MSG(FAILED, "Built-in CodeSign require regex module");
+#endif
+ return OK;
+ } else {
+ print_verbose("using external codesign...");
+ List<String> args;
+ if (p_preset->get("codesign/timestamp")) {
+ if (ad_hoc) {
+ if (p_warn) {
+ WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!");
+ }
+ } else {
+ args.push_back("--timestamp");
+ }
+ }
+ if (p_preset->get("codesign/hardened_runtime")) {
+ if (ad_hoc) {
+ if (p_warn) {
+ WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!");
+ }
+ } else {
+ args.push_back("--options");
+ args.push_back("runtime");
+ }
+ }
- if (p_path.get_extension() != "dmg") {
- args.push_back("--entitlements");
- args.push_back(p_ent_path);
- }
+ if (p_path.get_extension() != "dmg") {
+ args.push_back("--entitlements");
+ args.push_back(p_ent_path);
+ }
- PackedStringArray user_args = p_preset->get("codesign/custom_options");
- for (int i = 0; i < user_args.size(); i++) {
- String user_arg = user_args[i].strip_edges();
- if (!user_arg.is_empty()) {
- args.push_back(user_arg);
+ PackedStringArray user_args = p_preset->get("codesign/custom_options");
+ for (int i = 0; i < user_args.size(); i++) {
+ String user_arg = user_args[i].strip_edges();
+ if (!user_arg.is_empty()) {
+ args.push_back(user_arg);
+ }
}
- }
- args.push_back("-s");
- if (p_preset->get("codesign/identity") == "") {
- args.push_back("-");
- } else {
- args.push_back(p_preset->get("codesign/identity"));
- }
+ args.push_back("-s");
+ if (ad_hoc) {
+ args.push_back("-");
+ } else {
+ args.push_back(p_preset->get("codesign/identity"));
+ }
- args.push_back("-v"); /* provide some more feedback */
+ args.push_back("-v"); /* provide some more feedback */
- if (p_preset->get("codesign/replace_existing_signature")) {
- args.push_back("-f");
- }
+ if (p_preset->get("codesign/replace_existing_signature")) {
+ args.push_back("-f");
+ }
- args.push_back(p_path);
+ args.push_back(p_path);
- String str;
- Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
- ERR_FAIL_COND_V(err != OK, err);
+ String str;
+ Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
+ ERR_FAIL_COND_V(err != OK, err);
- print_line("codesign (" + p_path + "):\n" + str);
- if (str.find("no identity found") != -1) {
- EditorNode::add_io_error("codesign: no identity found");
- return FAILED;
- }
- if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) {
- EditorNode::add_io_error("codesign: invalid entitlements file");
- return FAILED;
+ print_verbose("codesign (" + p_path + "):\n" + str);
+ if (str.find("no identity found") != -1) {
+ EditorNode::add_io_error("CodeSign: " + TTR("No identity found."));
+ return FAILED;
+ }
+ if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) {
+ EditorNode::add_io_error("CodeSign: " + TTR("Invalid entitlements file."));
+ return FAILED;
+ }
+ return OK;
}
-#endif
-
- return OK;
}
Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
@@ -463,7 +582,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset
}
if (extensions_to_sign.find(current_file.get_extension()) > -1) {
- Error code_sign_error{ _code_sign(p_preset, current_file_path, p_ent_path) };
+ Error code_sign_error{ _code_sign(p_preset, current_file_path, p_ent_path, false) };
if (code_sign_error != OK) {
return code_sign_error;
}
@@ -507,7 +626,7 @@ Error EditorExportPlatformOSX::_copy_and_sign_files(DirAccessRef &dir_access, co
// If it is a directory, find and sign all dynamic libraries.
err = _code_sign_directory(p_preset, p_in_app_path, p_ent_path, p_should_error_on_non_code_sign);
} else {
- err = _code_sign(p_preset, p_in_app_path, p_ent_path);
+ err = _code_sign(p_preset, p_in_app_path, p_ent_path, false);
}
}
return err;
@@ -550,12 +669,12 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
Error err = OS::get_singleton()->execute("hdiutil", args, &str, nullptr, true);
ERR_FAIL_COND_V(err != OK, err);
- print_line("hdiutil returned: " + str);
+ print_verbose("hdiutil returned: " + str);
if (str.find("create failed") != -1) {
if (str.find("File exists") != -1) {
- EditorNode::add_io_error("hdiutil: create failed - file exists");
+ EditorNode::add_io_error("hdiutil: " + TTR("DMG creation failed, file already exists."));
} else {
- EditorNode::add_io_error("hdiutil: create failed");
+ EditorNode::add_io_error("hdiutil: " + TTR("DMG create failed."));
}
return FAILED;
}
@@ -563,6 +682,19 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
return OK;
}
+Error EditorExportPlatformOSX::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
+ FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
+
+ f->store_line("#!/bin/sh");
+ f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'");
+ f->store_line("function realpath() { python -c \"import os,sys; print(os.path.realpath(sys.argv[1]))\" \"$0\"; }");
+ f->store_line("base_path=\"$(dirname \"$(realpath \"$0\")\")\"");
+ f->store_line("\"$base_path/" + p_pkg_name + "\" \"$@\"");
+
+ return OK;
+}
+
Error EditorExportPlatformOSX::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);
@@ -592,13 +724,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
- if (ep.step("Creating app", 0)) {
+ if (ep.step(TTR("Creating app bundle"), 0)) {
return ERR_SKIP;
}
unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
if (!src_pkg_zip) {
- EditorNode::add_io_error("Could not find template app to export:\n" + src_pkg_name);
+ EditorNode::add_io_error(TTR("Could not find template app to export:") + "\n" + src_pkg_name);
return ERR_FILE_NOT_FOUND;
}
@@ -607,9 +739,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
String binary_to_use = "godot_osx_" + String(p_debug ? "debug" : "release") + ".64";
String pkg_name;
- if (p_preset->get("application/name") != "") {
- pkg_name = p_preset->get("application/name"); // app_name
- } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
+ if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name"));
} else {
pkg_name = "Unnamed";
@@ -617,30 +747,59 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name);
- String export_format = use_dmg() && p_path.ends_with("dmg") ? "dmg" : "zip";
+ String export_format;
+ if (use_dmg() && p_path.ends_with("dmg")) {
+ export_format = "dmg";
+ } else if (p_path.ends_with("zip")) {
+ export_format = "zip";
+ } else if (p_path.ends_with("app")) {
+ export_format = "app";
+ } else {
+ EditorNode::add_io_error("Invalid export format");
+ return ERR_CANT_CREATE;
+ }
// Create our application bundle.
String tmp_app_dir_name = pkg_name + ".app";
- 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);
+ String tmp_base_path_name;
+ String tmp_app_path_name;
+ String scr_path;
+ if (export_format == "app") {
+ tmp_base_path_name = p_path.get_base_dir();
+ tmp_app_path_name = p_path;
+ scr_path = p_path.get_basename() + ".command";
+ } else {
+ tmp_base_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name);
+ tmp_app_path_name = tmp_base_path_name.plus_file(tmp_app_dir_name);
+ scr_path = tmp_base_path_name.plus_file(pkg_name + ".command");
+ }
+
+ print_verbose("Exporting to " + tmp_app_path_name);
Error err = OK;
- DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_app_path_name);
+ DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name);
if (!tmp_app_dir) {
err = ERR_CANT_CREATE;
}
+ DirAccess::remove_file_or_error(scr_path);
+ if (DirAccess::exists(tmp_app_path_name)) {
+ if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) {
+ tmp_app_dir->erase_contents_recursive();
+ }
+ }
+
Array helpers = p_preset->get("codesign/entitlements/app_sandbox/helper_executables");
// Create our folder structure.
if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/MacOS");
+ print_verbose("Creating " + tmp_app_path_name + "/Contents/MacOS");
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/MacOS");
}
if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/Frameworks");
+ print_verbose("Creating " + tmp_app_path_name + "/Contents/Frameworks");
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks");
}
@@ -650,10 +809,121 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
if (err == OK) {
- print_line("Creating " + tmp_app_path_name + "/Contents/Resources");
+ print_verbose("Creating " + tmp_app_path_name + "/Contents/Resources");
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
}
+ Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized");
+ Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized");
+ Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized");
+ Dictionary location_usage_descriptions = p_preset->get("privacy/location_usage_description_localized");
+ Dictionary address_book_usage_descriptions = p_preset->get("privacy/address_book_usage_description_localized");
+ Dictionary calendar_usage_descriptions = p_preset->get("privacy/calendar_usage_description_localized");
+ Dictionary photos_library_usage_descriptions = p_preset->get("privacy/photos_library_usage_description_localized");
+ Dictionary desktop_folder_usage_descriptions = p_preset->get("privacy/desktop_folder_usage_description_localized");
+ Dictionary documents_folder_usage_descriptions = p_preset->get("privacy/documents_folder_usage_description_localized");
+ Dictionary downloads_folder_usage_descriptions = p_preset->get("privacy/downloads_folder_usage_description_localized");
+ Dictionary network_volumes_usage_descriptions = p_preset->get("privacy/network_volumes_usage_description_localized");
+ Dictionary removable_volumes_usage_descriptions = p_preset->get("privacy/removable_volumes_usage_description_localized");
+ Dictionary copyrights = p_preset->get("application/copyright_localized");
+
+ Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations");
+ if (translations.size() > 0) {
+ {
+ String fname = tmp_app_path_name + "/Contents/Resources/en.lproj";
+ tmp_app_dir->make_dir_recursive(fname);
+ FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ f->store_line("/* Localized versions of Info.plist keys */");
+ f->store_line("");
+ f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";");
+ if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) {
+ f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/camera_usage_description")).is_empty()) {
+ f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/location_usage_description")).is_empty()) {
+ f->store_line("NSLocationUsageDescription = \"" + p_preset->get("privacy/location_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) {
+ f->store_line("NSContactsUsageDescription = \"" + p_preset->get("privacy/address_book_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) {
+ f->store_line("NSCalendarsUsageDescription = \"" + p_preset->get("privacy/calendar_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) {
+ f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photos_library_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/desktop_folder_usage_description")).is_empty()) {
+ f->store_line("NSDesktopFolderUsageDescription = \"" + p_preset->get("privacy/desktop_folder_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/documents_folder_usage_description")).is_empty()) {
+ f->store_line("NSDocumentsFolderUsageDescription = \"" + p_preset->get("privacy/documents_folder_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/downloads_folder_usage_description")).is_empty()) {
+ f->store_line("NSDownloadsFolderUsageDescription = \"" + p_preset->get("privacy/downloads_folder_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/network_volumes_usage_description")).is_empty()) {
+ f->store_line("NSNetworkVolumesUsageDescription = \"" + p_preset->get("privacy/network_volumes_usage_description").operator String() + "\";");
+ }
+ if (!((String)p_preset->get("privacy/removable_volumes_usage_description")).is_empty()) {
+ f->store_line("NSRemovableVolumesUsageDescription = \"" + p_preset->get("privacy/removable_volumes_usage_description").operator String() + "\";");
+ }
+ f->store_line("NSHumanReadableCopyright = \"" + p_preset->get("application/copyright").operator String() + "\";");
+ }
+
+ for (const String &E : translations) {
+ Ref<Translation> tr = ResourceLoader::load(E);
+ if (tr.is_valid()) {
+ String lang = tr->get_locale();
+ String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj";
+ tmp_app_dir->make_dir_recursive(fname);
+ FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ f->store_line("/* Localized versions of Info.plist keys */");
+ f->store_line("");
+ if (appnames.has(lang)) {
+ f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
+ }
+ if (microphone_usage_descriptions.has(lang)) {
+ f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (camera_usage_descriptions.has(lang)) {
+ f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (location_usage_descriptions.has(lang)) {
+ f->store_line("NSLocationUsageDescription = \"" + location_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (address_book_usage_descriptions.has(lang)) {
+ f->store_line("NSContactsUsageDescription = \"" + address_book_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (calendar_usage_descriptions.has(lang)) {
+ f->store_line("NSCalendarsUsageDescription = \"" + calendar_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (photos_library_usage_descriptions.has(lang)) {
+ f->store_line("NSPhotoLibraryUsageDescription = \"" + photos_library_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (desktop_folder_usage_descriptions.has(lang)) {
+ f->store_line("NSDesktopFolderUsageDescription = \"" + desktop_folder_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (documents_folder_usage_descriptions.has(lang)) {
+ f->store_line("NSDocumentsFolderUsageDescription = \"" + documents_folder_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (downloads_folder_usage_descriptions.has(lang)) {
+ f->store_line("NSDownloadsFolderUsageDescription = \"" + downloads_folder_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (network_volumes_usage_descriptions.has(lang)) {
+ f->store_line("NSNetworkVolumesUsageDescription = \"" + network_volumes_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (removable_volumes_usage_descriptions.has(lang)) {
+ f->store_line("NSRemovableVolumesUsageDescription = \"" + removable_volumes_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (copyrights.has(lang)) {
+ f->store_line("NSHumanReadableCopyright = \"" + copyrights[lang].operator String() + "\";");
+ }
+ }
+ }
+ }
+
// Now process our template.
bool found_binary = false;
Vector<String> dylibs_found;
@@ -679,6 +949,25 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
// Write.
file = file.replace_first("osx_template.app/", "");
+ if (((info.external_fa >> 16L) & 0120000) == 0120000) {
+#ifndef UNIX_ENABLED
+ WARN_PRINT(vformat("Relative symlinks are not supported on this OS, the exported project might be broken!"));
+#endif
+ // Handle symlinks in the archive.
+ file = tmp_app_path_name.plus_file(file);
+ if (err == OK) {
+ err = tmp_app_dir->make_dir_recursive(file.get_base_dir());
+ }
+ if (err == OK) {
+ String lnk_data = String::utf8((const char *)data.ptr(), data.size());
+ err = tmp_app_dir->create_link(lnk_data, file);
+ print_verbose(vformat("ADDING SYMLINK %s => %s\n", file, lnk_data));
+ }
+
+ ret = unzGoToNextFile(src_pkg_zip);
+ continue; // next
+ }
+
if (file == "Contents/Info.plist") {
_fix_plist(p_preset, data, pkg_name);
}
@@ -742,7 +1031,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
dylibs_found.push_back(file);
}
- print_line("ADDING: " + file + " size: " + itos(data.size()));
+ print_verbose("ADDING: " + file + " size: " + itos(data.size()));
// Write it into our application bundle.
file = tmp_app_path_name.plus_file(file);
@@ -772,18 +1061,27 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
unzClose(src_pkg_zip);
if (!found_binary) {
- ERR_PRINT("Requested template binary '" + binary_to_use + "' not found. It might be missing from your template archive.");
+ ERR_PRINT(vformat("Requested template binary '%s' not found. It might be missing from your template archive.", binary_to_use));
err = ERR_FILE_NOT_FOUND;
}
+ // Save console script.
if (err == OK) {
- if (ep.step("Making PKG", 1)) {
+ int con_scr = p_preset->get("debug/export_console_script");
+ if ((con_scr == 1 && p_debug) || (con_scr == 2)) {
+ err = _export_debug_script(p_preset, pkg_name, tmp_app_path_name.get_file() + "/Contents/MacOS/" + pkg_name, scr_path);
+ FileAccess::set_unix_permissions(scr_path, 0755);
+ }
+ }
+
+ if (err == OK) {
+ if (ep.step(TTR("Making PKG"), 1)) {
return ERR_SKIP;
}
String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck";
Vector<SharedObject> shared_objects;
- err = save_pack(p_preset, pack_path, &shared_objects);
+ err = save_pack(p_preset, p_debug, pack_path, &shared_objects);
// See if we can code sign our new package.
bool sign_enabled = p_preset->get("codesign/enable");
@@ -950,17 +1248,38 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
String hlp_path = helpers[i];
err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file());
if (err == OK && sign_enabled) {
- err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path);
+ err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path, false);
}
FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755);
}
}
+
+ bool ad_hoc = true;
+ if (err == OK) {
+#ifdef OSX_ENABLED
+ String sign_identity = p_preset->get("codesign/identity");
+#else
+ String sign_identity = "-";
+#endif
+ ad_hoc = (sign_identity == "" || sign_identity == "-");
+ bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation");
+ if ((!dylibs_found.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) {
+ ERR_PRINT("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries.");
+ err = ERR_CANT_CREATE;
+ }
+ }
+
if (err == OK) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
for (int i = 0; i < shared_objects.size(); i++) {
String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path);
- String path_in_app{ tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file() };
- err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, true);
+ if (shared_objects[i].target.is_empty()) {
+ String path_in_app = tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file();
+ err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, true);
+ } else {
+ String path_in_app = tmp_app_path_name.plus_file(shared_objects[i].target).plus_file(src_path.get_file());
+ err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, false);
+ }
if (err != OK) {
break;
}
@@ -978,37 +1297,37 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
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);
+ err = _code_sign(p_preset, tmp_app_path_name + "/" + dylibs_found[i], ent_path, false);
}
}
}
if (err == OK && sign_enabled) {
- if (ep.step("Code signing bundle", 2)) {
+ if (ep.step(TTR("Code signing bundle"), 2)) {
return ERR_SKIP;
}
- err = _code_sign(p_preset, tmp_app_path_name + "/Contents/MacOS/" + pkg_name, ent_path);
+ err = _code_sign(p_preset, tmp_app_path_name, ent_path);
}
if (export_format == "dmg") {
// Create a DMG.
if (err == OK) {
- if (ep.step("Making DMG", 3)) {
+ if (ep.step(TTR("Making DMG"), 3)) {
return ERR_SKIP;
}
- err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
+ err = _create_dmg(p_path, pkg_name, tmp_base_path_name);
}
// Sign DMG.
- if (err == OK && sign_enabled) {
- if (ep.step("Code signing DMG", 3)) {
+ if (err == OK && sign_enabled && !ad_hoc) {
+ if (ep.step(TTR("Code signing DMG"), 3)) {
return ERR_SKIP;
}
- err = _code_sign(p_preset, p_path, ent_path);
+ err = _code_sign(p_preset, p_path, ent_path, false);
}
- } else {
+ } else if (export_format == "zip") {
// Create ZIP.
if (err == OK) {
- if (ep.step("Making ZIP", 3)) {
+ if (ep.step(TTR("Making ZIP"), 3)) {
return ERR_SKIP;
}
if (FileAccess::exists(p_path)) {
@@ -1019,35 +1338,47 @@ 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, EditorPaths::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name);
+ _zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name);
zipClose(zip, nullptr);
}
}
+#ifdef OSX_ENABLED
bool noto_enabled = p_preset->get("notarization/enable");
if (err == OK && noto_enabled) {
- if (ep.step("Sending archive for notarization", 4)) {
- return ERR_SKIP;
+ if (export_format == "app") {
+ WARN_PRINT("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.");
+ } else {
+ if (ep.step(TTR("Sending archive for notarization"), 4)) {
+ return ERR_SKIP;
+ }
+ err = _notarize(p_preset, p_path);
}
- err = _notarize(p_preset, p_path);
}
+#endif
// Clean up temporary entitlements files.
DirAccess::remove_file_or_error(hlp_ent_path);
- // Clean up temporary .app dir.
- tmp_app_dir->change_dir(tmp_app_path_name);
- tmp_app_dir->erase_contents_recursive();
- tmp_app_dir->change_dir("..");
- tmp_app_dir->remove(tmp_app_dir_name);
+ // Clean up temporary .app dir and generated entitlements.
+ if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") {
+ tmp_app_dir->remove(ent_path);
+ }
+ if (export_format != "app") {
+ if (tmp_app_dir->change_dir(tmp_base_path_name) == OK) {
+ tmp_app_dir->erase_contents_recursive();
+ tmp_app_dir->change_dir("..");
+ tmp_app_dir->remove(pkg_name);
+ }
+ }
}
return err;
}
void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) {
- String dir = p_root_path.plus_file(p_folder);
+ String dir = p_folder.is_empty() ? p_root_path : p_root_path.plus_file(p_folder);
DirAccessRef da = DirAccess::open(dir);
da->list_dir_begin();
@@ -1101,7 +1432,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String
} 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)) || p_folder.ends_with("Helpers");
+ bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers") || f.ends_with(".command");
OS::Time time = OS::get_singleton()->get_time();
OS::Date date = OS::get_singleton()->get_date();
@@ -1142,7 +1473,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String
FileAccessRef fa = FileAccess::open(dir.plus_file(f), FileAccess::READ);
if (!fa) {
- ERR_FAIL_MSG("Can't open file to read from path '" + String(dir.plus_file(f)) + "'.");
+ ERR_FAIL_MSG(vformat("Can't open file to read from path \"%s\".", dir.plus_file(f)));
}
const int bufsize = 16384;
uint8_t buf[bufsize];
@@ -1166,10 +1497,9 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
String err;
bool valid = false;
- // Look for export templates (first official, and if defined custom templates).
-
- bool dvalid = exists_export_template("osx.zip", &err);
- bool rvalid = dvalid; // Both in the same ZIP.
+ // Look for export templates (custom templates).
+ bool dvalid = false;
+ bool rvalid = false;
if (p_preset->get("custom_template/debug") != "") {
dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
@@ -1184,6 +1514,12 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
}
}
+ // Look for export templates (official templates, check only is custom templates are not set).
+ if (!dvalid || !rvalid) {
+ dvalid = exists_export_template("osx.zip", &err);
+ rvalid = dvalid; // Both in the same ZIP.
+ }
+
valid = dvalid || rvalid;
r_missing_templates = !valid;
@@ -1195,15 +1531,33 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
}
bool sign_enabled = p_preset->get("codesign/enable");
+
+#ifdef OSX_ENABLED
bool noto_enabled = p_preset->get("notarization/enable");
+ bool ad_hoc = ((p_preset->get("codesign/identity") == "") || (p_preset->get("codesign/identity") == "-"));
+
+ if (!ad_hoc && (bool)EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign")) {
+ err += TTR("Warning: Built-in \"codesign\" is selected in the Editor Settings. Code signing is limited to ad-hoc signature only.") + "\n";
+ }
+ if (!ad_hoc && !FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
+ err += TTR("Warning: Xcode command line tools are not installed, using built-in \"codesign\". Code signing is limited to ad-hoc signature only.") + "\n";
+ }
+
if (noto_enabled) {
+ if (ad_hoc) {
+ err += TTR("Notarization: Notarization with an ad-hoc signature is not supported.") + "\n";
+ valid = false;
+ }
if (!sign_enabled) {
- err += TTR("Notarization: code signing required.") + "\n";
+ err += TTR("Notarization: Code signing is required for notarization.") + "\n";
+ valid = false;
+ }
+ if (!(bool)p_preset->get("codesign/hardened_runtime")) {
+ err += TTR("Notarization: Hardened runtime is required for notarization.") + "\n";
valid = false;
}
- bool hr_enabled = p_preset->get("codesign/hardened_runtime");
- if (!hr_enabled) {
- err += TTR("Notarization: hardened runtime required.") + "\n";
+ if (!(bool)p_preset->get("codesign/timestamp")) {
+ err += TTR("Notarization: Timestamping is required for notarization.") + "\n";
valid = false;
}
if (p_preset->get("notarization/apple_id_name") == "") {
@@ -1214,6 +1568,51 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
err += TTR("Notarization: Apple ID password not specified.") + "\n";
valid = false;
}
+ } else {
+ err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n";
+ if (!sign_enabled) {
+ err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
+ } else {
+ if ((bool)p_preset->get("codesign/hardened_runtime") && ad_hoc) {
+ err += TTR("Hardened Runtime is not compatible with ad-hoc signature, and will be disabled!") + "\n";
+ }
+ if ((bool)p_preset->get("codesign/timestamp") && ad_hoc) {
+ err += TTR("Timestamping is not compatible with ad-hoc signature, and will be disabled!") + "\n";
+ }
+ }
+ }
+#else
+ err += TTR("Warning: Notarization is not supported from this OS. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n";
+ if (!sign_enabled) {
+ err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
+ }
+#endif
+
+ if (sign_enabled) {
+ if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) {
+ err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n";
+ valid = false;
+ }
+ if ((bool)p_preset->get("codesign/entitlements/camera") && ((String)p_preset->get("privacy/camera_usage_description")).is_empty()) {
+ err += TTR("Privacy: Camera access is enabled, but usage description is not specified.") + "\n";
+ valid = false;
+ }
+ if ((bool)p_preset->get("codesign/entitlements/location") && ((String)p_preset->get("privacy/location_usage_description")).is_empty()) {
+ err += TTR("Privacy: Location information access is enabled, but usage description is not specified.") + "\n";
+ valid = false;
+ }
+ if ((bool)p_preset->get("codesign/entitlements/address_book") && ((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) {
+ err += TTR("Privacy: Address book access is enabled, but usage description is not specified.") + "\n";
+ valid = false;
+ }
+ if ((bool)p_preset->get("codesign/entitlements/calendars") && ((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) {
+ err += TTR("Privacy: Calendar access is enabled, but usage description is not specified.") + "\n";
+ valid = false;
+ }
+ if ((bool)p_preset->get("codesign/entitlements/photos_library") && ((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) {
+ err += TTR("Privacy: Photo library access is enabled, but usage description is not specified.") + "\n";
+ valid = false;
+ }
}
if (!err.is_empty()) {
diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h
index aa22ad6384..b3edfb7f90 100644
--- a/platform/osx/export/export_plugin.h
+++ b/platform/osx/export/export_plugin.h
@@ -40,7 +40,6 @@
#include "core/os/os.h"
#include "core/version.h"
#include "editor/editor_export.h"
-#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "platform/osx/logo.gen.h"
@@ -57,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, const String &p_ent_path);
+ Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true);
Error _code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_should_error_on_non_code = true);
Error _copy_and_sign_files(DirAccessRef &dir_access, const String &p_src_path, const String &p_in_app_path,
bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path,
@@ -67,14 +66,15 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
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);
+ Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);
-#ifdef OSX_ENABLED
bool use_codesign() const { return true; }
+#ifdef OSX_ENABLED
bool use_dmg() const { return true; }
#else
- bool use_codesign() const { return false; }
bool use_dmg() const { return false; }
#endif
+
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
String pname = p_package;
@@ -87,7 +87,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
for (int i = 0; i < pname.length(); i++) {
char32_t c = pname[i];
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
+ if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
}
@@ -101,6 +101,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
virtual void get_export_options(List<ExportOption> *r_options) override;
+ virtual bool get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
public:
virtual String get_name() const override { return "macOS"; }
@@ -113,6 +114,7 @@ public:
list.push_back("dmg");
}
list.push_back("zip");
+ list.push_back("app");
return list;
}
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
diff --git a/platform/osx/export/lipo.cpp b/platform/osx/export/lipo.cpp
new file mode 100644
index 0000000000..66d2ecdbcf
--- /dev/null
+++ b/platform/osx/export/lipo.cpp
@@ -0,0 +1,243 @@
+/*************************************************************************/
+/* lipo.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex.
+
+#include "lipo.h"
+
+#ifdef MODULE_REGEX_ENABLED
+
+bool LipO::is_lipo(const String &p_path) {
+ FileAccessRef fb = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!fb, false, vformat("LipO: Can't open file: \"%s\".", p_path));
+ uint32_t magic = fb->get_32();
+ return (magic == 0xbebafeca || magic == 0xcafebabe || magic == 0xbfbafeca || magic == 0xcafebabf);
+}
+
+bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_files) {
+ close();
+
+ fa = FileAccess::open(p_output_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(!fa, false, vformat("LipO: Can't open file: \"%s\".", p_output_path));
+
+ uint64_t max_size = 0;
+ for (int i = 0; i < p_files.size(); i++) {
+ MachO mh;
+ if (!mh.open_file(p_files[i])) {
+ ERR_FAIL_V_MSG(false, vformat("LipO: Invalid MachO file: \"%s.\"", p_files[i]));
+ }
+
+ FatArch arch;
+ arch.cputype = mh.get_cputype();
+ arch.cpusubtype = mh.get_cpusubtype();
+ arch.offset = 0;
+ arch.size = mh.get_size();
+ arch.align = mh.get_align();
+ max_size += arch.size;
+
+ archs.push_back(arch);
+
+ FileAccessRef fb = FileAccess::open(p_files[i], FileAccess::READ);
+ if (!fb) {
+ close();
+ ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i]));
+ }
+ }
+
+ // Write header.
+ bool is_64 = (max_size >= std::numeric_limits<uint32_t>::max());
+ if (is_64) {
+ fa->store_32(0xbfbafeca);
+ } else {
+ fa->store_32(0xbebafeca);
+ }
+ fa->store_32(BSWAP32(archs.size()));
+ uint64_t offset = archs.size() * (is_64 ? 32 : 20) + 8;
+ for (int i = 0; i < archs.size(); i++) {
+ archs.write[i].offset = offset + PAD(offset, uint64_t(1) << archs[i].align);
+ if (is_64) {
+ fa->store_32(BSWAP32(archs[i].cputype));
+ fa->store_32(BSWAP32(archs[i].cpusubtype));
+ fa->store_64(BSWAP64(archs[i].offset));
+ fa->store_64(BSWAP64(archs[i].size));
+ fa->store_32(BSWAP32(archs[i].align));
+ fa->store_32(0);
+ } else {
+ fa->store_32(BSWAP32(archs[i].cputype));
+ fa->store_32(BSWAP32(archs[i].cpusubtype));
+ fa->store_32(BSWAP32(archs[i].offset));
+ fa->store_32(BSWAP32(archs[i].size));
+ fa->store_32(BSWAP32(archs[i].align));
+ }
+ offset = archs[i].offset + archs[i].size;
+ }
+
+ // Write files and padding.
+ for (int i = 0; i < archs.size(); i++) {
+ FileAccessRef fb = FileAccess::open(p_files[i], FileAccess::READ);
+ if (!fb) {
+ close();
+ ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i]));
+ }
+ uint64_t cur = fa->get_position();
+ for (uint64_t j = cur; j < archs[i].offset; j++) {
+ fa->store_8(0);
+ }
+ int pages = archs[i].size / 4096;
+ int remain = archs[i].size % 4096;
+ unsigned char step[4096];
+ for (int j = 0; j < pages; j++) {
+ uint64_t br = fb->get_buffer(step, 4096);
+ if (br > 0) {
+ fa->store_buffer(step, br);
+ }
+ }
+ uint64_t br = fb->get_buffer(step, remain);
+ if (br > 0) {
+ fa->store_buffer(step, br);
+ }
+ fb->close();
+ }
+ return true;
+}
+
+bool LipO::open_file(const String &p_path) {
+ close();
+
+ fa = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!fa, false, vformat("LipO: Can't open file: \"%s\".", p_path));
+
+ uint32_t magic = fa->get_32();
+ if (magic == 0xbebafeca) {
+ // 32-bit fat binary, bswap.
+ uint32_t nfat_arch = BSWAP32(fa->get_32());
+ for (uint32_t i = 0; i < nfat_arch; i++) {
+ FatArch arch;
+ arch.cputype = BSWAP32(fa->get_32());
+ arch.cpusubtype = BSWAP32(fa->get_32());
+ arch.offset = BSWAP32(fa->get_32());
+ arch.size = BSWAP32(fa->get_32());
+ arch.align = BSWAP32(fa->get_32());
+
+ archs.push_back(arch);
+ }
+ } else if (magic == 0xcafebabe) {
+ // 32-bit fat binary.
+ uint32_t nfat_arch = fa->get_32();
+ for (uint32_t i = 0; i < nfat_arch; i++) {
+ FatArch arch;
+ arch.cputype = fa->get_32();
+ arch.cpusubtype = fa->get_32();
+ arch.offset = fa->get_32();
+ arch.size = fa->get_32();
+ arch.align = fa->get_32();
+
+ archs.push_back(arch);
+ }
+ } else if (magic == 0xbfbafeca) {
+ // 64-bit fat binary, bswap.
+ uint32_t nfat_arch = BSWAP32(fa->get_32());
+ for (uint32_t i = 0; i < nfat_arch; i++) {
+ FatArch arch;
+ arch.cputype = BSWAP32(fa->get_32());
+ arch.cpusubtype = BSWAP32(fa->get_32());
+ arch.offset = BSWAP64(fa->get_64());
+ arch.size = BSWAP64(fa->get_64());
+ arch.align = BSWAP32(fa->get_32());
+ fa->get_32(); // Skip, reserved.
+
+ archs.push_back(arch);
+ }
+ } else if (magic == 0xcafebabf) {
+ // 64-bit fat binary.
+ uint32_t nfat_arch = fa->get_32();
+ for (uint32_t i = 0; i < nfat_arch; i++) {
+ FatArch arch;
+ arch.cputype = fa->get_32();
+ arch.cpusubtype = fa->get_32();
+ arch.offset = fa->get_64();
+ arch.size = fa->get_64();
+ arch.align = fa->get_32();
+ fa->get_32(); // Skip, reserved.
+
+ archs.push_back(arch);
+ }
+ } else {
+ close();
+ ERR_FAIL_V_MSG(false, vformat("LipO: Invalid fat binary: \"%s\".", p_path));
+ }
+ return true;
+}
+
+int LipO::get_arch_count() const {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "LipO: File not opened.");
+ return archs.size();
+}
+
+bool LipO::extract_arch(int p_index, const String &p_path) {
+ ERR_FAIL_COND_V_MSG(!fa, false, "LipO: File not opened.");
+ ERR_FAIL_INDEX_V(p_index, archs.size(), false);
+
+ FileAccessRef fb = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(!fb, false, vformat("LipO: Can't open file: \"%s\".", p_path));
+
+ fa->seek(archs[p_index].offset);
+
+ int pages = archs[p_index].size / 4096;
+ int remain = archs[p_index].size % 4096;
+ unsigned char step[4096];
+ for (int i = 0; i < pages; i++) {
+ uint64_t br = fa->get_buffer(step, 4096);
+ if (br > 0) {
+ fb->store_buffer(step, br);
+ }
+ }
+ uint64_t br = fa->get_buffer(step, remain);
+ if (br > 0) {
+ fb->store_buffer(step, br);
+ }
+ fb->close();
+ return true;
+}
+
+void LipO::close() {
+ if (fa) {
+ fa->close();
+ memdelete(fa);
+ fa = nullptr;
+ }
+ archs.clear();
+}
+
+LipO::~LipO() {
+ close();
+}
+
+#endif // MODULE_REGEX_ENABLED
diff --git a/platform/osx/export/lipo.h b/platform/osx/export/lipo.h
new file mode 100644
index 0000000000..68bbe42dd6
--- /dev/null
+++ b/platform/osx/export/lipo.h
@@ -0,0 +1,76 @@
+/*************************************************************************/
+/* lipo.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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. */
+/*************************************************************************/
+
+// Universal / Universal 2 fat binary file creator and extractor.
+
+#ifndef LIPO_H
+#define LIPO_H
+
+#include "core/io/file_access.h"
+#include "core/object/ref_counted.h"
+#include "modules/modules_enabled.gen.h" // For regex.
+
+#include "macho.h"
+
+#ifdef MODULE_REGEX_ENABLED
+
+class LipO : public RefCounted {
+ struct FatArch {
+ uint32_t cputype;
+ uint32_t cpusubtype;
+ uint64_t offset;
+ uint64_t size;
+ uint32_t align;
+ };
+
+ FileAccess *fa = nullptr;
+ Vector<FatArch> archs;
+
+ static inline size_t PAD(size_t s, size_t a) {
+ return (a - s % a);
+ }
+
+public:
+ static bool is_lipo(const String &p_path);
+
+ bool create_file(const String &p_output_path, const PackedStringArray &p_files);
+
+ bool open_file(const String &p_path);
+ int get_arch_count() const;
+ bool extract_arch(int p_index, const String &p_path);
+
+ void close();
+
+ ~LipO();
+};
+
+#endif // MODULE_REGEX_ENABLED
+
+#endif // LIPO_H
diff --git a/platform/osx/export/macho.cpp b/platform/osx/export/macho.cpp
new file mode 100644
index 0000000000..08f2a855b0
--- /dev/null
+++ b/platform/osx/export/macho.cpp
@@ -0,0 +1,556 @@
+/*************************************************************************/
+/* macho.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex.
+
+#include "macho.h"
+
+#ifdef MODULE_REGEX_ENABLED
+
+uint32_t MachO::seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max) {
+ uint32_t align = p_max;
+ if (p_vmaddr != 0) {
+ uint64_t seg_align = 1;
+ align = 0;
+ while ((seg_align & p_vmaddr) == 0) {
+ seg_align = seg_align << 1;
+ align++;
+ }
+ align = CLAMP(align, p_min, p_max);
+ }
+ return align;
+}
+
+bool MachO::alloc_signature(uint64_t p_size) {
+ ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
+ if (signature_offset != 0) {
+ // Nothing to do, already have signature load command.
+ return true;
+ }
+ if (lc_limit == 0 || lc_limit + 16 > exe_base) {
+ ERR_FAIL_V_MSG(false, "MachO: Can't allocate signature load command, please use \"codesign_allocate\" utility first.");
+ } else {
+ // Add signature load command.
+ signature_offset = lc_limit;
+
+ fa->seek(lc_limit);
+ LoadCommandHeader lc;
+ lc.cmd = LC_CODE_SIGNATURE;
+ lc.cmdsize = 16;
+ if (swap) {
+ lc.cmdsize = BSWAP32(lc.cmdsize);
+ }
+ fa->store_buffer((const uint8_t *)&lc, sizeof(LoadCommandHeader));
+
+ uint32_t lc_offset = fa->get_length() + PAD(fa->get_length(), 16);
+ uint32_t lc_size = 0;
+ if (swap) {
+ lc_offset = BSWAP32(lc_offset);
+ lc_size = BSWAP32(lc_size);
+ }
+ fa->store_32(lc_offset);
+ fa->store_32(lc_size);
+
+ // Write new command number.
+ fa->seek(0x10);
+ uint32_t ncmds = fa->get_32();
+ uint32_t cmdssize = fa->get_32();
+ if (swap) {
+ ncmds = BSWAP32(ncmds);
+ cmdssize = BSWAP32(cmdssize);
+ }
+ ncmds += 1;
+ cmdssize += 16;
+ if (swap) {
+ ncmds = BSWAP32(ncmds);
+ cmdssize = BSWAP32(cmdssize);
+ }
+ fa->seek(0x10);
+ fa->store_32(ncmds);
+ fa->store_32(cmdssize);
+
+ lc_limit = lc_limit + sizeof(LoadCommandHeader) + 8;
+
+ return true;
+ }
+}
+
+bool MachO::is_macho(const String &p_path) {
+ FileAccessRef fb = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(!fb, false, vformat("MachO: Can't open file: \"%s\".", p_path));
+ uint32_t magic = fb->get_32();
+ return (magic == 0xcefaedfe || magic == 0xfeedface || magic == 0xcffaedfe || magic == 0xfeedfacf);
+}
+
+bool MachO::open_file(const String &p_path) {
+ fa = FileAccess::open(p_path, FileAccess::READ_WRITE);
+ ERR_FAIL_COND_V_MSG(!fa, false, vformat("MachO: Can't open file: \"%s\".", p_path));
+ uint32_t magic = fa->get_32();
+ MachHeader mach_header;
+
+ // Read MachO header.
+ swap = (magic == 0xcffaedfe || magic == 0xcefaedfe);
+ if (magic == 0xcefaedfe || magic == 0xfeedface) {
+ // Thin 32-bit binary.
+ fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader));
+ } else if (magic == 0xcffaedfe || magic == 0xfeedfacf) {
+ // Thin 64-bit binary.
+ fa->get_buffer((uint8_t *)&mach_header, sizeof(MachHeader));
+ fa->get_32(); // Skip extra reserved field.
+ } else {
+ ERR_FAIL_V_MSG(false, vformat("MachO: File is not a valid MachO binary: \"%s\".", p_path));
+ }
+
+ if (swap) {
+ mach_header.ncmds = BSWAP32(mach_header.ncmds);
+ mach_header.cpusubtype = BSWAP32(mach_header.cpusubtype);
+ mach_header.cputype = BSWAP32(mach_header.cputype);
+ }
+ cpusubtype = mach_header.cpusubtype;
+ cputype = mach_header.cputype;
+ align = 0;
+ exe_base = std::numeric_limits<uint64_t>::max();
+ exe_limit = 0;
+ lc_limit = 0;
+ link_edit_offset = 0;
+ signature_offset = 0;
+
+ // Read load commands.
+ for (uint32_t i = 0; i < mach_header.ncmds; i++) {
+ LoadCommandHeader lc;
+ fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader));
+ if (swap) {
+ lc.cmd = BSWAP32(lc.cmd);
+ lc.cmdsize = BSWAP32(lc.cmdsize);
+ }
+ uint64_t ps = fa->get_position();
+ switch (lc.cmd) {
+ case LC_SEGMENT: {
+ LoadCommandSegment lc_seg;
+ fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment));
+ if (swap) {
+ lc_seg.nsects = BSWAP32(lc_seg.nsects);
+ lc_seg.vmaddr = BSWAP32(lc_seg.vmaddr);
+ lc_seg.vmsize = BSWAP32(lc_seg.vmsize);
+ }
+ align = MAX(align, seg_align(lc_seg.vmaddr, 2, 15));
+ if (String(lc_seg.segname) == "__TEXT") {
+ exe_limit = MAX(exe_limit, lc_seg.vmsize);
+ for (uint32_t j = 0; j < lc_seg.nsects; j++) {
+ Section lc_sect;
+ fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section));
+ if (String(lc_sect.sectname) == "__text") {
+ if (swap) {
+ exe_base = MIN(exe_base, BSWAP32(lc_sect.offset));
+ } else {
+ exe_base = MIN(exe_base, lc_sect.offset);
+ }
+ }
+ if (swap) {
+ align = MAX(align, BSWAP32(lc_sect.align));
+ } else {
+ align = MAX(align, lc_sect.align);
+ }
+ }
+ } else if (String(lc_seg.segname) == "__LINKEDIT") {
+ link_edit_offset = ps - 8;
+ }
+ } break;
+ case LC_SEGMENT_64: {
+ LoadCommandSegment64 lc_seg;
+ fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64));
+ if (swap) {
+ lc_seg.nsects = BSWAP32(lc_seg.nsects);
+ lc_seg.vmaddr = BSWAP64(lc_seg.vmaddr);
+ lc_seg.vmsize = BSWAP64(lc_seg.vmsize);
+ }
+ align = MAX(align, seg_align(lc_seg.vmaddr, 3, 15));
+ if (String(lc_seg.segname) == "__TEXT") {
+ exe_limit = MAX(exe_limit, lc_seg.vmsize);
+ for (uint32_t j = 0; j < lc_seg.nsects; j++) {
+ Section64 lc_sect;
+ fa->get_buffer((uint8_t *)&lc_sect, sizeof(Section64));
+ if (String(lc_sect.sectname) == "__text") {
+ if (swap) {
+ exe_base = MIN(exe_base, BSWAP32(lc_sect.offset));
+ } else {
+ exe_base = MIN(exe_base, lc_sect.offset);
+ }
+ if (swap) {
+ align = MAX(align, BSWAP32(lc_sect.align));
+ } else {
+ align = MAX(align, lc_sect.align);
+ }
+ }
+ }
+ } else if (String(lc_seg.segname) == "__LINKEDIT") {
+ link_edit_offset = ps - 8;
+ }
+ } break;
+ case LC_CODE_SIGNATURE: {
+ signature_offset = ps - 8;
+ } break;
+ default: {
+ } break;
+ }
+ fa->seek(ps + lc.cmdsize - 8);
+ lc_limit = ps + lc.cmdsize - 8;
+ }
+
+ if (exe_limit == 0 || lc_limit == 0) {
+ ERR_FAIL_V_MSG(false, vformat("MachO: No load commands or executable code found: \"%s\".", p_path));
+ }
+
+ return true;
+}
+
+uint64_t MachO::get_exe_base() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ return exe_base;
+}
+
+uint64_t MachO::get_exe_limit() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ return exe_limit;
+}
+
+int32_t MachO::get_align() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ return align;
+}
+
+uint32_t MachO::get_cputype() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ return cputype;
+}
+
+uint32_t MachO::get_cpusubtype() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ return cpusubtype;
+}
+
+uint64_t MachO::get_size() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ return fa->get_length();
+}
+
+uint64_t MachO::get_signature_offset() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command.");
+
+ fa->seek(signature_offset + 8);
+ if (swap) {
+ return BSWAP32(fa->get_32());
+ } else {
+ return fa->get_32();
+ }
+}
+
+uint64_t MachO::get_code_limit() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+
+ if (signature_offset == 0) {
+ return fa->get_length() + PAD(fa->get_length(), 16);
+ } else {
+ return get_signature_offset();
+ }
+}
+
+uint64_t MachO::get_signature_size() {
+ ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command.");
+
+ fa->seek(signature_offset + 12);
+ if (swap) {
+ return BSWAP32(fa->get_32());
+ } else {
+ return fa->get_32();
+ }
+}
+
+bool MachO::is_signed() {
+ ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
+ if (signature_offset == 0) {
+ return false;
+ }
+
+ fa->seek(get_signature_offset());
+ uint32_t magic = BSWAP32(fa->get_32());
+ if (magic != 0xfade0cc0) {
+ return false; // No SuperBlob found.
+ }
+ fa->get_32(); // Skip size field, unused.
+ uint32_t count = BSWAP32(fa->get_32());
+ for (uint32_t i = 0; i < count; i++) {
+ uint32_t index_type = BSWAP32(fa->get_32());
+ uint32_t offset = BSWAP32(fa->get_32());
+ if (index_type == 0x00000000) { // CodeDirectory index type.
+ fa->seek(get_signature_offset() + offset + 12);
+ uint32_t flags = BSWAP32(fa->get_32());
+ if (flags & 0x20000) {
+ return false; // Found CD, linker-signed.
+ } else {
+ return true; // Found CD, not linker-signed.
+ }
+ }
+ }
+ return false; // No CD found.
+}
+
+PackedByteArray MachO::get_cdhash_sha1() {
+ ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
+ if (signature_offset == 0) {
+ return PackedByteArray();
+ }
+
+ fa->seek(get_signature_offset());
+ uint32_t magic = BSWAP32(fa->get_32());
+ if (magic != 0xfade0cc0) {
+ return PackedByteArray(); // No SuperBlob found.
+ }
+ fa->get_32(); // Skip size field, unused.
+ uint32_t count = BSWAP32(fa->get_32());
+ for (uint32_t i = 0; i < count; i++) {
+ fa->get_32(); // Index type, skip.
+ uint32_t offset = BSWAP32(fa->get_32());
+ uint64_t pos = fa->get_position();
+
+ fa->seek(get_signature_offset() + offset);
+ uint32_t cdmagic = BSWAP32(fa->get_32());
+ uint32_t cdsize = BSWAP32(fa->get_32());
+ if (cdmagic == 0xfade0c02) { // CodeDirectory.
+ fa->seek(get_signature_offset() + offset + 36);
+ uint8_t hash_size = fa->get_8();
+ uint8_t hash_type = fa->get_8();
+ if (hash_size == 0x14 && hash_type == 0x01) { /* SHA-1 */
+ PackedByteArray hash;
+ hash.resize(0x14);
+
+ fa->seek(get_signature_offset() + offset);
+ PackedByteArray blob;
+ blob.resize(cdsize);
+ fa->get_buffer(blob.ptrw(), cdsize);
+
+ CryptoCore::SHA1Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+ }
+ }
+ fa->seek(pos);
+ }
+ return PackedByteArray();
+}
+
+PackedByteArray MachO::get_cdhash_sha256() {
+ ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
+ if (signature_offset == 0) {
+ return PackedByteArray();
+ }
+
+ fa->seek(get_signature_offset());
+ uint32_t magic = BSWAP32(fa->get_32());
+ if (magic != 0xfade0cc0) {
+ return PackedByteArray(); // No SuperBlob found.
+ }
+ fa->get_32(); // Skip size field, unused.
+ uint32_t count = BSWAP32(fa->get_32());
+ for (uint32_t i = 0; i < count; i++) {
+ fa->get_32(); // Index type, skip.
+ uint32_t offset = BSWAP32(fa->get_32());
+ uint64_t pos = fa->get_position();
+
+ fa->seek(get_signature_offset() + offset);
+ uint32_t cdmagic = BSWAP32(fa->get_32());
+ uint32_t cdsize = BSWAP32(fa->get_32());
+ if (cdmagic == 0xfade0c02) { // CodeDirectory.
+ fa->seek(get_signature_offset() + offset + 36);
+ uint8_t hash_size = fa->get_8();
+ uint8_t hash_type = fa->get_8();
+ if (hash_size == 0x20 && hash_type == 0x02) { /* SHA-256 */
+ PackedByteArray hash;
+ hash.resize(0x20);
+
+ fa->seek(get_signature_offset() + offset);
+ PackedByteArray blob;
+ blob.resize(cdsize);
+ fa->get_buffer(blob.ptrw(), cdsize);
+
+ CryptoCore::SHA256Context ctx;
+ ctx.start();
+ ctx.update(blob.ptr(), blob.size());
+ ctx.finish(hash.ptrw());
+
+ return hash;
+ }
+ }
+ fa->seek(pos);
+ }
+ return PackedByteArray();
+}
+
+PackedByteArray MachO::get_requirements() {
+ ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
+ if (signature_offset == 0) {
+ return PackedByteArray();
+ }
+
+ fa->seek(get_signature_offset());
+ uint32_t magic = BSWAP32(fa->get_32());
+ if (magic != 0xfade0cc0) {
+ return PackedByteArray(); // No SuperBlob found.
+ }
+ fa->get_32(); // Skip size field, unused.
+ uint32_t count = BSWAP32(fa->get_32());
+ for (uint32_t i = 0; i < count; i++) {
+ fa->get_32(); // Index type, skip.
+ uint32_t offset = BSWAP32(fa->get_32());
+ uint64_t pos = fa->get_position();
+
+ fa->seek(get_signature_offset() + offset);
+ uint32_t rqmagic = BSWAP32(fa->get_32());
+ uint32_t rqsize = BSWAP32(fa->get_32());
+ if (rqmagic == 0xfade0c01) { // Requirements.
+ PackedByteArray blob;
+ fa->seek(get_signature_offset() + offset);
+ blob.resize(rqsize);
+ fa->get_buffer(blob.ptrw(), rqsize);
+ return blob;
+ }
+ fa->seek(pos);
+ }
+ return PackedByteArray();
+}
+
+const FileAccess *MachO::get_file() const {
+ return fa;
+}
+
+FileAccess *MachO::get_file() {
+ return fa;
+}
+
+bool MachO::set_signature_size(uint64_t p_size) {
+ ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
+
+ // Ensure signature load command exists.
+ ERR_FAIL_COND_V_MSG(link_edit_offset == 0, false, "MachO: No __LINKEDIT segment found.");
+ ERR_FAIL_COND_V_MSG(!alloc_signature(p_size), false, "MachO: Can't allocate signature load command.");
+
+ // Update signature load command.
+ uint64_t old_size = get_signature_size();
+ uint64_t new_size = p_size + PAD(p_size, 16384);
+
+ if (new_size <= old_size) {
+ fa->seek(get_signature_offset());
+ for (uint64_t i = 0; i < old_size; i++) {
+ fa->store_8(0x00);
+ }
+ return true;
+ }
+
+ fa->seek(signature_offset + 12);
+ if (swap) {
+ fa->store_32(BSWAP32(new_size));
+ } else {
+ fa->store_32(new_size);
+ }
+
+ uint64_t end = get_signature_offset() + new_size;
+
+ // Update "__LINKEDIT" segment.
+ LoadCommandHeader lc;
+ fa->seek(link_edit_offset);
+ fa->get_buffer((uint8_t *)&lc, sizeof(LoadCommandHeader));
+ if (swap) {
+ lc.cmd = BSWAP32(lc.cmd);
+ lc.cmdsize = BSWAP32(lc.cmdsize);
+ }
+ switch (lc.cmd) {
+ case LC_SEGMENT: {
+ LoadCommandSegment lc_seg;
+ fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment));
+ if (swap) {
+ lc_seg.vmsize = BSWAP32(lc_seg.vmsize);
+ lc_seg.filesize = BSWAP32(lc_seg.filesize);
+ lc_seg.fileoff = BSWAP32(lc_seg.fileoff);
+ }
+
+ lc_seg.vmsize = end - lc_seg.fileoff;
+ lc_seg.vmsize += PAD(lc_seg.vmsize, 4096);
+ lc_seg.filesize = end - lc_seg.fileoff;
+
+ if (swap) {
+ lc_seg.vmsize = BSWAP32(lc_seg.vmsize);
+ lc_seg.filesize = BSWAP32(lc_seg.filesize);
+ }
+ fa->seek(link_edit_offset + 8);
+ fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment));
+ } break;
+ case LC_SEGMENT_64: {
+ LoadCommandSegment64 lc_seg;
+ fa->get_buffer((uint8_t *)&lc_seg, sizeof(LoadCommandSegment64));
+ if (swap) {
+ lc_seg.vmsize = BSWAP64(lc_seg.vmsize);
+ lc_seg.filesize = BSWAP64(lc_seg.filesize);
+ lc_seg.fileoff = BSWAP64(lc_seg.fileoff);
+ }
+ lc_seg.vmsize = end - lc_seg.fileoff;
+ lc_seg.vmsize += PAD(lc_seg.vmsize, 4096);
+ lc_seg.filesize = end - lc_seg.fileoff;
+ if (swap) {
+ lc_seg.vmsize = BSWAP64(lc_seg.vmsize);
+ lc_seg.filesize = BSWAP64(lc_seg.filesize);
+ }
+ fa->seek(link_edit_offset + 8);
+ fa->store_buffer((const uint8_t *)&lc_seg, sizeof(LoadCommandSegment64));
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(false, "MachO: Invalid __LINKEDIT segment type.");
+ } break;
+ }
+ fa->seek(get_signature_offset());
+ for (uint64_t i = 0; i < new_size; i++) {
+ fa->store_8(0x00);
+ }
+ return true;
+}
+
+MachO::~MachO() {
+ if (fa) {
+ fa->close();
+ memdelete(fa);
+ fa = nullptr;
+ }
+}
+
+#endif // MODULE_REGEX_ENABLED
diff --git a/platform/osx/export/macho.h b/platform/osx/export/macho.h
new file mode 100644
index 0000000000..e09906898b
--- /dev/null
+++ b/platform/osx/export/macho.h
@@ -0,0 +1,217 @@
+/*************************************************************************/
+/* macho.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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. */
+/*************************************************************************/
+
+// Mach-O binary object file format parser and editor.
+
+#ifndef MACHO_H
+#define MACHO_H
+
+#include "core/crypto/crypto.h"
+#include "core/crypto/crypto_core.h"
+#include "core/io/file_access.h"
+#include "core/object/ref_counted.h"
+#include "modules/modules_enabled.gen.h" // For regex.
+
+#ifdef MODULE_REGEX_ENABLED
+
+class MachO : public RefCounted {
+ struct MachHeader {
+ uint32_t cputype;
+ uint32_t cpusubtype;
+ uint32_t filetype;
+ uint32_t ncmds;
+ uint32_t sizeofcmds;
+ uint32_t flags;
+ };
+
+ enum LoadCommandID {
+ LC_SEGMENT = 0x00000001,
+ LC_SYMTAB = 0x00000002,
+ LC_SYMSEG = 0x00000003,
+ LC_THREAD = 0x00000004,
+ LC_UNIXTHREAD = 0x00000005,
+ LC_LOADFVMLIB = 0x00000006,
+ LC_IDFVMLIB = 0x00000007,
+ LC_IDENT = 0x00000008,
+ LC_FVMFILE = 0x00000009,
+ LC_PREPAGE = 0x0000000a,
+ LC_DYSYMTAB = 0x0000000b,
+ LC_LOAD_DYLIB = 0x0000000c,
+ LC_ID_DYLIB = 0x0000000d,
+ LC_LOAD_DYLINKER = 0x0000000e,
+ LC_ID_DYLINKER = 0x0000000f,
+ LC_PREBOUND_DYLIB = 0x00000010,
+ LC_ROUTINES = 0x00000011,
+ LC_SUB_FRAMEWORK = 0x00000012,
+ LC_SUB_UMBRELLA = 0x00000013,
+ LC_SUB_CLIENT = 0x00000014,
+ LC_SUB_LIBRARY = 0x00000015,
+ LC_TWOLEVEL_HINTS = 0x00000016,
+ LC_PREBIND_CKSUM = 0x00000017,
+ LC_LOAD_WEAK_DYLIB = 0x80000018,
+ LC_SEGMENT_64 = 0x00000019,
+ LC_ROUTINES_64 = 0x0000001a,
+ LC_UUID = 0x0000001b,
+ LC_RPATH = 0x8000001c,
+ LC_CODE_SIGNATURE = 0x0000001d,
+ LC_SEGMENT_SPLIT_INFO = 0x0000001e,
+ LC_REEXPORT_DYLIB = 0x8000001f,
+ LC_LAZY_LOAD_DYLIB = 0x00000020,
+ LC_ENCRYPTION_INFO = 0x00000021,
+ LC_DYLD_INFO = 0x00000022,
+ LC_DYLD_INFO_ONLY = 0x80000022,
+ LC_LOAD_UPWARD_DYLIB = 0x80000023,
+ LC_VERSION_MIN_MACOSX = 0x00000024,
+ LC_VERSION_MIN_IPHONEOS = 0x00000025,
+ LC_FUNCTION_STARTS = 0x00000026,
+ LC_DYLD_ENVIRONMENT = 0x00000027,
+ LC_MAIN = 0x80000028,
+ LC_DATA_IN_CODE = 0x00000029,
+ LC_SOURCE_VERSION = 0x0000002a,
+ LC_DYLIB_CODE_SIGN_DRS = 0x0000002b,
+ LC_ENCRYPTION_INFO_64 = 0x0000002c,
+ LC_LINKER_OPTION = 0x0000002d,
+ LC_LINKER_OPTIMIZATION_HINT = 0x0000002e,
+ LC_VERSION_MIN_TVOS = 0x0000002f,
+ LC_VERSION_MIN_WATCHOS = 0x00000030,
+ };
+
+ struct LoadCommandHeader {
+ uint32_t cmd;
+ uint32_t cmdsize;
+ };
+
+ struct LoadCommandSegment {
+ char segname[16];
+ uint32_t vmaddr;
+ uint32_t vmsize;
+ uint32_t fileoff;
+ uint32_t filesize;
+ uint32_t maxprot;
+ uint32_t initprot;
+ uint32_t nsects;
+ uint32_t flags;
+ };
+
+ struct LoadCommandSegment64 {
+ char segname[16];
+ uint64_t vmaddr;
+ uint64_t vmsize;
+ uint64_t fileoff;
+ uint64_t filesize;
+ uint32_t maxprot;
+ uint32_t initprot;
+ uint32_t nsects;
+ uint32_t flags;
+ };
+
+ struct Section {
+ char sectname[16];
+ char segname[16];
+ uint32_t addr;
+ uint32_t size;
+ uint32_t offset;
+ uint32_t align;
+ uint32_t reloff;
+ uint32_t nreloc;
+ uint32_t flags;
+ uint32_t reserved1;
+ uint32_t reserved2;
+ };
+
+ struct Section64 {
+ char sectname[16];
+ char segname[16];
+ uint64_t addr;
+ uint64_t size;
+ uint32_t offset;
+ uint32_t align;
+ uint32_t reloff;
+ uint32_t nreloc;
+ uint32_t flags;
+ uint32_t reserved1;
+ uint32_t reserved2;
+ uint32_t reserved3;
+ };
+
+ FileAccess *fa = nullptr;
+ bool swap = false;
+
+ uint64_t lc_limit = 0;
+
+ uint64_t exe_limit = 0;
+ uint64_t exe_base = std::numeric_limits<uint64_t>::max(); // Start of first __text section.
+ uint32_t align = 0;
+ uint32_t cputype = 0;
+ uint32_t cpusubtype = 0;
+
+ uint64_t link_edit_offset = 0; // __LINKEDIT segment offset.
+ uint64_t signature_offset = 0; // Load command offset.
+
+ uint32_t seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max);
+ bool alloc_signature(uint64_t p_size);
+
+ static inline size_t PAD(size_t s, size_t a) {
+ return (a - s % a);
+ }
+
+public:
+ static bool is_macho(const String &p_path);
+
+ bool open_file(const String &p_path);
+
+ uint64_t get_exe_base();
+ uint64_t get_exe_limit();
+ int32_t get_align();
+ uint32_t get_cputype();
+ uint32_t get_cpusubtype();
+ uint64_t get_size();
+ uint64_t get_code_limit();
+
+ uint64_t get_signature_offset();
+ bool is_signed();
+
+ PackedByteArray get_cdhash_sha1();
+ PackedByteArray get_cdhash_sha256();
+
+ PackedByteArray get_requirements();
+
+ const FileAccess *get_file() const;
+ FileAccess *get_file();
+
+ uint64_t get_signature_size();
+ bool set_signature_size(uint64_t p_size);
+
+ ~MachO();
+};
+
+#endif // MODULE_REGEX_ENABLED
+
+#endif // MACHO_H
diff --git a/platform/osx/export/plist.cpp b/platform/osx/export/plist.cpp
new file mode 100644
index 0000000000..553b864180
--- /dev/null
+++ b/platform/osx/export/plist.cpp
@@ -0,0 +1,570 @@
+/*************************************************************************/
+/* plist.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "modules/modules_enabled.gen.h" // For regex.
+
+#include "plist.h"
+
+#ifdef MODULE_REGEX_ENABLED
+
+Ref<PListNode> PListNode::new_array() {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_ARRAY;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_dict() {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_string(const String &p_string) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_STRING;
+ node->data_string = p_string.utf8();
+ return node;
+}
+
+Ref<PListNode> PListNode::new_data(const String &p_string) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATA;
+ node->data_string = p_string.utf8();
+ return node;
+}
+
+Ref<PListNode> PListNode::new_date(const String &p_string) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATE;
+ node->data_string = p_string.utf8();
+ return node;
+}
+
+Ref<PListNode> PListNode::new_bool(bool p_bool) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_BOOLEAN;
+ node->data_bool = p_bool;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_int(int32_t p_int) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_INTEGER;
+ node->data_int = p_int;
+ return node;
+}
+
+Ref<PListNode> PListNode::new_real(float p_real) {
+ Ref<PListNode> node = memnew(PListNode());
+ ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>());
+ node->data_type = PList::PLNodeType::PL_NODE_TYPE_REAL;
+ node->data_real = p_real;
+ return node;
+}
+
+bool PListNode::push_subnode(const Ref<PListNode> &p_node, const String &p_key) {
+ ERR_FAIL_COND_V(p_node.is_null(), false);
+ if (data_type == PList::PLNodeType::PL_NODE_TYPE_DICT) {
+ ERR_FAIL_COND_V(p_key.is_empty(), false);
+ ERR_FAIL_COND_V(data_dict.has(p_key), false);
+ data_dict[p_key] = p_node;
+ return true;
+ } else if (data_type == PList::PLNodeType::PL_NODE_TYPE_ARRAY) {
+ data_array.push_back(p_node);
+ return true;
+ } else {
+ ERR_FAIL_V_MSG(false, "PList: Invalid parent node type, should be DICT or ARRAY.");
+ }
+}
+
+size_t PListNode::get_asn1_size(uint8_t p_len_octets) const {
+ // Get size of all data, excluding type and size information.
+ switch (data_type) {
+ case PList::PLNodeType::PL_NODE_TYPE_NIL: {
+ return 0;
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DATA:
+ case PList::PLNodeType::PL_NODE_TYPE_DATE: {
+ ERR_FAIL_V_MSG(0, "PList: DATE and DATA nodes are not supported by ASN.1 serialization.");
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_STRING: {
+ return data_string.length();
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
+ return 1;
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_INTEGER:
+ case PList::PLNodeType::PL_NODE_TYPE_REAL: {
+ return 4;
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
+ size_t size = 0;
+ for (int i = 0; i < data_array.size(); i++) {
+ size += 1 + _asn1_size_len(p_len_octets) + data_array[i]->get_asn1_size(p_len_octets);
+ }
+ return size;
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DICT: {
+ size_t size = 0;
+ for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) {
+ size += 1 + _asn1_size_len(p_len_octets); // Sequence.
+ size += 1 + _asn1_size_len(p_len_octets) + it->key().utf8().length(); //Key.
+ size += 1 + _asn1_size_len(p_len_octets) + it->value()->get_asn1_size(p_len_octets); // Value.
+ }
+ return size;
+ } break;
+ default: {
+ return 0;
+ } break;
+ }
+}
+
+int PListNode::_asn1_size_len(uint8_t p_len_octets) {
+ if (p_len_octets > 1) {
+ return p_len_octets + 1;
+ } else {
+ return 1;
+ }
+}
+
+void PListNode::store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const {
+ uint32_t size = get_asn1_size(p_len_octets);
+ if (p_len_octets > 1) {
+ p_stream.push_back(0x80 + p_len_octets);
+ }
+ for (int i = p_len_octets - 1; i >= 0; i--) {
+ uint8_t x = (size >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+}
+
+bool PListNode::store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const {
+ // Convert to binary ASN1 stream.
+ bool valid = true;
+ switch (data_type) {
+ case PList::PLNodeType::PL_NODE_TYPE_NIL: {
+ // Nothing to store.
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DATE:
+ case PList::PLNodeType::PL_NODE_TYPE_DATA: {
+ ERR_FAIL_V_MSG(false, "PList: DATE and DATA nodes are not supported by ASN.1 serialization.");
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_STRING: {
+ p_stream.push_back(0x0C);
+ store_asn1_size(p_stream, p_len_octets);
+ for (int i = 0; i < data_string.size(); i++) {
+ p_stream.push_back(data_string[i]);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
+ p_stream.push_back(0x01);
+ store_asn1_size(p_stream, p_len_octets);
+ if (data_bool) {
+ p_stream.push_back(0x01);
+ } else {
+ p_stream.push_back(0x00);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
+ p_stream.push_back(0x02);
+ store_asn1_size(p_stream, p_len_octets);
+ for (int i = 4; i >= 0; i--) {
+ uint8_t x = (data_int >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_REAL: {
+ p_stream.push_back(0x03);
+ store_asn1_size(p_stream, p_len_octets);
+ for (int i = 4; i >= 0; i--) {
+ uint8_t x = (data_int >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
+ p_stream.push_back(0x30); // Sequence.
+ store_asn1_size(p_stream, p_len_octets);
+ for (int i = 0; i < data_array.size(); i++) {
+ valid = valid && data_array[i]->store_asn1(p_stream, p_len_octets);
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DICT: {
+ p_stream.push_back(0x31); // Set.
+ store_asn1_size(p_stream, p_len_octets);
+ for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) {
+ CharString cs = it->key().utf8();
+ uint32_t size = cs.length();
+
+ // Sequence.
+ p_stream.push_back(0x30);
+ uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + it->value()->get_asn1_size(p_len_octets);
+ if (p_len_octets > 1) {
+ p_stream.push_back(0x80 + p_len_octets);
+ }
+ for (int i = p_len_octets - 1; i >= 0; i--) {
+ uint8_t x = (seq_size >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+ // Key.
+ p_stream.push_back(0x0C);
+ if (p_len_octets > 1) {
+ p_stream.push_back(0x80 + p_len_octets);
+ }
+ for (int i = p_len_octets - 1; i >= 0; i--) {
+ uint8_t x = (size >> i * 8) & 0xFF;
+ p_stream.push_back(x);
+ }
+ for (uint32_t i = 0; i < size; i++) {
+ p_stream.push_back(cs[i]);
+ }
+ // Value.
+ valid = valid && it->value()->store_asn1(p_stream, p_len_octets);
+ }
+ } break;
+ }
+ return valid;
+}
+
+void PListNode::store_text(String &p_stream, uint8_t p_indent) const {
+ // Convert to text XML stream.
+ switch (data_type) {
+ case PList::PLNodeType::PL_NODE_TYPE_NIL: {
+ // Nothing to store.
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DATA: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<data>\n";
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += data_string + "\n";
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "</data>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DATE: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<date>";
+ p_stream += data_string;
+ p_stream += "</date>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_STRING: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<string>";
+ p_stream += String::utf8(data_string);
+ p_stream += "</string>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: {
+ p_stream += String("\t").repeat(p_indent);
+ if (data_bool) {
+ p_stream += "<true/>\n";
+ } else {
+ p_stream += "<false/>\n";
+ }
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_INTEGER: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<integer>";
+ p_stream += itos(data_int);
+ p_stream += "</integer>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_REAL: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<real>";
+ p_stream += rtos(data_real);
+ p_stream += "</real>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_ARRAY: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<array>\n";
+ for (int i = 0; i < data_array.size(); i++) {
+ data_array[i]->store_text(p_stream, p_indent + 1);
+ }
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "</array>\n";
+ } break;
+ case PList::PLNodeType::PL_NODE_TYPE_DICT: {
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "<dict>\n";
+ for (const Map<String, Ref<PListNode>>::Element *it = data_dict.front(); it; it = it->next()) {
+ p_stream += String("\t").repeat(p_indent + 1);
+ p_stream += "<key>";
+ p_stream += it->key();
+ p_stream += "</key>\n";
+ it->value()->store_text(p_stream, p_indent + 1);
+ }
+ p_stream += String("\t").repeat(p_indent);
+ p_stream += "</dict>\n";
+ } break;
+ }
+}
+
+/*************************************************************************/
+
+PList::PList() {
+ root = PListNode::new_dict();
+}
+
+PList::PList(const String &p_string) {
+ load_string(p_string);
+}
+
+bool PList::load_file(const String &p_filename) {
+ root = Ref<PListNode>();
+
+ FileAccessRef fb = FileAccess::open(p_filename, FileAccess::READ);
+ if (!fb) {
+ return false;
+ }
+
+ unsigned char magic[8];
+ fb->get_buffer(magic, 8);
+
+ if (String((const char *)magic, 8) == "bplist00") {
+ ERR_FAIL_V_MSG(false, "PList: Binary property lists are not supported.");
+ } else {
+ // Load text plist.
+ Error err;
+ Vector<uint8_t> array = FileAccess::get_file_as_array(p_filename, &err);
+ ERR_FAIL_COND_V(err != OK, false);
+
+ String ret;
+ ret.parse_utf8((const char *)array.ptr(), array.size());
+ return load_string(ret);
+ }
+}
+
+bool PList::load_string(const String &p_string) {
+ root = Ref<PListNode>();
+
+ int pos = 0;
+ bool in_plist = false;
+ bool done_plist = false;
+ List<Ref<PListNode>> stack;
+ String key;
+ while (pos >= 0) {
+ int open_token_s = p_string.find("<", pos);
+ if (open_token_s == -1) {
+ ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. No tags found.");
+ }
+ int open_token_e = p_string.find(">", open_token_s);
+ pos = open_token_e;
+
+ String token = p_string.substr(open_token_s + 1, open_token_e - open_token_s - 1);
+ if (token.is_empty()) {
+ ERR_FAIL_V_MSG(false, "PList: Invalid token name.");
+ }
+ String value;
+ if (token[0] == '?' || token[0] == '!') { // Skip <?xml ... ?> and <!DOCTYPE ... >
+ int end_token_e = p_string.find(">", open_token_s);
+ pos = end_token_e;
+ continue;
+ }
+
+ if (token.find("plist", 0) == 0) {
+ in_plist = true;
+ continue;
+ }
+
+ if (token == "/plist") {
+ in_plist = false;
+ done_plist = true;
+ break;
+ }
+
+ if (!in_plist) {
+ ERR_FAIL_V_MSG(false, "PList: Node outside of <plist> tag.");
+ }
+
+ if (token == "dict") {
+ if (!stack.is_empty()) {
+ // Add subnode end enter it.
+ Ref<PListNode> dict = PListNode::new_dict();
+ dict->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT;
+ if (!stack.back()->get()->push_subnode(dict, key)) {
+ ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type.");
+ }
+ stack.push_back(dict);
+ } else {
+ // Add root node.
+ if (!root.is_null()) {
+ ERR_FAIL_V_MSG(false, "PList: Root node already set.");
+ }
+ Ref<PListNode> dict = PListNode::new_dict();
+ stack.push_back(dict);
+ root = dict;
+ }
+ continue;
+ }
+
+ if (token == "/dict") {
+ // Exit current dict.
+ if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_DICT) {
+ ERR_FAIL_V_MSG(false, "PList: Mismatched </dict> tag.");
+ }
+ stack.pop_back();
+ continue;
+ }
+
+ if (token == "array") {
+ if (!stack.is_empty()) {
+ // Add subnode end enter it.
+ Ref<PListNode> arr = PListNode::new_array();
+ if (!stack.back()->get()->push_subnode(arr, key)) {
+ ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type.");
+ }
+ stack.push_back(arr);
+ } else {
+ // Add root node.
+ if (!root.is_null()) {
+ ERR_FAIL_V_MSG(false, "PList: Root node already set.");
+ }
+ Ref<PListNode> arr = PListNode::new_array();
+ stack.push_back(arr);
+ root = arr;
+ }
+ continue;
+ }
+
+ if (token == "/array") {
+ // Exit current array.
+ if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_ARRAY) {
+ ERR_FAIL_V_MSG(false, "PList: Mismatched </array> tag.");
+ }
+ stack.pop_back();
+ continue;
+ }
+
+ if (token[token.length() - 1] == '/') {
+ token = token.substr(0, token.length() - 1);
+ } else {
+ int end_token_s = p_string.find("</", pos);
+ if (end_token_s == -1) {
+ ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> tag.", token));
+ }
+ int end_token_e = p_string.find(">", end_token_s);
+ pos = end_token_e;
+ String end_token = p_string.substr(end_token_s + 2, end_token_e - end_token_s - 2);
+ if (end_token != token) {
+ ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> and <%s> token pair.", token, end_token));
+ }
+ value = p_string.substr(open_token_e + 1, end_token_s - open_token_e - 1);
+ }
+ if (token == "key") {
+ key = value;
+ } else {
+ Ref<PListNode> var = nullptr;
+ if (token == "true") {
+ var = PListNode::new_bool(true);
+ } else if (token == "false") {
+ var = PListNode::new_bool(false);
+ } else if (token == "integer") {
+ var = PListNode::new_int(value.to_int());
+ } else if (token == "real") {
+ var = PListNode::new_real(value.to_float());
+ } else if (token == "string") {
+ var = PListNode::new_string(value);
+ } else if (token == "data") {
+ var = PListNode::new_data(value);
+ } else if (token == "date") {
+ var = PListNode::new_date(value);
+ } else {
+ ERR_FAIL_V_MSG(false, "PList: Invalid value type.");
+ }
+ if (stack.is_empty() || !stack.back()->get()->push_subnode(var, key)) {
+ ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type.");
+ }
+ }
+ }
+ if (!stack.is_empty() || !done_plist) {
+ ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. Root node is not closed.");
+ }
+ return true;
+}
+
+PackedByteArray PList::save_asn1() const {
+ if (root == nullptr) {
+ ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node.");
+ }
+ size_t size = root->get_asn1_size(1);
+ uint8_t len_octets = 0;
+ if (size < 0x80) {
+ len_octets = 1;
+ } else {
+ size = root->get_asn1_size(2);
+ if (size < 0xFFFF) {
+ len_octets = 2;
+ } else {
+ size = root->get_asn1_size(3);
+ if (size < 0xFFFFFF) {
+ len_octets = 3;
+ } else {
+ size = root->get_asn1_size(4);
+ if (size < 0xFFFFFFFF) {
+ len_octets = 4;
+ } else {
+ ERR_FAIL_V_MSG(PackedByteArray(), "PList: Data is too big for ASN.1 serializer, should be < 4 GiB.");
+ }
+ }
+ }
+ }
+
+ PackedByteArray ret;
+ if (!root->store_asn1(ret, len_octets)) {
+ ERR_FAIL_V_MSG(PackedByteArray(), "PList: ASN.1 serializer error.");
+ }
+ return ret;
+}
+
+String PList::save_text() const {
+ if (root == nullptr) {
+ ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node.");
+ }
+
+ String ret;
+ ret += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ ret += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
+ ret += "<plist version=\"1.0\">\n";
+
+ root->store_text(ret, 0);
+
+ ret += "</plist>\n\n";
+ return ret;
+}
+
+Ref<PListNode> PList::get_root() {
+ return root;
+}
+
+#endif // MODULE_REGEX_ENABLED
diff --git a/platform/osx/export/plist.h b/platform/osx/export/plist.h
new file mode 100644
index 0000000000..fb4aaaa935
--- /dev/null
+++ b/platform/osx/export/plist.h
@@ -0,0 +1,116 @@
+/*************************************************************************/
+/* plist.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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. */
+/*************************************************************************/
+
+// Property list file format (application/x-plist) parser, property list ASN-1 serialization.
+
+#ifndef PLIST_H
+#define PLIST_H
+
+#include "core/crypto/crypto_core.h"
+#include "core/io/file_access.h"
+#include "modules/modules_enabled.gen.h" // For regex.
+
+#ifdef MODULE_REGEX_ENABLED
+
+class PListNode;
+
+class PList : public RefCounted {
+ friend class PListNode;
+
+public:
+ enum PLNodeType {
+ PL_NODE_TYPE_NIL,
+ PL_NODE_TYPE_STRING,
+ PL_NODE_TYPE_ARRAY,
+ PL_NODE_TYPE_DICT,
+ PL_NODE_TYPE_BOOLEAN,
+ PL_NODE_TYPE_INTEGER,
+ PL_NODE_TYPE_REAL,
+ PL_NODE_TYPE_DATA,
+ PL_NODE_TYPE_DATE,
+ };
+
+private:
+ Ref<PListNode> root;
+
+public:
+ PList();
+ PList(const String &p_string);
+
+ bool load_file(const String &p_filename);
+ bool load_string(const String &p_string);
+
+ PackedByteArray save_asn1() const;
+ String save_text() const;
+
+ Ref<PListNode> get_root();
+};
+
+/*************************************************************************/
+
+class PListNode : public RefCounted {
+ static int _asn1_size_len(uint8_t p_len_octets);
+
+public:
+ PList::PLNodeType data_type = PList::PLNodeType::PL_NODE_TYPE_NIL;
+
+ CharString data_string;
+ Vector<Ref<PListNode>> data_array;
+ Map<String, Ref<PListNode>> data_dict;
+ union {
+ int32_t data_int;
+ bool data_bool;
+ float data_real;
+ };
+
+ static Ref<PListNode> new_array();
+ static Ref<PListNode> new_dict();
+ static Ref<PListNode> new_string(const String &p_string);
+ static Ref<PListNode> new_data(const String &p_string);
+ static Ref<PListNode> new_date(const String &p_string);
+ static Ref<PListNode> new_bool(bool p_bool);
+ static Ref<PListNode> new_int(int32_t p_int);
+ static Ref<PListNode> new_real(float p_real);
+
+ bool push_subnode(const Ref<PListNode> &p_node, const String &p_key = "");
+
+ size_t get_asn1_size(uint8_t p_len_octets) const;
+
+ void store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const;
+ bool store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const;
+ void store_text(String &p_stream, uint8_t p_indent) const;
+
+ PListNode() {}
+ ~PListNode() {}
+};
+
+#endif // MODULE_REGEX_ENABLED
+
+#endif // PLIST_H
diff --git a/platform/osx/gl_manager_osx.h b/platform/osx/gl_manager_osx_legacy.h
index 0229b672a2..b5a1b9dd98 100644
--- a/platform/osx/gl_manager_osx.h
+++ b/platform/osx/gl_manager_osx_legacy.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* gl_manager_osx.h */
+/* gl_manager_osx_legacy.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef GL_MANAGER_OSX_H
-#define GL_MANAGER_OSX_H
+#ifndef GL_MANAGER_OSX_LEGACY_H
+#define GL_MANAGER_OSX_LEGACY_H
#if defined(OSX_ENABLED) && defined(GLES3_ENABLED)
@@ -50,29 +50,21 @@ public:
private:
struct GLWindow {
- GLWindow() { in_use = false; }
- bool in_use;
+ int width = 0;
+ int height = 0;
- DisplayServer::WindowID window_id;
- int width;
- int height;
-
- id window_view;
- NSOpenGLContext *context;
+ id window_view = nullptr;
+ NSOpenGLContext *context = nullptr;
};
- LocalVector<GLWindow> _windows;
-
- NSOpenGLContext *_shared_context = nullptr;
- GLWindow *_current_window;
+ Map<DisplayServer::WindowID, GLWindow> windows;
- Error _create_context(GLWindow &win);
- void _internal_set_current_window(GLWindow *p_win);
+ NSOpenGLContext *shared_context = nullptr;
+ DisplayServer::WindowID current_window = DisplayServer::INVALID_WINDOW_ID;
- GLWindow &get_window(unsigned int id) { return _windows[id]; }
- const GLWindow &get_window(unsigned int id) const { return _windows[id]; }
+ Error create_context(GLWindow &win);
- bool use_vsync;
+ bool use_vsync = false;
ContextType context_type;
public:
@@ -80,7 +72,6 @@ public:
void window_destroy(DisplayServer::WindowID p_window_id);
void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height);
- // get directly from the cached GLWindow
int window_get_width(DisplayServer::WindowID p_window_id = 0);
int window_get_height(DisplayServer::WindowID p_window_id = 0);
@@ -91,6 +82,7 @@ public:
void window_make_current(DisplayServer::WindowID p_window_id);
void window_update(DisplayServer::WindowID p_window_id);
+ void window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled);
Error initialize();
@@ -101,6 +93,5 @@ public:
~GLManager_OSX();
};
-#endif // defined(OSX_ENABLED) && defined(GLES3_ENABLED)
-
-#endif // GL_MANAGER_OSX_H
+#endif // OSX_ENABLED && GLES3_ENABLED
+#endif // GL_MANAGER_OSX_LEGACY_H
diff --git a/platform/osx/gl_manager_osx.mm b/platform/osx/gl_manager_osx_legacy.mm
index 3e70de8523..fbe64e32a3 100644
--- a/platform/osx/gl_manager_osx.mm
+++ b/platform/osx/gl_manager_osx_legacy.mm
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* gl_manager_osx.mm */
+/* gl_manager_osx_legacy.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "gl_manager_osx.h"
+#include "gl_manager_osx_legacy.h"
#ifdef OSX_ENABLED
#ifdef GLES3_ENABLED
@@ -36,7 +36,7 @@
#include <stdio.h>
#include <stdlib.h>
-Error GLManager_OSX::_create_context(GLWindow &win) {
+Error GLManager_OSX::create_context(GLWindow &win) {
NSOpenGLPixelFormatAttribute attributes[] = {
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAClosestPolicy,
@@ -50,10 +50,10 @@ Error GLManager_OSX::_create_context(GLWindow &win) {
NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
ERR_FAIL_COND_V(pixel_format == nil, ERR_CANT_CREATE);
- win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:_shared_context];
+ win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context];
ERR_FAIL_COND_V(win.context == nil, ERR_CANT_CREATE);
- if (_shared_context == nullptr) {
- _shared_context = win.context;
+ if (shared_context == nullptr) {
+ shared_context = win.context;
}
[win.context setView:win.window_view];
@@ -63,40 +63,27 @@ Error GLManager_OSX::_create_context(GLWindow &win) {
}
Error GLManager_OSX::window_create(DisplayServer::WindowID p_window_id, id p_view, int p_width, int p_height) {
- if (p_window_id >= (int)_windows.size()) {
- _windows.resize(p_window_id + 1);
- }
-
- GLWindow &win = _windows[p_window_id];
- win.in_use = true;
- win.window_id = p_window_id;
+ GLWindow win;
win.width = p_width;
win.height = p_height;
win.window_view = p_view;
- if (_create_context(win) != OK) {
- _windows.remove_at(_windows.size() - 1);
+ if (create_context(win) != OK) {
return FAILED;
}
- window_make_current(_windows.size() - 1);
+ windows[p_window_id] = win;
+ window_make_current(p_window_id);
return OK;
}
-void GLManager_OSX::_internal_set_current_window(GLWindow *p_win) {
- _current_window = p_win;
-}
-
void GLManager_OSX::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {
- if (p_window_id == -1) {
+ if (!windows.has(p_window_id)) {
return;
}
- GLWindow &win = _windows[p_window_id];
- if (!win.in_use) {
- return;
- }
+ GLWindow &win = windows[p_window_id];
win.width = p_width;
win.height = p_height;
@@ -116,24 +103,37 @@ void GLManager_OSX::window_resize(DisplayServer::WindowID p_window_id, int p_wid
}
int GLManager_OSX::window_get_width(DisplayServer::WindowID p_window_id) {
- return get_window(p_window_id).width;
+ if (!windows.has(p_window_id)) {
+ return 0;
+ }
+
+ GLWindow &win = windows[p_window_id];
+ return win.width;
}
int GLManager_OSX::window_get_height(DisplayServer::WindowID p_window_id) {
- return get_window(p_window_id).height;
+ if (!windows.has(p_window_id)) {
+ return 0;
+ }
+
+ GLWindow &win = windows[p_window_id];
+ return win.height;
}
void GLManager_OSX::window_destroy(DisplayServer::WindowID p_window_id) {
- GLWindow &win = get_window(p_window_id);
- win.in_use = false;
+ if (!windows.has(p_window_id)) {
+ return;
+ }
- if (_current_window == &win) {
- _current_window = nullptr;
+ if (current_window == p_window_id) {
+ current_window = DisplayServer::INVALID_WINDOW_ID;
}
+
+ windows.erase(p_window_id);
}
void GLManager_OSX::release_current() {
- if (!_current_window) {
+ if (current_window == DisplayServer::INVALID_WINDOW_ID) {
return;
}
@@ -141,63 +141,59 @@ void GLManager_OSX::release_current() {
}
void GLManager_OSX::window_make_current(DisplayServer::WindowID p_window_id) {
- if (p_window_id == -1) {
- return;
- }
-
- GLWindow &win = _windows[p_window_id];
- if (!win.in_use) {
+ if (current_window == p_window_id) {
return;
}
-
- if (&win == _current_window) {
+ if (!windows.has(p_window_id)) {
return;
}
+ GLWindow &win = windows[p_window_id];
[win.context makeCurrentContext];
- _internal_set_current_window(&win);
+ current_window = p_window_id;
}
void GLManager_OSX::make_current() {
- if (!_current_window) {
+ if (current_window == DisplayServer::INVALID_WINDOW_ID) {
return;
}
- if (!_current_window->in_use) {
- WARN_PRINT("current window not in use!");
+ if (!windows.has(current_window)) {
return;
}
- [_current_window->context makeCurrentContext];
+
+ GLWindow &win = windows[current_window];
+ [win.context makeCurrentContext];
}
void GLManager_OSX::swap_buffers() {
- // NO NEED TO CALL SWAP BUFFERS for each window...
- // see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml
-
- if (!_current_window) {
- return;
- }
- if (!_current_window->in_use) {
- WARN_PRINT("current window not in use!");
- return;
+ for (Map<DisplayServer::WindowID, GLWindow>::Element *E = windows.front(); E; E = E->next()) {
+ [E->get().context flushBuffer];
}
- [_current_window->context flushBuffer];
}
void GLManager_OSX::window_update(DisplayServer::WindowID p_window_id) {
- if (p_window_id == -1) {
+ if (!windows.has(p_window_id)) {
return;
}
- GLWindow &win = _windows[p_window_id];
- if (!win.in_use) {
- return;
- }
+ GLWindow &win = windows[p_window_id];
+ [win.context update];
+}
- if (&win == _current_window) {
+void GLManager_OSX::window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled) {
+ if (!windows.has(p_window_id)) {
return;
}
+ GLWindow &win = windows[p_window_id];
+ if (p_enabled) {
+ GLint opacity = 0;
+ [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity];
+ } else {
+ GLint opacity = 1;
+ [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity];
+ }
[win.context update];
}
@@ -207,6 +203,7 @@ Error GLManager_OSX::initialize() {
void GLManager_OSX::set_use_vsync(bool p_use) {
use_vsync = p_use;
+
CGLContextObj ctx = CGLGetCurrentContext();
if (ctx) {
GLint swapInterval = p_use ? 1 : 0;
@@ -221,8 +218,6 @@ bool GLManager_OSX::is_using_vsync() const {
GLManager_OSX::GLManager_OSX(ContextType p_context_type) {
context_type = p_context_type;
- use_vsync = false;
- _current_window = nullptr;
}
GLManager_OSX::~GLManager_OSX() {
diff --git a/platform/osx/godot_application.h b/platform/osx/godot_application.h
new file mode 100644
index 0000000000..8d48a659f3
--- /dev/null
+++ b/platform/osx/godot_application.h
@@ -0,0 +1,42 @@
+/*************************************************************************/
+/* godot_application.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_APPLICATION_H
+#define GODOT_APPLICATION_H
+
+#include "core/os/os.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotApplication : NSApplication
+@end
+
+#endif // GODOT_APPLICATION_H
diff --git a/platform/osx/godot_application.mm b/platform/osx/godot_application.mm
new file mode 100644
index 0000000000..00a58700e8
--- /dev/null
+++ b/platform/osx/godot_application.mm
@@ -0,0 +1,53 @@
+/*************************************************************************/
+/* godot_application.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "godot_application.h"
+
+#include "display_server_osx.h"
+
+@implementation GodotApplication
+
+- (void)sendEvent:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->send_event(event);
+ }
+
+ // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
+ // This works around an AppKit bug, where key up events while holding
+ // down the command key don't get sent to the key window.
+ if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) {
+ [[self keyWindow] sendEvent:event];
+ } else {
+ [super sendEvent:event];
+ }
+}
+
+@end
diff --git a/platform/osx/godot_application_delegate.h b/platform/osx/godot_application_delegate.h
new file mode 100644
index 0000000000..8eec762d8f
--- /dev/null
+++ b/platform/osx/godot_application_delegate.h
@@ -0,0 +1,45 @@
+/*************************************************************************/
+/* godot_application_delegate.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_APPLICATION_DELEGATE_H
+#define GODOT_APPLICATION_DELEGATE_H
+
+#include "core/os/os.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotApplicationDelegate : NSObject
+- (void)forceUnbundledWindowActivationHackStep1;
+- (void)forceUnbundledWindowActivationHackStep2;
+- (void)forceUnbundledWindowActivationHackStep3;
+@end
+
+#endif // GODOT_APPLICATION_DELEGATE_H
diff --git a/platform/osx/godot_application_delegate.mm b/platform/osx/godot_application_delegate.mm
new file mode 100644
index 0000000000..dc82075c44
--- /dev/null
+++ b/platform/osx/godot_application_delegate.mm
@@ -0,0 +1,136 @@
+/*************************************************************************/
+/* godot_application_delegate.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "godot_application_delegate.h"
+
+#include "display_server_osx.h"
+#include "os_osx.h"
+
+@implementation GodotApplicationDelegate
+
+- (void)forceUnbundledWindowActivationHackStep1 {
+ // Step 1: Switch focus to macOS SystemUIServer process.
+ // Required to perform step 2, TransformProcessType will fail if app is already the in focus.
+ for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) {
+ [app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
+ break;
+ }
+ [self performSelector:@selector(forceUnbundledWindowActivationHackStep2)
+ withObject:nil
+ afterDelay:0.02];
+}
+
+- (void)forceUnbundledWindowActivationHackStep2 {
+ // Step 2: Register app as foreground process.
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+ (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication);
+ [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02];
+}
+
+- (void)forceUnbundledWindowActivationHackStep3 {
+ // Step 3: Switch focus back to app window.
+ [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notice {
+ NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
+ if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) {
+ // If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
+ [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
+ }
+}
+
+- (void)applicationDidResignActive:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->mouse_process_popups(true);
+ }
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
+ }
+}
+
+- (void)applicationDidBecomeActive:(NSNotification *)notification {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
+ }
+}
+
+- (void)globalMenuCallback:(id)sender {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ return ds->menu_callback(sender);
+ }
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ return ds->get_dock_menu();
+ } else {
+ return nullptr;
+ }
+}
+
+- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
+ // Note: may be called called before main loop init!
+ OS_OSX *os = (OS_OSX *)OS::get_singleton();
+ if (os) {
+ os->set_open_with_filename(String::utf8([filename UTF8String]));
+ }
+
+#ifdef TOOLS_ENABLED
+ // Open new instance.
+ if (os && os->get_main_loop()) {
+ List<String> args;
+ args.push_back(os->get_open_with_filename());
+ String exec = os->get_executable_path();
+ os->create_process(exec, args);
+ }
+#endif
+ return YES;
+}
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->send_window_event(ds->get_window(DisplayServerOSX::MAIN_WINDOW_ID), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ }
+ return NSTerminateCancel;
+}
+
+- (void)showAbout:(id)sender {
+ OS_OSX *os = (OS_OSX *)OS::get_singleton();
+ if (os && os->get_main_loop()) {
+ os->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT);
+ }
+}
+
+@end
diff --git a/platform/osx/godot_content_view.h b/platform/osx/godot_content_view.h
new file mode 100644
index 0000000000..7942d716dc
--- /dev/null
+++ b/platform/osx/godot_content_view.h
@@ -0,0 +1,65 @@
+/*************************************************************************/
+/* godot_content_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_CONTENT_VIEW_H
+#define GODOT_CONTENT_VIEW_H
+
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#if defined(GLES3_ENABLED)
+#import <AppKit/NSOpenGLView.h>
+#define RootView NSOpenGLView
+#else
+#define RootView NSView
+#endif
+
+#import <QuartzCore/CAMetalLayer.h>
+
+@interface GodotContentView : RootView <NSTextInputClient> {
+ DisplayServer::WindowID window_id;
+ NSTrackingArea *tracking_area;
+ NSMutableAttributedString *marked_text;
+ bool ime_input_event_in_progress;
+ bool mouse_down_control;
+ bool ignore_momentum_scroll;
+}
+
+- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor;
+- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy;
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed;
+- (void)setWindowID:(DisplayServer::WindowID)wid;
+- (void)cancelComposition;
+
+@end
+
+#endif // GODOT_CONTENT_VIEW_H
diff --git a/platform/osx/godot_content_view.mm b/platform/osx/godot_content_view.mm
new file mode 100644
index 0000000000..e96f0a8098
--- /dev/null
+++ b/platform/osx/godot_content_view.mm
@@ -0,0 +1,764 @@
+/*************************************************************************/
+/* godot_content_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "godot_content_view.h"
+
+#include "display_server_osx.h"
+#include "key_mapping_osx.h"
+
+@implementation GodotContentView
+
+- (id)init {
+ self = [super init];
+ window_id = DisplayServer::INVALID_WINDOW_ID;
+ tracking_area = nil;
+ ime_input_event_in_progress = false;
+ mouse_down_control = false;
+ ignore_momentum_scroll = false;
+ [self updateTrackingAreas];
+
+ if (@available(macOS 10.13, *)) {
+ [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]];
+#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM.
+ } else {
+ [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
+#endif
+ }
+ marked_text = [[NSMutableAttributedString alloc] init];
+ return self;
+}
+
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
+ window_id = wid;
+}
+
+// MARK: Backing Layer
+
+- (CALayer *)makeBackingLayer {
+ return [[CAMetalLayer class] layer];
+}
+
+- (void)updateLayer {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ ds->window_update(window_id);
+ [super updateLayer];
+}
+
+- (BOOL)wantsUpdateLayer {
+ return YES;
+}
+
+- (BOOL)isOpaque {
+ return YES;
+}
+
+// MARK: IME
+
+- (BOOL)hasMarkedText {
+ return (marked_text.length > 0);
+}
+
+- (NSRange)markedRange {
+ return NSMakeRange(0, marked_text.length);
+}
+
+- (NSRange)selectedRange {
+ static const NSRange kEmptyRange = { NSNotFound, 0 };
+ return kEmptyRange;
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ marked_text = [[NSMutableAttributedString alloc] initWithAttributedString:aString];
+ } else {
+ marked_text = [[NSMutableAttributedString alloc] initWithString:aString];
+ }
+ if (marked_text.length == 0) {
+ [self unmarkText];
+ return;
+ }
+
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ if (wd.im_active) {
+ ime_input_event_in_progress = true;
+ ds->update_im_text(Point2i(selectedRange.location, selectedRange.length), String::utf8([[marked_text mutableString] UTF8String]));
+ }
+}
+
+- (void)doCommandBySelector:(SEL)aSelector {
+ [self tryToPerform:aSelector with:self];
+}
+
+- (void)unmarkText {
+ ime_input_event_in_progress = false;
+ [[marked_text mutableString] setString:@""];
+
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ if (wd.im_active) {
+ ds->update_im_text(Point2i(), String());
+ }
+}
+
+- (NSArray *)validAttributesForMarkedText {
+ return [NSArray array];
+}
+
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
+ return nil;
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint {
+ return 0;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return NSMakeRect(0, 0, 0, 0);
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ const NSRect content_rect = [wd.window_view frame];
+ const float scale = ds->screen_get_max_scale();
+ NSRect point_in_window_rect = NSMakeRect(wd.im_position.x / scale, content_rect.size.height - (wd.im_position.y / scale) - 1, 0, 0);
+ NSPoint point_on_screen = [wd.window_object convertRectToScreen:point_in_window_rect].origin;
+
+ return NSMakeRect(point_on_screen.x, point_on_screen.y, 0, 0);
+}
+
+- (void)cancelComposition {
+ [self unmarkText];
+ [[NSTextInputContext currentInputContext] discardMarkedText];
+}
+
+- (void)insertText:(id)aString {
+ [self insertText:aString replacementRange:NSMakeRange(0, 0)];
+}
+
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
+ NSEvent *event = [NSApp currentEvent];
+
+ NSString *characters;
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ characters = [aString string];
+ } else {
+ characters = (NSString *)aString;
+ }
+
+ NSCharacterSet *ctrl_chars = [NSCharacterSet controlCharacterSet];
+ NSCharacterSet *wsnl_chars = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ if ([characters rangeOfCharacterFromSet:ctrl_chars].length && [characters rangeOfCharacterFromSet:wsnl_chars].length == 0) {
+ [[NSTextInputContext currentInputContext] discardMarkedText];
+ [self cancelComposition];
+ return;
+ }
+
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ [self cancelComposition];
+ return;
+ }
+
+ Char16String text;
+ text.resize([characters length] + 1);
+ [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
+
+ String u32text;
+ u32text.parse_utf16(text.ptr(), text.length());
+
+ for (int i = 0; i < u32text.length(); i++) {
+ const char32_t codepoint = u32text[i];
+ if ((codepoint & 0xFF00) == 0xF700) {
+ continue;
+ }
+
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = false;
+ ke.raw = false; // IME input event.
+ ke.keycode = Key::NONE;
+ ke.physical_keycode = Key::NONE;
+ ke.unicode = codepoint;
+
+ ds->push_to_key_event_buffer(ke);
+ }
+ [self cancelComposition];
+}
+
+// MARK: Drag and drop
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
+ return NSDragOperationCopy;
+}
+
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {
+ return NSDragOperationCopy;
+}
+
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return NO;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ if (!wd.drop_files_callback.is_null()) {
+ Vector<String> files;
+ NSPasteboard *pboard = [sender draggingPasteboard];
+
+ if (@available(macOS 10.13, *)) {
+ NSArray *items = pboard.pasteboardItems;
+ for (NSPasteboardItem *item in items) {
+ NSString *url = [item stringForType:NSPasteboardTypeFileURL];
+ NSString *file = [NSURL URLWithString:url].path;
+ files.push_back(String::utf8([file UTF8String]));
+ }
+#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM.
+ } else {
+ NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
+ for (NSString *file in filenames) {
+ files.push_back(String::utf8([file UTF8String]));
+ }
+#endif
+ }
+
+ Variant v = files;
+ Variant *vp = &v;
+ Variant ret;
+ Callable::CallError ce;
+ wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+ }
+
+ return NO;
+}
+
+// MARK: Focus
+
+- (BOOL)canBecomeKeyView {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return YES;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ return !wd.no_focus && !wd.is_popup;
+}
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+// MARK: Mouse
+
+- (void)cursorUpdate:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds) {
+ return;
+ }
+
+ ds->cursor_update_shape();
+}
+
+- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ MouseButton last_button_state = ds->mouse_get_button_state();
+
+ if (pressed) {
+ last_button_state |= mask;
+ } else {
+ last_button_state &= (MouseButton)~mask;
+ }
+ ds->mouse_set_button_state(last_button_state);
+
+ Ref<InputEventMouseButton> mb;
+ mb.instantiate();
+ mb->set_window_id(window_id);
+ ds->update_mouse_pos(wd, [event locationInWindow]);
+ ds->get_key_modifier_state([event modifierFlags], mb);
+ mb->set_button_index(index);
+ mb->set_pressed(pressed);
+ mb->set_position(wd.mouse_pos);
+ mb->set_global_position(wd.mouse_pos);
+ mb->set_button_mask(last_button_state);
+ if (index == MouseButton::LEFT && pressed) {
+ mb->set_double_click([event clickCount] == 2);
+ }
+
+ Input::get_singleton()->parse_input_event(mb);
+}
+
+- (void)mouseDown:(NSEvent *)event {
+ if (([event modifierFlags] & NSEventModifierFlagControl)) {
+ mouse_down_control = true;
+ [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true];
+ } else {
+ mouse_down_control = false;
+ [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:true];
+ }
+}
+
+- (void)mouseDragged:(NSEvent *)event {
+ [self mouseMoved:event];
+}
+
+- (void)mouseUp:(NSEvent *)event {
+ if (mouse_down_control) {
+ [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false];
+ } else {
+ [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:false];
+ }
+}
+
+- (void)mouseMoved:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ NSPoint delta = NSMakePoint([event deltaX], [event deltaY]);
+ NSPoint mpos = [event locationInWindow];
+
+ if (ds->update_mouse_wrap(wd, delta, mpos, [event timestamp])) {
+ return;
+ }
+
+ Ref<InputEventMouseMotion> mm;
+ mm.instantiate();
+
+ mm->set_window_id(window_id);
+ mm->set_button_mask(ds->mouse_get_button_state());
+ ds->update_mouse_pos(wd, mpos);
+ mm->set_position(wd.mouse_pos);
+ mm->set_pressure([event pressure]);
+ if ([event subtype] == NSEventSubtypeTabletPoint) {
+ const NSPoint p = [event tilt];
+ mm->set_tilt(Vector2(p.x, p.y));
+ }
+ mm->set_global_position(wd.mouse_pos);
+ mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
+ const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * ds->screen_get_max_scale();
+ mm->set_relative(relativeMotion);
+ ds->get_key_modifier_state([event modifierFlags], mm);
+
+ Input::get_singleton()->parse_input_event(mm);
+}
+
+- (void)rightMouseDown:(NSEvent *)event {
+ [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true];
+}
+
+- (void)rightMouseDragged:(NSEvent *)event {
+ [self mouseMoved:event];
+}
+
+- (void)rightMouseUp:(NSEvent *)event {
+ [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false];
+}
+
+- (void)otherMouseDown:(NSEvent *)event {
+ if ((int)[event buttonNumber] == 2) {
+ [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:true];
+ } else if ((int)[event buttonNumber] == 3) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:true];
+ } else if ((int)[event buttonNumber] == 4) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:true];
+ } else {
+ return;
+ }
+}
+
+- (void)otherMouseDragged:(NSEvent *)event {
+ [self mouseMoved:event];
+}
+
+- (void)otherMouseUp:(NSEvent *)event {
+ if ((int)[event buttonNumber] == 2) {
+ [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:false];
+ } else if ((int)[event buttonNumber] == 3) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:false];
+ } else if ((int)[event buttonNumber] == 4) {
+ [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:false];
+ } else {
+ return;
+ }
+}
+
+- (void)mouseExited:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) {
+ ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT);
+ }
+}
+
+- (void)mouseEntered:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) {
+ ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER);
+ }
+
+ ds->cursor_update_shape();
+}
+
+- (void)magnifyWithEvent:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ Ref<InputEventMagnifyGesture> ev;
+ ev.instantiate();
+ ev->set_window_id(window_id);
+ ds->get_key_modifier_state([event modifierFlags], ev);
+ ds->update_mouse_pos(wd, [event locationInWindow]);
+ ev->set_position(wd.mouse_pos);
+ ev->set_factor([event magnification] + 1.0);
+
+ Input::get_singleton()->parse_input_event(ev);
+}
+
+- (void)updateTrackingAreas {
+ if (tracking_area != nil) {
+ [self removeTrackingArea:tracking_area];
+ }
+
+ NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect;
+ tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil];
+
+ [self addTrackingArea:tracking_area];
+ [super updateTrackingAreas];
+}
+
+// MARK: Keyboard
+
+- (void)keyDown:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ ignore_momentum_scroll = true;
+
+ // Ignore all input if IME input is in progress.
+ if (!ime_input_event_in_progress) {
+ NSString *characters = [event characters];
+ NSUInteger length = [characters length];
+
+ if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) {
+ // Fallback unicode character handler used if IME is not active.
+ Char16String text;
+ text.resize([characters length] + 1);
+ [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
+
+ String u32text;
+ u32text.parse_utf16(text.ptr(), text.length());
+
+ for (int i = 0; i < u32text.length(); i++) {
+ const char32_t codepoint = u32text[i];
+
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = [event isARepeat];
+ ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]);
+ ke.raw = true;
+ ke.unicode = codepoint;
+
+ ds->push_to_key_event_buffer(ke);
+ }
+ } else {
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = [event isARepeat];
+ ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]);
+ ke.raw = false;
+ ke.unicode = 0;
+
+ ds->push_to_key_event_buffer(ke);
+ }
+ }
+
+ // Pass events to IME handler
+ if (wd.im_active) {
+ [self interpretKeyEvents:[NSArray arrayWithObject:event]];
+ }
+}
+
+- (void)flagsChanged:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+ ignore_momentum_scroll = true;
+
+ // Ignore all input if IME input is in progress
+ if (!ime_input_event_in_progress) {
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.echo = false;
+ ke.raw = true;
+
+ int key = [event keyCode];
+ int mod = [event modifierFlags];
+
+ if (key == 0x36 || key == 0x37) {
+ if (mod & NSEventModifierFlagCommand) {
+ mod &= ~NSEventModifierFlagCommand;
+ ke.pressed = true;
+ } else {
+ ke.pressed = false;
+ }
+ } else if (key == 0x38 || key == 0x3c) {
+ if (mod & NSEventModifierFlagShift) {
+ mod &= ~NSEventModifierFlagShift;
+ ke.pressed = true;
+ } else {
+ ke.pressed = false;
+ }
+ } else if (key == 0x3a || key == 0x3d) {
+ if (mod & NSEventModifierFlagOption) {
+ mod &= ~NSEventModifierFlagOption;
+ ke.pressed = true;
+ } else {
+ ke.pressed = false;
+ }
+ } else if (key == 0x3b || key == 0x3e) {
+ if (mod & NSEventModifierFlagControl) {
+ mod &= ~NSEventModifierFlagControl;
+ ke.pressed = true;
+ } else {
+ ke.pressed = false;
+ }
+ } else {
+ return;
+ }
+
+ ke.osx_state = mod;
+ ke.keycode = KeyMappingOSX::remap_key(key, mod);
+ ke.physical_keycode = KeyMappingOSX::translate_key(key);
+ ke.unicode = 0;
+
+ ds->push_to_key_event_buffer(ke);
+ }
+}
+
+- (void)keyUp:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ // Ignore all input if IME input is in progress.
+ if (!ime_input_event_in_progress) {
+ NSString *characters = [event characters];
+ NSUInteger length = [characters length];
+
+ // Fallback unicode character handler used if IME is not active.
+ if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) {
+ Char16String text;
+ text.resize([characters length] + 1);
+ [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])];
+
+ String u32text;
+ u32text.parse_utf16(text.ptr(), text.length());
+
+ for (int i = 0; i < u32text.length(); i++) {
+ const char32_t codepoint = u32text[i];
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = false;
+ ke.echo = [event isARepeat];
+ ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]);
+ ke.raw = true;
+ ke.unicode = codepoint;
+
+ ds->push_to_key_event_buffer(ke);
+ }
+ } else {
+ DisplayServerOSX::KeyEvent ke;
+
+ ke.window_id = window_id;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = false;
+ ke.echo = [event isARepeat];
+ ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]);
+ ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]);
+ ke.raw = true;
+ ke.unicode = 0;
+
+ ds->push_to_key_event_buffer(ke);
+ }
+ }
+}
+
+// MARK: Scroll and pan
+
+- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ MouseButton mask = mouse_button_to_mask(button);
+
+ Ref<InputEventMouseButton> sc;
+ sc.instantiate();
+
+ sc->set_window_id(window_id);
+ ds->get_key_modifier_state([event modifierFlags], sc);
+ sc->set_button_index(button);
+ sc->set_factor(factor);
+ sc->set_pressed(true);
+ sc->set_position(wd.mouse_pos);
+ sc->set_global_position(wd.mouse_pos);
+ MouseButton last_button_state = ds->mouse_get_button_state() | (MouseButton)mask;
+ sc->set_button_mask(last_button_state);
+ ds->mouse_set_button_state(last_button_state);
+
+ Input::get_singleton()->parse_input_event(sc);
+
+ sc.instantiate();
+ sc->set_window_id(window_id);
+ sc->set_button_index(button);
+ sc->set_factor(factor);
+ sc->set_pressed(false);
+ sc->set_position(wd.mouse_pos);
+ sc->set_global_position(wd.mouse_pos);
+ last_button_state &= (MouseButton)~mask;
+ sc->set_button_mask(last_button_state);
+ ds->mouse_set_button_state(last_button_state);
+
+ Input::get_singleton()->parse_input_event(sc);
+}
+
+- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ Ref<InputEventPanGesture> pg;
+ pg.instantiate();
+
+ pg->set_window_id(window_id);
+ ds->get_key_modifier_state([event modifierFlags], pg);
+ pg->set_position(wd.mouse_pos);
+ pg->set_delta(Vector2(-dx, -dy));
+
+ Input::get_singleton()->parse_input_event(pg);
+}
+
+- (void)scrollWheel:(NSEvent *)event {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ ds->update_mouse_pos(wd, [event locationInWindow]);
+
+ double delta_x = [event scrollingDeltaX];
+ double delta_y = [event scrollingDeltaY];
+
+ if ([event hasPreciseScrollingDeltas]) {
+ delta_x *= 0.03;
+ delta_y *= 0.03;
+ }
+
+ if ([event momentumPhase] != NSEventPhaseNone) {
+ if (ignore_momentum_scroll) {
+ return;
+ }
+ } else {
+ ignore_momentum_scroll = false;
+ }
+
+ if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) {
+ [self processPanEvent:event dx:delta_x dy:delta_y];
+ } else {
+ if (fabs(delta_x)) {
+ [self processScrollEvent:event button:(0 > delta_x ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT) factor:fabs(delta_x * 0.3)];
+ }
+ if (fabs(delta_y)) {
+ [self processScrollEvent:event button:(0 < delta_y ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN) factor:fabs(delta_y * 0.3)];
+ }
+ }
+}
+
+@end
diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm
index 64a93f7292..f3db363151 100644
--- a/platform/osx/godot_main_osx.mm
+++ b/platform/osx/godot_main_osx.mm
@@ -37,7 +37,7 @@
int main(int argc, char **argv) {
#if defined(VULKAN_ENABLED)
- // MoltenVK - enable full component swizzling support
+ // MoltenVK - enable full component swizzling support.
setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
#endif
@@ -45,13 +45,14 @@ int main(int argc, char **argv) {
const char *dbg_arg = "-NSDocumentRevisionsDebugMode";
printf("arguments\n");
for (int i = 0; i < argc; i++) {
- if (strcmp(dbg_arg, argv[i]) == 0)
+ if (strcmp(dbg_arg, argv[i]) == 0) {
first_arg = i + 2;
+ }
printf("%i: %s\n", i, argv[i]);
- };
+ }
#ifdef DEBUG_ENABLED
- // lets report the path we made current after all that
+ // Lets report the path we made current after all that.
char cwd[4096];
getcwd(cwd, 4096);
printf("Current path: %s\n", cwd);
@@ -60,25 +61,27 @@ int main(int argc, char **argv) {
OS_OSX os;
Error err;
- // We must override main when testing is enabled
+ // We must override main when testing is enabled.
TEST_MAIN_OVERRIDE
- if (os.open_with_filename != "") {
- char *argv_c = (char *)malloc(os.open_with_filename.utf8().size());
- memcpy(argv_c, os.open_with_filename.utf8().get_data(), os.open_with_filename.utf8().size());
+ if (os.get_open_with_filename() != "") {
+ char *argv_c = (char *)malloc(os.get_open_with_filename().utf8().size());
+ memcpy(argv_c, os.get_open_with_filename().utf8().get_data(), os.get_open_with_filename().utf8().size());
err = Main::setup(argv[0], 1, &argv_c);
free(argv_c);
} else {
err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]);
}
- if (err != OK)
+ if (err != OK) {
return 255;
+ }
- if (Main::start())
- os.run(); // it is actually the OS that decides how to run
+ if (Main::start()) {
+ os.run(); // It is actually the OS that decides how to run.
+ }
Main::cleanup();
return os.get_exit_code();
-};
+}
diff --git a/platform/osx/godot_menu_item.h b/platform/osx/godot_menu_item.h
new file mode 100644
index 0000000000..50c4709c18
--- /dev/null
+++ b/platform/osx/godot_menu_item.h
@@ -0,0 +1,52 @@
+/*************************************************************************/
+/* godot_menu_item.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_MENU_ITEM_H
+#define GODOT_MENU_ITEM_H
+
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotMenuItem : NSObject {
+@public
+ Callable callback;
+ Variant meta;
+ int id;
+ bool checkable;
+}
+
+@end
+
+@implementation GodotMenuItem
+@end
+
+#endif // GODOT_MENU_ITEM_H
diff --git a/platform/osx/godot_window.h b/platform/osx/godot_window.h
new file mode 100644
index 0000000000..16ff101142
--- /dev/null
+++ b/platform/osx/godot_window.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* godot_window.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_WINDOW_H
+#define GODOT_WINDOW_H
+
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotWindow : NSWindow {
+ DisplayServer::WindowID window_id;
+}
+
+- (void)setWindowID:(DisplayServer::WindowID)wid;
+
+@end
+
+#endif //GODOT_WINDOW_H
diff --git a/platform/osx/godot_window.mm b/platform/osx/godot_window.mm
new file mode 100644
index 0000000000..d43853a94b
--- /dev/null
+++ b/platform/osx/godot_window.mm
@@ -0,0 +1,69 @@
+/*************************************************************************/
+/* godot_window.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "godot_window.h"
+
+#include "display_server_osx.h"
+
+@implementation GodotWindow
+
+- (id)init {
+ self = [super init];
+ window_id = DisplayServer::INVALID_WINDOW_ID;
+ return self;
+}
+
+- (void)setWindowID:(DisplayServerOSX::WindowID)wid {
+ window_id = wid;
+}
+
+- (BOOL)canBecomeKeyWindow {
+ // Required for NSWindowStyleMaskBorderless windows.
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return YES;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ return !wd.no_focus && !wd.is_popup;
+}
+
+- (BOOL)canBecomeMainWindow {
+ // Required for NSWindowStyleMaskBorderless windows.
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return YES;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ return !wd.no_focus && !wd.is_popup;
+}
+
+@end
diff --git a/platform/osx/godot_window_delegate.h b/platform/osx/godot_window_delegate.h
new file mode 100644
index 0000000000..8a1f681fcd
--- /dev/null
+++ b/platform/osx/godot_window_delegate.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* godot_window_delegate.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_WINDOW_DELEGATE_H
+#define GODOT_WINDOW_DELEGATE_H
+
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+@interface GodotWindowDelegate : NSObject <NSWindowDelegate> {
+ DisplayServer::WindowID window_id;
+}
+
+- (void)setWindowID:(DisplayServer::WindowID)wid;
+
+@end
+
+#endif //GODOT_WINDOW_DELEGATE_H
diff --git a/platform/osx/godot_window_delegate.mm b/platform/osx/godot_window_delegate.mm
new file mode 100644
index 0000000000..9f49a6a4e9
--- /dev/null
+++ b/platform/osx/godot_window_delegate.mm
@@ -0,0 +1,270 @@
+/*************************************************************************/
+/* godot_window_delegate.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "godot_window_delegate.h"
+
+#include "display_server_osx.h"
+
+@implementation GodotWindowDelegate
+
+- (void)setWindowID:(DisplayServer::WindowID)wid {
+ window_id = wid;
+}
+
+- (BOOL)windowShouldClose:(id)sender {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return YES;
+ }
+
+ ds->send_window_event(ds->get_window(window_id), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ return NO;
+}
+
+- (void)windowWillClose:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ ds->popup_close(window_id);
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ while (wd.transient_children.size()) {
+ ds->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID);
+ }
+
+ if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) {
+ ds->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID);
+ }
+
+ ds->window_destroy(window_id);
+}
+
+- (void)windowDidEnterFullScreen:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ wd.fullscreen = true;
+ // Reset window size limits.
+ [wd.window_object setContentMinSize:NSMakeSize(0, 0)];
+ [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+
+ // Force window resize event.
+ [self windowDidResize:notification];
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ wd.fullscreen = false;
+
+ // Set window size limits.
+ const float scale = ds->screen_get_max_scale();
+ if (wd.min_size != Size2i()) {
+ Size2i size = wd.min_size / scale;
+ [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+ }
+ if (wd.max_size != Size2i()) {
+ Size2i size = wd.max_size / scale;
+ [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+ }
+
+ // Restore resizability state.
+ if (wd.resize_disabled) {
+ [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+ }
+
+ // Restore on-top state.
+ if (wd.on_top) {
+ [wd.window_object setLevel:NSFloatingWindowLevel];
+ }
+
+ // Force window resize event.
+ [self windowDidResize:notification];
+}
+
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ CGFloat new_scale_factor = [wd.window_object backingScaleFactor];
+ CGFloat old_scale_factor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
+
+ if (new_scale_factor != old_scale_factor) {
+ // Set new display scale and window size.
+ const float scale = ds->screen_get_max_scale();
+ const NSRect content_rect = [wd.window_view frame];
+
+ wd.size.width = content_rect.size.width * scale;
+ wd.size.height = content_rect.size.height * scale;
+
+ ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE);
+
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ layer.contentsScale = scale;
+ }
+
+ //Force window resize event
+ [self windowDidResize:notification];
+ }
+}
+
+- (void)windowWillStartLiveResize:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->set_is_resizing(true);
+ }
+}
+
+- (void)windowDidEndLiveResize:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->set_is_resizing(false);
+ }
+}
+
+- (void)windowDidResize:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ const NSRect content_rect = [wd.window_view frame];
+ const float scale = ds->screen_get_max_scale();
+ wd.size.width = content_rect.size.width * scale;
+ wd.size.height = content_rect.size.height * scale;
+
+ CALayer *layer = [wd.window_view layer];
+ if (layer) {
+ layer.contentsScale = scale;
+ }
+
+ ds->window_resize(window_id, wd.size.width, wd.size.height);
+
+ if (!wd.rect_changed_callback.is_null()) {
+ Variant size = Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id));
+ Variant *sizep = &size;
+ Variant ret;
+ Callable::CallError ce;
+ wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+ }
+}
+
+- (void)windowDidMove:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+ ds->release_pressed_events();
+
+ if (!wd.rect_changed_callback.is_null()) {
+ Variant size = Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id));
+ Variant *sizep = &size;
+ Variant ret;
+ Callable::CallError ce;
+ wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+ }
+}
+
+- (void)windowDidBecomeKey:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ if (ds->mouse_get_mode() == DisplayServer::MOUSE_MODE_CAPTURED) {
+ const NSRect content_rect = [wd.window_view frame];
+ NSRect point_in_window_rect = NSMakeRect(content_rect.size.width / 2, content_rect.size.height / 2, 0, 0);
+ NSPoint point_on_screen = [[wd.window_view window] convertRectToScreen:point_in_window_rect].origin;
+ CGPoint mouse_warp_pos = { point_on_screen.x, CGDisplayBounds(CGMainDisplayID()).size.height - point_on_screen.y };
+ CGWarpMouseCursorPosition(mouse_warp_pos);
+ } else {
+ ds->update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
+ }
+
+ ds->set_last_focused_window(window_id);
+ ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
+}
+
+- (void)windowDidResignKey:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ ds->release_pressed_events();
+ ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
+}
+
+- (void)windowDidMiniaturize:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ ds->release_pressed_events();
+ ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT);
+}
+
+- (void)windowDidDeminiaturize:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+
+ DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
+
+ ds->set_last_focused_window(window_id);
+ ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN);
+}
+
+@end
diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp
index 2152b34aff..7d31ede61d 100644
--- a/platform/osx/joypad_osx.cpp
+++ b/platform/osx/joypad_osx.cpp
@@ -80,7 +80,7 @@ int joypad::get_hid_element_state(rec_element *p_element) const {
if (IOHIDDeviceGetValue(device_ref, p_element->ref, &valueRef) == kIOReturnSuccess) {
value = (SInt32)IOHIDValueGetIntegerValue(valueRef);
- /* record min and max for auto calibration */
+ // Record min and max for auto calibration.
if (value < p_element->min) {
p_element->min = value;
}
@@ -179,7 +179,7 @@ void joypad::add_hid_element(IOHIDElementRef p_element) {
break;
}
- if (list) { /* add to list */
+ if (list) { // Add to list.
rec_element element;
element.ref = p_element;
@@ -280,7 +280,7 @@ static String _hex_str(uint8_t p_byte) {
bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) {
p_joy->device_ref = p_device_ref;
- /* get device name */
+ // Get device name.
String name;
char c_name[256];
CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey));
@@ -319,13 +319,14 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) {
sprintf(uid, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version));
input->joy_connection_changed(id, true, name, uid);
} else {
- //bluetooth device
+ // Bluetooth device.
String guid = "05000000";
for (int i = 0; i < 12; i++) {
- if (i < name.size())
+ if (i < name.size()) {
guid += _hex_str(name[i]);
- else
+ } else {
guid += "00";
+ }
}
input->joy_connection_changed(id, true, name, guid);
}
@@ -381,8 +382,9 @@ bool joypad::check_ff_features() {
if (ret == FF_OK && (features.supportedEffects & FFCAP_ET_CONSTANTFORCE)) {
uint32_t val;
ret = FFDeviceGetForceFeedbackProperty(ff_device, FFPROP_FFGAIN, &val, sizeof(val));
- if (ret != FF_OK)
+ if (ret != FF_OK) {
return false;
+ }
int num_axes = features.numFfAxes;
ff_axes = (DWORD *)memalloc(sizeof(DWORD) * num_axes);
ff_directions = (LONG *)memalloc(sizeof(LONG) * num_axes);
@@ -445,24 +447,13 @@ static HatMask process_hat_value(int p_min, int p_max, int p_value, bool p_offse
void JoypadOSX::poll_joypads() const {
while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
- /* no-op. Pending callbacks will fire. */
+ // No-op. Pending callbacks will fire.
}
}
-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) {
- jx.value = (float)-p_value / p_min;
- } else
- jx.value = (float)p_value / p_max;
- }
- if (p_min == 0) {
- jx.min = 0;
- jx.value = 0.0f + (float)p_value / p_max;
- }
- return jx;
+static float axis_correct(int p_value, int p_min, int p_max) {
+ // Convert to a value between -1.0f and 1.0f.
+ return 2.0f * (p_value - p_min) / (p_max - p_min) - 1.0f;
}
void JoypadOSX::process_joypads() {
@@ -520,16 +511,18 @@ void JoypadOSX::joypad_vibration_stop(int p_id, uint64_t p_timestamp) {
int JoypadOSX::get_joy_index(int p_id) const {
for (int i = 0; i < device_list.size(); i++) {
- if (device_list[i].id == p_id)
+ if (device_list[i].id == p_id) {
return i;
+ }
}
return -1;
}
int JoypadOSX::get_joy_ref(IOHIDDeviceRef p_device) const {
for (int i = 0; i < device_list.size(); i++) {
- if (device_list[i].device_ref == p_device)
+ if (device_list[i].device_ref == p_device) {
return i;
+ }
}
return -1;
}
@@ -579,7 +572,7 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const {
IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE);
while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
- /* no-op. Callback fires once per existing device. */
+ // No-op. Callback fires once per existing device.
}
}
diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h
index 8ea1033a77..4ca7fb1698 100644
--- a/platform/osx/joypad_osx.h
+++ b/platform/osx/joypad_osx.h
@@ -66,7 +66,7 @@ struct joypad {
int id = 0;
bool offset_hat = false;
- io_service_t ffservice = 0; /* Interface for force feedback, 0 = no ff */
+ io_service_t ffservice = 0; // Interface for force feedback, 0 = no ff.
FFCONSTANTFORCE ff_constant_force;
FFDeviceObjectReference ff_device = nullptr;
FFEffectObjectReference ff_object = nullptr;
diff --git a/platform/osx/key_mapping_osx.h b/platform/osx/key_mapping_osx.h
new file mode 100644
index 0000000000..252cc907bb
--- /dev/null
+++ b/platform/osx/key_mapping_osx.h
@@ -0,0 +1,52 @@
+/*************************************************************************/
+/* key_mapping_osx.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 KEY_MAPPING_OSX_H
+#define KEY_MAPPING_OSX_H
+
+#include "core/os/keyboard.h"
+
+class KeyMappingOSX {
+ KeyMappingOSX() {}
+
+ static bool is_numpad_key(unsigned int key);
+
+public:
+ // Mappings input.
+ static Key translate_key(unsigned int key);
+ static unsigned int unmap_key(Key key);
+ static Key remap_key(unsigned int key, unsigned int state);
+
+ // Mapping for menu shortcuts.
+ static String keycode_get_native_string(Key p_keycode);
+ static unsigned int keycode_get_native_mask(Key p_keycode);
+};
+
+#endif // KEY_MAPPING_OSX_H
diff --git a/platform/osx/key_mapping_osx.mm b/platform/osx/key_mapping_osx.mm
new file mode 100644
index 0000000000..fde9206824
--- /dev/null
+++ b/platform/osx/key_mapping_osx.mm
@@ -0,0 +1,477 @@
+/*************************************************************************/
+/* key_mapping_osx.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "key_mapping_osx.h"
+
+#include <Carbon/Carbon.h>
+#include <Cocoa/Cocoa.h>
+
+bool KeyMappingOSX::is_numpad_key(unsigned int key) {
+ static const unsigned int table[] = {
+ 0x41, /* kVK_ANSI_KeypadDecimal */
+ 0x43, /* kVK_ANSI_KeypadMultiply */
+ 0x45, /* kVK_ANSI_KeypadPlus */
+ 0x47, /* kVK_ANSI_KeypadClear */
+ 0x4b, /* kVK_ANSI_KeypadDivide */
+ 0x4c, /* kVK_ANSI_KeypadEnter */
+ 0x4e, /* kVK_ANSI_KeypadMinus */
+ 0x51, /* kVK_ANSI_KeypadEquals */
+ 0x52, /* kVK_ANSI_Keypad0 */
+ 0x53, /* kVK_ANSI_Keypad1 */
+ 0x54, /* kVK_ANSI_Keypad2 */
+ 0x55, /* kVK_ANSI_Keypad3 */
+ 0x56, /* kVK_ANSI_Keypad4 */
+ 0x57, /* kVK_ANSI_Keypad5 */
+ 0x58, /* kVK_ANSI_Keypad6 */
+ 0x59, /* kVK_ANSI_Keypad7 */
+ 0x5b, /* kVK_ANSI_Keypad8 */
+ 0x5c, /* kVK_ANSI_Keypad9 */
+ 0x5f, /* kVK_JIS_KeypadComma */
+ 0x00
+ };
+ for (int i = 0; table[i] != 0; i++) {
+ if (key == table[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Keyboard symbol translation table.
+static const Key _osx_to_godot_table[128] = {
+ /* 00 */ Key::A,
+ /* 01 */ Key::S,
+ /* 02 */ Key::D,
+ /* 03 */ Key::F,
+ /* 04 */ Key::H,
+ /* 05 */ Key::G,
+ /* 06 */ Key::Z,
+ /* 07 */ Key::X,
+ /* 08 */ Key::C,
+ /* 09 */ Key::V,
+ /* 0a */ Key::SECTION, /* ISO Section */
+ /* 0b */ Key::B,
+ /* 0c */ Key::Q,
+ /* 0d */ Key::W,
+ /* 0e */ Key::E,
+ /* 0f */ Key::R,
+ /* 10 */ Key::Y,
+ /* 11 */ Key::T,
+ /* 12 */ Key::KEY_1,
+ /* 13 */ Key::KEY_2,
+ /* 14 */ Key::KEY_3,
+ /* 15 */ Key::KEY_4,
+ /* 16 */ Key::KEY_6,
+ /* 17 */ Key::KEY_5,
+ /* 18 */ Key::EQUAL,
+ /* 19 */ Key::KEY_9,
+ /* 1a */ Key::KEY_7,
+ /* 1b */ Key::MINUS,
+ /* 1c */ Key::KEY_8,
+ /* 1d */ Key::KEY_0,
+ /* 1e */ Key::BRACERIGHT,
+ /* 1f */ Key::O,
+ /* 20 */ Key::U,
+ /* 21 */ Key::BRACELEFT,
+ /* 22 */ Key::I,
+ /* 23 */ Key::P,
+ /* 24 */ Key::ENTER,
+ /* 25 */ Key::L,
+ /* 26 */ Key::J,
+ /* 27 */ Key::APOSTROPHE,
+ /* 28 */ Key::K,
+ /* 29 */ Key::SEMICOLON,
+ /* 2a */ Key::BACKSLASH,
+ /* 2b */ Key::COMMA,
+ /* 2c */ Key::SLASH,
+ /* 2d */ Key::N,
+ /* 2e */ Key::M,
+ /* 2f */ Key::PERIOD,
+ /* 30 */ Key::TAB,
+ /* 31 */ Key::SPACE,
+ /* 32 */ Key::QUOTELEFT,
+ /* 33 */ Key::BACKSPACE,
+ /* 34 */ Key::UNKNOWN,
+ /* 35 */ Key::ESCAPE,
+ /* 36 */ Key::META,
+ /* 37 */ Key::META,
+ /* 38 */ Key::SHIFT,
+ /* 39 */ Key::CAPSLOCK,
+ /* 3a */ Key::ALT,
+ /* 3b */ Key::CTRL,
+ /* 3c */ Key::SHIFT,
+ /* 3d */ Key::ALT,
+ /* 3e */ Key::CTRL,
+ /* 3f */ Key::UNKNOWN, /* Function */
+ /* 40 */ Key::UNKNOWN, /* F17 */
+ /* 41 */ Key::KP_PERIOD,
+ /* 42 */ Key::UNKNOWN,
+ /* 43 */ Key::KP_MULTIPLY,
+ /* 44 */ Key::UNKNOWN,
+ /* 45 */ Key::KP_ADD,
+ /* 46 */ Key::UNKNOWN,
+ /* 47 */ Key::NUMLOCK, /* Really KeypadClear... */
+ /* 48 */ Key::VOLUMEUP, /* VolumeUp */
+ /* 49 */ Key::VOLUMEDOWN, /* VolumeDown */
+ /* 4a */ Key::VOLUMEMUTE, /* Mute */
+ /* 4b */ Key::KP_DIVIDE,
+ /* 4c */ Key::KP_ENTER,
+ /* 4d */ Key::UNKNOWN,
+ /* 4e */ Key::KP_SUBTRACT,
+ /* 4f */ Key::UNKNOWN, /* F18 */
+ /* 50 */ Key::UNKNOWN, /* F19 */
+ /* 51 */ Key::EQUAL, /* KeypadEqual */
+ /* 52 */ Key::KP_0,
+ /* 53 */ Key::KP_1,
+ /* 54 */ Key::KP_2,
+ /* 55 */ Key::KP_3,
+ /* 56 */ Key::KP_4,
+ /* 57 */ Key::KP_5,
+ /* 58 */ Key::KP_6,
+ /* 59 */ Key::KP_7,
+ /* 5a */ Key::UNKNOWN, /* F20 */
+ /* 5b */ Key::KP_8,
+ /* 5c */ Key::KP_9,
+ /* 5d */ Key::YEN, /* JIS Yen */
+ /* 5e */ Key::UNDERSCORE, /* JIS Underscore */
+ /* 5f */ Key::COMMA, /* JIS KeypadComma */
+ /* 60 */ Key::F5,
+ /* 61 */ Key::F6,
+ /* 62 */ Key::F7,
+ /* 63 */ Key::F3,
+ /* 64 */ Key::F8,
+ /* 65 */ Key::F9,
+ /* 66 */ Key::UNKNOWN, /* JIS Eisu */
+ /* 67 */ Key::F11,
+ /* 68 */ Key::UNKNOWN, /* JIS Kana */
+ /* 69 */ Key::F13,
+ /* 6a */ Key::F16,
+ /* 6b */ Key::F14,
+ /* 6c */ Key::UNKNOWN,
+ /* 6d */ Key::F10,
+ /* 6e */ Key::MENU,
+ /* 6f */ Key::F12,
+ /* 70 */ Key::UNKNOWN,
+ /* 71 */ Key::F15,
+ /* 72 */ Key::INSERT, /* Really Help... */
+ /* 73 */ Key::HOME,
+ /* 74 */ Key::PAGEUP,
+ /* 75 */ Key::KEY_DELETE,
+ /* 76 */ Key::F4,
+ /* 77 */ Key::END,
+ /* 78 */ Key::F2,
+ /* 79 */ Key::PAGEDOWN,
+ /* 7a */ Key::F1,
+ /* 7b */ Key::LEFT,
+ /* 7c */ Key::RIGHT,
+ /* 7d */ Key::DOWN,
+ /* 7e */ Key::UP,
+ /* 7f */ Key::UNKNOWN,
+};
+
+// Translates a OS X keycode to a Godot keycode.
+Key KeyMappingOSX::translate_key(unsigned int key) {
+ if (key >= 128) {
+ return Key::UNKNOWN;
+ }
+
+ return _osx_to_godot_table[key];
+}
+
+// Translates a Godot keycode back to a OSX keycode.
+unsigned int KeyMappingOSX::unmap_key(Key key) {
+ for (int i = 0; i <= 126; i++) {
+ if (_osx_to_godot_table[i] == key) {
+ return i;
+ }
+ }
+ return 127;
+}
+
+struct _KeyCodeMap {
+ UniChar kchar;
+ Key kcode;
+};
+
+static const _KeyCodeMap _keycodes[55] = {
+ { '`', Key::QUOTELEFT },
+ { '~', Key::ASCIITILDE },
+ { '0', Key::KEY_0 },
+ { '1', Key::KEY_1 },
+ { '2', Key::KEY_2 },
+ { '3', Key::KEY_3 },
+ { '4', Key::KEY_4 },
+ { '5', Key::KEY_5 },
+ { '6', Key::KEY_6 },
+ { '7', Key::KEY_7 },
+ { '8', Key::KEY_8 },
+ { '9', Key::KEY_9 },
+ { '-', Key::MINUS },
+ { '_', Key::UNDERSCORE },
+ { '=', Key::EQUAL },
+ { '+', Key::PLUS },
+ { 'q', Key::Q },
+ { 'w', Key::W },
+ { 'e', Key::E },
+ { 'r', Key::R },
+ { 't', Key::T },
+ { 'y', Key::Y },
+ { 'u', Key::U },
+ { 'i', Key::I },
+ { 'o', Key::O },
+ { 'p', Key::P },
+ { '[', Key::BRACELEFT },
+ { ']', Key::BRACERIGHT },
+ { '{', Key::BRACELEFT },
+ { '}', Key::BRACERIGHT },
+ { 'a', Key::A },
+ { 's', Key::S },
+ { 'd', Key::D },
+ { 'f', Key::F },
+ { 'g', Key::G },
+ { 'h', Key::H },
+ { 'j', Key::J },
+ { 'k', Key::K },
+ { 'l', Key::L },
+ { ';', Key::SEMICOLON },
+ { ':', Key::COLON },
+ { '\'', Key::APOSTROPHE },
+ { '\"', Key::QUOTEDBL },
+ { '\\', Key::BACKSLASH },
+ { '#', Key::NUMBERSIGN },
+ { 'z', Key::Z },
+ { 'x', Key::X },
+ { 'c', Key::C },
+ { 'v', Key::V },
+ { 'b', Key::B },
+ { 'n', Key::N },
+ { 'm', Key::M },
+ { ',', Key::COMMA },
+ { '.', Key::PERIOD },
+ { '/', Key::SLASH }
+};
+
+// Remap key according to current keyboard layout.
+Key KeyMappingOSX::remap_key(unsigned int key, unsigned int state) {
+ if (is_numpad_key(key)) {
+ return translate_key(key);
+ }
+
+ TISInputSourceRef current_keyboard = TISCopyCurrentKeyboardInputSource();
+ if (!current_keyboard) {
+ return translate_key(key);
+ }
+
+ CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData);
+ if (!layout_data) {
+ return translate_key(key);
+ }
+
+ const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
+
+ UInt32 keys_down = 0;
+ UniChar chars[4];
+ UniCharCount real_length;
+
+ OSStatus err = UCKeyTranslate(keyboard_layout,
+ key,
+ kUCKeyActionDisplay,
+ (state >> 8) & 0xFF,
+ LMGetKbdType(),
+ kUCKeyTranslateNoDeadKeysBit,
+ &keys_down,
+ sizeof(chars) / sizeof(chars[0]),
+ &real_length,
+ chars);
+
+ if (err != noErr) {
+ return translate_key(key);
+ }
+
+ for (unsigned int i = 0; i < 55; i++) {
+ if (_keycodes[i].kchar == chars[0]) {
+ return _keycodes[i].kcode;
+ }
+ }
+ return translate_key(key);
+}
+
+struct _KeyCodeText {
+ Key code;
+ char32_t text;
+};
+
+static const _KeyCodeText _native_keycodes[] = {
+ /* clang-format off */
+ {Key::ESCAPE ,0x001B},
+ {Key::TAB ,0x0009},
+ {Key::BACKTAB ,0x007F},
+ {Key::BACKSPACE ,0x0008},
+ {Key::ENTER ,0x000D},
+ {Key::INSERT ,NSInsertFunctionKey},
+ {Key::KEY_DELETE ,0x007F},
+ {Key::PAUSE ,NSPauseFunctionKey},
+ {Key::PRINT ,NSPrintScreenFunctionKey},
+ {Key::SYSREQ ,NSSysReqFunctionKey},
+ {Key::CLEAR ,NSClearLineFunctionKey},
+ {Key::HOME ,0x2196},
+ {Key::END ,0x2198},
+ {Key::LEFT ,0x001C},
+ {Key::UP ,0x001E},
+ {Key::RIGHT ,0x001D},
+ {Key::DOWN ,0x001F},
+ {Key::PAGEUP ,0x21DE},
+ {Key::PAGEDOWN ,0x21DF},
+ {Key::NUMLOCK ,NSClearLineFunctionKey},
+ {Key::SCROLLLOCK ,NSScrollLockFunctionKey},
+ {Key::F1 ,NSF1FunctionKey},
+ {Key::F2 ,NSF2FunctionKey},
+ {Key::F3 ,NSF3FunctionKey},
+ {Key::F4 ,NSF4FunctionKey},
+ {Key::F5 ,NSF5FunctionKey},
+ {Key::F6 ,NSF6FunctionKey},
+ {Key::F7 ,NSF7FunctionKey},
+ {Key::F8 ,NSF8FunctionKey},
+ {Key::F9 ,NSF9FunctionKey},
+ {Key::F10 ,NSF10FunctionKey},
+ {Key::F11 ,NSF11FunctionKey},
+ {Key::F12 ,NSF12FunctionKey},
+ {Key::F13 ,NSF13FunctionKey},
+ {Key::F14 ,NSF14FunctionKey},
+ {Key::F15 ,NSF15FunctionKey},
+ {Key::F16 ,NSF16FunctionKey}, //* ... NSF35FunctionKey */
+ {Key::MENU ,NSMenuFunctionKey},
+ {Key::HELP ,NSHelpFunctionKey},
+ {Key::STOP ,NSStopFunctionKey},
+ {Key::LAUNCH0 ,NSUserFunctionKey},
+ {Key::SPACE ,0x0020},
+ {Key::EXCLAM ,'!'},
+ {Key::QUOTEDBL ,'\"'},
+ {Key::NUMBERSIGN ,'#'},
+ {Key::DOLLAR ,'$'},
+ {Key::PERCENT ,'\%'},
+ {Key::AMPERSAND ,'&'},
+ {Key::APOSTROPHE ,'\''},
+ {Key::PARENLEFT ,'('},
+ {Key::PARENRIGHT ,')'},
+ {Key::ASTERISK ,'*'},
+ {Key::PLUS ,'+'},
+ {Key::COMMA ,','},
+ {Key::MINUS ,'-'},
+ {Key::PERIOD ,'.'},
+ {Key::SLASH ,'/'},
+ {Key::KEY_0 ,'0'},
+ {Key::KEY_1 ,'1'},
+ {Key::KEY_2 ,'2'},
+ {Key::KEY_3 ,'3'},
+ {Key::KEY_4 ,'4'},
+ {Key::KEY_5 ,'5'},
+ {Key::KEY_6 ,'6'},
+ {Key::KEY_7 ,'7'},
+ {Key::KEY_8 ,'8'},
+ {Key::KEY_9 ,'9'},
+ {Key::COLON ,':'},
+ {Key::SEMICOLON ,';'},
+ {Key::LESS ,'<'},
+ {Key::EQUAL ,'='},
+ {Key::GREATER ,'>'},
+ {Key::QUESTION ,'?'},
+ {Key::AT ,'@'},
+ {Key::A ,'a'},
+ {Key::B ,'b'},
+ {Key::C ,'c'},
+ {Key::D ,'d'},
+ {Key::E ,'e'},
+ {Key::F ,'f'},
+ {Key::G ,'g'},
+ {Key::H ,'h'},
+ {Key::I ,'i'},
+ {Key::J ,'j'},
+ {Key::K ,'k'},
+ {Key::L ,'l'},
+ {Key::M ,'m'},
+ {Key::N ,'n'},
+ {Key::O ,'o'},
+ {Key::P ,'p'},
+ {Key::Q ,'q'},
+ {Key::R ,'r'},
+ {Key::S ,'s'},
+ {Key::T ,'t'},
+ {Key::U ,'u'},
+ {Key::V ,'v'},
+ {Key::W ,'w'},
+ {Key::X ,'x'},
+ {Key::Y ,'y'},
+ {Key::Z ,'z'},
+ {Key::BRACKETLEFT ,'['},
+ {Key::BACKSLASH ,'\\'},
+ {Key::BRACKETRIGHT ,']'},
+ {Key::ASCIICIRCUM ,'^'},
+ {Key::UNDERSCORE ,'_'},
+ {Key::QUOTELEFT ,'`'},
+ {Key::BRACELEFT ,'{'},
+ {Key::BAR ,'|'},
+ {Key::BRACERIGHT ,'}'},
+ {Key::ASCIITILDE ,'~'},
+ {Key::NONE ,0x0000}
+ /* clang-format on */
+};
+
+String KeyMappingOSX::keycode_get_native_string(Key p_keycode) {
+ const _KeyCodeText *kct = &_native_keycodes[0];
+
+ while (kct->text) {
+ if (kct->code == p_keycode) {
+ return String::chr(kct->text);
+ }
+ kct++;
+ }
+ return String();
+}
+
+unsigned int KeyMappingOSX::keycode_get_native_mask(Key p_keycode) {
+ unsigned int mask = 0;
+ if ((p_keycode & KeyModifierMask::CTRL) != Key::NONE) {
+ mask |= NSEventModifierFlagControl;
+ }
+ if ((p_keycode & KeyModifierMask::ALT) != Key::NONE) {
+ mask |= NSEventModifierFlagOption;
+ }
+ if ((p_keycode & KeyModifierMask::SHIFT) != Key::NONE) {
+ mask |= NSEventModifierFlagShift;
+ }
+ if ((p_keycode & KeyModifierMask::META) != Key::NONE) {
+ mask |= NSEventModifierFlagCommand;
+ }
+ if ((p_keycode & KeyModifierMask::KPAD) != Key::NONE) {
+ mask |= NSEventModifierFlagNumericPad;
+ }
+ return mask;
+}
diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h
index 555ce4c75f..53c5c8bd90 100644
--- a/platform/osx/os_osx.h
+++ b/platform/osx/os_osx.h
@@ -40,9 +40,7 @@
#include "servers/audio_server.h"
class OS_OSX : public OS_Unix {
- virtual void delete_main_loop() override;
-
- bool force_quit;
+ bool force_quit = false;
JoypadOSX *joypad_osx = nullptr;
@@ -55,13 +53,15 @@ class OS_OSX : public OS_Unix {
CrashHandler crash_handler;
- MainLoop *main_loop;
+ CFRunLoopObserverRef pre_wait_observer;
- static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context);
+ MainLoop *main_loop = nullptr;
-public:
String open_with_filename;
+ static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
+ static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context);
+
protected:
virtual void initialize_core() override;
virtual void initialize() override;
@@ -70,8 +70,12 @@ protected:
virtual void initialize_joypads() override;
virtual void set_main_loop(MainLoop *p_main_loop) override;
+ virtual void delete_main_loop() override;
public:
+ String get_open_with_filename() const;
+ void set_open_with_filename(const String &p_path);
+
virtual String get_name() const override;
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
@@ -89,26 +93,28 @@ public:
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
- Error shell_open(String p_uri) override;
+ virtual Error shell_open(String p_uri) override;
- String get_locale() const override;
+ virtual String get_locale() const override;
virtual String get_executable_path() const override;
- virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
+ virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
- virtual String get_unique_id() const override; //++
+ virtual String get_unique_id() const override;
+ virtual String get_processor_name() const override;
virtual bool _check_internal_feature_support(const String &p_feature) override;
- void run();
-
virtual void disable_crash_handler() override;
virtual bool is_disable_crash_handler() const override;
virtual Error move_to_trash(const String &p_path) override;
+ void run();
+
OS_OSX();
+ ~OS_OSX();
};
#endif
diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm
index 7d07b0076b..7e0cf9f9cc 100644
--- a/platform/osx/os_osx.mm
+++ b/platform/osx/os_osx.mm
@@ -31,241 +31,57 @@
#include "os_osx.h"
#include "core/version_generated.gen.h"
+#include "main/main.h"
#include "dir_access_osx.h"
#include "display_server_osx.h"
-#include "main/main.h"
+#include "godot_application.h"
+#include "godot_application_delegate.h"
+#include "osx_terminal_logger.h"
#include <dlfcn.h>
#include <libproc.h>
#include <mach-o/dyld.h>
#include <os/log.h>
+#include <sys/sysctl.h>
-#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton()))
-
-/*************************************************************************/
-/* GodotApplication */
-/*************************************************************************/
-
-@interface GodotApplication : NSApplication
-@end
-
-@implementation GodotApplication
-
-- (void)sendEvent:(NSEvent *)event {
- if (DS_OSX) {
- DS_OSX->_send_event(event);
- }
-
- // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
- // This works around an AppKit bug, where key up events while holding
- // down the command key don't get sent to the key window.
- if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) {
- [[self keyWindow] sendEvent:event];
- } else {
- [super sendEvent:event];
- }
-}
-
-@end
-
-/*************************************************************************/
-/* GodotApplicationDelegate */
-/*************************************************************************/
-
-@interface GodotApplicationDelegate : NSObject
-- (void)forceUnbundledWindowActivationHackStep1;
-- (void)forceUnbundledWindowActivationHackStep2;
-- (void)forceUnbundledWindowActivationHackStep3;
-@end
-
-@implementation GodotApplicationDelegate
-
-- (void)forceUnbundledWindowActivationHackStep1 {
- // Step1: Switch focus to macOS Dock.
- // Required to perform step 2, TransformProcessType will fail if app is already the in focus.
- for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) {
- [app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
- break;
- }
- [self performSelector:@selector(forceUnbundledWindowActivationHackStep2)
- withObject:nil
- afterDelay:0.02];
-}
-
-- (void)forceUnbundledWindowActivationHackStep2 {
- // Step 2: Register app as foreground process.
- ProcessSerialNumber psn = { 0, kCurrentProcess };
- (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication);
- [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02];
-}
-
-- (void)forceUnbundledWindowActivationHackStep3 {
- // Step 3: Switch focus back to app window.
- [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
-}
-
-- (void)applicationDidFinishLaunching:(NSNotification *)notice {
- NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
- if (nsappname == nil) {
- // If executable is not a bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
- [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02];
- }
-}
-
-- (void)applicationDidResignActive:(NSNotification *)notification {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
- }
-}
-
-- (void)applicationDidBecomeActive:(NSNotification *)notification {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
- }
-}
-
-- (void)globalMenuCallback:(id)sender {
- if (DS_OSX) {
- return DS_OSX->_menu_callback(sender);
- }
-}
-
-- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
- if (DS_OSX) {
- return DS_OSX->_get_dock_menu();
+_FORCE_INLINE_ String OS_OSX::get_framework_executable(const String &p_path) {
+ // Append framework executable name, or return as is if p_path is not a framework.
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) {
+ return p_path.plus_file(p_path.get_file().get_basename());
} else {
- return nullptr;
- }
-}
-
-- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
- // Note: may be called called before main loop init!
- char *utfs = strdup([filename UTF8String]);
- ((OS_OSX *)OS_OSX::get_singleton())->open_with_filename.parse_utf8(utfs);
- free(utfs);
-
-#ifdef TOOLS_ENABLED
- // Open new instance
- if (OS_OSX::get_singleton()->get_main_loop()) {
- List<String> args;
- args.push_back(((OS_OSX *)OS_OSX::get_singleton())->open_with_filename);
- String exec = OS_OSX::get_singleton()->get_executable_path();
- OS_OSX::get_singleton()->create_process(exec, args);
- }
-#endif
- return YES;
-}
-
-- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
- if (DS_OSX) {
- DS_OSX->_send_window_event(DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
- }
- return NSTerminateCancel;
-}
-
-- (void)showAbout:(id)sender {
- if (OS_OSX::get_singleton()->get_main_loop()) {
- OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT);
+ return p_path;
}
}
-@end
-
-/*************************************************************************/
-/* OSXTerminalLogger */
-/*************************************************************************/
-
-class OSXTerminalLogger : public StdLogger {
-public:
- virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) {
- if (!should_log(true)) {
- return;
- }
-
- const char *err_details;
- if (p_rationale && p_rationale[0])
- err_details = p_rationale;
- else
- err_details = p_code;
-
- switch (p_type) {
- case ERR_WARNING:
- os_log_info(OS_LOG_DEFAULT,
- "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)",
- err_details, p_function, p_file, p_line);
- logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details);
- logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
- break;
- case ERR_SCRIPT:
- os_log_error(OS_LOG_DEFAULT,
- "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
- err_details, p_function, p_file, p_line);
- logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details);
- logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
- break;
- case ERR_SHADER:
- os_log_error(OS_LOG_DEFAULT,
- "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
- err_details, p_function, p_file, p_line);
- logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details);
- logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
- break;
- case ERR_ERROR:
- default:
- os_log_error(OS_LOG_DEFAULT,
- "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
- err_details, p_function, p_file, p_line);
- logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details);
- logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
- break;
- }
- }
-};
-
-/*************************************************************************/
-/* OS_OSX */
-/*************************************************************************/
-
-String OS_OSX::get_unique_id() const {
- static String serial_number;
-
- if (serial_number.is_empty()) {
- io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
- CFStringRef serialNumberAsCFString = nullptr;
- if (platformExpert) {
- serialNumberAsCFString = (CFStringRef)IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0);
- IOObjectRelease(platformExpert);
- }
+void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) {
+ // Prevent main loop from sleeping and redraw window during resize / modal popups.
- NSString *serialNumberAsNSString = nil;
- if (serialNumberAsCFString) {
- serialNumberAsNSString = [NSString stringWithString:(NSString *)serialNumberAsCFString];
- CFRelease(serialNumberAsCFString);
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (get_singleton()->get_main_loop() && ds && (get_singleton()->get_render_thread_mode() != RENDER_SEPARATE_THREAD || !ds->get_is_resizing())) {
+ Main::force_redraw();
+ if (!Main::is_iterating()) { // Avoid cyclic loop.
+ Main::iteration();
}
-
- serial_number = [serialNumberAsNSString UTF8String];
}
- return serial_number;
+ CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping.
}
-void OS_OSX::alert(const String &p_alert, const String &p_title) {
- NSAlert *window = [[NSAlert alloc] init];
- NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
- NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()];
+void OS_OSX::initialize() {
+ crash_handler.initialize();
- [window addButtonWithTitle:@"OK"];
- [window setMessageText:ns_title];
- [window setInformativeText:ns_alert];
- [window setAlertStyle:NSAlertStyleWarning];
+ initialize_core();
+}
- id key_window = [[NSApplication sharedApplication] keyWindow];
- [window runModal];
- [window release];
- if (key_window) {
- [key_window makeKeyAndOrderFront:nil];
+String OS_OSX::get_processor_name() const {
+ char buffer[256];
+ size_t buffer_len = 256;
+ if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) {
+ return String::utf8(buffer, buffer_len);
}
+ ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
}
void OS_OSX::initialize_core() {
@@ -276,17 +92,6 @@ void OS_OSX::initialize_core() {
DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM);
}
-void OS_OSX::initialize_joypads() {
- joypad_osx = memnew(JoypadOSX(Input::get_singleton()));
-}
-
-void OS_OSX::initialize() {
- crash_handler.initialize();
-
- initialize_core();
- //ensure_user_data_dir();
-}
-
void OS_OSX::finalize() {
#ifdef COREMIDI_ENABLED
midi_driver.close();
@@ -299,42 +104,63 @@ void OS_OSX::finalize() {
}
}
+void OS_OSX::initialize_joypads() {
+ joypad_osx = memnew(JoypadOSX(Input::get_singleton()));
+}
+
void OS_OSX::set_main_loop(MainLoop *p_main_loop) {
main_loop = p_main_loop;
}
void OS_OSX::delete_main_loop() {
- if (!main_loop)
+ if (!main_loop) {
return;
+ }
+
memdelete(main_loop);
main_loop = nullptr;
}
+String OS_OSX::get_open_with_filename() const {
+ return open_with_filename;
+}
+
+void OS_OSX::set_open_with_filename(const String &p_path) {
+ open_with_filename = p_path;
+}
+
String OS_OSX::get_name() const {
return "macOS";
}
-_FORCE_INLINE_ String _get_framework_executable(const String p_path) {
- // Append framework executable name, or return as is if p_path is not a framework.
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) {
- return p_path.plus_file(p_path.get_file().get_basename());
- } else {
- return p_path;
+void OS_OSX::alert(const String &p_alert, const String &p_title) {
+ NSAlert *window = [[NSAlert alloc] init];
+ NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
+ NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()];
+
+ [window addButtonWithTitle:@"OK"];
+ [window setMessageText:ns_title];
+ [window setInformativeText:ns_alert];
+ [window setAlertStyle:NSAlertStyleWarning];
+
+ id key_window = [[NSApplication sharedApplication] keyWindow];
+ [window runModal];
+ if (key_window) {
+ [key_window makeKeyAndOrderFront:nil];
}
}
Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
- String path = _get_framework_executable(p_path);
+ String path = get_framework_executable(p_path);
if (!FileAccess::exists(path)) {
- // This code exists so gdnative can load .dylib files from within the executable path.
- path = _get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file()));
+ // Load .dylib or framework from within the executable path.
+ path = get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file()));
}
if (!FileAccess::exists(path)) {
- // This code exists so gdnative can load .dylib files from a standard macOS location.
- path = _get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file()));
+ // Load .dylib or framework from a standard macOS location.
+ path = get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file()));
}
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
@@ -393,8 +219,8 @@ String OS_OSX::get_bundle_resource_dir() const {
NSBundle *main = [NSBundle mainBundle];
if (main) {
- NSString *resourcePath = [main resourcePath];
- ret.parse_utf8([resourcePath UTF8String]);
+ NSString *resource_path = [main resourcePath];
+ ret.parse_utf8([resource_path UTF8String]);
}
return ret;
}
@@ -404,9 +230,9 @@ String OS_OSX::get_bundle_icon_path() const {
NSBundle *main = [NSBundle mainBundle];
if (main) {
- NSString *iconPath = [[main infoDictionary] objectForKey:@"CFBundleIconFile"];
- if (iconPath) {
- ret.parse_utf8([iconPath UTF8String]);
+ NSString *icon_path = [[main infoDictionary] objectForKey:@"CFBundleIconFile"];
+ if (icon_path) {
+ ret.parse_utf8([icon_path UTF8String]);
}
}
return ret;
@@ -449,9 +275,7 @@ String OS_OSX::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
if (found) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES);
if (paths && [paths count] >= 1) {
- char *utfs = strdup([[paths firstObject] UTF8String]);
- ret.parse_utf8(utfs);
- free(utfs);
+ ret.parse_utf8([[paths firstObject] UTF8String]);
}
}
@@ -475,12 +299,9 @@ String OS_OSX::get_locale() const {
}
String OS_OSX::get_executable_path() const {
- int ret;
- pid_t pid;
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
-
- pid = getpid();
- ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
+ int pid = getpid();
+ pid_t ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
if (ret <= 0) {
return OS::get_executable_path();
} else {
@@ -491,19 +312,7 @@ String OS_OSX::get_executable_path() const {
}
}
-Error OS_OSX::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) {
- // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly.
- NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
- if (nsappname != nil) {
- String path;
- path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]);
- return create_process(path, p_arguments, r_child_id);
- } else {
- return create_process(get_executable_path(), p_arguments, r_child_id);
- }
-}
-
-Error OS_OSX::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) {
+Error OS_OSX::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
if (@available(macOS 10.15, *)) {
// Use NSWorkspace if path is an .app bundle.
NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
@@ -532,7 +341,6 @@ Error OS_OSX::create_process(const String &p_path, const List<String> &p_argumen
dispatch_semaphore_signal(lock);
}];
dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch.
- dispatch_release(lock);
if (err == OK) {
if (r_child_id) {
@@ -542,24 +350,73 @@ Error OS_OSX::create_process(const String &p_path, const List<String> &p_argumen
return err;
} else {
- return OS_Unix::create_process(p_path, p_arguments, r_child_id);
+ return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console);
}
} else {
- return OS_Unix::create_process(p_path, p_arguments, r_child_id);
+ return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console);
}
}
-void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) {
- // Prevent main loop from sleeping and redraw window during resize / modal popups.
+Error OS_OSX::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) {
+ // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly.
+ NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
+ if (nsappname != nil) {
+ String path;
+ path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]);
+ return create_process(path, p_arguments, r_child_id, false);
+ } else {
+ return create_process(get_executable_path(), p_arguments, r_child_id, false);
+ }
+}
- if (get_singleton()->get_main_loop()) {
- Main::force_redraw();
- if (!Main::is_iterating()) { // Avoid cyclic loop.
- Main::iteration();
+String OS_OSX::get_unique_id() const {
+ static String serial_number;
+
+ if (serial_number.is_empty()) {
+ io_service_t platform_expert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
+ CFStringRef serial_number_cf_string = nullptr;
+ if (platform_expert) {
+ serial_number_cf_string = (CFStringRef)IORegistryEntryCreateCFProperty(platform_expert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0);
+ IOObjectRelease(platform_expert);
+ }
+
+ NSString *serial_number_ns_string = nil;
+ if (serial_number_cf_string) {
+ serial_number_ns_string = [NSString stringWithString:(__bridge NSString *)serial_number_cf_string];
+ CFRelease(serial_number_cf_string);
+ }
+
+ if (serial_number_ns_string) {
+ serial_number.parse_utf8([serial_number_ns_string UTF8String]);
}
}
- CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping.
+ return serial_number;
+}
+
+bool OS_OSX::_check_internal_feature_support(const String &p_feature) {
+ return p_feature == "pc";
+}
+
+void OS_OSX::disable_crash_handler() {
+ crash_handler.disable();
+}
+
+bool OS_OSX::is_disable_crash_handler() const {
+ return crash_handler.is_disabled();
+}
+
+Error OS_OSX::move_to_trash(const String &p_path) {
+ NSFileManager *fm = [NSFileManager defaultManager];
+ NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
+ NSError *err;
+
+ if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) {
+ ERR_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String));
+ return FAILED;
+ }
+
+ return OK;
}
void OS_OSX::run() {
@@ -571,14 +428,11 @@ void OS_OSX::run() {
main_loop->initialize();
- CFRunLoopObserverRef pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
- CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
-
bool quit = false;
while (!force_quit && !quit) {
@try {
if (DisplayServer::get_singleton()) {
- DisplayServer::get_singleton()->process_events(); // get rid of pending events
+ DisplayServer::get_singleton()->process_events(); // Get rid of pending events.
}
joypad_osx->process_joypads();
@@ -588,25 +442,9 @@ void OS_OSX::run() {
} @catch (NSException *exception) {
ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
}
- };
-
- CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
- CFRelease(pre_wait_observer);
-
- main_loop->finalize();
-}
-
-Error OS_OSX::move_to_trash(const String &p_path) {
- NSFileManager *fm = [NSFileManager defaultManager];
- NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
- NSError *err;
-
- if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) {
- ERR_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String));
- return FAILED;
}
- return OK;
+ main_loop->finalize();
}
OS_OSX::OS_OSX() {
@@ -623,17 +461,17 @@ OS_OSX::OS_OSX() {
DisplayServerOSX::register_osx_driver();
- // Implicitly create shared NSApplication instance
+ // Implicitly create shared NSApplication instance.
[GodotApplication sharedApplication];
- // In case we are unbundled, make us a proper UI application
+ // In case we are unbundled, make us a proper UI application.
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
// Menu bar setup must go between sharedApplication above and
// finishLaunching below, in order to properly emulate the behavior
- // of NSApplicationMain
+ // of NSApplicationMain.
- NSMenu *main_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+ NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""];
[NSApp setMainMenu:main_menu];
[NSApp finishLaunching];
@@ -641,7 +479,10 @@ OS_OSX::OS_OSX() {
ERR_FAIL_COND(!delegate);
[NSApp setDelegate:delegate];
- //process application:openFile: event
+ pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
+ CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
+
+ // Process application:openFile: event.
while (true) {
NSEvent *event = [NSApp
nextEventMatchingMask:NSEventMaskAny
@@ -659,14 +500,7 @@ OS_OSX::OS_OSX() {
[NSApp activateIgnoringOtherApps:YES];
}
-bool OS_OSX::_check_internal_feature_support(const String &p_feature) {
- return p_feature == "pc";
-}
-
-void OS_OSX::disable_crash_handler() {
- crash_handler.disable();
-}
-
-bool OS_OSX::is_disable_crash_handler() const {
- return crash_handler.is_disabled();
+OS_OSX::~OS_OSX() {
+ CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
+ CFRelease(pre_wait_observer);
}
diff --git a/platform/osx/osx_terminal_logger.h b/platform/osx/osx_terminal_logger.h
new file mode 100644
index 0000000000..8413509c4b
--- /dev/null
+++ b/platform/osx/osx_terminal_logger.h
@@ -0,0 +1,44 @@
+/*************************************************************************/
+/* osx_terminal_logger.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 OSX_TERMINAL_LOGGER_H
+#define OSX_TERMINAL_LOGGER_H
+
+#ifdef OSX_ENABLED
+
+#include "core/io/logger.h"
+
+class OSXTerminalLogger : public StdLogger {
+public:
+ virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override;
+};
+
+#endif // OSX_ENABLED
+#endif // OSX_TERMINAL_LOGGER_H
diff --git a/platform/osx/osx_terminal_logger.mm b/platform/osx/osx_terminal_logger.mm
new file mode 100644
index 0000000000..48e26f42bf
--- /dev/null
+++ b/platform/osx/osx_terminal_logger.mm
@@ -0,0 +1,82 @@
+/*************************************************************************/
+/* osx_terminal_logger.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "osx_terminal_logger.h"
+
+#ifdef OSX_ENABLED
+
+#include <os/log.h>
+
+void OSXTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) {
+ if (!should_log(true)) {
+ return;
+ }
+
+ const char *err_details;
+ if (p_rationale && p_rationale[0]) {
+ err_details = p_rationale;
+ } else {
+ err_details = p_code;
+ }
+
+ switch (p_type) {
+ case ERR_WARNING:
+ os_log_info(OS_LOG_DEFAULT,
+ "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)",
+ err_details, p_function, p_file, p_line);
+ logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details);
+ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
+ break;
+ case ERR_SCRIPT:
+ os_log_error(OS_LOG_DEFAULT,
+ "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
+ err_details, p_function, p_file, p_line);
+ logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details);
+ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
+ break;
+ case ERR_SHADER:
+ os_log_error(OS_LOG_DEFAULT,
+ "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
+ err_details, p_function, p_file, p_line);
+ logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details);
+ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
+ break;
+ case ERR_ERROR:
+ default:
+ os_log_error(OS_LOG_DEFAULT,
+ "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)",
+ err_details, p_function, p_file, p_line);
+ logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details);
+ logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line);
+ break;
+ }
+}
+
+#endif // OSX_ENABLED
diff --git a/platform/osx/vulkan_context_osx.mm b/platform/osx/vulkan_context_osx.mm
index f32fab1eee..bdabc24c28 100644
--- a/platform/osx/vulkan_context_osx.mm
+++ b/platform/osx/vulkan_context_osx.mm
@@ -44,7 +44,7 @@ Error VulkanContextOSX::window_create(DisplayServer::WindowID p_window_id, Displ
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = nullptr;
createInfo.flags = 0;
- createInfo.pView = p_window;
+ createInfo.pView = (__bridge const void *)p_window;
VkSurfaceKHR surface;
VkResult err = vkCreateMacOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface);
diff --git a/platform/uwp/app_uwp.cpp b/platform/uwp/app_uwp.cpp
index 6832d71a6f..6460c43447 100644
--- a/platform/uwp/app_uwp.cpp
+++ b/platform/uwp/app_uwp.cpp
@@ -177,7 +177,7 @@ static MouseButton _get_button(Windows::UI::Input::PointerPoint ^ pt) {
#endif
return MOUSE_BUTTON_NONE;
-};
+}
static bool _is_touch(Windows::UI::Input::PointerPoint ^ pointerPoint) {
#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
@@ -231,11 +231,11 @@ static Windows::Foundation::Point _get_pixel_position(CoreWindow ^ window, Windo
outputPosition.Y *= vm.height;
return outputPosition;
-};
+}
static int _get_finger(uint32_t p_touch_id) {
return p_touch_id % 31; // for now
-};
+}
void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args, bool p_pressed, bool p_is_wheel) {
Windows::UI::Input::PointerPoint ^ point = args->CurrentPoint;
@@ -281,15 +281,15 @@ void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Cor
os->input_event(mouse_button);
}
}
-};
+}
void App::OnPointerPressed(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) {
pointer_event(sender, args, true);
-};
+}
void App::OnPointerReleased(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) {
pointer_event(sender, args, false);
-};
+}
void App::OnPointerWheelChanged(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) {
pointer_event(sender, args, true, true);
@@ -416,8 +416,9 @@ void App::Load(Platform::String ^ entryPoint) {
// This method is called after the window becomes active.
void App::Run() {
- if (Main::start())
+ if (Main::start()) {
os->run();
+ }
}
// Terminate events do not cause Uninitialize to be called. It will be called if your IFrameworkView
diff --git a/platform/uwp/context_egl_uwp.cpp b/platform/uwp/context_egl_uwp.cpp
index a08693c72f..8ec7bdfcee 100644
--- a/platform/uwp/context_egl_uwp.cpp
+++ b/platform/uwp/context_egl_uwp.cpp
@@ -36,26 +36,26 @@ using Platform::Exception;
void ContextEGL_UWP::release_current() {
eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext);
-};
+}
void ContextEGL_UWP::make_current() {
eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
-};
+}
int ContextEGL_UWP::get_window_width() {
return width;
-};
+}
int ContextEGL_UWP::get_window_height() {
return height;
-};
+}
void ContextEGL_UWP::reset() {
cleanup();
window = CoreWindow::GetForCurrentThread();
initialize();
-};
+}
void ContextEGL_UWP::swap_buffers() {
if (eglSwapBuffers(mEglDisplay, mEglSurface) != EGL_TRUE) {
@@ -66,7 +66,7 @@ void ContextEGL_UWP::swap_buffers() {
// tell rasterizer to reload textures and stuff?
}
-};
+}
Error ContextEGL_UWP::initialize() {
EGLint configAttribList[] = {
@@ -170,7 +170,7 @@ Error ContextEGL_UWP::initialize() {
}
} catch (...) {
return FAILED;
- };
+ }
mEglDisplay = display;
mEglSurface = surface;
@@ -180,7 +180,7 @@ Error ContextEGL_UWP::initialize() {
eglQuerySurface(display, surface, EGL_HEIGHT, &height);
return OK;
-};
+}
void ContextEGL_UWP::cleanup() {
if (mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE) {
@@ -197,7 +197,7 @@ void ContextEGL_UWP::cleanup() {
eglTerminate(mEglDisplay);
mEglDisplay = EGL_NO_DISPLAY;
}
-};
+}
ContextEGL_UWP::ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver) :
mEglDisplay(EGL_NO_DISPLAY),
@@ -209,4 +209,4 @@ ContextEGL_UWP::ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver) :
ContextEGL_UWP::~ContextEGL_UWP() {
cleanup();
-};
+}
diff --git a/platform/uwp/export/app_packager.cpp b/platform/uwp/export/app_packager.cpp
index 9b586a640e..e7978ff74d 100644
--- a/platform/uwp/export/app_packager.cpp
+++ b/platform/uwp/export/app_packager.cpp
@@ -30,6 +30,9 @@
#include "app_packager.h"
+#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
+
String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len) {
unsigned char hash[32];
char base64[45];
diff --git a/platform/uwp/export/app_packager.h b/platform/uwp/export/app_packager.h
index a5f5896592..da118449c7 100644
--- a/platform/uwp/export/app_packager.h
+++ b/platform/uwp/export/app_packager.h
@@ -41,7 +41,6 @@
#include "core/object/class_db.h"
#include "core/version.h"
#include "editor/editor_export.h"
-#include "editor/editor_node.h"
#include "thirdparty/minizip/unzip.h"
#include "thirdparty/minizip/zip.h"
diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp
index 594495375a..230e5c749c 100644
--- a/platform/uwp/export/export_plugin.cpp
+++ b/platform/uwp/export/export_plugin.cpp
@@ -98,13 +98,13 @@ void EditorExportPlatformUWP::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait_flipped"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "images/background_color"), "transparent"));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/store_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square44x44_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square71x71_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square150x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square310x310_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/wide310x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/splash_screen", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/store_logo", PROPERTY_HINT_RESOURCE_TYPE, "CompressedTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square44x44_logo", PROPERTY_HINT_RESOURCE_TYPE, "CompressedTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square71x71_logo", PROPERTY_HINT_RESOURCE_TYPE, "CompressedTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square150x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "CompressedTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square310x310_logo", PROPERTY_HINT_RESOURCE_TYPE, "CompressedTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/wide310x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "CompressedTexture2D"), Variant()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/splash_screen", PROPERTY_HINT_RESOURCE_TYPE, "CompressedTexture2D"), Variant()));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_square150x150"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_wide310x150"), false));
@@ -201,37 +201,37 @@ bool EditorExportPlatformUWP::can_export(const Ref<EditorExportPreset> &p_preset
err += TTR("Invalid background color.") + "\n";
}
- if (!p_preset->get("images/store_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/store_logo"))), 50, 50)) {
+ if (!p_preset->get("images/store_logo").is_zero() && !_valid_image((Object::cast_to<CompressedTexture2D>((Object *)p_preset->get("images/store_logo"))), 50, 50)) {
valid = false;
err += TTR("Invalid Store Logo image dimensions (should be 50x50).") + "\n";
}
- if (!p_preset->get("images/square44x44_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square44x44_logo"))), 44, 44)) {
+ if (!p_preset->get("images/square44x44_logo").is_zero() && !_valid_image((Object::cast_to<CompressedTexture2D>((Object *)p_preset->get("images/square44x44_logo"))), 44, 44)) {
valid = false;
err += TTR("Invalid square 44x44 logo image dimensions (should be 44x44).") + "\n";
}
- if (!p_preset->get("images/square71x71_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square71x71_logo"))), 71, 71)) {
+ if (!p_preset->get("images/square71x71_logo").is_zero() && !_valid_image((Object::cast_to<CompressedTexture2D>((Object *)p_preset->get("images/square71x71_logo"))), 71, 71)) {
valid = false;
err += TTR("Invalid square 71x71 logo image dimensions (should be 71x71).") + "\n";
}
- if (!p_preset->get("images/square150x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square150x150_logo"))), 150, 150)) {
+ if (!p_preset->get("images/square150x150_logo").is_zero() && !_valid_image((Object::cast_to<CompressedTexture2D>((Object *)p_preset->get("images/square150x150_logo"))), 150, 150)) {
valid = false;
err += TTR("Invalid square 150x150 logo image dimensions (should be 150x150).") + "\n";
}
- if (!p_preset->get("images/square310x310_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/square310x310_logo"))), 310, 310)) {
+ if (!p_preset->get("images/square310x310_logo").is_zero() && !_valid_image((Object::cast_to<CompressedTexture2D>((Object *)p_preset->get("images/square310x310_logo"))), 310, 310)) {
valid = false;
err += TTR("Invalid square 310x310 logo image dimensions (should be 310x310).") + "\n";
}
- if (!p_preset->get("images/wide310x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/wide310x150_logo"))), 310, 150)) {
+ if (!p_preset->get("images/wide310x150_logo").is_zero() && !_valid_image((Object::cast_to<CompressedTexture2D>((Object *)p_preset->get("images/wide310x150_logo"))), 310, 150)) {
valid = false;
err += TTR("Invalid wide 310x150 logo image dimensions (should be 310x150).") + "\n";
}
- if (!p_preset->get("images/splash_screen").is_zero() && !_valid_image((Object::cast_to<StreamTexture2D>((Object *)p_preset->get("images/splash_screen"))), 620, 300)) {
+ if (!p_preset->get("images/splash_screen").is_zero() && !_valid_image((Object::cast_to<CompressedTexture2D>((Object *)p_preset->get("images/splash_screen"))), 620, 300)) {
valid = false;
err += TTR("Invalid splash screen image dimensions (should be 620x300).") + "\n";
}
@@ -416,7 +416,7 @@ Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_p
EditorNode::progress_add_task("project_files", "Project Files", 100);
packager.set_progress_task("project_files");
- err = export_project_files(p_preset, save_appx_file, &packager);
+ err = export_project_files(p_preset, p_debug, save_appx_file, &packager);
EditorNode::progress_end_task("project_files");
diff --git a/platform/uwp/export/export_plugin.h b/platform/uwp/export/export_plugin.h
index eab958534a..bf89b10ffa 100644
--- a/platform/uwp/export/export_plugin.h
+++ b/platform/uwp/export/export_plugin.h
@@ -41,6 +41,7 @@
#include "core/version.h"
#include "editor/editor_export.h"
#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
#include "thirdparty/minizip/unzip.h"
#include "thirdparty/minizip/zip.h"
@@ -190,7 +191,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
return false;
}
- bool _valid_image(const StreamTexture2D *p_image, int p_width, int p_height) const {
+ bool _valid_image(const CompressedTexture2D *p_image, int p_width, int p_height) const {
if (!p_image) {
return false;
}
@@ -310,22 +311,22 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
Vector<uint8_t> _get_image_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
Vector<uint8_t> data;
- StreamTexture2D *texture = nullptr;
+ CompressedTexture2D *texture = nullptr;
if (p_path.find("StoreLogo") != -1) {
- texture = 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<CompressedTexture2D>(((Object *)p_preset->get("images/store_logo")));
} else if (p_path.find("Square44x44Logo") != -1) {
- texture = 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<CompressedTexture2D>(((Object *)p_preset->get("images/square44x44_logo")));
} else if (p_path.find("Square71x71Logo") != -1) {
- texture = 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<CompressedTexture2D>(((Object *)p_preset->get("images/square71x71_logo")));
} else if (p_path.find("Square150x150Logo") != -1) {
- texture = 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<CompressedTexture2D>(((Object *)p_preset->get("images/square150x150_logo")));
} else if (p_path.find("Square310x310Logo") != -1) {
- texture = 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<CompressedTexture2D>(((Object *)p_preset->get("images/square310x310_logo")));
} else if (p_path.find("Wide310x150Logo") != -1) {
- texture = 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<CompressedTexture2D>(((Object *)p_preset->get("images/wide310x150_logo")));
} else if (p_path.find("SplashScreen") != -1) {
- texture = 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<CompressedTexture2D>(((Object *)p_preset->get("images/splash_screen")));
} else {
ERR_PRINT("Unable to load logo");
}
@@ -392,7 +393,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
".webp", // Same reasoning as .png
".cfb", // Don't let small config files slow-down startup
".scn", // Binary scenes are usually already compressed
- ".stex", // Streamable textures are usually already compressed
+ ".ctex", // Streamable textures are usually already compressed
// Trailer for easier processing
nullptr
};
diff --git a/platform/uwp/joypad_uwp.cpp b/platform/uwp/joypad_uwp.cpp
index ef44f0b14d..85c8959cf1 100644
--- a/platform/uwp/joypad_uwp.cpp
+++ b/platform/uwp/joypad_uwp.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "joypad_uwp.h"
+
#include "core/os/os.h"
using namespace Windows::Gaming::Input;
@@ -45,8 +46,9 @@ void JoypadUWP::process_controllers() {
for (int i = 0; i < MAX_CONTROLLERS; i++) {
ControllerDevice &joy = controllers[i];
- if (!joy.connected)
+ if (!joy.connected) {
break;
+ }
switch (joy.type) {
case ControllerType::GAMEPAD_CONTROLLER: {
@@ -76,8 +78,9 @@ void JoypadUWP::process_controllers() {
}
} else if (joy.vibrating && joy.ff_end_timestamp != 0) {
uint64_t current_time = OS::get_singleton()->get_ticks_usec();
- if (current_time >= joy.ff_end_timestamp)
+ if (current_time >= joy.ff_end_timestamp) {
joypad_vibration_stop(i, current_time);
+ }
}
break;
@@ -87,8 +90,9 @@ void JoypadUWP::process_controllers() {
}
JoypadUWP::JoypadUWP() {
- for (int i = 0; i < MAX_CONTROLLERS; i++)
+ for (int i = 0; i < MAX_CONTROLLERS; i++) {
controllers[i].id = i;
+ }
}
JoypadUWP::JoypadUWP(InputDefault *p_input) {
@@ -134,13 +138,12 @@ void JoypadUWP::OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Inp
input->joy_connection_changed(idx, false, "Xbox Controller");
}
-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);
-
- return jx;
+float JoypadUWP::axis_correct(double p_val, bool p_negate, bool p_trigger) const {
+ if (p_trigger) {
+ // Convert to a value between -1.0f and 1.0f.
+ return 2.0f * p_val - 1.0f;
+ }
+ return (float)(p_negate ? -p_val : p_val);
}
void JoypadUWP::joypad_vibration_start(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) {
diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h
index 1d68996358..29f5109056 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::JoyAxisValue axis_correct(double p_val, bool p_negate = false, bool p_trigger = false) const;
+ float 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 728f9a1616..22a54911f9 100644
--- a/platform/uwp/os_uwp.cpp
+++ b/platform/uwp/os_uwp.cpp
@@ -95,12 +95,12 @@ void OS_UWP::set_window_fullscreen(bool p_enabled) {
video_mode.fullscreen = view->IsFullScreenMode;
- if (video_mode.fullscreen == p_enabled)
+ if (video_mode.fullscreen == p_enabled) {
return;
+ }
if (p_enabled) {
video_mode.fullscreen = view->TryEnterFullScreenMode();
-
} else {
view->ExitFullScreenMode();
video_mode.fullscreen = false;
@@ -112,13 +112,15 @@ bool OS_UWP::is_window_fullscreen() const {
}
void OS_UWP::set_keep_screen_on(bool p_enabled) {
- if (is_keep_screen_on() == p_enabled)
+ if (is_keep_screen_on() == p_enabled) {
return;
+ }
- if (p_enabled)
+ if (p_enabled) {
display_request->RequestActive();
- else
+ } else {
display_request->RequestRelease();
+ }
OS::set_keep_screen_on(p_enabled);
}
@@ -136,12 +138,8 @@ void OS_UWP::initialize_core() {
NetSocketPosix::make_default();
// We need to know how often the clock is updated
- if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second))
- ticks_per_second = 1000;
- // If timeAtGameStart is 0 then we get the time since
- // the start of the computer when we call GetGameTime()
- ticks_start = 0;
- ticks_start = get_ticks_usec();
+ QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second);
+ QueryPerformanceCounter((LARGE_INTEGER *)&ticks_start);
IPUnix::make_default();
@@ -154,7 +152,7 @@ void OS_UWP::set_window(Windows::UI::Core::CoreWindow ^ p_window) {
void OS_UWP::screen_size_changed() {
gl_context->reset();
-};
+}
Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
main_loop = nullptr;
@@ -273,8 +271,9 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
_ensure_user_data_dir();
- if (is_keep_screen_on())
+ if (is_keep_screen_on()) {
display_request->RequestActive();
+ }
set_keep_screen_on(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
@@ -287,22 +286,24 @@ void OS_UWP::set_clipboard(const String &p_text) {
clip->SetText(ref new Platform::String((LPCWSTR)(p_text.utf16().get_data())));
Clipboard::SetContent(clip);
-};
+}
String OS_UWP::get_clipboard() const {
- if (managed_object->clipboard != nullptr)
+ if (managed_object->clipboard != nullptr) {
return managed_object->clipboard->Data();
- else
+ } else {
return "";
-};
+ }
+}
void OS_UWP::input_event(const Ref<InputEvent> &p_event) {
input->parse_input_event(p_event);
-};
+}
void OS_UWP::delete_main_loop() {
- if (main_loop)
+ if (main_loop) {
memdelete(main_loop);
+ }
main_loop = nullptr;
}
@@ -312,16 +313,18 @@ void OS_UWP::set_main_loop(MainLoop *p_main_loop) {
}
void OS_UWP::finalize() {
- if (main_loop)
+ if (main_loop) {
memdelete(main_loop);
+ }
main_loop = nullptr;
rendering_server->finish();
memdelete(rendering_server);
#ifdef GLES3_ENABLED
- if (gl_context)
+ if (gl_context) {
memdelete(gl_context);
+ }
#endif
memdelete(input);
@@ -443,7 +446,7 @@ String OS_UWP::get_name() const {
OS::Date OS_UWP::get_date(bool p_utc) const {
SYSTEMTIME systemtime;
- if (utc) {
+ if (p_utc) {
GetSystemTime(&systemtime);
} else {
GetLocalTime(&systemtime);
@@ -460,10 +463,11 @@ OS::Date OS_UWP::get_date(bool p_utc) const {
OS::Time OS_UWP::get_time(bool p_utc) const {
SYSTEMTIME systemtime;
- if (utc)
+ if (p_utc) {
GetSystemTime(&systemtime);
- else
+ } else {
GetLocalTime(&systemtime);
+ }
Time time;
time.hour = systemtime.wHour;
@@ -475,8 +479,9 @@ OS::Time OS_UWP::get_time(bool p_utc) const {
OS::TimeZoneInfo OS_UWP::get_time_zone_info() const {
TIME_ZONE_INFORMATION info;
bool daylight = false;
- if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT)
+ if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) {
daylight = true;
+ }
TimeZoneInfo ret;
if (daylight) {
@@ -510,7 +515,7 @@ uint64_t OS_UWP::get_unix_time() const {
SystemTimeToFileTime(&ep, &fep);
return (*(uint64_t *)&ft - *(uint64_t *)&fep) / 10000000;
-};
+}
void OS_UWP::delay_usec(uint32_t p_usec) const {
int msec = p_usec < 1000 ? 1 : p_usec / 1000;
@@ -524,6 +529,9 @@ uint64_t OS_UWP::get_ticks_usec() const {
// This is the number of clock ticks since start
QueryPerformanceCounter((LARGE_INTEGER *)&ticks);
+ // Subtract the ticks at game start to get
+ // the ticks since the game started
+ ticks -= ticks_start;
// Divide by frequency to get the time in seconds
// original calculation shown below is subject to overflow
@@ -543,9 +551,6 @@ uint64_t OS_UWP::get_ticks_usec() const {
// seconds
time += seconds * 1000000L;
- // Subtract the time at game start to get
- // the time since the game started
- time -= ticks_start;
return time;
}
@@ -593,8 +598,9 @@ void OS_UWP::queue_key_event(KeyEvent &p_event) {
void OS_UWP::set_cursor_shape(CursorShape p_shape) {
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
- if (cursor_shape == p_shape)
+ if (cursor_shape == p_shape) {
return;
+ }
static const CoreCursorType uwp_cursors[CURSOR_MAX] = {
CoreCursorType::Arrow,
@@ -629,17 +635,17 @@ void OS_UWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c
// TODO
}
-Error OS_UWP::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
+Error OS_UWP::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
return FAILED;
-};
+}
-Error OS_UWP::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) {
+Error OS_UWP::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
return FAILED;
-};
+}
Error OS_UWP::kill(const ProcessID &p_pid) {
return FAILED;
-};
+}
Error OS_UWP::set_cwd(const String &p_cwd) {
return FAILED;
@@ -654,11 +660,11 @@ void OS_UWP::set_icon(const Ref<Image> &p_icon) {
bool OS_UWP::has_environment(const String &p_var) const {
return false;
-};
+}
String OS_UWP::get_environment(const String &p_var) const {
return "";
-};
+}
bool OS_UWP::set_environment(const String &p_var, const String &p_value) const {
return false;
@@ -754,8 +760,9 @@ Error OS_UWP::get_dynamic_library_symbol_handle(void *p_library_handle, const St
}
void OS_UWP::run() {
- if (!main_loop)
+ if (!main_loop) {
return;
+ }
main_loop->init();
@@ -766,12 +773,14 @@ void OS_UWP::run() {
while (!force_quit) {
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
- if (managed_object->alert_close_handle)
+ if (managed_object->alert_close_handle) {
continue;
+ }
process_events(); // get rid of pending events
- if (Main::iteration())
+ if (Main::iteration()) {
break;
- };
+ }
+ }
main_loop->finish();
}
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index 5784c11d53..573d86af7c 100644
--- a/platform/uwp/os_uwp.h
+++ b/platform/uwp/os_uwp.h
@@ -195,8 +195,8 @@ public:
virtual void delay_usec(uint32_t p_usec) const;
virtual uint64_t get_ticks_usec() const;
- virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr);
- virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr);
+ virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false);
+ virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false);
virtual Error kill(const ProcessID &p_pid);
virtual bool has_environment(const String &p_var) const;
diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp
index 71e9d9acbd..3b2c6fe9f6 100644
--- a/platform/windows/crash_handler_windows.cpp
+++ b/platform/windows/crash_handler_windows.cpp
@@ -33,7 +33,6 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
#include "core/version.h"
-#include "core/version_hash.gen.h"
#include "main/main.h"
#ifdef CRASH_HANDLER_EXCEPTION
@@ -81,8 +80,9 @@ public:
std::string name() { return std::string(sym->Name); }
std::string undecorated_name() {
- if (*sym->Name == '\0')
+ if (*sym->Name == '\0') {
return "<couldn't map PC to fn name>";
+ }
std::vector<char> und_name(max_name_len);
UnDecorateSymbolName(sym->Name, &und_name[0], max_name_len, UNDNAME_COMPLETE);
return std::string(&und_name[0], strlen(&und_name[0]));
@@ -132,12 +132,14 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
fprintf(stderr, "\n================================================================\n");
fprintf(stderr, "%s: Program crashed\n", __FUNCTION__);
- if (OS::get_singleton()->get_main_loop())
+ if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
+ }
// Load the symbols:
- if (!SymInitialize(process, nullptr, false))
+ if (!SymInitialize(process, nullptr, false)) {
return EXCEPTION_CONTINUE_SEARCH;
+ }
SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded);
@@ -179,10 +181,10 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
}
// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
- if (String(VERSION_HASH).length() != 0) {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n");
+ if (String(VERSION_HASH).is_empty()) {
+ fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
} else {
- fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n");
+ fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
}
fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
@@ -194,18 +196,21 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
if (frame.AddrPC.Offset != 0) {
std::string fnName = symbol(process, frame.AddrPC.Offset).undecorated_name();
- if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &offset_from_symbol, &line))
+ if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &offset_from_symbol, &line)) {
fprintf(stderr, "[%d] %s (%s:%d)\n", n, fnName.c_str(), line.FileName, line.LineNumber);
- else
+ } else {
fprintf(stderr, "[%d] %s\n", n, fnName.c_str());
- } else
+ }
+ } else {
fprintf(stderr, "[%d] ???\n", n);
+ }
n++;
}
- if (!StackWalk64(image_type, process, hThread, &frame, context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
+ if (!StackWalk64(image_type, process, hThread, &frame, context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) {
break;
+ }
} while (frame.AddrReturn.Offset != 0 && n < 256);
fprintf(stderr, "-- END OF BACKTRACE --\n");
@@ -226,8 +231,9 @@ CrashHandler::~CrashHandler() {
}
void CrashHandler::disable() {
- if (disabled)
+ if (disabled) {
return;
+ }
disabled = true;
}
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index e9ecc99ef5..249a0d2e79 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -65,7 +65,7 @@ def get_opts():
# Vista support dropped after EOL due to GH-10243
("target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601"),
BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True),
- EnumVariable("windows_subsystem", "Windows subsystem", "default", ("default", "console", "gui")),
+ EnumVariable("windows_subsystem", "Windows subsystem", "gui", ("gui", "console")),
BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False),
("msvc_version", "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", None),
BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed.", False),
@@ -178,15 +178,6 @@ def configure_msvc(env, manual_msvc_config):
# Build type
- if env["tests"]:
- env["windows_subsystem"] = "console"
- elif env["windows_subsystem"] == "default":
- # Default means we use console for debug, gui for release.
- if "debug" in env["target"]:
- env["windows_subsystem"] = "console"
- else:
- env["windows_subsystem"] = "gui"
-
if env["target"] == "release":
if env["optimize"] == "speed": # optimize for speed (default)
env.Append(CCFLAGS=["/O2"])
@@ -326,15 +317,6 @@ def configure_mingw(env):
## Build type
- if env["tests"]:
- env["windows_subsystem"] = "console"
- elif env["windows_subsystem"] == "default":
- # Default means we use console for debug, gui for release.
- if "debug" in env["target"]:
- env["windows_subsystem"] = "console"
- else:
- env["windows_subsystem"] = "gui"
-
if env["target"] == "release":
env.Append(CCFLAGS=["-msse2"])
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index ca2b68371c..d243d4c05d 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -77,7 +77,6 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_CLIPBOARD:
case FEATURE_CURSOR_SHAPE:
case FEATURE_CUSTOM_CURSOR_SHAPE:
- case FEATURE_CONSOLE_WINDOW:
case FEATURE_IME:
case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_HIDPI:
@@ -98,7 +97,10 @@ String DisplayServerWindows::get_name() const {
void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) {
if (windows.has(MAIN_WINDOW_ID) && (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN)) {
// Mouse is grabbed (captured or confined).
- WindowData &wd = windows[MAIN_WINDOW_ID];
+
+ WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID;
+
+ WindowData &wd = windows[window_id];
RECT clipRect;
GetClientRect(wd.hWnd, &clipRect);
@@ -229,7 +231,7 @@ String DisplayServerWindows::clipboard_get() const {
String ret;
if (!OpenClipboard(windows[last_focused_window].hWnd)) {
ERR_FAIL_V_MSG("", "Unable to open clipboard.");
- };
+ }
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
@@ -238,8 +240,8 @@ String DisplayServerWindows::clipboard_get() const {
if (ptr != nullptr) {
ret = String::utf16((const char16_t *)ptr);
GlobalUnlock(mem);
- };
- };
+ }
+ }
} else if (IsClipboardFormatAvailable(CF_TEXT)) {
HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
@@ -248,9 +250,9 @@ String DisplayServerWindows::clipboard_get() const {
if (ptr != nullptr) {
ret.parse_utf8((const char *)ptr);
GlobalUnlock(mem);
- };
- };
- };
+ }
+ }
+ }
CloseClipboard();
@@ -324,6 +326,12 @@ typedef struct {
Rect2i rect;
} EnumRectData;
+typedef struct {
+ int count;
+ int screen;
+ float rate;
+} EnumRefreshRateData;
+
static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumSizeData *data = (EnumSizeData *)dwData;
if (data->count == data->screen) {
@@ -361,6 +369,26 @@ static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonito
return TRUE;
}
+static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ EnumRefreshRateData *data = (EnumRefreshRateData *)dwData;
+ if (data->count == data->screen) {
+ MONITORINFOEXW minfo;
+ memset(&minfo, 0, sizeof(minfo));
+ minfo.cbSize = sizeof(minfo);
+ GetMonitorInfoW(hMonitor, &minfo);
+
+ DEVMODEW dm;
+ memset(&dm, 0, sizeof(dm));
+ dm.dmSize = sizeof(dm);
+ EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm);
+
+ data->rate = dm.dmDisplayFrequency;
+ }
+
+ data->count++;
+ return TRUE;
+}
+
Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const {
_THREAD_SAFE_METHOD_
@@ -394,8 +422,9 @@ static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Defau
getDPIForMonitor = Shcore ? (GetDPIForMonitor_t)GetProcAddress(Shcore, "GetDpiForMonitor") : nullptr;
if ((Shcore == nullptr) || (getDPIForMonitor == nullptr)) {
- if (Shcore)
+ if (Shcore) {
FreeLibrary(Shcore);
+ }
Shcore = (HMODULE)INVALID_HANDLE_VALUE;
}
}
@@ -444,6 +473,13 @@ int DisplayServerWindows::screen_get_dpi(int p_screen) const {
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data);
return data.dpi;
}
+float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ EnumRefreshRateData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, SCREEN_REFRESH_RATE_FALLBACK };
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data);
+ return data.rate;
+}
bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const {
#ifndef _MSC_VER
@@ -504,13 +540,25 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {
wd.borderless = true;
}
- if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN) {
+ if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
wd.always_on_top = true;
}
if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
wd.no_focus = true;
}
+ if (p_flags & WINDOW_FLAG_POPUP_BIT) {
+ wd.is_popup = true;
+ }
+ // Inherit icons from MAIN_WINDOW for all sub windows.
+ HICON mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_SMALL, 0);
+ if (mainwindow_icon) {
+ SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)mainwindow_icon);
+ }
+ mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_BIG, 0);
+ if (mainwindow_icon) {
+ SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_BIG, (LPARAM)mainwindow_icon);
+ }
return window_id;
}
@@ -518,13 +566,14 @@ void DisplayServerWindows::show_window(WindowID p_id) {
ERR_FAIL_COND(!windows.has(p_id));
WindowData &wd = windows[p_id];
+ popup_open(p_id);
if (p_id != MAIN_WINDOW_ID) {
_update_window_style(p_id);
}
- ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window.
- if (!wd.no_focus) {
+ ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window.
+ if (!wd.no_focus && !wd.is_popup) {
SetForegroundWindow(wd.hWnd); // Slightly higher priority.
SetFocus(wd.hWnd); // Set keyboard focus.
}
@@ -536,6 +585,8 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window cannot be deleted.");
+ popup_close(p_window);
+
WindowData &wd = windows[p_window];
while (wd.transient_children.size()) {
@@ -571,6 +622,24 @@ void DisplayServerWindows::gl_window_make_current(DisplayServer::WindowID p_wind
#endif
}
+int64_t DisplayServerWindows::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+ ERR_FAIL_COND_V(!windows.has(p_window), 0);
+ switch (p_handle_type) {
+ case DISPLAY_HANDLE: {
+ return 0; // Not supported.
+ }
+ case WINDOW_HANDLE: {
+ return (int64_t)windows[p_window].hWnd;
+ }
+ case WINDOW_VIEW: {
+ return 0; // Not supported.
+ }
+ default: {
+ return 0;
+ }
+ }
+}
+
void DisplayServerWindows::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -675,8 +744,29 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi
ERR_FAIL_COND(!windows.has(p_window));
ERR_FAIL_INDEX(p_screen, get_screen_count());
- Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
- window_set_position(ofs + screen_get_position(p_screen), p_window);
+ const WindowData &wd = windows[p_window];
+ if (wd.fullscreen) {
+ int cs = window_get_current_screen(p_window);
+ if (cs == p_screen) {
+ return;
+ }
+ Point2 pos = screen_get_position(p_screen);
+ Size2 size = screen_get_size(p_screen);
+
+ MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE);
+ } else {
+ Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
+ window_set_position(ofs + screen_get_position(p_screen), p_window);
+ }
+
+ // Don't let the mouse leave the window when resizing to a smaller resolution.
+ if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
+ RECT crect;
+ GetClientRect(wd.hWnd, &crect);
+ ClientToScreen(wd.hWnd, (POINT *)&crect.left);
+ ClientToScreen(wd.hWnd, (POINT *)&crect.right);
+ ClipCursor(&crect);
+ }
}
Point2i DisplayServerWindows::window_get_position(WindowID p_window) const {
@@ -747,6 +837,23 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window
_update_real_mouse_position(p_window);
}
+void DisplayServerWindows::window_set_exclusive(WindowID p_window, bool p_exclusive) {
+ _THREAD_SAFE_METHOD_
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ if (wd.exclusive != p_exclusive) {
+ wd.exclusive = p_exclusive;
+ if (wd.transient_parent != INVALID_WINDOW_ID) {
+ WindowData &wd_parent = windows[wd.transient_parent];
+ if (wd.exclusive) {
+ SetWindowLongPtr(wd.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd);
+ } else {
+ SetWindowLongPtr(wd.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr);
+ }
+ }
+ }
+}
+
void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_parent) {
_THREAD_SAFE_METHOD_
@@ -769,7 +876,9 @@ void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_pa
wd_window.transient_parent = INVALID_WINDOW_ID;
wd_parent.transient_children.erase(p_window);
- SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr);
+ if (wd_window.exclusive) {
+ SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr);
+ }
} else {
ERR_FAIL_COND(!windows.has(p_parent));
ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent");
@@ -778,7 +887,9 @@ void DisplayServerWindows::window_set_transient(WindowID p_window, WindowID p_pa
wd_window.transient_parent = p_parent;
wd_parent.transient_children.insert(p_window);
- SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd);
+ if (wd_window.exclusive) {
+ SetWindowLongPtr(wd_window.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd);
+ }
}
}
@@ -905,7 +1016,7 @@ Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const {
return Size2();
}
-void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
+void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
// Windows docs for window styles:
// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
@@ -914,10 +1025,14 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
r_style_ex = WS_EX_WINDOWEDGE;
if (p_main_window) {
r_style_ex |= WS_EX_APPWINDOW;
+ r_style |= WS_VISIBLE;
}
if (p_fullscreen || p_borderless) {
r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past.
+ if (p_fullscreen && p_multiwindow_fs) {
+ r_style |= WS_BORDER; // Allows child windows to be displayed on top of full screen.
+ }
} else {
if (p_resizable) {
if (p_maximized) {
@@ -929,14 +1044,17 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
}
}
- if (!p_borderless) {
- r_style |= WS_VISIBLE;
- }
if (p_no_activate_focus) {
r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE;
}
+
+ if (!p_borderless && !p_no_activate_focus) {
+ r_style |= WS_VISIBLE;
+ }
+
r_style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
+ r_style_ex |= WS_EX_ACCEPTFILES;
}
void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repaint) {
@@ -948,12 +1066,12 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain
DWORD style = 0;
DWORD style_ex = 0;
- _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex);
+ _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus || wd.is_popup, style, style_ex);
SetWindowLongPtr(wd.hWnd, GWL_STYLE, style);
SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex);
- SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | (wd.no_focus ? SWP_NOACTIVATE : 0));
+ SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | ((wd.no_focus || wd.is_popup) ? SWP_NOACTIVATE : 0));
if (p_repaint) {
RECT rect;
@@ -968,10 +1086,11 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
- if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) {
+ if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
RECT rect;
wd.fullscreen = false;
+ wd.multiwindow_fs = false;
wd.maximized = wd.was_maximized;
if (wd.pre_fs_valid) {
@@ -1010,7 +1129,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
wd.minimized = true;
}
- if (p_mode == WINDOW_MODE_FULLSCREEN && !wd.fullscreen) {
+ if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
+ wd.multiwindow_fs = false;
+ _update_window_style(p_window, false);
+ } else {
+ wd.multiwindow_fs = true;
+ _update_window_style(p_window, false);
+ }
+
+ if ((p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) && !wd.fullscreen) {
if (wd.minimized) {
ShowWindow(wd.hWnd, SW_RESTORE);
}
@@ -1028,7 +1155,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
wd.maximized = false;
wd.minimized = false;
- _update_window_style(false);
+ _update_window_style(p_window, false);
MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE);
@@ -1039,6 +1166,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, 0, 0);
}
}
+
+ // Don't let the mouse leave the window when resizing to a smaller resolution.
+ if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
+ RECT crect;
+ GetClientRect(wd.hWnd, &crect);
+ ClientToScreen(wd.hWnd, (POINT *)&crect.left);
+ ClientToScreen(wd.hWnd, (POINT *)&crect.right);
+ ClipCursor(&crect);
+ }
}
DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const {
@@ -1048,7 +1184,11 @@ DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_windo
const WindowData &wd = windows[p_window];
if (wd.fullscreen) {
- return WINDOW_MODE_FULLSCREEN;
+ if (wd.multiwindow_fs) {
+ return WINDOW_MODE_FULLSCREEN;
+ } else {
+ return WINDOW_MODE_EXCLUSIVE_FULLSCREEN;
+ }
} else if (wd.minimized) {
return WINDOW_MODE_MINIMIZED;
} else if (wd.maximized) {
@@ -1082,6 +1222,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
wd.borderless = p_enabled;
_update_window_style(p_window);
_update_window_mouse_passthrough(p_window);
+ ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window.
} break;
case WINDOW_FLAG_ALWAYS_ON_TOP: {
ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top");
@@ -1095,6 +1236,11 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
wd.no_focus = p_enabled;
_update_window_style(p_window);
} break;
+ case WINDOW_FLAG_POPUP: {
+ ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
+ ERR_FAIL_COND_MSG(IsWindowVisible(wd.hWnd) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");
+ wd.is_popup = p_enabled;
+ } break;
case WINDOW_FLAG_MAX:
break;
}
@@ -1121,6 +1267,9 @@ bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window
case WINDOW_FLAG_NO_FOCUS: {
return wd.no_focus;
} break;
+ case WINDOW_FLAG_POPUP: {
+ return wd.is_popup;
+ } break;
case WINDOW_FLAG_MAX:
break;
}
@@ -1149,7 +1298,9 @@ void DisplayServerWindows::window_move_to_foreground(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
- SetForegroundWindow(wd.hWnd);
+ if (!wd.no_focus && !wd.is_popup) {
+ SetForegroundWindow(wd.hWnd);
+ }
}
bool DisplayServerWindows::window_can_draw(WindowID p_window) const {
@@ -1196,8 +1347,9 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI
wd.im_position = p_pos;
HIMC himc = ImmGetContext(wd.hWnd);
- if (himc == (HIMC)0)
+ if (himc == (HIMC)0) {
return;
+ }
COMPOSITIONFORM cps;
cps.dwStyle = CFS_FORCE_POSITION;
@@ -1207,23 +1359,6 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI
ImmReleaseContext(wd.hWnd, himc);
}
-void DisplayServerWindows::console_set_visible(bool p_enabled) {
- _THREAD_SAFE_METHOD_
-
- if (console_visible == p_enabled) {
- return;
- }
- if (!((OS_Windows *)OS::get_singleton())->_is_win11_terminal()) {
- // GetConsoleWindow is not supported by the Windows Terminal.
- ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE);
- console_visible = p_enabled;
- }
-}
-
-bool DisplayServerWindows::is_console_visible() const {
- return console_visible;
-}
-
void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) {
_THREAD_SAFE_METHOD_
@@ -1782,16 +1917,14 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) {
void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
#if defined(VULKAN_ENABLED)
- // TODO disabling for now
- //context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
+ context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
#endif
}
DisplayServer::VSyncMode DisplayServerWindows::window_get_vsync_mode(WindowID p_window) const {
_THREAD_SAFE_METHOD_
#if defined(VULKAN_ENABLED)
- //TODO disabling for now
- //return context_vulkan->get_vsync_mode(p_window);
+ return context_vulkan->get_vsync_mode(p_window);
#endif
return DisplayServer::VSYNC_ENABLED;
}
@@ -1876,33 +2009,144 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event)
Variant ret;
Callable::CallError ce;
+ {
+ List<WindowID>::Element *E = popup_list.front();
+ if (E && Object::cast_to<InputEventKey>(*p_event)) {
+ // Redirect keyboard input to active popup.
+ if (windows.has(E->get())) {
+ Callable callable = windows[E->get()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+ in_dispatch_input_event = false;
+ return;
+ }
+ }
+
Ref<InputEventFromWindow> event_from_window = p_event;
if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
// Send to a single window.
- if (!windows.has(event_from_window->get_window_id())) {
- in_dispatch_input_event = false;
- ERR_FAIL_MSG("DisplayServerWindows: Invalid window id in input event.");
- }
- Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
- if (callable.is_null()) {
- in_dispatch_input_event = false;
- return;
+ if (windows.has(event_from_window->get_window_id())) {
+ Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
}
- callable.call((const Variant **)&evp, 1, ret, ce);
} else {
// Send to all windows.
for (const KeyValue<WindowID, WindowData> &E : windows) {
const Callable callable = E.value.input_event_callback;
- if (callable.is_null()) {
- continue;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
}
- callable.call((const Variant **)&evp, 1, ret, ce);
}
}
in_dispatch_input_event = false;
}
+LRESULT CALLBACK MouseProc(int code, WPARAM wParam, LPARAM lParam) {
+ DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton());
+ if (ds_win) {
+ return ds_win->MouseProc(code, wParam, lParam);
+ } else {
+ return ::CallNextHookEx(nullptr, code, wParam, lParam);
+ }
+}
+
+DisplayServer::WindowID DisplayServerWindows::window_get_active_popup() const {
+ const List<WindowID>::Element *E = popup_list.back();
+ if (E) {
+ return E->get();
+ } else {
+ return INVALID_WINDOW_ID;
+ }
+}
+
+void DisplayServerWindows::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.parent_safe_rect = p_rect;
+}
+
+Rect2i DisplayServerWindows::window_get_popup_safe_rect(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());
+ const WindowData &wd = windows[p_window];
+ return wd.parent_safe_rect;
+}
+
+void DisplayServerWindows::popup_open(WindowID p_window) {
+ WindowData &wd = windows[p_window];
+ if (wd.is_popup) {
+ // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ List<WindowID>::Element *E = popup_list.back();
+ while (E) {
+ if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
+ _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ } else {
+ break;
+ }
+ }
+
+ time_since_popup = OS::get_singleton()->get_ticks_msec();
+ popup_list.push_back(p_window);
+ }
+}
+
+void DisplayServerWindows::popup_close(WindowID p_window) {
+ List<WindowID>::Element *E = popup_list.find(p_window);
+ while (E) {
+ _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->next();
+ popup_list.erase(E);
+ E = F;
+ }
+}
+
+LRESULT DisplayServerWindows::MouseProc(int code, WPARAM wParam, LPARAM lParam) {
+ _THREAD_SAFE_METHOD_
+ uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
+ if (delta > 250) {
+ switch (wParam) {
+ case WM_NCLBUTTONDOWN:
+ case WM_NCRBUTTONDOWN:
+ case WM_NCMBUTTONDOWN:
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN: {
+ MOUSEHOOKSTRUCT *ms = (MOUSEHOOKSTRUCT *)lParam;
+ Point2i pos = Point2i(ms->pt.x, ms->pt.y);
+ List<WindowID>::Element *E = popup_list.back();
+ while (E) {
+ // Popup window area.
+ Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
+ // Area of the parent window, which responsible for opening sub-menu.
+ Rect2i safe_rect = window_get_popup_safe_rect(E->get());
+ if (win_rect.has_point(pos)) {
+ break;
+ } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
+ break;
+ } else {
+ _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ List<WindowID>::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ }
+ }
+
+ } break;
+ }
+ }
+ return ::CallNextHookEx(mouse_monitor, code, wParam, lParam);
+}
+
// Our default window procedure to handle processing of window-related system messages/events.
// Also known as DefProc or DefWindowProc.
// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-procedures
@@ -1913,7 +2157,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} else {
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
- };
+ }
WindowID window_id = INVALID_WINDOW_ID;
bool window_created = false;
@@ -1935,6 +2179,13 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
// Process window messages.
switch (uMsg) {
+ case WM_MOUSEACTIVATE: {
+ if (windows[window_id].no_focus) {
+ return MA_NOACTIVATEANDEAT; // Do not activate, and discard mouse messages.
+ } else if (windows[window_id].is_popup) {
+ return MA_NOACTIVATE; // Do not activate, but process mouse messages.
+ }
+ } break;
case WM_SETFOCUS: {
windows[window_id].window_has_focus = true;
last_focused_window = window_id;
@@ -2018,8 +2269,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
case SC_MONITORPOWER: // Monitor trying to enter powersave?
return 0; // Prevent from happening.
case SC_KEYMENU:
- if ((lParam >> 16) <= 0)
+ if ((lParam >> 16) <= 0) {
return 0;
+ }
}
} break;
case WM_CLOSE: // Did we receive a close message?
@@ -2051,8 +2303,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
return 0;
}
- if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)
+ if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) {
OutputDebugString(TEXT("GetRawInputData does not return correct size !\n"));
+ }
RAWINPUT *raw = (RAWINPUT *)lpb;
@@ -2078,8 +2331,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_position(c);
mm->set_global_position(c);
- Input::get_singleton()->set_mouse_position(c);
- mm->set_speed(Vector2(0, 0));
+ mm->set_velocity(Vector2(0, 0));
if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY));
@@ -2148,8 +2400,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
ScreenToClient(windows[window_id].hWnd, &coords);
// Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
- if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED)
+ if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) {
break;
+ }
Ref<InputEventMouseMotion> mm;
mm.instantiate();
@@ -2183,8 +2436,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
SetCursorPos(pos.x, pos.y);
}
- Input::get_singleton()->set_mouse_position(mm->get_position());
- mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+ mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
if (old_invalid) {
old_x = mm->get_position().x;
@@ -2195,8 +2447,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
old_x = mm->get_position().x;
old_y = mm->get_position().y;
- if (windows[window_id].window_has_focus)
+ if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
+ }
}
return 0;
}
@@ -2325,8 +2578,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
SetCursorPos(pos.x, pos.y);
}
- Input::get_singleton()->set_mouse_position(mm->get_position());
- mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+ mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
if (old_invalid) {
old_x = mm->get_position().x;
@@ -2337,7 +2589,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
old_x = mm->get_position().x;
old_y = mm->get_position().y;
- if (windows[window_id].window_has_focus) {
+ if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
}
@@ -2426,8 +2678,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
SetCursorPos(pos.x, pos.y);
}
- Input::get_singleton()->set_mouse_position(mm->get_position());
- mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+ mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
if (old_invalid) {
old_x = mm->get_position().x;
@@ -2438,8 +2689,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
old_x = mm->get_position().x;
old_y = mm->get_position().y;
- if (windows[window_id].window_has_focus)
+ if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
+ }
} break;
case WM_LBUTTONDOWN:
@@ -2585,8 +2837,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
if (uMsg != WM_MOUSEWHEEL && uMsg != WM_MOUSEHWHEEL) {
if (mb->is_pressed()) {
- if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED)
+ if (++pressrc > 0 && mouse_mode != MOUSE_MODE_CAPTURED) {
SetCapture(hWnd);
+ }
} else {
if (--pressrc <= 0) {
if (mouse_mode != MOUSE_MODE_CAPTURED) {
@@ -2620,98 +2873,78 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
} break;
- case WM_MOVE: {
- if (!IsIconic(windows[window_id].hWnd)) {
- int x = int16_t(LOWORD(lParam));
- int y = int16_t(HIWORD(lParam));
- windows[window_id].last_pos = Point2(x, y);
-
- if (!windows[window_id].rect_changed_callback.is_null()) {
- Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height);
- Variant *sizep = &size;
- Variant ret;
- Callable::CallError ce;
- windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+
+ case WM_WINDOWPOSCHANGED: {
+ Rect2i window_client_rect;
+ Rect2i window_rect;
+ {
+ RECT rect;
+ GetClientRect(hWnd, &rect);
+ ClientToScreen(hWnd, (POINT *)&rect.left);
+ ClientToScreen(hWnd, (POINT *)&rect.right);
+ window_client_rect = Rect2i(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+
+ RECT wrect;
+ GetWindowRect(hWnd, &wrect);
+ window_rect = Rect2i(wrect.left, wrect.top, wrect.right - wrect.left, wrect.bottom - wrect.top);
+ }
+
+ WINDOWPOS *window_pos_params = (WINDOWPOS *)lParam;
+ WindowData &window = windows[window_id];
+
+ bool rect_changed = false;
+ if (!(window_pos_params->flags & SWP_NOSIZE) || window_pos_params->flags & SWP_FRAMECHANGED) {
+ int screen_id = window_get_current_screen(window_id);
+ Size2i screen_size = screen_get_size(screen_id);
+ Point2i screen_position = screen_get_position(screen_id);
+
+ window.maximized = false;
+ window.minimized = false;
+ window.fullscreen = false;
+
+ if (IsIconic(hWnd)) {
+ window.minimized = true;
+ } else if (IsZoomed(hWnd)) {
+ window.maximized = true;
+ } else if (window_rect.position == screen_position && window_rect.size == screen_size) {
+ window.fullscreen = true;
}
- }
- } break;
- case WM_SIZE: {
- // Ignore window size change when a SIZE_MINIMIZED event is triggered.
- if (wParam != SIZE_MINIMIZED) {
- // The new width and height of the client area.
- int window_w = LOWORD(lParam);
- int window_h = HIWORD(lParam);
-
- // Set new value to the size if it isn't preserved.
- if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) {
- windows[window_id].width = window_w;
- windows[window_id].height = window_h;
+ if (!window.minimized) {
+ window.width = window_client_rect.size.width;
+ window.height = window_client_rect.size.height;
+
+ rect_changed = true;
+ }
#if defined(VULKAN_ENABLED)
- if (context_vulkan && window_created) {
- context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height);
- }
+ if (context_vulkan && window_created) {
+ // Note: Trigger resize event to update swapchains when window is minimized/restored, even if size is not changed.
+ context_vulkan->window_resize(window_id, window.width, window.height);
+ }
#endif
+ }
- } else { // If the size is preserved.
- windows[window_id].preserve_window_size = false;
+ if (!window.minimized && (!(window_pos_params->flags & SWP_NOMOVE) || window_pos_params->flags & SWP_FRAMECHANGED)) {
+ window.last_pos = window_client_rect.position;
+ rect_changed = true;
+ }
- // Restore the old size.
- window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id);
+ if (rect_changed) {
+ if (!window.rect_changed_callback.is_null()) {
+ Variant size = Rect2i(window.last_pos.x, window.last_pos.y, window.width, window.height);
+ const Variant *args[] = { &size };
+ Variant ret;
+ Callable::CallError ce;
+ window.rect_changed_callback.call(args, 1, ret, ce);
}
- } else { // When the window has been minimized, preserve its size.
- windows[window_id].preserve_window_size = true;
}
- // Call windows rect change callback.
- if (!windows[window_id].rect_changed_callback.is_null()) {
- Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height);
- Variant *size_ptr = &size;
- Variant ret;
- Callable::CallError ce;
- windows[window_id].rect_changed_callback.call((const Variant **)&size_ptr, 1, ret, ce);
- }
-
- // The window has been maximized.
- if (wParam == SIZE_MAXIMIZED) {
- windows[window_id].maximized = true;
- windows[window_id].minimized = false;
- }
- // The window has been minimized.
- else if (wParam == SIZE_MINIMIZED) {
- windows[window_id].maximized = false;
- windows[window_id].minimized = true;
- windows[window_id].preserve_window_size = false;
- }
- // The window has been resized, but neither the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies.
- else if (wParam == SIZE_RESTORED) {
- windows[window_id].maximized = false;
- windows[window_id].minimized = false;
- }
-#if 0
- if (is_layered_allowed() && layered_window) {
- DeleteObject(hBitmap);
-
- RECT r;
- GetWindowRect(hWnd, &r);
- dib_size = Size2i(r.right - r.left, r.bottom - r.top);
-
- BITMAPINFO bmi;
- ZeroMemory(&bmi, sizeof(BITMAPINFO));
- bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
- bmi.bmiHeader.biWidth = dib_size.x;
- bmi.bmiHeader.biHeight = dib_size.y;
- bmi.bmiHeader.biPlanes = 1;
- bmi.bmiHeader.biBitCount = 32;
- bmi.bmiHeader.biCompression = BI_RGB;
- bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4;
- hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, nullptr, 0x0);
- SelectObject(hDC_dib, hBitmap);
-
- ZeroMemory(dib_data, dib_size.x * dib_size.y * 4);
- }
-#endif
+ // Return here to prevent WM_MOVE and WM_SIZE from being sent
+ // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-windowposchanged#remarks
+ return 0;
+
} break;
+
case WM_ENTERSIZEMOVE: {
Input::get_singleton()->release_pressed_events();
windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
@@ -2735,14 +2968,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
case WM_SYSKEYUP:
case WM_KEYUP:
case WM_KEYDOWN: {
- if (wParam == VK_SHIFT)
+ if (wParam == VK_SHIFT) {
shift_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
- if (wParam == VK_CONTROL)
+ }
+ if (wParam == VK_CONTROL) {
control_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
+ }
if (wParam == VK_MENU) {
alt_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
- if (lParam & (1 << 24))
+ if (lParam & (1 << 24)) {
gr_mem = alt_mem;
+ }
}
if (mouse_mode == MOUSE_MODE_CAPTURED) {
@@ -2751,10 +2987,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
_send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);
}
}
- /*
- if (wParam==VK_WIN) TODO wtf is this?
- meta_mem=uMsg==WM_KEYDOWN;
- */
[[fallthrough]];
}
case WM_CHAR: {
@@ -2769,10 +3001,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
ke.uMsg = uMsg;
ke.window_id = window_id;
- if (ke.uMsg == WM_SYSKEYDOWN)
+ if (ke.uMsg == WM_SYSKEYDOWN) {
ke.uMsg = WM_KEYDOWN;
- if (ke.uMsg == WM_SYSKEYUP)
+ }
+ if (ke.uMsg == WM_SYSKEYUP) {
ke.uMsg = WM_KEYUP;
+ }
ke.wParam = wParam;
ke.lParam = lParam;
@@ -2800,7 +3034,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
_drag_event(window_id, touch_pos.x, touch_pos.y, ti.dwID);
} else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) {
_touch_event(window_id, ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID);
- };
+ }
}
bHandled = TRUE;
} else {
@@ -2813,12 +3047,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
if (bHandled) {
CloseTouchInputHandle((HTOUCHINPUT)lParam);
return 0;
- };
+ }
} break;
case WM_DEVICECHANGE: {
joypad->probe_joypads();
} break;
+ case WM_DESTROY: {
+ Input::get_singleton()->flush_buffered_events();
+ } break;
case WM_SETCURSOR: {
if (LOWORD(lParam) == HTCLIENT) {
if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) {
@@ -2864,8 +3101,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
default: {
if (user_proc) {
return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam);
- };
- };
+ }
+ }
}
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
@@ -2873,10 +3110,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton());
- if (ds_win)
+ if (ds_win) {
return ds_win->WndProc(hWnd, uMsg, wParam, lParam);
- else
+ } else {
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
+ }
}
void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM wParam, LPARAM lParam) {
@@ -2886,6 +3124,9 @@ void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM
alt_mem = false;
control_mem = false;
shift_mem = false;
+
+ // Restore mouse mode.
+ _set_mouse_mode_impl(mouse_mode);
} else { // WM_INACTIVE.
Input::get_singleton()->release_pressed_events();
_send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT);
@@ -2940,8 +3181,9 @@ void DisplayServerWindows::_process_key_events() {
k->set_ctrl_pressed(false);
}
- if (k->get_unicode() < 32)
+ if (k->get_unicode() < 32) {
k->set_unicode(0);
+ }
Input::get_singleton()->parse_input_event(k);
} else {
@@ -2996,8 +3238,9 @@ void DisplayServerWindows::_process_key_events() {
k->set_ctrl_pressed(false);
}
- if (k->get_unicode() < 32)
+ if (k->get_unicode() < 32) {
k->set_unicode(0);
+ }
k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30))));
@@ -3053,7 +3296,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
DWORD dwExStyle;
DWORD dwStyle;
- _get_window_style(window_id_counter == MAIN_WINDOW_ID, p_mode == WINDOW_MODE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle);
+ _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle);
RECT WindowRect;
@@ -3062,7 +3305,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
WindowRect.top = p_rect.position.y;
WindowRect.bottom = p_rect.position.y + p_rect.size.y;
- if (p_mode == WINDOW_MODE_FULLSCREEN) {
+ if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
int nearest_area = 0;
Rect2i screen_rect;
for (int i = 0; i < get_screen_count(); i++) {
@@ -3105,7 +3348,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
windows.erase(id);
return INVALID_WINDOW_ID;
}
- if (p_mode != WINDOW_MODE_FULLSCREEN) {
+ if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
wd.pre_fs_valid = true;
}
@@ -3240,7 +3483,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
shift_mem = false;
control_mem = false;
meta_mem = false;
- console_visible = IsWindowVisible(GetConsoleWindow());
hInstance = ((OS_Windows *)OS::get_singleton())->get_hinstance();
pressrc = 0;
@@ -3302,7 +3544,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
wc.cbWndExtra = 0;
wc.hInstance = hInstance ? hInstance : GetModuleHandle(nullptr);
wc.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
- wc.hCursor = nullptr; //LoadCursor(nullptr, IDC_ARROW);
+ wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = L"Engine";
@@ -3353,11 +3595,12 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
return;
}
- // gl_manager->set_use_vsync(current_videomode.use_vsync);
RasterizerGLES3::make_current();
}
#endif
+ mouse_monitor = SetWindowsHookEx(WH_MOUSE, ::MouseProc, nullptr, GetCurrentThreadId());
+
Point2i window_position(
(screen_get_size(0).width - p_resolution.width) / 2,
(screen_get_size(0).height - p_resolution.height) / 2);
@@ -3383,14 +3626,13 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
#endif
- //set_ime_active(false);
-
if (!OS::get_singleton()->is_in_low_processor_usage_mode()) {
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
DWORD index = 0;
HANDLE handle = AvSetMmThreadCharacteristics("Games", &index);
- if (handle)
+ if (handle) {
AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL);
+ }
// This is needed to make sure that background work does not starve the main thread.
// This is only setting the priority of this thread, not the whole process.
@@ -3442,12 +3684,16 @@ DisplayServerWindows::~DisplayServerWindows() {
cursors_cache.clear();
+ if (mouse_monitor) {
+ UnhookWindowsHookEx(mouse_monitor);
+ }
+
if (user_proc) {
SetWindowLongPtr(windows[MAIN_WINDOW_ID].hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc);
- };
+ }
#ifdef GLES3_ENABLED
- // destroy windows .. NYI?
+ // destroy windows .. NYI?
#endif
if (windows.has(MAIN_WINDOW_ID)) {
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 409335b41c..a56a2b83ac 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -326,12 +326,12 @@ class DisplayServerWindows : public DisplayServer {
Vector<Vector2> mpath;
- bool preserve_window_size = false;
bool pre_fs_valid = false;
RECT pre_fs_rect;
bool maximized = false;
bool minimized = false;
bool fullscreen = false;
+ bool multiwindow_fs = false;
bool borderless = false;
bool resizable = true;
bool window_focused = false;
@@ -339,6 +339,7 @@ class DisplayServerWindows : public DisplayServer {
bool always_on_top = false;
bool no_focus = false;
bool window_has_focus = false;
+ bool exclusive = false;
// Used to transfer data between events using timer.
WPARAM saved_wparam;
@@ -386,9 +387,15 @@ class DisplayServerWindows : public DisplayServer {
WindowID transient_parent = INVALID_WINDOW_ID;
Set<WindowID> transient_children;
+
+ bool is_popup = false;
+ Rect2i parent_safe_rect;
};
JoypadWindows *joypad;
+ HHOOK mouse_monitor = nullptr;
+ List<WindowID> popup_list;
+ uint64_t time_since_popup = 0;
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect);
WindowID window_id_counter = MAIN_WINDOW_ID;
@@ -401,7 +408,7 @@ class DisplayServerWindows : public DisplayServer {
WNDPROC user_proc = nullptr;
void _send_window_event(const WindowData &wd, WindowEvent p_event);
- void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
+ void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
MouseMode mouse_mode;
int restore_mouse_trails = 0;
@@ -414,7 +421,6 @@ class DisplayServerWindows : public DisplayServer {
bool use_raw_input = false;
bool drop_events = false;
bool in_dispatch_input_event = false;
- bool console_visible = false;
WNDCLASSEXW wc;
@@ -440,6 +446,10 @@ class DisplayServerWindows : public DisplayServer {
public:
LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam);
+
+ void popup_open(WindowID p_window);
+ void popup_close(WindowID p_window);
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
@@ -459,6 +469,7 @@ public:
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
@@ -473,11 +484,17 @@ public:
virtual void show_window(WindowID p_window) override;
virtual void delete_sub_window(WindowID p_window) override;
+ virtual WindowID window_get_active_popup() const override;
+ virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override;
+ virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override;
+
+ virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
- virtual void gl_window_make_current(DisplayServer::WindowID p_window_id);
+ virtual void gl_window_make_current(DisplayServer::WindowID p_window_id) override;
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
@@ -497,6 +514,7 @@ public:
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+ virtual void window_set_exclusive(WindowID p_window, bool p_exclusive) override;
virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
@@ -529,9 +547,6 @@ public:
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
- virtual void console_set_visible(bool p_enabled) override;
- virtual bool is_console_visible() const override;
-
virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override;
virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 02b2d026b5..7b9cb59896 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -30,6 +30,9 @@
#include "export_plugin.h"
+#include "core/config/project_settings.h"
+#include "editor/editor_node.h"
+
Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
if (p_preset->get("codesign/enable")) {
return _code_sign(p_preset, p_path);
@@ -38,6 +41,18 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres
}
}
+Error EditorExportPlatformWindows::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
+ FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
+
+ f->store_line("@echo off");
+ f->store_line("title \"" + p_app_name + "\"");
+ f->store_line("\"%~dp0" + p_pkg_name + "\" \"%*\"");
+ f->store_line("pause > nul");
+
+ return OK;
+}
+
Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags);
@@ -51,16 +66,39 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>
err = _code_sign(p_preset, p_path);
}
+ String app_name;
+ if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") {
+ app_name = String(ProjectSettings::get_singleton()->get("application/config/name"));
+ } else {
+ app_name = "Unnamed";
+ }
+ app_name = OS::get_singleton()->get_safe_dir_name(app_name);
+
+ // Save console script.
+ if (err == OK) {
+ int con_scr = p_preset->get("debug/export_console_script");
+ if ((con_scr == 1 && p_debug) || (con_scr == 2)) {
+ String scr_path = p_path.get_basename() + ".cmd";
+ err = _export_debug_script(p_preset, app_name, p_path.get_file(), scr_path);
+ }
+ }
+
return err;
}
+bool EditorExportPlatformWindows::get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
+ // This option is not supported by "osslsigncode", used on non-Windows host.
+ if (!OS::get_singleton()->has_feature("windows") && p_option == "codesign/identity_type") {
+ return false;
+ }
+ return true;
+}
+
void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) {
EditorExportPlatformPC::get_export_options(r_options);
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false));
-#ifdef WINDOWS_ENABLED
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA1 hash)"), 0));
-#endif
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
@@ -70,8 +108,8 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), ""));
@@ -83,6 +121,7 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset>
String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
if (rcedit_path.is_empty()) {
+ WARN_PRINT("The rcedit tool is not configured in the Editor Settings (Export > Windows > Rcedit). No custom icon or app information data will be embedded in the exported executable.");
return;
}
@@ -321,3 +360,46 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
return OK;
}
+
+bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+ String err = "";
+ bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates);
+
+ String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
+ if (rcedit_path.is_empty()) {
+ err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n";
+ }
+
+ String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon"));
+ if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) {
+ err += TTR("Invalid icon path:") + " " + icon_path + "\n";
+ }
+
+ // Only non-negative integers can exist in the version string.
+
+ String file_version = p_preset->get("application/file_version");
+ if (!file_version.is_empty()) {
+ PackedStringArray version_array = file_version.split(".", false);
+ if (version_array.size() != 4 || !version_array[0].is_valid_int() ||
+ !version_array[1].is_valid_int() || !version_array[2].is_valid_int() ||
+ !version_array[3].is_valid_int() || file_version.find("-") > -1) {
+ err += TTR("Invalid file version:") + " " + file_version + "\n";
+ }
+ }
+
+ String product_version = p_preset->get("application/product_version");
+ if (!product_version.is_empty()) {
+ PackedStringArray version_array = product_version.split(".", false);
+ if (version_array.size() != 4 || !version_array[0].is_valid_int() ||
+ !version_array[1].is_valid_int() || !version_array[2].is_valid_int() ||
+ !version_array[3].is_valid_int() || product_version.find("-") > -1) {
+ err += TTR("Invalid product version:") + " " + product_version + "\n";
+ }
+ }
+
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index 4ec9342cdf..b40e872461 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -34,18 +34,20 @@
#include "core/io/file_access.h"
#include "core/os/os.h"
#include "editor/editor_export.h"
-#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "platform/windows/logo.gen.h"
class EditorExportPlatformWindows : public EditorExportPlatformPC {
void _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path);
+ Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);
public:
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
- virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
- virtual void get_export_options(List<ExportOption> *r_options);
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override;
+ virtual void get_export_options(List<ExportOption> *r_options) override;
+ virtual bool get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
};
#endif
diff --git a/platform/windows/gl_manager_windows.cpp b/platform/windows/gl_manager_windows.cpp
index 74b5f48502..a97fa99d7f 100644
--- a/platform/windows/gl_manager_windows.cpp
+++ b/platform/windows/gl_manager_windows.cpp
@@ -56,14 +56,9 @@ typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int
int GLManager_Windows::_find_or_create_display(GLWindow &win) {
// find display NYI, only 1 supported so far
- if (_displays.size())
+ if (_displays.size()) {
return 0;
-
- // for (unsigned int n = 0; n < _displays.size(); n++) {
- // const GLDisplay &d = _displays[n];
- // if (d.x11_display == p_x11_display)
- // return n;
- // }
+ }
// create
GLDisplay d_temp = {};
@@ -230,23 +225,27 @@ void GLManager_Windows::window_destroy(DisplayServer::WindowID p_window_id) {
}
void GLManager_Windows::release_current() {
- if (!_current_window)
+ if (!_current_window) {
return;
+ }
wglMakeCurrent(_current_window->hDC, nullptr);
}
void GLManager_Windows::window_make_current(DisplayServer::WindowID p_window_id) {
- if (p_window_id == -1)
+ if (p_window_id == -1) {
return;
+ }
GLWindow &win = _windows[p_window_id];
- if (!win.in_use)
+ if (!win.in_use) {
return;
+ }
// noop
- if (&win == _current_window)
+ if (&win == _current_window) {
return;
+ }
const GLDisplay &disp = get_display(win.gldisplay_id);
wglMakeCurrent(win.hDC, disp.hRC);
@@ -255,8 +254,9 @@ void GLManager_Windows::window_make_current(DisplayServer::WindowID p_window_id)
}
void GLManager_Windows::make_current() {
- if (!_current_window)
+ if (!_current_window) {
return;
+ }
if (!_current_window->in_use) {
WARN_PRINT("current window not in use!");
return;
@@ -269,8 +269,9 @@ void GLManager_Windows::swap_buffers() {
// NO NEED TO CALL SWAP BUFFERS for each window...
// see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml
- if (!_current_window)
+ if (!_current_window) {
return;
+ }
if (!_current_window->in_use) {
WARN_PRINT("current window not in use!");
return;
@@ -304,12 +305,15 @@ void GLManager_Windows::set_use_vsync(bool p_use) {
if (!setup) {
setup = true;
String extensions = glXQueryExtensionsString(x11_display, DefaultScreen(x11_display));
- if (extensions.find("GLX_EXT_swap_control") != -1)
+ if (extensions.find("GLX_EXT_swap_control") != -1) {
glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT");
- if (extensions.find("GLX_MESA_swap_control") != -1)
+ }
+ if (extensions.find("GLX_MESA_swap_control") != -1) {
glXSwapIntervalMESA = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalMESA");
- if (extensions.find("GLX_SGI_swap_control") != -1)
+ }
+ if (extensions.find("GLX_SGI_swap_control") != -1) {
glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI");
+ }
}
int val = p_use ? 1 : 0;
if (glXSwapIntervalMESA) {
@@ -319,8 +323,9 @@ void GLManager_Windows::set_use_vsync(bool p_use) {
} else if (glXSwapIntervalEXT) {
GLXDrawable drawable = glXGetCurrentDrawable();
glXSwapIntervalEXT(x11_display, drawable, val);
- } else
+ } else {
return;
+ }
use_vsync = p_use;
*/
}
diff --git a/platform/windows/godot.ico b/platform/windows/godot.ico
index dd611e07da..25830ffdc6 100644
--- a/platform/windows/godot.ico
+++ b/platform/windows/godot.ico
Binary files differ
diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp
index 7819ab9a32..ad4e3ae77c 100644
--- a/platform/windows/godot_windows.cpp
+++ b/platform/windows/godot_windows.cpp
@@ -39,7 +39,7 @@
#ifndef TOOLS_ENABLED
#if defined _MSC_VER
#pragma section("pck", read)
-__declspec(allocate("pck")) static char dummy[8] = { 0 };
+__declspec(allocate("pck")) static const char dummy[8] = { 0 };
#elif defined __GNUC__
static const char dummy[8] __attribute__((section("pck"), used)) = { 0 };
#endif
@@ -140,6 +140,11 @@ int widechar_main(int argc, wchar_t **argv) {
setlocale(LC_CTYPE, "");
+#ifndef TOOLS_ENABLED
+ // Workaround to prevent LTCG (MSVC LTO) from removing "pck" section
+ const char *dummy_guard = dummy;
+#endif
+
char **argv_utf8 = new char *[argc];
for (int i = 0; i < argc; ++i) {
@@ -158,8 +163,9 @@ int widechar_main(int argc, wchar_t **argv) {
return 255;
}
- if (Main::start())
+ if (Main::start()) {
os.run();
+ }
Main::cleanup();
for (int i = 0; i < argc; ++i) {
@@ -168,7 +174,7 @@ int widechar_main(int argc, wchar_t **argv) {
delete[] argv_utf8;
return os.get_exit_code();
-};
+}
int _main() {
LPWSTR *wc_argv;
diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp
index 8b6081d606..494e0b9105 100644
--- a/platform/windows/joypad_windows.cpp
+++ b/platform/windows/joypad_windows.cpp
@@ -60,8 +60,9 @@ JoypadWindows::JoypadWindows(HWND *hwnd) {
load_xinput();
- for (int i = 0; i < JOYPADS_MAX; i++)
+ for (int i = 0; i < JOYPADS_MAX; i++) {
attached_joypads[i] = false;
+ }
HRESULT result = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, nullptr);
if (result == DI_OK) {
@@ -144,8 +145,9 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
HRESULT hr;
int num = input->get_unused_joy_id();
- if (have_device(instance->guidInstance) || num == -1)
+ if (have_device(instance->guidInstance) || num == -1) {
return false;
+ }
d_joypads[num] = dinput_gamepad();
dinput_gamepad *joy = &d_joypads[num];
@@ -196,27 +198,28 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_
DIPROPRANGE prop_range;
DIPROPDWORD dilong;
LONG ofs;
- if (ob->guidType == GUID_XAxis)
+ if (ob->guidType == GUID_XAxis) {
ofs = DIJOFS_X;
- else if (ob->guidType == GUID_YAxis)
+ } else if (ob->guidType == GUID_YAxis) {
ofs = DIJOFS_Y;
- else if (ob->guidType == GUID_ZAxis)
+ } else if (ob->guidType == GUID_ZAxis) {
ofs = DIJOFS_Z;
- else if (ob->guidType == GUID_RxAxis)
+ } else if (ob->guidType == GUID_RxAxis) {
ofs = DIJOFS_RX;
- else if (ob->guidType == GUID_RyAxis)
+ } else if (ob->guidType == GUID_RyAxis) {
ofs = DIJOFS_RY;
- else if (ob->guidType == GUID_RzAxis)
+ } else if (ob->guidType == GUID_RzAxis) {
ofs = DIJOFS_RZ;
- else if (ob->guidType == GUID_Slider) {
+ } else if (ob->guidType == GUID_Slider) {
if (slider_count < 2) {
ofs = DIJOFS_SLIDER(slider_count);
slider_count++;
} else {
return;
}
- } else
+ } else {
return;
+ }
prop_range.diph.dwSize = sizeof(DIPROPRANGE);
prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
prop_range.diph.dwObj = ob->dwType;
@@ -227,8 +230,9 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_
dinput_gamepad &joy = d_joypads[p_joy_id];
res = IDirectInputDevice8_SetProperty(joy.di_joy, DIPROP_RANGE, &prop_range.diph);
- if (FAILED(res))
+ if (FAILED(res)) {
return;
+ }
dilong.diph.dwSize = sizeof(dilong);
dilong.diph.dwHeaderSize = sizeof(dilong.diph);
@@ -237,8 +241,9 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_
dilong.dwData = 0;
res = IDirectInputDevice8_SetProperty(joy.di_joy, DIPROP_DEADZONE, &dilong.diph);
- if (FAILED(res))
+ if (FAILED(res)) {
return;
+ }
joy.joy_axis.push_back(ofs);
}
@@ -268,8 +273,9 @@ void JoypadWindows::close_joypad(int id) {
return;
}
- if (!d_joypads[id].attached)
+ if (!d_joypads[id].attached) {
return;
+ }
d_joypads[id].di_joy->Unacquire();
d_joypads[id].di_joy->Release();
@@ -355,16 +361,18 @@ void JoypadWindows::process_joypads() {
}
} else if (joy.vibrating && joy.ff_end_timestamp != 0) {
uint64_t current_time = OS::get_singleton()->get_ticks_usec();
- if (current_time >= joy.ff_end_timestamp)
+ if (current_time >= joy.ff_end_timestamp) {
joypad_vibration_stop_xinput(i, current_time);
+ }
}
}
for (int i = 0; i < JOYPADS_MAX; i++) {
dinput_gamepad *joy = &d_joypads[i];
- if (!joy->attached)
+ if (!joy->attached) {
continue;
+ }
DIJOYSTATE2 js;
hr = joy->di_joy->Poll();
@@ -404,9 +412,9 @@ void JoypadWindows::process_joypads() {
if (joy->joy_axis[j] == axes[k]) {
input->joy_axis(joy->id, (JoyAxis)j, axis_correct(values[k]));
break;
- };
- };
- };
+ }
+ }
+ }
}
return;
}
@@ -446,35 +454,29 @@ void JoypadWindows::post_hat(int p_device, DWORD p_dpad) {
dpad_val = (HatMask)(HatMask::LEFT | HatMask::UP);
}
input->joy_hat(p_device, dpad_val);
-};
+}
-Input::JoyAxisValue JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const {
- Input::JoyAxisValue jx;
+float JoypadWindows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const {
if (Math::abs(p_val) < MIN_JOY_AXIS) {
- jx.min = p_trigger ? 0 : -1;
- jx.value = 0.0f;
- return jx;
- }
- if (p_xinput) {
- if (p_trigger) {
- jx.min = 0;
- jx.value = (float)p_val / MAX_TRIGGER;
- return jx;
- }
- jx.min = -1;
- if (p_val < 0) {
- jx.value = (float)p_val / MAX_JOY_AXIS;
- } else {
- jx.value = (float)p_val / (MAX_JOY_AXIS - 1);
- }
- if (p_negate) {
- jx.value = -jx.value;
- }
- return jx;
+ return p_trigger ? -1.0f : 0.0f;
+ }
+ if (!p_xinput) {
+ return (float)p_val / MAX_JOY_AXIS;
+ }
+ if (p_trigger) {
+ // Convert to a value between -1.0f and 1.0f.
+ return 2.0f * p_val / MAX_TRIGGER - 1.0f;
+ }
+ float value;
+ if (p_val < 0) {
+ value = (float)p_val / MAX_JOY_AXIS;
+ } else {
+ value = (float)p_val / (MAX_JOY_AXIS - 1);
+ }
+ if (p_negate) {
+ value = -value;
}
- jx.min = -1;
- jx.value = (float)p_val / MAX_JOY_AXIS;
- return jx;
+ return value;
}
void JoypadWindows::joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) {
diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h
index 4faefe932f..4f15bcf080 100644
--- a/platform/windows/joypad_windows.h
+++ b/platform/windows/joypad_windows.h
@@ -86,8 +86,9 @@ private:
attached = false;
confirmed = false;
- for (int i = 0; i < MAX_JOY_BUTTONS; i++)
+ for (int i = 0; i < MAX_JOY_BUTTONS; i++) {
last_buttons[i] = false;
+ }
}
};
@@ -132,7 +133,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::JoyAxisValue axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const;
+ float 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 938a777de6..e32dc0d1a6 100644
--- a/platform/windows/key_mapping_windows.cpp
+++ b/platform/windows/key_mapping_windows.cpp
@@ -32,117 +32,134 @@
#include <stdio.h>
+// This provides translation from Windows virtual key codes to Godot and back.
+// See WinUser.h and the below for documentation:
+// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
+
struct _WinTranslatePair {
Key keysym;
unsigned int keycode;
};
static _WinTranslatePair _vk_to_keycode[] = {
- { Key::BACKSPACE, VK_BACK }, // (0x08) // backspace
- { Key::TAB, VK_TAB }, //(0x09)
-
- //VK_CLEAR (0x0C)
-
- { Key::ENTER, VK_RETURN }, //(0x0D)
-
- { Key::SHIFT, VK_SHIFT }, //(0x10)
-
- { Key::CTRL, VK_CONTROL }, //(0x11)
-
- { Key::ALT, VK_MENU }, //(0x12)
-
- { Key::PAUSE, VK_PAUSE }, //(0x13)
+ // VK_LBUTTON (0x01)
+ // VK_RBUTTON (0x02)
+ // VK_CANCEL (0x03)
+ // VK_MBUTTON (0x04)
+ // VK_XBUTTON1 (0x05)
+ // VK_XBUTTON2 (0x06)
+ // We have no mappings for the above, as we only map keyboard buttons here.
- { Key::CAPSLOCK, VK_CAPITAL }, //(0x14)
+ // 0x07 is undefined.
- { Key::ESCAPE, VK_ESCAPE }, //(0x1B)
+ { Key::BACKSPACE, VK_BACK }, // (0x08)
+ { Key::TAB, VK_TAB }, // (0x09)
- { Key::SPACE, VK_SPACE }, //(0x20)
+ // 0x0A-0B are reserved.
- { Key::PAGEUP, VK_PRIOR }, //(0x21)
+ { Key::CLEAR, VK_CLEAR }, // (0x0C)
+ { Key::ENTER, VK_RETURN }, // (0x0D)
- { Key::PAGEDOWN, VK_NEXT }, //(0x22)
+ // 0x0E-0F are undefined.
- { Key::END, VK_END }, //(0x23)
+ { Key::SHIFT, VK_SHIFT }, // (0x10)
+ { Key::CTRL, VK_CONTROL }, // (0x11)
+ { Key::ALT, VK_MENU }, // (0x12)
+ { Key::PAUSE, VK_PAUSE }, // (0x13)
+ { Key::CAPSLOCK, VK_CAPITAL }, // (0x14)
- { Key::HOME, VK_HOME }, //(0x24)
+ // 0x15-1A are IME keys. We have no mapping.
- { Key::LEFT, VK_LEFT }, //(0x25)
+ { Key::ESCAPE, VK_ESCAPE }, // (0x1B)
- { Key::UP, VK_UP }, //(0x26)
-
- { Key::RIGHT, VK_RIGHT }, //(0x27)
+ // 0x1C-1F are IME keys. We have no mapping.
+ { Key::SPACE, VK_SPACE }, // (0x20)
+ { Key::PAGEUP, VK_PRIOR }, // (0x21)
+ { Key::PAGEDOWN, VK_NEXT }, // (0x22)
+ { Key::END, VK_END }, // (0x23)
+ { Key::HOME, VK_HOME }, // (0x24)
+ { Key::LEFT, VK_LEFT }, // (0x25)
+ { Key::UP, VK_UP }, // (0x26)
+ { Key::RIGHT, VK_RIGHT }, // (0x27)
{ Key::DOWN, VK_DOWN }, // (0x28)
- //VK_SELECT (0x29)
+ // VK_SELECT (0x29)
+ // Old select key, e.g. on Digital Equipment Corporation keyboards.
+ // Old and uncommon, we have no mapping.
{ Key::PRINT, VK_PRINT }, // (0x2A)
+ // Old IBM key, modern keyboards use VK_SNAPSHOT. Map to VK_SNAPSHOT.
- //VK_EXECUTE (0x2B)
+ // VK_EXECUTE (0x2B)
+ // Old and uncommon, we have no mapping.
{ Key::PRINT, VK_SNAPSHOT }, // (0x2C)
-
{ Key::INSERT, VK_INSERT }, // (0x2D)
-
{ Key::KEY_DELETE, VK_DELETE }, // (0x2E)
{ Key::HELP, VK_HELP }, // (0x2F)
-
- { Key::KEY_0, (0x30) }, ////0 key
- { Key::KEY_1, (0x31) }, ////1 key
- { Key::KEY_2, (0x32) }, ////2 key
- { Key::KEY_3, (0x33) }, ////3 key
- { Key::KEY_4, (0x34) }, ////4 key
- { Key::KEY_5, (0x35) }, ////5 key
- { Key::KEY_6, (0x36) }, ////6 key
- { Key::KEY_7, (0x37) }, ////7 key
- { Key::KEY_8, (0x38) }, ////8 key
- { Key::KEY_9, (0x39) }, ////9 key
- { Key::A, (0x41) }, ////A key
- { Key::B, (0x42) }, ////B key
- { Key::C, (0x43) }, ////C key
- { Key::D, (0x44) }, ////D key
- { Key::E, (0x45) }, ////E key
- { Key::F, (0x46) }, ////F key
- { Key::G, (0x47) }, ////G key
- { Key::H, (0x48) }, ////H key
- { Key::I, (0x49) }, ////I key
- { Key::J, (0x4A) }, ////J key
- { Key::K, (0x4B) }, ////K key
- { Key::L, (0x4C) }, ////L key
- { Key::M, (0x4D) }, ////M key
- { Key::N, (0x4E) }, ////N key
- { Key::O, (0x4F) }, ////O key
- { Key::P, (0x50) }, ////P key
- { Key::Q, (0x51) }, ////Q key
- { Key::R, (0x52) }, ////R key
- { Key::S, (0x53) }, ////S key
- { Key::T, (0x54) }, ////T key
- { Key::U, (0x55) }, ////U key
- { Key::V, (0x56) }, ////V key
- { Key::W, (0x57) }, ////W key
- { Key::X, (0x58) }, ////X key
- { Key::Y, (0x59) }, ////Y key
- { Key::Z, (0x5A) }, ////Z key
-
- { (Key)KeyModifierMask::META, VK_LWIN }, //(0x5B)
- { (Key)KeyModifierMask::META, VK_RWIN }, //(0x5C)
- { Key::MENU, VK_APPS }, //(0x5D)
- { Key::STANDBY, VK_SLEEP }, //(0x5F)
- { Key::KP_0, VK_NUMPAD0 }, //(0x60)
- { Key::KP_1, VK_NUMPAD1 }, //(0x61)
- { Key::KP_2, VK_NUMPAD2 }, //(0x62)
- { Key::KP_3, VK_NUMPAD3 }, //(0x63)
- { Key::KP_4, VK_NUMPAD4 }, //(0x64)
- { Key::KP_5, VK_NUMPAD5 }, //(0x65)
- { Key::KP_6, VK_NUMPAD6 }, //(0x66)
- { Key::KP_7, VK_NUMPAD7 }, //(0x67)
- { Key::KP_8, VK_NUMPAD8 }, //(0x68)
- { Key::KP_9, VK_NUMPAD9 }, //(0x69)
+ // Old and uncommon, but we have a mapping.
+
+ { Key::KEY_0, (0x30) }, // 0 key.
+ { Key::KEY_1, (0x31) }, // 1 key.
+ { Key::KEY_2, (0x32) }, // 2 key.
+ { Key::KEY_3, (0x33) }, // 3 key.
+ { Key::KEY_4, (0x34) }, // 4 key.
+ { Key::KEY_5, (0x35) }, // 5 key.
+ { Key::KEY_6, (0x36) }, // 6 key.
+ { Key::KEY_7, (0x37) }, // 7 key.
+ { Key::KEY_8, (0x38) }, // 8 key.
+ { Key::KEY_9, (0x39) }, // 9 key.
+ // 0x3A-40 are undefined.
+ { Key::A, (0x41) }, // A key.
+ { Key::B, (0x42) }, // B key.
+ { Key::C, (0x43) }, // C key.
+ { Key::D, (0x44) }, // D key.
+ { Key::E, (0x45) }, // E key.
+ { Key::F, (0x46) }, // F key.
+ { Key::G, (0x47) }, // G key.
+ { Key::H, (0x48) }, // H key.
+ { Key::I, (0x49) }, // I key
+ { Key::J, (0x4A) }, // J key.
+ { Key::K, (0x4B) }, // K key.
+ { Key::L, (0x4C) }, // L key.
+ { Key::M, (0x4D) }, // M key.
+ { Key::N, (0x4E) }, // N key.
+ { Key::O, (0x4F) }, // O key.
+ { Key::P, (0x50) }, // P key.
+ { Key::Q, (0x51) }, // Q key.
+ { Key::R, (0x52) }, // R key.
+ { Key::S, (0x53) }, // S key.
+ { Key::T, (0x54) }, // T key.
+ { Key::U, (0x55) }, // U key.
+ { Key::V, (0x56) }, // V key.
+ { Key::W, (0x57) }, // W key.
+ { Key::X, (0x58) }, // X key.
+ { Key::Y, (0x59) }, // Y key.
+ { Key::Z, (0x5A) }, // Z key.
+
+ { (Key)KeyModifierMask::META, VK_LWIN }, // (0x5B)
+ { (Key)KeyModifierMask::META, VK_RWIN }, // (0x5C)
+ { Key::MENU, VK_APPS }, // (0x5D)
+ // 0x5E is reserved.
+ { Key::STANDBY, VK_SLEEP }, // (0x5F)
+ { Key::KP_0, VK_NUMPAD0 }, // (0x60)
+ { Key::KP_1, VK_NUMPAD1 }, // (0x61)
+ { Key::KP_2, VK_NUMPAD2 }, // (0x62)
+ { Key::KP_3, VK_NUMPAD3 }, // (0x63)
+ { Key::KP_4, VK_NUMPAD4 }, // (0x64)
+ { Key::KP_5, VK_NUMPAD5 }, // (0x65)
+ { Key::KP_6, VK_NUMPAD6 }, // (0x66)
+ { Key::KP_7, VK_NUMPAD7 }, // (0x67)
+ { Key::KP_8, VK_NUMPAD8 }, // (0x68)
+ { Key::KP_9, VK_NUMPAD9 }, // (0x69)
{ Key::KP_MULTIPLY, VK_MULTIPLY }, // (0x6A)
{ Key::KP_ADD, VK_ADD }, // (0x6B)
- //VK_SEPARATOR (0x6C)
+ { Key::KP_PERIOD, VK_SEPARATOR }, // (0x6C)
+ // VK_SEPERATOR (key 0x6C) is not found on US keyboards.
+ // It is used on some Brazilian and Far East keyboards.
+ // We don't have a direct mapping, map to period.
{ Key::KP_SUBTRACT, VK_SUBTRACT }, // (0x6D)
{ Key::KP_PERIOD, VK_DECIMAL }, // (0x6E)
{ Key::KP_DIVIDE, VK_DIVIDE }, // (0x6F)
@@ -162,8 +179,17 @@ static _WinTranslatePair _vk_to_keycode[] = {
{ Key::F14, VK_F14 }, // (0x7D)
{ Key::F15, VK_F15 }, // (0x7E)
{ Key::F16, VK_F16 }, // (0x7F)
+ // We have no mappings for F17-F24. (0x80-87)
+ // 0x88-8F are reserved for UI navigation.
{ Key::NUMLOCK, VK_NUMLOCK }, // (0x90)
{ Key::SCROLLLOCK, VK_SCROLL }, // (0x91)
+
+ { Key::EQUAL, VK_OEM_NEC_EQUAL }, // (0x92)
+ // OEM NEC PC-9800 numpad '=' key.
+
+ // 0x93-96 are OEM specific (e.g. used by Fujitsu/OASYS), we have no mappings.
+ // 0x97-9F are unassigned.
+
{ Key::SHIFT, VK_LSHIFT }, // (0xA0)
{ Key::SHIFT, VK_RSHIFT }, // (0xA1)
{ Key::CTRL, VK_LCONTROL }, // (0xA2)
@@ -172,70 +198,124 @@ static _WinTranslatePair _vk_to_keycode[] = {
{ Key::MENU, VK_RMENU }, // (0xA5)
{ Key::BACK, VK_BROWSER_BACK }, // (0xA6)
-
{ Key::FORWARD, VK_BROWSER_FORWARD }, // (0xA7)
-
{ Key::REFRESH, VK_BROWSER_REFRESH }, // (0xA8)
-
{ Key::STOP, VK_BROWSER_STOP }, // (0xA9)
-
{ Key::SEARCH, VK_BROWSER_SEARCH }, // (0xAA)
-
{ Key::FAVORITES, VK_BROWSER_FAVORITES }, // (0xAB)
-
{ Key::HOMEPAGE, VK_BROWSER_HOME }, // (0xAC)
-
{ Key::VOLUMEMUTE, VK_VOLUME_MUTE }, // (0xAD)
-
{ Key::VOLUMEDOWN, VK_VOLUME_DOWN }, // (0xAE)
-
{ Key::VOLUMEUP, VK_VOLUME_UP }, // (0xAF)
-
{ Key::MEDIANEXT, VK_MEDIA_NEXT_TRACK }, // (0xB0)
-
{ Key::MEDIAPREVIOUS, VK_MEDIA_PREV_TRACK }, // (0xB1)
-
{ Key::MEDIASTOP, VK_MEDIA_STOP }, // (0xB2)
- //VK_MEDIA_PLAY_PAUSE (0xB3)
+ { Key::MEDIAPLAY, VK_MEDIA_PLAY_PAUSE }, // (0xB3)
+ // Media button play/pause toggle.
+ // Map to media play (there is no other 'play' mapping on Windows).
{ Key::LAUNCHMAIL, VK_LAUNCH_MAIL }, // (0xB4)
-
{ Key::LAUNCHMEDIA, VK_LAUNCH_MEDIA_SELECT }, // (0xB5)
-
{ Key::LAUNCH0, VK_LAUNCH_APP1 }, // (0xB6)
-
{ Key::LAUNCH1, VK_LAUNCH_APP2 }, // (0xB7)
+ // 0xB8-B9 are reserved.
+
{ Key::SEMICOLON, VK_OEM_1 }, // (0xBA)
+ // Misc. character, can vary by keyboard/region.
+ // Windows 2000/XP: For US standard keyboards, the ';:' key.
- { Key::EQUAL, VK_OEM_PLUS }, // (0xBB) // Windows 2000/XP: For any country/region, the '+' key
- { Key::COMMA, VK_OEM_COMMA }, // (0xBC) // Windows 2000/XP: For any country/region, the ',' key
- { Key::MINUS, VK_OEM_MINUS }, // (0xBD) // Windows 2000/XP: For any country/region, the '-' key
- { Key::PERIOD, VK_OEM_PERIOD }, // (0xBE) // Windows 2000/XP: For any country/region, the '.' key
- { Key::SLASH, VK_OEM_2 }, // (0xBF) //Windows 2000/XP: For the US standard keyboard, the '/?' key
+ { Key::EQUAL, VK_OEM_PLUS }, // (0xBB)
+ // Windows 2000/XP: For any country/region, the '+' key.
+ { Key::COMMA, VK_OEM_COMMA }, // (0xBC)
+ // Windows 2000/XP: For any country/region, the ',' key.
+ { Key::MINUS, VK_OEM_MINUS }, // (0xBD)
+ // Windows 2000/XP: For any country/region, the '-' key.
+ { Key::PERIOD, VK_OEM_PERIOD }, // (0xBE)
+ // Windows 2000/XP: For any country/region, the '.' key.
+
+ { Key::SLASH, VK_OEM_2 }, // (0xBF)
+ // Windows 2000/XP: For US standard keyboards, the '/?' key.
{ Key::QUOTELEFT, VK_OEM_3 }, // (0xC0)
- { Key::BRACELEFT, VK_OEM_4 }, // (0xDB)
+ // Windows 2000/XP: For US standard keyboards, the '`~' key.
+
+ // 0xC1-D7 are reserved. 0xD8-DA are unassigned.
+ // TODO: 0xC3-DA may be used for old gamepads? Maybe we want to support this? See WinUser.h.
+
+ { Key::BRACKETLEFT, VK_OEM_4 }, // (0xDB)
+ // Misc. character, can vary by keyboard/region.
+ // Windows 2000/XP: For US standard keyboards, the '[{' key.
+
{ Key::BACKSLASH, VK_OEM_5 }, // (0xDC)
- { Key::BRACERIGHT, VK_OEM_6 }, // (0xDD)
+ // Misc. character, can vary by keyboard/region.
+ // Windows 2000/XP: For US standard keyboards, the '\|' key.
+
+ { Key::BRACKETRIGHT, VK_OEM_6 }, // (0xDD)
+ // Misc. character, can vary by keyboard/region.
+ // Windows 2000/XP: For US standard keyboards, the ']}' key.
+
{ Key::APOSTROPHE, VK_OEM_7 }, // (0xDE)
- /*
-{VK_OEM_8 (0xDF)
-{VK_OEM_102 (0xE2) // Windows 2000/XP: Either the angle bracket key or the backslash key on the RT 102-key keyboard
-*/
- //{ Key::PLAY, VK_PLAY},// (0xFA)
+ // Misc. character, can vary by keyboard/region.
+ // Windows 2000/XP: For US standard keyboards, single quote/double quote.
+
+ // VK_OEM_8 (0xDF)
+ // Misc. character, can vary by keyboard/region. We have no mapping.
+
+ // 0xE0 is reserved. 0xE1 is OEM specific, we have no mapping.
+
+ // VK_OEM_102 (0xE2)
+ // Either angle bracket or backslash key on the RT 102-key keyboard.
+ // Old and uncommon, we have no mapping.
+
+ { Key::HELP, VK_ICO_HELP }, // (0xE3)
+ // OEM (ICO) help key. Map to help.
+
+ // 0xE4 is OEM (e.g. ICO) specific, we have no mapping.
+
+ // VK_PROCESSKEY (0xE5)
+ // For IME, we have no mapping.
+
+ { Key::CLEAR, VK_ICO_CLEAR }, // (0xE6)
+ // OEM (ICO) clear key. Map to clear.
+
+ // VK_PACKET (0xE7)
+ // Used to pass Unicode characters as if they were keystrokes.
+ // See Win32 API docs. We have no mapping.
+
+ // 0xE8 is unassigned, 0xE9-F5 are OEM (Nokia/Ericsson) specific, we have no mappings.
+
+ { Key::ESCAPE, VK_ATTN }, // (0xF6)
+ // Old IBM 'ATTN' key used on midrange computers, e.g. AS/400, map to Escape.
+
+ { Key::TAB, VK_CRSEL }, // (0xF7)
+ // Old IBM 3270 'CrSel' (cursor select) key, used to select data fields, map to Tab.
+
+ // VK_EXSEL (0xF7)
+ // Old IBM 3270 extended selection key. No mapping.
+
+ // VK_EREOF (0xF8)
+ // Old IBM 3270 erase to end of field key. No mapping.
+
+ { Key::MEDIAPLAY, VK_PLAY }, // (0xFA)
+ // Old IBM 3270 'Play' key. Map to media play.
+
+ // VK_ZOOM (0xFB)
+ // Old IBM 3290 'Zoom' key. No mapping.
+
+ // VK_NONAME (0xFC)
+ // Reserved. No mapping.
+
+ // VK_PA1 (0xFD)
+ // Old IBM 3270 PA1 key. No mapping.
+
+ { Key::CLEAR, VK_OEM_CLEAR }, // (0xFE)
+ // OEM specific clear key. Unclear how it differs from normal clear. Map to clear.
{ Key::UNKNOWN, 0 }
};
-/*
-VK_ZOOM (0xFB)
-VK_NONAME (0xFC)
-VK_PA1 (0xFD)
-VK_OEM_CLEAR (0xFE)
-*/
-
static _WinTranslatePair _scancode_to_keycode[] = {
{ Key::ESCAPE, 0x01 },
{ Key::KEY_1, 0x02 },
@@ -320,7 +400,6 @@ static _WinTranslatePair _scancode_to_keycode[] = {
{ Key::PAGEDOWN, 0x51 },
{ Key::INSERT, 0x52 },
{ Key::KEY_DELETE, 0x53 },
- //{ Key::???, 0x56 }, //NON US BACKSLASH
{ Key::F11, 0x57 },
{ Key::F12, 0x58 },
{ Key::META, 0x5B },
@@ -336,8 +415,6 @@ static _WinTranslatePair _scancode_to_keycode[] = {
Key KeyMappingWindows::get_keysym(unsigned int p_code) {
for (int i = 0; _vk_to_keycode[i].keysym != Key::UNKNOWN; i++) {
if (_vk_to_keycode[i].keycode == p_code) {
- //printf("outcode: %x\n",_vk_to_keycode[i].keysym);
-
return _vk_to_keycode[i].keysym;
}
}
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 8477746001..b4669e452a 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -46,14 +46,13 @@
#include "windows_terminal_logger.h"
#include <avrt.h>
+#include <bcrypt.h>
#include <direct.h>
#include <knownfolders.h>
#include <process.h>
#include <regstr.h>
#include <shlobj.h>
-static const WORD MAX_CONSOLE_LINES = 1500;
-
extern "C" {
__declspec(dllexport) DWORD NvOptimusEnablement = 1;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
@@ -85,71 +84,32 @@ static String format_error_message(DWORD id) {
return msg;
}
-void RedirectIOToConsole() {
- int hConHandle;
-
- intptr_t lStdHandle;
-
- CONSOLE_SCREEN_BUFFER_INFO coninfo;
-
- FILE *fp;
-
- // allocate a console for this app
-
- AllocConsole();
-
- // set the screen buffer to be big enough to let us scroll text
-
- GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
-
- coninfo.dwSize.Y = MAX_CONSOLE_LINES;
-
- SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
-
- // redirect unbuffered STDOUT to the console
-
- lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
-
- hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
-
- fp = _fdopen(hConHandle, "w");
-
- *stdout = *fp;
-
- setvbuf(stdout, nullptr, _IONBF, 0);
-
- // redirect unbuffered STDIN to the console
-
- lStdHandle = (intptr_t)GetStdHandle(STD_INPUT_HANDLE);
-
- hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
-
- fp = _fdopen(hConHandle, "r");
-
- *stdin = *fp;
-
- setvbuf(stdin, nullptr, _IONBF, 0);
-
- // redirect unbuffered STDERR to the console
-
- lStdHandle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
-
- hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
-
- fp = _fdopen(hConHandle, "w");
-
- *stderr = *fp;
-
- setvbuf(stderr, nullptr, _IONBF, 0);
+void RedirectStream(const char *p_file_name, const char *p_mode, FILE *p_cpp_stream, const DWORD p_std_handle) {
+ const HANDLE h_existing = GetStdHandle(p_std_handle);
+ if (h_existing != INVALID_HANDLE_VALUE) { // Redirect only if attached console have a valid handle.
+ const HANDLE h_cpp = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(p_cpp_stream)));
+ if (h_cpp == INVALID_HANDLE_VALUE) { // Redirect only if it's not already redirected to the pipe or file.
+ FILE *fp = p_cpp_stream;
+ freopen_s(&fp, p_file_name, p_mode, p_cpp_stream); // Redirect stream.
+ setvbuf(p_cpp_stream, nullptr, _IONBF, 0); // Disable stream buffering.
+ }
+ }
+}
- // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
+void RedirectIOToConsole() {
+ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
+ RedirectStream("CONIN$", "r", stdin, STD_INPUT_HANDLE);
+ RedirectStream("CONOUT$", "w", stdout, STD_OUTPUT_HANDLE);
+ RedirectStream("CONOUT$", "w", stderr, STD_ERROR_HANDLE);
- // point to console as well
+ printf("\n"); // Make sure our output is starting from the new line.
+ }
}
BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) {
- if (!EngineDebugger::is_active())
+ if (!EngineDebugger::is_active()) {
return FALSE;
+ }
switch (dwCtrlType) {
case CTRL_C_EVENT:
@@ -172,7 +132,9 @@ void OS_Windows::initialize_debugging() {
void OS_Windows::initialize() {
crash_handler.initialize();
- //RedirectIOToConsole();
+#ifndef WINDOWS_SUBSYSTEM_CONSOLE
+ RedirectIOToConsole();
+#endif
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA);
@@ -184,12 +146,8 @@ void OS_Windows::initialize() {
NetSocketPosix::make_default();
// We need to know how often the clock is updated
- if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second))
- ticks_per_second = 1000;
- // If timeAtGameStart is 0 then we get the time since
- // the start of the computer when we call GetGameTime()
- ticks_start = 0;
- ticks_start = get_ticks_usec();
+ QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second);
+ QueryPerformanceCounter((LARGE_INTEGER *)&ticks_start);
// set minimum resolution for periodic timers, otherwise Sleep(n) may wait at least as
// long as the windows scheduler resolution (~16-30ms) even for calls like Sleep(1)
@@ -209,8 +167,9 @@ void OS_Windows::initialize() {
}
void OS_Windows::delete_main_loop() {
- if (main_loop)
+ if (main_loop) {
memdelete(main_loop);
+ }
main_loop = nullptr;
}
@@ -223,8 +182,9 @@ void OS_Windows::finalize() {
driver_midi.close();
#endif
- if (main_loop)
+ if (main_loop) {
memdelete(main_loop);
+ }
main_loop = nullptr;
}
@@ -236,6 +196,12 @@ void OS_Windows::finalize_core() {
NetSocketPosix::cleanup();
}
+Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) {
+ NTSTATUS status = BCryptGenRandom(nullptr, r_buffer, p_bytes, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ ERR_FAIL_COND_V(status, FAILED);
+ return OK;
+}
+
Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
String path = p_path.replace("/", "\\");
@@ -298,12 +264,19 @@ OS::Date OS_Windows::get_date(bool p_utc) const {
GetLocalTime(&systemtime);
}
+ //Get DST information from Windows, but only if p_utc is false.
+ TIME_ZONE_INFORMATION info;
+ bool daylight = false;
+ if (!p_utc && GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) {
+ daylight = true;
+ }
+
Date date;
date.day = systemtime.wDay;
date.month = Month(systemtime.wMonth);
date.weekday = Weekday(systemtime.wDayOfWeek);
date.year = systemtime.wYear;
- date.dst = false;
+ date.dst = daylight;
return date;
}
@@ -325,19 +298,23 @@ OS::Time OS_Windows::get_time(bool p_utc) const {
OS::TimeZoneInfo OS_Windows::get_time_zone_info() const {
TIME_ZONE_INFORMATION info;
bool daylight = false;
- if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT)
+ if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) {
daylight = true;
+ }
+ // Daylight Bias needs to be added to the bias if DST is in effect, or else it will not properly update.
TimeZoneInfo ret;
if (daylight) {
ret.name = info.DaylightName;
+ ret.bias = info.Bias + info.DaylightBias;
} else {
ret.name = info.StandardName;
+ ret.bias = info.Bias + info.StandardBias;
}
// 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
- ret.bias = -info.Bias;
+ ret.bias = -ret.bias;
return ret;
}
@@ -359,18 +336,21 @@ double OS_Windows::get_unix_time() const {
}
void OS_Windows::delay_usec(uint32_t p_usec) const {
- if (p_usec < 1000)
+ if (p_usec < 1000) {
Sleep(1);
- else
+ } else {
Sleep(p_usec / 1000);
+ }
}
uint64_t OS_Windows::get_ticks_usec() const {
uint64_t ticks;
// This is the number of clock ticks since start
- if (!QueryPerformanceCounter((LARGE_INTEGER *)&ticks))
- ticks = (UINT64)timeGetTime();
+ QueryPerformanceCounter((LARGE_INTEGER *)&ticks);
+ // Subtract the ticks at game start to get
+ // the ticks since the game started
+ ticks -= ticks_start;
// Divide by frequency to get the time in seconds
// original calculation shown below is subject to overflow
@@ -390,9 +370,6 @@ uint64_t OS_Windows::get_ticks_usec() const {
// seconds
time += seconds * 1000000L;
- // Subtract the time at game start to get
- // the time since the game started
- time -= ticks_start;
return time;
}
@@ -406,78 +383,87 @@ String OS_Windows::_quote_command_line_argument(const String &p_text) const {
return p_text;
}
-Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
+Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
String path = p_path.replace("/", "\\");
String command = _quote_command_line_argument(path);
for (const String &E : p_arguments) {
command += " " + _quote_command_line_argument(E);
}
+ ProcessInfo pi;
+ ZeroMemory(&pi.si, sizeof(pi.si));
+ pi.si.cb = sizeof(pi.si);
+ ZeroMemory(&pi.pi, sizeof(pi.pi));
+ LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si;
+
+ bool inherit_handles = false;
+ HANDLE pipe[2] = { nullptr, nullptr };
if (r_pipe) {
+ // Create pipe for StdOut and StdErr.
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa.bInheritHandle = true;
+ sa.lpSecurityDescriptor = nullptr;
+
+ ERR_FAIL_COND_V(!CreatePipe(&pipe[0], &pipe[1], &sa, 0), ERR_CANT_FORK);
+ ERR_FAIL_COND_V(!SetHandleInformation(pipe[0], HANDLE_FLAG_INHERIT, 0), ERR_CANT_FORK); // Read handle is for host process only and should not be inherited.
+
+ pi.si.dwFlags |= STARTF_USESTDHANDLES;
+ pi.si.hStdOutput = pipe[1];
if (read_stderr) {
- command += " 2>&1"; // Include stderr
+ pi.si.hStdError = pipe[1];
}
- // Add extra quotes around the full command, to prevent it from stripping quotes in the command,
- // because _wpopen calls command as "cmd.exe /c command", instead of executing it directly
- command = _quote_command_line_argument(command);
-
- FILE *f = _wpopen((LPCWSTR)(command.utf16().get_data()), L"r");
- ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, "Cannot create pipe from command: " + command);
- char buf[65535];
- while (fgets(buf, 65535, f)) {
+ inherit_handles = true;
+ }
+ DWORD creation_flags = NORMAL_PRIORITY_CLASS;
+ if (p_open_console) {
+ creation_flags |= CREATE_NEW_CONSOLE;
+ } else {
+ creation_flags |= CREATE_NO_WINDOW;
+ }
+
+ int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, nullptr, si_w, &pi.pi);
+ if (!ret && r_pipe) {
+ CloseHandle(pipe[0]); // Cleanup pipe handles.
+ CloseHandle(pipe[1]);
+ }
+ ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command);
+
+ if (r_pipe) {
+ CloseHandle(pipe[1]); // Close pipe write handle (only child process is writing).
+ char buf[4096];
+ DWORD read = 0;
+ for (;;) { // Read StdOut and StdErr from pipe.
+ bool success = ReadFile(pipe[0], buf, 4096, &read, NULL);
+ if (!success || read == 0) {
+ break;
+ }
if (p_pipe_mutex) {
p_pipe_mutex->lock();
}
- (*r_pipe) += String::utf8(buf);
+ (*r_pipe) += String::utf8(buf, read);
if (p_pipe_mutex) {
p_pipe_mutex->unlock();
}
}
- int rv = _pclose(f);
-
- if (r_exitcode) {
- *r_exitcode = rv;
- }
- return OK;
+ CloseHandle(pipe[0]); // Close pipe read handle.
+ } else {
+ WaitForSingleObject(pi.pi.hProcess, INFINITE);
}
- ProcessInfo pi;
- ZeroMemory(&pi.si, sizeof(pi.si));
- pi.si.cb = sizeof(pi.si);
- ZeroMemory(&pi.pi, sizeof(pi.pi));
- LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si;
-
- DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS;
-#ifndef DEBUG_ENABLED
- dwCreationFlags |= CREATE_NO_WINDOW;
-#endif
-
- int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, dwCreationFlags, nullptr, nullptr, si_w, &pi.pi);
- ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command);
-
- WaitForSingleObject(pi.pi.hProcess, INFINITE);
if (r_exitcode) {
DWORD ret2;
GetExitCodeProcess(pi.pi.hProcess, &ret2);
*r_exitcode = ret2;
}
+
CloseHandle(pi.pi.hProcess);
CloseHandle(pi.pi.hThread);
return OK;
-};
-
-bool OS_Windows::_is_win11_terminal() const {
- HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
- DWORD dwMode = 0;
- if (GetConsoleMode(hStdOut, &dwMode)) {
- return ((dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == ENABLE_VIRTUAL_TERMINAL_PROCESSING);
- } else {
- return false;
- }
}
-Error OS_Windows::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) {
+Error OS_Windows::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
String path = p_path.replace("/", "\\");
String command = _quote_command_line_argument(path);
for (const String &E : p_arguments) {
@@ -490,16 +476,14 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg
ZeroMemory(&pi.pi, sizeof(pi.pi));
LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si;
- DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS;
-#ifndef DEBUG_ENABLED
- dwCreationFlags |= CREATE_NO_WINDOW;
-#endif
- if (p_path == get_executable_path() && GetConsoleWindow() != nullptr && _is_win11_terminal()) {
- // Open a new terminal as a workaround for Windows Terminal bug.
- dwCreationFlags |= CREATE_NEW_CONSOLE;
+ DWORD creation_flags = NORMAL_PRIORITY_CLASS;
+ if (p_open_console) {
+ creation_flags |= CREATE_NEW_CONSOLE;
+ } else {
+ creation_flags |= CREATE_NO_WINDOW;
}
- int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, dwCreationFlags, nullptr, nullptr, si_w, &pi.pi);
+ int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, nullptr, si_w, &pi.pi);
ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command);
ProcessID pid = pi.pi.dwProcessId;
@@ -509,7 +493,7 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg
process_map->insert(pid, pi);
return OK;
-};
+}
Error OS_Windows::kill(const ProcessID &p_pid) {
ERR_FAIL_COND_V(!process_map->has(p_pid), FAILED);
@@ -523,15 +507,16 @@ Error OS_Windows::kill(const ProcessID &p_pid) {
CloseHandle(pi.hThread);
return ret != 0 ? OK : FAILED;
-};
+}
int OS_Windows::get_process_id() const {
return _getpid();
}
Error OS_Windows::set_cwd(const String &p_cwd) {
- if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0)
+ if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0) {
return ERR_CANT_OPEN;
+ }
return OK;
}
@@ -554,7 +539,7 @@ bool OS_Windows::has_environment(const String &p_var) const {
free(env);
return has_env;
#endif
-};
+}
String OS_Windows::get_environment(const String &p_var) const {
WCHAR wval[0x7fff]; // MSDN says 32767 char is the maximum
@@ -573,7 +558,7 @@ String OS_Windows::get_stdin_string(bool p_block) {
if (p_block) {
char buff[1024];
return fgets(buff, 1024, stdin);
- };
+ }
return String();
}
@@ -611,17 +596,20 @@ String OS_Windows::get_locale() const {
int sublang = SUBLANGID(langid);
while (wl->locale) {
- if (wl->main_lang == lang && wl->sublang == SUBLANG_NEUTRAL)
+ if (wl->main_lang == lang && wl->sublang == SUBLANG_NEUTRAL) {
neutral = wl->locale;
+ }
- if (lang == wl->main_lang && sublang == wl->sublang)
+ if (lang == wl->main_lang && sublang == wl->sublang) {
return String(wl->locale).replace("-", "_");
+ }
wl++;
}
- if (!neutral.is_empty())
+ if (!neutral.is_empty()) {
return String(neutral).replace("-", "_");
+ }
return "en";
}
@@ -648,25 +636,48 @@ BOOL is_wow64() {
int OS_Windows::get_processor_count() const {
SYSTEM_INFO sysinfo;
- if (is_wow64())
+ if (is_wow64()) {
GetNativeSystemInfo(&sysinfo);
- else
+ } else {
GetSystemInfo(&sysinfo);
+ }
return sysinfo.dwNumberOfProcessors;
}
+String OS_Windows::get_processor_name() const {
+ const String id = "Hardware\\Description\\System\\CentralProcessor\\0";
+
+ HKEY hkey;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) {
+ ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
+ }
+
+ WCHAR buffer[256];
+ DWORD buffer_len = 256;
+ DWORD vtype = REG_SZ;
+ if (RegQueryValueExW(hkey, L"ProcessorNameString", NULL, &vtype, (LPBYTE)buffer, &buffer_len) == ERROR_SUCCESS) {
+ RegCloseKey(hkey);
+ return String::utf16((const char16_t *)buffer, buffer_len).strip_edges();
+ } else {
+ RegCloseKey(hkey);
+ ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
+ }
+}
+
void OS_Windows::run() {
- if (!main_loop)
+ if (!main_loop) {
return;
+ }
main_loop->initialize();
while (!force_quit) {
DisplayServer::get_singleton()->process_events(); // get rid of pending events
- if (Main::iteration())
+ if (Main::iteration()) {
break;
- };
+ }
+ }
main_loop->finalize();
}
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 4e61f3be7e..5bfd24327e 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -40,8 +40,6 @@
#include "drivers/winmidi/midi_driver_winmidi.h"
#include "key_mapping_windows.h"
#include "servers/audio_server.h"
-#include "servers/rendering/renderer_compositor.h"
-#include "servers/rendering_server.h"
#ifdef XAUDIO2_ENABLED
#include "drivers/xaudio2/audio_driver_xaudio2.h"
#endif
@@ -108,6 +106,8 @@ protected:
public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+ virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override;
+
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
@@ -128,8 +128,8 @@ public:
virtual void delay_usec(uint32_t p_usec) const override;
virtual uint64_t get_ticks_usec() const override;
- virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override;
- virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
+ virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
+ virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override;
@@ -142,6 +142,7 @@ public:
virtual String get_locale() const override;
virtual int get_processor_count() const override;
+ virtual String get_processor_name() const override;
virtual String get_config_path() const override;
virtual String get_data_path() const override;
@@ -157,7 +158,6 @@ public:
void run();
- bool _is_win11_terminal() const;
virtual bool _check_internal_feature_support(const String &p_feature) override;
virtual void disable_crash_handler() override;
diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp
index 0d5f0e617c..df21977698 100644
--- a/platform/windows/windows_terminal_logger.cpp
+++ b/platform/windows/windows_terminal_logger.cpp
@@ -44,25 +44,29 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er
const unsigned int BUFFER_SIZE = 16384;
char buf[BUFFER_SIZE + 1]; // +1 for the terminating character
int len = vsnprintf(buf, BUFFER_SIZE, p_format, p_list);
- if (len <= 0)
+ if (len <= 0) {
return;
- if ((unsigned int)len >= BUFFER_SIZE)
+ }
+ if ((unsigned int)len >= BUFFER_SIZE) {
len = BUFFER_SIZE; // Output is too big, will be truncated
+ }
buf[len] = 0;
int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, len, nullptr, 0);
- if (wlen < 0)
+ if (wlen < 0) {
return;
+ }
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;
- if (p_err)
+ if (p_err) {
fwprintf(stderr, L"%ls", wbuf);
- else
+ } else {
wprintf(L"%ls", wbuf);
+ }
memfree(wbuf);