summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/README.md7
-rw-r--r--platform/android/SCsub8
-rw-r--r--platform/android/detect.py44
-rw-r--r--platform/android/dir_access_jandroid.cpp14
-rw-r--r--platform/android/dir_access_jandroid.h1
-rw-r--r--platform/android/display_server_android.cpp4
-rw-r--r--platform/android/display_server_android.h4
-rw-r--r--platform/android/export/export_plugin.cpp27
-rw-r--r--platform/android/export/export_plugin.h5
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp13
-rw-r--r--platform/android/file_access_filesystem_jandroid.h2
-rw-r--r--platform/android/java/build.gradle4
-rw-r--r--platform/android/java/lib/build.gradle2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt5
-rw-r--r--platform/android/java_godot_io_wrapper.cpp4
-rw-r--r--platform/android/java_godot_io_wrapper.h4
-rw-r--r--platform/android/os_android.cpp6
-rw-r--r--platform/ios/README.md14
-rw-r--r--platform/ios/detect.py15
-rw-r--r--platform/ios/display_server_ios.h2
-rw-r--r--platform/ios/display_server_ios.mm4
-rw-r--r--platform/ios/export/export_plugin.cpp148
-rw-r--r--platform/ios/export/export_plugin.h7
-rw-r--r--platform/javascript/README.md11
-rw-r--r--platform/javascript/detect.py10
-rw-r--r--platform/javascript/display_server_javascript.cpp2
-rw-r--r--platform/javascript/display_server_javascript.h2
-rw-r--r--platform/javascript/export/export_plugin.cpp24
-rw-r--r--platform/javascript/export/export_plugin.h3
-rw-r--r--platform/javascript/js/libs/library_godot_display.js28
-rw-r--r--platform/javascript/os_javascript.h1
-rw-r--r--platform/linuxbsd/README.md14
-rw-r--r--platform/linuxbsd/detect.py46
-rw-r--r--platform/linuxbsd/display_server_x11.cpp4
-rw-r--r--platform/linuxbsd/display_server_x11.h2
-rw-r--r--platform/macos/README.md19
-rw-r--r--platform/macos/detect.py29
-rw-r--r--platform/macos/display_server_macos.h30
-rw-r--r--platform/macos/display_server_macos.mm468
-rw-r--r--platform/macos/export/export.cpp8
-rw-r--r--platform/macos/export/export_plugin.cpp748
-rw-r--r--platform/macos/export/export_plugin.h5
-rw-r--r--platform/macos/godot_menu_item.h1
-rw-r--r--platform/uwp/README.md20
-rw-r--r--platform/uwp/detect.py39
-rw-r--r--platform/uwp/export/export_plugin.cpp23
-rw-r--r--platform/uwp/export/export_plugin.h3
-rw-r--r--platform/windows/README.md15
-rw-r--r--platform/windows/detect.py100
-rw-r--r--platform/windows/display_server_windows.cpp4
-rw-r--r--platform/windows/display_server_windows.h2
-rw-r--r--platform/windows/export/export_plugin.cpp29
-rw-r--r--platform/windows/export/export_plugin.h5
-rw-r--r--platform/windows/platform_windows_builders.py2
55 files changed, 1406 insertions, 643 deletions
diff --git a/platform/android/README.md b/platform/android/README.md
index 343e588553..f6aabab708 100644
--- a/platform/android/README.md
+++ b/platform/android/README.md
@@ -3,6 +3,13 @@
This folder contains the Java and C++ (JNI) code for the Android platform port,
using [Gradle](https://gradle.org/) as a build system.
+## Documentation
+
+- [Compiling for Android](https://docs.godotengine.org/en/latest/development/compiling/compiling_for_android.html)
+ - Instructions on building this platform port from source.
+- [Exporting for Android](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_android.html)
+ - Instructions on using the compiled export templates to export a project.
+
## Artwork license
[`logo.png`](logo.png) and [`run_icon.png`](run_icon.png) are licensed under
diff --git a/platform/android/SCsub b/platform/android/SCsub
index d370a4d18d..344ca036de 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -41,13 +41,13 @@ lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSU
env.Depends(lib, thirdparty_obj)
lib_arch_dir = ""
-if env["android_arch"] == "armv7":
+if env["arch"] == "arm32":
lib_arch_dir = "armeabi-v7a"
-elif env["android_arch"] == "arm64v8":
+elif env["arch"] == "arm64":
lib_arch_dir = "arm64-v8a"
-elif env["android_arch"] == "x86":
+elif env["arch"] == "x86_32":
lib_arch_dir = "x86"
-elif env["android_arch"] == "x86_64":
+elif env["arch"] == "x86_64":
lib_arch_dir = "x86_64"
else:
print("WARN: Architecture not suitable for embedding into APK; keeping .so at \\bin")
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 2ff5bf59ea..ad63821162 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -22,7 +22,6 @@ def get_opts():
return [
("ANDROID_SDK_ROOT", "Path to the Android SDK", get_env_android_sdk_root()),
("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"),
- EnumVariable("android_arch", "Target architecture", "arm64v8", ("armv7", "arm64v8", "x86", "x86_64")),
]
@@ -46,6 +45,7 @@ def get_ndk_version():
def get_flags():
return [
+ ("arch", "arm64"), # Default for convenience.
("tools", False),
]
@@ -75,35 +75,37 @@ def install_ndk_if_needed(env):
def configure(env):
+ # Validate arch.
+ supported_arches = ["x86_32", "x86_64", "arm32", "arm64"]
+ if env["arch"] not in supported_arches:
+ print(
+ 'Unsupported CPU architecture "%s" for Android. Supported architectures are: %s.'
+ % (env["arch"], ", ".join(supported_arches))
+ )
+ sys.exit()
+
install_ndk_if_needed(env)
ndk_root = env["ANDROID_NDK_ROOT"]
# Architecture
- if env["android_arch"] not in ["armv7", "arm64v8", "x86", "x86_64"]:
- env["android_arch"] = "arm64v8"
-
- print("Building for Android, platform " + env["ndk_platform"] + " (" + env["android_arch"] + ")")
-
- if get_min_sdk_version(env["ndk_platform"]) < 21:
- if env["android_arch"] == "x86_64" or env["android_arch"] == "arm64v8":
- print(
- "WARNING: android_arch="
- + env["android_arch"]
- + " is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21"
- )
- env["ndk_platform"] = "android-21"
+ if get_min_sdk_version(env["ndk_platform"]) < 21 and env["arch"] in ["x86_64", "arm64"]:
+ print(
+ 'WARNING: arch="%s" is not supported with "ndk_platform" lower than "android-21". Forcing platform 21.'
+ % env["arch"]
+ )
+ env["ndk_platform"] = "android-21"
- if env["android_arch"] == "armv7":
+ if env["arch"] == "arm32":
target_triple = "armv7a-linux-androideabi"
env.extra_suffix = ".armv7" + env.extra_suffix
- elif env["android_arch"] == "arm64v8":
+ elif env["arch"] == "arm64":
target_triple = "aarch64-linux-android"
env.extra_suffix = ".armv8" + env.extra_suffix
- elif env["android_arch"] == "x86":
+ elif env["arch"] == "x86_32":
target_triple = "i686-linux-android"
env.extra_suffix = ".x86" + env.extra_suffix
- elif env["android_arch"] == "x86_64":
+ elif env["arch"] == "x86_64":
target_triple = "x86_64-linux-android"
env.extra_suffix = ".x86_64" + env.extra_suffix
@@ -176,14 +178,14 @@ def configure(env):
if get_min_sdk_version(env["ndk_platform"]) >= 24:
env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)])
- if env["android_arch"] == "x86":
+ if env["arch"] == "x86_32":
# The NDK adds this if targeting API < 24, so we can drop it when Godot targets it at least
env.Append(CCFLAGS=["-mstackrealign"])
- elif env["android_arch"] == "armv7":
+ elif env["arch"] == "arm32":
env.Append(CCFLAGS="-march=armv7-a -mfloat-abi=softfp".split())
env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"])
env.Append(CPPDEFINES=["__ARM_NEON__"])
- elif env["android_arch"] == "arm64v8":
+ elif env["arch"] == "arm64":
env.Append(CCFLAGS=["-mfix-cortex-a53-835769"])
env.Append(CPPDEFINES=["__ARM_ARCH_8A__"])
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index eb344d3b43..428135de56 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -135,6 +135,20 @@ String DirAccessJAndroid::get_drive(int p_drive) {
}
}
+String DirAccessJAndroid::get_current_dir(bool p_include_drive) const {
+ String base = _get_root_path();
+ String bd = current_dir;
+ if (!base.is_empty()) {
+ bd = current_dir.replace_first(base, "");
+ }
+
+ if (bd.begins_with("/")) {
+ return _get_root_string() + bd.substr(1, bd.length());
+ } else {
+ return _get_root_string() + bd;
+ }
+}
+
Error DirAccessJAndroid::change_dir(String p_dir) {
String new_dir = get_absolute_path(p_dir);
if (new_dir == current_dir) {
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index d469c9d317..5b7b4a9c4d 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -67,6 +67,7 @@ public:
virtual int get_drive_count() override;
virtual String get_drive(int p_drive) override;
+ virtual String get_current_dir(bool p_include_drive = true) const override; ///< return current dir location
virtual Error change_dir(String p_dir) override; ///< can be relative or absolute, return false on success
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index b51dd18af6..d3bce12de1 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -83,7 +83,7 @@ bool DisplayServerAndroid::tts_is_paused() const {
return TTS_Android::is_paused();
}
-Array DisplayServerAndroid::tts_get_voices() const {
+TypedArray<Dictionary> DisplayServerAndroid::tts_get_voices() const {
return TTS_Android::get_voices();
}
@@ -136,7 +136,7 @@ bool DisplayServerAndroid::clipboard_has() const {
}
}
-Array DisplayServerAndroid::get_display_cutouts() const {
+TypedArray<Rect2> DisplayServerAndroid::get_display_cutouts() const {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_NULL_V(godot_io_java, Array());
return godot_io_java->get_display_cutouts();
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 2f30642319..6e14ba3e23 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -93,7 +93,7 @@ public:
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
@@ -104,7 +104,7 @@ public:
virtual String clipboard_get() const override;
virtual bool clipboard_has() const override;
- virtual Array get_display_cutouts() const override;
+ virtual TypedArray<Rect2> get_display_cutouts() const override;
virtual Rect2i get_display_safe_area() const override;
virtual void screen_set_keep_on(bool p_enable) override;
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 6f1b4bde40..685b1f01af 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -248,6 +248,7 @@ static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/instal
static const int DEFAULT_MIN_SDK_VERSION = 19; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
+#ifndef ANDROID_ENABLED
void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
EditorExportPlatformAndroid *ea = static_cast<EditorExportPlatformAndroid *>(ud);
@@ -277,7 +278,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
}
}
-#ifndef ANDROID_ENABLED
// Check for devices updates
String adb = get_adb_path();
if (FileAccess::exists(adb)) {
@@ -389,7 +389,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
ea->devices_changed.set();
}
}
-#endif
uint64_t sleep = 200;
uint64_t wait = 3000000;
@@ -402,7 +401,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
}
}
-#ifndef ANDROID_ENABLED
if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) {
String adb = get_adb_path();
if (!FileAccess::exists(adb)) {
@@ -413,8 +411,8 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
args.push_back("kill-server");
OS::get_singleton()->execute(adb, args);
}
-#endif
}
+#endif
String EditorExportPlatformAndroid::get_project_name(const String &p_name) const {
String aname;
@@ -2049,7 +2047,7 @@ String EditorExportPlatformAndroid::get_apksigner_path() {
return apksigner_path;
}
-bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err;
bool valid = false;
const bool custom_build_enabled = p_preset->get("custom_build/use_custom_build");
@@ -2097,7 +2095,7 @@ bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_pr
valid = installed_android_build_template && !r_missing_templates;
}
- // Validate the rest of the configuration.
+ // Validate the rest of the export configuration.
String dk = p_preset->get("keystore/debug");
String dk_user = p_preset->get("keystore/debug_user");
@@ -2173,6 +2171,19 @@ bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_pr
}
}
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ String err;
+ bool valid = true;
+ const bool custom_build_enabled = p_preset->get("custom_build/use_custom_build");
+
+ // Validate the project configuration.
bool apk_expansion = p_preset->get("apk_expansion/enable");
if (apk_expansion) {
@@ -3125,10 +3136,14 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
devices_changed.set();
plugins_changed.set();
+#ifndef ANDROID_ENABLED
check_for_changes_thread.start(_check_for_changes_poll_thread, this);
+#endif
}
EditorExportPlatformAndroid::~EditorExportPlatformAndroid() {
+#ifndef ANDROID_ENABLED
quit_request.set();
check_for_changes_thread.wait_to_finish();
+#endif
}
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 1da3f68f9a..46012bd46c 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -80,10 +80,12 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
Vector<Device> devices;
SafeFlag devices_changed;
Mutex device_lock;
+#ifndef ANDROID_ENABLED
Thread check_for_changes_thread;
SafeFlag quit_request;
static void _check_for_changes_poll_thread(void *ud);
+#endif
String get_project_name(const String &p_name) const;
@@ -186,7 +188,8 @@ public:
static String get_apksigner_path();
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index 6b21c18d59..56561cb616 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -46,6 +46,7 @@ jmethodID FileAccessFilesystemJAndroid::_file_seek_end = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_read = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_tell = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_eof = nullptr;
+jmethodID FileAccessFilesystemJAndroid::_file_set_eof = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_close = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_write = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_flush = nullptr;
@@ -162,6 +163,16 @@ bool FileAccessFilesystemJAndroid::eof_reached() const {
}
}
+void FileAccessFilesystemJAndroid::_set_eof(bool eof) {
+ if (_file_set_eof) {
+ ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
+
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+ env->CallVoidMethod(file_access_handler, _file_set_eof, id, eof);
+ }
+}
+
uint8_t FileAccessFilesystemJAndroid::get_8() const {
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
uint8_t byte;
@@ -184,6 +195,7 @@ String FileAccessFilesystemJAndroid::get_line() const {
while (true) {
size_t line_buffer_size = MIN(buffer_size_limit, file_size - get_position());
if (line_buffer_size <= 0) {
+ const_cast<FileAccessFilesystemJAndroid *>(this)->_set_eof(true);
break;
}
@@ -310,6 +322,7 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) {
_file_get_size = env->GetMethodID(cls, "fileGetSize", "(I)J");
_file_tell = env->GetMethodID(cls, "fileGetPosition", "(I)J");
_file_eof = env->GetMethodID(cls, "isFileEof", "(I)Z");
+ _file_set_eof = env->GetMethodID(cls, "setFileEof", "(IZ)V");
_file_seek = env->GetMethodID(cls, "fileSeek", "(IJ)V");
_file_seek_end = env->GetMethodID(cls, "fileSeekFromEnd", "(IJ)V");
_file_read = env->GetMethodID(cls, "fileRead", "(ILjava/nio/ByteBuffer;)I");
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index 7deb8de37b..76d7db6e3a 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -44,6 +44,7 @@ class FileAccessFilesystemJAndroid : public FileAccess {
static jmethodID _file_seek_end;
static jmethodID _file_tell;
static jmethodID _file_eof;
+ static jmethodID _file_set_eof;
static jmethodID _file_read;
static jmethodID _file_write;
static jmethodID _file_flush;
@@ -56,6 +57,7 @@ class FileAccessFilesystemJAndroid : public FileAccess {
String path_src;
void _close(); ///< close a file
+ void _set_eof(bool eof);
public:
virtual Error _open(const String &p_path, int p_mode_flags) override; ///< open a file
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index da30bd3a95..81c7130c03 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -28,7 +28,7 @@ allprojects {
}
ext {
- supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"]
+ supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"]
supportedTargetsMap = [release: "release", dev: "debug", debug: "release_debug"]
supportedFlavors = ["editor", "template"]
@@ -37,7 +37,7 @@ ext {
// If building manually on the command line, it's recommended to use the
// `./gradlew generateGodotTemplates` build command instead after running the `scons` command(s).
// The {selectedAbis} values must be from the {supportedAbis} values.
- selectedAbis = ["arm64v8"]
+ selectedAbis = ["arm64"]
}
def rootDir = "../../.."
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 6b82326a27..318ae1143f 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -159,7 +159,7 @@ android {
def taskName = getSconsTaskName(flavorName, buildType, selectedAbi)
tasks.create(name: taskName, type: Exec) {
executable sconsExecutableFile.absolutePath
- args "--directory=${pathToRootDir}", "platform=android", "tools=${toolsFlag}", "target=${sconsTarget}", "android_arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()
+ args "--directory=${pathToRootDir}", "platform=android", "tools=${toolsFlag}", "target=${sconsTarget}", "arch=${selectedAbi}", "-j" + Runtime.runtime.availableProcessors()
}
// Schedule the tasks so the generated libs are present before the aar file is packaged.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
index 463dabfb23..f23537a29e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
@@ -104,7 +104,6 @@ internal abstract class DataAccess(private val filePath: String) {
protected abstract val fileChannel: FileChannel
internal var endOfFile = false
- private set
fun close() {
try {
@@ -125,9 +124,7 @@ internal abstract class DataAccess(private val filePath: String) {
fun seek(position: Long) {
try {
fileChannel.position(position)
- if (position <= size()) {
- endOfFile = false
- }
+ endOfFile = position >= fileChannel.size()
} catch (e: Exception) {
Log.w(TAG, "Exception when seeking file $filePath.", e)
}
@@ -161,8 +158,7 @@ internal abstract class DataAccess(private val filePath: String) {
fun read(buffer: ByteBuffer): Int {
return try {
val readBytes = fileChannel.read(buffer)
- endOfFile = readBytes == -1
- || (fileChannel.position() >= fileChannel.size() && fileChannel.size() > 0)
+ endOfFile = readBytes == -1 || (fileChannel.position() >= fileChannel.size())
if (readBytes == -1) {
0
} else {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
index 04b6772c45..83da3a24b3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
@@ -194,6 +194,11 @@ class FileAccessHandler(val context: Context) {
return files[fileId].endOfFile
}
+ fun setFileEof(fileId: Int, eof: Boolean) {
+ val file = files[fileId] ?: return
+ file.endOfFile = eof
+ }
+
fun fileClose(fileId: Int) {
if (hasFileId(fileId)) {
files[fileId].close()
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 5877c15114..cea64a7f22 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -165,8 +165,8 @@ float GodotIOJavaWrapper::get_screen_refresh_rate(float fallback) {
return fallback;
}
-Array GodotIOJavaWrapper::get_display_cutouts() {
- Array result;
+TypedArray<Rect2> GodotIOJavaWrapper::get_display_cutouts() {
+ TypedArray<Rect2> result;
ERR_FAIL_NULL_V(_get_display_cutouts, result);
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, result);
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index dc68f4d90d..9a1a877b6f 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -38,7 +38,7 @@
#include <jni.h>
#include "core/math/rect2i.h"
-#include "core/variant/array.h"
+#include "core/variant/typed_array.h"
#include "string_android.h"
// Class that makes functions in java/src/org/godotengine/godot/GodotIO.java callable from C++
@@ -78,7 +78,7 @@ public:
int get_screen_dpi();
float get_scaled_density();
float get_screen_refresh_rate(float fallback);
- Array get_display_cutouts();
+ TypedArray<Rect2> get_display_cutouts();
Rect2i get_display_safe_area();
String get_unique_id();
bool has_vk();
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 0f551e7f4f..f94614c741 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -370,15 +370,15 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) {
return true;
}
#if defined(__aarch64__)
- if (p_feature == "arm64-v8a") {
+ if (p_feature == "arm64-v8a" || p_feature == "arm64") {
return true;
}
#elif defined(__ARM_ARCH_7A__)
- if (p_feature == "armeabi-v7a" || p_feature == "armeabi") {
+ if (p_feature == "armeabi-v7a" || p_feature == "armeabi" || p_feature == "arm32") {
return true;
}
#elif defined(__arm__)
- if (p_feature == "armeabi") {
+ if (p_feature == "armeabi" || p_feature == "arm") {
return true;
}
#endif
diff --git a/platform/ios/README.md b/platform/ios/README.md
new file mode 100644
index 0000000000..82c275ad31
--- /dev/null
+++ b/platform/ios/README.md
@@ -0,0 +1,14 @@
+# iOS platform port
+
+This folder contains the C++, Objective-C and Objective-C++ code for the iOS
+platform port.
+
+See also [`misc/dist/ios_xcode`](/misc/dist/ios_xcode) folder for the Xcode
+project template used for packaging the iOS export templates.
+
+## Documentation
+
+- [Compiling for iOS](https://docs.godotengine.org/en/latest/development/compiling/compiling_for_ios.html)
+ - Instructions on building this platform port from source.
+- [Exporting for iOS](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_ios.html)
+ - Instructions on using the compiled export templates to export a project.
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 67c90b10a0..1a8d24d12d 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -36,12 +36,22 @@ def get_opts():
def get_flags():
return [
+ ("arch", "arm64"), # Default for convenience.
("tools", False),
("use_volk", False),
]
def configure(env):
+ # Validate arch.
+ supported_arches = ["x86_64", "arm64"]
+ if env["arch"] not in supported_arches:
+ print(
+ 'Unsupported CPU architecture "%s" for iOS. Supported architectures are: %s.'
+ % (env["arch"], ", ".join(supported_arches))
+ )
+ sys.exit()
+
## Build type
if env["target"].startswith("release"):
@@ -64,11 +74,6 @@ def configure(env):
env.Append(CCFLAGS=["-flto"])
env.Append(LINKFLAGS=["-flto"])
- ## Architecture
- env["bits"] = "64"
- if env["arch"] != "x86_64":
- env["arch"] = "arm64"
-
## Compiler configuration
# Save this in environment for use by other modules
diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h
index bbb2dd3ab3..f3624f24ab 100644
--- a/platform/ios/display_server_ios.h
+++ b/platform/ios/display_server_ios.h
@@ -127,7 +127,7 @@ public:
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index 6ce7e676a2..74d6bc2e97 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -336,8 +336,8 @@ bool DisplayServerIOS::tts_is_paused() const {
return [tts isPaused];
}
-Array DisplayServerIOS::tts_get_voices() const {
- ERR_FAIL_COND_V(!tts, Array());
+TypedArray<Dictionary> DisplayServerIOS::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, TypedArray<Dictionary>());
return [tts getVoices];
}
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index 001c9b803d..f49cf7a88d 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -140,27 +140,27 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with Retina display
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone with Retina HD display
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPhone/iPod Touch with Retina display
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/iphone_180x180", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPhone with Retina HD display
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad with Retina display
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad Pro
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPad
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_152x152", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPad with Retina display
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/ipad_167x167", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Home screen on iPad Pro
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // App Store
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with Retina display
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Spotlight
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); // Spotlight on devices with Retina display
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png"), ""));
+ 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"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
}
}
@@ -387,13 +387,17 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
String locale_files;
Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations");
if (translations.size() > 0) {
- int index = 0;
+ HashSet<String> languages;
for (const String &E : translations) {
Ref<Translation> tr = ResourceLoader::load(E);
- if (tr.is_valid()) {
- String lang = tr->get_locale();
- locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"<group>\"; };";
+ if (tr.is_valid() && tr->get_locale() != "en") {
+ languages.insert(tr->get_locale());
}
+ }
+
+ int index = 0;
+ for (const String &lang : languages) {
+ locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"<group>\"; };\n";
index++;
}
}
@@ -402,13 +406,17 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
String locale_files;
Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations");
if (translations.size() > 0) {
- int index = 0;
+ HashSet<String> languages;
for (const String &E : translations) {
Ref<Translation> tr = ResourceLoader::load(E);
- if (tr.is_valid()) {
- String lang = tr->get_locale();
- locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,";
+ if (tr.is_valid() && tr->get_locale() != "en") {
+ languages.insert(tr->get_locale());
}
+ }
+
+ int index = 0;
+ for (const String &lang : languages) {
+ locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,\n";
index++;
}
}
@@ -531,26 +539,27 @@ struct IconInfo {
const char *actual_size_side;
const char *scale;
const char *unscaled_size;
+ const bool force_opaque;
};
static const IconInfo icon_infos[] = {
// Home screen on iPhone
- { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60" },
- { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40" },
- { "icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60" },
+ { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "2x", "60x60", false },
+ { "icons/iphone_120x120", "iphone", "Icon-120.png", "120", "3x", "40x40", false },
+ { "icons/iphone_180x180", "iphone", "Icon-180.png", "180", "3x", "60x60", false },
// Home screen on iPad
- { "icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76" },
- { "icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76" },
- { "icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5" },
+ { "icons/ipad_76x76", "ipad", "Icon-76.png", "76", "1x", "76x76", false },
+ { "icons/ipad_152x152", "ipad", "Icon-152.png", "152", "2x", "76x76", false },
+ { "icons/ipad_167x167", "ipad", "Icon-167.png", "167", "2x", "83.5x83.5", false },
// App Store
- { "icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024" },
+ { "icons/app_store_1024x1024", "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", true },
// Spotlight
- { "icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40" },
- { "icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40" },
- { "icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40" }
+ { "icons/spotlight_40x40", "ipad", "Icon-40.png", "40", "1x", "40x40", false },
+ { "icons/spotlight_80x80", "iphone", "Icon-80.png", "80", "2x", "40x40", false },
+ { "icons/spotlight_80x80", "ipad", "Icon-80.png", "80", "2x", "40x40", false }
};
Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) {
@@ -570,14 +579,17 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
Ref<Image> img = memnew(Image);
Error err = ImageLoader::load_image(icon_path, img);
if (err != OK) {
- ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'.");
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
+ return ERR_UNCONFIGURED;
+ }
+ if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
return ERR_UNCONFIGURED;
}
img->resize(side_size, side_size);
err = img->save_png(p_iconset_dir + info.export_name);
if (err) {
- String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
- ERR_PRINT(err_str.utf8().get_data());
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
return err;
}
} else {
@@ -585,11 +597,15 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
Ref<Image> img = memnew(Image);
Error err = ImageLoader::load_image(icon_path, img);
if (err != OK) {
- ERR_PRINT("Invalid icon (" + String(info.preset_key) + "): '" + icon_path + "'.");
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
+ return ERR_UNCONFIGURED;
+ }
+ if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
return ERR_UNCONFIGURED;
}
if (img->get_width() != side_size || img->get_height() != side_size) {
- WARN_PRINT("Icon (" + String(info.preset_key) + "): '" + icon_path + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(side_size) + "x" + String::num_int64(side_size) + ".");
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2i(side_size, side_size)));
img->resize(side_size, side_size);
err = img->save_png(p_iconset_dir + info.export_name);
} else {
@@ -597,8 +613,7 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
}
if (err) {
- String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'.");
- ERR_PRINT(err_str.utf8().get_data());
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
return err;
}
}
@@ -1651,27 +1666,31 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";");
}
+ HashSet<String> languages;
for (const String &E : translations) {
Ref<Translation> tr = ResourceLoader::load(E);
- if (tr.is_valid()) {
- String lang = tr->get_locale();
- String fname = dest_dir + binary_name + "/" + lang + ".lproj";
- tmp_app_path->make_dir_recursive(fname);
- Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
- f->store_line("/* Localized versions of Info.plist keys */");
- f->store_line("");
- if (appnames.has(lang)) {
- f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
- }
- if (camera_usage_descriptions.has(lang)) {
- f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";");
- }
- if (microphone_usage_descriptions.has(lang)) {
- f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";");
- }
- if (photolibrary_usage_descriptions.has(lang)) {
- f->store_line("NSPhotoLibraryUsageDescription = \"" + photolibrary_usage_descriptions[lang].operator String() + "\";");
- }
+ if (tr.is_valid() && tr->get_locale() != "en") {
+ languages.insert(tr->get_locale());
+ }
+ }
+
+ for (const String &lang : languages) {
+ String fname = dest_dir + binary_name + "/" + lang + ".lproj";
+ tmp_app_path->make_dir_recursive(fname);
+ Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ f->store_line("/* Localized versions of Info.plist keys */");
+ f->store_line("");
+ if (appnames.has(lang)) {
+ f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
+ }
+ if (camera_usage_descriptions.has(lang)) {
+ f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (microphone_usage_descriptions.has(lang)) {
+ f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (photolibrary_usage_descriptions.has(lang)) {
+ f->store_line("NSPhotoLibraryUsageDescription = \"" + photolibrary_usage_descriptions[lang].operator String() + "\";");
}
}
}
@@ -1817,7 +1836,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
return OK;
}
-bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err;
bool valid = false;
@@ -1842,7 +1861,18 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
valid = dvalid || rvalid;
r_missing_templates = !valid;
- // Validate the rest of the configuration.
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ String err;
+ bool valid = true;
+
+ // Validate the project configuration.
String team_id = p_preset->get("application/app_store_team_id");
if (team_id.length() == 0) {
@@ -1873,10 +1903,14 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
EditorExportPlatformIOS::EditorExportPlatformIOS() {
logo = ImageTexture::create_from_image(memnew(Image(_ios_logo)));
plugins_changed.set();
+#ifndef ANDROID_ENABLED
check_for_changes_thread.start(_check_for_changes_poll_thread, this);
+#endif
}
EditorExportPlatformIOS::~EditorExportPlatformIOS() {
+#ifndef ANDROID_ENABLED
quit_request.set();
check_for_changes_thread.wait_to_finish();
+#endif
}
diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h
index e32aef82dd..abda8e218a 100644
--- a/platform/ios/export/export_plugin.h
+++ b/platform/ios/export/export_plugin.h
@@ -57,8 +57,10 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
// Plugins
SafeFlag plugins_changed;
+#ifndef ANDROID_ENABLED
Thread check_for_changes_thread;
SafeFlag quit_request;
+#endif
Mutex plugins_lock;
Vector<PluginConfigIOS> plugins;
@@ -139,6 +141,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
return true;
}
+#ifndef ANDROID_ENABLED
static void _check_for_changes_poll_thread(void *ud) {
EditorExportPlatformIOS *ea = static_cast<EditorExportPlatformIOS *>(ud);
@@ -172,6 +175,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
}
}
}
+#endif
protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
@@ -198,7 +202,8 @@ public:
}
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual void get_platform_features(List<String> *r_features) const override {
r_features->push_back("mobile");
diff --git a/platform/javascript/README.md b/platform/javascript/README.md
index f181bea9e0..812ab6778b 100644
--- a/platform/javascript/README.md
+++ b/platform/javascript/README.md
@@ -5,8 +5,15 @@ compiled using [Emscripten](https://emscripten.org/).
It also contains a ESLint linting setup (see [`package.json`](package.json)).
-See also [`misc/dist/html`](/misc/dist/html) folder for files used by this platform
-such as the HTML5 shell.
+See also [`misc/dist/html`](/misc/dist/html) folder for additional files used by
+this platform such as the HTML5 shell.
+
+## Documentation
+
+- [Compiling for the Web](https://docs.godotengine.org/en/latest/development/compiling/compiling_for_web.html)
+ - Instructions on building this platform port from source.
+- [Exporting for the Web](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_web.html)
+ - Instructions on using the compiled export templates to export a project.
## Artwork license
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index a769260f01..048c9c2eb4 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -46,6 +46,7 @@ def get_opts():
def get_flags():
return [
+ ("arch", "wasm32"),
("tools", False),
("builtin_pcre2_with_jit", False),
("vulkan", False),
@@ -53,6 +54,15 @@ def get_flags():
def configure(env):
+ # Validate arch.
+ supported_arches = ["wasm32"]
+ if env["arch"] not in supported_arches:
+ print(
+ 'Unsupported CPU architecture "%s" for iOS. Supported architectures are: %s.'
+ % (env["arch"], ", ".join(supported_arches))
+ )
+ sys.exit()
+
try:
env["initial_memory"] = int(env["initial_memory"])
except Exception:
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index 48f637fcfe..30240ad2db 100644
--- a/platform/javascript/display_server_javascript.cpp
+++ b/platform/javascript/display_server_javascript.cpp
@@ -296,7 +296,7 @@ void DisplayServerJavaScript::update_voices_callback(int p_size, const char **p_
}
}
-Array DisplayServerJavaScript::tts_get_voices() const {
+TypedArray<Dictionary> DisplayServerJavaScript::tts_get_voices() const {
godot_js_tts_get_voices(update_voices_callback);
return voices;
}
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
index fb7f5d02a8..cbb91477b7 100644
--- a/platform/javascript/display_server_javascript.h
+++ b/platform/javascript/display_server_javascript.h
@@ -124,7 +124,7 @@ public:
// tts
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/javascript/export/export_plugin.cpp b/platform/javascript/export/export_plugin.cpp
index b99f88d067..0bdee11018 100644
--- a/platform/javascript/export/export_plugin.cpp
+++ b/platform/javascript/export/export_plugin.cpp
@@ -362,7 +362,7 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const {
return logo;
}
-bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformJavaScript::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
#ifndef DEV_ENABLED
// We don't provide export templates for the HTML5 platform currently as there
// is no suitable renderer to use with them. So we forbid exporting and tell
@@ -396,7 +396,27 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p
valid = dvalid || rvalid;
r_missing_templates = !valid;
- // Validate the rest of the configuration.
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformJavaScript::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+#ifndef DEV_ENABLED
+ // We don't provide export templates for the HTML5 platform currently as there
+ // is no suitable renderer to use with them. So we forbid exporting and tell
+ // users why. This is skipped in DEV_ENABLED so that contributors can still test
+ // the pipeline once we start having WebGL or WebGPU support.
+ r_error = "The HTML5 platform is currently not supported in Godot 4.0, as there is no suitable renderer for it.\n";
+ return false;
+#endif
+
+ String err;
+ bool valid = true;
+
+ // Validate the project configuration.
if (p_preset->get("vram_texture_compression/for_mobile")) {
String etc_error = test_etc2();
diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h
index fbaa3615cb..16bab02d54 100644
--- a/platform/javascript/export/export_plugin.h
+++ b/platform/javascript/export/export_plugin.h
@@ -118,7 +118,8 @@ public:
virtual String get_os_name() const override;
virtual Ref<Texture2D> get_logo() const override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js
index c7729a8c5b..768eaf9e1d 100644
--- a/platform/javascript/js/libs/library_godot_display.js
+++ b/platform/javascript/js/libs/library_godot_display.js
@@ -336,26 +336,12 @@ const GodotDisplay = {
$GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotEventListeners', '$GodotDisplayScreen', '$GodotDisplayVK'],
$GodotDisplay: {
window_icon: '',
- findDPI: function () {
- function testDPI(dpi) {
- return window.matchMedia(`(max-resolution: ${dpi}dpi)`).matches;
- }
- function bisect(low, high, func) {
- const mid = parseInt(((high - low) / 2) + low, 10);
- if (high - low <= 1) {
- return func(high) ? high : low;
- }
- if (func(mid)) {
- return bisect(low, mid, func);
- }
- return bisect(mid, high, func);
- }
- try {
- const dpi = bisect(0, 800, testDPI);
- return dpi >= 96 ? dpi : 96;
- } catch (e) {
- return 96;
- }
+ getDPI: function () {
+ // devicePixelRatio is given in dppx
+ // https://drafts.csswg.org/css-values/#resolution
+ // > due to the 1:96 fixed ratio of CSS *in* to CSS *px*, 1dppx is equivalent to 96dpi.
+ const dpi = Math.round(window.devicePixelRatio * 96);
+ return dpi >= 96 ? dpi : 96;
},
},
@@ -461,7 +447,7 @@ const GodotDisplay = {
godot_js_display_screen_dpi_get__sig: 'i',
godot_js_display_screen_dpi_get: function () {
- return GodotDisplay.findDPI();
+ return GodotDisplay.getDPI();
},
godot_js_display_pixel_ratio_get__sig: 'f',
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index 35e13c94fc..d932745177 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -98,7 +98,6 @@ public:
String get_user_data_dir() const override;
bool is_userfs_persistent() const override;
- bool is_single_window() const override { return true; }
void alert(const String &p_alert, const String &p_title = "ALERT!") override;
diff --git a/platform/linuxbsd/README.md b/platform/linuxbsd/README.md
index 0d3fb37be5..efa8682062 100644
--- a/platform/linuxbsd/README.md
+++ b/platform/linuxbsd/README.md
@@ -2,10 +2,20 @@
This folder contains the C++ code for the Linux/*BSD platform port.
+See also [`misc/dist/linux`](/misc/dist/linux) folder for additional files
+used by this platform.
+
+## Documentation
+
+- [Compiling for Linux/*BSD](https://docs.godotengine.org/en/latest/development/compiling/compiling_for_linuxbsd.html)
+ - Instructions on building this platform port from source.
+- [Exporting for Linux/*BSD](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_linux.html)
+ - Instructions on using the compiled export templates to export a project.
+
## Artwork license
[`logo.png`](logo.png) is derived from the [Linux logo](https://isc.tamu.edu/~lewing/linux/):
> Permission to use and/or modify this image is granted provided you acknowledge me
- <lewing@isc.tamu.edu> and [The GIMP](https://isc.tamu.edu/~lewing/gimp/)
- if someone asks.
+> <lewing@isc.tamu.edu> and [The GIMP](https://isc.tamu.edu/~lewing/gimp/)
+> if someone asks.
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index 00e2b9e6eb..dd829bdb9b 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -2,6 +2,7 @@ import os
import platform
import sys
from methods import get_compiler_version, using_gcc
+from platform_methods import detect_arch
def is_active():
@@ -52,10 +53,21 @@ def get_opts():
def get_flags():
- return []
+ return [
+ ("arch", detect_arch()),
+ ]
def configure(env):
+ # Validate arch.
+ supported_arches = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64"]
+ if env["arch"] not in supported_arches:
+ print(
+ 'Unsupported CPU architecture "%s" for Linux / *BSD. Supported architectures are: %s.'
+ % (env["arch"], ", ".join(supported_arches))
+ )
+ sys.exit()
+
## Build type
if env["target"] == "release":
@@ -80,23 +92,7 @@ def configure(env):
env.Prepend(CCFLAGS=["-g3"])
env.Append(LINKFLAGS=["-rdynamic"])
- ## Architecture
-
- is64 = sys.maxsize > 2**32
- if env["bits"] == "default":
- env["bits"] = "64" if is64 else "32"
-
- machines = {
- "riscv64": "rv64",
- "ppc64le": "ppc64",
- "ppc64": "ppc64",
- "ppcle": "ppc",
- "ppc": "ppc",
- }
-
- if env["arch"] == "" and platform.machine() in machines:
- env["arch"] = machines[platform.machine()]
-
+ # CPU architecture flags.
if env["arch"] == "rv64":
# G = General-purpose extensions, C = Compression extension (very common).
env.Append(CCFLAGS=["-march=rv64gc"])
@@ -262,8 +258,7 @@ def configure(env):
env["builtin_libvorbis"] = False # Needed to link against system libtheora
env.ParseConfig("pkg-config theora theoradec --cflags --libs")
else:
- list_of_x86 = ["x86_64", "x86", "i386", "i586"]
- if any(platform.machine() in s for s in list_of_x86):
+ if env["arch"] in ["x86_64", "x86_32"]:
env["x86_libtheora_opt_gcc"] = True
if not env["builtin_libvorbis"]:
@@ -392,7 +387,9 @@ def configure(env):
import subprocess
import re
- linker_version_str = subprocess.check_output([env.subst(env["LINK"]), "-Wl,--version"]).decode("utf-8")
+ linker_version_str = subprocess.check_output(
+ [env.subst(env["LINK"]), "-Wl,--version"] + env.subst(env["LINKFLAGS"])
+ ).decode("utf-8")
gnu_ld_version = re.search("^GNU ld [^$]*(\d+\.\d+)$", linker_version_str, re.MULTILINE)
if not gnu_ld_version:
print(
@@ -405,11 +402,12 @@ def configure(env):
env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.legacy.ld"])
## Cross-compilation
-
- if is64 and env["bits"] == "32":
+ # TODO: Support cross-compilation on architectures other than x86.
+ host_is_64_bit = sys.maxsize > 2**32
+ if host_is_64_bit and env["arch"] == "x86_32":
env.Append(CCFLAGS=["-m32"])
env.Append(LINKFLAGS=["-m32", "-L/usr/lib/i386-linux-gnu"])
- elif not is64 and env["bits"] == "64":
+ elif not host_is_64_bit and env["arch"] == "x86_64":
env.Append(CCFLAGS=["-m64"])
env.Append(LINKFLAGS=["-m64", "-L/usr/lib/i686-linux-gnu"])
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 8efbd2e3c5..63998e2fde 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -322,8 +322,8 @@ bool DisplayServerX11::tts_is_paused() const {
return tts->is_paused();
}
-Array DisplayServerX11::tts_get_voices() const {
- ERR_FAIL_COND_V(!tts, Array());
+TypedArray<Dictionary> DisplayServerX11::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, TypedArray<Dictionary>());
return tts->get_voices();
}
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
index 9ce6a557db..0174cfb881 100644
--- a/platform/linuxbsd/display_server_x11.h
+++ b/platform/linuxbsd/display_server_x11.h
@@ -308,7 +308,7 @@ public:
#ifdef SPEECHD_ENABLED
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/macos/README.md b/platform/macos/README.md
new file mode 100644
index 0000000000..feead80736
--- /dev/null
+++ b/platform/macos/README.md
@@ -0,0 +1,19 @@
+# macOS platform port
+
+This folder contains the C++, Objective-C and Objective-C++ code for the macOS
+platform port.
+
+See also [`misc/dist/macos`](/misc/dist/macos) folder for additional files used
+by this platform. [`misc/dist/macos_tools.app`](/misc/dist/macos_tools.app) is
+an `.app` bundle template used for packaging the macOS editor, while
+[`misc/dist/macos_template.app`](/misc/dist/macos_template.app) is used for
+packaging macOS export templates.
+
+## Documentation
+
+- [Compiling for macOS](https://docs.godotengine.org/en/latest/development/compiling/compiling_for_macos.html)
+ - Instructions on building this platform port from source.
+- [Exporting for macOS](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_macos.html)
+ - Instructions on using the compiled export templates to export a project.
+- [Running Godot apps on macOS](https://docs.godotengine.org/en/latest/tutorials/export/running_on_macos.html)
+ - Instructions on running Godot projects on macOS.
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index 20e7afa772..e5bcb46b02 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -1,6 +1,7 @@
import os
import sys
from methods import detect_darwin_sdk_path
+from platform_methods import detect_arch
def is_active():
@@ -37,6 +38,7 @@ def get_opts():
def get_flags():
return [
+ ("arch", detect_arch()),
("use_volk", False),
]
@@ -71,6 +73,15 @@ def get_mvk_sdk_path():
def configure(env):
+ # Validate arch.
+ supported_arches = ["x86_64", "arm64"]
+ if env["arch"] not in supported_arches:
+ print(
+ 'Unsupported CPU architecture "%s" for macOS. Supported architectures are: %s.'
+ % (env["arch"], ", ".join(supported_arches))
+ )
+ sys.exit()
+
## Build type
if env["target"] == "release":
@@ -96,25 +107,20 @@ def configure(env):
env.Prepend(CCFLAGS=["-g3"])
env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"])
- ## Architecture
-
- # macOS no longer runs on 32-bit since 10.7 which is unsupported since 2014
- # As such, we only support 64-bit
- env["bits"] = "64"
-
## Compiler configuration
# Save this in environment for use by other modules
if "OSXCROSS_ROOT" in os.environ:
env["osxcross"] = True
+ # CPU architecture.
if env["arch"] == "arm64":
- print("Building for macOS 11.0+, platform arm64.")
+ print("Building for macOS 11.0+.")
env.Append(ASFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"])
env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"])
env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"])
- else:
- print("Building for macOS 10.12+, platform x86_64.")
+ elif env["arch"] == "x86_64":
+ print("Building for macOS 10.12+.")
env.Append(ASFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"])
@@ -185,9 +191,8 @@ def configure(env):
## Dependencies
- if env["builtin_libtheora"]:
- if env["arch"] != "arm64":
- env["x86_libtheora_opt_gcc"] = True
+ if env["builtin_libtheora"] and env["arch"] == "x86_64":
+ env["x86_libtheora_opt_gcc"] = True
## Flags
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index 54c479fc81..a08667a259 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -196,6 +196,9 @@ private:
static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);
+ bool _has_help_menu() const;
+ NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out);
+
public:
NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
@@ -224,15 +227,15 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
- virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
- virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
+ virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
+ virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override;
virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const override;
@@ -250,6 +253,7 @@ public:
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override;
virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const override;
+ virtual int global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const override;
virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override;
virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
@@ -264,6 +268,7 @@ public:
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override;
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override;
virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) override;
+ virtual void global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) override;
virtual int global_menu_get_item_count(const String &p_menu_root) const override;
@@ -272,7 +277,7 @@ public:
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
@@ -372,6 +377,11 @@ public:
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
+ virtual bool window_maximize_on_title_dbl_click() const override;
+ virtual bool window_minimize_on_title_dbl_click() const override;
+
+ virtual Vector2i window_get_safe_title_margins(WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual Point2i ime_get_selection() const override;
virtual String ime_get_text() const override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index a49485154b..b06ae9f27c 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -63,7 +63,7 @@
const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) const {
const NSMenu *menu = nullptr;
- if (p_menu_root == "") {
+ if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
// Main menu.
menu = [NSApp mainMenu];
} else if (p_menu_root.to_lower() == "_dock") {
@@ -84,7 +84,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons
NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
NSMenu *menu = nullptr;
- if (p_menu_root == "") {
+ if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
// Main menu.
menu = [NSApp mainMenu];
} else if (p_menu_root.to_lower() == "_dock") {
@@ -703,6 +703,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
//case FEATURE_KEEP_SCREEN_ON:
case FEATURE_SWAP_BUFFERS:
case FEATURE_TEXT_TO_SPEECH:
+ case FEATURE_EXTEND_TO_TITLE:
return true;
default: {
}
@@ -714,18 +715,51 @@ String DisplayServerMacOS::get_name() const {
return "macOS";
}
-void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
+bool DisplayServerMacOS::_has_help_menu() const {
+ if ([NSApp helpMenu]) {
+ return true;
+ } else {
+ NSMenu *menu = [NSApp mainMenu];
+ const NSMenuItem *menu_item = [menu itemAtIndex:[menu numberOfItems] - 1];
+ if (menu_item) {
+ String menu_name = String::utf8([[menu_item title] UTF8String]);
+ if (menu_name == "Help" || menu_name == RTR("Help")) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
+ if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
+ p_index = [menu numberOfItems];
+ } else if (p_index < 0) {
+ p_index = item_count;
} else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_index++;
+ }
+ p_index = CLAMP(p_index, 0, item_count);
}
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ *r_out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
+ return menu_item;
+ }
+ return nullptr;
+}
+
+int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ _THREAD_SAFE_METHOD_
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -735,20 +769,15 @@ void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const S
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -758,20 +787,15 @@ void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, c
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -790,20 +814,15 @@ void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, co
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -822,20 +841,15 @@ void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_ro
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -845,20 +859,15 @@ void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_r
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -877,20 +886,31 @@ void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_m
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
+ int out = -1;
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
+ if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
+ p_index = [menu numberOfItems];
+ } else if (p_index < 0) {
+ p_index = item_count;
} else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_index++;
+ }
+ p_index = CLAMP(p_index, 0, item_count);
}
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
+
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -900,44 +920,69 @@ void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_ro
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
+int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
NSMenu *sub_menu = _get_menu_root(p_submenu);
+ int out = -1;
if (menu && sub_menu) {
if (sub_menu == menu) {
ERR_PRINT("Can't set submenu to self!");
- return;
+ return -1;
}
if ([sub_menu supermenu]) {
ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
- return;
+ return -1;
}
NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
+ int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
+ if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
+ p_index = [menu numberOfItems];
+ } else if (p_index < 0) {
+ p_index = item_count;
} else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_index++;
+ }
+ p_index = CLAMP(p_index, 0, item_count);
}
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
+ out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
+
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = Callable();
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setRepresentedObject:obj];
+
[sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
[menu setSubmenu:sub_menu forItem:menu_item];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) {
+int DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if (p_index != -1) {
- [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
+ if (menu == [NSApp mainMenu]) { // Do not add separators into main menu.
+ return -1;
+ }
+ if (p_index < 0) {
+ p_index = [menu numberOfItems];
} else {
- [menu addItem:[NSMenuItem separatorItem]];
+ p_index = CLAMP(p_index, 0, [menu numberOfItems]);
}
+ [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
+ return p_index;
}
+ return -1;
}
int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const {
@@ -945,7 +990,11 @@ int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - 1;
+ } else {
+ return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ }
}
return -1;
@@ -956,12 +1005,16 @@ int DisplayServerMacOS::global_menu_get_item_index_from_tag(const String &p_menu
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- for (NSInteger i = 0; i < [menu numberOfItems]; i++) {
+ for (NSInteger i = (menu == [NSApp mainMenu]) ? 1 : 0; i < [menu numberOfItems]; i++) {
const NSMenuItem *menu_item = [menu itemAtIndex:i];
if (menu_item) {
const GodotMenuItem *obj = [menu_item representedObject];
if (obj && obj->meta == p_tag) {
- return i;
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ return i - 1;
+ } else {
+ return i;
+ }
}
}
}
@@ -975,6 +1028,11 @@ bool DisplayServerMacOS::global_menu_is_item_checked(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return ([menu_item state] == NSControlStateValueOn);
@@ -988,6 +1046,11 @@ bool DisplayServerMacOS::global_menu_is_item_checkable(const String &p_menu_root
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1004,6 +1067,11 @@ bool DisplayServerMacOS::global_menu_is_item_radio_checkable(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1020,6 +1088,11 @@ Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, Callable());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1036,6 +1109,11 @@ Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, Variant());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Variant());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1052,6 +1130,11 @@ String DisplayServerMacOS::global_menu_get_item_text(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return String::utf8([[menu_item title] UTF8String]);
@@ -1065,6 +1148,11 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
const NSMenu *sub_menu = [menu_item submenu];
@@ -1085,6 +1173,11 @@ Key DisplayServerMacOS::global_menu_get_item_accelerator(const String &p_menu_ro
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, Key::NONE);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Key::NONE);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
@@ -1116,6 +1209,11 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return ![menu_item isEnabled];
@@ -1129,6 +1227,11 @@ String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return String::utf8([[menu_item toolTip] UTF8String]);
@@ -1142,6 +1245,11 @@ int DisplayServerMacOS::global_menu_get_item_state(const String &p_menu_root, in
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1158,6 +1266,11 @@ int DisplayServerMacOS::global_menu_get_item_max_states(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1174,6 +1287,11 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Ref<Texture2D>());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1187,14 +1305,34 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men
return Ref<Texture2D>();
}
+int DisplayServerMacOS::global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return [menu_item indentationLevel];
+ }
+ }
+ return 0;
+}
+
void DisplayServerMacOS::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
if (p_checked) {
@@ -1211,12 +1349,15 @@ void DisplayServerMacOS::global_menu_set_item_checkable(const String &p_menu_roo
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
}
}
@@ -1227,12 +1368,15 @@ void DisplayServerMacOS::global_menu_set_item_radio_checkable(const String &p_me
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE;
}
}
@@ -1243,12 +1387,15 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
obj->callback = p_callback;
}
}
@@ -1259,12 +1406,15 @@ void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
obj->meta = p_tag;
}
}
@@ -1275,9 +1425,11 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
@@ -1299,9 +1451,11 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root,
ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
return;
}
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu setSubmenu:sub_menu forItem:menu_item];
@@ -1314,9 +1468,11 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
@@ -1331,9 +1487,11 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setEnabled:(!p_disabled)];
@@ -1346,9 +1504,11 @@ void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root,
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
@@ -1361,15 +1521,16 @@ void DisplayServerMacOS::global_menu_set_item_state(const String &p_menu_root, i
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- obj->state = p_state;
- }
+ ERR_FAIL_COND(!obj);
+ obj->state = p_state;
}
}
}
@@ -1379,15 +1540,16 @@ void DisplayServerMacOS::global_menu_set_item_max_states(const String &p_menu_ro
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- obj->max_states = p_max_states;
- }
+ ERR_FAIL_COND(!obj);
+ obj->max_states = p_max_states;
}
}
}
@@ -1397,12 +1559,15 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
if (p_icon.is_valid()) {
obj->img = p_icon->get_image();
obj->img = obj->img->duplicate();
@@ -1419,12 +1584,33 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in
}
}
+void DisplayServerMacOS::global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setIndentationLevel:p_level];
+ }
+ }
+}
+
int DisplayServerMacOS::global_menu_get_item_count(const String &p_menu_root) const {
_THREAD_SAFE_METHOD_
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- return [menu numberOfItems];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ return [menu numberOfItems] - 1;
+ } else {
+ return [menu numberOfItems];
+ }
} else {
return 0;
}
@@ -1435,9 +1621,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not delete Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
[menu removeItemAtIndex:p_idx];
}
}
@@ -1453,6 +1641,9 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
[menu setSubmenu:apple_menu forItem:menu_item];
}
+ if (submenu.has(p_menu_root)) {
+ submenu.erase(p_menu_root);
+ }
}
}
@@ -1466,7 +1657,7 @@ bool DisplayServerMacOS::tts_is_paused() const {
return [tts isPaused];
}
-Array DisplayServerMacOS::tts_get_voices() const {
+TypedArray<Dictionary> DisplayServerMacOS::tts_get_voices() const {
ERR_FAIL_COND_V(!tts, Array());
return [tts getVoices];
}
@@ -2348,6 +2539,45 @@ bool DisplayServerMacOS::window_is_maximize_allowed(WindowID p_window) const {
return true;
}
+bool DisplayServerMacOS::window_maximize_on_title_dbl_click() const {
+ id value = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleActionOnDoubleClick"];
+ if ([value isKindOfClass:[NSString class]]) {
+ return [value isEqualToString:@"Maximize"];
+ }
+ return false;
+}
+
+bool DisplayServerMacOS::window_minimize_on_title_dbl_click() const {
+ id value = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleActionOnDoubleClick"];
+ if ([value isKindOfClass:[NSString class]]) {
+ return [value isEqualToString:@"Minimize"];
+ }
+ return false;
+}
+
+Vector2i DisplayServerMacOS::window_get_safe_title_margins(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Vector2i());
+ const WindowData &wd = windows[p_window];
+
+ float max_x = 0.f;
+ NSButton *cb = [wd.window_object standardWindowButton:NSWindowCloseButton];
+ if (cb) {
+ max_x = MAX(max_x, [cb frame].origin.x + [cb frame].size.width);
+ }
+ NSButton *mb = [wd.window_object standardWindowButton:NSWindowMiniaturizeButton];
+ if (mb) {
+ max_x = MAX(max_x, [mb frame].origin.x + [mb frame].size.width);
+ }
+ NSButton *zb = [wd.window_object standardWindowButton:NSWindowZoomButton];
+ if (zb) {
+ max_x = MAX(max_x, [zb frame].origin.x + [zb frame].size.width);
+ }
+
+ return Vector2i(max_x * screen_get_max_scale(), 0);
+}
+
void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -2366,6 +2596,19 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win
[wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable];
}
} break;
+ case WINDOW_FLAG_EXTEND_TO_TITLE: {
+ NSRect rect = [wd.window_object frame];
+ if (p_enabled) {
+ [wd.window_object setTitlebarAppearsTransparent:YES];
+ [wd.window_object setTitleVisibility:NSWindowTitleHidden];
+ [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskFullSizeContentView];
+ } else {
+ [wd.window_object setTitlebarAppearsTransparent:NO];
+ [wd.window_object setTitleVisibility:NSWindowTitleVisible];
+ [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskFullSizeContentView];
+ }
+ [wd.window_object setFrame:rect display:YES];
+ } break;
case WINDOW_FLAG_BORDERLESS: {
// OrderOut prevents a lose focus bug with the window.
if ([wd.window_object isVisible]) {
@@ -2433,6 +2676,9 @@ bool DisplayServerMacOS::window_get_flag(WindowFlags p_flag, WindowID p_window)
case WINDOW_FLAG_RESIZE_DISABLED: {
return wd.resize_disabled;
} break;
+ case WINDOW_FLAG_EXTEND_TO_TITLE: {
+ return [wd.window_object styleMask] & NSWindowStyleMaskFullSizeContentView;
+ } break;
case WINDOW_FLAG_BORDERLESS: {
return [wd.window_object styleMask] == NSWindowStyleMaskBorderless;
} break;
@@ -3222,7 +3468,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
[apple_menu addItem:[NSMenuItem separatorItem]];
- title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
+ title = [NSString stringWithFormat:NSLocalizedString(@"\t\tQuit %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
// Add items to the menu bar.
diff --git a/platform/macos/export/export.cpp b/platform/macos/export/export.cpp
index ff7457081f..f219616df4 100644
--- a/platform/macos/export/export.cpp
+++ b/platform/macos/export/export.cpp
@@ -33,8 +33,12 @@
#include "export_plugin.h"
void register_macos_exporter() {
- EDITOR_DEF("export/macos/force_builtin_codesign", false);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::BOOL, "export/macos/force_builtin_codesign", PROPERTY_HINT_NONE));
+ EDITOR_DEF("export/macos/rcodesign", "");
+#ifdef WINDOWS_ENABLED
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/macos/rcodesign", PROPERTY_HINT_GLOBAL_FILE, "*.exe"));
+#else
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/macos/rcodesign", PROPERTY_HINT_GLOBAL_FILE));
+#endif
Ref<EditorExportPlatformMacOS> platform;
platform.instantiate();
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index bcc2636c07..4b453add5a 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -51,11 +51,51 @@ void EditorExportPlatformMacOS::get_preset_features(const Ref<EditorExportPreset
r_features->push_back(p_preset->get("binary_format/architecture"));
}
-bool EditorExportPlatformMacOS::get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const {
- // These options are not supported by built-in codesign, used on non macOS host.
- if (!OS::get_singleton()->has_feature("macos")) {
- if (p_option == "codesign/identity" || p_option == "codesign/timestamp" || p_option == "codesign/hardened_runtime" || p_option == "codesign/custom_options" || p_option.begins_with("notarization/")) {
- return false;
+bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+ // Hide irrelevant code signing options.
+ if (p_preset) {
+ int codesign_tool = p_preset->get("codesign/codesign");
+ switch (codesign_tool) {
+ case 1: { // built-in ad-hoc
+ if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options") {
+ return false;
+ }
+ } break;
+ case 2: { // "rcodesign"
+ if (p_option == "codesign/identity") {
+ return false;
+ }
+ } break;
+#ifdef MACOS_ENABLED
+ case 3: { // "codesign"
+ if (p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password") {
+ return false;
+ }
+ } break;
+#endif
+ default: { // disabled
+ if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options" || p_option.begins_with("codesign/entitlements")) {
+ return false;
+ }
+ } break;
+ }
+
+ // Hide irrelevant notarization options.
+ int notary_tool = p_preset->get("notarization/notarization");
+ switch (notary_tool) {
+ case 1: { // "rcodesign"
+ if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id") {
+ return false;
+ }
+ } break;
+ case 2: { // "altool"
+ // All options are visible.
+ } break;
+ default: { // disabled
+ if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key") {
+ return false;
+ }
+ } break;
}
}
@@ -83,40 +123,22 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/location_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/address_book_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/calendar_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photos_library_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/desktop_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/documents_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/downloads_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/network_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/removable_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), true));
+#ifdef MACOS_ENABLED
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign,Xcode codesign"), 3, true));
+#else
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign"), 1, true));
+#endif
+ // "codesign" only options:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/replace_existing_signature"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true));
+ // "rcodesign" only options:
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_file", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_password", PROPERTY_HINT_PASSWORD), ""));
+ // "codesign" and "rcodesign" only options:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
-
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false));
-
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/disable_library_validation"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/audio_input"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/camera"), false));
@@ -126,7 +148,6 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/photos_library"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/apple_events"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/debugging"), false));
-
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_server"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_client"), false));
@@ -137,13 +158,43 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array()));
-
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false));
+#ifdef MACOS_ENABLED
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign,Xcode altool"), 0, true));
+#else
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign"), 0, true));
+#endif
+ // "altool" only options:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PASSWORD, "Enable two-factor authentication and provide app-specific password"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), ""));
+ // "altool" and "rcodesign" only options:
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), ""));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/location_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/address_book_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/calendar_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photos_library_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/desktop_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/documents_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/downloads_folder_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/network_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/removable_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false));
@@ -415,156 +466,284 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres
*/
Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+ int notary_tool = p_preset->get("notarization/notarization");
+ switch (notary_tool) {
+ case 1: { // "rcodesign"
+ print_verbose("using rcodesign notarization...");
+
+ String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
+ if (rcodesign.is_empty()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign)."));
+ return Error::FAILED;
+ }
+
+ List<String> args;
+
+ args.push_back("notary-submit");
+
+ if (p_preset->get("notarization/api_uuid") == "") {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect issuer ID name not specified."));
+ return Error::FAILED;
+ }
+ if (p_preset->get("notarization/api_key") == "") {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified."));
+ return Error::FAILED;
+ }
+
+ args.push_back("--api-issuer");
+ args.push_back(p_preset->get("notarization/api_uuid"));
+
+ args.push_back("--api-key");
+ args.push_back(p_preset->get("notarization/api_key"));
+
+ args.push_back(p_path);
+
+ String str;
+ int exitcode = 0;
+
+ Error err = OS::get_singleton()->execute(rcodesign, args, &str, &exitcode, true);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start rcodesign executable."));
+ return err;
+ }
+
+ int rq_offset = str.find("created submission ID:");
+ if (exitcode != 0 || rq_offset == -1) {
+ print_line("rcodesign (" + p_path + "):\n" + str);
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details."));
+ return Error::FAILED;
+ } else {
+ print_verbose("rcodesign (" + p_path + "):\n" + str);
+ int next_nl = str.find("\n", rq_offset);
+ String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid));
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour."));
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:"));
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"rcodesign notary-log --api-issuer <api uuid> --api-key <api key> <request uuid>\"");
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):"));
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"rcodesign staple <app path>\"");
+ }
+ } break;
#ifdef MACOS_ENABLED
- List<String> args;
+ case 2: { // "altool"
+ print_verbose("using altool notarization...");
- args.push_back("altool");
- args.push_back("--notarize-app");
+ if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Xcode command line tools are not installed."));
+ return Error::FAILED;
+ }
- args.push_back("--primary-bundle-id");
- args.push_back(p_preset->get("application/bundle_identifier"));
+ List<String> args;
- args.push_back("--username");
- args.push_back(p_preset->get("notarization/apple_id_name"));
+ args.push_back("altool");
+ args.push_back("--notarize-app");
- args.push_back("--password");
- args.push_back(p_preset->get("notarization/apple_id_password"));
+ args.push_back("--primary-bundle-id");
+ args.push_back(p_preset->get("application/bundle_identifier"));
- args.push_back("--type");
- args.push_back("osx");
+ if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified."));
+ return Error::FAILED;
+ }
+ if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time."));
+ return Error::FAILED;
+ }
- if (p_preset->get("notarization/apple_team_id")) {
- args.push_back("--asc-provider");
- args.push_back(p_preset->get("notarization/apple_team_id"));
- }
+ if (p_preset->get("notarization/apple_id_name") != "") {
+ if (p_preset->get("notarization/apple_id_password") == "") {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Apple ID password not specified."));
+ return Error::FAILED;
+ }
+ args.push_back("--username");
+ args.push_back(p_preset->get("notarization/apple_id_name"));
- args.push_back("--file");
- args.push_back(p_path);
+ args.push_back("--password");
+ args.push_back(p_preset->get("notarization/apple_id_password"));
+ } else {
+ if (p_preset->get("notarization/api_key") == "") {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified."));
+ return Error::FAILED;
+ }
+ args.push_back("--apiIssuer");
+ args.push_back(p_preset->get("notarization/api_uuid"));
- String str;
- Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true);
- if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable."));
- return err;
- }
+ args.push_back("--apiKey");
+ args.push_back(p_preset->get("notarization/api_key"));
+ }
- print_verbose("altool (" + p_path + "):\n" + str);
- int rq_offset = str.find("RequestUUID");
- if (rq_offset == -1) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed."));
- return FAILED;
- } else {
- int next_nl = str.find("\n", rq_offset);
- String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
- add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid));
- add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."));
- add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:"));
- add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
- add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):"));
- add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple <app path>\"");
- }
+ args.push_back("--type");
+ args.push_back("osx");
-#endif
+ if (p_preset->get("notarization/apple_team_id")) {
+ args.push_back("--asc-provider");
+ args.push_back(p_preset->get("notarization/apple_team_id"));
+ }
+
+ args.push_back("--file");
+ args.push_back(p_path);
+
+ String str;
+ int exitcode = 0;
+ Error err = OS::get_singleton()->execute("xcrun", args, &str, &exitcode, true);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable."));
+ return err;
+ }
+ int rq_offset = str.find("RequestUUID");
+ if (exitcode != 0 || rq_offset == -1) {
+ print_line("xcrun altool (" + p_path + "):\n" + str);
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details."));
+ return Error::FAILED;
+ } else {
+ print_verbose("xcrun altool (" + p_path + "):\n" + str);
+ int next_nl = str.find("\n", rq_offset);
+ String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14);
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid));
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email."));
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:"));
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):"));
+ add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple <app path>\"");
+ }
+ } break;
+#endif
+ default: {
+ };
+ }
return OK;
}
Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn) {
- bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign");
- bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
-
- if ((!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) || force_builtin_codesign) {
- print_verbose("using built-in codesign...");
+ int codesign_tool = p_preset->get("codesign/codesign");
+ switch (codesign_tool) {
+ case 1: { // built-in ad-hoc
+ print_verbose("using built-in codesign...");
#ifdef MODULE_REGEX_ENABLED
-
-#ifdef MACOS_ENABLED
- if (p_preset->get("codesign/timestamp") && p_warn) {
- add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!"));
- }
- if (p_preset->get("codesign/hardened_runtime") && p_warn) {
- add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"));
- }
-#endif
-
- String error_msg;
- Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg);
- if (err != OK) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg));
- return FAILED;
- }
+ String error_msg;
+ Error err = CodeSign::codesign(false, true, p_path, p_ent_path, error_msg);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg));
+ return Error::FAILED;
+ }
#else
- add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module."));
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module."));
#endif
- return OK;
- } else {
- print_verbose("using external codesign...");
- List<String> args;
- if (p_preset->get("codesign/timestamp")) {
- if (ad_hoc) {
- if (p_warn) {
- add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!"));
- }
- } else {
- args.push_back("--timestamp");
+ } break;
+ case 2: { // "rcodesign"
+ print_verbose("using rcodesign codesign...");
+
+ String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
+ if (rcodesign.is_empty()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Xrcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign)."));
+ return Error::FAILED;
}
- }
- if (p_preset->get("codesign/hardened_runtime")) {
- if (ad_hoc) {
- if (p_warn) {
- add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"));
- }
+
+ List<String> args;
+ args.push_back("sign");
+
+ if (p_path.get_extension() != "dmg") {
+ args.push_back("--entitlements-xml-path");
+ args.push_back(p_ent_path);
+ }
+
+ String certificate_file = p_preset->get("codesign/certificate_file");
+ String certificate_pass = p_preset->get("codesign/certificate_password");
+ if (!certificate_file.is_empty() && !certificate_file.is_empty()) {
+ args.push_back("--p12-file");
+ args.push_back(certificate_file);
+ args.push_back("--p12-password");
+ args.push_back(certificate_pass);
+ }
+
+ args.push_back("-v"); /* provide some more feedback */
+
+ args.push_back(p_path);
+
+ String str;
+ int exitcode = 0;
+
+ Error err = OS::get_singleton()->execute(rcodesign, args, &str, &exitcode, true);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start rcodesign executable."));
+ return err;
+ }
+
+ if (exitcode != 0) {
+ print_line("rcodesign (" + p_path + "):\n" + str);
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Code signing failed, see editor log for details."));
+ return Error::FAILED;
} else {
+ print_verbose("rcodesign (" + p_path + "):\n" + str);
+ }
+ } break;
+#ifdef MACOS_ENABLED
+ case 3: { // "codesign"
+ print_verbose("using xcode codesign...");
+
+ if (!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Xcode command line tools are not installed."));
+ return Error::FAILED;
+ }
+
+ bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
+
+ List<String> args;
+ if (!ad_hoc) {
+ args.push_back("--timestamp");
args.push_back("--options");
args.push_back("runtime");
}
- }
- if (p_path.get_extension() != "dmg") {
- args.push_back("--entitlements");
- args.push_back(p_ent_path);
- }
-
- PackedStringArray user_args = p_preset->get("codesign/custom_options");
- for (int i = 0; i < user_args.size(); i++) {
- String user_arg = user_args[i].strip_edges();
- if (!user_arg.is_empty()) {
- args.push_back(user_arg);
+ if (p_path.get_extension() != "dmg") {
+ args.push_back("--entitlements");
+ args.push_back(p_ent_path);
}
- }
- args.push_back("-s");
- if (ad_hoc) {
- args.push_back("-");
- } else {
- args.push_back(p_preset->get("codesign/identity"));
- }
+ PackedStringArray user_args = p_preset->get("codesign/custom_options");
+ for (int i = 0; i < user_args.size(); i++) {
+ String user_arg = user_args[i].strip_edges();
+ if (!user_arg.is_empty()) {
+ args.push_back(user_arg);
+ }
+ }
- args.push_back("-v"); /* provide some more feedback */
+ args.push_back("-s");
+ if (ad_hoc) {
+ args.push_back("-");
+ } else {
+ args.push_back(p_preset->get("codesign/identity"));
+ }
- if (p_preset->get("codesign/replace_existing_signature")) {
+ args.push_back("-v"); /* provide some more feedback */
args.push_back("-f");
- }
- args.push_back(p_path);
+ args.push_back(p_path);
- String str;
- Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
- if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed."));
- return err;
- }
+ String str;
+ int exitcode = 0;
- print_verbose("codesign (" + p_path + "):\n" + str);
- if (str.find("no identity found") != -1) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found."));
- return FAILED;
- }
- if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid entitlements file."));
- return FAILED;
- }
- return OK;
+ Error err = OS::get_singleton()->execute("codesign", args, &str, nullptr, true);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed."));
+ return err;
+ }
+
+ if (exitcode != 0) {
+ print_line("codesign (" + p_path + "):\n" + str);
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Code signing failed, see editor log for details."));
+ return Error::FAILED;
+ } else {
+ print_verbose("codesign (" + p_path + "):\n" + str);
+ }
+ } break;
+#endif
+ default: {
+ };
}
+
+ return OK;
}
Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
@@ -816,7 +995,9 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
err = ERR_CANT_CREATE;
}
- DirAccess::remove_file_or_error(scr_path);
+ if (FileAccess::exists(scr_path)) {
+ DirAccess::remove_file_or_error(scr_path);
+ }
if (DirAccess::exists(tmp_app_path_name)) {
String old_dir = tmp_app_dir->get_current_dir();
if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) {
@@ -919,54 +1100,58 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
f->store_line("NSHumanReadableCopyright = \"" + p_preset->get("application/copyright").operator String() + "\";");
}
+ HashSet<String> languages;
for (const String &E : translations) {
Ref<Translation> tr = ResourceLoader::load(E);
- if (tr.is_valid()) {
- String lang = tr->get_locale();
- String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj";
- tmp_app_dir->make_dir_recursive(fname);
- Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
- f->store_line("/* Localized versions of Info.plist keys */");
- f->store_line("");
- if (appnames.has(lang)) {
- f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
- }
- if (microphone_usage_descriptions.has(lang)) {
- f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";");
- }
- if (camera_usage_descriptions.has(lang)) {
- f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";");
- }
- if (location_usage_descriptions.has(lang)) {
- f->store_line("NSLocationUsageDescription = \"" + location_usage_descriptions[lang].operator String() + "\";");
- }
- if (address_book_usage_descriptions.has(lang)) {
- f->store_line("NSContactsUsageDescription = \"" + address_book_usage_descriptions[lang].operator String() + "\";");
- }
- if (calendar_usage_descriptions.has(lang)) {
- f->store_line("NSCalendarsUsageDescription = \"" + calendar_usage_descriptions[lang].operator String() + "\";");
- }
- if (photos_library_usage_descriptions.has(lang)) {
- f->store_line("NSPhotoLibraryUsageDescription = \"" + photos_library_usage_descriptions[lang].operator String() + "\";");
- }
- if (desktop_folder_usage_descriptions.has(lang)) {
- f->store_line("NSDesktopFolderUsageDescription = \"" + desktop_folder_usage_descriptions[lang].operator String() + "\";");
- }
- if (documents_folder_usage_descriptions.has(lang)) {
- f->store_line("NSDocumentsFolderUsageDescription = \"" + documents_folder_usage_descriptions[lang].operator String() + "\";");
- }
- if (downloads_folder_usage_descriptions.has(lang)) {
- f->store_line("NSDownloadsFolderUsageDescription = \"" + downloads_folder_usage_descriptions[lang].operator String() + "\";");
- }
- if (network_volumes_usage_descriptions.has(lang)) {
- f->store_line("NSNetworkVolumesUsageDescription = \"" + network_volumes_usage_descriptions[lang].operator String() + "\";");
- }
- if (removable_volumes_usage_descriptions.has(lang)) {
- f->store_line("NSRemovableVolumesUsageDescription = \"" + removable_volumes_usage_descriptions[lang].operator String() + "\";");
- }
- if (copyrights.has(lang)) {
- f->store_line("NSHumanReadableCopyright = \"" + copyrights[lang].operator String() + "\";");
- }
+ if (tr.is_valid() && tr->get_locale() != "en") {
+ languages.insert(tr->get_locale());
+ }
+ }
+
+ for (const String &lang : languages) {
+ String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj";
+ tmp_app_dir->make_dir_recursive(fname);
+ Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ f->store_line("/* Localized versions of Info.plist keys */");
+ f->store_line("");
+ if (appnames.has(lang)) {
+ f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
+ }
+ if (microphone_usage_descriptions.has(lang)) {
+ f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (camera_usage_descriptions.has(lang)) {
+ f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (location_usage_descriptions.has(lang)) {
+ f->store_line("NSLocationUsageDescription = \"" + location_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (address_book_usage_descriptions.has(lang)) {
+ f->store_line("NSContactsUsageDescription = \"" + address_book_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (calendar_usage_descriptions.has(lang)) {
+ f->store_line("NSCalendarsUsageDescription = \"" + calendar_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (photos_library_usage_descriptions.has(lang)) {
+ f->store_line("NSPhotoLibraryUsageDescription = \"" + photos_library_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (desktop_folder_usage_descriptions.has(lang)) {
+ f->store_line("NSDesktopFolderUsageDescription = \"" + desktop_folder_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (documents_folder_usage_descriptions.has(lang)) {
+ f->store_line("NSDocumentsFolderUsageDescription = \"" + documents_folder_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (downloads_folder_usage_descriptions.has(lang)) {
+ f->store_line("NSDownloadsFolderUsageDescription = \"" + downloads_folder_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (network_volumes_usage_descriptions.has(lang)) {
+ f->store_line("NSNetworkVolumesUsageDescription = \"" + network_volumes_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (removable_volumes_usage_descriptions.has(lang)) {
+ f->store_line("NSRemovableVolumesUsageDescription = \"" + removable_volumes_usage_descriptions[lang].operator String() + "\";");
+ }
+ if (copyrights.has(lang)) {
+ f->store_line("NSHumanReadableCopyright = \"" + copyrights[lang].operator String() + "\";");
}
}
}
@@ -1144,7 +1329,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
err = save_pack(p_preset, p_debug, pack_path, &shared_objects);
// See if we can code sign our new package.
- bool sign_enabled = p_preset->get("codesign/enable");
+ bool sign_enabled = (p_preset->get("codesign/codesign").operator int() > 0);
String ent_path = p_preset->get("codesign/entitlements/custom_file");
String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + "_helper.entitlements");
@@ -1310,14 +1495,25 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
}
}
- bool ad_hoc = true;
- if (err == OK) {
+ bool ad_hoc = false;
+ int codesign_tool = p_preset->get("codesign/codesign");
+ switch (codesign_tool) {
+ case 1: { // built-in ad-hoc
+ ad_hoc = true;
+ } break;
+ case 2: { // "rcodesign"
+ ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty();
+ } break;
#ifdef MACOS_ENABLED
- String sign_identity = p_preset->get("codesign/identity");
-#else
- String sign_identity = "-";
+ case 3: { // "codesign"
+ ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
+ } break;
#endif
- ad_hoc = (sign_identity == "" || sign_identity == "-");
+ default: {
+ };
+ }
+
+ if (err == OK) {
bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation");
if ((!dylibs_found.is_empty() || !shared_objects.is_empty()) && sign_enabled && ad_hoc && !lib_validation) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries."));
@@ -1400,8 +1596,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
}
}
-#ifdef MACOS_ENABLED
- bool noto_enabled = p_preset->get("notarization/enable");
+ bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0);
if (err == OK && noto_enabled) {
if (export_format == "app") {
add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead."));
@@ -1412,10 +1607,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
err = _notarize(p_preset, p_path);
}
}
-#endif
// Clean up temporary entitlements files.
- DirAccess::remove_file_or_error(hlp_ent_path);
+ if (FileAccess::exists(hlp_ent_path)) {
+ DirAccess::remove_file_or_error(hlp_ent_path);
+ }
// Clean up temporary .app dir and generated entitlements.
if ((String)(p_preset->get("codesign/entitlements/custom_file")) == "") {
@@ -1550,7 +1746,7 @@ void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const Stri
da->list_dir_end();
}
-bool EditorExportPlatformMacOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformMacOS::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err;
bool valid = false;
@@ -1580,6 +1776,17 @@ bool EditorExportPlatformMacOS::can_export(const Ref<EditorExportPreset> &p_pres
valid = dvalid || rvalid;
r_missing_templates = !valid;
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ String err;
+ bool valid = true;
+
String identifier = p_preset->get("application/bundle_identifier");
String pn_err;
if (!is_package_name_valid(identifier, &pn_err)) {
@@ -1587,65 +1794,98 @@ bool EditorExportPlatformMacOS::can_export(const Ref<EditorExportPreset> &p_pres
valid = false;
}
- bool sign_enabled = p_preset->get("codesign/enable");
-
+ bool ad_hoc = false;
+ int codesign_tool = p_preset->get("codesign/codesign");
+ switch (codesign_tool) {
+ case 1: { // built-in ad-hoc
+ ad_hoc = true;
+ } break;
+ case 2: { // "rcodesign"
+ ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty();
+ } break;
#ifdef MACOS_ENABLED
- bool noto_enabled = p_preset->get("notarization/enable");
- bool ad_hoc = ((p_preset->get("codesign/identity") == "") || (p_preset->get("codesign/identity") == "-"));
-
- if (!ad_hoc && (bool)EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign")) {
- err += TTR("Warning: Built-in \"codesign\" is selected in the Editor Settings. Code signing is limited to ad-hoc signature only.") + "\n";
- }
- if (!ad_hoc && !FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
- err += TTR("Warning: Xcode command line tools are not installed, using built-in \"codesign\". Code signing is limited to ad-hoc signature only.") + "\n";
+ case 3: { // "codesign"
+ ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
+ } break;
+#endif
+ default: {
+ };
}
+ int notary_tool = p_preset->get("notarization/notarization");
- if (noto_enabled) {
+ if (notary_tool > 0) {
if (ad_hoc) {
err += TTR("Notarization: Notarization with an ad-hoc signature is not supported.") + "\n";
valid = false;
}
- if (!sign_enabled) {
+ if (codesign_tool == 0) {
err += TTR("Notarization: Code signing is required for notarization.") + "\n";
valid = false;
}
- if (!(bool)p_preset->get("codesign/hardened_runtime")) {
- err += TTR("Notarization: Hardened runtime is required for notarization.") + "\n";
- valid = false;
- }
- if (!(bool)p_preset->get("codesign/timestamp")) {
- err += TTR("Notarization: Timestamping is required for notarization.") + "\n";
- valid = false;
- }
- if (p_preset->get("notarization/apple_id_name") == "") {
- err += TTR("Notarization: Apple ID name not specified.") + "\n";
- valid = false;
- }
- if (p_preset->get("notarization/apple_id_password") == "") {
- err += TTR("Notarization: Apple ID password not specified.") + "\n";
- valid = false;
+ if (notary_tool == 2) {
+ if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) {
+ err += TTR("Notarization: Xcode command line tools are not installed.") + "\n";
+ valid = false;
+ }
+ if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") {
+ err += TTR("Notarization: Neither Apple ID name nor App Store Connect issuer ID name not specified.") + "\n";
+ valid = false;
+ } else if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") {
+ err += TTR("Notarization: Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time.") + "\n";
+ valid = false;
+ } else {
+ if (p_preset->get("notarization/apple_id_name") != "") {
+ if (p_preset->get("notarization/apple_id_password") == "") {
+ err += TTR("Notarization: Apple ID password not specified.") + "\n";
+ }
+ valid = false;
+ }
+ if (p_preset->get("notarization/api_uuid") != "") {
+ if (p_preset->get("notarization/api_key") == "") {
+ err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n";
+ valid = false;
+ }
+ }
+ }
+ } else if (notary_tool == 1) {
+ if (p_preset->get("notarization/api_uuid") == "") {
+ err += TTR("Notarization: App Store Connect issuer ID name not specified.") + "\n";
+ valid = false;
+ }
+ if (p_preset->get("notarization/api_key") == "") {
+ err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n";
+ valid = false;
+ }
+
+ String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
+ if (rcodesign.is_empty()) {
+ err += TTR("Notarization: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign).") + "\n";
+ valid = false;
+ }
}
} else {
err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n";
- if (!sign_enabled) {
+ if (codesign_tool == 0) {
err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
- } else {
- if ((bool)p_preset->get("codesign/hardened_runtime") && ad_hoc) {
- err += TTR("Hardened Runtime is not compatible with ad-hoc signature, and will be disabled!") + "\n";
- }
- if ((bool)p_preset->get("codesign/timestamp") && ad_hoc) {
- err += TTR("Timestamping is not compatible with ad-hoc signature, and will be disabled!") + "\n";
- }
}
}
-#else
- err += TTR("Warning: Notarization is not supported from this OS. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n";
- if (!sign_enabled) {
- err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n";
- }
-#endif
- if (sign_enabled) {
+ if (codesign_tool > 0) {
+ if (ad_hoc) {
+ err += TTR("Code signing: Using ad-hoc signature. The exported project will be blocked by Gatekeeper") + "\n";
+ }
+ if (codesign_tool == 3) {
+ if (!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
+ err += TTR("Code signing: Xcode command line tools are not installed.") + "\n";
+ valid = false;
+ }
+ } else if (codesign_tool == 2) {
+ String rcodesign = EditorSettings::get_singleton()->get("export/macos/rcodesign").operator String();
+ if (rcodesign.is_empty()) {
+ err += TTR("Code signing: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign).") + "\n";
+ valid = false;
+ }
+ }
if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) {
err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n";
valid = false;
diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h
index 21bc380d55..87790129d3 100644
--- a/platform/macos/export/export_plugin.h
+++ b/platform/macos/export/export_plugin.h
@@ -101,7 +101,7 @@ class EditorExportPlatformMacOS : public EditorExportPlatform {
protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
virtual void get_export_options(List<ExportOption> *r_options) override;
- virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
+ virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
public:
virtual String get_name() const override { return "macOS"; }
@@ -119,7 +119,8 @@ public:
}
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual void get_platform_features(List<String> *r_features) const override {
r_features->push_back("pc");
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index 2c12897f10..e0b9f41632 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -46,7 +46,6 @@ enum GlobalMenuCheckType {
@public
Callable callback;
Variant meta;
- int id;
GlobalMenuCheckType checkable_type;
int max_states;
int state;
diff --git a/platform/uwp/README.md b/platform/uwp/README.md
new file mode 100644
index 0000000000..575f90e3c7
--- /dev/null
+++ b/platform/uwp/README.md
@@ -0,0 +1,20 @@
+# UWP platform port
+
+> **Warning**
+>
+> The UWP platform port is not currently in a working state for the `master`
+> branch, and may be dropped in the future.
+
+This folder contains the C++ code for the Universal Windows Platform (UWP)
+platform port. **This is not to be confused with the "standard" Win32 port**,
+which is available in [`platform/windows`](/platform/windows).
+
+See also [`misc/dist/uwp_template`](/misc/dist/uwp_template) folder for the UWP
+project template used for packaging the UWP export templates.
+
+## Documentation
+
+- [Compiling for Universal Windows Platform](https://docs.godotengine.org/en/latest/development/compiling/compiling_for_uwp.html)
+ - Instructions on building this platform port from source.
+- [Exporting for Universal Windows Platform](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_uwp.html)
+ - Instructions on using the compiled export templates to export a project.
diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py
index 9c91378b22..2c5746cb06 100644
--- a/platform/uwp/detect.py
+++ b/platform/uwp/detect.py
@@ -1,6 +1,7 @@
import methods
import os
import sys
+from platform_methods import detect_arch
def is_active():
@@ -31,6 +32,7 @@ def get_opts():
def get_flags():
return [
+ ("arch", detect_arch()),
("tools", False),
("xaudio2", True),
("builtin_pcre2_with_jit", False),
@@ -38,19 +40,17 @@ def get_flags():
def configure(env):
- env.msvc = True
-
- if env["bits"] != "default":
- print("Error: bits argument is disabled for MSVC")
+ # Validate arch.
+ supported_arches = ["x86_32", "x86_64", "arm32"]
+ if env["arch"] not in supported_arches:
print(
- """
- Bits argument is not supported for MSVC compilation. Architecture depends on the Native/Cross Compile Tools Prompt/Developer Console
- (or Visual Studio settings) that is being used to run SCons. As a consequence, bits argument is disabled. Run scons again without bits
- argument (example: scons p=uwp) and SCons will attempt to detect what MSVC compiler will be executed and inform you.
- """
+ 'Unsupported CPU architecture "%s" for UWP. Supported architectures are: %s.'
+ % (env["arch"], ", ".join(supported_arches))
)
sys.exit()
+ env.msvc = True
+
## Build type
if env["target"] == "release":
@@ -101,11 +101,10 @@ def configure(env):
arch = ""
if str(os.getenv("Platform")).lower() == "arm":
-
- print("Compiled program architecture will be an ARM executable. (forcing bits=32).")
+ print("Compiled program architecture will be an ARM executable (forcing arch=arm32).")
arch = "arm"
- env["bits"] = "32"
+ env["arch"] = "arm32"
env.Append(LINKFLAGS=["/MACHINE:ARM"])
env.Append(LIBPATH=[vc_base_path + "lib/store/arm"])
@@ -117,20 +116,20 @@ def configure(env):
compiler_version_str = methods.detect_visual_c_compiler_version(env["ENV"])
if compiler_version_str == "amd64" or compiler_version_str == "x86_amd64":
- env["bits"] = "64"
- print("Compiled program architecture will be a x64 executable (forcing bits=64).")
+ env["arch"] = "x86_64"
+ print("Compiled program architecture will be a x64 executable (forcing arch=x86_64).")
elif compiler_version_str == "x86" or compiler_version_str == "amd64_x86":
- env["bits"] = "32"
- print("Compiled program architecture will be a x86 executable. (forcing bits=32).")
+ env["arch"] = "x86_32"
+ print("Compiled program architecture will be a x86 executable (forcing arch=x86_32).")
else:
print(
- "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings"
- " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture"
+ "Failed to detect MSVC compiler architecture version... Defaulting to x86 32-bit executable settings"
+ " (forcing arch=x86_32). Compilation attempt will continue, but SCons can not detect for what architecture"
" this build is compiled for. You should check your settings/compilation setup."
)
- env["bits"] = "32"
+ env["arch"] = "x86_32"
- if env["bits"] == "32":
+ if env["arch"] == "x86_32":
arch = "x86"
angle_build_cmd += "Win32"
diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp
index 070c46242f..4e4afb9704 100644
--- a/platform/uwp/export/export_plugin.cpp
+++ b/platform/uwp/export/export_plugin.cpp
@@ -121,7 +121,7 @@ void EditorExportPlatformUWP::get_export_options(List<ExportOption> *r_options)
}
}
-bool EditorExportPlatformUWP::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformUWP::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
#ifndef DEV_ENABLED
// We don't provide export templates for the UWP platform currently as it
// has not been ported for Godot 4.0. This is skipped in DEV_ENABLED so that
@@ -163,7 +163,26 @@ bool EditorExportPlatformUWP::can_export(const Ref<EditorExportPreset> &p_preset
valid = dvalid || rvalid;
r_missing_templates = !valid;
- // Validate the rest of the configuration.
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformUWP::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+#ifndef DEV_ENABLED
+ // We don't provide export templates for the UWP platform currently as it
+ // has not been ported for Godot 4.0. This is skipped in DEV_ENABLED so that
+ // contributors can still test the pipeline if/when we can build it again.
+ r_error = "The UWP platform is currently not supported in Godot 4.0.\n";
+ return false;
+#endif
+
+ String err;
+ bool valid = true;
+
+ // Validate the project configuration.
if (!_valid_resource_name(p_preset->get("package/short_name"))) {
valid = false;
diff --git a/platform/uwp/export/export_plugin.h b/platform/uwp/export/export_plugin.h
index 4a3c5db377..71d0479543 100644
--- a/platform/uwp/export/export_plugin.h
+++ b/platform/uwp/export/export_plugin.h
@@ -429,7 +429,8 @@ public:
virtual void get_export_options(List<ExportOption> *r_options) override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
diff --git a/platform/windows/README.md b/platform/windows/README.md
new file mode 100644
index 0000000000..17ce6f216b
--- /dev/null
+++ b/platform/windows/README.md
@@ -0,0 +1,15 @@
+# Windows platform port
+
+This folder contains the C++ and JavaScript code for the Windows platform port.
+
+See also [`misc/dist/windows`](/misc/dist/windows) folder for additional files
+used by this platform.
+
+## Documentation
+
+- [Compiling for Windows](https://docs.godotengine.org/en/latest/development/compiling/compiling_for_windows.html)
+ - Instructions on building this platform port from source.
+- [Exporting for Windows](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_windows.html)
+ - Instructions on using the compiled export templates to export a project.
+- [Changing application icon for Windows](https://docs.godotengine.org/en/stable/tutorials/export/changing_application_icon_for_windows.html)
+ - Instructions on using a custom icon for the exported project executable.
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index dd2df1f004..6dd6892757 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -1,5 +1,6 @@
import methods
import os
+from platform_methods import detect_arch
# To match other platforms
STACK_SIZE = 8388608
@@ -46,6 +47,7 @@ def can_build():
def get_opts():
from SCons.Variables import BoolVariable, EnumVariable
+ # TODO: These shouldn't be hard-coded for x86.
mingw32 = ""
mingw64 = ""
if os.name == "posix":
@@ -77,11 +79,14 @@ def get_opts():
def get_flags():
- return []
+ return [
+ ("arch", detect_arch()),
+ ]
def build_res_file(target, source, env):
- if env["bits"] == "32":
+ # TODO: This shouldn't be hard-coded for x86.
+ if env["arch"] == "x86_32":
cmdbase = env["mingw_prefix_32"]
else:
cmdbase = env["mingw_prefix_64"]
@@ -100,21 +105,27 @@ def build_res_file(target, source, env):
def setup_msvc_manual(env):
+ # FIXME: This is super hacky, and probably obsolete.
+ # Won't work with detect_arch() used for `arch` by default.
+
"""Set up env to use MSVC manually, using VCINSTALLDIR"""
- if env["bits"] != "default":
+ if env["arch"] != "auto":
print(
"""
- Bits argument is not supported for MSVC compilation. Architecture depends on the Native/Cross Compile Tools Prompt/Developer Console
- (or Visual Studio settings) that is being used to run SCons. As a consequence, bits argument is disabled. Run scons again without bits
- argument (example: scons p=windows) and SCons will attempt to detect what MSVC compiler will be executed and inform you.
+ Arch argument is not supported for MSVC manual configuration (VCINSTALLDIR configured).
+ Architecture depends on the Native/Cross Compile Tools Prompt/Developer Console (or Visual Studio settings) that is being used to run SCons.
+ As a consequence, the arch argument is disabled. Run scons again without arch argument (example: scons p=windows)
+ and SCons will attempt to detect what MSVC compiler will be executed and inform you.
"""
)
- raise SCons.Errors.UserError("Bits argument should not be used when using VCINSTALLDIR")
+ raise SCons.Errors.UserError("Arch argument should not be used when using VCINSTALLDIR")
- # Force bits arg
+ # Force ARCH arg
# (Actually msys2 mingw can support 64-bit, we could detect that)
- env["bits"] = "32"
- env["x86_libtheora_opt_vc"] = True
+ # TODO: This is wrong, but not sure what to do about it.
+ # We want to determine the arch and bitness in the SConstruct only.
+ # We can check if it's correct in here, but if it's not, it should
+ # just fail with an error message instead of trying to force it.
# find compiler manually
compiler_version_str = methods.detect_visual_c_compiler_version(env["ENV"])
@@ -122,17 +133,19 @@ def setup_msvc_manual(env):
# If building for 64bit architecture, disable assembly optimisations for 32 bit builds (theora as of writing)... vc compiler for 64bit can not compile _asm
if compiler_version_str == "amd64" or compiler_version_str == "x86_amd64":
- env["bits"] = "64"
+ env["arch"] = "x86_64"
env["x86_libtheora_opt_vc"] = False
- print("Compiled program architecture will be a 64 bit executable (forcing bits=64).")
+ print("Compiled program architecture will be a 64 bit executable (forcing arch=x86_64).")
elif compiler_version_str == "x86" or compiler_version_str == "amd64_x86":
- print("Compiled program architecture will be a 32 bit executable. (forcing bits=32).")
+ env["arch"] = "x86_32"
+ env["x86_libtheora_opt_vc"] = True
+ print("Compiled program architecture will be a 32 bit executable (forcing arch=x86_32).")
else:
print(
- "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings"
- " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this"
- " build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR."
+ "Failed to manually detect MSVC compiler architecture version.\n"
+ "You should check your settings/compilation setup, or avoid setting VCINSTALLDIR."
)
+ sys.exit()
def setup_msvc_auto(env):
@@ -141,6 +154,18 @@ def setup_msvc_auto(env):
# If MSVC_VERSION is set by SCons, we know MSVC is installed.
# But we may want a different version or target arch.
+ # Valid architectures for MSVC's TARGET_ARCH:
+ # ['amd64', 'emt64', 'i386', 'i486', 'i586', 'i686', 'ia64', 'itanium', 'x86', 'x86_64', 'arm', 'arm64', 'aarch64']
+ # Our x86_64 and arm64 are the same, and we need to map the 32-bit
+ # architectures to other names since MSVC isn't as explicit.
+ # The rest we don't need to worry about because they are
+ # aliases or aren't supported by Godot (itanium & ia64).
+ msvc_arch_aliases = {"x86_32": "x86", "arm32": "arm"}
+ if env["arch"] in msvc_arch_aliases.keys():
+ env["TARGET_ARCH"] = msvc_arch_aliases[env["arch"]]
+ else:
+ env["TARGET_ARCH"] = env["arch"]
+
# The env may have already been set up with default MSVC tools, so
# reset a few things so we can set it up with the tools we want.
# (Ideally we'd decide on the tool config before configuring any
@@ -149,21 +174,14 @@ def setup_msvc_auto(env):
env["MSVC_SETUP_RUN"] = False # Need to set this to re-run the tool
env["MSVS_VERSION"] = None
env["MSVC_VERSION"] = None
- env["TARGET_ARCH"] = None
- if env["bits"] != "default":
- env["TARGET_ARCH"] = {"32": "x86", "64": "x86_64"}[env["bits"]]
+
if "msvc_version" in env:
env["MSVC_VERSION"] = env["msvc_version"]
env.Tool("msvc")
env.Tool("mssdk") # we want the MS SDK
# Note: actual compiler version can be found in env['MSVC_VERSION'], e.g. "14.1" for VS2015
- # Get actual target arch into bits (it may be "default" at this point):
- if env["TARGET_ARCH"] in ("amd64", "x86_64"):
- env["bits"] = "64"
- else:
- env["bits"] = "32"
- print("Found MSVC version %s, arch %s, bits=%s" % (env["MSVC_VERSION"], env["TARGET_ARCH"], env["bits"]))
- if env["TARGET_ARCH"] in ("amd64", "x86_64"):
+ print("Found MSVC version %s, arch %s" % (env["MSVC_VERSION"], env["TARGET_ARCH"]))
+ if env["arch"] == "x86_32":
env["x86_libtheora_opt_vc"] = False
@@ -244,7 +262,7 @@ def configure_msvc(env, manual_msvc_config):
]
)
env.AppendUnique(CPPDEFINES=["NOMINMAX"]) # disable bogus min/max WinDef.h macros
- if env["bits"] == "64":
+ if env["arch"] == "x86_64":
env.AppendUnique(CPPDEFINES=["_WIN64"])
## Libs
@@ -328,10 +346,10 @@ def configure_mingw(env):
env.Append(CCFLAGS=["-msse2"])
if env["optimize"] == "speed": # optimize for speed (default)
- if env["bits"] == "64":
- env.Append(CCFLAGS=["-O3"])
- else:
+ if env["arch"] == "x86_32":
env.Append(CCFLAGS=["-O2"])
+ else:
+ env.Append(CCFLAGS=["-O3"])
else: # optimize for size
env.Prepend(CCFLAGS=["-Os"])
@@ -365,15 +383,12 @@ def configure_mingw(env):
if os.name != "nt":
env["PROGSUFFIX"] = env["PROGSUFFIX"] + ".exe" # for linux cross-compilation
- if env["bits"] == "default":
- if os.name == "nt":
- env["bits"] = "64" if "PROGRAMFILES(X86)" in os.environ else "32"
- else: # default to 64-bit on Linux
- env["bits"] = "64"
-
mingw_prefix = ""
- if env["bits"] == "32":
+ # TODO: This doesn't seem to be working, or maybe I just have
+ # MinGW set up incorrectly. It always gives me x86_64 builds,
+ # even though arch == "x86_32" and the file name has x86_32 in it.
+ if env["arch"] == "x86_32":
if env["use_static_cpp"]:
env.Append(LINKFLAGS=["-static"])
env.Append(LINKFLAGS=["-static-libgcc"])
@@ -460,11 +475,18 @@ def configure_mingw(env):
def configure(env):
+ # Validate arch.
+ supported_arches = ["x86_32", "x86_64", "arm32", "arm64"]
+ if env["arch"] not in supported_arches:
+ print(
+ 'Unsupported CPU architecture "%s" for Windows. Supported architectures are: %s.'
+ % (env["arch"], ", ".join(supported_arches))
+ )
+ sys.exit()
+
# At this point the env has been set up with basic tools/compilers.
env.Prepend(CPPPATH=["#platform/windows"])
- print("Configuring for Windows: target=%s, bits=%s" % (env["target"], env["bits"]))
-
if os.name == "nt":
env["ENV"] = os.environ # this makes build less repeatable, but simplifies some things
env["ENV"]["TMP"] = os.environ["TMP"]
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 8c8dbef8a4..7eb61b3038 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -144,8 +144,8 @@ bool DisplayServerWindows::tts_is_paused() const {
return tts->is_paused();
}
-Array DisplayServerWindows::tts_get_voices() const {
- ERR_FAIL_COND_V(!tts, Array());
+TypedArray<Dictionary> DisplayServerWindows::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, TypedArray<Dictionary>());
return tts->get_voices();
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index db9b589304..556ce9ff5d 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -478,7 +478,7 @@ public:
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index febef5ad12..cb0bd44cfb 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -113,7 +113,7 @@ List<String> EditorExportPlatformWindows::get_binary_extensions(const Ref<Editor
return list;
}
-bool EditorExportPlatformWindows::get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
// This option is not supported by "osslsigncode", used on non-Windows host.
if (!OS::get_singleton()->has_feature("windows") && p_option == "codesign/identity_type") {
return false;
@@ -128,7 +128,7 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA1 hash)"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password", PROPERTY_HINT_PASSWORD), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/timestamp_server_url"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/digest_algorithm", PROPERTY_HINT_ENUM, "SHA1,SHA256"), 1));
@@ -231,7 +231,7 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset
String str;
Error err = OS::get_singleton()->execute(rcedit_path, args, &str, nullptr, true);
if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable. Configure rcedit path in the Editor Settings (Export > Windows > Rcedit), or disable \"Application > Modify Resources\" in the export preset."));
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable. Configure rcedit path in the Editor Settings (Export > Windows > rcedit), or disable \"Application > Modify Resources\" in the export preset."));
return err;
}
print_line("rcedit (" + p_path + "): " + str);
@@ -379,7 +379,11 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
String str;
Error err = OS::get_singleton()->execute(signtool_path, args, &str, nullptr, true);
if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start signtool executable. Configure signtool path in the Editor Settings (Export > Windows > Signtool), or disable \"Codesign\" in the export preset."));
+#ifndef WINDOWS_ENABLED
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start signtool executable. Configure signtool path in the Editor Settings (Export > Windows > signtool), or disable \"Codesign\" in the export preset."));
+#else
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start osslsigncode executable. Configure signtool path in the Editor Settings (Export > Windows > osslsigncode), or disable \"Codesign\" in the export preset."));
+#endif
return err;
}
@@ -412,15 +416,26 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
return OK;
}
-bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformWindows::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err = "";
- bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates);
+ bool valid = EditorExportPlatformPC::has_valid_export_configuration(p_preset, err, r_missing_templates);
String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
if (p_preset->get("application/modify_resources") && rcedit_path.is_empty()) {
- err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n";
+ err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > rcedit) to change the icon or app information data.") + "\n";
+ }
+
+ if (!err.is_empty()) {
+ r_error = err;
}
+ return valid;
+}
+
+bool EditorExportPlatformWindows::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ String err = "";
+ bool valid = true;
+
String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon"));
if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) {
err += TTR("Invalid icon path:") + " " + icon_path + "\n";
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index b9e59829a0..f85331c898 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -48,8 +48,9 @@ public:
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual void get_export_options(List<ExportOption> *r_options) override;
- virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
+ virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
};
diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py
index 22e33b51b4..33ca2e8ffa 100644
--- a/platform/windows/platform_windows_builders.py
+++ b/platform/windows/platform_windows_builders.py
@@ -9,7 +9,7 @@ from platform_methods import subprocess_main
def make_debug_mingw(target, source, env):
mingw_prefix = ""
- if env["bits"] == "32":
+ if env["arch"] == "x86_32":
mingw_prefix = env["mingw_prefix_32"]
else:
mingw_prefix = env["mingw_prefix_64"]