summaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/android_input_handler.cpp1
-rw-r--r--platform/android/android_keys_utils.cpp9
-rw-r--r--platform/android/android_keys_utils.h20
-rw-r--r--platform/android/detect.py2
-rw-r--r--platform/android/export/export.cpp3
-rw-r--r--platform/android/export/export_plugin.cpp123
-rw-r--r--platform/android/export/export_plugin.h3
-rw-r--r--platform/android/file_access_android.cpp81
-rw-r--r--platform/android/file_access_android.h6
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp51
-rw-r--r--platform/android/file_access_filesystem_jandroid.h6
-rw-r--r--platform/android/java/app/AndroidManifest.xml1
-rw-r--r--platform/android/java/app/assetPacks/installTime/build.gradle4
-rw-r--r--platform/android/java/app/build.gradle43
-rw-r--r--platform/android/java/app/config.gradle23
-rw-r--r--platform/android/java/app/settings.gradle4
-rw-r--r--platform/android/java/build.gradle19
-rw-r--r--platform/android/java/editor/build.gradle17
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml1
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt4
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.properties3
-rw-r--r--platform/android/java/lib/AndroidManifest.xml1
-rw-r--r--platform/android/java/lib/build.gradle8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt16
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java26
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java44
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java44
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java16
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java14
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt4
-rw-r--r--platform/android/java/nativeSrcsConfigs/AndroidManifest.xml2
-rw-r--r--platform/android/java/nativeSrcsConfigs/build.gradle2
-rw-r--r--platform/android/java/settings.gradle5
-rw-r--r--platform/android/java_godot_lib_jni.cpp2
-rw-r--r--platform/android/java_godot_wrapper.cpp2
-rw-r--r--platform/android/java_godot_wrapper.h2
-rw-r--r--platform/ios/SCsub1
-rw-r--r--platform/ios/detect.py2
-rw-r--r--platform/ios/display_server_ios.h2
-rw-r--r--platform/ios/display_server_ios.mm3
-rw-r--r--platform/ios/doc_classes/EditorExportPlatformIOS.xml55
-rw-r--r--platform/ios/export/export_plugin.cpp582
-rw-r--r--platform/ios/export/export_plugin.h12
-rw-r--r--platform/ios/ios_terminal_logger.h45
-rw-r--r--platform/ios/ios_terminal_logger.mm74
-rw-r--r--platform/ios/key_mapping_ios.h1
-rw-r--r--platform/ios/key_mapping_ios.mm20
-rw-r--r--platform/ios/keyboard_input_view.mm8
-rw-r--r--platform/ios/os_ios.mm10
-rw-r--r--platform/ios/view_controller.mm10
-rw-r--r--platform/linuxbsd/detect.py2
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp2
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp115
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h8
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp2
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp74
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h5
-rw-r--r--platform/linuxbsd/x11/key_mapping_x11.cpp24
-rw-r--r--platform/linuxbsd/x11/key_mapping_x11.h2
-rw-r--r--platform/macos/SCsub1
-rw-r--r--platform/macos/detect.py43
-rw-r--r--platform/macos/display_server_macos.h6
-rw-r--r--platform/macos/display_server_macos.mm274
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml7
-rw-r--r--platform/macos/export/codesign.cpp2
-rw-r--r--platform/macos/export/codesign.h3
-rw-r--r--platform/macos/export/export_plugin.cpp35
-rw-r--r--platform/macos/export/plist.cpp848
-rw-r--r--platform/macos/export/plist.h128
-rw-r--r--platform/macos/godot_content_view.mm29
-rw-r--r--platform/macos/godot_open_save_delegate.h66
-rw-r--r--platform/macos/godot_open_save_delegate.mm250
-rw-r--r--platform/macos/key_mapping_macos.h1
-rw-r--r--platform/macos/key_mapping_macos.mm24
-rw-r--r--platform/macos/os_macos.mm2
-rw-r--r--platform/web/.eslintrc.html.js2
-rw-r--r--platform/web/SCsub4
-rw-r--r--platform/web/audio_driver_web.cpp52
-rw-r--r--platform/web/audio_driver_web.h51
-rw-r--r--platform/web/detect.py35
-rw-r--r--platform/web/display_server_web.cpp3
-rw-r--r--platform/web/display_server_web.h1
-rw-r--r--platform/web/doc_classes/EditorExportPlatformWeb.xml4
-rw-r--r--platform/web/dom_keys.inc21
-rw-r--r--platform/web/emscripten_helpers.py41
-rw-r--r--platform/web/export/export_plugin.cpp30
-rw-r--r--platform/web/export/export_plugin.h7
-rw-r--r--platform/web/js/engine/features.js22
-rw-r--r--platform/web/js/libs/audio.worklet.js4
-rw-r--r--platform/windows/SCsub22
-rw-r--r--platform/windows/detect.py97
-rw-r--r--platform/windows/display_server_windows.cpp352
-rw-r--r--platform/windows/display_server_windows.h6
-rw-r--r--platform/windows/export/export_plugin.cpp2
-rw-r--r--platform/windows/godot.natvis141
-rw-r--r--platform/windows/godot_res.rc14
-rw-r--r--platform/windows/godot_res_wrap.rc14
-rw-r--r--platform/windows/key_mapping_windows.cpp23
-rw-r--r--platform/windows/key_mapping_windows.h1
-rw-r--r--platform/windows/os_windows.cpp6
-rw-r--r--platform/windows/vulkan_context_win.h2
105 files changed, 2561 insertions, 1827 deletions
diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp
index bd194478d9..8e7f355114 100644
--- a/platform/android/android_input_handler.cpp
+++ b/platform/android/android_input_handler.cpp
@@ -124,6 +124,7 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod
ev->set_physical_keycode(physical_keycode);
ev->set_key_label(fix_key_label(p_key_label, keycode));
ev->set_unicode(fix_unicode(unicode));
+ ev->set_location(godot_location_from_android_code(p_physical_keycode));
ev->set_pressed(p_pressed);
ev->set_echo(p_echo);
diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp
index f50437e82a..83ee98e8bc 100644
--- a/platform/android/android_keys_utils.cpp
+++ b/platform/android/android_keys_utils.cpp
@@ -38,3 +38,12 @@ Key godot_code_from_android_code(unsigned int p_code) {
}
return Key::UNKNOWN;
}
+
+KeyLocation godot_location_from_android_code(unsigned int p_code) {
+ for (int i = 0; android_godot_location_pairs[i].android_code != AKEYCODE_MAX; i++) {
+ if (android_godot_location_pairs[i].android_code == p_code) {
+ return android_godot_location_pairs[i].godot_code;
+ }
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h
index 5cf5628a8b..77c0911f2b 100644
--- a/platform/android/android_keys_utils.h
+++ b/platform/android/android_keys_utils.h
@@ -177,4 +177,24 @@ static AndroidGodotCodePair android_godot_code_pairs[] = {
Key godot_code_from_android_code(unsigned int p_code);
+// Key location determination.
+struct AndroidGodotLocationPair {
+ unsigned int android_code = 0;
+ KeyLocation godot_code = KeyLocation::UNSPECIFIED;
+};
+
+static AndroidGodotLocationPair android_godot_location_pairs[] = {
+ { AKEYCODE_ALT_LEFT, KeyLocation::LEFT },
+ { AKEYCODE_ALT_RIGHT, KeyLocation::RIGHT },
+ { AKEYCODE_SHIFT_LEFT, KeyLocation::LEFT },
+ { AKEYCODE_SHIFT_RIGHT, KeyLocation::RIGHT },
+ { AKEYCODE_CTRL_LEFT, KeyLocation::LEFT },
+ { AKEYCODE_CTRL_RIGHT, KeyLocation::RIGHT },
+ { AKEYCODE_META_LEFT, KeyLocation::LEFT },
+ { AKEYCODE_META_RIGHT, KeyLocation::RIGHT },
+ { AKEYCODE_MAX, KeyLocation::UNSPECIFIED }
+};
+
+KeyLocation godot_location_from_android_code(unsigned int p_code);
+
#endif // ANDROID_KEYS_UTILS_H
diff --git a/platform/android/detect.py b/platform/android/detect.py
index a417ef454b..b396e5eb2d 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -200,7 +200,7 @@ def configure(env: "Environment"):
env.Append(LIBS=["OpenSLES", "EGL", "android", "log", "z", "dl"])
if env["vulkan"]:
- env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
if not env["use_volk"]:
env.Append(LIBS=["vulkan"])
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index ee1ec2790d..138714634f 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -42,12 +42,15 @@ void register_android_exporter_types() {
void register_android_exporter() {
#ifndef ANDROID_ENABLED
+ EDITOR_DEF("export/android/java_sdk_path", OS::get_singleton()->get_environment("JAVA_HOME"));
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME"));
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
EDITOR_DEF("export/android/debug_keystore", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"));
EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey");
EDITOR_DEF("export/android/debug_keystore_pass", "android");
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD));
EDITOR_DEF("export/android/force_system_user", false);
EDITOR_DEF("export/android/shutdown_adb_on_exit", true);
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 10d54e8d97..459f5a5983 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -45,9 +45,9 @@
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/import/resource_importer_texture_settings.h"
+#include "editor/themes/editor_scale.h"
#include "main/splash.gen.h"
#include "scene/resources/image_texture.h"
@@ -255,7 +255,7 @@ static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/instal
static const int OPENGL_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
static const int VULKAN_MIN_SDK_VERSION = 24;
-static const int DEFAULT_TARGET_SDK_VERSION = 33; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
+static const int DEFAULT_TARGET_SDK_VERSION = 34; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
#ifndef ANDROID_ENABLED
void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
@@ -1111,7 +1111,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
}
for (int i = 0; i < feature_names.size(); i++) {
- String feature_name = feature_names[i];
+ const String &feature_name = feature_names[i];
bool feature_required = feature_required_list[i];
int feature_version = feature_versions[i];
bool has_version_attribute = feature_version != -1;
@@ -1605,7 +1605,11 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> &
print_verbose("Loading regular icon from " + path);
if (path.is_empty() || ImageLoader::load_image(path, icon) != OK) {
print_verbose("- falling back to project icon: " + project_icon_path);
- ImageLoader::load_image(project_icon_path, icon);
+ if (!project_icon_path.is_empty()) {
+ ImageLoader::load_image(project_icon_path, icon);
+ } else {
+ ERR_PRINT("No project icon specified. Please specify one in the Project Settings under Application -> Config -> Icon");
+ }
}
// Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
@@ -1823,10 +1827,10 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password", PROPERTY_HINT_PASSWORD, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password", PROPERTY_HINT_PASSWORD, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
@@ -2111,8 +2115,17 @@ Ref<Texture2D> EditorExportPlatformAndroid::get_run_icon() const {
return run_icon;
}
+String EditorExportPlatformAndroid::get_java_path() {
+ String exe_ext;
+ if (OS::get_singleton()->get_name() == "Windows") {
+ exe_ext = ".exe";
+ }
+ String java_sdk_path = EDITOR_GET("export/android/java_sdk_path");
+ return java_sdk_path.path_join("bin/java" + exe_ext);
+}
+
String EditorExportPlatformAndroid::get_adb_path() {
- String exe_ext = "";
+ String exe_ext;
if (OS::get_singleton()->get_name() == "Windows") {
exe_ext = ".exe";
}
@@ -2124,13 +2137,13 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_
if (p_target_sdk == -1) {
p_target_sdk = DEFAULT_TARGET_SDK_VERSION;
}
- String exe_ext = "";
+ String exe_ext;
if (OS::get_singleton()->get_name() == "Windows") {
exe_ext = ".bat";
}
String apksigner_command_name = "apksigner" + exe_ext;
String sdk_path = EDITOR_GET("export/android/android_sdk_path");
- String apksigner_path = "";
+ String apksigner_path;
Error errn;
String build_tools_dir = sdk_path.path_join("build-tools");
@@ -2229,6 +2242,54 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_
return apksigner_path;
}
+static bool has_valid_keystore_credentials(String &r_error_str, const String &p_keystore, const String &p_username, const String &p_password, const String &p_type) {
+ String output;
+ List<String> args;
+ args.push_back("-list");
+ args.push_back("-keystore");
+ args.push_back(p_keystore);
+ args.push_back("-storepass");
+ args.push_back(p_password);
+ args.push_back("-alias");
+ args.push_back(p_username);
+ Error error = OS::get_singleton()->execute("keytool", args, &output, nullptr, true);
+ String keytool_error = "keytool error:";
+ bool valid = output.substr(0, keytool_error.length()) != keytool_error;
+
+ if (error != OK) {
+ r_error_str = TTR("Error: There was a problem validating the keystore username and password");
+ return false;
+ }
+ if (!valid) {
+ r_error_str = TTR(p_type + " Username and/or Password is invalid for the given " + p_type + " Keystore");
+ return false;
+ }
+ r_error_str = "";
+ return true;
+}
+
+bool EditorExportPlatformAndroid::has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error) {
+ String dk = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH);
+ String dk_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER);
+ String dk_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS);
+ String rk = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH);
+ String rk_user = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER);
+ String rk_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS);
+
+ bool valid = true;
+ if (!dk.is_empty() && !dk_user.is_empty() && !dk_password.is_empty()) {
+ String err = "";
+ valid = has_valid_keystore_credentials(err, dk, dk_user, dk_password, "Debug");
+ r_error += err;
+ }
+ if (!rk.is_empty() && !rk_user.is_empty() && !rk_password.is_empty()) {
+ String err = "";
+ valid = has_valid_keystore_credentials(err, rk, rk_user, rk_password, "Release");
+ r_error += err;
+ }
+ return valid;
+}
+
bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
String err;
bool valid = false;
@@ -2329,6 +2390,32 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
err += TTR("Release keystore incorrectly configured in the export preset.") + "\n";
}
+ String java_sdk_path = EDITOR_GET("export/android/java_sdk_path");
+ if (java_sdk_path.is_empty()) {
+ err += TTR("A valid Java SDK path is required in Editor Settings.") + "\n";
+ valid = false;
+ } else {
+ // Validate the given path by checking that `java` is present under the `bin` directory.
+ Error errn;
+ // Check for the bin directory.
+ Ref<DirAccess> da = DirAccess::open(java_sdk_path.path_join("bin"), &errn);
+ if (errn != OK) {
+ err += TTR("Invalid Java SDK path in Editor Settings.");
+ err += TTR("Missing 'bin' directory!");
+ err += "\n";
+ valid = false;
+ } else {
+ // Check for the `java` command.
+ String java_path = get_java_path();
+ if (!FileAccess::exists(java_path)) {
+ err += TTR("Unable to find 'java' command using the Java SDK path.");
+ err += TTR("Please check the Java SDK directory specified in Editor Settings.");
+ err += "\n";
+ valid = false;
+ }
+ }
+ }
+
String sdk_path = EDITOR_GET("export/android/android_sdk_path");
if (sdk_path.is_empty()) {
err += TTR("A valid Android SDK path is required in Editor Settings.") + "\n";
@@ -2842,6 +2929,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unsupported export format!"));
return ERR_UNCONFIGURED;
}
+ String err_string;
+ if (!has_valid_username_and_password(p_preset, err_string)) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR(err_string));
+ return ERR_UNCONFIGURED;
+ }
if (use_gradle_build) {
print_verbose("Starting gradle build...");
@@ -2861,6 +2953,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
}
}
const String assets_directory = get_assets_directory(p_preset, export_format);
+ String java_sdk_path = EDITOR_GET("export/android/java_sdk_path");
+ if (java_sdk_path.is_empty()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Java SDK path must be configured in Editor Settings at 'export/android/java_sdk_path'."));
+ return ERR_UNCONFIGURED;
+ }
+ print_verbose("Java sdk path: " + java_sdk_path);
+
String sdk_path = EDITOR_GET("export/android/android_sdk_path");
if (sdk_path.is_empty()) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'."));
@@ -2911,8 +3010,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
print_verbose("Storing command line flags...");
store_file_at_path(assets_directory + "/_cl_", command_line_flags);
+ print_verbose("Updating JAVA_HOME environment to " + java_sdk_path);
+ OS::get_singleton()->set_environment("JAVA_HOME", java_sdk_path);
+
print_verbose("Updating ANDROID_HOME environment to " + sdk_path);
- OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
+ OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path);
String build_command;
#ifdef WINDOWS_ENABLED
@@ -2975,6 +3077,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
String combined_android_dependencies_maven_repos = String("|").join(android_dependencies_maven_repos);
List<String> cmdline;
+ cmdline.push_back("validateJavaVersion");
if (clean_build_required) {
cmdline.push_back("clean");
}
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index a2d0417c5d..c282055fba 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -226,8 +226,11 @@ public:
static String get_apksigner_path(int p_target_sdk = -1, bool p_check_executes = false);
+ static String get_java_path();
+
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
+ static bool has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error);
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index 1249f2219f..f56eda4694 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -121,6 +121,75 @@ uint8_t FileAccessAndroid::get_8() const {
return byte;
}
+uint16_t FileAccessAndroid::get_16() const {
+ if (pos >= len) {
+ eof = true;
+ return 0;
+ }
+
+ uint16_t bytes = 0;
+ int r = AAsset_read(asset, &bytes, 2);
+
+ if (r >= 0) {
+ pos += r;
+ if (pos >= len) {
+ eof = true;
+ }
+ }
+
+ if (big_endian) {
+ bytes = BSWAP16(bytes);
+ }
+
+ return bytes;
+}
+
+uint32_t FileAccessAndroid::get_32() const {
+ if (pos >= len) {
+ eof = true;
+ return 0;
+ }
+
+ uint32_t bytes = 0;
+ int r = AAsset_read(asset, &bytes, 4);
+
+ if (r >= 0) {
+ pos += r;
+ if (pos >= len) {
+ eof = true;
+ }
+ }
+
+ if (big_endian) {
+ bytes = BSWAP32(bytes);
+ }
+
+ return bytes;
+}
+
+uint64_t FileAccessAndroid::get_64() const {
+ if (pos >= len) {
+ eof = true;
+ return 0;
+ }
+
+ uint64_t bytes = 0;
+ int r = AAsset_read(asset, &bytes, 8);
+
+ if (r >= 0) {
+ pos += r;
+ if (pos >= len) {
+ eof = true;
+ }
+ }
+
+ if (big_endian) {
+ bytes = BSWAP64(bytes);
+ }
+
+ return bytes;
+}
+
uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
@@ -151,6 +220,18 @@ void FileAccessAndroid::store_8(uint8_t p_dest) {
ERR_FAIL();
}
+void FileAccessAndroid::store_16(uint16_t p_dest) {
+ ERR_FAIL();
+}
+
+void FileAccessAndroid::store_32(uint32_t p_dest) {
+ ERR_FAIL();
+}
+
+void FileAccessAndroid::store_64(uint64_t p_dest) {
+ ERR_FAIL();
+}
+
bool FileAccessAndroid::file_exists(const String &p_path) {
String path = fix_path(p_path).simplify_path();
if (path.begins_with("/")) {
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index 3aa4ca98fc..ec613b6687 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -66,12 +66,18 @@ public:
virtual bool eof_reached() const override; // reading passed EOF
virtual uint8_t get_8() const override; // get a byte
+ virtual uint16_t get_16() const override;
+ virtual uint32_t get_32() const override;
+ virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; // get last error
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; // store a byte
+ virtual void store_16(uint16_t p_dest) override;
+ virtual void store_32(uint32_t p_dest) override;
+ virtual void store_64(uint64_t p_dest) override;
virtual bool file_exists(const String &p_path) override; // return true if a file exists
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index beea73fd61..46d9728632 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -181,6 +181,36 @@ uint8_t FileAccessFilesystemJAndroid::get_8() const {
return byte;
}
+uint16_t FileAccessFilesystemJAndroid::get_16() const {
+ ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
+ uint16_t bytes = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&bytes), 2);
+ if (big_endian) {
+ bytes = BSWAP16(bytes);
+ }
+ return bytes;
+}
+
+uint32_t FileAccessFilesystemJAndroid::get_32() const {
+ ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
+ uint32_t bytes = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&bytes), 4);
+ if (big_endian) {
+ bytes = BSWAP32(bytes);
+ }
+ return bytes;
+}
+
+uint64_t FileAccessFilesystemJAndroid::get_64() const {
+ ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
+ uint64_t bytes = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&bytes), 8);
+ if (big_endian) {
+ bytes = BSWAP64(bytes);
+ }
+ return bytes;
+}
+
String FileAccessFilesystemJAndroid::get_line() const {
ERR_FAIL_COND_V_MSG(!is_open(), String(), "File must be opened before use.");
@@ -250,6 +280,27 @@ void FileAccessFilesystemJAndroid::store_8(uint8_t p_dest) {
store_buffer(&p_dest, 1);
}
+void FileAccessFilesystemJAndroid::store_16(uint16_t p_dest) {
+ if (big_endian) {
+ p_dest = BSWAP16(p_dest);
+ }
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 2);
+}
+
+void FileAccessFilesystemJAndroid::store_32(uint32_t p_dest) {
+ if (big_endian) {
+ p_dest = BSWAP32(p_dest);
+ }
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 4);
+}
+
+void FileAccessFilesystemJAndroid::store_64(uint64_t p_dest) {
+ if (big_endian) {
+ p_dest = BSWAP64(p_dest);
+ }
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 8);
+}
+
void FileAccessFilesystemJAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) {
if (_file_write) {
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index 0c3f8d7259..f33aa64ebe 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -77,6 +77,9 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
virtual uint8_t get_8() const override; ///< get a byte
+ virtual uint16_t get_16() const override;
+ virtual uint32_t get_32() const override;
+ virtual uint64_t get_64() const override;
virtual String get_line() const override; ///< get a line
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
@@ -84,6 +87,9 @@ public:
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
+ virtual void store_16(uint16_t p_dest) override;
+ virtual void store_32(uint32_t p_dest) override;
+ virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 079f629b12..4abc6548bf 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="com.godot.game"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto" >
diff --git a/platform/android/java/app/assetPacks/installTime/build.gradle b/platform/android/java/app/assetPacks/installTime/build.gradle
index b06faac374..46fa046ed4 100644
--- a/platform/android/java/app/assetPacks/installTime/build.gradle
+++ b/platform/android/java/app/assetPacks/installTime/build.gradle
@@ -1,4 +1,6 @@
-apply plugin: 'com.android.asset-pack'
+plugins {
+ id 'com.android.asset-pack'
+}
assetPack {
packName = "installTime" // Directory name for the asset pack
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 01b148aeef..f084c60209 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -1,17 +1,4 @@
// Gradle build config for Godot Engine's Android port.
-buildscript {
- apply from: 'config.gradle'
-
- repositories {
- google()
- mavenCentral()
- }
- dependencies {
- classpath libraries.androidGradlePlugin
- classpath libraries.kotlinGradlePlugin
- }
-}
-
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
@@ -23,6 +10,8 @@ allprojects {
repositories {
google()
mavenCentral()
+ gradlePluginPortal()
+ maven { url "https://plugins.gradle.org/m2/" }
// Godot user plugins custom maven repos
String[] mavenRepos = getGodotPluginsMavenRepos()
@@ -42,8 +31,7 @@ configurations {
}
dependencies {
- implementation libraries.kotlinStdLib
- implementation libraries.androidxFragment
+ implementation "androidx.fragment:fragment:$versions.fragmentVersion"
if (rootProject.findProject(":lib")) {
implementation project(":lib")
@@ -88,6 +76,8 @@ android {
assetPacks = [":assetPacks:installTime"]
+ namespace = 'com.godot.game'
+
defaultConfig {
// The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects.
aaptOptions {
@@ -241,3 +231,26 @@ task copyAndRenameReleaseAab(type: Copy) {
into getExportPath()
rename "build-release.aab", getExportFilename()
}
+
+/**
+ * Used to validate the version of the Java SDK used for the Godot gradle builds.
+ */
+task validateJavaVersion {
+ if (JavaVersion.current() != versions.javaVersion) {
+ throw new GradleException("Invalid Java version ${JavaVersion.current()}. Version ${versions.javaVersion} is the required Java version for Godot gradle builds.")
+ }
+}
+
+/*
+When they're scheduled to run, the copy*AARToAppModule tasks generate dependencies for the 'app'
+module, so we're ensuring the ':app:preBuild' task is set to run after those tasks.
+ */
+if (rootProject.tasks.findByPath("copyDebugAARToAppModule") != null) {
+ preBuild.mustRunAfter(rootProject.tasks.named("copyDebugAARToAppModule"))
+}
+if (rootProject.tasks.findByPath("copyDevAARToAppModule") != null) {
+ preBuild.mustRunAfter(rootProject.tasks.named("copyDevAARToAppModule"))
+}
+if (rootProject.tasks.findByPath("copyReleaseAARToAppModule") != null) {
+ preBuild.mustRunAfter(rootProject.tasks.named("copyReleaseAARToAppModule"))
+}
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index a91e7bc7ce..7224765f28 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,27 +1,20 @@
ext.versions = [
- androidGradlePlugin: '7.2.1',
- compileSdk : 33,
+ androidGradlePlugin: '8.2.0',
+ compileSdk : 34,
// Also update 'platform/android/export/export_plugin.cpp#OPENGL_MIN_SDK_VERSION'
minSdk : 21,
// Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
- targetSdk : 33,
- buildTools : '33.0.2',
- kotlinVersion : '1.7.0',
- fragmentVersion : '1.3.6',
- nexusPublishVersion: '1.1.0',
- javaVersion : 17,
+ targetSdk : 34,
+ buildTools : '34.0.0',
+ kotlinVersion : '1.9.20',
+ fragmentVersion : '1.6.2',
+ nexusPublishVersion: '1.3.0',
+ javaVersion : JavaVersion.VERSION_17,
// Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
ndkVersion : '23.2.8568313'
]
-ext.libraries = [
- androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin",
- kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion",
- kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlinVersion",
- androidxFragment : "androidx.fragment:fragment:$versions.fragmentVersion",
-]
-
ext.getExportPackageName = { ->
// Retrieve the app id from the project property set by the Godot build command.
String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : ""
diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle
index b4524a3f60..dcac44e393 100644
--- a/platform/android/java/app/settings.gradle
+++ b/platform/android/java/app/settings.gradle
@@ -7,8 +7,10 @@ pluginManagement {
id 'org.jetbrains.kotlin.android' version versions.kotlinVersion
}
repositories {
- gradlePluginPortal()
google()
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url "https://plugins.gradle.org/m2/" }
}
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index f94454e2a7..a8cabc7045 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -1,18 +1,3 @@
-buildscript {
- apply from: 'app/config.gradle'
-
- 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.3.0'
- }
-}
-
plugins {
id 'io.github.gradle-nexus.publish-plugin'
}
@@ -31,6 +16,8 @@ allprojects {
repositories {
google()
mavenCentral()
+ gradlePluginPortal()
+ maven { url "https://plugins.gradle.org/m2/" }
}
}
@@ -303,7 +290,7 @@ task generateGodotTemplates {
*/
task generateDevTemplate {
// add parameter to set symbols to true
- gradle.startParameter.projectProperties += [doNotStrip: true]
+ gradle.startParameter.projectProperties += [doNotStrip: "true"]
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
dependsOn = templateBuildTasks()
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index 38034aa47c..0f7ffeecae 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -2,14 +2,14 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
+ id 'base'
}
dependencies {
- implementation libraries.kotlinStdLib
- implementation libraries.androidxFragment
+ implementation "androidx.fragment:fragment:$versions.fragmentVersion"
implementation project(":lib")
- implementation "androidx.window:window:1.0.0"
+ implementation "androidx.window:window:1.2.0"
}
ext {
@@ -81,6 +81,8 @@ android {
buildToolsVersion versions.buildTools
ndkVersion versions.ndkVersion
+ namespace = "org.godotengine.editor"
+
defaultConfig {
// The 'applicationId' suffix allows to install Godot 3.x(v3) and 4.x(v4) on the same device
applicationId "org.godotengine.editor.v4"
@@ -90,7 +92,10 @@ android {
targetSdkVersion versions.targetSdk
missingDimensionStrategy 'products', 'editor'
- setProperty("archivesBaseName", "android_editor")
+ }
+
+ base {
+ archivesName = "android_editor"
}
compileOptions {
@@ -111,6 +116,10 @@ android {
}
}
+ buildFeatures {
+ buildConfig = true
+ }
+
buildTypes {
dev {
initWith debug
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index 1405b6c737..78dcddac0e 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="org.godotengine.editor"
android:installLocation="auto">
<supports-screens
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index 0d7017ae71..caf64bc933 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -91,6 +91,10 @@ open class GodotEditor : GodotActivity() {
private val commandLineParams = ArrayList<String>()
override fun onCreate(savedInstanceState: Bundle?) {
+ // We exclude certain permissions from the set we request at startup, as they'll be
+ // requested on demand based on use-cases.
+ PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
+
val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
Log.d(TAG, "Received parameters ${params.contentToString()}")
updateCommandLineParams(params)
diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
index aa991fceae..471fefaf90 100644
--- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties
+++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Wed Jan 17 12:08:26 PST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml
index f03a1dd47a..8240843876 100644
--- a/platform/android/java/lib/AndroidManifest.xml
+++ b/platform/android/java/lib/AndroidManifest.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.godotengine.godot"
android:versionCode="1"
android:versionName="1.0">
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 4340250ad3..61ae0cd58a 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -10,8 +10,7 @@ ext {
apply from: "../scripts/publish-module.gradle"
dependencies {
- implementation libraries.kotlinStdLib
- implementation libraries.androidxFragment
+ implementation "androidx.fragment:fragment:$versions.fragmentVersion"
}
def pathToRootDir = "../../../../"
@@ -39,6 +38,11 @@ android {
jvmTarget = versions.javaVersion
}
+ buildFeatures {
+ aidl = true
+ buildConfig = true
+ }
+
buildTypes {
dev {
initWith debug
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 217e7a2b60..da86e67c7d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -484,6 +484,14 @@ class Godot(private val context: Context) : SensorEventListener {
return containerLayout
}
+ fun onStart(host: GodotHost) {
+ if (host != primaryHost) {
+ return
+ }
+
+ renderView!!.onActivityStarted()
+ }
+
fun onResume(host: GodotHost) {
if (host != primaryHost) {
return
@@ -528,6 +536,14 @@ class Godot(private val context: Context) : SensorEventListener {
}
}
+ fun onStop(host: GodotHost) {
+ if (host != primaryHost) {
+ return
+ }
+
+ renderView!!.onActivityStopped()
+ }
+
fun onDestroy(primaryHost: GodotHost) {
if (this.primaryHost != primaryHost) {
return
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
index a60f6e997e..e01c5481d5 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -30,7 +30,6 @@
package org.godotengine.godot
-import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
@@ -65,10 +64,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
private set
override fun onCreate(savedInstanceState: Bundle?) {
- // We exclude certain permissions from the set we request at startup, as they'll be
- // requested on demand based on use-cases.
- PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
-
super.onCreate(savedInstanceState)
setContentView(R.layout.godot_app_layout)
@@ -156,7 +151,8 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE) {
+ // Logging the result of permission requests
+ if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE || requestCode == PermissionsUtil.REQUEST_SINGLE_PERMISSION_REQ_CODE) {
Log.d(TAG, "Received permissions request result..")
for (i in permissions.indices) {
val permissionGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
index f1c029e7a1..643c9a658e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
@@ -271,6 +271,32 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
}
@Override
+ public void onStop() {
+ super.onStop();
+ if (!godot.isInitialized()) {
+ if (null != mDownloaderClientStub) {
+ mDownloaderClientStub.disconnect(getActivity());
+ }
+ return;
+ }
+
+ godot.onStop(this);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (!godot.isInitialized()) {
+ if (null != mDownloaderClientStub) {
+ mDownloaderClientStub.connect(getActivity());
+ }
+ return;
+ }
+
+ godot.onStart(this);
+ }
+
+ @Override
public void onResume() {
super.onResume();
if (!godot.isInitialized()) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
index 52350c12a6..81043ce782 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -114,12 +114,30 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
@Override
public void onActivityPaused() {
- onPause();
+ queueEvent(() -> {
+ GodotLib.focusout();
+ // Pause the renderer
+ godotRenderer.onActivityPaused();
+ });
+ }
+
+ @Override
+ public void onActivityStopped() {
+ pauseGLThread();
}
@Override
public void onActivityResumed() {
- onResume();
+ queueEvent(() -> {
+ // Resume the renderer
+ godotRenderer.onActivityResumed();
+ GodotLib.focusin();
+ });
+ }
+
+ @Override
+ public void onActivityStarted() {
+ resumeGLThread();
}
@Override
@@ -283,26 +301,4 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
/* Set the renderer responsible for frame rendering */
setRenderer(godotRenderer);
}
-
- @Override
- public void onResume() {
- super.onResume();
-
- queueEvent(() -> {
- // Resume the renderer
- godotRenderer.onActivityResumed();
- GodotLib.focusin();
- });
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- queueEvent(() -> {
- GodotLib.focusout();
- // Pause the renderer
- godotRenderer.onActivityPaused();
- });
- }
}
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 edcd9c4d1f..4b51bd778d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -178,12 +178,10 @@ public class GodotIO {
}
public int[] getDisplaySafeArea() {
- DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
- Display display = activity.getWindowManager().getDefaultDisplay();
- Point size = new Point();
- display.getRealSize(size);
+ Rect rect = new Rect();
+ activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
- int[] result = { 0, 0, size.x, size.y };
+ int[] result = { rect.left, rect.top, rect.right, rect.bottom };
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets();
DisplayCutout cutout = insets.getDisplayCutout();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index ebf3a6b2fb..5b2f9f57c7 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -47,8 +47,13 @@ public interface GodotRenderView {
void queueOnRenderThread(Runnable event);
void onActivityPaused();
+
+ void onActivityStopped();
+
void onActivityResumed();
+ void onActivityStarted();
+
void onBackPressed();
GodotInputHandler getInputHandler();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index 48708152be..a1ee9bd6b4 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -92,12 +92,30 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
@Override
public void onActivityPaused() {
- onPause();
+ queueOnVkThread(() -> {
+ GodotLib.focusout();
+ // Pause the renderer
+ mRenderer.onVkPause();
+ });
+ }
+
+ @Override
+ public void onActivityStopped() {
+ pauseRenderThread();
+ }
+
+ @Override
+ public void onActivityStarted() {
+ resumeRenderThread();
}
@Override
public void onActivityResumed() {
- onResume();
+ queueOnVkThread(() -> {
+ // Resume the renderer
+ mRenderer.onVkResume();
+ GodotLib.focusin();
+ });
}
@Override
@@ -211,26 +229,4 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
}
return super.onResolvePointerIcon(me, pointerIndex);
}
-
- @Override
- public void onResume() {
- super.onResume();
-
- queueOnVkThread(() -> {
- // Resume the renderer
- mRenderer.onVkResume();
- GodotLib.focusin();
- });
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- queueOnVkThread(() -> {
- GodotLib.focusout();
- // Pause the renderer
- mRenderer.onVkPause();
- });
- }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
index 56397bb2c2..ef97aaeab9 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
@@ -122,8 +122,8 @@ import javax.microedition.khronos.opengles.GL10;
* <p>
* <h3>Activity Life-cycle</h3>
* A GLSurfaceView must be notified when to pause and resume rendering. GLSurfaceView clients
- * are required to call {@link #onPause()} when the activity stops and
- * {@link #onResume()} when the activity starts. These calls allow GLSurfaceView to
+ * are required to call {@link #pauseGLThread()} when the activity stops and
+ * {@link #resumeGLThread()} when the activity starts. These calls allow GLSurfaceView to
* pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate
* the OpenGL display.
* <p>
@@ -339,8 +339,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
* setRenderer is called:
* <ul>
* <li>{@link #getRenderMode()}
- * <li>{@link #onPause()}
- * <li>{@link #onResume()}
+ * <li>{@link #pauseGLThread()}
+ * <li>{@link #resumeGLThread()}
* <li>{@link #queueEvent(Runnable)}
* <li>{@link #requestRender()}
* <li>{@link #setRenderMode(int)}
@@ -568,6 +568,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
}
+ // -- GODOT start --
/**
* Pause the rendering thread, optionally tearing down the EGL context
* depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}.
@@ -578,22 +579,23 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
*
* Must not be called before a renderer has been set.
*/
- public void onPause() {
+ protected final void pauseGLThread() {
mGLThread.onPause();
}
/**
* Resumes the rendering thread, re-creating the OpenGL context if necessary. It
- * is the counterpart to {@link #onPause()}.
+ * is the counterpart to {@link #pauseGLThread()}.
*
* This method should typically be called in
* {@link android.app.Activity#onStart Activity.onStart}.
*
* Must not be called before a renderer has been set.
*/
- public void onResume() {
+ protected final void resumeGLThread() {
mGLThread.onResume();
}
+ // -- GODOT end --
/**
* Queue a runnable to be run on the GL rendering thread. This can be used
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
index e26c9d39c2..89fbb9f580 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
@@ -210,21 +210,23 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
}
override fun onScroll(
- originEvent: MotionEvent,
+ originEvent: MotionEvent?,
terminusEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (scaleInProgress) {
if (dragInProgress) {
- // Cancel the drag
- GodotInputHandler.handleMotionEvent(
- originEvent.source,
- MotionEvent.ACTION_CANCEL,
- originEvent.buttonState,
- originEvent.x,
- originEvent.y
- )
+ if (originEvent != null) {
+ // Cancel the drag
+ GodotInputHandler.handleMotionEvent(
+ originEvent.source,
+ MotionEvent.ACTION_CANCEL,
+ originEvent.buttonState,
+ originEvent.x,
+ originEvent.y
+ )
+ }
dragInProgress = false
}
}
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 9a82204467..737b4ac20b 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
@@ -56,9 +56,9 @@ import java.util.Set;
public final class PermissionsUtil {
private static final String TAG = PermissionsUtil.class.getSimpleName();
- static final int REQUEST_RECORD_AUDIO_PERMISSION = 1;
- static final int REQUEST_CAMERA_PERMISSION = 2;
- static final int REQUEST_VIBRATE_PERMISSION = 3;
+ public static final int REQUEST_RECORD_AUDIO_PERMISSION = 1;
+ public static final int REQUEST_CAMERA_PERMISSION = 2;
+ public static final int REQUEST_VIBRATE_PERMISSION = 3;
public static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001;
public static final int REQUEST_SINGLE_PERMISSION_REQ_CODE = 1002;
public static final int REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE = 2002;
@@ -70,7 +70,7 @@ public final class PermissionsUtil {
* Request a dangerous permission. name must be specified in <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/AndroidManifest.xml">this</a>
* @param permissionName the name of the requested permission.
* @param activity the caller activity for this method.
- * @return true/false. "true" if permission was granted otherwise returns "false".
+ * @return true/false. "true" if permission is already granted, "false" if a permission request was dispatched.
*/
public static boolean requestPermission(String permissionName, Activity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@@ -124,7 +124,7 @@ public final class PermissionsUtil {
/**
* Request dangerous permissions which are defined in the Android manifest file from the user.
* @param activity the caller activity for this method.
- * @return true/false. "true" if all permissions were granted otherwise returns "false".
+ * @return true/false. "true" if all permissions were already granted, returns "false" if permissions requests were dispatched.
*/
public static boolean requestManifestPermissions(Activity activity) {
return requestManifestPermissions(activity, null);
@@ -134,7 +134,7 @@ public final class PermissionsUtil {
* Request dangerous permissions which are defined in the Android manifest file from the user.
* @param activity the caller activity for this method.
* @param excludes Set of permissions to exclude from the request
- * @return true/false. "true" if all permissions were granted otherwise returns "false".
+ * @return true/false. "true" if all permissions were already granted, returns "false" if permissions requests were dispatched.
*/
public static boolean requestManifestPermissions(Activity activity, @Nullable Set<String> excludes) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@@ -235,7 +235,7 @@ public final class PermissionsUtil {
/**
* Check if the given permission is in the AndroidManifest.xml file.
* @param context the caller context for this method.
- * @param permission the permession to look for in the manifest file.
+ * @param permission the permission to look for in the manifest file.
* @return "true" if the permission is in the manifest file of the activity, "false" otherwise.
*/
public static boolean hasManifestPermission(Context context, String permission) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
index 3828004198..791b425444 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
@@ -99,7 +99,7 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf
*
* Must not be called before a [VkRenderer] has been set.
*/
- open fun onResume() {
+ protected fun resumeRenderThread() {
vkThread.onResume()
}
@@ -108,7 +108,7 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf
*
* Must not be called before a [VkRenderer] has been set.
*/
- open fun onPause() {
+ protected fun pauseRenderThread() {
vkThread.onPause()
}
diff --git a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml
index dc180375d5..8072ee00db 100644
--- a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml
+++ b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest package="org.godotengine.godot" />
+<manifest />
diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle
index 5e810ae1ba..a728241181 100644
--- a/platform/android/java/nativeSrcsConfigs/build.gradle
+++ b/platform/android/java/nativeSrcsConfigs/build.gradle
@@ -9,6 +9,8 @@ android {
buildToolsVersion versions.buildTools
ndkVersion versions.ndkVersion
+ namespace = "org.godotengine.godot"
+
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle
index 466ffebf22..3137e74244 100644
--- a/platform/android/java/settings.gradle
+++ b/platform/android/java/settings.gradle
@@ -5,12 +5,15 @@ pluginManagement {
plugins {
id 'com.android.application' version versions.androidGradlePlugin
id 'com.android.library' version versions.androidGradlePlugin
+ id 'com.android.asset-pack' version versions.androidGradlePlugin
id 'org.jetbrains.kotlin.android' version versions.kotlinVersion
id 'io.github.gradle-nexus.publish-plugin' version versions.nexusPublishVersion
}
repositories {
- gradlePluginPortal()
google()
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url "https://plugins.gradle.org/m2/" }
}
}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 50075ed3f5..08e792cc04 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -484,7 +484,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *
env->DeleteLocalRef(jobj);
}
- MessageQueue::get_singleton()->push_callp(obj, str_method, argptrs, count);
+ Callable(obj, str_method).call_deferredp(argptrs, count);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 4e401e633e..3c950bb1b1 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -334,7 +334,7 @@ void GodotJavaWrapper::vibrate(int p_duration_ms) {
}
}
-int GodotJavaWrapper::create_new_godot_instance(List<String> args) {
+int GodotJavaWrapper::create_new_godot_instance(const List<String> &args) {
if (_create_new_godot_instance) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, 0);
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 52043c6027..93998021a9 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -104,7 +104,7 @@ public:
void init_input_devices();
void vibrate(int p_duration_ms);
String get_input_fallback_mapping();
- int create_new_godot_instance(List<String> args);
+ int create_new_godot_instance(const List<String> &args);
void begin_benchmark_measure(const String &p_context, const String &p_label);
void end_benchmark_measure(const String &p_context, const String &p_label);
void dump_benchmark(const String &benchmark_file);
diff --git a/platform/ios/SCsub b/platform/ios/SCsub
index 18ba6617af..30d4216464 100644
--- a/platform/ios/SCsub
+++ b/platform/ios/SCsub
@@ -20,6 +20,7 @@ ios_lib = [
"device_metrics.m",
"keyboard_input_view.mm",
"key_mapping_ios.mm",
+ "ios_terminal_logger.mm",
]
env_ios = env.Clone()
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 26d81c8ed6..23f688501b 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -152,7 +152,7 @@ def configure(env: "Environment"):
env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
if env["vulkan"]:
- env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
if env["opengl3"]:
env.Append(CPPDEFINES=["GLES3_ENABLED", "GLES_SILENCE_DEPRECATION"])
diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h
index 3d19222fa8..3fdcc07f0b 100644
--- a/platform/ios/display_server_ios.h
+++ b/platform/ios/display_server_ios.h
@@ -119,7 +119,7 @@ public:
// MARK: Keyboard
- void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed);
+ void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location);
bool is_keyboard_active() const;
// MARK: Motion
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index c31f503605..c660dc5697 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -247,7 +247,7 @@ void DisplayServerIOS::touches_canceled(int p_idx) {
// MARK: Keyboard
-void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed) {
+void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) {
Ref<InputEventKey> ev;
ev.instantiate();
ev->set_echo(false);
@@ -270,6 +270,7 @@ void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_ph
ev->set_key_label(p_unshifted);
ev->set_physical_keycode(p_physical);
ev->set_unicode(fix_unicode(p_char));
+ ev->set_location(p_location);
perform_event(ev);
}
diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
index ecae6d721e..35ef6d6a78 100644
--- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml
+++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
@@ -10,6 +10,13 @@
<link title="iOS plugins documentation index">$DOCS_URL/tutorials/platform/ios/index.html</link>
</tutorials>
<members>
+ <member name="application/additional_plist_content" type="String" setter="" getter="">
+ Additional data added to the root [code]&lt;dict&gt;[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.:
+ [codeblock]
+ &lt;key&gt;key_name&lt;/key&gt;
+ &lt;string&gt;value&lt;/string&gt;
+ [/codeblock]
+ </member>
<member name="application/app_store_team_id" type="String" setter="" getter="">
Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organizational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url].
</member>
@@ -34,8 +41,8 @@
<member name="application/icon_interpolation" type="int" setter="" getter="">
Interpolation method used to resize application icon.
</member>
- <member name="application/launch_screens_interpolation" type="int" setter="" getter="">
- Interpolation method used to resize launch screen images.
+ <member name="application/min_ios_version" type="String" setter="" getter="">
+ Minimum version of iOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]).
</member>
<member name="application/provisioning_profile_uuid_debug" type="String" setter="" getter="">
UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url].
@@ -63,6 +70,14 @@
<member name="capabilities/access_wifi" type="bool" setter="" getter="">
If [code]true[/code], networking features related to Wi-Fi access are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url].
</member>
+ <member name="capabilities/performance_a12" type="bool" setter="" getter="">
+ Requires the graphics performance and features of the A12 Bionic and later chips (devices supporting all Vulkan renderer features).
+ Enabling this option limits supported devices to: iPhone XS, iPhone XR, iPad Mini (5th gen.), iPad Air (3rd gen.), iPad (8th gen) and newer.
+ </member>
+ <member name="capabilities/performance_gaming_tier" type="bool" setter="" getter="">
+ Requires the graphics performance and features of the A17 Pro and later chips.
+ Enabling this option limits supported devices to: iPhone 15 Pro and newer.
+ </member>
<member name="capabilities/push_notifications" type="bool" setter="" getter="">
If [code]true[/code], push notifications are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url].
</member>
@@ -108,39 +123,6 @@
<member name="icons/spotlight_80x80" type="String" setter="" getter="">
Spotlight icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
- <member name="landscape_launch_screens/ipad_1024x768" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="landscape_launch_screens/ipad_2048x1536" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="landscape_launch_screens/iphone_2208x1242" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="landscape_launch_screens/iphone_2436x1125" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="portrait_launch_screens/ipad_768x1024" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="portrait_launch_screens/ipad_1536x2048" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="portrait_launch_screens/iphone_640x960" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="portrait_launch_screens/iphone_640x1136" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="portrait_launch_screens/iphone_750x1334" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="portrait_launch_screens/iphone_1125x2436" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
- <member name="portrait_launch_screens/iphone_1242x2208" type="String" setter="" getter="">
- Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
- </member>
<member name="privacy/camera_usage_description" type="String" setter="" getter="">
A message displayed when requesting access to the device's camera (in English).
</member>
@@ -174,9 +156,6 @@
<member name="storyboard/use_custom_bg_color" type="bool" setter="" getter="">
If [code]true[/code], [member storyboard/custom_bg_color] is used as a launch screen background color, otherwise [code]application/boot_splash/bg_color[/code] project setting is used.
</member>
- <member name="storyboard/use_launch_screen_storyboard" type="bool" setter="" getter="">
- If [code]true[/code], storyboard launch screen is used instead of launch screen images.
- </member>
<member name="user_data/accessible_from_files_app" type="bool" setter="" getter="">
If [code]true[/code], the app "Documents" folder can be accessed via "Files" app. See [url=https://developer.apple.com/documentation/bundleresources/information_property_list/lssupportsopeningdocumentsinplace]LSSupportsOpeningDocumentsInPlace[/url].
</member>
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index b478759e45..d35819c34d 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -34,14 +34,15 @@
#include "run_icon_svg.gen.h"
#include "core/io/json.h"
+#include "core/io/plist.h"
#include "core/string/translation.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
-#include "editor/editor_scale.h"
#include "editor/editor_string_names.h"
#include "editor/export/editor_export.h"
#include "editor/import/resource_importer_texture_settings.h"
#include "editor/plugins/script_editor_plugin.h"
+#include "editor/themes/editor_scale.h"
#include "modules/modules_enabled.gen.h" // For mono and svg.
#ifdef MODULE_SVG_ENABLED
@@ -105,29 +106,6 @@ static const IconInfo icon_infos[] = {
{ PNAME("icons/notification_60x60"), "iphone", "Icon-60.png", "60", "3x", "20x20", false }
};
-struct LoadingScreenInfo {
- const char *preset_key;
- const char *export_name;
- int width = 0;
- int height = 0;
- bool rotate = false;
-};
-
-static const LoadingScreenInfo loading_screen_infos[] = {
- { PNAME("landscape_launch_screens/iphone_2436x1125"), "Default-Landscape-X.png", 2436, 1125, false },
- { PNAME("landscape_launch_screens/iphone_2208x1242"), "Default-Landscape-736h@3x.png", 2208, 1242, false },
- { PNAME("landscape_launch_screens/ipad_1024x768"), "Default-Landscape.png", 1024, 768, false },
- { PNAME("landscape_launch_screens/ipad_2048x1536"), "Default-Landscape@2x.png", 2048, 1536, false },
-
- { PNAME("portrait_launch_screens/iphone_640x960"), "Default-480h@2x.png", 640, 960, false },
- { PNAME("portrait_launch_screens/iphone_640x1136"), "Default-568h@2x.png", 640, 1136, false },
- { PNAME("portrait_launch_screens/iphone_750x1334"), "Default-667h@2x.png", 750, 1334, false },
- { PNAME("portrait_launch_screens/iphone_1125x2436"), "Default-Portrait-X.png", 1125, 2436, false },
- { PNAME("portrait_launch_screens/ipad_768x1024"), "Default-Portrait.png", 768, 1024, false },
- { PNAME("portrait_launch_screens/ipad_1536x2048"), "Default-Portrait@2x.png", 1536, 2048, false },
- { PNAME("portrait_launch_screens/iphone_1242x2208"), "Default-Portrait-736h@3x.png", 1242, 2208, false }
-};
-
String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {
if (p_preset) {
if (p_name == "application/app_store_team_id") {
@@ -147,12 +125,6 @@ String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPres
}
bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {
- if (p_preset) {
- bool sb = p_preset->get("storyboard/use_launch_screen_storyboard");
- if (!sb && p_option != "storyboard/use_launch_screen_storyboard" && p_option.begins_with("storyboard/")) {
- return false;
- }
- }
return true;
}
@@ -181,8 +153,11 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_ios_version"), "12.0"));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), ""));
+
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/launch_screens_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/export_project_only"), false));
@@ -217,6 +192,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_gaming_tier"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_a12"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false));
@@ -235,16 +212,11 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, icon_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
}
}
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false, true));
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));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
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()));
-
- 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,*.jpg,*.jpeg"), ""));
- }
}
void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug) {
@@ -262,8 +234,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
};
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_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "iPhone Developer");
- bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "iPhone Distribution");
+ bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "iPhone Developer" && dbg_sign_id != "iPhone Distribution");
+ bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "iPhone Developer" && rel_sign_id != "iPhone Distribution");
String str;
String strnew;
str.parse_utf8((const char *)pfile.ptr(), pfile.size());
@@ -287,6 +259,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n";
} else if (lines[i].find("$version") != -1) {
strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n";
+ } else if (lines[i].find("$min_version") != -1) {
+ strnew += lines[i].replace("$min_version", p_preset->get("application/min_ios_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("$team_id") != -1) {
@@ -359,7 +333,12 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) {
capabilities_list.push_back("wifi");
}
-
+ if ((bool)p_preset->get("capabilities/performance_gaming_tier") && !capabilities_list.has("iphone-performance-gaming-tier")) {
+ capabilities_list.push_back("iphone-performance-gaming-tier");
+ }
+ if ((bool)p_preset->get("capabilities/performance_a12") && !capabilities_list.has("iphone-ipad-minimum-performance-a12")) {
+ capabilities_list.push_back("iphone-ipad-minimum-performance-a12");
+ }
for (int idx = 0; idx < capabilities_list.size(); idx++) {
capabilities += "<string>" + capabilities_list[idx] + "</string>\n";
}
@@ -413,29 +392,20 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
String description = p_preset->get("privacy/photolibrary_usage_description");
strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n";
} else if (lines[i].find("$plist_launch_screen_name") != -1) {
- bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
- String value = is_on ? "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>" : "";
+ String value = "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>";
strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n";
} else if (lines[i].find("$pbx_launch_screen_file_reference") != -1) {
- bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
- String value = is_on ? "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };" : "";
+ String value = "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };";
strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n";
} else if (lines[i].find("$pbx_launch_screen_copy_files") != -1) {
- bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
- String value = is_on ? "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */," : "";
+ String value = "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */,";
strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n";
} else if (lines[i].find("$pbx_launch_screen_build_phase") != -1) {
- bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
- String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */," : "";
+ String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */,";
strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n";
} else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) {
- bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
- String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };" : "";
+ String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };";
strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n";
- } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) {
- bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard");
- String value = is_on ? "" : "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;";
- strnew += lines[i].replace("$pbx_launch_image_usage_setting", value) + "\n";
} else if (lines[i].find("$launch_screen_image_mode") != -1) {
int image_scale_mode = p_preset->get("storyboard/image_scale_mode");
String value;
@@ -783,94 +753,6 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor
return OK;
}
-Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
- Ref<DirAccess> da = DirAccess::open(p_dest_dir);
- ERR_FAIL_COND_V_MSG(da.is_null(), 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 = GLOBAL_GET("application/boot_splash/bg_color");
- String boot_logo_path = GLOBAL_GET("application/boot_splash/image");
- bool boot_logo_scale = GLOBAL_GET("application/boot_splash/fullsize");
-
- if (loading_screen_file.size() > 0) {
- // 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) {
- ERR_PRINT("Invalid loading screen (" + String(info.preset_key) + "): '" + loading_screen_file + "'.");
- return ERR_UNCONFIGURED;
- }
- if (img->get_width() != info.width || img->get_height() != info.height) {
- 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, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int()));
- } else {
- img->resize(info.width, info.width / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int()));
- }
- }
- Ref<Image> new_img = Image::create_empty(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);
- }
- if (err) {
- 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 {
- // Generate loading screen from the splash screen
- Ref<Image> img = Image::create_empty(info.width, info.height, false, Image::FORMAT_RGBA8);
- img->fill(boot_bg_color);
-
- Ref<Image> img_bs;
-
- if (boot_logo_path.length() > 0) {
- img_bs = Ref<Image>(memnew(Image));
- ImageLoader::load_image(boot_logo_path, img_bs);
- }
- if (!img_bs.is_valid()) {
- img_bs = Ref<Image>(memnew(Image(boot_splash_png)));
- }
- if (img_bs.is_valid()) {
- float aspect_ratio = (float)img_bs->get_width() / (float)img_bs->get_height();
- if (info.rotate) {
- if (boot_logo_scale) {
- if (info.width * aspect_ratio <= info.height) {
- img_bs->resize(info.width * aspect_ratio, info.width, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int()));
- } else {
- img_bs->resize(info.height, info.height / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int()));
- }
- }
- } else {
- if (boot_logo_scale) {
- if (info.height * aspect_ratio <= info.width) {
- img_bs->resize(info.height * aspect_ratio, info.height, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int()));
- } else {
- img_bs->resize(info.width, info.width / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int()));
- }
- }
- }
- _blend_and_rotate(img, img_bs, info.rotate);
- }
- Error err = img->save_png(p_dest_dir + info.export_name);
- if (err) {
- String err_str = String("Failed to export loading screen (") + info.preset_key + ") from splash screen.";
- WARN_PRINT(err_str.utf8().get_data());
- }
- }
- }
-
- return OK;
-}
-
Error EditorExportPlatformIOS::_walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata) {
Vector<String> dirs;
String current_dir = p_da->get_current_dir();
@@ -986,7 +868,183 @@ struct ExportLibsData {
String dest_dir;
};
-void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) {
+void EditorExportPlatformIOS::_check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const {
+ Ref<PList> plist;
+ plist.instantiate();
+ plist->load_file(p_path.path_join("Info.plist"));
+ Ref<PListNode> root_node = plist->get_root();
+ if (root_node.is_null()) {
+ return;
+ }
+ Dictionary root = root_node->get_value();
+ if (!root.has("AvailableLibraries")) {
+ return;
+ }
+ Ref<PListNode> libs_node = root["AvailableLibraries"];
+ if (libs_node.is_null()) {
+ return;
+ }
+ Array libs = libs_node->get_value();
+ r_total_libs = libs.size();
+ for (int j = 0; j < libs.size(); j++) {
+ Ref<PListNode> lib_node = libs[j];
+ if (lib_node.is_null()) {
+ return;
+ }
+ Dictionary lib = lib_node->get_value();
+ if (lib.has("BinaryPath")) {
+ Ref<PListNode> path_node = lib["BinaryPath"];
+ if (path_node.is_valid()) {
+ String path = path_node->get_value();
+ if (path.ends_with(".a")) {
+ r_static_libs++;
+ }
+ if (path.ends_with(".dylib")) {
+ r_dylibs++;
+ }
+ if (path.ends_with(".framework")) {
+ r_frameworks++;
+ }
+ }
+ }
+ }
+}
+
+Error EditorExportPlatformIOS::_convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const {
+ print_line("Converting to .framework", p_source, " -> ", p_destination);
+
+ Ref<DirAccess> da = DirAccess::create_for_path(p_source);
+ if (da.is_null()) {
+ return ERR_CANT_OPEN;
+ }
+
+ Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (filesystem_da.is_null()) {
+ return ERR_CANT_OPEN;
+ }
+
+ if (!filesystem_da->dir_exists(p_destination)) {
+ Error make_dir_err = filesystem_da->make_dir_recursive(p_destination);
+ if (make_dir_err) {
+ return make_dir_err;
+ }
+ }
+
+ String asset = p_source.ends_with("/") ? p_source.left(p_source.length() - 1) : p_source;
+ if (asset.ends_with(".xcframework")) {
+ Ref<PList> plist;
+ plist.instantiate();
+ plist->load_file(p_source.path_join("Info.plist"));
+ Ref<PListNode> root_node = plist->get_root();
+ if (root_node.is_null()) {
+ return ERR_CANT_OPEN;
+ }
+ Dictionary root = root_node->get_value();
+ if (!root.has("AvailableLibraries")) {
+ return ERR_CANT_OPEN;
+ }
+ Ref<PListNode> libs_node = root["AvailableLibraries"];
+ if (libs_node.is_null()) {
+ return ERR_CANT_OPEN;
+ }
+ Array libs = libs_node->get_value();
+ for (int j = 0; j < libs.size(); j++) {
+ Ref<PListNode> lib_node = libs[j];
+ if (lib_node.is_null()) {
+ return ERR_CANT_OPEN;
+ }
+ Dictionary lib = lib_node->get_value();
+ if (lib.has("BinaryPath") && lib.has("LibraryPath") && lib.has("LibraryIdentifier")) {
+ Ref<PListNode> bpath_node = lib["BinaryPath"];
+ Ref<PListNode> lpath_node = lib["LibraryPath"];
+ Ref<PListNode> lid_node = lib["LibraryIdentifier"];
+ if (bpath_node.is_valid() && lpath_node.is_valid() && lid_node.is_valid()) {
+ String binary_path = bpath_node->get_value();
+ String library_identifier = lid_node->get_value();
+
+ String file_name = binary_path.get_basename().get_file();
+ String framework_name = file_name + ".framework";
+
+ bpath_node->data_string = framework_name.utf8();
+ lpath_node->data_string = framework_name.utf8();
+ if (!filesystem_da->dir_exists(p_destination.path_join(library_identifier))) {
+ filesystem_da->make_dir_recursive(p_destination.path_join(library_identifier));
+ }
+ _convert_to_framework(p_source.path_join(library_identifier).path_join(binary_path), p_destination.path_join(library_identifier).path_join(framework_name), p_id);
+ if (lib.has("DebugSymbolsPath")) {
+ Ref<PListNode> dpath_node = lib["DebugSymbolsPath"];
+ if (dpath_node.is_valid()) {
+ String dpath = dpath_node->get_value();
+ if (da->dir_exists(p_source.path_join(library_identifier).path_join(dpath))) {
+ da->copy_dir(p_source.path_join(library_identifier).path_join(dpath), p_destination.path_join(library_identifier).path_join("dSYMs"));
+ }
+ }
+ }
+ }
+ }
+ }
+ String info_plist = plist->save_text();
+
+ Ref<FileAccess> f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE);
+ if (f.is_valid()) {
+ f->store_string(info_plist);
+ }
+ } else {
+ String file_name = p_destination.get_basename().get_file();
+ String framework_name = file_name + ".framework";
+
+ da->copy(p_source, p_destination.path_join(file_name));
+
+ // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
+ {
+ List<String> install_name_args;
+ install_name_args.push_back("-id");
+ install_name_args.push_back(String("@rpath").path_join(framework_name).path_join(file_name));
+ install_name_args.push_back(p_destination.path_join(file_name));
+
+ OS::get_singleton()->execute("install_name_tool", install_name_args);
+ }
+
+ // Creating Info.plist
+ {
+ String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ " <dict>\n"
+ " <key>CFBundleShortVersionString</key>\n"
+ " <string>1.0</string>\n"
+ " <key>CFBundleIdentifier</key>\n"
+ " <string>$id.framework.$name</string>\n"
+ " <key>CFBundleName</key>\n"
+ " <string>$name</string>\n"
+ " <key>CFBundleExecutable</key>\n"
+ " <string>$name</string>\n"
+ " <key>DTPlatformName</key>\n"
+ " <string>iphoneos</string>\n"
+ " <key>CFBundleInfoDictionaryVersion</key>\n"
+ " <string>6.0</string>\n"
+ " <key>CFBundleVersion</key>\n"
+ " <string>1</string>\n"
+ " <key>CFBundlePackageType</key>\n"
+ " <string>FMWK</string>\n"
+ " <key>MinimumOSVersion</key>\n"
+ " <string>12.0</string>\n"
+ " </dict>\n"
+ "</plist>";
+
+ String info_plist = info_plist_format.replace("$id", p_id).replace("$name", file_name);
+
+ Ref<FileAccess> f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE);
+ if (f.is_valid()) {
+ f->store_string(info_plist);
+ }
+ }
+ }
+
+ return OK;
+}
+
+void EditorExportPlatformIOS::_add_assets_to_project(const String &p_out_dir, const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) {
// that is just a random number, we just need Godot IDs not to clash with
// existing IDs in the project.
PbxId current_id = { 0x58938401, 0, 0 };
@@ -1011,7 +1069,12 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
String type;
if (asset.exported_path.ends_with(".framework")) {
- if (asset.should_embed) {
+ int total_libs = 0;
+ int static_libs = 0;
+ int dylibs = 0;
+ int frameworks = 0;
+ _check_xcframework_content(p_out_dir.path_join(asset.exported_path), total_libs, static_libs, dylibs, frameworks);
+ if (asset.should_embed && (static_libs != total_libs)) {
additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
framework_id = (++current_id).str();
pbx_embeded_frameworks += framework_id + ",\n";
@@ -1074,12 +1137,12 @@ 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) {
+Error EditorExportPlatformIOS::_copy_asset(const Ref<EditorExportPreset> &p_preset, 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) {
String binary_name = p_out_dir.get_file().get_basename();
Ref<DirAccess> da = DirAccess::create_for_path(p_asset);
if (da.is_null()) {
- ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + ".");
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open directory: " + p_asset + ".");
}
bool file_exists = da->file_exists(p_asset);
bool dir_exists = da->dir_exists(p_asset);
@@ -1093,7 +1156,8 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String
String destination;
String asset_path;
- bool create_framework = false;
+ Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
if (p_is_framework && asset.ends_with(".dylib")) {
// For iOS we need to turn .dylib into .framework
@@ -1113,10 +1177,23 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String
asset_path = asset_path.path_join(framework_name);
destination_dir = p_out_dir.path_join(asset_path);
destination = destination_dir.path_join(file_name);
- create_framework = true;
- } else if (p_is_framework && (asset.ends_with(".framework") || asset.ends_with(".xcframework"))) {
- asset_path = String("dylibs").path_join(base_dir);
+ // Convert to framework and copy.
+ Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier"));
+ if (err) {
+ return err;
+ }
+ } else if (p_is_framework && asset.ends_with(".xcframework")) {
+ // For iOS we need to turn .dylib inside .xcframework
+ // into .framework to be able to send application to AppStore
+
+ int total_libs = 0;
+ int static_libs = 0;
+ int dylibs = 0;
+ int frameworks = 0;
+ _check_xcframework_content(p_asset, total_libs, static_libs, dylibs, frameworks);
+
+ asset_path = String("dylibs").path_join(base_dir);
String file_name;
if (!p_custom_file_name) {
@@ -1128,8 +1205,29 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String
asset_path = asset_path.path_join(file_name);
destination_dir = p_out_dir.path_join(asset_path);
destination = destination_dir;
- } else {
- asset_path = base_dir;
+
+ if (dylibs > 0) {
+ // Convert to framework and copy.
+ Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier"));
+ if (err) {
+ return err;
+ }
+ } else {
+ // Copy as is.
+ if (!filesystem_da->dir_exists(destination_dir)) {
+ Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+ if (make_dir_err) {
+ return make_dir_err;
+ }
+ }
+ Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+ if (err) {
+ return err;
+ }
+ }
+ } else if (p_is_framework && asset.ends_with(".framework")) {
+ // Framework.
+ asset_path = String("dylibs").path_join(base_dir);
String file_name;
@@ -1139,99 +1237,67 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String
file_name = *p_custom_file_name;
}
- destination_dir = p_out_dir.path_join(asset_path);
asset_path = asset_path.path_join(file_name);
- destination = p_out_dir.path_join(asset_path);
- }
-
- Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
+ destination_dir = p_out_dir.path_join(asset_path);
+ destination = destination_dir;
- if (!filesystem_da->dir_exists(destination_dir)) {
- Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
- if (make_dir_err) {
- return make_dir_err;
+ // Copy as is.
+ if (!filesystem_da->dir_exists(destination_dir)) {
+ Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+ if (make_dir_err) {
+ return make_dir_err;
+ }
}
- }
-
- Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
- if (err) {
- return err;
- }
- if (asset_path.ends_with("/")) {
- asset_path = asset_path.left(asset_path.length() - 1);
- }
- IOSExportAsset exported_asset = { binary_name.path_join(asset_path), p_is_framework, p_should_embed };
- r_exported_assets.push_back(exported_asset);
+ Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+ if (err) {
+ return err;
+ }
+ } else {
+ // Unknown resource.
+ asset_path = base_dir;
- if (create_framework) {
String file_name;
if (!p_custom_file_name) {
- file_name = p_asset.get_basename().get_file();
+ file_name = p_asset.get_file();
} else {
file_name = *p_custom_file_name;
}
- String framework_name = file_name + ".framework";
-
- // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
- {
- List<String> install_name_args;
- install_name_args.push_back("-id");
- install_name_args.push_back(String("@rpath").path_join(framework_name).path_join(file_name));
- install_name_args.push_back(destination);
-
- OS::get_singleton()->execute("install_name_tool", install_name_args);
- }
-
- // Creating Info.plist
- {
- String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
- "<plist version=\"1.0\">\n"
- "<dict>\n"
- "<key>CFBundleShortVersionString</key>\n"
- "<string>1.0</string>\n"
- "<key>CFBundleIdentifier</key>\n"
- "<string>com.gdextension.framework.$name</string>\n"
- "<key>CFBundleName</key>\n"
- "<string>$name</string>\n"
- "<key>CFBundleExecutable</key>\n"
- "<string>$name</string>\n"
- "<key>DTPlatformName</key>\n"
- "<string>iphoneos</string>\n"
- "<key>CFBundleInfoDictionaryVersion</key>\n"
- "<string>6.0</string>\n"
- "<key>CFBundleVersion</key>\n"
- "<string>1</string>\n"
- "<key>CFBundlePackageType</key>\n"
- "<string>FMWK</string>\n"
- "<key>MinimumOSVersion</key>\n"
- "<string>10.0</string>\n"
- "</dict>\n"
- "</plist>";
-
- String info_plist = info_plist_format.replace("$name", file_name);
+ destination_dir = p_out_dir.path_join(asset_path);
+ asset_path = asset_path.path_join(file_name);
+ destination = p_out_dir.path_join(asset_path);
- Ref<FileAccess> f = FileAccess::open(destination_dir.path_join("Info.plist"), FileAccess::WRITE);
- if (f.is_valid()) {
- f->store_string(info_plist);
+ // Copy as is.
+ if (!filesystem_da->dir_exists(destination_dir)) {
+ Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+ if (make_dir_err) {
+ return make_dir_err;
}
}
+ Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+ if (err) {
+ return err;
+ }
}
+ if (asset_path.ends_with("/")) {
+ asset_path = asset_path.left(asset_path.length() - 1);
+ }
+ IOSExportAsset exported_asset = { binary_name.path_join(asset_path), p_is_framework, p_should_embed };
+ r_exported_assets.push_back(exported_asset);
+
return OK;
}
-Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
+Error EditorExportPlatformIOS::_export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) {
- String asset = p_assets[f_idx];
+ const String &asset = p_assets[f_idx];
if (asset.begins_with("res://")) {
- Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
+ Error err = _copy_asset(p_preset, p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
ERR_FAIL_COND_V(err != OK, err);
} else if (ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) {
- Error err = _copy_asset(p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets);
+ Error err = _copy_asset(p_preset, p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets);
ERR_FAIL_COND_V(err != OK, err);
} else {
// either SDK-builtin or already a part of the export template
@@ -1243,26 +1309,26 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
return OK;
}
-Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) {
+Error EditorExportPlatformIOS::_export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) {
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
for (int i = 0; i < export_plugins.size(); i++) {
Vector<String> linked_frameworks = export_plugins[i]->get_ios_frameworks();
- Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets);
+ Error err = _export_additional_assets(p_preset, p_out_dir, linked_frameworks, true, false, r_exported_assets);
ERR_FAIL_COND_V(err, err);
Vector<String> embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks();
- err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets);
+ err = _export_additional_assets(p_preset, p_out_dir, embedded_frameworks, true, true, r_exported_assets);
ERR_FAIL_COND_V(err, err);
Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();
for (int j = 0; j < project_static_libs.size(); j++) {
project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project
}
- err = _export_additional_assets(p_out_dir, project_static_libs, true, false, r_exported_assets);
+ err = _export_additional_assets(p_preset, p_out_dir, project_static_libs, true, false, r_exported_assets);
ERR_FAIL_COND_V(err, err);
Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files();
- err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets);
+ err = _export_additional_assets(p_preset, p_out_dir, ios_bundle_files, false, false, r_exported_assets);
ERR_FAIL_COND_V(err, err);
}
@@ -1270,7 +1336,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
for (int i = 0; i < p_libraries.size(); ++i) {
library_paths.push_back(p_libraries[i].path);
}
- Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets);
+ Error err = _export_additional_assets(p_preset, p_out_dir, library_paths, true, true, r_exported_assets);
ERR_FAIL_COND_V(err, err);
return OK;
@@ -1315,7 +1381,7 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset>
String plugin_binary_result_file = plugin.binary.get_file();
// We shouldn't embed .xcframework that contains static libraries.
// Static libraries are not embedded anyway.
- err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets);
+ err = _copy_asset(p_preset, dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets);
ERR_FAIL_COND_V(err != OK, err);
// Adding dependencies.
@@ -1440,15 +1506,15 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset>
// Export files
{
// Export linked plugin dependency
- err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets);
+ err = _export_additional_assets(p_preset, dest_dir, plugin_linked_dependencies, true, false, r_exported_assets);
ERR_FAIL_COND_V(err != OK, err);
// Export embedded plugin dependency
- err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets);
+ err = _export_additional_assets(p_preset, dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets);
ERR_FAIL_COND_V(err != OK, err);
// Export plugin files
- err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets);
+ err = _export_additional_assets(p_preset, dest_dir, plugin_files, false, false, r_exported_assets);
ERR_FAIL_COND_V(err != OK, err);
}
@@ -1619,6 +1685,8 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
false
};
+ config_data.plist_content += p_preset->get("application/additional_plist_content").operator String() + "\n";
+
Vector<IOSExportAsset> assets;
Ref<DirAccess> tmp_app_path = DirAccess::create_for_path(dest_dir);
@@ -1824,9 +1892,6 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
}
{
- bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard");
-
- String launch_image_path = binary_dir + "/Images.xcassets/LaunchImage.launchimage/";
String splash_image_path = binary_dir + "/Images.xcassets/SplashImage.imageset/";
Ref<DirAccess> launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -1835,30 +1900,11 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
return ERR_CANT_CREATE;
}
- if (use_storyboard) {
- print_line("Using Launch Storyboard");
+ print_line("Exporting launch screen storyboard");
- 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);
+ err = _export_loading_screen_file(p_preset, splash_image_path);
+ if (err != OK) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create a file at path \"%s\" with code %d."), splash_image_path, err));
- } else {
- print_line("Using Launch Images");
-
- const String launch_screen_path = binary_dir + "/Launch Screen.storyboard";
-
- 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);
- }
-
- err = _export_loading_screen_images(p_preset, launch_image_path);
- add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create a file at path \"%s\" with code %d."), launch_image_path, err));
}
}
@@ -1867,8 +1913,8 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
}
print_line("Exporting additional assets");
- _export_additional_assets(binary_dir, libraries, assets);
- _add_assets_to_project(p_preset, project_file_data, assets);
+ _export_additional_assets(p_preset, binary_dir, libraries, assets);
+ _add_assets_to_project(dest_dir, p_preset, project_file_data, assets);
String project_file_name = binary_dir + ".xcodeproj/project.pbxproj";
{
Ref<FileAccess> f = FileAccess::open(project_file_name, FileAccess::WRITE);
@@ -2010,6 +2056,26 @@ bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExp
valid = dvalid || rvalid;
r_missing_templates = !valid;
+ const String &additional_plist_content = p_preset->get("application/additional_plist_content");
+ if (!additional_plist_content.is_empty()) {
+ const String &plist = vformat("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
+ "<plist version=\"1.0\">"
+ "<dict>\n"
+ "%s\n"
+ "</dict>\n"
+ "</plist>\n",
+ additional_plist_content);
+
+ String plist_err;
+ Ref<PList> plist_parser;
+ plist_parser.instantiate();
+ if (!plist_parser->load_string(plist, plist_err)) {
+ err += TTR("Invalid additional PList content: ") + plist_err + "\n";
+ valid = false;
+ }
+ }
+
if (!err.is_empty()) {
r_error = err;
}
diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h
index fd2243bcda..edbe566dab 100644
--- a/platform/ios/export/export_plugin.h
+++ b/platform/ios/export/export_plugin.h
@@ -127,17 +127,19 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
String _get_linker_flags();
String _get_cpp_code();
void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug);
- Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir);
Vector<ExportArchitecture> _get_supported_architectures() const;
Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset) const;
- void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
- Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
- Error _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);
- Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
+ void _check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const;
+ Error _convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const;
+
+ void _add_assets_to_project(const String &p_out_dir, const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
+ Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
+ Error _copy_asset(const Ref<EditorExportPreset> &p_preset, 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);
+ Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick);
diff --git a/platform/ios/ios_terminal_logger.h b/platform/ios/ios_terminal_logger.h
new file mode 100644
index 0000000000..7f0bc37a07
--- /dev/null
+++ b/platform/ios/ios_terminal_logger.h
@@ -0,0 +1,45 @@
+/**************************************************************************/
+/* ios_terminal_logger.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 IOS_TERMINAL_LOGGER_H
+#define IOS_TERMINAL_LOGGER_H
+
+#ifdef IOS_ENABLED
+
+#include "core/io/logger.h"
+
+class IOSTerminalLogger : 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 // IOS_ENABLED
+
+#endif // IOS_TERMINAL_LOGGER_H
diff --git a/platform/ios/ios_terminal_logger.mm b/platform/ios/ios_terminal_logger.mm
new file mode 100644
index 0000000000..b4c9821cdc
--- /dev/null
+++ b/platform/ios/ios_terminal_logger.mm
@@ -0,0 +1,74 @@
+/**************************************************************************/
+/* ios_terminal_logger.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 "ios_terminal_logger.h"
+
+#ifdef IOS_ENABLED
+
+#include <os/log.h>
+
+void IOSTerminalLogger::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);
+ 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);
+ 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);
+ 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);
+ break;
+ }
+}
+
+#endif // IOS_ENABLED
diff --git a/platform/ios/key_mapping_ios.h b/platform/ios/key_mapping_ios.h
index 6cc61175bb..8874da3024 100644
--- a/platform/ios/key_mapping_ios.h
+++ b/platform/ios/key_mapping_ios.h
@@ -41,6 +41,7 @@ class KeyMappingIOS {
public:
static void initialize();
static Key remap_key(CFIndex p_keycode);
+ static KeyLocation key_location(CFIndex p_keycode);
};
#endif // KEY_MAPPING_IOS_H
diff --git a/platform/ios/key_mapping_ios.mm b/platform/ios/key_mapping_ios.mm
index d2c84884d1..61f28aa84b 100644
--- a/platform/ios/key_mapping_ios.mm
+++ b/platform/ios/key_mapping_ios.mm
@@ -38,6 +38,7 @@ struct HashMapHasherKeys {
};
HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map;
+HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingIOS::initialize() {
if (@available(iOS 13.4, *)) {
@@ -172,6 +173,15 @@ void KeyMappingIOS::initialize() {
keyusage_map[0x029D] = Key::GLOBE; // "Globe" key on smart connector / Mac keyboard.
keyusage_map[UIKeyboardHIDUsageKeyboardLANG1] = Key::JIS_EISU;
keyusage_map[UIKeyboardHIDUsageKeyboardLANG2] = Key::JIS_KANA;
+
+ location_map[UIKeyboardHIDUsageKeyboardLeftAlt] = KeyLocation::LEFT;
+ location_map[UIKeyboardHIDUsageKeyboardRightAlt] = KeyLocation::RIGHT;
+ location_map[UIKeyboardHIDUsageKeyboardLeftControl] = KeyLocation::LEFT;
+ location_map[UIKeyboardHIDUsageKeyboardRightControl] = KeyLocation::RIGHT;
+ location_map[UIKeyboardHIDUsageKeyboardLeftShift] = KeyLocation::LEFT;
+ location_map[UIKeyboardHIDUsageKeyboardRightShift] = KeyLocation::RIGHT;
+ location_map[UIKeyboardHIDUsageKeyboardLeftGUI] = KeyLocation::LEFT;
+ location_map[UIKeyboardHIDUsageKeyboardRightGUI] = KeyLocation::RIGHT;
}
}
@@ -184,3 +194,13 @@ Key KeyMappingIOS::remap_key(CFIndex p_keycode) {
}
return Key::NONE;
}
+
+KeyLocation KeyMappingIOS::key_location(CFIndex p_keycode) {
+ if (@available(iOS 13.4, *)) {
+ const KeyLocation *location = location_map.getptr(p_keycode);
+ if (location) {
+ return *location;
+ }
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm
index bc6eb63ed5..8b614662b7 100644
--- a/platform/ios/keyboard_input_view.mm
+++ b/platform/ios/keyboard_input_view.mm
@@ -116,8 +116,8 @@
- (void)deleteText:(NSInteger)charactersToDelete {
for (int i = 0; i < charactersToDelete; i++) {
- DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true);
- DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false);
+ DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
+ DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
@@ -137,8 +137,8 @@
key = Key::SPACE;
}
- DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true);
- DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false);
+ DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
+ DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm
index a646705305..6ac21fa9c8 100644
--- a/platform/ios/os_ios.mm
+++ b/platform/ios/os_ios.mm
@@ -35,6 +35,7 @@
#import "app_delegate.h"
#import "display_server_ios.h"
#import "godot_view.h"
+#import "ios_terminal_logger.h"
#import "view_controller.h"
#include "core/config/project_settings.h"
@@ -105,12 +106,7 @@ OS_IOS::OS_IOS() {
main_loop = nullptr;
Vector<Logger *> loggers;
- loggers.push_back(memnew(SyslogLogger));
-#ifdef DEBUG_ENABLED
- // it seems iOS app's stdout/stderr is only obtainable if you launch it from
- // Xcode
- loggers.push_back(memnew(StdLogger));
-#endif
+ loggers.push_back(memnew(IOSTerminalLogger));
_set_logger(memnew(CompositeLogger(loggers)));
AudioDriverManager::add_driver(&audio_driver);
@@ -257,6 +253,8 @@ Error OS_IOS::open_dynamic_library(const String p_path, void *&p_library_handle,
path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework"));
}
+ ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
+
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
diff --git a/platform/ios/view_controller.mm b/platform/ios/view_controller.mm
index 1f55670b68..6f6c04c2c8 100644
--- a/platform/ios/view_controller.mm
+++ b/platform/ios/view_controller.mm
@@ -78,13 +78,15 @@
us = u32lbl[0];
}
+ KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode);
+
if (!u32text.is_empty() && !u32text.begins_with("UIKey")) {
for (int i = 0; i < u32text.length(); i++) {
const char32_t c = u32text[i];
- DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true);
+ DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
} else {
- DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true);
+ DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
}
}
@@ -110,7 +112,9 @@
us = u32lbl[0];
}
- DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false);
+ KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode);
+
+ DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location);
}
}
}
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index 59cc6e7962..eaaaad82b9 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -434,7 +434,7 @@ def configure(env: "Environment"):
env.Append(CPPDEFINES=["X11_ENABLED"])
if env["vulkan"]:
- env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
if not env["use_volk"]:
env.ParseConfig("pkg-config vulkan --cflags --libs")
if not env["builtin_glslang"]:
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
index 64efcffae3..773b124c6a 100644
--- a/platform/linuxbsd/export/export_plugin.cpp
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -36,9 +36,9 @@
#include "core/config/project_settings.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
-#include "editor/editor_scale.h"
#include "editor/editor_string_names.h"
#include "editor/export/editor_export.h"
+#include "editor/themes/editor_scale.h"
#include "modules/modules_enabled.gen.h" // For svg.
#ifdef MODULE_SVG_ENABLED
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index 6a5b5b8064..a3633e72b7 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -142,6 +142,54 @@ void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const
}
}
+void FreeDesktopPortalDesktop::append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options) {
+ DBusMessageIter dict_iter;
+ DBusMessageIter var_iter;
+ DBusMessageIter arr_iter;
+ const char *choices_key = "choices";
+
+ dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
+ dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &choices_key);
+ dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(ssa(ss)s)", &var_iter);
+ dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(ss(ss)s)", &arr_iter);
+
+ for (int i = 0; i < p_options.size(); i++) {
+ const Dictionary &item = p_options[i];
+ if (!item.has("name") || !item.has("values") || !item.has("default")) {
+ continue;
+ }
+ const String &name = item["name"];
+ const Vector<String> &options = item["values"];
+ int default_idx = item["default"];
+
+ DBusMessageIter struct_iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter array_struct_iter;
+ dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
+ append_dbus_string(&struct_iter, name); // ID.
+ append_dbus_string(&struct_iter, name); // User visible name.
+
+ dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(ss)", &array_iter);
+ for (int j = 0; j < options.size(); j++) {
+ dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
+ append_dbus_string(&array_struct_iter, itos(j));
+ append_dbus_string(&array_struct_iter, options[j]);
+ dbus_message_iter_close_container(&array_iter, &array_struct_iter);
+ }
+ dbus_message_iter_close_container(&struct_iter, &array_iter);
+ if (options.is_empty()) {
+ append_dbus_string(&struct_iter, (default_idx) ? "true" : "false"); // Default selection.
+ } else {
+ append_dbus_string(&struct_iter, itos(default_idx)); // Default selection.
+ }
+
+ dbus_message_iter_close_container(&arr_iter, &struct_iter);
+ }
+ dbus_message_iter_close_container(&var_iter, &arr_iter);
+ dbus_message_iter_close_container(&dict_iter, &var_iter);
+ dbus_message_iter_close_container(p_iter, &dict_iter);
+}
+
void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts) {
DBusMessageIter dict_iter;
DBusMessageIter var_iter;
@@ -162,7 +210,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter,
append_dbus_string(&struct_iter, p_filter_names[i]);
dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
- String flt = p_filter_exts[i];
+ const String &flt = p_filter_exts[i];
int filter_slice_count = flt.get_slice_count(",");
for (int j = 0; j < filter_slice_count; j++) {
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
@@ -223,7 +271,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co
dbus_message_iter_close_container(p_iter, &dict_iter);
}
-bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index) {
+bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options) {
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);
dbus_uint32_t resp_code;
@@ -262,6 +310,34 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
}
}
}
+ } else if (strcmp(key, "choices") == 0) { // a(ss) {
+ if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
+ DBusMessageIter struct_iter;
+ dbus_message_iter_recurse(&var_iter, &struct_iter);
+ while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRUCT) {
+ DBusMessageIter opt_iter;
+ dbus_message_iter_recurse(&struct_iter, &opt_iter);
+ const char *opt_key = nullptr;
+ dbus_message_iter_get_basic(&opt_iter, &opt_key);
+ String opt_skey = String::utf8(opt_key);
+
+ dbus_message_iter_next(&opt_iter);
+ const char *opt_val = nullptr;
+ dbus_message_iter_get_basic(&opt_iter, &opt_val);
+ String opt_sval = String::utf8(opt_val);
+ if (opt_sval == "true") {
+ r_options[opt_skey] = true;
+ } else if (opt_sval == "false") {
+ r_options[opt_skey] = false;
+ } else {
+ r_options[opt_skey] = opt_sval.to_int();
+ }
+
+ if (!dbus_message_iter_next(&struct_iter)) {
+ break;
+ }
+ }
+ }
} else if (strcmp(key, "uris") == 0) { // as
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
DBusMessageIter uri_iter;
@@ -285,7 +361,7 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
return true;
}
-Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
if (unsupported) {
return FAILED;
}
@@ -322,6 +398,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
fd.callback = p_callback;
fd.prev_focus = p_window_id;
fd.filter_names = filter_names;
+ fd.opt_in_cb = p_options_in_cb;
CryptoCore::RandomGenerator rng;
ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
@@ -373,6 +450,8 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);
append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);
append_dbus_dict_filters(&arr_iter, filter_names, filter_exts);
+
+ append_dbus_dict_options(&arr_iter, p_options);
append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);
if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
append_dbus_dict_string(&arr_iter, "current_name", p_filename);
@@ -427,14 +506,25 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
return OK;
}
-void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index) {
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &p_status, &p_list, &p_index };
+void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index, const Variant &p_options, bool p_opt_in_cb) {
+ if (p_opt_in_cb) {
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &p_status, &p_list, &p_index, &p_options };
- p_callable.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 3, ce)));
+ p_callable.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 4, ce)));
+ }
+ } else {
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &p_status, &p_list, &p_index };
+
+ p_callable.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 3, ce)));
+ }
}
}
@@ -458,11 +548,12 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) {
if (dbus_message_iter_init(msg, &iter)) {
bool cancel = false;
Vector<String> uris;
+ Dictionary options;
int index = 0;
- file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index);
+ file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index, options);
if (fd.callback.is_valid()) {
- callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index);
+ callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index, options, fd.opt_in_cb);
}
if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h
index 71e9812ea9..c9da387241 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.h
+++ b/platform/linuxbsd/freedesktop_portal_desktop.h
@@ -49,12 +49,13 @@ private:
bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value);
static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string);
+ static void append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options);
static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts);
static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false);
static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value);
- static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index);
+ static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options);
- void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index);
+ void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index, const Variant &p_options, bool p_opt_in_cb);
struct FileDialogData {
Vector<String> filter_names;
@@ -62,6 +63,7 @@ private:
DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID;
Callable callback;
String path;
+ bool opt_in_cb = false;
};
Mutex file_dialog_mutex;
@@ -77,7 +79,7 @@ public:
bool is_supported() { return !unsupported; }
- Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
+ Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb);
// Retrieve the system's preferred color scheme.
// 0: No preference or unknown.
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index d22d398a67..aed8574902 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -316,7 +316,7 @@ Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const {
continue;
}
String device_class = columns[1].trim_suffix(":");
- String vendor_device_id_mapping = columns[2];
+ const String &vendor_device_id_mapping = columns[2];
#ifdef MODULE_REGEX_ENABLED
if (regex_id_format.search(vendor_device_id_mapping).is_null()) {
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 1434342bbc..20e2e897f2 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -372,7 +372,18 @@ Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_
}
String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
- return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
+ return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+}
+
+Error DisplayServerX11::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ WindowID window_id = last_focused_window;
+
+ if (!windows.has(window_id)) {
+ window_id = MAIN_WINDOW_ID;
+ }
+
+ String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
+ return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true);
}
#endif
@@ -2010,8 +2021,8 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent
// 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.no_focus && !wd_window.is_popup && wd_window.focused) {
- if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup) {
- XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime);
+ if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup && _window_focus_check()) {
+ _set_input_focus(wd_parent.x11_window, RevertToPointerRoot);
}
}
} else {
@@ -2891,10 +2902,15 @@ void DisplayServerX11::window_move_to_foreground(WindowID p_window) {
XFlush(x11_display);
}
+DisplayServerX11::WindowID DisplayServerX11::get_focused_window() const {
+ return last_focused_window;
+}
+
bool DisplayServerX11::window_is_focused(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), false);
+
const WindowData &wd = windows[p_window];
return wd.focused;
@@ -2945,8 +2961,8 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win
XWindowAttributes xwa;
XSync(x11_display, False);
XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);
- if (xwa.map_state == IsViewable) {
- XSetInputFocus(x11_display, wd.x11_xim_window, RevertToParent, CurrentTime);
+ if (xwa.map_state == IsViewable && _window_focus_check()) {
+ _set_input_focus(wd.x11_xim_window, RevertToParent);
}
XSetICFocus(wd.xic);
} else {
@@ -3496,6 +3512,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
bool keypress = xkeyevent->type == KeyPress;
Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+ KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);
if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
keycode -= 'a' - 'A';
@@ -3533,6 +3550,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
k->set_unicode(fix_unicode(tmp[i]));
}
+ k->set_location(key_location);
+
k->set_echo(false);
if (k->get_keycode() == Key::BACKTAB) {
@@ -3558,6 +3577,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+ KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);
+
/* Phase 3, obtain a unicode character from the keysym */
// KeyMappingX11 also translates keysym to unicode.
@@ -3657,6 +3678,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
if (keypress) {
k->set_unicode(fix_unicode(unicode));
}
+
+ k->set_location(key_location);
+
k->set_echo(p_echo);
if (k->get_keycode() == Key::BACKTAB) {
@@ -3875,7 +3899,7 @@ void DisplayServerX11::_xim_preedit_draw_callback(::XIM xim, ::XPointer client_d
ds->im_selection = Point2i();
}
- OS_Unix::get_singleton()->get_main_loop()->call_deferred(SNAME("notification"), MainLoop::NOTIFICATION_OS_IME_UPDATE);
+ callable_mp((Object *)OS_Unix::get_singleton()->get_main_loop(), &Object::notification).call_deferred(MainLoop::NOTIFICATION_OS_IME_UPDATE, false);
}
}
@@ -4019,6 +4043,18 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev
}
}
+void DisplayServerX11::_set_input_focus(Window p_window, int p_revert_to) {
+ Window focused_window;
+ int focus_ret_state;
+ XGetInputFocus(x11_display, &focused_window, &focus_ret_state);
+
+ // Only attempt to change focus if the window isn't already focused, in order to
+ // prevent issues with Godot stealing input focus with alternative window managers.
+ if (p_window != focused_window) {
+ XSetInputFocus(x11_display, p_window, p_revert_to, CurrentTime);
+ }
+}
+
void DisplayServerX11::_poll_events_thread(void *ud) {
DisplayServerX11 *display_server = static_cast<DisplayServerX11 *>(ud);
display_server->_poll_events();
@@ -4228,6 +4264,22 @@ bool DisplayServerX11::mouse_process_popups() {
return closed;
}
+bool DisplayServerX11::_window_focus_check() {
+ Window focused_window;
+ int focus_ret_state;
+ XGetInputFocus(x11_display, &focused_window, &focus_ret_state);
+
+ bool has_focus = false;
+ for (const KeyValue<int, DisplayServerX11::WindowData> &wid : windows) {
+ if (wid.value.x11_window == focused_window) {
+ has_focus = true;
+ break;
+ }
+ }
+
+ return has_focus;
+}
+
void DisplayServerX11::process_events() {
_THREAD_SAFE_METHOD_
@@ -4499,8 +4551,8 @@ 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 ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) {
- XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) {
+ _set_input_focus(wd.x11_window, RevertToPointerRoot);
}
// Have we failed to set fullscreen while the window was unmapped?
@@ -4675,8 +4727,8 @@ void DisplayServerX11::process_events() {
// 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 ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) {
- XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) {
+ _set_input_focus(wd.x11_window, RevertToPointerRoot);
}
_window_changed(&event);
@@ -4720,7 +4772,7 @@ void DisplayServerX11::process_events() {
// 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 && !wd.is_popup) {
- XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ _set_input_focus(wd.x11_window, RevertToPointerRoot);
}
uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms;
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index ac2c7843f6..da4085772a 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -354,10 +354,12 @@ class DisplayServerX11 : public DisplayServer {
Context context = CONTEXT_ENGINE;
WindowID _get_focused_window_or_popup() const;
+ bool _window_focus_check();
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);
+ void _set_input_focus(Window p_window, int p_revert_to);
mutable Mutex events_mutex;
Thread events_thread;
@@ -400,6 +402,7 @@ public:
virtual bool is_dark_mode() const override;
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
+ virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override;
#endif
virtual void mouse_set_mode(MouseMode p_mode) override;
@@ -491,6 +494,8 @@ public:
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual WindowID get_focused_window() const override;
+
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool can_any_window_draw() const override;
diff --git a/platform/linuxbsd/x11/key_mapping_x11.cpp b/platform/linuxbsd/x11/key_mapping_x11.cpp
index 0f709872cb..b589a2a573 100644
--- a/platform/linuxbsd/x11/key_mapping_x11.cpp
+++ b/platform/linuxbsd/x11/key_mapping_x11.cpp
@@ -88,7 +88,6 @@ void KeyMappingX11::initialize() {
xkeysym_map[XK_KP_Equal] = Key::EQUAL;
xkeysym_map[XK_KP_Separator] = Key::COMMA;
xkeysym_map[XK_KP_Decimal] = Key::KP_PERIOD;
- xkeysym_map[XK_KP_Delete] = Key::KP_PERIOD;
xkeysym_map[XK_KP_Multiply] = Key::KP_MULTIPLY;
xkeysym_map[XK_KP_Divide] = Key::KP_DIVIDE;
xkeysym_map[XK_KP_Subtract] = Key::KP_SUBTRACT;
@@ -105,6 +104,7 @@ void KeyMappingX11::initialize() {
xkeysym_map[XK_KP_9] = Key::KP_9;
// Same keys but with numlock off.
xkeysym_map[XK_KP_Insert] = Key::INSERT;
+ xkeysym_map[XK_KP_Delete] = Key::KEY_DELETE;
xkeysym_map[XK_KP_End] = Key::END;
xkeysym_map[XK_KP_Down] = Key::DOWN;
xkeysym_map[XK_KP_Page_Down] = Key::PAGEDOWN;
@@ -1113,6 +1113,20 @@ void KeyMappingX11::initialize() {
xkeysym_unicode_map[0x13BD] = 0x0153;
xkeysym_unicode_map[0x13BE] = 0x0178;
xkeysym_unicode_map[0x20AC] = 0x20AC;
+
+ // Scancode to physical location map.
+ // Ctrl.
+ location_map[0x25] = KeyLocation::LEFT;
+ location_map[0x69] = KeyLocation::RIGHT;
+ // Shift.
+ location_map[0x32] = KeyLocation::LEFT;
+ location_map[0x3E] = KeyLocation::RIGHT;
+ // Alt.
+ location_map[0x40] = KeyLocation::LEFT;
+ location_map[0x6C] = KeyLocation::RIGHT;
+ // Meta.
+ location_map[0x85] = KeyLocation::LEFT;
+ location_map[0x86] = KeyLocation::RIGHT;
}
Key KeyMappingX11::get_keycode(KeySym p_keysym) {
@@ -1173,3 +1187,11 @@ char32_t KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) {
}
return 0;
}
+
+KeyLocation KeyMappingX11::get_location(unsigned int p_code) {
+ const KeyLocation *location = location_map.getptr(p_code);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/linuxbsd/x11/key_mapping_x11.h b/platform/linuxbsd/x11/key_mapping_x11.h
index ae8fd67f27..a51ee5f48e 100644
--- a/platform/linuxbsd/x11/key_mapping_x11.h
+++ b/platform/linuxbsd/x11/key_mapping_x11.h
@@ -54,6 +54,7 @@ class KeyMappingX11 {
static inline HashMap<unsigned int, Key, HashMapHasherKeys> scancode_map;
static inline HashMap<Key, unsigned int, HashMapHasherKeys> scancode_map_inv;
static inline HashMap<KeySym, char32_t, HashMapHasherKeys> xkeysym_unicode_map;
+ static inline HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
KeyMappingX11() {}
@@ -64,6 +65,7 @@ public:
static unsigned int get_xlibcode(Key p_keysym);
static Key get_scancode(unsigned int p_code);
static char32_t get_unicode_from_keysym(KeySym p_keysym);
+ static KeyLocation get_location(unsigned int p_code);
};
#endif // KEY_MAPPING_X11_H
diff --git a/platform/macos/SCsub b/platform/macos/SCsub
index 30202e5de7..ad19f12c58 100644
--- a/platform/macos/SCsub
+++ b/platform/macos/SCsub
@@ -20,6 +20,7 @@ files = [
"godot_main_macos.mm",
"godot_menu_delegate.mm",
"godot_menu_item.mm",
+ "godot_open_save_delegate.mm",
"dir_access_macos.mm",
"tts_macos.mm",
"joypad_macos.cpp",
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index b41d2141fb..4a8e9cd956 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -67,21 +67,30 @@ def get_mvk_sdk_path():
if not os.path.exists(dirname):
return ""
- ver_file = "0.0.0.0"
- ver_num = ver_parse(ver_file)
-
+ ver_num = ver_parse("0.0.0.0")
files = os.listdir(dirname)
+ lib_name_out = dirname
for file in files:
if os.path.isdir(os.path.join(dirname, file)):
ver_comp = ver_parse(file)
- lib_name = os.path.join(
- os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/libMoltenVK.a"
- )
- if os.path.isfile(lib_name) and ver_comp > ver_num:
- ver_num = ver_comp
- ver_file = file
+ if ver_comp > ver_num:
+ # Try new SDK location.
+ lib_name = os.path.join(
+ os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
+ )
+ if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
+ ver_num = ver_comp
+ lib_name_out = lib_name
+ else:
+ # Try old SDK location.
+ lib_name = os.path.join(
+ os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/"
+ )
+ if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
+ ver_num = ver_comp
+ lib_name_out = lib_name
- return os.path.join(os.path.join(dirname, ver_file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/")
+ return lib_name_out
def configure(env: "Environment"):
@@ -121,12 +130,12 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.13"])
cc_version = get_compiler_version(env)
- cc_version_major = cc_version["major"]
- cc_version_minor = cc_version["minor"]
+ cc_version_major = cc_version["apple_major"]
+ cc_version_minor = cc_version["apple_minor"]
vanilla = is_vanilla_clang(env)
# Workaround for Xcode 15 linker bug.
- if not vanilla and cc_version_major == 15 and cc_version_minor == 0:
+ if not vanilla and cc_version_major == 1500 and cc_version_minor == 0:
env.Prepend(LINKFLAGS=["-ld_classic"])
env.Append(CCFLAGS=["-fobjc-arc"])
@@ -260,7 +269,7 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"])
if env["vulkan"]:
- env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "IOSurface"])
if not env["use_volk"]:
env.Append(LINKFLAGS=["-lMoltenVK"])
@@ -272,6 +281,12 @@ def configure(env: "Environment"):
mvk_list.insert(
0,
os.path.join(
+ os.path.expanduser(env["vulkan_sdk_path"]), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
+ ),
+ )
+ mvk_list.insert(
+ 0,
+ os.path.join(
os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/"
),
)
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index f8fd0f93ef..24d4a349e2 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -75,6 +75,7 @@ public:
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0;
+ KeyLocation location = KeyLocation::UNSPECIFIED;
};
struct WindowData {
@@ -234,6 +235,8 @@ private:
int _get_system_menu_count(const NSMenu *p_menu) const;
NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out);
+ Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb);
+
public:
NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
@@ -345,6 +348,7 @@ public:
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
+ virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override;
virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const override;
@@ -428,6 +432,8 @@ public:
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual WindowID get_focused_window() const override;
+
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool can_any_window_draw() const override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index bd92b7d472..396b943251 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -34,6 +34,7 @@
#include "godot_content_view.h"
#include "godot_menu_delegate.h"
#include "godot_menu_item.h"
+#include "godot_open_save_delegate.h"
#include "godot_window.h"
#include "godot_window_delegate.h"
#include "key_mapping_macos.h"
@@ -477,6 +478,7 @@ void DisplayServerMacOS::_process_key_events() {
k->set_physical_keycode(ke.physical_keycode);
k->set_key_label(ke.key_label);
k->set_unicode(ke.unicode);
+ k->set_location(ke.location);
_push_input(k);
} else {
@@ -505,6 +507,7 @@ void DisplayServerMacOS::_process_key_events() {
k->set_keycode(ke.keycode);
k->set_physical_keycode(ke.physical_keycode);
k->set_key_label(ke.key_label);
+ k->set_location(ke.location);
if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) {
k->set_unicode(key_event_buffer[i + 1].unicode);
@@ -2079,139 +2082,37 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect
return OK;
}
-@interface FileDialogDropdown : NSObject {
- NSSavePanel *dialog;
- NSMutableArray *allowed_types;
- int cur_index;
-}
-
-- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types;
-- (void)popupAction:(id)sender;
-- (int)getIndex;
-
-@end
-
-@implementation FileDialogDropdown
-
-- (int)getIndex {
- return cur_index;
-}
-
-- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types {
- if ((self = [super init])) {
- dialog = p_dialog;
- allowed_types = p_allowed_types;
- cur_index = 0;
- }
- return self;
-}
-
-- (void)popupAction:(id)sender {
- NSUInteger index = [sender indexOfSelectedItem];
- if (index < [allowed_types count]) {
- [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]];
- cur_index = index;
- } else {
- [dialog setAllowedFileTypes:@[]];
- cur_index = -1;
- }
+Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
}
-@end
-
-FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) {
- NSView *group = [[NSView alloc] initWithFrame:NSZeroRect];
- group.translatesAutoresizingMaskIntoConstraints = NO;
-
- NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
- label.translatesAutoresizingMaskIntoConstraints = NO;
- if (@available(macOS 10.14, *)) {
- label.textColor = NSColor.secondaryLabelColor;
- }
- if (@available(macOS 11.10, *)) {
- label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
- }
- [group addSubview:label];
-
- NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
- popup.translatesAutoresizingMaskIntoConstraints = NO;
-
- NSMutableArray *allowed_types = [[NSMutableArray alloc] init];
- bool allow_other = false;
- for (int i = 0; i < p_filters.size(); i++) {
- Vector<String> tokens = p_filters[i].split(";");
- if (tokens.size() >= 1) {
- String flt = tokens[0].strip_edges();
- int filter_slice_count = flt.get_slice_count(",");
-
- NSMutableArray *type_filters = [[NSMutableArray alloc] init];
- for (int j = 0; j < filter_slice_count; j++) {
- String str = (flt.get_slice(",", j).strip_edges());
- if (str.strip_edges() == "*.*" || str.strip_edges() == "*") {
- allow_other = true;
- } else if (!str.is_empty()) {
- [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
- }
- }
-
- if ([type_filters count] > 0) {
- NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()];
- [allowed_types addObject:type_filters];
- [popup addItemWithTitle:name_str];
- }
- }
- }
- FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types];
- popup.target = handler;
- popup.action = @selector(popupAction:);
-
- [group addSubview:popup];
-
- NSView *view = [[NSView alloc] initWithFrame:NSZeroRect];
- view.translatesAutoresizingMaskIntoConstraints = NO;
- [view addSubview:group];
-
- NSMutableArray *constraints = [NSMutableArray array];
- [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]];
- [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]];
- [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]];
- [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]];
- [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]];
- [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]];
- [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]];
- [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]];
- [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]];
- [NSLayoutConstraint activateConstraints:constraints];
-
- [p_panel setAllowsOtherFileTypes:allow_other];
- if ([allowed_types count] > 0) {
- [p_panel setAccessoryView:view];
- [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]];
- }
-
- return handler;
+Error DisplayServerMacOS::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true);
}
-Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+Error DisplayServerMacOS::_file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
_THREAD_SAFE_METHOD_
ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()];
- FileDialogDropdown *handler = nullptr;
-
WindowID prev_focus = last_focused_window;
+ GodotOpenSaveDelegate *panel_delegate = [[GodotOpenSaveDelegate alloc] init];
+ if (p_root.length() > 0) {
+ [panel_delegate setRootPath:p_root];
+ }
Callable callback = p_callback; // Make a copy for async completion handler.
if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
NSSavePanel *panel = [NSSavePanel savePanel];
[panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- handler = _make_accessory_view(panel, p_filters);
+ [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options];
[panel setExtensionHidden:YES];
[panel setCanSelectHiddenExtension:YES];
[panel setCanCreateDirectories:YES];
[panel setShowsHiddenFiles:p_show_hidden];
+ [panel setDelegate:panel_delegate];
if (p_filename != "") {
NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
[panel setNameFieldStringValue:fileurl];
@@ -2248,30 +2149,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &
url.parse_utf8([[[panel URL] path] UTF8String]);
files.push_back(url);
if (!callback.is_null()) {
- Variant v_result = true;
- Variant v_files = files;
- Variant v_index = [handler getIndex];
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = true;
+ Variant v_files = files;
+ Variant v_index = [panel_delegate getIndex];
+ Variant v_opt = [panel_delegate getSelection];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = true;
+ Variant v_files = files;
+ Variant v_index = [panel_delegate getIndex];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ }
}
}
} else {
if (!callback.is_null()) {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = [handler getIndex];
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [panel_delegate getIndex];
+ Variant v_opt = [panel_delegate getSelection];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [panel_delegate getIndex];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ }
}
}
}
@@ -2283,13 +2214,14 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- handler = _make_accessory_view(panel, p_filters);
+ [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options];
[panel setExtensionHidden:YES];
[panel setCanSelectHiddenExtension:YES];
[panel setCanCreateDirectories:YES];
[panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)];
[panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)];
[panel setShowsHiddenFiles:p_show_hidden];
+ [panel setDelegate:panel_delegate];
if (p_filename != "") {
NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
[panel setNameFieldStringValue:fileurl];
@@ -2333,30 +2265,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &
files.push_back(url);
}
if (!callback.is_null()) {
- Variant v_result = true;
- Variant v_files = files;
- Variant v_index = [handler getIndex];
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = true;
+ Variant v_files = files;
+ Variant v_index = [panel_delegate getIndex];
+ Variant v_opt = [panel_delegate getSelection];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = true;
+ Variant v_files = files;
+ Variant v_index = [panel_delegate getIndex];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ }
}
}
} else {
if (!callback.is_null()) {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = [handler getIndex];
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [panel_delegate getIndex];
+ Variant v_opt = [panel_delegate getSelection];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [panel_delegate getIndex];
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce)));
+ }
}
}
}
@@ -3723,6 +3685,10 @@ bool DisplayServerMacOS::window_is_focused(WindowID p_window) const {
return wd.focused;
}
+DisplayServerMacOS::WindowID DisplayServerMacOS::get_focused_window() const {
+ return last_focused_window;
+}
+
bool DisplayServerMacOS::window_can_draw(WindowID p_window) const {
return windows[p_window].is_visible;
}
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index 85fefe65c0..506b0dffb8 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -10,6 +10,13 @@
<link title="Running Godot apps on macOS">$DOCS_URL/tutorials//export/running_on_macos.html</link>
</tutorials>
<members>
+ <member name="application/additional_plist_content" type="String" setter="" getter="">
+ Additional data added to the root [code]&lt;dict&gt;[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.:
+ [codeblock]
+ &lt;key&gt;key_name&lt;/key&gt;
+ &lt;string&gt;value&lt;/string&gt;
+ [/codeblock]
+ </member>
<member name="application/app_category" type="String" setter="" getter="">
Application category for the App Store.
</member>
diff --git a/platform/macos/export/codesign.cpp b/platform/macos/export/codesign.cpp
index 2b8898e6a1..082c0abea7 100644
--- a/platform/macos/export/codesign.cpp
+++ b/platform/macos/export/codesign.cpp
@@ -32,8 +32,8 @@
#include "lipo.h"
#include "macho.h"
-#include "plist.h"
+#include "core/io/plist.h"
#include "core/os/os.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
diff --git a/platform/macos/export/codesign.h b/platform/macos/export/codesign.h
index 3e61751a96..49d53b376e 100644
--- a/platform/macos/export/codesign.h
+++ b/platform/macos/export/codesign.h
@@ -41,11 +41,10 @@
// - 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.
-#include "plist.h"
-
#include "core/crypto/crypto_core.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
+#include "core/io/plist.h"
#include "core/object/ref_counted.h"
#include "modules/modules_enabled.gen.h" // For regex.
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 6f9db1427b..7ed78db6f8 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -37,13 +37,14 @@
#include "run_icon_svg.gen.h"
#include "core/io/image_loader.h"
+#include "core/io/plist.h"
#include "core/string/translation.h"
#include "drivers/png/png_driver_common.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
-#include "editor/editor_scale.h"
#include "editor/editor_string_names.h"
#include "editor/import/resource_importer_texture_settings.h"
+#include "editor/themes/editor_scale.h"
#include "scene/resources/image_texture.h"
#include "modules/modules_enabled.gen.h" // For svg and regex.
@@ -388,6 +389,8 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_angle", PROPERTY_HINT_ENUM, "Auto,Yes,No"), 0, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), ""));
+
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/platform_build"), "14C18"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_version"), "13.1"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_build"), "22C55"));
@@ -672,6 +675,8 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres
strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version")) + "\n";
} else if (lines[i].find("$highres") != -1) {
strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n";
+ } else if (lines[i].find("$additional_plist_content") != -1) {
+ strnew += lines[i].replace("$additional_plist_content", p_preset->get("application/additional_plist_content")) + "\n";
} else if (lines[i].find("$platfbuild") != -1) {
strnew += lines[i].replace("$platfbuild", p_preset->get("xcode/platform_build")) + "\n";
} else if (lines[i].find("$sdkver") != -1) {
@@ -2051,13 +2056,17 @@ bool EditorExportPlatformMacOS::has_valid_export_configuration(const Ref<EditorE
String architecture = p_preset->get("binary_format/architecture");
if (architecture == "universal" || architecture == "x86_64") {
if (!ResourceImporterTextureSettings::should_import_s3tc_bptc()) {
+ err += TTR("Cannot export for universal or x86_64 if S3TC BPTC texture format is disabled. Enable it in the Project Settings (Rendering > Textures > VRAM Compression > Import S3TC BPTC).") + "\n";
valid = false;
}
- } else if (architecture == "arm64") {
+ }
+ if (architecture == "universal" || architecture == "arm64") {
if (!ResourceImporterTextureSettings::should_import_etc2_astc()) {
+ err += TTR("Cannot export for universal or arm64 if ETC2 ASTC texture format is disabled. Enable it in the Project Settings (Rendering > Textures > VRAM Compression > Import ETC2 ASTC).") + "\n";
valid = false;
}
- } else {
+ }
+ if (architecture != "universal" && architecture != "x86_64" && architecture != "arm64") {
ERR_PRINT("Invalid architecture");
}
@@ -2091,6 +2100,26 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor
};
}
+ const String &additional_plist_content = p_preset->get("application/additional_plist_content");
+ if (!additional_plist_content.is_empty()) {
+ const String &plist = vformat("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
+ "<plist version=\"1.0\">"
+ "<dict>\n"
+ "%s\n"
+ "</dict>\n"
+ "</plist>\n",
+ additional_plist_content);
+
+ String plist_err;
+ Ref<PList> plist_parser;
+ plist_parser.instantiate();
+ if (!plist_parser->load_string(plist, plist_err)) {
+ err += TTR("Invalid additional PList content: ") + plist_err + "\n";
+ valid = false;
+ }
+ }
+
List<ExportOption> options;
get_export_options(&options);
for (const EditorExportPlatform::ExportOption &E : options) {
diff --git a/platform/macos/export/plist.cpp b/platform/macos/export/plist.cpp
deleted file mode 100644
index f494c58fc9..0000000000
--- a/platform/macos/export/plist.cpp
+++ /dev/null
@@ -1,848 +0,0 @@
-/**************************************************************************/
-/* plist.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* 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 "plist.h"
-
-PList::PLNodeType PListNode::get_type() const {
- return data_type;
-}
-
-Variant PListNode::get_value() const {
- switch (data_type) {
- case PList::PL_NODE_TYPE_NIL: {
- return Variant();
- } break;
- case PList::PL_NODE_TYPE_STRING: {
- return String::utf8(data_string.get_data());
- } break;
- case PList::PL_NODE_TYPE_ARRAY: {
- Array arr;
- for (const Ref<PListNode> &E : data_array) {
- arr.push_back(E);
- }
- return arr;
- } break;
- case PList::PL_NODE_TYPE_DICT: {
- Dictionary dict;
- for (const KeyValue<String, Ref<PListNode>> &E : data_dict) {
- dict[E.key] = E.value;
- }
- return dict;
- } break;
- case PList::PL_NODE_TYPE_BOOLEAN: {
- return data_bool;
- } break;
- case PList::PL_NODE_TYPE_INTEGER: {
- return data_int;
- } break;
- case PList::PL_NODE_TYPE_REAL: {
- return data_real;
- } break;
- case PList::PL_NODE_TYPE_DATA: {
- int strlen = data_string.length();
-
- size_t arr_len = 0;
- Vector<uint8_t> buf;
- {
- buf.resize(strlen / 4 * 3 + 1);
- uint8_t *w = buf.ptrw();
-
- ERR_FAIL_COND_V(CryptoCore::b64_decode(&w[0], buf.size(), &arr_len, (unsigned char *)data_string.get_data(), strlen) != OK, Vector<uint8_t>());
- }
- buf.resize(arr_len);
- return buf;
- } break;
- case PList::PL_NODE_TYPE_DATE: {
- return String(data_string.get_data());
- } break;
- }
- return Variant();
-}
-
-Ref<PListNode> PListNode::new_node(const Variant &p_value) {
- Ref<PListNode> node;
- node.instantiate();
-
- switch (p_value.get_type()) {
- case Variant::NIL: {
- node->data_type = PList::PL_NODE_TYPE_NIL;
- } break;
- case Variant::BOOL: {
- node->data_type = PList::PL_NODE_TYPE_BOOLEAN;
- node->data_bool = p_value;
- } break;
- case Variant::INT: {
- node->data_type = PList::PL_NODE_TYPE_INTEGER;
- node->data_int = p_value;
- } break;
- case Variant::FLOAT: {
- node->data_type = PList::PL_NODE_TYPE_REAL;
- node->data_real = p_value;
- } break;
- case Variant::STRING_NAME:
- case Variant::STRING: {
- node->data_type = PList::PL_NODE_TYPE_STRING;
- node->data_string = p_value.operator String().utf8();
- } break;
- case Variant::DICTIONARY: {
- node->data_type = PList::PL_NODE_TYPE_DICT;
- Dictionary dict = p_value;
- const Variant *next = dict.next(nullptr);
- while (next) {
- Ref<PListNode> sub_node = dict[*next];
- ERR_FAIL_COND_V_MSG(sub_node.is_null(), Ref<PListNode>(), "Invalid dictionary element, should be PListNode.");
- node->data_dict[*next] = sub_node;
- next = dict.next(next);
- }
- } break;
- case Variant::ARRAY: {
- node->data_type = PList::PL_NODE_TYPE_ARRAY;
- Array ar = p_value;
- for (int i = 0; i < ar.size(); i++) {
- Ref<PListNode> sub_node = ar[i];
- ERR_FAIL_COND_V_MSG(sub_node.is_null(), Ref<PListNode>(), "Invalid array element, should be PListNode.");
- node->data_array.push_back(sub_node);
- }
- } break;
- case Variant::PACKED_BYTE_ARRAY: {
- node->data_type = PList::PL_NODE_TYPE_DATA;
- PackedByteArray buf = p_value;
- node->data_string = CryptoCore::b64_encode_str(buf.ptr(), buf.size()).utf8();
- } break;
- default: {
- ERR_FAIL_V_MSG(Ref<PListNode>(), "Unsupported data type.");
- } break;
- }
- return node;
-}
-
-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();
- node->data_real = (double)Time::get_singleton()->get_unix_time_from_datetime_string(p_string) - 978307200.0;
- 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(int64_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(double 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 KeyValue<String, Ref<PListNode>> &E : data_dict) {
- size += 1 + _asn1_size_len(p_len_octets); // Sequence.
- size += 1 + _asn1_size_len(p_len_octets) + E.key.utf8().length(); //Key.
- size += 1 + _asn1_size_len(p_len_octets) + E.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 KeyValue<String, Ref<PListNode>> &E : data_dict) {
- CharString cs = E.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 + E.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 && E.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 KeyValue<String, Ref<PListNode>> &E : data_dict) {
- p_stream += String("\t").repeat(p_indent + 1);
- p_stream += "<key>";
- p_stream += E.key;
- p_stream += "</key>\n";
- E.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);
-}
-
-uint64_t PList::read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size) {
- uint64_t pos = p_file->get_position();
- uint64_t ret = 0;
- switch (p_size) {
- case 1: {
- ret = p_file->get_8();
- } break;
- case 2: {
- ret = BSWAP16(p_file->get_16());
- } break;
- case 3: {
- ret = BSWAP32(p_file->get_32() & 0x00FFFFFF);
- } break;
- case 4: {
- ret = BSWAP32(p_file->get_32());
- } break;
- case 5: {
- ret = BSWAP64(p_file->get_64() & 0x000000FFFFFFFFFF);
- } break;
- case 6: {
- ret = BSWAP64(p_file->get_64() & 0x0000FFFFFFFFFFFF);
- } break;
- case 7: {
- ret = BSWAP64(p_file->get_64() & 0x00FFFFFFFFFFFFFF);
- } break;
- case 8: {
- ret = BSWAP64(p_file->get_64());
- } break;
- default: {
- ret = 0;
- }
- }
- p_file->seek(pos + p_size);
-
- return ret;
-}
-
-Ref<PListNode> PList::read_bplist_obj(Ref<FileAccess> p_file, uint64_t p_offset_idx) {
- Ref<PListNode> node;
- node.instantiate();
-
- uint64_t ot_off = trailer.offset_table_start + p_offset_idx * trailer.offset_size;
- p_file->seek(ot_off);
- uint64_t marker_off = read_bplist_var_size_int(p_file, trailer.offset_size);
- ERR_FAIL_COND_V_MSG(marker_off == 0, Ref<PListNode>(), "Invalid marker size.");
-
- p_file->seek(marker_off);
- uint8_t marker = p_file->get_8();
- uint8_t marker_type = marker & 0xF0;
- uint64_t marker_size = marker & 0x0F;
-
- switch (marker_type) {
- case 0x00: {
- if (marker_size == 0x00) {
- node->data_type = PL_NODE_TYPE_NIL;
- } else if (marker_size == 0x08) {
- node->data_type = PL_NODE_TYPE_BOOLEAN;
- node->data_bool = false;
- } else if (marker_size == 0x09) {
- node->data_type = PL_NODE_TYPE_BOOLEAN;
- node->data_bool = true;
- } else {
- ERR_FAIL_V_MSG(Ref<PListNode>(), "Invalid nil/bool marker value.");
- }
- } break;
- case 0x10: {
- node->data_type = PL_NODE_TYPE_INTEGER;
- node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, pow(2, marker_size)));
- } break;
- case 0x20: {
- node->data_type = PL_NODE_TYPE_REAL;
- node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, pow(2, marker_size)));
- } break;
- case 0x30: {
- node->data_type = PL_NODE_TYPE_DATE;
- node->data_int = BSWAP64(p_file->get_64());
- node->data_string = Time::get_singleton()->get_datetime_string_from_unix_time(node->data_real + 978307200.0).utf8();
- } break;
- case 0x40: {
- if (marker_size == 0x0F) {
- uint8_t ext = p_file->get_8() & 0xF;
- marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
- }
- node->data_type = PL_NODE_TYPE_DATA;
- PackedByteArray buf;
- buf.resize(marker_size + 1);
- p_file->get_buffer(reinterpret_cast<uint8_t *>(buf.ptrw()), marker_size);
- node->data_string = CryptoCore::b64_encode_str(buf.ptr(), buf.size()).utf8();
- } break;
- case 0x50: {
- if (marker_size == 0x0F) {
- uint8_t ext = p_file->get_8() & 0xF;
- marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
- }
- node->data_type = PL_NODE_TYPE_STRING;
- node->data_string.resize(marker_size + 1);
- p_file->get_buffer(reinterpret_cast<uint8_t *>(node->data_string.ptrw()), marker_size);
- } break;
- case 0x60: {
- if (marker_size == 0x0F) {
- uint8_t ext = p_file->get_8() & 0xF;
- marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
- }
- Char16String cs16;
- cs16.resize(marker_size + 1);
- for (uint64_t i = 0; i < marker_size; i++) {
- cs16[i] = BSWAP16(p_file->get_16());
- }
- node->data_type = PL_NODE_TYPE_STRING;
- node->data_string = String::utf16(cs16.ptr(), cs16.length()).utf8();
- } break;
- case 0x80: {
- node->data_type = PL_NODE_TYPE_INTEGER;
- node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, marker_size + 1));
- } break;
- case 0xA0:
- case 0xC0: {
- if (marker_size == 0x0F) {
- uint8_t ext = p_file->get_8() & 0xF;
- marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
- }
- uint64_t pos = p_file->get_position();
-
- node->data_type = PL_NODE_TYPE_ARRAY;
- for (uint64_t i = 0; i < marker_size; i++) {
- p_file->seek(pos + trailer.ref_size * i);
- uint64_t ref = read_bplist_var_size_int(p_file, trailer.ref_size);
-
- Ref<PListNode> element = read_bplist_obj(p_file, ref);
- ERR_FAIL_COND_V(element.is_null(), Ref<PListNode>());
- node->data_array.push_back(element);
- }
- } break;
- case 0xD0: {
- if (marker_size == 0x0F) {
- uint8_t ext = p_file->get_8() & 0xF;
- marker_size = read_bplist_var_size_int(p_file, pow(2, ext));
- }
- uint64_t pos = p_file->get_position();
-
- node->data_type = PL_NODE_TYPE_DICT;
- for (uint64_t i = 0; i < marker_size; i++) {
- p_file->seek(pos + trailer.ref_size * i);
- uint64_t key_ref = read_bplist_var_size_int(p_file, trailer.ref_size);
-
- p_file->seek(pos + trailer.ref_size * (i + marker_size));
- uint64_t obj_ref = read_bplist_var_size_int(p_file, trailer.ref_size);
-
- Ref<PListNode> element_key = read_bplist_obj(p_file, key_ref);
- ERR_FAIL_COND_V(element_key.is_null() || element_key->data_type != PL_NODE_TYPE_STRING, Ref<PListNode>());
- Ref<PListNode> element = read_bplist_obj(p_file, obj_ref);
- ERR_FAIL_COND_V(element.is_null(), Ref<PListNode>());
- node->data_dict[String::utf8(element_key->data_string.ptr(), element_key->data_string.length())] = element;
- }
- } break;
- default: {
- ERR_FAIL_V_MSG(Ref<PListNode>(), "Invalid marker type.");
- }
- }
- return node;
-}
-
-bool PList::load_file(const String &p_filename) {
- root = Ref<PListNode>();
-
- Ref<FileAccess> fb = FileAccess::open(p_filename, FileAccess::READ);
- if (fb.is_null()) {
- return false;
- }
-
- unsigned char magic[8];
- fb->get_buffer(magic, 8);
-
- if (String((const char *)magic, 8) == "bplist00") {
- fb->seek_end(-26);
- trailer.offset_size = fb->get_8();
- trailer.ref_size = fb->get_8();
- trailer.object_num = BSWAP64(fb->get_64());
- trailer.root_offset_idx = BSWAP64(fb->get_64());
- trailer.offset_table_start = BSWAP64(fb->get_64());
- root = read_bplist_obj(fb, trailer.root_offset_idx);
-
- return root.is_valid();
- } else {
- // Load text plist.
- Error err;
- Vector<uint8_t> array = FileAccess::get_file_as_bytes(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") {
- 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;
-}
diff --git a/platform/macos/export/plist.h b/platform/macos/export/plist.h
deleted file mode 100644
index 28b02e4eb7..0000000000
--- a/platform/macos/export/plist.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/**************************************************************************/
-/* plist.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* 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 MACOS_PLIST_H
-#define MACOS_PLIST_H
-
-// Property list file format (application/x-plist) parser, property list ASN-1 serialization.
-
-#include "core/crypto/crypto_core.h"
-#include "core/io/file_access.h"
-#include "core/os/time.h"
-
-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:
- struct PListTrailer {
- uint8_t offset_size;
- uint8_t ref_size;
- uint64_t object_num;
- uint64_t root_offset_idx;
- uint64_t offset_table_start;
- };
-
- PListTrailer trailer;
- Ref<PListNode> root;
-
- uint64_t read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size);
- Ref<PListNode> read_bplist_obj(Ref<FileAccess> p_file, uint64_t p_offset_idx);
-
-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;
- HashMap<String, Ref<PListNode>> data_dict;
- union {
- int64_t data_int;
- bool data_bool;
- double data_real;
- };
-
- PList::PLNodeType get_type() const;
- Variant get_value() const;
-
- static Ref<PListNode> new_node(const Variant &p_value);
- 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(int64_t p_int);
- static Ref<PListNode> new_real(double 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 // MACOS_PLIST_H
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index 139411249c..4505becbc2 100644
--- a/platform/macos/godot_content_view.mm
+++ b/platform/macos/godot_content_view.mm
@@ -576,21 +576,23 @@
String u32text;
u32text.parse_utf16(text.ptr(), text.length());
+ DisplayServerMacOS::KeyEvent ke;
+ ke.window_id = window_id;
+ ke.macos_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = [event isARepeat];
+ ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false);
+ ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
+ ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
+ ke.raw = true;
+
+ if (u32text.is_empty()) {
+ ke.unicode = 0;
+ ds->push_to_key_event_buffer(ke);
+ }
for (int i = 0; i < u32text.length(); i++) {
const char32_t codepoint = u32text[i];
-
- DisplayServerMacOS::KeyEvent ke;
-
- ke.window_id = window_id;
- ke.macos_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = [event isARepeat];
- ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false);
- ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
- ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = fix_unicode(codepoint);
- ke.raw = true;
-
ds->push_to_key_event_buffer(ke);
}
} else {
@@ -604,6 +606,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location([event keyCode]);
ke.raw = false;
ds->push_to_key_event_buffer(ke);
@@ -669,6 +672,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key(key);
ke.key_label = KeyMappingMacOS::remap_key(key, mod, true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location(key);
ds->push_to_key_event_buffer(ke);
}
@@ -696,6 +700,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location([event keyCode]);
ke.raw = true;
ds->push_to_key_event_buffer(ke);
diff --git a/platform/macos/godot_open_save_delegate.h b/platform/macos/godot_open_save_delegate.h
new file mode 100644
index 0000000000..8857ef1fa9
--- /dev/null
+++ b/platform/macos/godot_open_save_delegate.h
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* godot_open_save_delegate.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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_OPEN_SAVE_DELEGATE_H
+#define GODOT_OPEN_SAVE_DELEGATE_H
+
+#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+
+#include "core/templates/hash_map.h"
+#include "core/variant/typed_array.h"
+#include "core/variant/variant.h"
+
+@interface GodotOpenSaveDelegate : NSObject <NSOpenSavePanelDelegate> {
+ NSSavePanel *dialog;
+ NSMutableArray *allowed_types;
+
+ HashMap<int, String> ctr_ids;
+ Dictionary options;
+ int cur_index;
+ int ctr_id;
+
+ String root;
+}
+
+- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options;
+- (void)setFileTypes:(NSMutableArray *)p_allowed_types;
+- (void)popupOptionAction:(id)p_sender;
+- (void)popupCheckAction:(id)p_sender;
+- (void)popupFileAction:(id)p_sender;
+- (int)getIndex;
+- (Dictionary)getSelection;
+- (int)setDefaultInt:(const String &)p_name value:(int)p_value;
+- (int)setDefaultBool:(const String &)p_name value:(bool)p_value;
+- (void)setRootPath:(const String &)p_root_path;
+
+@end
+
+#endif // GODOT_OPEN_SAVE_DELEGATE_H
diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm
new file mode 100644
index 0000000000..306015d644
--- /dev/null
+++ b/platform/macos/godot_open_save_delegate.mm
@@ -0,0 +1,250 @@
+/**************************************************************************/
+/* godot_open_save_delegate.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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_open_save_delegate.h"
+
+@implementation GodotOpenSaveDelegate
+
+- (instancetype)init {
+ self = [super init];
+ if ((self = [super init])) {
+ dialog = nullptr;
+ cur_index = 0;
+ ctr_id = 1;
+ allowed_types = nullptr;
+ root = String();
+ }
+ return self;
+}
+
+- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options {
+ dialog = p_panel;
+
+ NSMutableArray *constraints = [NSMutableArray array];
+
+ NSView *base_view = [[NSView alloc] initWithFrame:NSZeroRect];
+ base_view.translatesAutoresizingMaskIntoConstraints = NO;
+
+ NSGridView *view = [NSGridView gridViewWithNumberOfColumns:2 rows:0];
+ view.translatesAutoresizingMaskIntoConstraints = NO;
+ view.columnSpacing = 10;
+ view.rowSpacing = 10;
+ view.rowAlignment = NSGridRowAlignmentLastBaseline;
+
+ int option_count = 0;
+
+ for (int i = 0; i < p_options.size(); i++) {
+ const Dictionary &item = p_options[i];
+ if (!item.has("name") || !item.has("values") || !item.has("default")) {
+ continue;
+ }
+ const String &name = item["name"];
+ const Vector<String> &values = item["values"];
+ int default_idx = item["default"];
+
+ NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:name.utf8().get_data()]];
+ if (@available(macOS 10.14, *)) {
+ label.textColor = NSColor.secondaryLabelColor;
+ }
+ if (@available(macOS 11.10, *)) {
+ label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ }
+
+ NSView *popup = nullptr;
+ if (values.is_empty()) {
+ NSButton *popup_check = [NSButton checkboxWithTitle:@"" target:self action:@selector(popupCheckAction:)];
+ int tag = [self setDefaultBool:name value:(bool)default_idx];
+ popup_check.state = (default_idx) ? NSControlStateValueOn : NSControlStateValueOff;
+ popup_check.tag = tag;
+ popup = popup_check;
+ } else {
+ NSPopUpButton *popup_list = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ for (int i = 0; i < values.size(); i++) {
+ [popup_list addItemWithTitle:[NSString stringWithUTF8String:values[i].utf8().get_data()]];
+ }
+ int tag = [self setDefaultInt:name value:default_idx];
+ [popup_list selectItemAtIndex:default_idx];
+ popup_list.tag = tag;
+ popup_list.target = self;
+ popup_list.action = @selector(popupOptionAction:);
+ popup = popup_list;
+ }
+
+ [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]];
+
+ option_count++;
+ }
+
+ NSMutableArray *new_allowed_types = [[NSMutableArray alloc] init];
+ bool allow_other = false;
+ {
+ NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
+ if (@available(macOS 10.14, *)) {
+ label.textColor = NSColor.secondaryLabelColor;
+ }
+ if (@available(macOS 11.10, *)) {
+ label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ }
+
+ NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ if (p_filters.is_empty()) {
+ [popup addItemWithTitle:@"All Files"];
+ }
+
+ for (int i = 0; i < p_filters.size(); i++) {
+ Vector<String> tokens = p_filters[i].split(";");
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+
+ NSMutableArray *type_filters = [[NSMutableArray alloc] init];
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (str.strip_edges() == "*.*" || str.strip_edges() == "*") {
+ allow_other = true;
+ } else if (!str.is_empty()) {
+ [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
+ }
+ }
+
+ if ([type_filters count] > 0) {
+ NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()];
+ [new_allowed_types addObject:type_filters];
+ [popup addItemWithTitle:name_str];
+ }
+ }
+ }
+ [self setFileTypes:new_allowed_types];
+ popup.target = self;
+ popup.action = @selector(popupFileAction:);
+
+ [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]];
+ }
+
+ [base_view addSubview:view];
+ [constraints addObject:[view.topAnchor constraintEqualToAnchor:base_view.topAnchor constant:10]];
+ [constraints addObject:[base_view.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:10]];
+ [constraints addObject:[base_view.centerXAnchor constraintEqualToAnchor:view.centerXAnchor constant:10]];
+ [NSLayoutConstraint activateConstraints:constraints];
+
+ [p_panel setAllowsOtherFileTypes:allow_other];
+ if (option_count > 0 || [new_allowed_types count] > 0) {
+ [p_panel setAccessoryView:base_view];
+ }
+ if ([new_allowed_types count] > 0) {
+ [p_panel setAllowedFileTypes:[new_allowed_types objectAtIndex:0]];
+ }
+}
+
+- (int)getIndex {
+ return cur_index;
+}
+
+- (Dictionary)getSelection {
+ return options;
+}
+
+- (int)setDefaultInt:(const String &)p_name value:(int)p_value {
+ int cid = ctr_id++;
+ options[p_name] = p_value;
+ ctr_ids[cid] = p_name;
+
+ return cid;
+}
+
+- (int)setDefaultBool:(const String &)p_name value:(bool)p_value {
+ int cid = ctr_id++;
+ options[p_name] = p_value;
+ ctr_ids[cid] = p_name;
+
+ return cid;
+}
+
+- (void)setFileTypes:(NSMutableArray *)p_allowed_types {
+ allowed_types = p_allowed_types;
+}
+
+- (instancetype)initWithDialog:(NSSavePanel *)p_dialog {
+ if ((self = [super init])) {
+ dialog = p_dialog;
+ cur_index = 0;
+ ctr_id = 1;
+ allowed_types = nullptr;
+ }
+ return self;
+}
+
+- (void)popupCheckAction:(id)p_sender {
+ NSButton *btn = p_sender;
+ if (btn && ctr_ids.has(btn.tag)) {
+ options[ctr_ids[btn.tag]] = ([btn state] == NSControlStateValueOn);
+ }
+}
+
+- (void)popupOptionAction:(id)p_sender {
+ NSPopUpButton *btn = p_sender;
+ if (btn && ctr_ids.has(btn.tag)) {
+ options[ctr_ids[btn.tag]] = (int)[btn indexOfSelectedItem];
+ }
+}
+
+- (void)popupFileAction:(id)p_sender {
+ NSPopUpButton *btn = p_sender;
+ if (btn) {
+ NSUInteger index = [btn indexOfSelectedItem];
+ if (allowed_types && index < [allowed_types count]) {
+ [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]];
+ cur_index = index;
+ } else {
+ [dialog setAllowedFileTypes:@[]];
+ cur_index = -1;
+ }
+ }
+}
+
+- (void)setRootPath:(const String &)p_root_path {
+ root = p_root_path;
+}
+
+- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError *_Nullable *)outError {
+ if (root.is_empty()) {
+ return YES;
+ }
+
+ NSString *ns_path = url.URLByStandardizingPath.URLByResolvingSymlinksInPath.path;
+ String path = String::utf8([ns_path UTF8String]).simplify_path();
+ if (!path.begins_with(root.simplify_path())) {
+ return NO;
+ }
+
+ return YES;
+}
+
+@end
diff --git a/platform/macos/key_mapping_macos.h b/platform/macos/key_mapping_macos.h
index 1bda4eb406..f5b0ff8d02 100644
--- a/platform/macos/key_mapping_macos.h
+++ b/platform/macos/key_mapping_macos.h
@@ -45,6 +45,7 @@ public:
static Key translate_key(unsigned int p_key);
static unsigned int unmap_key(Key p_key);
static Key remap_key(unsigned int p_key, unsigned int p_state, bool p_unicode);
+ static KeyLocation translate_location(unsigned int p_key);
// Mapping for menu shortcuts.
static String keycode_get_native_string(Key p_keycode);
diff --git a/platform/macos/key_mapping_macos.mm b/platform/macos/key_mapping_macos.mm
index db3fa4e02d..b5e72048e7 100644
--- a/platform/macos/key_mapping_macos.mm
+++ b/platform/macos/key_mapping_macos.mm
@@ -46,6 +46,7 @@ HashSet<unsigned int> numpad_keys;
HashMap<unsigned int, Key, HashMapHasherKeys> keysym_map;
HashMap<Key, unsigned int, HashMapHasherKeys> keysym_map_inv;
HashMap<Key, char32_t, HashMapHasherKeys> keycode_map;
+HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingMacOS::initialize() {
numpad_keys.insert(0x41); //kVK_ANSI_KeypadDecimal
@@ -321,6 +322,20 @@ void KeyMappingMacOS::initialize() {
keycode_map[Key::BAR] = '|';
keycode_map[Key::BRACERIGHT] = '}';
keycode_map[Key::ASCIITILDE] = '~';
+
+ // Keysym -> physical location.
+ // Ctrl.
+ location_map[0x3b] = KeyLocation::LEFT;
+ location_map[0x3e] = KeyLocation::RIGHT;
+ // Shift.
+ location_map[0x38] = KeyLocation::LEFT;
+ location_map[0x3c] = KeyLocation::RIGHT;
+ // Alt/Option.
+ location_map[0x3a] = KeyLocation::LEFT;
+ location_map[0x3d] = KeyLocation::RIGHT;
+ // Meta/Command (yes, right < left).
+ location_map[0x36] = KeyLocation::RIGHT;
+ location_map[0x37] = KeyLocation::LEFT;
}
bool KeyMappingMacOS::is_numpad_key(unsigned int p_key) {
@@ -396,6 +411,15 @@ Key KeyMappingMacOS::remap_key(unsigned int p_key, unsigned int p_state, bool p_
}
}
+// Translates a macOS keycode to a Godot key location.
+KeyLocation KeyMappingMacOS::translate_location(unsigned int p_key) {
+ const KeyLocation *location = location_map.getptr(p_key);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
+
String KeyMappingMacOS::keycode_get_native_string(Key p_keycode) {
const char32_t *key = keycode_map.getptr(p_keycode);
if (key) {
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index 29dff683d5..b8496d72fb 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -230,6 +230,8 @@ Error OS_MacOS::open_dynamic_library(const String p_path, void *&p_library_handl
path = get_framework_executable(get_executable_path().get_base_dir().path_join("../Frameworks").path_join(p_path.get_file()));
}
+ ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
+
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
diff --git a/platform/web/.eslintrc.html.js b/platform/web/.eslintrc.html.js
index 5cb8de360a..8c9a3d83da 100644
--- a/platform/web/.eslintrc.html.js
+++ b/platform/web/.eslintrc.html.js
@@ -15,5 +15,7 @@ module.exports = {
"Godot": true,
"Engine": true,
"$GODOT_CONFIG": true,
+ "$GODOT_THREADS_ENABLED": true,
+ "___GODOT_THREADS_ENABLED___": true,
},
};
diff --git a/platform/web/SCsub b/platform/web/SCsub
index 1af0642554..3e0cc9ac4a 100644
--- a/platform/web/SCsub
+++ b/platform/web/SCsub
@@ -13,7 +13,7 @@ if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS:
except Exception:
print("GODOT_WEB_TEST_PORT must be a valid integer")
sys.exit(255)
- serve(env.Dir("#bin/.web_zip").abspath, port, "run" in COMMAND_LINE_TARGETS)
+ serve(env.Dir(env.GetTemplateZipPath()).abspath, port, "run" in COMMAND_LINE_TARGETS)
sys.exit(0)
web_files = [
@@ -95,7 +95,7 @@ engine = [
"js/engine/engine.js",
]
externs = [env.File("#platform/web/js/engine/engine.externs.js")]
-js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs)
+js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs, env["threads"])
env.Depends(js_engine, externs)
wrap_list = [
diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp
index 1298d28ebf..ec3c22bf7c 100644
--- a/platform/web/audio_driver_web.cpp
+++ b/platform/web/audio_driver_web.cpp
@@ -30,6 +30,8 @@
#include "audio_driver_web.h"
+#include "godot_audio.h"
+
#include "core/config/project_settings.h"
#include <emscripten.h>
@@ -184,6 +186,8 @@ Error AudioDriverWeb::input_stop() {
return OK;
}
+#ifdef THREADS_ENABLED
+
/// AudioWorkletNode implementation (threads)
void AudioDriverWorklet::_audio_thread_func(void *p_data) {
AudioDriverWorklet *driver = static_cast<AudioDriverWorklet *>(p_data);
@@ -245,3 +249,51 @@ void AudioDriverWorklet::finish_driver() {
quit = true; // Ask thread to quit.
thread.wait_to_finish();
}
+
+#else // No threads.
+
+/// AudioWorkletNode implementation (no threads)
+AudioDriverWorklet *AudioDriverWorklet::singleton = nullptr;
+
+Error AudioDriverWorklet::create(int &p_buffer_size, int p_channels) {
+ if (!godot_audio_has_worklet()) {
+ return ERR_UNAVAILABLE;
+ }
+ return (Error)godot_audio_worklet_create(p_channels);
+}
+
+void AudioDriverWorklet::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
+ _audio_driver_process();
+ godot_audio_worklet_start_no_threads(p_out_buf, p_out_buf_size, &_process_callback, p_in_buf, p_in_buf_size, &_capture_callback);
+}
+
+void AudioDriverWorklet::_process_callback(int p_pos, int p_samples) {
+ AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton();
+ driver->_audio_driver_process(p_pos, p_samples);
+}
+
+void AudioDriverWorklet::_capture_callback(int p_pos, int p_samples) {
+ AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton();
+ driver->_audio_driver_capture(p_pos, p_samples);
+}
+
+/// ScriptProcessorNode implementation
+AudioDriverScriptProcessor *AudioDriverScriptProcessor::singleton = nullptr;
+
+void AudioDriverScriptProcessor::_process_callback() {
+ AudioDriverScriptProcessor::get_singleton()->_audio_driver_capture();
+ AudioDriverScriptProcessor::get_singleton()->_audio_driver_process();
+}
+
+Error AudioDriverScriptProcessor::create(int &p_buffer_samples, int p_channels) {
+ if (!godot_audio_has_script_processor()) {
+ return ERR_UNAVAILABLE;
+ }
+ return (Error)godot_audio_script_create(&p_buffer_samples, p_channels);
+}
+
+void AudioDriverScriptProcessor::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
+ godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback);
+}
+
+#endif // THREADS_ENABLED
diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h
index 12a61746c3..df88d0a94c 100644
--- a/platform/web/audio_driver_web.h
+++ b/platform/web/audio_driver_web.h
@@ -90,6 +90,7 @@ public:
AudioDriverWeb() {}
};
+#ifdef THREADS_ENABLED
class AudioDriverWorklet : public AudioDriverWeb {
private:
enum {
@@ -120,4 +121,54 @@ public:
virtual void unlock() override;
};
+#else
+
+class AudioDriverWorklet : public AudioDriverWeb {
+private:
+ static void _process_callback(int p_pos, int p_samples);
+ static void _capture_callback(int p_pos, int p_samples);
+
+ static AudioDriverWorklet *singleton;
+
+protected:
+ virtual Error create(int &p_buffer_size, int p_output_channels) override;
+ virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
+
+public:
+ virtual const char *get_name() const override {
+ return "AudioWorklet";
+ }
+
+ virtual void lock() override {}
+ virtual void unlock() override {}
+
+ static AudioDriverWorklet *get_singleton() { return singleton; }
+
+ AudioDriverWorklet() { singleton = this; }
+};
+
+class AudioDriverScriptProcessor : public AudioDriverWeb {
+private:
+ static void _process_callback();
+
+ static AudioDriverScriptProcessor *singleton;
+
+protected:
+ virtual Error create(int &p_buffer_size, int p_output_channels) override;
+ virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
+ virtual void finish_driver() override;
+
+public:
+ virtual const char *get_name() const override { return "ScriptProcessor"; }
+
+ virtual void lock() override {}
+ virtual void unlock() override {}
+
+ static AudioDriverScriptProcessor *get_singleton() { return singleton; }
+
+ AudioDriverScriptProcessor() { singleton = this; }
+};
+
+#endif // THREADS_ENABLED
+
#endif // AUDIO_DRIVER_WEB_H
diff --git a/platform/web/detect.py b/platform/web/detect.py
index 579eaaff03..bce03eb79e 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -8,6 +8,7 @@ from emscripten_helpers import (
add_js_pre,
add_js_externs,
create_template_zip,
+ get_template_zip_path,
)
from methods import get_compiler_version
from SCons.Util import WhereIs
@@ -30,6 +31,9 @@ def get_opts():
return [
("initial_memory", "Initial WASM memory (in MiB)", 32),
+ # Matches default values from before Emscripten 3.1.27. New defaults are too low for Godot.
+ ("stack_size", "WASM stack size (in KiB)", 5120),
+ ("default_pthread_stack_size", "WASM pthread default stack size (in KiB)", 2048),
BoolVariable("use_assertions", "Use Emscripten runtime assertions", False),
BoolVariable("use_ubsan", "Use Emscripten undefined behavior sanitizer (UBSAN)", False),
BoolVariable("use_asan", "Use Emscripten address sanitizer (ASAN)", False),
@@ -158,6 +162,9 @@ def configure(env: "Environment"):
# Add method that joins/compiles our Engine files.
env.AddMethod(create_engine_file, "CreateEngineFile")
+ # Add method for getting the final zip path
+ env.AddMethod(get_template_zip_path, "GetTemplateZipPath")
+
# Add method for creating the final zip file
env.AddMethod(create_template_zip, "CreateTemplateZip")
@@ -193,11 +200,6 @@ def configure(env: "Environment"):
env.Prepend(CPPPATH=["#platform/web"])
env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"])
- if cc_semver >= (3, 1, 25):
- env.Append(LINKFLAGS=["-s", "STACK_SIZE=5MB"])
- else:
- env.Append(LINKFLAGS=["-s", "TOTAL_STACK=5MB"])
-
if env["opengl3"]:
env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"])
# This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1.
@@ -208,13 +210,20 @@ def configure(env: "Environment"):
if env["javascript_eval"]:
env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
- # Thread support (via SharedArrayBuffer).
- env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
- env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
- env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
- env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=2MB"])
- env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"])
- env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"])
+ stack_size_opt = "STACK_SIZE" if cc_semver >= (3, 1, 25) else "TOTAL_STACK"
+ env.Append(LINKFLAGS=["-s", "%s=%sKB" % (stack_size_opt, env["stack_size"])])
+
+ if env["threads"]:
+ # Thread support (via SharedArrayBuffer).
+ env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
+ env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
+ env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
+ env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=%sKB" % env["default_pthread_stack_size"]])
+ env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"])
+ env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"])
+ elif env["proxy_to_pthread"]:
+ print('"threads=no" support requires "proxy_to_pthread=no", disabling proxy to pthread.')
+ env["proxy_to_pthread"] = False
if env["lto"] != "none":
# Workaround https://github.com/emscripten-core/emscripten/issues/19781.
@@ -223,7 +232,7 @@ def configure(env: "Environment"):
if env["dlink_enabled"]:
if env["proxy_to_pthread"]:
- print("GDExtension support requires proxy_to_pthread=no, disabling")
+ print("GDExtension support requires proxy_to_pthread=no, disabling proxy to pthread.")
env["proxy_to_pthread"] = False
if cc_semver < (3, 1, 14):
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index b4a190d47e..aacbe4879f 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -187,6 +187,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false);
Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true);
+ KeyLocation location = dom_code2godot_key_location(p_key_event_code.utf8().get_data());
DisplayServerWeb::KeyEvent ke;
@@ -197,6 +198,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
ke.physical_keycode = scancode;
ke.key_label = fix_key_label(c, keycode);
ke.unicode = fix_unicode(c);
+ ke.location = location;
ke.mod = p_modifiers;
if (ds->key_event_pos >= ds->key_event_buffer.size()) {
@@ -1383,6 +1385,7 @@ void DisplayServerWeb::process_events() {
ev->set_physical_keycode(ke.physical_keycode);
ev->set_key_label(ke.key_label);
ev->set_unicode(ke.unicode);
+ ev->set_location(ke.location);
if (ke.raw) {
dom2godot_mod(ev, ke.mod, ke.keycode);
}
diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h
index 140aef952b..682d10704f 100644
--- a/platform/web/display_server_web.h
+++ b/platform/web/display_server_web.h
@@ -95,6 +95,7 @@ private:
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0;
+ KeyLocation location = KeyLocation::UNSPECIFIED;
int mod = 0;
};
diff --git a/platform/web/doc_classes/EditorExportPlatformWeb.xml b/platform/web/doc_classes/EditorExportPlatformWeb.xml
index c4c4fd870b..f07f265b0d 100644
--- a/platform/web/doc_classes/EditorExportPlatformWeb.xml
+++ b/platform/web/doc_classes/EditorExportPlatformWeb.xml
@@ -47,6 +47,10 @@
</member>
<member name="variant/extensions_support" type="bool" setter="" getter="">
</member>
+ <member name="variant/thread_support" type="bool" setter="" getter="">
+ If enabled, the exported game will support threads. It requires [url=https://web.dev/articles/coop-coep]a "cross-origin isolated" website[/url], which can be difficult to setup and brings some limitations (e.g. not being able to communicate with third-party websites).
+ If disabled, the exported game will not support threads. As a result, it is more prone to performance and audio issues, but will only require to be run on a HTTPS website.
+ </member>
<member name="vram_texture_compression/for_desktop" type="bool" setter="" getter="">
</member>
<member name="vram_texture_compression/for_mobile" type="bool" setter="" getter="">
diff --git a/platform/web/dom_keys.inc b/platform/web/dom_keys.inc
index cd94b779c0..b20a3a46b9 100644
--- a/platform/web/dom_keys.inc
+++ b/platform/web/dom_keys.inc
@@ -223,3 +223,24 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b
return Key::NONE;
#undef DOM2GODOT
}
+
+KeyLocation dom_code2godot_key_location(EM_UTF8 const p_code[32]) {
+#define DOM2GODOT(m_str, m_godot_code) \
+ if (memcmp((const void *)m_str, (void *)p_code, strlen(m_str) + 1) == 0) { \
+ return KeyLocation::m_godot_code; \
+ }
+
+ DOM2GODOT("AltLeft", LEFT);
+ DOM2GODOT("AltRight", RIGHT);
+ DOM2GODOT("ControlLeft", LEFT);
+ DOM2GODOT("ControlRight", RIGHT);
+ DOM2GODOT("MetaLeft", LEFT);
+ DOM2GODOT("MetaRight", RIGHT);
+ DOM2GODOT("OSLeft", LEFT);
+ DOM2GODOT("OSRight", RIGHT);
+ DOM2GODOT("ShiftLeft", LEFT);
+ DOM2GODOT("ShiftRight", RIGHT);
+
+ return KeyLocation::UNSPECIFIED;
+#undef DOM2GODOT
+}
diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py
index ec33397842..3ba133c9a1 100644
--- a/platform/web/emscripten_helpers.py
+++ b/platform/web/emscripten_helpers.py
@@ -4,7 +4,12 @@ from SCons.Util import WhereIs
def run_closure_compiler(target, source, env, for_signature):
- closure_bin = os.path.join(os.path.dirname(WhereIs("emcc")), "node_modules", ".bin", "google-closure-compiler")
+ closure_bin = os.path.join(
+ os.path.dirname(WhereIs("emcc")),
+ "node_modules",
+ ".bin",
+ "google-closure-compiler",
+ )
cmd = [WhereIs("node"), closure_bin]
cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"])
for f in env["JSEXTERNS"]:
@@ -31,27 +36,29 @@ def get_build_version():
return v
-def create_engine_file(env, target, source, externs):
+def create_engine_file(env, target, source, externs, threads_enabled):
if env["use_closure_compiler"]:
return env.BuildJS(target, source, JSEXTERNS=externs)
- return env.Textfile(target, [env.File(s) for s in source])
+ subst_dict = {"___GODOT_THREADS_ENABLED": "true" if threads_enabled else "false"}
+ return env.Substfile(target=target, source=[env.File(s) for s in source], SUBST_DICT=subst_dict)
def create_template_zip(env, js, wasm, worker, side):
binary_name = "godot.editor" if env.editor_build else "godot"
- zip_dir = env.Dir("#bin/.web_zip")
+ zip_dir = env.Dir(env.GetTemplateZipPath())
in_files = [
js,
wasm,
- worker,
"#platform/web/js/libs/audio.worklet.js",
]
out_files = [
zip_dir.File(binary_name + ".js"),
zip_dir.File(binary_name + ".wasm"),
- zip_dir.File(binary_name + ".worker.js"),
zip_dir.File(binary_name + ".audio.worklet.js"),
]
+ if env["threads"]:
+ in_files.append(worker)
+ out_files.append(zip_dir.File(binary_name + ".worker.js"))
# Dynamic linking (extensions) specific.
if env["dlink_enabled"]:
in_files.append(side) # Side wasm (contains the actual Godot code).
@@ -65,18 +72,20 @@ def create_template_zip(env, js, wasm, worker, side):
"godot.editor.html",
"offline.html",
"godot.editor.js",
- "godot.editor.worker.js",
"godot.editor.audio.worklet.js",
"logo.svg",
"favicon.png",
]
+ if env["threads"]:
+ cache.append("godot.editor.worker.js")
opt_cache = ["godot.editor.wasm"]
subst_dict = {
- "@GODOT_VERSION@": get_build_version(),
- "@GODOT_NAME@": "GodotEngine",
- "@GODOT_CACHE@": json.dumps(cache),
- "@GODOT_OPT_CACHE@": json.dumps(opt_cache),
- "@GODOT_OFFLINE_PAGE@": "offline.html",
+ "___GODOT_VERSION___": get_build_version(),
+ "___GODOT_NAME___": "GodotEngine",
+ "___GODOT_CACHE___": json.dumps(cache),
+ "___GODOT_OPT_CACHE___": json.dumps(opt_cache),
+ "___GODOT_OFFLINE_PAGE___": "offline.html",
+ "___GODOT_THREADS_ENABLED___": "true" if env["threads"] else "false",
}
html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict)
in_files.append(html)
@@ -88,7 +97,9 @@ def create_template_zip(env, js, wasm, worker, side):
out_files.append(zip_dir.File("favicon.png"))
# PWA
service_worker = env.Substfile(
- target="#bin/godot${PROGSUFFIX}.service.worker.js", source=service_worker, SUBST_DICT=subst_dict
+ target="#bin/godot${PROGSUFFIX}.service.worker.js",
+ source=service_worker,
+ SUBST_DICT=subst_dict,
)
in_files.append(service_worker)
out_files.append(zip_dir.File("service.worker.js"))
@@ -115,6 +126,10 @@ def create_template_zip(env, js, wasm, worker, side):
)
+def get_template_zip_path(env):
+ return "#bin/.web_zip"
+
+
def add_js_libraries(env, libraries):
env.Append(JS_LIBS=env.File(libraries))
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index a70812cf5b..d7e72612b4 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -34,11 +34,11 @@
#include "run_icon_svg.gen.h"
#include "core/config/project_settings.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/export/editor_export.h"
#include "editor/import/resource_importer_texture_settings.h"
+#include "editor/themes/editor_scale.h"
#include "scene/resources/image_texture.h"
#include "modules/modules_enabled.gen.h" // For mono and svg.
@@ -112,7 +112,7 @@ Error EditorExportPlatformWeb::_write_or_error(const uint8_t *p_content, int p_s
return OK;
}
-void EditorExportPlatformWeb::_replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template) {
+void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template) {
String str_template = String::utf8(reinterpret_cast<const char *>(r_template.ptr()), r_template.size());
String out;
Vector<String> lines = str_template.split("\n");
@@ -169,6 +169,13 @@ void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<Edito
replaces["$GODOT_PROJECT_NAME"] = GLOBAL_GET("application/config/name");
replaces["$GODOT_HEAD_INCLUDE"] = head_include + custom_head_include;
replaces["$GODOT_CONFIG"] = str_config;
+
+ if (p_preset->get("variant/thread_support")) {
+ replaces["$GODOT_THREADS_ENABLED"] = "true";
+ } else {
+ replaces["$GODOT_THREADS_ENABLED"] = "false";
+ }
+
_replace_strings(replaces, p_html);
}
@@ -216,9 +223,9 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
const String name = p_path.get_file().get_basename();
bool extensions = (bool)p_preset->get("variant/extensions_support");
HashMap<String, String> replaces;
- 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";
+ 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";
// Files cached during worker install.
Array cache_files;
@@ -231,7 +238,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
}
cache_files.push_back(name + ".worker.js");
cache_files.push_back(name + ".audio.worklet.js");
- replaces["@GODOT_CACHE@"] = Variant(cache_files).to_json_string();
+ replaces["___GODOT_CACHE___"] = Variant(cache_files).to_json_string();
// Heavy files that are cached on demand.
Array opt_cache_files;
@@ -243,7 +250,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
opt_cache_files.push_back(p_shared_objects[i].path.get_file());
}
}
- replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string();
+ replaces["___GODOT_OPT_CACHE___"] = Variant(opt_cache_files).to_json_string();
const String sw_path = dir.path_join(name + ".service.worker.js");
Vector<uint8_t> sw;
@@ -335,6 +342,7 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type.
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/thread_support"), true)); // Thread support (i.e. run with or without COEP/COOP headers).
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer
@@ -377,10 +385,11 @@ bool EditorExportPlatformWeb::has_valid_export_configuration(const Ref<EditorExp
String err;
bool valid = false;
bool extensions = (bool)p_preset->get("variant/extensions_support");
+ bool thread_support = (bool)p_preset->get("variant/thread_support");
// Look for export templates (first official, and if defined custom templates).
- bool dvalid = exists_export_template(_get_template_name(extensions, true), &err);
- bool rvalid = exists_export_template(_get_template_name(extensions, false), &err);
+ bool dvalid = exists_export_template(_get_template_name(extensions, thread_support, true), &err);
+ bool rvalid = exists_export_template(_get_template_name(extensions, thread_support, false), &err);
if (p_preset->get("custom_template/debug") != "") {
dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
@@ -454,7 +463,8 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p
template_path = template_path.strip_edges();
if (template_path.is_empty()) {
bool extensions = (bool)p_preset->get("variant/extensions_support");
- template_path = find_export_template(_get_template_name(extensions, p_debug));
+ bool thread_support = (bool)p_preset->get("variant/thread_support");
+ template_path = find_export_template(_get_template_name(extensions, thread_support, p_debug));
}
if (!template_path.is_empty() && !FileAccess::exists(template_path)) {
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index 887000ac45..98e3fe729e 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -56,11 +56,14 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
Mutex server_lock;
Thread server_thread;
- String _get_template_name(bool p_extension, bool p_debug) const {
+ String _get_template_name(bool p_extension, bool p_thread_support, bool p_debug) const {
String name = "web";
if (p_extension) {
name += "_dlink";
}
+ if (!p_thread_support) {
+ name += "_nothreads";
+ }
if (p_debug) {
name += "_debug.zip";
} else {
@@ -90,7 +93,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
}
Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa);
- void _replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template);
+ void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template);
void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr);
Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects);
diff --git a/platform/web/js/engine/features.js b/platform/web/js/engine/features.js
index b7c6c9d445..81bc82f3c6 100644
--- a/platform/web/js/engine/features.js
+++ b/platform/web/js/engine/features.js
@@ -72,8 +72,14 @@ const Features = { // eslint-disable-line no-unused-vars
*
* @returns {Array<string>} A list of human-readable missing features.
* @function Engine.getMissingFeatures
+ * @typedef {{ threads: boolean }} SupportedFeatures
+ * @param {SupportedFeatures} supportedFeatures
*/
- getMissingFeatures: function () {
+ getMissingFeatures: function (supportedFeatures = {}) {
+ const {
+ threads: supportsThreads = true,
+ } = supportedFeatures;
+
const missing = [];
if (!Features.isWebGLAvailable(2)) {
missing.push('WebGL2 - Check web browser configuration and hardware support');
@@ -84,12 +90,16 @@ const Features = { // eslint-disable-line no-unused-vars
if (!Features.isSecureContext()) {
missing.push('Secure Context - Check web server configuration (use HTTPS)');
}
- if (!Features.isCrossOriginIsolated()) {
- missing.push('Cross Origin Isolation - Check web server configuration (send correct headers)');
- }
- if (!Features.isSharedArrayBufferAvailable()) {
- missing.push('SharedArrayBuffer - Check web server configuration (send correct headers)');
+
+ if (supportsThreads) {
+ if (!Features.isCrossOriginIsolated()) {
+ missing.push('Cross-Origin Isolation - Check that the web server configuration sends the correct headers.');
+ }
+ if (!Features.isSharedArrayBufferAvailable()) {
+ missing.push('SharedArrayBuffer - Check that the web server configuration sends the correct headers.');
+ }
}
+
// Audio is normally optional since we have a dummy fallback.
return missing;
},
diff --git a/platform/web/js/libs/audio.worklet.js b/platform/web/js/libs/audio.worklet.js
index 89b581b3d6..3b94cab85c 100644
--- a/platform/web/js/libs/audio.worklet.js
+++ b/platform/web/js/libs/audio.worklet.js
@@ -167,7 +167,7 @@ class GodotProcessor extends AudioWorkletProcessor {
GodotProcessor.write_input(this.input_buffer, input);
this.input.write(this.input_buffer);
} else {
- this.port.postMessage('Input buffer is full! Skipping input frame.');
+ // this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer.
}
}
const process_output = GodotProcessor.array_has_data(outputs);
@@ -184,7 +184,7 @@ class GodotProcessor extends AudioWorkletProcessor {
this.port.postMessage({ 'cmd': 'read', 'data': chunk });
}
} else {
- this.port.postMessage('Output buffer has not enough frames! Skipping output frame.');
+ // this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer.
}
}
this.process_notify();
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 0549e408a7..7aaf70e625 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -3,6 +3,7 @@
Import("env")
import os
+from pathlib import Path
from platform_methods import run_in_subprocess
import platform_windows_builders
@@ -25,6 +26,19 @@ common_win_wrap = [
"console_wrapper_windows.cpp",
]
+
+def arrange_program_clean(prog):
+ """
+ Given an SCons program, arrange for output files SCons doesn't know about
+ to be cleaned when SCons is called with --clean
+ """
+ extensions_to_clean = [".ilk", ".exp", ".pdb", ".lib"]
+ for program in prog:
+ executable_stem = Path(program.name).stem
+ extra_files_to_clean = [f"#bin/{executable_stem}{extension}" for extension in extensions_to_clean]
+ Clean(prog, extra_files_to_clean)
+
+
res_file = "godot_res.rc"
res_target = "godot_res" + env["OBJSUFFIX"]
res_obj = env.RES(res_target, res_file)
@@ -32,6 +46,7 @@ res_obj = env.RES(res_target, res_file)
sources = common_win + res_obj
prog = env.add_program("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"])
+arrange_program_clean(prog)
# Build console wrapper app.
if env["windows_subsystem"] == "gui":
@@ -48,6 +63,7 @@ if env["windows_subsystem"] == "gui":
env_wrap.Append(LIBS=["version"])
prog_wrap = env_wrap.add_program("#bin/godot", common_win_wrap + res_wrap_obj, PROGSUFFIX=env["PROGSUFFIX_WRAP"])
+ arrange_program_clean(prog_wrap)
env_wrap.Depends(prog_wrap, prog)
# Microsoft Visual Studio Project Generation
@@ -81,7 +97,7 @@ if env["d3d12"]:
arch_bin_dir = "#bin/" + env["arch"]
# DXC
- if env["dxc_path"] != "":
+ if env["dxc_path"] != "" and os.path.exists(env["dxc_path"]):
dxc_dll = "dxil.dll"
# Whether this one is loaded from arch-specific directory or not can be determined at runtime.
# Let's copy to both and let the user decide the distribution model.
@@ -93,7 +109,7 @@ if env["d3d12"]:
)
# Agility SDK
- if env["agility_sdk_path"] != "":
+ if env["agility_sdk_path"] != "" and os.path.exists(env["agility_sdk_path"]):
agility_dlls = ["D3D12Core.dll", "d3d12SDKLayers.dll"]
# Whether these are loaded from arch-specific directory or not has to be known at build time.
target_dir = arch_bin_dir if env["agility_sdk_multiarch"] else "#bin"
@@ -105,7 +121,7 @@ if env["d3d12"]:
)
# PIX
- if env["pix_path"] != "":
+ if env["use_pix"]:
pix_dll = "WinPixEventRuntime.dll"
env.Command(
"#bin/" + pix_dll,
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 801b32140a..e5e0a07f82 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -164,6 +164,22 @@ def get_opts():
mingw = os.getenv("MINGW_PREFIX", "")
+ # Direct3D 12 SDK dependencies folder.
+ d3d12_deps_folder = os.getenv("LOCALAPPDATA")
+ if d3d12_deps_folder:
+ d3d12_deps_folder = os.path.join(d3d12_deps_folder, "Godot", "build_deps")
+ else:
+ # Cross-compiling, the deps install script puts things in `bin`.
+ # Getting an absolute path to it is a bit hacky in Python.
+ try:
+ import inspect
+
+ caller_frame = inspect.stack()[1]
+ caller_script_dir = os.path.dirname(os.path.abspath(caller_frame[1]))
+ d3d12_deps_folder = os.path.join(caller_script_dir, "bin", "build_deps")
+ except: # Give up.
+ d3d12_deps_folder = ""
+
return [
("mingw_prefix", "MinGW prefix", mingw),
# Targeted Windows version: 7 (and later), minimum supported version
@@ -188,15 +204,32 @@ def get_opts():
BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False),
("angle_libs", "Path to the ANGLE static libraries", ""),
# Direct3D 12 support.
- ("mesa_libs", "Path to the MESA/NIR static libraries (required for D3D12)", ""),
- ("dxc_path", "Path to the DirectX Shader Compiler distribution (required for D3D12)", ""),
- ("agility_sdk_path", "Path to the Agility SDK distribution (optional for D3D12)", ""),
+ (
+ "mesa_libs",
+ "Path to the MESA/NIR static libraries (required for D3D12)",
+ os.path.join(d3d12_deps_folder, "mesa"),
+ ),
+ (
+ "dxc_path",
+ "Path to the DirectX Shader Compiler distribution (required for D3D12)",
+ os.path.join(d3d12_deps_folder, "dxc"),
+ ),
+ (
+ "agility_sdk_path",
+ "Path to the Agility SDK distribution (optional for D3D12)",
+ os.path.join(d3d12_deps_folder, "agility_sdk"),
+ ),
BoolVariable(
"agility_sdk_multiarch",
"Whether the Agility SDK DLLs will be stored in arch-specific subdirectories",
False,
),
- ("pix_path", "Path to the PIX runtime distribution (optional for D3D12)", ""),
+ BoolVariable("use_pix", "Use PIX (Performance tuning and debugging for DirectX 12) runtime", False),
+ (
+ "pix_path",
+ "Path to the PIX runtime distribution (optional for D3D12)",
+ os.path.join(d3d12_deps_folder, "pix"),
+ ),
]
@@ -436,16 +469,22 @@ def configure_msvc(env, vcvars_msvc_config):
LIBS += ["psapi", "dbghelp"]
if env["vulkan"]:
- env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED"])
+ env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
if not env["use_volk"]:
LIBS += ["vulkan"]
if env["d3d12"]:
- if env["dxc_path"] == "":
- print("The Direct3D 12 rendering driver requires dxc_path to be set.")
+ # Check whether we have d3d12 dependencies installed.
+ if not os.path.exists(env["mesa_libs"]):
+ print("The Direct3D 12 rendering driver requires dependencies to be installed.")
+ print("You can install them by running `python misc\scripts\install_d3d12_sdk_windows.py`.")
+ print("See the documentation for more information:")
+ print(
+ "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
+ )
sys.exit(255)
- env.AppendUnique(CPPDEFINES=["D3D12_ENABLED"])
+ env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
LIBS += ["d3d12", "dxgi", "dxguid"]
LIBS += ["version"] # Mesa dependency.
@@ -453,18 +492,16 @@ def configure_msvc(env, vcvars_msvc_config):
if env["target"] == "release_debug":
env.Append(CXXFLAGS=["/bigobj"])
- arch_subdir = "arm64" if env["arch"] == "arm64" else "x64"
-
# PIX
- if env["pix_path"] != "":
+ if not env["arch"] in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]):
+ env["use_pix"] = False
+
+ if env["use_pix"]:
+ arch_subdir = "arm64" if env["arch"] == "arm64" else "x64"
+
env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir])
LIBS += ["WinPixEventRuntime"]
- # Mesa
- if env["mesa_libs"] == "":
- print("The Direct3D 12 rendering driver requires mesa_libs to be set.")
- sys.exit(255)
-
env.Append(LIBPATH=[env["mesa_libs"] + "/bin"])
LIBS += ["libNIR.windows." + env["arch"]]
@@ -657,26 +694,34 @@ def configure_mingw(env):
env.Append(LIBS=["psapi", "dbghelp"])
if env["vulkan"]:
- env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
if not env["use_volk"]:
env.Append(LIBS=["vulkan"])
if env["d3d12"]:
- env.AppendUnique(CPPDEFINES=["D3D12_ENABLED"])
- env.Append(LIBS=["d3d12", "dxgi", "dxguid"])
+ # Check whether we have d3d12 dependencies installed.
+ if not os.path.exists(env["mesa_libs"]):
+ print("The Direct3D 12 rendering driver requires dependencies to be installed.")
+ print("You can install them by running `python misc\scripts\install_d3d12_sdk_windows.py`.")
+ print("See the documentation for more information:")
+ print(
+ "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
+ )
+ sys.exit(255)
- arch_subdir = "arm64" if env["arch"] == "arm64" else "x64"
+ env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
+ env.Append(LIBS=["d3d12", "dxgi", "dxguid"])
# PIX
- if env["pix_path"] != "":
+ if not env["arch"] in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]):
+ env["use_pix"] = False
+
+ if env["use_pix"]:
+ arch_subdir = "arm64" if env["arch"] == "arm64" else "x64"
+
env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir])
env.Append(LIBS=["WinPixEventRuntime"])
- # Mesa
- if env["mesa_libs"] == "":
- print("The Direct3D 12 rendering driver requires mesa_libs to be set.")
- sys.exit(255)
-
env.Append(LIBPATH=[env["mesa_libs"] + "/bin"])
env.Append(LIBS=["libNIR.windows." + env["arch"]])
env.Append(LIBS=["version"]) # Mesa dependency.
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 2bec804c7c..657caa7939 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -52,6 +52,10 @@
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
+#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1
+#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
+#endif
+
#if defined(__GNUC__)
// Workaround GCC warning from -Wcast-function-type.
#define GetProcAddress (void *)GetProcAddress
@@ -165,20 +169,26 @@ DisplayServer::WindowID DisplayServerWindows::_get_focused_window_or_popup() con
void DisplayServerWindows::_register_raw_input_devices(WindowID p_target_window) {
use_raw_input = true;
- RAWINPUTDEVICE rid[1] = {};
- rid[0].usUsagePage = 0x01;
- rid[0].usUsage = 0x02;
+ RAWINPUTDEVICE rid[2] = {};
+ rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
+ rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
rid[0].dwFlags = 0;
+ rid[1].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
+ rid[1].usUsage = 0x06; // HID_USAGE_GENERIC_KEYBOARD
+ rid[1].dwFlags = 0;
+
if (p_target_window != INVALID_WINDOW_ID && windows.has(p_target_window)) {
// Follow the defined window
rid[0].hwndTarget = windows[p_target_window].hWnd;
+ rid[1].hwndTarget = windows[p_target_window].hWnd;
} else {
// Follow the keyboard focus
rid[0].hwndTarget = 0;
+ rid[1].hwndTarget = 0;
}
- if (RegisterRawInputDevices(rid, 1, sizeof(rid[0])) == FALSE) {
+ if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE) {
// Registration failed.
use_raw_input = false;
}
@@ -219,7 +229,137 @@ void DisplayServerWindows::tts_stop() {
tts->stop();
}
+// Silence warning due to a COM API weirdness.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+class FileDialogEventHandler : public IFileDialogEvents, public IFileDialogControlEvents {
+ LONG ref_count = 1;
+ int ctl_id = 1;
+
+ HashMap<int, String> ctls;
+ Dictionary selected;
+ String root;
+
+public:
+ // IUnknown methods
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) {
+ static const QITAB qit[] = {
+ QITABENT(FileDialogEventHandler, IFileDialogEvents),
+ QITABENT(FileDialogEventHandler, IFileDialogControlEvents),
+ { 0, 0 },
+ };
+ return QISearch(this, qit, riid, ppv);
+ }
+
+ ULONG STDMETHODCALLTYPE AddRef() {
+ return InterlockedIncrement(&ref_count);
+ }
+
+ ULONG STDMETHODCALLTYPE Release() {
+ long ref = InterlockedDecrement(&ref_count);
+ if (!ref) {
+ delete this;
+ }
+ return ref;
+ }
+
+ // IFileDialogEvents methods
+ HRESULT STDMETHODCALLTYPE OnFileOk(IFileDialog *) { return S_OK; };
+ HRESULT STDMETHODCALLTYPE OnFolderChange(IFileDialog *) { return S_OK; };
+
+ HRESULT STDMETHODCALLTYPE OnFolderChanging(IFileDialog *p_pfd, IShellItem *p_item) {
+ if (root.is_empty()) {
+ return S_OK;
+ }
+
+ LPWSTR lpw_path = nullptr;
+ p_item->GetDisplayName(SIGDN_FILESYSPATH, &lpw_path);
+ if (!lpw_path) {
+ return S_FALSE;
+ }
+ String path = String::utf16((const char16_t *)lpw_path).simplify_path();
+ if (!path.begins_with(root.simplify_path())) {
+ return S_FALSE;
+ }
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnHelp(IFileDialog *) { return S_OK; };
+ HRESULT STDMETHODCALLTYPE OnSelectionChange(IFileDialog *) { return S_OK; };
+ HRESULT STDMETHODCALLTYPE OnShareViolation(IFileDialog *, IShellItem *, FDE_SHAREVIOLATION_RESPONSE *) { return S_OK; };
+ HRESULT STDMETHODCALLTYPE OnTypeChange(IFileDialog *pfd) { return S_OK; };
+ HRESULT STDMETHODCALLTYPE OnOverwrite(IFileDialog *, IShellItem *, FDE_OVERWRITE_RESPONSE *) { return S_OK; };
+
+ // IFileDialogControlEvents methods
+ HRESULT STDMETHODCALLTYPE OnItemSelected(IFileDialogCustomize *p_pfdc, DWORD p_ctl_id, DWORD p_item_idx) {
+ if (ctls.has(p_ctl_id)) {
+ selected[ctls[p_ctl_id]] = (int)p_item_idx;
+ }
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnButtonClicked(IFileDialogCustomize *, DWORD) { return S_OK; };
+ HRESULT STDMETHODCALLTYPE OnCheckButtonToggled(IFileDialogCustomize *p_pfdc, DWORD p_ctl_id, BOOL p_checked) {
+ if (ctls.has(p_ctl_id)) {
+ selected[ctls[p_ctl_id]] = (bool)p_checked;
+ }
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE OnControlActivating(IFileDialogCustomize *, DWORD) { return S_OK; };
+
+ Dictionary get_selected() {
+ return selected;
+ }
+
+ void set_root(const String &p_root) {
+ root = p_root;
+ }
+
+ void add_option(IFileDialogCustomize *p_pfdc, const String &p_name, const Vector<String> &p_options, int p_default) {
+ int gid = ctl_id++;
+ int cid = ctl_id++;
+
+ if (p_options.size() == 0) {
+ // Add check box.
+ p_pfdc->StartVisualGroup(gid, L"");
+ p_pfdc->AddCheckButton(cid, (LPCWSTR)p_name.utf16().get_data(), p_default);
+ p_pfdc->SetControlState(cid, CDCS_VISIBLE | CDCS_ENABLED);
+ p_pfdc->EndVisualGroup();
+ selected[p_name] = (bool)p_default;
+ } else {
+ // Add combo box.
+ p_pfdc->StartVisualGroup(gid, (LPCWSTR)p_name.utf16().get_data());
+ p_pfdc->AddComboBox(cid);
+ p_pfdc->SetControlState(cid, CDCS_VISIBLE | CDCS_ENABLED);
+ for (int i = 0; i < p_options.size(); i++) {
+ p_pfdc->AddControlItem(cid, i, (LPCWSTR)p_options[i].utf16().get_data());
+ }
+ p_pfdc->SetSelectedControlItem(cid, p_default);
+ p_pfdc->EndVisualGroup();
+ selected[p_name] = p_default;
+ }
+ ctls[cid] = p_name;
+ }
+
+ virtual ~FileDialogEventHandler(){};
+};
+
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+}
+
+Error DisplayServerWindows::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true);
+}
+
+Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
_THREAD_SAFE_METHOD_
ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
@@ -269,6 +409,31 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void **)&pfd);
}
if (SUCCEEDED(hr)) {
+ IFileDialogEvents *pfde = nullptr;
+ FileDialogEventHandler *event_handler = new FileDialogEventHandler();
+ hr = event_handler->QueryInterface(IID_PPV_ARGS(&pfde));
+
+ DWORD cookie = 0;
+ hr = pfd->Advise(pfde, &cookie);
+
+ IFileDialogCustomize *pfdc = nullptr;
+ hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc));
+
+ for (int i = 0; i < p_options.size(); i++) {
+ const Dictionary &item = p_options[i];
+ if (!item.has("name") || !item.has("values") || !item.has("default")) {
+ continue;
+ }
+ const String &name = item["name"];
+ const Vector<String> &options = item["values"];
+ int default_idx = item["default"];
+
+ event_handler->add_option(pfdc, name, options, default_idx);
+ }
+ event_handler->set_root(p_root);
+
+ pfdc->Release();
+
DWORD flags;
pfd->GetOptions(&flags);
if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) {
@@ -306,8 +471,18 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
}
hr = pfd->Show(windows[window_id].hWnd);
+ pfd->Unadvise(cookie);
+
+ Dictionary options = event_handler->get_selected();
+
+ pfde->Release();
+ event_handler->Release();
+
UINT index = 0;
pfd->GetFileTypeIndex(&index);
+ if (index > 0) {
+ index = index - 1;
+ }
if (SUCCEEDED(hr)) {
Vector<String> file_names;
@@ -346,30 +521,60 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
}
}
if (!p_callback.is_null()) {
- Variant v_result = true;
- Variant v_files = file_names;
- Variant v_index = index;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- p_callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = true;
+ Variant v_files = file_names;
+ Variant v_index = index;
+ Variant v_opt = options;
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ p_callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = true;
+ Variant v_files = file_names;
+ Variant v_index = index;
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ p_callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce)));
+ }
}
}
} else {
if (!p_callback.is_null()) {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = index;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
-
- p_callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce)));
+ if (p_options_in_cb) {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = index;
+ Variant v_opt = options;
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ p_callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 4, ce)));
+ }
+ } else {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = index;
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &v_result, &v_files, &v_index };
+
+ p_callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce)));
+ }
}
}
}
@@ -687,6 +892,8 @@ typedef struct {
} EnumRectData;
typedef struct {
+ Vector<DISPLAYCONFIG_PATH_INFO> paths;
+ Vector<DISPLAYCONFIG_MODE_INFO> modes;
int count;
int screen;
float rate;
@@ -738,12 +945,30 @@ static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonit
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);
+ bool found = false;
+ for (const DISPLAYCONFIG_PATH_INFO &path : data->paths) {
+ DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name;
+ memset(&source_name, 0, sizeof(source_name));
+ source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
+ source_name.header.size = sizeof(source_name);
+ source_name.header.adapterId = path.sourceInfo.adapterId;
+ source_name.header.id = path.sourceInfo.id;
+ if (DisplayConfigGetDeviceInfo(&source_name.header) == ERROR_SUCCESS) {
+ if (wcscmp(minfo.szDevice, source_name.viewGdiDeviceName) == 0 && path.targetInfo.refreshRate.Numerator != 0 && path.targetInfo.refreshRate.Denominator != 0) {
+ data->rate = (double)path.targetInfo.refreshRate.Numerator / (double)path.targetInfo.refreshRate.Denominator;
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ DEVMODEW dm;
+ memset(&dm, 0, sizeof(dm));
+ dm.dmSize = sizeof(dm);
+ EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm);
- data->rate = dm.dmDisplayFrequency;
+ data->rate = dm.dmDisplayFrequency;
+ }
}
data->count++;
@@ -932,7 +1157,19 @@ float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_
p_screen = _get_screen_index(p_screen);
- EnumRefreshRateData data = { 0, p_screen, SCREEN_REFRESH_RATE_FALLBACK };
+ EnumRefreshRateData data = { Vector<DISPLAYCONFIG_PATH_INFO>(), Vector<DISPLAYCONFIG_MODE_INFO>(), 0, p_screen, SCREEN_REFRESH_RATE_FALLBACK };
+
+ uint32_t path_count = 0;
+ uint32_t mode_count = 0;
+ if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) == ERROR_SUCCESS) {
+ data.paths.resize(path_count);
+ data.modes.resize(mode_count);
+ if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_count, data.paths.ptrw(), &mode_count, data.modes.ptrw(), nullptr) != ERROR_SUCCESS) {
+ data.paths.clear();
+ data.modes.clear();
+ }
+ }
+
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data);
return data.rate;
}
@@ -1916,6 +2153,10 @@ bool DisplayServerWindows::window_is_focused(WindowID p_window) const {
return wd.window_focused;
}
+DisplayServerWindows::WindowID DisplayServerWindows::get_focused_window() const {
+ return last_focused_window;
+}
+
bool DisplayServerWindows::window_can_draw(WindowID p_window) const {
_THREAD_SAFE_METHOD_
@@ -2927,6 +3168,14 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
// Process window messages.
switch (uMsg) {
+ case WM_CREATE: {
+ if (is_dark_mode_supported() && dark_title_available) {
+ BOOL value = is_dark_mode();
+
+ ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
+ SendMessageW(windows[window_id].hWnd, WM_PAINT, 0, 0);
+ }
+ } break;
case WM_NCPAINT: {
if (RenderingServer::get_singleton() && (windows[window_id].borderless || (windows[window_id].fullscreen && windows[window_id].multiwindow_fs))) {
Color color = RenderingServer::get_singleton()->get_default_clear_color();
@@ -3059,14 +3308,14 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
if (lParam && CompareStringOrdinal(reinterpret_cast<LPCWCH>(lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL) {
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
- ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
+ ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
}
}
} break;
case WM_THEMECHANGED: {
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
- ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
+ ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
}
} break;
case WM_SYSCOMMAND: // Intercept system commands.
@@ -3105,7 +3354,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WM_INPUT: {
- if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) {
+ if (!use_raw_input) {
break;
}
@@ -3123,7 +3372,32 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
RAWINPUT *raw = (RAWINPUT *)lpb;
- if (raw->header.dwType == RIM_TYPEMOUSE) {
+ if (raw->header.dwType == RIM_TYPEKEYBOARD) {
+ if (raw->data.keyboard.VKey == VK_SHIFT) {
+ // If multiple Shifts are held down at the same time,
+ // Windows natively only sends a KEYUP for the last one to be released.
+ if (raw->data.keyboard.Flags & RI_KEY_BREAK) {
+ if (GetAsyncKeyState(VK_SHIFT) < 0) {
+ // A Shift is released, but another Shift is still held
+ ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
+
+ KeyEvent ke;
+ ke.shift = false;
+ ke.alt = alt_mem;
+ ke.control = control_mem;
+ ke.meta = meta_mem;
+ ke.uMsg = WM_KEYUP;
+ ke.window_id = window_id;
+
+ ke.wParam = VK_SHIFT;
+ // data.keyboard.MakeCode -> 0x2A - left shift, 0x36 - right shift.
+ // Bit 30 -> key was previously down, bit 31 -> key is being released.
+ ke.lParam = raw->data.keyboard.MakeCode << 16 | 1 << 30 | 1 << 31;
+ key_event_buffer[key_event_pos++] = ke;
+ }
+ }
+ }
+ } else if (mouse_mode == MOUSE_MODE_CAPTURED && raw->header.dwType == RIM_TYPEMOUSE) {
Ref<InputEventMouseMotion> mm;
mm.instantiate();
@@ -4128,6 +4402,7 @@ void DisplayServerWindows::_process_key_events() {
}
Key key_label = keycode;
Key physical_keycode = KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24));
+ KeyLocation location = KeyMappingWindows::get_location((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24));
static BYTE keyboard_state[256];
memset(keyboard_state, 0, 256);
@@ -4154,6 +4429,7 @@ void DisplayServerWindows::_process_key_events() {
}
k->set_keycode(keycode);
k->set_physical_keycode(physical_keycode);
+ k->set_location(location);
k->set_key_label(key_label);
if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) {
@@ -4319,7 +4595,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
- ::DwmSetWindowAttribute(wd.hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
+ ::DwmSetWindowAttribute(wd.hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
}
#ifdef RD_ENABLED
@@ -4457,6 +4733,7 @@ WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr;
// UXTheme API.
bool DisplayServerWindows::dark_title_available = false;
+bool DisplayServerWindows::use_legacy_dark_mode_before_20H1 = false;
bool DisplayServerWindows::ux_theme_available = false;
ShouldAppsUseDarkModePtr DisplayServerWindows::ShouldAppsUseDarkMode = nullptr;
GetImmersiveColorFromColorSetExPtr DisplayServerWindows::GetImmersiveColorFromColorSetEx = nullptr;
@@ -4578,8 +4855,11 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
GetImmersiveUserColorSetPreference = (GetImmersiveUserColorSetPreferencePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(98));
ux_theme_available = ShouldAppsUseDarkMode && GetImmersiveColorFromColorSetEx && GetImmersiveColorTypeFromName && GetImmersiveUserColorSetPreference;
- if (os_ver.dwBuildNumber >= 22000) {
+ if (os_ver.dwBuildNumber >= 18363) {
dark_title_available = true;
+ if (os_ver.dwBuildNumber < 19041) {
+ use_legacy_dark_mode_before_20H1 = true;
+ }
}
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 29c2460c10..4e1d2cf85c 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -294,6 +294,7 @@ class DisplayServerWindows : public DisplayServer {
// UXTheme API
static bool dark_title_available;
+ static bool use_legacy_dark_mode_before_20H1;
static bool ux_theme_available;
static ShouldAppsUseDarkModePtr ShouldAppsUseDarkMode;
static GetImmersiveColorFromColorSetExPtr GetImmersiveColorFromColorSetEx;
@@ -497,6 +498,8 @@ class DisplayServerWindows : public DisplayServer {
LRESULT _handle_early_window_message(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
Point2i _get_screens_origin() const;
+ Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb);
+
public:
LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam);
@@ -521,6 +524,7 @@ public:
virtual Color get_accent_color() const override;
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
+ virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override;
virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const override;
@@ -611,6 +615,8 @@ public:
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual WindowID get_focused_window() const override;
+
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool can_any_window_draw() const override;
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 418f38c127..6cdd370bfe 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -37,9 +37,9 @@
#include "core/io/image_loader.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
-#include "editor/editor_scale.h"
#include "editor/editor_string_names.h"
#include "editor/export/editor_export.h"
+#include "editor/themes/editor_scale.h"
#include "modules/modules_enabled.gen.h" // For svg.
#ifdef MODULE_SVG_ENABLED
diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis
index 36b0919185..94cc52be98 100644
--- a/platform/windows/godot.natvis
+++ b/platform/windows/godot.natvis
@@ -5,11 +5,43 @@
<Item Name="[size]">_cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0</Item>
<ArrayItems>
<Size>_cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0</Size>
- <ValuePointer>_cowdata._ptr</ValuePointer>
+ <ValuePointer>($T1 *) _cowdata._ptr</ValuePointer>
</ArrayItems>
</Expand>
</Type>
+ <Type Name="Array">
+ <Expand>
+ <Item Name="[size]">_p->array._cowdata._ptr ? (((const unsigned int *)(_p->array._cowdata._ptr))[-1]) : 0</Item>
+ <ArrayItems>
+ <Size>_p->array._cowdata._ptr ? (((const unsigned int *)(_p->array._cowdata._ptr))[-1]) : 0</Size>
+ <ValuePointer>(Variant *) _p->array._cowdata._ptr</ValuePointer>
+ </ArrayItems>
+ </Expand>
+ </Type>
+
+ <Type Name="TypedArray&lt;*&gt;">
+ <Expand>
+ <Item Name="[size]"> _p->array._cowdata._ptr ? (((const unsigned int *)(_p->array._cowdata._ptr))[-1]) : 0</Item>
+ <ArrayItems>
+ <Size>_p->array._cowdata._ptr ? (((const unsigned int *)(_p->array._cowdata._ptr))[-1]) : 0</Size>
+ <ValuePointer >(Variant *) _p->array._cowdata._ptr</ValuePointer>
+ </ArrayItems>
+ </Expand>
+ </Type>
+
+ <Type Name="Dictionary">
+ <Expand>
+ <Item Name="[size]">_p &amp;&amp; _p->variant_map.head_element ? _p->variant_map.num_elements : 0</Item>
+ <LinkedListItems>
+ <Size>_p &amp;&amp; _p->variant_map.head_element ? _p->variant_map.num_elements : 0</Size>
+ <HeadPointer>_p ? _p->variant_map.head_element : nullptr</HeadPointer>
+ <NextPointer>next</NextPointer>
+ <ValueNode Name="[{data.key}]">(*this),view(MapHelper)</ValueNode>
+ </LinkedListItems>
+ </Expand>
+ </Type>
+
<Type Name="LocalVector&lt;*&gt;">
<Expand>
<Item Name="[size]">count</Item>
@@ -32,18 +64,80 @@
</Expand>
</Type>
- <Type Name="HashMap&lt;*,*&gt;">
+ <Type Name="NodePath">
+ <DisplayString Condition="!data">[empty]</DisplayString>
+ <DisplayString Condition="!!data">{{[absolute] = {data->absolute} [path] = {data->path,view(NodePathHelper)} [subpath] = {data->subpath,view(NodePathHelper)}}}</DisplayString>
<Expand>
- <Item Name="[size]">num_elements</Item>
+ <Item Name="[path]">data->path,view(NodePathHelper)</Item>
+ <Item Name="[subpath]">data->subpath,view(NodePathHelper)</Item>
+ <Item Name="[absolute]">data->absolute</Item>
+ </Expand>
+ </Type>
+
+ <Type Name="Vector&lt;StringName&gt;" IncludeView="NodePathHelper">
+ <Expand>
+ <ArrayItems>
+ <Size>_cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0</Size>
+ <ValuePointer>((StringName *)_cowdata._ptr),view(NodePathHelper)</ValuePointer>
+ </ArrayItems>
+ </Expand>
+ </Type>
+
+ <Type Name="StringName" IncludeView="NodePathHelper">
+ <DisplayString Condition="_data &amp;&amp; _data->cname">{_data->cname,s8b}</DisplayString>
+ <DisplayString Condition="_data &amp;&amp; !_data->cname">{_data->name,s32b}</DisplayString>
+ <DisplayString Condition="!_data">[empty]</DisplayString>
+ <StringView Condition="_data &amp;&amp; _data->cname">_data->cname,s8b</StringView>
+ <StringView Condition="_data &amp;&amp; !_data->cname">_data->name,s32b</StringView>
+ </Type>
+
+ <Type Name="HashMapElement&lt;*,*&gt;">
+ <DisplayString>{{Key = {($T1 *) &amp;data.key} Value = {($T2 *) &amp;data.value}}}</DisplayString>
+ <Expand>
+ <Item Name="[key]">($T1 *) &amp;data.key</Item>
+ <Item Name="[value]">($T2 *) &amp;data.value</Item>
+ </Expand>
+ </Type>
+
+ <!-- elements displayed by index -->
+ <Type Name="HashMap&lt;*,*,*,*,*&gt;" Priority="Medium">
+ <Expand>
+ <Item Name="[size]">head_element ? num_elements : 0</Item>
<LinkedListItems>
- <Size>num_elements</Size>
+ <Size>head_element ? num_elements : 0</Size>
<HeadPointer>head_element</HeadPointer>
<NextPointer>next</NextPointer>
- <ValueNode>data</ValueNode>
+ <ValueNode>(*this)</ValueNode>
</LinkedListItems>
</Expand>
</Type>
+ <!-- elements by key:value -->
+ <!-- show elements by index by specifying "ShowElementsByIndex"-->
+ <Type Name="HashMap&lt;*,*,*,*,*&gt;" ExcludeView="ShowElementsByIndex" Priority="MediumHigh">
+ <Expand>
+ <Item Name="[size]">head_element ? num_elements : 0</Item>
+ <LinkedListItems>
+ <Size>head_element ? num_elements : 0</Size>
+ <HeadPointer>head_element</HeadPointer>
+ <NextPointer>next</NextPointer>
+ <ValueNode Name="[{data.key}]">(*this),view(MapHelper)</ValueNode>
+ </LinkedListItems>
+ </Expand>
+ </Type>
+
+ <Type Name="KeyValue&lt;*,*&gt;" IncludeView="MapHelper">
+ <DisplayString>{value}</DisplayString>
+ </Type>
+
+ <Type Name="HashMapElement&lt;*,*&gt;" IncludeView="MapHelper">
+ <DisplayString>{data.value}</DisplayString>
+ <Expand>
+ <Item Name="[key]" >($T1 *) &amp;data.key</Item>
+ <Item Name="[value]">($T2 *) &amp;data.value</Item>
+ </Expand>
+ </Type>
+
<Type Name="VMap&lt;*,*&gt;">
<Expand>
<Item Condition="_cowdata._ptr" Name="[size]">*(reinterpret_cast&lt;int*&gt;(_cowdata._ptr) - 1)</Item>
@@ -58,11 +152,6 @@
<DisplayString Condition="dynamic_cast&lt;CallableCustomMethodPointerBase*&gt;(key.custom)">{dynamic_cast&lt;CallableCustomMethodPointerBase*&gt;(key.custom)->text}</DisplayString>
</Type>
- <!-- requires PR 64364
- <Type Name="GDScriptThreadContext">
- <DisplayString Condition="_is_main == true">main thread {_debug_thread_id}</DisplayString>
- </Type>
- -->
<Type Name="Variant">
<DisplayString Condition="type == Variant::NIL">nil</DisplayString>
@@ -82,22 +171,22 @@
<DisplayString Condition="type == Variant::PLANE">{*(Plane *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::QUATERNION">{*(Quaternion *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::COLOR">{*(Color *)_data._mem}</DisplayString>
+ <DisplayString Condition="type == Variant::STRING_NAME">{*(StringName *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::NODE_PATH">{*(NodePath *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::RID">{*(::RID *)_data._mem}</DisplayString>
- <DisplayString Condition="type == Variant::OBJECT">{*(Object *)_data._mem}</DisplayString>
+ <DisplayString Condition="type == Variant::OBJECT">{*(*reinterpret_cast&lt;ObjData*&gt;(&amp;_data._mem[0])).obj}</DisplayString>
<DisplayString Condition="type == Variant::DICTIONARY">{*(Dictionary *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::ARRAY">{*(Array *)_data._mem}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_BYTE_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;unsigned char&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_INT32_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;int&gt;*&gt;(_data.packed_array)->array}</DisplayString>
- <!-- broken, will show incorrect data
- <DisplayString Condition="type == Variant::PACKED_INT64_ARRAY">{*(PackedInt64Array *)_data._mem}</DisplayString>
- -->
+ <DisplayString Condition="type == Variant::PACKED_INT64_ARRAY">{*reinterpret_cast&lt;PackedInt64Array *&gt;(&amp;_data.packed_array[1])}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_FLOAT32_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;float&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_FLOAT64_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;double&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_STRING_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;String&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_VECTOR2_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Vector2&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_VECTOR3_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Vector3&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_COLOR_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Color&gt;*&gt;(_data.packed_array)->array}</DisplayString>
+ <DisplayString Condition="type &lt; 0 || type >= Variant::VARIANT_MAX">[INVALID]</DisplayString>
<StringView Condition="type == Variant::STRING &amp;&amp; ((String *)(_data._mem))->_cowdata._ptr">((String *)(_data._mem))->_cowdata._ptr,s32</StringView>
@@ -116,20 +205,22 @@
<Item Name="[value]" Condition="type == Variant::PLANE">*(Plane *)_data._mem</Item>
<Item Name="[value]" Condition="type == Variant::QUATERNION">*(Quaternion *)_data._mem</Item>
<Item Name="[value]" Condition="type == Variant::COLOR">*(Color *)_data._mem</Item>
+ <Item Name="[value]" Condition="type == Variant::STRING_NAME">*(StringName *)_data._mem</Item>
<Item Name="[value]" Condition="type == Variant::NODE_PATH">*(NodePath *)_data._mem</Item>
<Item Name="[value]" Condition="type == Variant::RID">*(::RID *)_data._mem</Item>
- <Item Name="[value]" Condition="type == Variant::OBJECT">*(Object *)_data._mem</Item>
+ <Item Name="[value]" Condition="type == Variant::OBJECT">*(*reinterpret_cast&lt;ObjData*&gt;(&amp;_data._mem[0])).obj</Item>
<Item Name="[value]" Condition="type == Variant::DICTIONARY">*(Dictionary *)_data._mem</Item>
<Item Name="[value]" Condition="type == Variant::ARRAY">*(Array *)_data._mem</Item>
<Item Name="[value]" Condition="type == Variant::PACKED_BYTE_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;unsigned char&gt;*&gt;(_data.packed_array)->array</Item>
- <Item Name="[value]" Condition="type == Variant::PACKED_INT32_ARRAY">*(PackedInt32Array *)_data._mem</Item>
- <Item Name="[value]" Condition="type == Variant::PACKED_INT64_ARRAY">*(PackedInt64Array *)_data._mem</Item>
- <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT32_ARRAY">*(PackedFloat32Array *)_data._mem</Item>
- <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT64_ARRAY">*(PackedFloat64Array *)_data._mem</Item>
- <Item Name="[value]" Condition="type == Variant::PACKED_STRING_ARRAY">*(PackedStringArray *)_data._mem</Item>
- <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR2_ARRAY">*(PackedVector2Array *)_data._mem</Item>
- <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR3_ARRAY">*(PackedVector3Array *)_data._mem</Item>
- <Item Name="[value]" Condition="type == Variant::PACKED_COLOR_ARRAY">*(PackedColorArray *)_data._mem</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_INT32_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;int&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_INT64_ARRAY">*reinterpret_cast&lt;PackedInt64Array *&gt;(&amp;_data.packed_array[1])</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT32_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;float&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT64_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;double&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_STRING_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;String&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR2_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Vector2&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR3_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Vector3&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_COLOR_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Color&gt;*&gt;(_data.packed_array)->array</Item>
+
</Expand>
</Type>
@@ -148,10 +239,10 @@
</Type>
<Type Name="StringName">
- <DisplayString Condition="_data &amp;&amp; _data->cname">{_data->cname}</DisplayString>
+ <DisplayString Condition="_data &amp;&amp; _data->cname">{_data->cname,na}</DisplayString>
<DisplayString Condition="_data &amp;&amp; !_data->cname">{_data->name,s32}</DisplayString>
<DisplayString Condition="!_data">[empty]</DisplayString>
- <StringView Condition="_data &amp;&amp; _data->cname">_data->cname</StringView>
+ <StringView Condition="_data &amp;&amp; _data->cname">_data->cname,na</StringView>
<StringView Condition="_data &amp;&amp; !_data->cname">_data->name,s32</StringView>
</Type>
diff --git a/platform/windows/godot_res.rc b/platform/windows/godot_res.rc
index 0593c8b069..8187c0c936 100644
--- a/platform/windows/godot_res.rc
+++ b/platform/windows/godot_res.rc
@@ -1,16 +1,12 @@
#include "core/version.h"
-#ifndef _STR
-#define _STR(m_x) #m_x
-#define _MKSTR(m_x) _STR(m_x)
-#endif
GODOT_ICON ICON platform/windows/godot.ico
1 VERSIONINFO
-FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
-PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
-FILEOS 4
-FILETYPE 1
+FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
+PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
+FILEOS 4
+FILETYPE 1
BEGIN
BLOCK "StringFileInfo"
BEGIN
@@ -21,7 +17,7 @@ BEGIN
VALUE "FileVersion", VERSION_NUMBER
VALUE "ProductName", VERSION_NAME
VALUE "Licence", "MIT"
- VALUE "LegalCopyright", "Copyright (c) 2007-" _MKSTR(VERSION_YEAR) " Juan Linietsky, Ariel Manzur and contributors"
+ VALUE "LegalCopyright", "(c) 2007-present Juan Linietsky, Ariel Manzur and Godot Engine contributors"
VALUE "Info", "https://godotengine.org"
VALUE "ProductVersion", VERSION_FULL_BUILD
END
diff --git a/platform/windows/godot_res_wrap.rc b/platform/windows/godot_res_wrap.rc
index 9dd29afe51..27ad26cbc5 100644
--- a/platform/windows/godot_res_wrap.rc
+++ b/platform/windows/godot_res_wrap.rc
@@ -1,16 +1,12 @@
#include "core/version.h"
-#ifndef _STR
-#define _STR(m_x) #m_x
-#define _MKSTR(m_x) _STR(m_x)
-#endif
GODOT_ICON ICON platform/windows/godot_console.ico
1 VERSIONINFO
-FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
-PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
-FILEOS 4
-FILETYPE 1
+FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
+PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
+FILEOS 4
+FILETYPE 1
BEGIN
BLOCK "StringFileInfo"
BEGIN
@@ -21,7 +17,7 @@ BEGIN
VALUE "FileVersion", VERSION_NUMBER
VALUE "ProductName", VERSION_NAME " (Console)"
VALUE "Licence", "MIT"
- VALUE "LegalCopyright", "Copyright (c) 2007-" _MKSTR(VERSION_YEAR) " Juan Linietsky, Ariel Manzur and contributors"
+ VALUE "LegalCopyright", "(c) 2007-present Juan Linietsky, Ariel Manzur and Godot Engine contributors"
VALUE "Info", "https://godotengine.org"
VALUE "ProductVersion", VERSION_FULL_BUILD
END
diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp
index b376854c0c..20905d0fe9 100644
--- a/platform/windows/key_mapping_windows.cpp
+++ b/platform/windows/key_mapping_windows.cpp
@@ -46,6 +46,7 @@ HashMap<unsigned int, Key, HashMapHasherKeys> vk_map;
HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map;
HashMap<Key, unsigned int, HashMapHasherKeys> scansym_map_inv;
HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map_ext;
+HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingWindows::initialize() {
// VK_LBUTTON (0x01)
@@ -380,6 +381,15 @@ void KeyMappingWindows::initialize() {
scansym_map_ext[0x6C] = Key::LAUNCHMAIL;
scansym_map_ext[0x6D] = Key::LAUNCHMEDIA;
scansym_map_ext[0x78] = Key::MEDIARECORD;
+
+ // Scancode to physical location map.
+ // Shift.
+ location_map[0x2A] = KeyLocation::LEFT;
+ location_map[0x36] = KeyLocation::RIGHT;
+ // Meta.
+ location_map[0x5B] = KeyLocation::LEFT;
+ location_map[0x5C] = KeyLocation::RIGHT;
+ // Ctrl and Alt must be handled differently.
}
Key KeyMappingWindows::get_keysym(unsigned int p_code) {
@@ -424,3 +434,16 @@ bool KeyMappingWindows::is_extended_key(unsigned int p_code) {
p_code == VK_RIGHT ||
p_code == VK_DOWN;
}
+
+KeyLocation KeyMappingWindows::get_location(unsigned int p_code, bool p_extended) {
+ // Right- ctrl and alt have the same scancode as left, but are in the extended keys.
+ const Key *key = scansym_map.getptr(p_code);
+ if (key && (*key == Key::CTRL || *key == Key::ALT)) {
+ return p_extended ? KeyLocation::RIGHT : KeyLocation::LEFT;
+ }
+ const KeyLocation *location = location_map.getptr(p_code);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h
index a98aa7ed68..e6f184a2cc 100644
--- a/platform/windows/key_mapping_windows.h
+++ b/platform/windows/key_mapping_windows.h
@@ -47,6 +47,7 @@ public:
static unsigned int get_scancode(Key p_keycode);
static Key get_scansym(unsigned int p_code, bool p_extended);
static bool is_extended_key(unsigned int p_code);
+ static KeyLocation get_location(unsigned int p_code, bool p_extended);
};
#endif // KEY_MAPPING_WINDOWS_H
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 1d3b80e21e..6ad616da85 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -360,6 +360,8 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han
path = get_executable_path().get_base_dir().path_join(p_path.get_file());
}
+ ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
+
typedef DLL_DIRECTORY_COOKIE(WINAPI * PAddDllDirectory)(PCWSTR);
typedef BOOL(WINAPI * PRemoveDllDirectory)(DLL_DIRECTORY_COOKIE);
@@ -1183,7 +1185,7 @@ Vector<String> OS_Windows::get_system_font_path_for_text(const String &p_font_na
if (FAILED(hr)) {
continue;
}
- String fpath = String::utf16((const char16_t *)&file_path[0]);
+ String fpath = String::utf16((const char16_t *)&file_path[0]).replace("\\", "/");
WIN32_FIND_DATAW d;
HANDLE fnd = FindFirstFileW((LPCWSTR)&file_path[0], &d);
@@ -1262,7 +1264,7 @@ String OS_Windows::get_system_font_path(const String &p_font_name, int p_weight,
if (FAILED(hr)) {
continue;
}
- String fpath = String::utf16((const char16_t *)&file_path[0]);
+ String fpath = String::utf16((const char16_t *)&file_path[0]).replace("\\", "/");
WIN32_FIND_DATAW d;
HANDLE fnd = FindFirstFileW((LPCWSTR)&file_path[0], &d);
diff --git a/platform/windows/vulkan_context_win.h b/platform/windows/vulkan_context_win.h
index 29ab1d45c3..770a59de9e 100644
--- a/platform/windows/vulkan_context_win.h
+++ b/platform/windows/vulkan_context_win.h
@@ -39,7 +39,7 @@
#include <windows.h>
class VulkanContextWindows : public VulkanContext {
- virtual const char *_get_platform_surface_extension() const;
+ virtual const char *_get_platform_surface_extension() const override final;
public:
struct WindowPlatformData {