diff options
Diffstat (limited to 'platform')
44 files changed, 1471 insertions, 488 deletions
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp index f544f29b10..f80f1e3051 100644 --- a/platform/android/api/api.cpp +++ b/platform/android/api/api.cpp @@ -64,14 +64,14 @@ void JavaClassWrapper::_bind_methods() { #if !defined(ANDROID_ENABLED) -Variant JavaClass::call(const StringName &, const Variant **, int, Callable::CallError &) { +Variant JavaClass::callp(const StringName &, const Variant **, int, Callable::CallError &) { return Variant(); } JavaClass::JavaClass() { } -Variant JavaObject::call(const StringName &, const Variant **, int, Callable::CallError &) { +Variant JavaObject::callp(const StringName &, const Variant **, int, Callable::CallError &) { return Variant(); } diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index d4d1208757..96b7b48e48 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -179,7 +179,7 @@ class JavaClass : public RefCounted { #endif public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; JavaClass(); }; @@ -195,7 +195,7 @@ class JavaObject : public RefCounted { #endif public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; #ifdef ANDROID_ENABLED JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance); diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index 57d08ac83e..74ca10e5e2 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -52,7 +52,7 @@ class JNISingleton : public Object { #endif public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { #ifdef ANDROID_ENABLED Map<StringName, MethodData>::Element *E = method_map.find(p_method); @@ -70,7 +70,7 @@ public: if (call_error) { // The method is not in this map, defaulting to the regular instance calls. - return Object::call(p_method, p_args, p_argcount, r_error); + return Object::callp(p_method, p_args, p_argcount, r_error); } ERR_FAIL_COND_V(!instance, Variant()); @@ -176,7 +176,7 @@ public: #else // ANDROID_ENABLED // Defaulting to the regular instance calls. - return Object::call(p_method, p_args, p_argcount, r_error); + return Object::callp(p_method, p_args, p_argcount, r_error); #endif } diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index ca6a45cb83..df3693ba61 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1387,6 +1387,7 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> & Vector<String> string_table; String package_name = p_preset->get("package/name"); + Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); for (uint32_t i = 0; i < string_count; i++) { uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]); @@ -1401,9 +1402,8 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> & } else { String lang = str.substr(str.rfind("-") + 1, str.length()).replace("-", "_"); - String prop = "application/config/name_" + lang; - if (ProjectSettings::get_singleton()->has_setting(prop)) { - str = ProjectSettings::get_singleton()->get(prop); + if (appnames.has(lang)) { + str = appnames[lang]; } else { str = get_project_name(package_name); } @@ -2256,9 +2256,9 @@ String EditorExportPlatformAndroid::get_apk_expansion_fullpath(const Ref<EditorE return fullpath; } -Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { String fullpath = get_apk_expansion_fullpath(p_preset, p_path); - Error err = save_pack(p_preset, fullpath); + Error err = save_pack(p_preset, p_debug, fullpath); return err; } @@ -2576,7 +2576,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP CustomExportData user_data; user_data.assets_directory = assets_directory; user_data.debug = p_debug; - err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); + err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); if (err != OK) { EditorNode::add_io_error(TTR("Could not export project files to gradle project\n")); return err; @@ -2589,7 +2589,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } } else { print_verbose("Saving apk expansion file.."); - err = save_apk_expansion_file(p_preset, p_path); + err = save_apk_expansion_file(p_preset, p_debug, p_path); if (err != OK) { EditorNode::add_io_error(TTR("Could not write expansion package file!")); return err; @@ -2915,10 +2915,10 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so); + err = export_project_files(p_preset, p_debug, ignore_apk_file, &ed, save_apk_so); } else { if (apk_expansion) { - err = save_apk_expansion_file(p_preset, p_path); + err = save_apk_expansion_file(p_preset, p_debug, p_path); if (err != OK) { EditorNode::add_io_error(TTR("Could not write expansion package file!")); return err; @@ -2927,7 +2927,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so); + err = export_project_files(p_preset, p_debug, save_apk_file, &ed, save_apk_so); } } diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index a4eb608b19..0f267cf13a 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -210,7 +210,7 @@ public: String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path); - Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path); + Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags); diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 9598d2f9fd..ab915a5f85 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -75,11 +75,10 @@ String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_or // Utility method used to create a directory. Error create_directory(const String &p_dir) { if (!DirAccess::exists(p_dir)) { - DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + DirAccessRef filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES); ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'."); Error err = filesystem_da->make_dir_recursive(p_dir); ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'."); - memdelete(filesystem_da); } return OK; } @@ -161,6 +160,7 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset return ERR_CANT_OPEN; } da->list_dir_begin(); + Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); while (true) { String file = da->get_next(); if (file.is_empty()) { @@ -171,10 +171,9 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset continue; } String locale = file.replace("values-", "").replace("-r", "_"); - String property_name = "application/config/name_" + locale; String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml"; - if (ProjectSettings::get_singleton()->has_setting(property_name)) { - String locale_project_name = ProjectSettings::get_singleton()->get(property_name); + if (appnames.has(locale)) { + String locale_project_name = appnames[locale]; String processed_xml_string = vformat(godot_project_name_xml_string, _android_xml_escape(locale_project_name)); print_verbose("Storing project name for locale " + locale + " under " + locale_directory); store_string_at_path(locale_directory, processed_xml_string); diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 32e03998da..c238d1b361 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -4,7 +4,7 @@ ext.versions = [ minSdk : 19, // Also update 'platform/android/java/lib/AndroidManifest.xml#minSdkVersion' & 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION' targetSdk : 30, // Also update 'platform/android/java/lib/AndroidManifest.xml#targetSdkVersion' & 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION' buildTools : '30.0.3', - kotlinVersion : '1.5.10', + kotlinVersion : '1.6.10', fragmentVersion : '1.3.6', javaVersion : 11, ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. @@ -86,13 +86,12 @@ ext.getGodotEditorVersion = { -> return editorVersion } -ext.getGodotLibraryVersion = { -> +ext.generateGodotLibraryVersion = { List<String> requiredKeys -> // Attempt to read the version from the `version.py` file. String libraryVersion = "" File versionFile = new File("../../../version.py") if (versionFile.isFile()) { - List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"] def map = [:] List<String> lines = versionFile.readLines() @@ -121,6 +120,16 @@ ext.getGodotLibraryVersion = { -> return libraryVersion } +ext.getGodotLibraryVersion = { -> + List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"] + return generateGodotLibraryVersion(requiredKeys) +} + +ext.getGodotPublishVersion = { -> + List<String> requiredKeys = ["major", "minor", "patch", "status"] + return generateGodotLibraryVersion(requiredKeys) +} + final String VALUE_SEPARATOR_REGEX = "\\|" // get the list of ABIs the project should be exported to diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index ac008edbed..83bc68c992 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -1,4 +1,6 @@ +apply plugin: 'io.github.gradle-nexus.publish-plugin' apply from: 'app/config.gradle' +apply from: 'scripts/publish-root.gradle' buildscript { apply from: 'app/config.gradle' @@ -6,10 +8,12 @@ buildscript { repositories { google() mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath libraries.androidGradlePlugin classpath libraries.kotlinGradlePlugin + classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' } } diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index fbed4ed078..120a40a31d 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -1,6 +1,13 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +ext { + PUBLISH_VERSION = getGodotPublishVersion() + PUBLISH_ARTIFACT_ID = 'godot' +} + +apply from: "../scripts/publish-module.gradle" + dependencies { implementation libraries.kotlinStdLib implementation libraries.androidxFragment @@ -21,6 +28,8 @@ android { manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersion()] } + namespace = "org.godotengine.godot" + compileOptions { sourceCompatibility versions.javaVersion targetCompatibility versions.javaVersion @@ -111,4 +120,12 @@ android { // Schedule the tasks so the generated libs are present before the aar file is packaged. tasks["merge${buildType}JniLibFolders"].dependsOn taskName } + + // TODO: Enable when issues with AGP 7.1+ are resolved (https://github.com/GodotVR/godot_openxr/issues/187). +// publishing { +// singleVariant("release") { +// withSourcesJar() +// withJavadocJar() +// } +// } } diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt index 34925684da..966c02f7d7 100644 --- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt +++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt @@ -15,5 +15,4 @@ file(GLOB_RECURSE HEADERS ${GODOT_ROOT_DIR}/*.h**) add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC - ${GODOT_ROOT_DIR} - ${GODOT_ROOT_DIR}/modules/gdnative/include) + ${GODOT_ROOT_DIR}) diff --git a/platform/android/java/scripts/publish-module.gradle b/platform/android/java/scripts/publish-module.gradle new file mode 100644 index 0000000000..6b2aea5caf --- /dev/null +++ b/platform/android/java/scripts/publish-module.gradle @@ -0,0 +1,74 @@ +apply plugin: 'maven-publish' +apply plugin: 'signing' + +group = ossrhGroupId +version = PUBLISH_VERSION + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + // The coordinates of the library, being set from variables that + // we'll set up later + groupId ossrhGroupId + artifactId PUBLISH_ARTIFACT_ID + version PUBLISH_VERSION + + // Two artifacts, the `aar` (or `jar`) and the sources + if (project.plugins.findPlugin("com.android.library")) { + from components.release + } else { + from components.java + } + + // Mostly self-explanatory metadata + pom { + name = PUBLISH_ARTIFACT_ID + description = 'Godot Engine Android Library' + url = 'https://godotengine.org/' + licenses { + license { + name = 'MIT License' + url = 'https://github.com/godotengine/godot/blob/master/LICENSE.txt' + } + } + developers { + developer { + id = 'm4gr3d' + name = 'Fredia Huya-Kouadio' + email = 'fhuyakou@gmail.com' + } + developer { + id = 'reduz' + name = 'Juan Linietsky' + email = 'reduzio@gmail.com' + } + developer { + id = 'akien-mga' + name = 'Rémi Verschelde' + email = 'rverschelde@gmail.com' + } + // Add all other devs here... + } + + // Version control info - if you're using GitHub, follow the + // format as seen here + scm { + connection = 'scm:git:github.com/godotengine/godot.git' + developerConnection = 'scm:git:ssh://github.com/godotengine/godot.git' + url = 'https://github.com/godotengine/godot/tree/master' + } + } + } + } + } +} + +signing { + useInMemoryPgpKeys( + rootProject.ext["signing.keyId"], + rootProject.ext["signing.key"], + rootProject.ext["signing.password"], + ) + sign publishing.publications +} diff --git a/platform/android/java/scripts/publish-root.gradle b/platform/android/java/scripts/publish-root.gradle new file mode 100644 index 0000000000..ae88487c34 --- /dev/null +++ b/platform/android/java/scripts/publish-root.gradle @@ -0,0 +1,39 @@ +// Create variables with empty default values +ext["signing.keyId"] = '' +ext["signing.password"] = '' +ext["signing.key"] = '' +ext["ossrhGroupId"] = '' +ext["ossrhUsername"] = '' +ext["ossrhPassword"] = '' +ext["sonatypeStagingProfileId"] = '' + +File secretPropsFile = project.rootProject.file('local.properties') +if (secretPropsFile.exists()) { + // Read local.properties file first if it exists + Properties p = new Properties() + new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } + p.each { name, value -> ext[name] = value } +} else { + // Use system environment variables + ext["ossrhGroupId"] = System.getenv('OSSRH_GROUP_ID') + ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') + ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') + ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') + ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') + ext["signing.password"] = System.getenv('SIGNING_PASSWORD') + ext["signing.key"] = System.getenv('SIGNING_KEY') +} + +// Set up Sonatype repository +nexusPublishing { + repositories { + sonatype { + stagingProfileId = sonatypeStagingProfileId + username = ossrhUsername + password = ossrhPassword + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + } + } +} + diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 7c788b4dc4..1805807f90 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -485,14 +485,14 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, return success; } -Variant JavaClass::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { Variant ret; bool found = _call_method(nullptr, p_method, p_args, p_argcount, r_error, ret); if (found) { return ret; } - return RefCounted::call(p_method, p_args, p_argcount, r_error); + return RefCounted::callp(p_method, p_args, p_argcount, r_error); } JavaClass::JavaClass() { @@ -500,7 +500,7 @@ JavaClass::JavaClass() { ///////////////////// -Variant JavaObject::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { +Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { return Variant(); } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index dd4fa9de7b..249717921f 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -446,7 +446,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en } Callable::CallError err; - obj->call(str_method, (const Variant **)vptr, count, err); + obj->callp(str_method, (const Variant **)vptr, count, err); // something env->PopLocalFrame(nullptr); @@ -462,18 +462,20 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * String str_method = jstring_to_string(method, env); int count = env->GetArrayLength(params); - Variant args[VARIANT_ARG_MAX]; - for (int i = 0; i < MIN(count, VARIANT_ARG_MAX); i++) { + Variant *args = (Variant *)alloca(sizeof(Variant) * count); + const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * count); + + for (int i = 0; i < count; i++) { jobject obj = env->GetObjectArrayElement(params, i); if (obj) { args[i] = _jobject_to_variant(env, obj); } env->DeleteLocalRef(obj); + argptrs[i] = &args[i]; } - static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); - obj->call_deferred(str_method, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); + MessageQueue::get_singleton()->push_callp(obj, str_method, (const Variant **)argptrs, count); // something env->PopLocalFrame(nullptr); } diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index 48aeb3d070..158512803a 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -114,10 +114,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS String signal_name = jstring_to_string(j_signal_name, env); int count = env->GetArrayLength(j_signal_params); - ERR_FAIL_COND_MSG(count > VARIANT_ARG_MAX, "Maximum argument count exceeded!"); - Variant variant_params[VARIANT_ARG_MAX]; - const Variant *args[VARIANT_ARG_MAX]; + Variant *variant_params = (Variant *)alloca(sizeof(Variant) * count); + const Variant **args = (const Variant **)alloca(sizeof(Variant *) * count); for (int i = 0; i < count; i++) { jobject j_param = env->GetObjectArrayElement(j_signal_params, i); @@ -126,7 +125,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS env->DeleteLocalRef(j_param); }; - singleton->emit_signal(StringName(signal_name), args, count); + singleton->emit_signalp(StringName(signal_name), args, count); } JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths) { diff --git a/platform/iphone/export/export_plugin.cpp b/platform/iphone/export/export_plugin.cpp index 2eaf5e47ac..ac5886e620 100644 --- a/platform/iphone/export/export_plugin.cpp +++ b/platform/iphone/export/export_plugin.cpp @@ -92,13 +92,10 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); Vector<PluginConfigIOS> found_plugins = get_plugins(); for (int i = 0; i < found_plugins.size(); i++) { @@ -139,8 +136,11 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); r_options->push_back(ExportOption(PropertyInfo(Variant::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 @@ -200,8 +200,6 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n"; } else if (lines[i].find("$name") != -1) { strnew += lines[i].replace("$name", p_config.pkg_name) + "\n"; - } else if (lines[i].find("$info") != -1) { - strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n"; } else if (lines[i].find("$bundle_identifier") != -1) { strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { @@ -210,8 +208,6 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$version", p_preset->get("application/version")) + "\n"; } else if (lines[i].find("$signature") != -1) { strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; - } else if (lines[i].find("$copyright") != -1) { - strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; } else if (lines[i].find("$team_id") != -1) { strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n"; } else if (lines[i].find("$default_build_config") != -1) { @@ -534,7 +530,7 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr String json_description = "{\"images\":["; String sizes; - DirAccess *da = DirAccess::open(p_iconset_dir); + DirAccessRef da = DirAccess::open(p_iconset_dir); ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'."); for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { @@ -574,7 +570,6 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr } if (err) { - memdelete(da); String err_str = String("Failed to export icon(" + String(info.preset_key) + "): '" + icon_path + "'."); ERR_PRINT(err_str.utf8().get_data()); return err; @@ -592,7 +587,6 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr json_description += String("}"); } json_description += "]}"; - memdelete(da); FileAccess *json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE); ERR_FAIL_COND_V(!json_file, ERR_CANT_CREATE); @@ -678,7 +672,7 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor } Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { - DirAccess *da = DirAccess::open(p_dest_dir); + DirAccessRef da = DirAccess::open(p_dest_dir); ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { @@ -716,7 +710,6 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExp err = da->copy(loading_screen_file, p_dest_dir + info.export_name); } if (err) { - memdelete(da); String err_str = String("Failed to export loading screen (") + info.preset_key + ") from path '" + loading_screen_file + "'."; ERR_PRINT(err_str.utf8().get_data()); return err; @@ -764,7 +757,6 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExp } } } - memdelete(da); return OK; } @@ -970,21 +962,15 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese } Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { - DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); - String binary_name = p_out_dir.get_file().get_basename(); - DirAccess *da = DirAccess::create_for_path(p_asset); + DirAccessRef da = DirAccess::create_for_path(p_asset); if (!da) { - memdelete(filesystem_da); ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + "."); } bool file_exists = da->file_exists(p_asset); bool dir_exists = da->dir_exists(p_asset); if (!file_exists && !dir_exists) { - memdelete(da); - memdelete(filesystem_da); return ERR_FILE_NOT_FOUND; } @@ -1044,19 +1030,18 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String destination = p_out_dir.plus_file(asset_path); } + DirAccessRef filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); + if (!filesystem_da->dir_exists(destination_dir)) { Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); if (make_dir_err) { - memdelete(da); - memdelete(filesystem_da); return make_dir_err; } } Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); - memdelete(da); if (err) { - memdelete(filesystem_da); return err; } IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed }; @@ -1121,8 +1106,6 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String } } - memdelete(filesystem_da); - return OK; } @@ -1427,29 +1410,29 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p return ERR_FILE_BAD_PATH; } - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da) { - String current_dir = da->get_current_dir(); - - // remove leftovers from last export so they don't interfere - // in case some files are no longer needed - if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) { - da->erase_contents_recursive(); - } - if (da->change_dir(dest_dir + binary_name) == OK) { - da->erase_contents_recursive(); - } + { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da) { + String current_dir = da->get_current_dir(); + + // remove leftovers from last export so they don't interfere + // in case some files are no longer needed + if (da->change_dir(dest_dir + binary_name + ".xcodeproj") == OK) { + da->erase_contents_recursive(); + } + if (da->change_dir(dest_dir + binary_name) == OK) { + da->erase_contents_recursive(); + } - da->change_dir(current_dir); + da->change_dir(current_dir); - if (!da->dir_exists(dest_dir + binary_name)) { - Error err = da->make_dir(dest_dir + binary_name); - if (err) { - memdelete(da); - return err; + if (!da->dir_exists(dest_dir + binary_name)) { + Error err = da->make_dir(dest_dir + binary_name); + if (err) { + return err; + } } } - memdelete(da); } if (ep.step("Making .pck", 0)) { @@ -1457,7 +1440,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p } String pack_path = dest_dir + binary_name + ".pck"; Vector<SharedObject> libraries; - Error err = save_pack(p_preset, pack_path, &libraries); + Error err = save_pack(p_preset, p_debug, pack_path, &libraries); if (err) { return err; } @@ -1470,9 +1453,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p print_line("Static framework: " + library_to_use); String pkg_name; - if (p_preset->get("application/name") != "") { - pkg_name = p_preset->get("application/name"); // app_name - } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { + if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); } else { pkg_name = "Unnamed"; @@ -1507,7 +1488,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p Vector<IOSExportAsset> assets; - DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir); + DirAccessRef tmp_app_path = DirAccess::create_for_path(dest_dir); ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE); print_line("Unzipping..."); @@ -1586,7 +1567,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p if (dir_err) { ERR_PRINT("Can't create '" + dir_name + "'."); unzClose(src_pkg_zip); - memdelete(tmp_app_path); return ERR_CANT_CREATE; } } @@ -1596,7 +1576,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p if (!f) { ERR_PRINT("Can't write '" + file + "'."); unzClose(src_pkg_zip); - memdelete(tmp_app_path); return ERR_CANT_CREATE; }; f->store_buffer(data.ptr(), data.size()); @@ -1619,28 +1598,48 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p if (!found_library) { ERR_PRINT("Requested template library '" + library_to_use + "' not found. It might be missing from your template archive."); - memdelete(tmp_app_path); return ERR_FILE_NOT_FOUND; } + Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); + Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); + Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); + Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized"); + Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); if (translations.size() > 0) { { String fname = dest_dir + binary_name + "/en.lproj"; tmp_app_path->make_dir_recursive(fname); FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";"); + f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); + f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); + f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";"); } for (const String &E : translations) { Ref<Translation> tr = ResourceLoader::load(E); if (tr.is_valid()) { - String fname = dest_dir + binary_name + "/" + tr->get_locale() + ".lproj"; + String lang = tr->get_locale(); + String fname = dest_dir + binary_name + "/" + lang + ".lproj"; tmp_app_path->make_dir_recursive(fname); FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); - String prop = "application/config/name_" + tr->get_locale(); - if (ProjectSettings::get_singleton()->has_setting(prop)) { - f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get(prop).operator String() + "\";"); + 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() + "\";"); } } } @@ -1656,7 +1655,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p Error lib_copy_err = tmp_app_path->copy(static_lib_path, dest_lib_file_path); if (lib_copy_err != OK) { ERR_PRINT("Can't copy '" + static_lib_path + "'."); - memdelete(tmp_app_path); return lib_copy_err; } } @@ -1667,7 +1665,6 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p if (!tmp_app_path->dir_exists(iconset_dir)) { err = tmp_app_path->make_dir_recursive(iconset_dir); } - memdelete(tmp_app_path); if (err) { return err; } @@ -1677,43 +1674,43 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p return err; } - bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard"); + { + bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard"); - String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"; - String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/"; + String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"; + String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/"; - DirAccess *launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (!launch_screen_da) { - return ERR_CANT_CREATE; - } + if (!launch_screen_da) { + return ERR_CANT_CREATE; + } - if (use_storyboard) { - print_line("Using Launch Storyboard"); + if (use_storyboard) { + print_line("Using Launch Storyboard"); - if (launch_screen_da->change_dir(launch_image_path) == OK) { - launch_screen_da->erase_contents_recursive(); - launch_screen_da->remove(launch_image_path); - } + if (launch_screen_da->change_dir(launch_image_path) == OK) { + launch_screen_da->erase_contents_recursive(); + launch_screen_da->remove(launch_image_path); + } - err = _export_loading_screen_file(p_preset, splash_image_path); - } else { - print_line("Using Launch Images"); + err = _export_loading_screen_file(p_preset, splash_image_path); + } else { + print_line("Using Launch Images"); - const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard"; + const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard"; - launch_screen_da->remove(launch_screen_path); + launch_screen_da->remove(launch_screen_path); - if (launch_screen_da->change_dir(splash_image_path) == OK) { - launch_screen_da->erase_contents_recursive(); - launch_screen_da->remove(splash_image_path); - } + if (launch_screen_da->change_dir(splash_image_path) == OK) { + launch_screen_da->erase_contents_recursive(); + launch_screen_da->remove(splash_image_path); + } - err = _export_loading_screen_images(p_preset, launch_image_path); + err = _export_loading_screen_images(p_preset, launch_image_path); + } } - memdelete(launch_screen_da); - if (err) { return err; } @@ -1732,15 +1729,17 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p memdelete(f); #ifdef OSX_ENABLED - if (ep.step("Code-signing dylibs", 2)) { - return ERR_SKIP; + { + if (ep.step("Code-signing dylibs", 2)) { + return ERR_SKIP; + } + DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs"); + ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN); + CodesignData codesign_data(p_preset, p_debug); + err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data); + memdelete(dylibs_dir); + ERR_FAIL_COND_V(err, err); } - DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs"); - ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN); - CodesignData codesign_data(p_preset, p_debug); - err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data); - memdelete(dylibs_dir); - ERR_FAIL_COND_V(err, err); if (ep.step("Making .xcarchive", 3)) { return ERR_SKIP; diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 56cb49318c..1d990b5625 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -259,11 +259,9 @@ Error OSIPhone::shell_open(String p_uri) { } void OSIPhone::set_user_data_dir(String p_dir) { - DirAccess *da = DirAccess::open(p_dir); - + DirAccessRef da = DirAccess::open(p_dir); user_data_dir = da->get_current_dir(); printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data()); - memdelete(da); } String OSIPhone::get_user_data_dir() const { diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 4190b24b8e..46a0a816bf 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -37,8 +37,8 @@ static JavaScript *javascript_eval; void register_javascript_api() { JavaScriptToolsEditorPlugin::initialize(); - GDREGISTER_VIRTUAL_CLASS(JavaScriptObject); - GDREGISTER_VIRTUAL_CLASS(JavaScript); + GDREGISTER_ABSTRACT_CLASS(JavaScriptObject); + GDREGISTER_ABSTRACT_CLASS(JavaScript); javascript_eval = memnew(JavaScript); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval)); } diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index bea54ae1cb..0442a1eaeb 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -124,7 +124,7 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z } void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) { - DirAccess *dir = DirAccess::open(p_path); + DirAccessRef dir = DirAccess::open(p_path); if (!dir) { WARN_PRINT("Unable to open directory for zipping: " + p_path); return; diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 2caf369354..a38040922d 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -34,7 +34,7 @@ #include "drivers/gles3/rasterizer_gles3.h" #endif #include "platform/javascript/os_javascript.h" -#include "servers/rendering/rasterizer_dummy.h" +#include "servers/rendering/dummy/rasterizer_dummy.h" #include <emscripten.h> #include <png.h> diff --git a/platform/javascript/export/export_plugin.cpp b/platform/javascript/export/export_plugin.cpp index c7e503732d..84694461cf 100644 --- a/platform/javascript/export/export_plugin.cpp +++ b/platform/javascript/export/export_plugin.cpp @@ -252,7 +252,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & // Custom offline page const String offline_page = p_preset->get("progressive_web_app/offline_page"); if (!offline_page.is_empty()) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); const String offline_dest = dir.plus_file(name + ".offline.html"); err = da->copy(ProjectSettings::get_singleton()->globalize_path(offline_page), offline_dest); if (err != OK) { @@ -360,6 +360,15 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const { } bool EditorExportPlatformJavaScript::can_export(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 + // 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 = false; ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); @@ -440,23 +449,23 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese // Export pck and shared objects Vector<SharedObject> shared_objects; String pck_path = base_path + ".pck"; - Error error = save_pack(p_preset, pck_path, &shared_objects); + Error error = save_pack(p_preset, p_debug, pck_path, &shared_objects); if (error != OK) { EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path); return error; } - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - for (int i = 0; i < shared_objects.size(); i++) { - String dst = base_dir.plus_file(shared_objects[i].path.get_file()); - error = da->copy(shared_objects[i].path, dst); - if (error != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file()); - memdelete(da); - return error; + + { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < shared_objects.size(); i++) { + String dst = base_dir.plus_file(shared_objects[i].path.get_file()); + error = da->copy(shared_objects[i].path, dst); + if (error != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file()); + return error; + } } } - memdelete(da); - da = nullptr; // Extract templates. error = _extract_template(template_path, base_dir, base_name, pwa); diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp index eb5c02822f..c9c4d6e1b9 100644 --- a/platform/javascript/javascript_singleton.cpp +++ b/platform/javascript/javascript_singleton.cpp @@ -81,7 +81,7 @@ protected: public: Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override; void setvar(const Variant &p_key, const Variant &p_value, bool *r_valid = nullptr) override; - Variant call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) override; + Variant callp(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) override; JavaScriptObjectImpl() {} JavaScriptObjectImpl(int p_id) { _js_id = p_id; } ~JavaScriptObjectImpl() { @@ -231,7 +231,7 @@ int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_w return type; } -Variant JavaScriptObjectImpl::call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) { +Variant JavaScriptObjectImpl::callp(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) { godot_js_wrapper_ex exchange; const String method = p_method; void *lock = nullptr; diff --git a/platform/javascript/package-lock.json b/platform/javascript/package-lock.json index 35f864f01a..f72cde955a 100644 --- a/platform/javascript/package-lock.json +++ b/platform/javascript/package-lock.json @@ -1884,9 +1884,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/mkdirp": { @@ -4444,9 +4444,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "mkdirp": { diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index ab643b254a..03c85d09ad 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -261,27 +261,6 @@ def configure(env): if not env["builtin_libpng"]: env.ParseConfig("pkg-config libpng16 --cflags --libs") - if not env["builtin_bullet"]: - # We need at least version 2.90 - min_bullet_version = "2.90" - - import subprocess - - bullet_version = subprocess.check_output(["pkg-config", "bullet", "--modversion"]).strip() - if str(bullet_version) < min_bullet_version: - # Abort as system bullet was requested but too old - print( - "Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format( - bullet_version, min_bullet_version - ) - ) - sys.exit(255) - env.ParseConfig("pkg-config bullet --cflags --libs") - - if False: # not env['builtin_assimp']: - # FIXME: Add min version check - env.ParseConfig("pkg-config assimp --cflags --libs") - if not env["builtin_enet"]: env.ParseConfig("pkg-config libenet --cflags --libs") diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 07cb6a23e8..2ddccdb8bf 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -363,15 +363,15 @@ DisplayServerX11::MouseMode DisplayServerX11::mouse_get_mode() const { return mouse_mode; } -void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) { +void DisplayServerX11::warp_mouse(const Point2i &p_position) { _THREAD_SAFE_METHOD_ if (mouse_mode == MOUSE_MODE_CAPTURED) { - last_mouse_pos = p_to; + last_mouse_pos = p_position; } else { WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; XWarpPointer(x11_display, None, windows[window_id].x11_window, - 0, 0, 0, 0, (int)p_to.x, (int)p_to.y); + 0, 0, 0, 0, (int)p_position.x, (int)p_position.y); } } diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 63d32d939d..0f44aa4b11 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -301,7 +301,7 @@ public: virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; - virtual void mouse_warp_to_position(const Point2i &p_to) override; + virtual void warp_mouse(const Point2i &p_position) override; virtual Point2i mouse_get_position() const override; virtual MouseButton mouse_get_button_state() const override; diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index f05d2faa11..ec83e52f09 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -30,15 +30,10 @@ #include "export.h" -#include "core/io/file_access.h" -#include "editor/editor_export.h" -#include "platform/linuxbsd/logo.gen.h" -#include "scene/resources/texture.h" - -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); +#include "export_plugin.h" void register_linuxbsd_exporter() { - Ref<EditorExportPlatformPC> platform; + Ref<EditorExportPlatformLinuxBSD> platform; platform.instantiate(); Ref<Image> img = memnew(Image(_linuxbsd_logo)); @@ -47,120 +42,10 @@ void register_linuxbsd_exporter() { logo->create_from_image(img); platform->set_logo(logo); platform->set_name("Linux/X11"); - platform->set_extension("x86"); + platform->set_extension("x86_32"); platform->set_extension("x86_64", "binary_format/64_bits"); - platform->set_release_32("linux_x11_32_release"); - platform->set_debug_32("linux_x11_32_debug"); - platform->set_release_64("linux_x11_64_release"); - platform->set_debug_64("linux_x11_64_debug"); platform->set_os_name("LinuxBSD"); platform->set_chmod_flags(0755); - platform->set_fixup_embedded_pck_func(&fixup_embedded_pck); EditorExport::get_singleton()->add_export_platform(platform); } - -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { - // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data - - FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); - if (!f) { - return ERR_CANT_OPEN; - } - - // Read and check ELF magic number - { - uint32_t magic = f->get_32(); - if (magic != 0x464c457f) { // 0x7F + "ELF" - f->close(); - return ERR_FILE_CORRUPT; - } - } - - // Read program architecture bits from class field - - int bits = f->get_8() * 32; - - if (bits == 32 && p_embedded_size >= 0x100000000) { - f->close(); - ERR_FAIL_V_MSG(ERR_INVALID_DATA, "32-bit executables cannot have embedded data >= 4 GiB."); - } - - // Get info about the section header table - - int64_t section_table_pos; - int64_t section_header_size; - if (bits == 32) { - section_header_size = 40; - f->seek(0x20); - section_table_pos = f->get_32(); - f->seek(0x30); - } else { // 64 - section_header_size = 64; - f->seek(0x28); - section_table_pos = f->get_64(); - f->seek(0x3c); - } - int num_sections = f->get_16(); - int string_section_idx = f->get_16(); - - // Load the strings table - uint8_t *strings; - { - // Jump to the strings section header - f->seek(section_table_pos + string_section_idx * section_header_size); - - // Read strings data size and offset - int64_t string_data_pos; - int64_t string_data_size; - if (bits == 32) { - f->seek(f->get_position() + 0x10); - string_data_pos = f->get_32(); - string_data_size = f->get_32(); - } else { // 64 - f->seek(f->get_position() + 0x18); - string_data_pos = f->get_64(); - string_data_size = f->get_64(); - } - - // Read strings data - f->seek(string_data_pos); - strings = (uint8_t *)memalloc(string_data_size); - if (!strings) { - f->close(); - return ERR_OUT_OF_MEMORY; - } - f->get_buffer(strings, string_data_size); - } - - // Search for the "pck" section - - bool found = false; - for (int i = 0; i < num_sections; ++i) { - int64_t section_header_pos = section_table_pos + i * section_header_size; - f->seek(section_header_pos); - - uint32_t name_offset = f->get_32(); - if (strcmp((char *)strings + name_offset, "pck") == 0) { - // "pck" section found, let's patch! - - if (bits == 32) { - f->seek(section_header_pos + 0x10); - f->store_32(p_embedded_start); - f->store_32(p_embedded_size); - } else { // 64 - f->seek(section_header_pos + 0x18); - f->store_64(p_embedded_start); - f->store_64(p_embedded_size); - } - - found = true; - break; - } - } - - memfree(strings); - f->close(); - - return found ? OK : ERR_FILE_CORRUPT; -} diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp new file mode 100644 index 0000000000..24906fa3fb --- /dev/null +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -0,0 +1,204 @@ +/*************************************************************************/ +/* export_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "export_plugin.h" + +#include "core/config/project_settings.h" +#include "editor/editor_node.h" + +Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { + FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); + + f->store_line("#!/bin/sh"); + f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'"); + f->store_line("base_path=\"$(dirname \"$(realpath \"$0\")\")\""); + f->store_line("\"$base_path/" + p_pkg_name + "\" \"$@\""); + + return OK; +} + +Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { + Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags); + + if (err != OK) { + return err; + } + + String app_name; + if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { + app_name = String(ProjectSettings::get_singleton()->get("application/config/name")); + } else { + app_name = "Unnamed"; + } + app_name = OS::get_singleton()->get_safe_dir_name(app_name); + + // Save console script. + if (err == OK) { + int con_scr = p_preset->get("debug/export_console_script"); + if ((con_scr == 1 && p_debug) || (con_scr == 2)) { + String scr_path = p_path.get_basename() + ".sh"; + err = _export_debug_script(p_preset, app_name, p_path.get_file(), scr_path); + FileAccess::set_unix_permissions(scr_path, 0755); + } + } + + return err; +} + +void EditorExportPlatformLinuxBSD::set_extension(const String &p_extension, const String &p_feature_key) { + extensions[p_feature_key] = p_extension; +} + +String EditorExportPlatformLinuxBSD::get_template_file_name(const String &p_target, const String &p_arch) const { + return "linux_x11_" + p_arch + "_" + p_target; +} + +List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + List<String> list; + for (const KeyValue<String, String> &E : extensions) { + if (p_preset->get(E.key)) { + list.push_back(extensions[E.key]); + return list; + } + } + + if (extensions.has("default")) { + list.push_back(extensions["default"]); + return list; + } + + return list; +} + +Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const { + // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data + + FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); + if (!f) { + return ERR_CANT_OPEN; + } + + // Read and check ELF magic number + { + uint32_t magic = f->get_32(); + if (magic != 0x464c457f) { // 0x7F + "ELF" + f->close(); + return ERR_FILE_CORRUPT; + } + } + + // Read program architecture bits from class field + + int bits = f->get_8() * 32; + + if (bits == 32 && p_embedded_size >= 0x100000000) { + f->close(); + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "32-bit executables cannot have embedded data >= 4 GiB."); + } + + // Get info about the section header table + + int64_t section_table_pos; + int64_t section_header_size; + if (bits == 32) { + section_header_size = 40; + f->seek(0x20); + section_table_pos = f->get_32(); + f->seek(0x30); + } else { // 64 + section_header_size = 64; + f->seek(0x28); + section_table_pos = f->get_64(); + f->seek(0x3c); + } + int num_sections = f->get_16(); + int string_section_idx = f->get_16(); + + // Load the strings table + uint8_t *strings; + { + // Jump to the strings section header + f->seek(section_table_pos + string_section_idx * section_header_size); + + // Read strings data size and offset + int64_t string_data_pos; + int64_t string_data_size; + if (bits == 32) { + f->seek(f->get_position() + 0x10); + string_data_pos = f->get_32(); + string_data_size = f->get_32(); + } else { // 64 + f->seek(f->get_position() + 0x18); + string_data_pos = f->get_64(); + string_data_size = f->get_64(); + } + + // Read strings data + f->seek(string_data_pos); + strings = (uint8_t *)memalloc(string_data_size); + if (!strings) { + f->close(); + return ERR_OUT_OF_MEMORY; + } + f->get_buffer(strings, string_data_size); + } + + // Search for the "pck" section + + bool found = false; + for (int i = 0; i < num_sections; ++i) { + int64_t section_header_pos = section_table_pos + i * section_header_size; + f->seek(section_header_pos); + + uint32_t name_offset = f->get_32(); + if (strcmp((char *)strings + name_offset, "pck") == 0) { + // "pck" section found, let's patch! + + if (bits == 32) { + f->seek(section_header_pos + 0x10); + f->store_32(p_embedded_start); + f->store_32(p_embedded_size); + } else { // 64 + f->seek(section_header_pos + 0x18); + f->store_64(p_embedded_start); + f->store_64(p_embedded_size); + } + + found = true; + break; + } + } + + memfree(strings); + f->close(); + + return found ? OK : ERR_FILE_CORRUPT; +} diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h new file mode 100644 index 0000000000..f46fc68e1d --- /dev/null +++ b/platform/linuxbsd/export/export_plugin.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* export_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef LINUXBSD_EXPORT_PLUGIN_H +#define LINUXBSD_EXPORT_PLUGIN_H + +#include "core/io/file_access.h" +#include "editor/editor_export.h" +#include "editor/editor_settings.h" +#include "platform/linuxbsd/logo.gen.h" +#include "scene/resources/texture.h" + +class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC { + Map<String, String> extensions; + Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); + +public: + void set_extension(const String &p_extension, const String &p_feature_key = "default"); + 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; + 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) const override; +}; + +#endif diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 6461a3d9fe..86874687ad 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -399,9 +399,11 @@ static String get_mountpoint(const String &p_path) { } Error OS_LinuxBSD::move_to_trash(const String &p_path) { + String path = p_path.rstrip("/"); // Strip trailing slash when path points to a directory + int err_code; List<String> args; - args.push_back(p_path); + args.push_back(path); args.push_front("trash"); // The command is `gio trash <file_name>` so we need to add it to args. Error result = execute("gio", args, nullptr, &err_code); // For GNOME based machines. if (result == OK && !err_code) { @@ -431,14 +433,14 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { // If the commands `kioclient5`, `gio` or `gvfs-trash` don't exist on the system we do it manually. String trash_path = ""; - String mnt = get_mountpoint(p_path); + String mnt = get_mountpoint(path); // If there is a directory "[Mountpoint]/.Trash-[UID], use it as the trash can. if (!mnt.is_empty()) { - String path(mnt + "/.Trash-" + itos(getuid())); + String mountpoint_trash_path(mnt + "/.Trash-" + itos(getuid())); struct stat s; - if (!stat(path.utf8().get_data(), &s)) { - trash_path = path; + if (!stat(mountpoint_trash_path.utf8().get_data(), &s)) { + trash_path = mountpoint_trash_path; } } @@ -469,18 +471,15 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { // Issue an error if trash can is not created properly. ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\""); err = dir_access->make_dir_recursive(trash_path + "/files"); - ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/files"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "/files\""); err = dir_access->make_dir_recursive(trash_path + "/info"); - ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/info"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "/info\""); } // The trash can is successfully created, now we check that we don't exceed our file name length limit. // If the file name is too long trim it so we can add the identifying number and ".trashinfo". // Assumes that the file name length limit is 255 characters. - String file_name = p_path.get_file(); - if (file_name.length() == 0) { - file_name = p_path.get_base_dir().get_file(); - } + String file_name = path.get_file(); if (file_name.length() > 240) { file_name = file_name.substr(0, file_name.length() - 15); } @@ -503,29 +502,31 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { } file_name = fn; + String renamed_path = path.get_base_dir() + "/" + file_name; + // Generates the .trashinfo file OS::Date date = OS::get_singleton()->get_date(false); OS::Time time = OS::get_singleton()->get_time(false); String timestamp = vformat("%04d-%02d-%02dT%02d:%02d:", date.year, (int)date.month, date.day, time.hour, time.minute); timestamp = vformat("%s%02d", timestamp, time.second); // vformat only supports up to 6 arguments. - String trash_info = "[Trash Info]\nPath=" + p_path.uri_encode() + "\nDeletionDate=" + timestamp + "\n"; + String trash_info = "[Trash Info]\nPath=" + path.uri_encode() + "\nDeletionDate=" + timestamp + "\n"; { Error err; FileAccessRef file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err); - ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file:" + trash_path + "/info/" + file_name + ".trashinfo"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file: \"" + trash_path + "/info/" + file_name + ".trashinfo\""); file->store_string(trash_info); file->close(); // Rename our resource before moving it to the trash can. DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - err = dir_access->rename(p_path, p_path.get_base_dir() + "/" + file_name); - ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + p_path + "\""); + err = dir_access->rename(path, renamed_path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + path + "\" to \"" + renamed_path + "\""); } // Move the given resource to the trash can. // Do not use DirAccess:rename() because it can't move files across multiple mountpoints. List<String> mv_args; - mv_args.push_back(p_path.get_base_dir() + "/" + file_name); + mv_args.push_back(renamed_path); mv_args.push_back(trash_path + "/files"); { int retval; @@ -533,11 +534,10 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { // Issue an error if "mv" failed to move the given resource to the trash can. if (err != OK || retval != 0) { - ERR_PRINT("move_to_trash: Could not move the resource \"" + p_path + "\" to the trash can \"" + trash_path + "/files\""); - DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - err = dir_access->rename(p_path.get_base_dir() + "/" + file_name, p_path); - memdelete(dir_access); - ERR_FAIL_COND_V_MSG(err != OK, err, "Could not rename " + p_path.get_base_dir() + "/" + file_name + " back to its original name:" + p_path); + ERR_PRINT("move_to_trash: Could not move the resource \"" + path + "\" to the trash can \"" + trash_path + "/files\""); + DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + err = dir_access->rename(renamed_path, path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Could not rename \"" + renamed_path + "\" back to its original name: \"" + path + "\""); return FAILED; } } diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index cc9ac162ea..fa3091ff81 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -186,6 +186,7 @@ private: void _process_key_events(); void _update_keyboard_layouts(); static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info); + NSImage *_convert_to_nsimg(Ref<Image> &p_image) const; static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil); @@ -217,24 +218,46 @@ 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, const Variant &p_tag = Variant()) override; - virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override; - virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) override; - virtual void global_menu_add_separator(const String &p_menu_root) 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_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; virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const override; virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override; - virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) override; - virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) override; - virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) override; - virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) override; + virtual bool global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const override; + virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) const override; + virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override; + virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override; + virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override; + 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 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; + virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override; virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override; virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override; virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override; + virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override; + virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override; + virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override; + 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 int global_menu_get_item_count(const String &p_menu_root) const override; @@ -248,7 +271,7 @@ public: virtual MouseMode mouse_get_mode() const override; bool update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp); - virtual void mouse_warp_to_position(const Point2i &p_to) override; + virtual void warp_mouse(const Point2i &p_position) override; virtual Point2i mouse_get_position() const override; void mouse_set_button_state(MouseButton p_state); virtual MouseButton mouse_get_button_state() const override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 89ca6e50ec..6cdadcae39 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -91,6 +91,7 @@ NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { // Submenu. if (!submenu.has(p_menu_root)) { NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; + [n_menu setAutoenablesItems:NO]; submenu[p_menu_root] = n_menu; } menu = submenu[p_menu_root]; @@ -472,6 +473,40 @@ void DisplayServerOSX::_keyboard_layout_changed(CFNotificationCenterRef center, } } +NSImage *DisplayServerOSX::_convert_to_nsimg(Ref<Image> &p_image) const { + p_image->convert(Image::FORMAT_RGBA8); + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:p_image->get_width() + pixelsHigh:p_image->get_height() + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:int(p_image->get_width()) * 4 + bitsPerPixel:32]; + ERR_FAIL_COND_V(imgrep == nil, nil); + uint8_t *pixels = [imgrep bitmapData]; + + int len = p_image->get_width() * p_image->get_height(); + const uint8_t *r = p_image->get_data().ptr(); + + /* Premultiply the alpha channel */ + for (int i = 0; i < len; i++) { + uint8_t alpha = r[i * 4 + 3]; + pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255); + pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255); + pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255); + pixels[i * 4 + 3] = alpha; + } + + NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(p_image->get_width(), p_image->get_height())]; + ERR_FAIL_COND_V(nsimg == nil, nil); + [nsimg addRepresentation:imgrep]; + return nsimg; +} + NSCursor *DisplayServerOSX::_cursor_from_selector(SEL p_selector, SEL p_fallback) { if ([NSCursor respondsToSelector:p_selector]) { id object = [NSCursor performSelector:p_selector]; @@ -498,7 +533,14 @@ void DisplayServerOSX::menu_callback(id p_sender) { GodotMenuItem *value = [p_sender representedObject]; if (value) { - if (value->checkable) { + if (value->max_states > 0) { + value->state++; + if (value->state >= value->max_states) { + value->state = 0; + } + } + + if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) { if ([p_sender state] == NSControlStateValueOff) { [p_sender setState:NSControlStateValueOn]; } else { @@ -671,35 +713,195 @@ String DisplayServerOSX::get_name() const { return "OSX"; } -void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) { +void DisplayServerOSX::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_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingOSX::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()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::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 = KeyMappingOSX::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()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::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 = KeyMappingOSX::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()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::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 = KeyMappingOSX::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()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::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 = KeyMappingOSX::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()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::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) { - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; + String keycode = KeyMappingOSX::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()]]; + } GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; - obj->checkable = false; + obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } } -void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) { +void DisplayServerOSX::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); if (menu) { - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; + String keycode = KeyMappingOSX::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()]]; + } GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; - obj->checkable = true; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = p_max_states; + obj->state = p_default_state; + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } } -void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) { +void DisplayServerOSX::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); @@ -713,18 +915,58 @@ void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, c ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!"); return; } - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""]; + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""]; + } + [sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]]; [menu setSubmenu:sub_menu forItem:menu_item]; } } -void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root) { +void DisplayServerOSX::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) { - [menu addItem:[NSMenuItem separatorItem]]; + if (p_index != -1) { + [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index]; + } else { + [menu addItem:[NSMenuItem separatorItem]]; + } + } +} + +int DisplayServerOSX::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; } + + return -1; +} + +int DisplayServerOSX::global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + for (NSInteger i = 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; + } + } + } + } + + return -1; } bool DisplayServerOSX::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const { @@ -749,14 +991,30 @@ bool DisplayServerOSX::global_menu_is_item_checkable(const String &p_menu_root, if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; if (obj) { - return obj->checkable; + return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX; + } + } + } + return false; +} + +bool DisplayServerOSX::global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON; } } } return false; } -Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) { +Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); @@ -772,7 +1030,7 @@ Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_ro return Callable(); } -Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) { +Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); @@ -788,22 +1046,20 @@ Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, in return Variant(); } -String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) { +String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - String ret; - ret.parse_utf8([[menu_item title] UTF8String]); - return ret; + return String::utf8([[menu_item title] UTF8String]); } } return String(); } -String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) { +String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); @@ -823,6 +1079,116 @@ String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, return String(); } +Key DisplayServerOSX::global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + String ret = String::utf8([[menu_item keyEquivalent] UTF8String]); + Key keycode = find_keycode(ret); + NSUInteger mask = [menu_item keyEquivalentModifierMask]; + if (mask & NSEventModifierFlagControl) { + keycode |= KeyModifierMask::CTRL; + } + if (mask & NSEventModifierFlagOption) { + keycode |= KeyModifierMask::ALT; + } + if (mask & NSEventModifierFlagShift) { + keycode |= KeyModifierMask::SHIFT; + } + if (mask & NSEventModifierFlagCommand) { + keycode |= KeyModifierMask::META; + } + if (mask & NSEventModifierFlagNumericPad) { + keycode |= KeyModifierMask::KPAD; + } + return keycode; + } + } + return Key::NONE; +} + +bool DisplayServerOSX::global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + return ![menu_item isEnabled]; + } + } + return false; +} + +String DisplayServerOSX::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + return String::utf8([[menu_item toolTip] UTF8String]); + } + } + return String(); +} + +int DisplayServerOSX::global_menu_get_item_state(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->state; + } + } + } + return 0; +} + +int DisplayServerOSX::global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->max_states; + } + } + } + return 0; +} + +Ref<Texture2D> DisplayServerOSX::global_menu_get_item_icon(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + if (obj->img.is_valid()) { + Ref<ImageTexture> txt; + txt.instantiate(); + txt->create_from_image(obj->img); + return txt; + } + } + } + } + return Ref<Texture2D>(); +} + void DisplayServerOSX::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) { _THREAD_SAFE_METHOD_ @@ -853,7 +1219,23 @@ void DisplayServerOSX::global_menu_set_item_checkable(const String &p_menu_root, NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; - obj->checkable = p_checkable; + obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE; + } + } +} + +void DisplayServerOSX::global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) { + _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; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE; } } } @@ -929,6 +1311,116 @@ void DisplayServerOSX::global_menu_set_item_submenu(const String &p_menu_root, i } } +void DisplayServerOSX::global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) { + _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; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_keycode)]; + String keycode = KeyMappingOSX::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK); + [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + } +} + +void DisplayServerOSX::global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) { + _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; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setEnabled:(!p_disabled)]; + } + } +} + +void DisplayServerOSX::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) { + _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; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; + } + } +} + +void DisplayServerOSX::global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) { + _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; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + obj->state = p_state; + } + } + } +} + +void DisplayServerOSX::global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) { + _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; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + obj->max_states = p_max_states; + } + } + } +} + +void DisplayServerOSX::global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) { + _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; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } else { + obj->img = Ref<Image>(); + [menu_item setImage:nil]; + } + } + } +} + int DisplayServerOSX::global_menu_get_item_count(const String &p_menu_root) const { _THREAD_SAFE_METHOD_ @@ -1154,7 +1646,7 @@ bool DisplayServerOSX::update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSP return false; } -void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { +void DisplayServerOSX::warp_mouse(const Point2i &p_position) { _THREAD_SAFE_METHOD_ if (mouse_mode != MOUSE_MODE_CAPTURED) { @@ -1164,7 +1656,7 @@ void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { // Local point in window coords. const NSRect contentRect = [wd.window_view frame]; const float scale = screen_get_max_scale(); - NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0); + NSRect pointInWindowRect = NSMakeRect(p_position.x / scale, contentRect.size.height - (p_position.y / scale - 1), 0, 0); NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; // Point in scren coords. @@ -1190,7 +1682,11 @@ Point2i DisplayServerOSX::mouse_get_position() const { for (NSScreen *screen in [NSScreen screens]) { NSRect frame = [screen frame]; if (NSMouseInRect(mouse_pos, frame, NO)) { - return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) * scale + _get_screens_origin(); + Vector2i pos = Vector2i((int)mouse_pos.x, (int)mouse_pos.y); + pos *= scale; + pos -= _get_screens_origin(); + pos.y *= -1; + return pos; } } return Vector2i(); @@ -2628,11 +3124,13 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode // Setup Dock menu. dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; + [dock_menu setAutoenablesItems:NO]; // Setup Apple menu. apple_menu = [[NSMenu alloc] initWithTitle:@""]; title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; + [apple_menu setAutoenablesItems:NO]; [apple_menu addItem:[NSMenuItem separatorItem]]; @@ -2660,6 +3158,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode NSMenu *main_menu = [NSApp mainMenu]; menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; [main_menu setSubmenu:apple_menu forItem:menu_item]; + [main_menu setAutoenablesItems:NO]; //!!!!!!!!!!!!!!!!!!!!!!!!!! //TODO - do Vulkan and OpenGL support checks, driver selection and fallback diff --git a/platform/osx/export/codesign.cpp b/platform/osx/export/codesign.cpp index 1a2ad2bee6..b609a21c44 100644 --- a/platform/osx/export/codesign.cpp +++ b/platform/osx/export/codesign.cpp @@ -1208,7 +1208,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const String id; String main_exe = p_exe_path; - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (!da) { r_error_msg = TTR("Can't get filesystem access."); ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); @@ -1522,7 +1522,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const } Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (!da) { r_error_msg = TTR("Can't get filesystem access."); ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access."); diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 4d0fc9add6..4f06342fac 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -72,8 +72,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "debug/export_console_script", PROPERTY_HINT_ENUM, "No,Debug Only,Debug and Release"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); @@ -81,18 +80,30 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/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)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); @@ -316,9 +327,7 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset if (lines[i].find("$binary") != -1) { strnew += lines[i].replace("$binary", p_binary) + "\n"; } else if (lines[i].find("$name") != -1) { - strnew += lines[i].replace("$name", p_binary) + "\n"; - } else if (lines[i].find("$info") != -1) { - strnew += lines[i].replace("$info", p_preset->get("application/info")) + "\n"; + strnew += lines[i].replace("$name", ProjectSettings::get_singleton()->get("application/config/name")) + "\n"; } else if (lines[i].find("$bundle_identifier") != -1) { strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { @@ -450,7 +459,7 @@ Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset return OK; } -Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path) { +Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn) { bool force_builtin_codesign = EditorSettings::get_singleton()->get("export/macos/force_builtin_codesign"); bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-"); @@ -459,10 +468,10 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese #ifdef MODULE_REGEX_ENABLED #ifdef OSX_ENABLED - if (p_preset->get("codesign/timestamp")) { + if (p_preset->get("codesign/timestamp") && p_warn) { WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!"); } - if (p_preset->get("codesign/hardened_runtime")) { + if (p_preset->get("codesign/hardened_runtime") && p_warn) { WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"); } #endif @@ -482,14 +491,18 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese List<String> args; if (p_preset->get("codesign/timestamp")) { if (ad_hoc) { - WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!"); + if (p_warn) { + WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!"); + } } else { args.push_back("--timestamp"); } } if (p_preset->get("codesign/hardened_runtime")) { if (ad_hoc) { - WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"); + if (p_warn) { + WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"); + } } else { args.push_back("--options"); args.push_back("runtime"); @@ -569,7 +582,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset } if (extensions_to_sign.find(current_file.get_extension()) > -1) { - Error code_sign_error{ _code_sign(p_preset, current_file_path, p_ent_path) }; + Error code_sign_error{ _code_sign(p_preset, current_file_path, p_ent_path, false) }; if (code_sign_error != OK) { return code_sign_error; } @@ -613,7 +626,7 @@ Error EditorExportPlatformOSX::_copy_and_sign_files(DirAccessRef &dir_access, co // If it is a directory, find and sign all dynamic libraries. err = _code_sign_directory(p_preset, p_in_app_path, p_ent_path, p_should_error_on_non_code_sign); } else { - err = _code_sign(p_preset, p_in_app_path, p_ent_path); + err = _code_sign(p_preset, p_in_app_path, p_ent_path, false); } } return err; @@ -669,6 +682,19 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin return OK; } +Error EditorExportPlatformOSX::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { + FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); + + f->store_line("#!/bin/sh"); + f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'"); + f->store_line("function realpath() { python -c \"import os,sys; print(os.path.realpath(sys.argv[1]))\" \"$0\"; }"); + f->store_line("base_path=\"$(dirname \"$(realpath \"$0\")\")\""); + f->store_line("\"$base_path/" + p_pkg_name + "\" \"$@\""); + + return OK; +} + Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); @@ -713,9 +739,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p String binary_to_use = "godot_osx_" + String(p_debug ? "debug" : "release") + ".64"; String pkg_name; - if (p_preset->get("application/name") != "") { - pkg_name = p_preset->get("application/name"); // app_name - } else if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { + if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { pkg_name = String(ProjectSettings::get_singleton()->get("application/config/name")); } else { pkg_name = "Unnamed"; @@ -737,22 +761,30 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // Create our application bundle. String tmp_app_dir_name = pkg_name + ".app"; + String tmp_base_path_name; String tmp_app_path_name; + String scr_path; if (export_format == "app") { + tmp_base_path_name = p_path.get_base_dir(); tmp_app_path_name = p_path; + scr_path = p_path.get_basename() + ".command"; } else { - tmp_app_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name); + tmp_base_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name); + tmp_app_path_name = tmp_base_path_name.plus_file(tmp_app_dir_name); + scr_path = tmp_base_path_name.plus_file(pkg_name + ".command"); } + print_verbose("Exporting to " + tmp_app_path_name); Error err = OK; - DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_app_path_name); + DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name); if (!tmp_app_dir) { err = ERR_CANT_CREATE; } - if (DirAccess::exists(tmp_app_dir_name)) { + DirAccess::remove_file_or_error(scr_path); + if (DirAccess::exists(tmp_app_path_name)) { if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) { tmp_app_dir->erase_contents_recursive(); } @@ -781,24 +813,112 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); } + Dictionary appnames = ProjectSettings::get_singleton()->get("application/config/name_localized"); + Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized"); + Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized"); + Dictionary location_usage_descriptions = p_preset->get("privacy/location_usage_description_localized"); + Dictionary address_book_usage_descriptions = p_preset->get("privacy/address_book_usage_description_localized"); + Dictionary calendar_usage_descriptions = p_preset->get("privacy/calendar_usage_description_localized"); + Dictionary photos_library_usage_descriptions = p_preset->get("privacy/photos_library_usage_description_localized"); + Dictionary desktop_folder_usage_descriptions = p_preset->get("privacy/desktop_folder_usage_description_localized"); + Dictionary documents_folder_usage_descriptions = p_preset->get("privacy/documents_folder_usage_description_localized"); + Dictionary downloads_folder_usage_descriptions = p_preset->get("privacy/downloads_folder_usage_description_localized"); + Dictionary network_volumes_usage_descriptions = p_preset->get("privacy/network_volumes_usage_description_localized"); + Dictionary removable_volumes_usage_descriptions = p_preset->get("privacy/removable_volumes_usage_description_localized"); + Dictionary copyrights = p_preset->get("application/copyright_localized"); + Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); if (translations.size() > 0) { { String fname = tmp_app_path_name + "/Contents/Resources/en.lproj"; tmp_app_dir->make_dir_recursive(fname); FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + f->store_line("/* Localized versions of Info.plist keys */"); + f->store_line(""); f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";"); + if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { + f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/camera_usage_description")).is_empty()) { + f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/location_usage_description")).is_empty()) { + f->store_line("NSLocationUsageDescription = \"" + p_preset->get("privacy/location_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) { + f->store_line("NSContactsUsageDescription = \"" + p_preset->get("privacy/address_book_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) { + f->store_line("NSCalendarsUsageDescription = \"" + p_preset->get("privacy/calendar_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) { + f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photos_library_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/desktop_folder_usage_description")).is_empty()) { + f->store_line("NSDesktopFolderUsageDescription = \"" + p_preset->get("privacy/desktop_folder_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/documents_folder_usage_description")).is_empty()) { + f->store_line("NSDocumentsFolderUsageDescription = \"" + p_preset->get("privacy/documents_folder_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/downloads_folder_usage_description")).is_empty()) { + f->store_line("NSDownloadsFolderUsageDescription = \"" + p_preset->get("privacy/downloads_folder_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/network_volumes_usage_description")).is_empty()) { + f->store_line("NSNetworkVolumesUsageDescription = \"" + p_preset->get("privacy/network_volumes_usage_description").operator String() + "\";"); + } + if (!((String)p_preset->get("privacy/removable_volumes_usage_description")).is_empty()) { + f->store_line("NSRemovableVolumesUsageDescription = \"" + p_preset->get("privacy/removable_volumes_usage_description").operator String() + "\";"); + } + f->store_line("NSHumanReadableCopyright = \"" + p_preset->get("application/copyright").operator String() + "\";"); } for (const String &E : translations) { Ref<Translation> tr = ResourceLoader::load(E); if (tr.is_valid()) { - String fname = tmp_app_path_name + "/Contents/Resources/" + tr->get_locale() + ".lproj"; + String lang = tr->get_locale(); + String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj"; tmp_app_dir->make_dir_recursive(fname); FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); - String prop = "application/config/name_" + tr->get_locale(); - if (ProjectSettings::get_singleton()->has_setting(prop)) { - f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get(prop).operator String() + "\";"); + 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() + "\";"); } } } @@ -945,6 +1065,15 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = ERR_FILE_NOT_FOUND; } + // Save console script. + if (err == OK) { + int con_scr = p_preset->get("debug/export_console_script"); + if ((con_scr == 1 && p_debug) || (con_scr == 2)) { + err = _export_debug_script(p_preset, pkg_name, tmp_app_path_name.get_file() + "/Contents/MacOS/" + pkg_name, scr_path); + FileAccess::set_unix_permissions(scr_path, 0755); + } + } + if (err == OK) { if (ep.step(TTR("Making PKG"), 1)) { return ERR_SKIP; @@ -952,7 +1081,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; Vector<SharedObject> shared_objects; - err = save_pack(p_preset, pack_path, &shared_objects); + err = save_pack(p_preset, p_debug, pack_path, &shared_objects); // See if we can code sign our new package. bool sign_enabled = p_preset->get("codesign/enable"); @@ -1119,7 +1248,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p String hlp_path = helpers[i]; err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file()); if (err == OK && sign_enabled) { - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path); + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path, false); } FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755); } @@ -1144,8 +1273,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); - String path_in_app{ tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file() }; - err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, true); + if (shared_objects[i].target.is_empty()) { + String path_in_app = tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(); + err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, true); + } else { + String path_in_app = tmp_app_path_name.plus_file(shared_objects[i].target).plus_file(src_path.get_file()); + err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, false); + } if (err != OK) { break; } @@ -1163,7 +1297,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (sign_enabled) { for (int i = 0; i < dylibs_found.size(); i++) { if (err == OK) { - err = _code_sign(p_preset, tmp_app_path_name + "/" + dylibs_found[i], ent_path); + err = _code_sign(p_preset, tmp_app_path_name + "/" + dylibs_found[i], ent_path, false); } } } @@ -1181,14 +1315,14 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p if (ep.step(TTR("Making DMG"), 3)) { return ERR_SKIP; } - err = _create_dmg(p_path, pkg_name, tmp_app_path_name); + err = _create_dmg(p_path, pkg_name, tmp_base_path_name); } // Sign DMG. if (err == OK && sign_enabled && !ad_hoc) { if (ep.step(TTR("Code signing DMG"), 3)) { return ERR_SKIP; } - err = _code_sign(p_preset, p_path, ent_path); + err = _code_sign(p_preset, p_path, ent_path, false); } } else if (export_format == "zip") { // Create ZIP. @@ -1204,7 +1338,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f); zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); - _zip_folder_recursive(zip, EditorPaths::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name); + _zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name); zipClose(zip, nullptr); } @@ -1232,10 +1366,10 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p tmp_app_dir->remove(ent_path); } if (export_format != "app") { - if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) { + if (tmp_app_dir->change_dir(tmp_base_path_name) == OK) { tmp_app_dir->erase_contents_recursive(); tmp_app_dir->change_dir(".."); - tmp_app_dir->remove(tmp_app_dir_name); + tmp_app_dir->remove(pkg_name); } } } @@ -1244,7 +1378,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { - String dir = p_root_path.plus_file(p_folder); + String dir = p_folder.is_empty() ? p_root_path : p_root_path.plus_file(p_folder); DirAccessRef da = DirAccess::open(dir); da->list_dir_begin(); @@ -1298,7 +1432,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String } else if (da->current_is_dir()) { _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name); } else { - bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers"); + bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers") || f.ends_with(".command"); OS::Time time = OS::get_singleton()->get_time(); OS::Date date = OS::get_singleton()->get_date(); diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h index b85e9d662c..b3edfb7f90 100644 --- a/platform/osx/export/export_plugin.h +++ b/platform/osx/export/export_plugin.h @@ -56,7 +56,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { void _make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data); Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path); - Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path); + Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true); Error _code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_should_error_on_non_code = true); Error _copy_and_sign_files(DirAccessRef &dir_access, const String &p_src_path, const String &p_in_app_path, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path, @@ -66,6 +66,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { const String &p_ent_path); Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); + Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); bool use_codesign() const { return true; } #ifdef OSX_ENABLED diff --git a/platform/osx/godot_menu_item.h b/platform/osx/godot_menu_item.h index 50c4709c18..2c12897f10 100644 --- a/platform/osx/godot_menu_item.h +++ b/platform/osx/godot_menu_item.h @@ -36,12 +36,21 @@ #import <AppKit/AppKit.h> #import <Foundation/Foundation.h> +enum GlobalMenuCheckType { + CHECKABLE_TYPE_NONE, + CHECKABLE_TYPE_CHECK_BOX, + CHECKABLE_TYPE_RADIO_BUTTON, +}; + @interface GodotMenuItem : NSObject { @public Callable callback; Variant meta; int id; - bool checkable; + GlobalMenuCheckType checkable_type; + int max_states; + int state; + Ref<Image> img; } @end diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp index a76ff042b2..88343d6f85 100644 --- a/platform/uwp/export/export_plugin.cpp +++ b/platform/uwp/export/export_plugin.cpp @@ -131,6 +131,14 @@ 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 { +#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 = false; @@ -416,7 +424,7 @@ Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_p EditorNode::progress_add_task("project_files", "Project Files", 100); packager.set_progress_task("project_files"); - err = export_project_files(p_preset, save_appx_file, &packager); + err = export_project_files(p_preset, p_debug, save_appx_file, &packager); EditorNode::progress_end_task("project_files"); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 27b4a2018f..16323dcc13 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -150,7 +150,7 @@ DisplayServer::MouseMode DisplayServerWindows::mouse_get_mode() const { return mouse_mode; } -void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { +void DisplayServerWindows::warp_mouse(const Point2i &p_position) { _THREAD_SAFE_METHOD_ if (!windows.has(last_focused_window)) { @@ -158,12 +158,12 @@ void DisplayServerWindows::mouse_warp_to_position(const Point2i &p_to) { } if (mouse_mode == MOUSE_MODE_CAPTURED) { - old_x = p_to.x; - old_y = p_to.y; + old_x = p_position.x; + old_y = p_position.y; } else { POINT p; - p.x = p_to.x; - p.y = p_to.y; + p.x = p_position.x; + p.y = p_position.y; ClientToScreen(windows[last_focused_window].hWnd, &p); SetCursorPos(p.x, p.y); @@ -1917,16 +1917,14 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { _THREAD_SAFE_METHOD_ #if defined(VULKAN_ENABLED) - // TODO disabling for now - //context_vulkan->set_vsync_mode(p_window, p_vsync_mode); + context_vulkan->set_vsync_mode(p_window, p_vsync_mode); #endif } DisplayServer::VSyncMode DisplayServerWindows::window_get_vsync_mode(WindowID p_window) const { _THREAD_SAFE_METHOD_ #if defined(VULKAN_ENABLED) - //TODO disabling for now - //return context_vulkan->get_vsync_mode(p_window); + return context_vulkan->get_vsync_mode(p_window); #endif return DisplayServer::VSYNC_ENABLED; } @@ -2122,7 +2120,6 @@ LRESULT DisplayServerWindows::MouseProc(int code, WPARAM wParam, LPARAM lParam) case WM_NCRBUTTONDOWN: case WM_NCMBUTTONDOWN: case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: { MOUSEHOOKSTRUCT *ms = (MOUSEHOOKSTRUCT *)lParam; Point2i pos = Point2i(ms->pt.x, ms->pt.y); @@ -3598,12 +3595,11 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win return; } - //gl_manager->set_use_vsync(current_videomode.use_vsync); RasterizerGLES3::make_current(); } #endif - HHOOK mouse_monitor = SetWindowsHookEx(WH_MOUSE, ::MouseProc, nullptr, GetCurrentThreadId()); + mouse_monitor = SetWindowsHookEx(WH_MOUSE, ::MouseProc, nullptr, GetCurrentThreadId()); Point2i window_position( (screen_get_size(0).width - p_resolution.width) / 2, diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index a56a2b83ac..71fedf2bca 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -457,7 +457,7 @@ public: virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; - virtual void mouse_warp_to_position(const Point2i &p_to) override; + virtual void warp_mouse(const Point2i &p_position) override; virtual Point2i mouse_get_position() const override; virtual MouseButton mouse_get_button_state() const override; diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 17a24c08bf..0fa2913218 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -32,8 +32,6 @@ #include "export_plugin.h" -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); - void register_windows_exporter() { EDITOR_DEF("export/windows/rcedit", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/rcedit", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); @@ -57,84 +55,7 @@ void register_windows_exporter() { logo->create_from_image(img); platform->set_logo(logo); platform->set_name("Windows Desktop"); - platform->set_extension("exe"); - platform->set_release_32("windows_32_release.exe"); - platform->set_debug_32("windows_32_debug.exe"); - platform->set_release_64("windows_64_release.exe"); - platform->set_debug_64("windows_64_debug.exe"); platform->set_os_name("Windows"); - platform->set_fixup_embedded_pck_func(&fixup_embedded_pck); EditorExport::get_singleton()->add_export_platform(platform); } - -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { - // Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data - - FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); - if (!f) { - return ERR_CANT_OPEN; - } - - // Jump to the PE header and check the magic number - { - f->seek(0x3c); - uint32_t pe_pos = f->get_32(); - - f->seek(pe_pos); - uint32_t magic = f->get_32(); - if (magic != 0x00004550) { - f->close(); - return ERR_FILE_CORRUPT; - } - } - - // Process header - - int num_sections; - { - int64_t header_pos = f->get_position(); - - f->seek(header_pos + 2); - num_sections = f->get_16(); - f->seek(header_pos + 16); - uint16_t opt_header_size = f->get_16(); - - // Skip rest of header + optional header to go to the section headers - f->seek(f->get_position() + 2 + opt_header_size); - } - - // Search for the "pck" section - - int64_t section_table_pos = f->get_position(); - - bool found = false; - for (int i = 0; i < num_sections; ++i) { - int64_t section_header_pos = section_table_pos + i * 40; - f->seek(section_header_pos); - - uint8_t section_name[9]; - f->get_buffer(section_name, 8); - section_name[8] = '\0'; - - if (strcmp((char *)section_name, "pck") == 0) { - // "pck" section found, let's patch! - - // Set virtual size to a little to avoid it taking memory (zero would give issues) - f->seek(section_header_pos + 8); - f->store_32(8); - - f->seek(section_header_pos + 16); - f->store_32(p_embedded_size); - f->seek(section_header_pos + 20); - f->store_32(p_embedded_start); - - found = true; - break; - } - } - - f->close(); - - return found ? OK : ERR_FILE_CORRUPT; -} diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 5ebc930735..e627253739 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -41,6 +41,18 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres } } +Error EditorExportPlatformWindows::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) { + FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); + + f->store_line("@echo off"); + f->store_line("title \"" + p_app_name + "\""); + f->store_line("\"%~dp0" + p_pkg_name + "\" \"%*\""); + f->store_line("pause > nul"); + + return OK; +} + Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags); @@ -54,9 +66,36 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> err = _code_sign(p_preset, p_path); } + String app_name; + if (String(ProjectSettings::get_singleton()->get("application/config/name")) != "") { + app_name = String(ProjectSettings::get_singleton()->get("application/config/name")); + } else { + app_name = "Unnamed"; + } + app_name = OS::get_singleton()->get_safe_dir_name(app_name); + + // Save console script. + if (err == OK) { + int con_scr = p_preset->get("debug/export_console_script"); + if ((con_scr == 1 && p_debug) || (con_scr == 2)) { + String scr_path = p_path.get_basename() + ".cmd"; + err = _export_debug_script(p_preset, app_name, p_path.get_file(), scr_path); + } + } + return err; } +String EditorExportPlatformWindows::get_template_file_name(const String &p_target, const String &p_arch) const { + return "windows_" + p_arch + "_" + p_target + ".exe"; +} + +List<String> EditorExportPlatformWindows::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + List<String> list; + list.push_back("exe"); + return list; +} + bool EditorExportPlatformWindows::get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { // This option is not supported by "osslsigncode", used on non-Windows host. if (!OS::get_singleton()->has_feature("windows") && p_option == "codesign/identity_type") { @@ -374,3 +413,74 @@ bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_pr return valid; } + +Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const { + // Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data + + FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); + if (!f) { + return ERR_CANT_OPEN; + } + + // Jump to the PE header and check the magic number + { + f->seek(0x3c); + uint32_t pe_pos = f->get_32(); + + f->seek(pe_pos); + uint32_t magic = f->get_32(); + if (magic != 0x00004550) { + f->close(); + return ERR_FILE_CORRUPT; + } + } + + // Process header + + int num_sections; + { + int64_t header_pos = f->get_position(); + + f->seek(header_pos + 2); + num_sections = f->get_16(); + f->seek(header_pos + 16); + uint16_t opt_header_size = f->get_16(); + + // Skip rest of header + optional header to go to the section headers + f->seek(f->get_position() + 2 + opt_header_size); + } + + // Search for the "pck" section + + int64_t section_table_pos = f->get_position(); + + bool found = false; + for (int i = 0; i < num_sections; ++i) { + int64_t section_header_pos = section_table_pos + i * 40; + f->seek(section_header_pos); + + uint8_t section_name[9]; + f->get_buffer(section_name, 8); + section_name[8] = '\0'; + + if (strcmp((char *)section_name, "pck") == 0) { + // "pck" section found, let's patch! + + // Set virtual size to a little to avoid it taking memory (zero would give issues) + f->seek(section_header_pos + 8); + f->store_32(8); + + f->seek(section_header_pos + 16); + f->store_32(p_embedded_size); + f->seek(section_header_pos + 20); + f->store_32(p_embedded_start); + + found = true; + break; + } + } + + f->close(); + + return found ? OK : ERR_FILE_CORRUPT; +} diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index 86e9d49b05..39d1cf4c77 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -40,13 +40,17 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC { void _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path); Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path); + Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); public: virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; 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 Map<StringName, Variant> &p_options) const override; virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; + 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) const override; }; #endif diff --git a/platform/windows/godot.ico b/platform/windows/godot.ico Binary files differindex dd611e07da..25830ffdc6 100644 --- a/platform/windows/godot.ico +++ b/platform/windows/godot.ico diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 13e3aa7883..b4669e452a 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -264,12 +264,19 @@ OS::Date OS_Windows::get_date(bool p_utc) const { GetLocalTime(&systemtime); } + //Get DST information from Windows, but only if p_utc is false. + TIME_ZONE_INFORMATION info; + bool daylight = false; + if (!p_utc && GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) { + daylight = true; + } + Date date; date.day = systemtime.wDay; date.month = Month(systemtime.wMonth); date.weekday = Weekday(systemtime.wDayOfWeek); date.year = systemtime.wYear; - date.dst = false; + date.dst = daylight; return date; } @@ -295,16 +302,19 @@ OS::TimeZoneInfo OS_Windows::get_time_zone_info() const { daylight = true; } + // Daylight Bias needs to be added to the bias if DST is in effect, or else it will not properly update. TimeZoneInfo ret; if (daylight) { ret.name = info.DaylightName; + ret.bias = info.Bias + info.DaylightBias; } else { ret.name = info.StandardName; + ret.bias = info.Bias + info.StandardBias; } // Bias value returned by GetTimeZoneInformation is inverted of what we expect // For example, on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 - ret.bias = -info.Bias; + ret.bias = -ret.bias; return ret; } |