diff options
Diffstat (limited to 'platform')
58 files changed, 2611 insertions, 605 deletions
diff --git a/platform/android/detect.py b/platform/android/detect.py index ec36a40941..7515d0020d 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -32,6 +32,16 @@ def get_opts(): ] +def get_doc_classes(): + return [ + "EditorExportPlatformAndroid", + ] + + +def get_doc_path(): + return "doc_classes" + + # Return the ANDROID_SDK_ROOT environment variable. def get_env_android_sdk_root(): return os.environ.get("ANDROID_SDK_ROOT", -1) diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml new file mode 100644 index 0000000000..570e8f01f1 --- /dev/null +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -0,0 +1,581 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformAndroid" inherits="EditorExportPlatform" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Exporter for Android. + </brief_description> + <description> + </description> + <tutorials> + <link title="Exporting for Android">$DOCS_URL/tutorials/export/exporting_for_android.html</link> + <link title="Custom builds for Android">$DOCS_URL/tutorials/export/android_custom_build.html</link> + </tutorials> + <members> + <member name="apk_expansion/SALT" type="String" setter="" getter=""> + Array of random bytes that the licensing Policy uses to create an [url=https://developer.android.com/google/play/licensing/adding-licensing#impl-Obfuscator]Obfuscator[/url]. + </member> + <member name="apk_expansion/enable" type="bool" setter="" getter=""> + If [code]true[/code], project resources are stored in the separate APK expansion file, instead APK. + [b]Note:[/b] APK expansion should be enabled to use PCK encryption. + </member> + <member name="apk_expansion/public_key" type="String" setter="" getter=""> + Base64 encoded RSA public key for your publisher account, available from the profile page on the "Play Console". + </member> + <member name="architectures/arm64-v8a" type="bool" setter="" getter=""> + If [code]true[/code], [code]arm64[/code] binaries are included into exported project. + </member> + <member name="architectures/armeabi-v7a" type="bool" setter="" getter=""> + If [code]true[/code], [code]arm32[/code] binaries are included into exported project. + </member> + <member name="architectures/x86" type="bool" setter="" getter=""> + If [code]true[/code], [code]x86_32[/code] binaries are included into exported project. + </member> + <member name="architectures/x86_64" type="bool" setter="" getter=""> + If [code]true[/code], [code]x86_64[/code] binaries are included into exported project. + </member> + <member name="command_line/extra_args" type="String" setter="" getter=""> + A list of additional command line arguments, exported project will receive when started. + </member> + <member name="custom_template/debug" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="custom_template/release" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="gradle_build/export_format" type="int" setter="" getter=""> + Export format for Gradle build. + </member> + <member name="gradle_build/min_sdk" type="String" setter="" getter=""> + Minimal Android SDK version for Gradle build. + </member> + <member name="gradle_build/target_sdk" type="String" setter="" getter=""> + Target Android SDK version for Gradle build. + </member> + <member name="gradle_build/use_gradle_build" type="bool" setter="" getter=""> + If [code]true[/code], Gradle build is used instead of pre-built APK. + </member> + <member name="graphics/opengl_debug" type="bool" setter="" getter=""> + If [code]true[/code], OpenGL ES debug context will be created (additional runtime checking, validation, and logging). + </member> + <member name="keystore/debug" type="String" setter="" getter=""> + Path of the debug keystore file. + </member> + <member name="keystore/debug_password" type="String" setter="" getter=""> + Password for the debug keystore file. + </member> + <member name="keystore/debug_user" type="String" setter="" getter=""> + User name for the debug keystore file. + </member> + <member name="keystore/release" type="String" setter="" getter=""> + Path of the release keystore file. + </member> + <member name="keystore/release_password" type="String" setter="" getter=""> + Password for the release keystore file. + </member> + <member name="keystore/release_user" type="String" setter="" getter=""> + User name for the release keystore file. + </member> + <member name="launcher_icons/adaptive_background_432x432" type="String" setter="" getter=""> + Background layer of the application adaptive icon file. + </member> + <member name="launcher_icons/adaptive_foreground_432x432" type="String" setter="" getter=""> + Foreground layer of the application adaptive icon file. + </member> + <member name="launcher_icons/main_192x192" type="String" setter="" getter=""> + Application icon file. If left empty, project icon is used instead. + </member> + <member name="package/app_category" type="int" setter="" getter=""> + Application category for the Play Store. + </member> + <member name="package/exclude_from_recents" type="bool" setter="" getter=""> + If [code]true[/code], task initiated by main activity will be excluded from the list of recently used applications. + </member> + <member name="package/name" type="String" setter="" getter=""> + Name of the application. + </member> + <member name="package/retain_data_on_uninstall" type="bool" setter="" getter=""> + If [code]true[/code], when the user uninstalls an app, a prompt to keep the app's data will be shown. + </member> + <member name="package/signed" type="bool" setter="" getter=""> + If [code]true[/code], package signing is enabled. + </member> + <member name="package/unique_name" type="String" setter="" getter=""> + Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]). + </member> + <member name="permissions/access_checkin_properties" type="bool" setter="" getter=""> + Allows read/write access to the "properties" table in the checkin database. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_CHECKIN_PROPERTIES]ACCESS_CHECKIN_PROPERTIES[/url]. + </member> + <member name="permissions/access_coarse_location" type="bool" setter="" getter=""> + Allows access to the approximate location information. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_COARSE_LOCATION]ACCESS_COARSE_LOCATION[/url]. + </member> + <member name="permissions/access_fine_location" type="bool" setter="" getter=""> + Allows access to the precise location information. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_FINE_LOCATION]ACCESS_FINE_LOCATION[/url]. + </member> + <member name="permissions/access_location_extra_commands" type="bool" setter="" getter=""> + Allows access to the extra location provider commands. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_LOCATION_EXTRA_COMMANDS]ACCESS_LOCATION_EXTRA_COMMANDS[/url]. + </member> + <member name="permissions/access_mock_location" type="bool" setter="" getter=""> + Allows an application to create mock location providers for testing. + </member> + <member name="permissions/access_network_state" type="bool" setter="" getter=""> + Allows access to the information about networks. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_NETWORK_STATE]ACCESS_NETWORK_STATE[/url]. + </member> + <member name="permissions/access_surface_flinger" type="bool" setter="" getter=""> + Allows an application to use SurfaceFlinger's low level features. + </member> + <member name="permissions/access_wifi_state" type="bool" setter="" getter=""> + Allows access to the information about Wi-Fi networks. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_WIFI_STATE]ACCESS_WIFI_STATE[/url]. + </member> + <member name="permissions/account_manager" type="bool" setter="" getter=""> + Allows applications to call into AccountAuthenticators. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCOUNT_MANAGER]ACCOUNT_MANAGER[/url]. + </member> + <member name="permissions/add_voicemail" type="bool" setter="" getter=""> + Allows an application to add voicemails into the system. See [url=https://developer.android.com/reference/android/Manifest.permission#ADD_VOICEMAIL]ADD_VOICEMAIL[/url]. + </member> + <member name="permissions/authenticate_accounts" type="bool" setter="" getter=""> + Allows an application to act as an AccountAuthenticator for the AccountManager. + </member> + <member name="permissions/battery_stats" type="bool" setter="" getter=""> + Allows an application to collect battery statistics. Sett [url=https://developer.android.com/reference/android/Manifest.permission#BATTERY_STATS]BATTERY_STATS[/url]. + </member> + <member name="permissions/bind_accessibility_service" type="bool" setter="" getter=""> + Must be required by an AccessibilityService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_ACCESSIBILITY_SERVICE]BIND_ACCESSIBILITY_SERVICE[/url]. + </member> + <member name="permissions/bind_appwidget" type="bool" setter="" getter=""> + Allows an application to tell the AppWidget service which application can access AppWidget's data. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_APPWIDGET]BIND_APPWIDGET[/url]. + </member> + <member name="permissions/bind_device_admin" type="bool" setter="" getter=""> + Must be required by device administration receiver, to ensure that only the system can interact with it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_DEVICE_ADMIN]BIND_DEVICE_ADMIN[/url]. + </member> + <member name="permissions/bind_input_method" type="bool" setter="" getter=""> + Must be required by an InputMethodService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_INPUT_METHOD]BIND_INPUT_METHOD[/url]. + </member> + <member name="permissions/bind_nfc_service" type="bool" setter="" getter=""> + Must be required by a HostApduService or OffHostApduService to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_NFC_SERVICE]BIND_NFC_SERVICE[/url]. + </member> + <member name="permissions/bind_notification_listener_service" type="bool" setter="" getter=""> + Must be required by an NotificationListenerService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE]BIND_NOTIFICATION_LISTENER_SERVICE[/url]. + </member> + <member name="permissions/bind_print_service" type="bool" setter="" getter=""> + Must be required by a PrintService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_PRINT_SERVICE]BIND_PRINT_SERVICE[/url]. + </member> + <member name="permissions/bind_remoteviews" type="bool" setter="" getter=""> + Must be required by a RemoteViewsService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_REMOTEVIEWS]BIND_REMOTEVIEWS[/url]. + </member> + <member name="permissions/bind_text_service" type="bool" setter="" getter=""> + Must be required by a TextService (e.g. SpellCheckerService) to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_TEXT_SERVICE]BIND_TEXT_SERVICE[/url]. + </member> + <member name="permissions/bind_vpn_service" type="bool" setter="" getter=""> + Must be required by a VpnService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_VPN_SERVICE]BIND_VPN_SERVICE[/url]. + </member> + <member name="permissions/bind_wallpaper" type="bool" setter="" getter=""> + Must be required by a WallpaperService, to ensure that only the system can bind to it. See [url=https://developer.android.com/reference/android/Manifest.permission#BIND_WALLPAPER]BIND_WALLPAPER[/url]. + </member> + <member name="permissions/bluetooth" type="bool" setter="" getter=""> + Allows applications to connect to paired bluetooth devices. See [url=https://developer.android.com/reference/android/Manifest.permission#BLUETOOTH]BLUETOOTH[/url]. + </member> + <member name="permissions/bluetooth_admin" type="bool" setter="" getter=""> + Allows applications to discover and pair bluetooth devices. See [url=https://developer.android.com/reference/android/Manifest.permission#BLUETOOTH_ADMIN]BLUETOOTH_ADMIN[/url]. + </member> + <member name="permissions/bluetooth_privileged" type="bool" setter="" getter=""> + Allows applications to pair bluetooth devices without user interaction, and to allow or disallow phonebook access or message access. See [url=https://developer.android.com/reference/android/Manifest.permission#BLUETOOTH_PRIVILEGED]BLUETOOTH_PRIVILEGED[/url]. + </member> + <member name="permissions/brick" type="bool" setter="" getter=""> + Required to be able to disable the device (very dangerous!). + </member> + <member name="permissions/broadcast_package_removed" type="bool" setter="" getter=""> + Allows an application to broadcast a notification that an application package has been removed. See [url=https://developer.android.com/reference/android/Manifest.permission#BROADCAST_PACKAGE_REMOVED]BROADCAST_PACKAGE_REMOVED[/url]. + </member> + <member name="permissions/broadcast_sms" type="bool" setter="" getter=""> + Allows an application to broadcast an SMS receipt notification. See [url=https://developer.android.com/reference/android/Manifest.permission#BROADCAST_SMS]BROADCAST_SMS[/url]. + </member> + <member name="permissions/broadcast_sticky" type="bool" setter="" getter=""> + Allows an application to broadcast sticky intents. See [url=https://developer.android.com/reference/android/Manifest.permission#BROADCAST_STICKY]BROADCAST_STICKY[/url]. + </member> + <member name="permissions/broadcast_wap_push" type="bool" setter="" getter=""> + Allows an application to broadcast a WAP PUSH receipt notification. See [url=https://developer.android.com/reference/android/Manifest.permission#BROADCAST_WAP_PUSH]BROADCAST_WAP_PUSH[/url]. + </member> + <member name="permissions/call_phone" type="bool" setter="" getter=""> + Allows an application to initiate a phone call without going through the Dialer user interface. See [url=https://developer.android.com/reference/android/Manifest.permission#CALL_PHONE]CALL_PHONE[/url]. + </member> + <member name="permissions/call_privileged" type="bool" setter="" getter=""> + Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface. See [url=https://developer.android.com/reference/android/Manifest.permission#CALL_PRIVILEGED]CALL_PRIVILEGED[/url]. + </member> + <member name="permissions/camera" type="bool" setter="" getter=""> + Required to be able to access the camera device. See [url=https://developer.android.com/reference/android/Manifest.permission#CAMERA]CAMERA[/url]. + </member> + <member name="permissions/capture_audio_output" type="bool" setter="" getter=""> + Allows an application to capture audio output. See [url=https://developer.android.com/reference/android/Manifest.permission#CAPTURE_AUDIO_OUTPUT]CAPTURE_AUDIO_OUTPUT[/url]. + </member> + <member name="permissions/capture_secure_video_output" type="bool" setter="" getter=""> + Allows an application to capture secure video output. + </member> + <member name="permissions/capture_video_output" type="bool" setter="" getter=""> + Allows an application to capture video output. + </member> + <member name="permissions/change_component_enabled_state" type="bool" setter="" getter=""> + Allows an application to change whether an application component (other than its own) is enabled or not. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_COMPONENT_ENABLED_STATE]CHANGE_COMPONENT_ENABLED_STATE[/url]. + </member> + <member name="permissions/change_configuration" type="bool" setter="" getter=""> + Allows an application to modify the current configuration, such as locale. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_CONFIGURATION]CHANGE_CONFIGURATION[/url]. + </member> + <member name="permissions/change_network_state" type="bool" setter="" getter=""> + Allows applications to change network connectivity state. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_NETWORK_STATE]CHANGE_NETWORK_STATE[/url]. + </member> + <member name="permissions/change_wifi_multicast_state" type="bool" setter="" getter=""> + Allows applications to enter Wi-Fi Multicast mode. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_WIFI_MULTICAST_STATE]CHANGE_WIFI_MULTICAST_STATE[/url]. + </member> + <member name="permissions/change_wifi_state" type="bool" setter="" getter=""> + Allows applications to change Wi-Fi connectivity state. See [url=https://developer.android.com/reference/android/Manifest.permission#CHANGE_WIFI_STATE]CHANGE_WIFI_STATE[/url]. + </member> + <member name="permissions/clear_app_cache" type="bool" setter="" getter=""> + Allows an application to clear the caches of all installed applications on the device. See [url=https://developer.android.com/reference/android/Manifest.permission#CLEAR_APP_CACHE]CLEAR_APP_CACHE[/url]. + </member> + <member name="permissions/clear_app_user_data" type="bool" setter="" getter=""> + Allows an application to clear user data. + </member> + <member name="permissions/control_location_updates" type="bool" setter="" getter=""> + Allows enabling/disabling location update notifications from the radio. See [url=https://developer.android.com/reference/android/Manifest.permission#CONTROL_LOCATION_UPDATES]CONTROL_LOCATION_UPDATES[/url]. + </member> + <member name="permissions/custom_permissions" type="PackedStringArray" setter="" getter=""> + Array of custom permission strings. + </member> + <member name="permissions/delete_cache_files" type="bool" setter="" getter=""> + Deprecated. + </member> + <member name="permissions/delete_packages" type="bool" setter="" getter=""> + Allows an application to delete packages. See [url=https://developer.android.com/reference/android/Manifest.permission#DELETE_PACKAGES]DELETE_PACKAGES[/url]. + </member> + <member name="permissions/device_power" type="bool" setter="" getter=""> + Allows low-level access to power management. + </member> + <member name="permissions/diagnostic" type="bool" setter="" getter=""> + Allows applications to RW to diagnostic resources. See [url=https://developer.android.com/reference/android/Manifest.permission#DIAGNOSTIC]DIAGNOSTIC[/url]. + </member> + <member name="permissions/disable_keyguard" type="bool" setter="" getter=""> + Allows applications to disable the keyguard if it is not secure. See [url=https://developer.android.com/reference/android/Manifest.permission#DISABLE_KEYGUARD]DISABLE_KEYGUARD[/url]. + </member> + <member name="permissions/dump" type="bool" setter="" getter=""> + Allows an application to retrieve state dump information from system services. See [url=https://developer.android.com/reference/android/Manifest.permission#DUMP]DUMP[/url]. + </member> + <member name="permissions/expand_status_bar" type="bool" setter="" getter=""> + Allows an application to expand or collapse the status bar. See [url=https://developer.android.com/reference/android/Manifest.permission#EXPAND_STATUS_BAR]EXPAND_STATUS_BAR[/url]. + </member> + <member name="permissions/factory_test" type="bool" setter="" getter=""> + Run as a manufacturer test application, running as the root user. See [url=https://developer.android.com/reference/android/Manifest.permission#FACTORY_TEST]FACTORY_TEST[/url]. + </member> + <member name="permissions/flashlight" type="bool" setter="" getter=""> + Allows access to the flashlight. + </member> + <member name="permissions/force_back" type="bool" setter="" getter=""> + Allows an application to force a BACK operation on whatever is the top activity. + </member> + <member name="permissions/get_accounts" type="bool" setter="" getter=""> + Allows access to the list of accounts in the Accounts Service. See [url=https://developer.android.com/reference/android/Manifest.permission#GET_ACCOUNTS]GET_ACCOUNTS[/url]. + </member> + <member name="permissions/get_package_size" type="bool" setter="" getter=""> + Allows an application to find out the space used by any package. See [url=https://developer.android.com/reference/android/Manifest.permission#GET_PACKAGE_SIZE]GET_PACKAGE_SIZE[/url]. + </member> + <member name="permissions/get_tasks" type="bool" setter="" getter=""> + Deprecated in API level 21. + </member> + <member name="permissions/get_top_activity_info" type="bool" setter="" getter=""> + Allows an application to retrieve private information about the current top activity. + </member> + <member name="permissions/global_search" type="bool" setter="" getter=""> + Used on content providers to allow the global search system to access their data. See [url=https://developer.android.com/reference/android/Manifest.permission#GLOBAL_SEARCH]GLOBAL_SEARCH[/url]. + </member> + <member name="permissions/hardware_test" type="bool" setter="" getter=""> + Allows access to hardware peripherals. + </member> + <member name="permissions/inject_events" type="bool" setter="" getter=""> + Allows an application to inject user events (keys, touch, trackball) into the event stream and deliver them to ANY window. + </member> + <member name="permissions/install_location_provider" type="bool" setter="" getter=""> + Allows an application to install a location provider into the Location Manager. See [url=https://developer.android.com/reference/android/Manifest.permission#INSTALL_LOCATION_PROVIDER]INSTALL_LOCATION_PROVIDER[/url]. + </member> + <member name="permissions/install_packages" type="bool" setter="" getter=""> + Allows an application to install packages. See [url=https://developer.android.com/reference/android/Manifest.permission#INSTALL_PACKAGES]INSTALL_PACKAGES[/url]. + </member> + <member name="permissions/install_shortcut" type="bool" setter="" getter=""> + Allows an application to install a shortcut in Launcher. See [url=https://developer.android.com/reference/android/Manifest.permission#INSTALL_SHORTCUT]INSTALL_SHORTCUT[/url]. + </member> + <member name="permissions/internal_system_window" type="bool" setter="" getter=""> + Allows an application to open windows that are for use by parts of the system user interface. + </member> + <member name="permissions/internet" type="bool" setter="" getter=""> + Allows applications to open network sockets. See [url=https://developer.android.com/reference/android/Manifest.permission#INTERNET]INTERNET[/url]. + </member> + <member name="permissions/kill_background_processes" type="bool" setter="" getter=""> + Allows an application to call ActivityManager.killBackgroundProcesses(String). See [url=https://developer.android.com/reference/android/Manifest.permission#KILL_BACKGROUND_PROCESSES]KILL_BACKGROUND_PROCESSES[/url]. + </member> + <member name="permissions/location_hardware" type="bool" setter="" getter=""> + Allows an application to use location features in hardware, such as the geofencing api. See [url=https://developer.android.com/reference/android/Manifest.permission#LOCATION_HARDWARE]LOCATION_HARDWARE[/url]. + </member> + <member name="permissions/manage_accounts" type="bool" setter="" getter=""> + Allows an application to manage the list of accounts in the AccountManager. + </member> + <member name="permissions/manage_app_tokens" type="bool" setter="" getter=""> + Allows an application to manage (create, destroy, Z-order) application tokens in the window manager. + </member> + <member name="permissions/manage_documents" type="bool" setter="" getter=""> + Allows an application to manage access to documents, usually as part of a document picker. See [url=https://developer.android.com/reference/android/Manifest.permission#MANAGE_DOCUMENTS]MANAGE_DOCUMENTS[/url]. + </member> + <member name="permissions/manage_external_storage" type="bool" setter="" getter=""> + Allows an application a broad access to external storage in scoped storage. See [url=https://developer.android.com/reference/android/Manifest.permission#MANAGE_EXTERNAL_STORAGE]MANAGE_EXTERNAL_STORAGE[/url]. + </member> + <member name="permissions/master_clear" type="bool" setter="" getter=""> + See [url=https://developer.android.com/reference/android/Manifest.permission#MASTER_CLEAR]MASTER_CLEAR[/url]. + </member> + <member name="permissions/media_content_control" type="bool" setter="" getter=""> + Allows an application to know what content is playing and control its playback. See [url=https://developer.android.com/reference/android/Manifest.permission#MEDIA_CONTENT_CONTROL]MEDIA_CONTENT_CONTROL[/url]. + </member> + <member name="permissions/modify_audio_settings" type="bool" setter="" getter=""> + Allows an application to modify global audio settings. See [url=https://developer.android.com/reference/android/Manifest.permission#MODIFY_AUDIO_SETTINGS]MODIFY_AUDIO_SETTINGS[/url]. + </member> + <member name="permissions/modify_phone_state" type="bool" setter="" getter=""> + Allows modification of the telephony state - power on, mmi, etc. Does not include placing calls. See [url=https://developer.android.com/reference/android/Manifest.permission#MODIFY_PHONE_STATE]MODIFY_PHONE_STATE[/url]. + </member> + <member name="permissions/mount_format_filesystems" type="bool" setter="" getter=""> + Allows formatting file systems for removable storage. See [url=https://developer.android.com/reference/android/Manifest.permission#MOUNT_FORMAT_FILESYSTEMS]MOUNT_FORMAT_FILESYSTEMS[/url]. + </member> + <member name="permissions/mount_unmount_filesystems" type="bool" setter="" getter=""> + Allows mounting and unmounting file systems for removable storage. See [url=https://developer.android.com/reference/android/Manifest.permission#MOUNT_UNMOUNT_FILESYSTEMS]MOUNT_UNMOUNT_FILESYSTEMS[/url]. + </member> + <member name="permissions/nfc" type="bool" setter="" getter=""> + Allows applications to perform I/O operations over NFC. See [url=https://developer.android.com/reference/android/Manifest.permission#NFC]NFC[/url]. + </member> + <member name="permissions/persistent_activity" type="bool" setter="" getter=""> + Allow an application to make its activities persistent. + Deprecated in API level 15. + </member> + <member name="permissions/process_outgoing_calls" type="bool" setter="" getter=""> + Allows an application to see the number being dialed during an outgoing call with the option to redirect the call to a different number or abort the call altogether. See [url=https://developer.android.com/reference/android/Manifest.permission#PROCESS_OUTGOING_CALLS]PROCESS_OUTGOING_CALLS[/url]. + Deprecated in API level 29. + </member> + <member name="permissions/read_calendar" type="bool" setter="" getter=""> + Allows an application to read the user's calendar data. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_CALENDAR]READ_CALENDAR[/url]. + </member> + <member name="permissions/read_call_log" type="bool" setter="" getter=""> + Allows an application to read the user's call log. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_CALL_LOG]READ_CALL_LOG[/url]. + </member> + <member name="permissions/read_contacts" type="bool" setter="" getter=""> + Allows an application to read the user's contacts data. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_CONTACTS]READ_CONTACTS[/url]. + </member> + <member name="permissions/read_external_storage" type="bool" setter="" getter=""> + Allows an application to read from external storage. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE]READ_EXTERNAL_STORAGE[/url]. + Deprecated in API level 33. + </member> + <member name="permissions/read_frame_buffer" type="bool" setter="" getter=""> + Allows an application to take screen shots and more generally get access to the frame buffer data. + </member> + <member name="permissions/read_history_bookmarks" type="bool" setter="" getter=""> + Allows an application to read (but not write) the user's browsing history and bookmarks. + </member> + <member name="permissions/read_input_state" type="bool" setter="" getter=""> + Deprecated in API level 16. + </member> + <member name="permissions/read_logs" type="bool" setter="" getter=""> + Allows an application to read the low-level system log files. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_LOGS]READ_LOGS[/url]. + </member> + <member name="permissions/read_phone_state" type="bool" setter="" getter=""> + Allows read only access to phone state. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_PHONE_STATE]READ_PHONE_STATE[/url]. + </member> + <member name="permissions/read_profile" type="bool" setter="" getter=""> + Allows an application to read the user's personal profile data. + </member> + <member name="permissions/read_sms" type="bool" setter="" getter=""> + Allows an application to read SMS messages. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_SMS]READ_SMS[/url]. + </member> + <member name="permissions/read_social_stream" type="bool" setter="" getter=""> + Allows an application to read from the user's social stream. + </member> + <member name="permissions/read_sync_settings" type="bool" setter="" getter=""> + Allows applications to read the sync settings. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_SYNC_SETTINGS]READ_SYNC_SETTINGS[/url]. + </member> + <member name="permissions/read_sync_stats" type="bool" setter="" getter=""> + Allows applications to read the sync stats. See [url=https://developer.android.com/reference/android/Manifest.permission#READ_SYNC_STATS]READ_SYNC_STATS[/url]. + </member> + <member name="permissions/read_user_dictionary" type="bool" setter="" getter=""> + Allows an application to read the user dictionary. + </member> + <member name="permissions/reboot" type="bool" setter="" getter=""> + Required to be able to reboot the device. See [url=https://developer.android.com/reference/android/Manifest.permission#REBOOT]REBOOT[/url]. + </member> + <member name="permissions/receive_boot_completed" type="bool" setter="" getter=""> + Allows an application to receive the Intent.ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting. See [url=https://developer.android.com/reference/android/Manifest.permission#RECEIVE_BOOT_COMPLETED]RECEIVE_BOOT_COMPLETED[/url]. + </member> + <member name="permissions/receive_mms" type="bool" setter="" getter=""> + Allows an application to monitor incoming MMS messages. See [url=https://developer.android.com/reference/android/Manifest.permission#RECEIVE_MMS]RECEIVE_MMS[/url]. + </member> + <member name="permissions/receive_sms" type="bool" setter="" getter=""> + Allows an application to receive SMS messages. See [url=https://developer.android.com/reference/android/Manifest.permission#RECEIVE_SMS]RECEIVE_SMS[/url]. + </member> + <member name="permissions/receive_wap_push" type="bool" setter="" getter=""> + Allows an application to receive WAP push messages. See [url=https://developer.android.com/reference/android/Manifest.permission#RECEIVE_WAP_PUSH]RECEIVE_WAP_PUSH[/url]. + </member> + <member name="permissions/record_audio" type="bool" setter="" getter=""> + Allows an application to record audio. See [url=https://developer.android.com/reference/android/Manifest.permission#RECORD_AUDIO]RECORD_AUDIO[/url]. + </member> + <member name="permissions/reorder_tasks" type="bool" setter="" getter=""> + Allows an application to change the Z-order of tasks. See [url= https://developer.android.com/reference/android/Manifest.permission#REORDER_TASKS]REORDER_TASKS[/url]. + </member> + <member name="permissions/restart_packages" type="bool" setter="" getter=""> + Deprecated in API level 15. + </member> + <member name="permissions/send_respond_via_message" type="bool" setter="" getter=""> + Allows an application (Phone) to send a request to other applications to handle the respond-via-message action during incoming calls. See [url=https://developer.android.com/reference/android/Manifest.permission#SEND_RESPOND_VIA_MESSAGE]SEND_RESPOND_VIA_MESSAGE[/url]. + </member> + <member name="permissions/send_sms" type="bool" setter="" getter=""> + Allows an application to send SMS messages. See [url=https://developer.android.com/reference/android/Manifest.permission#SEND_SMS]SEND_SMS[/url]. + </member> + <member name="permissions/set_activity_watcher" type="bool" setter="" getter=""> + Allows an application to watch and control how activities are started globally in the system. + </member> + <member name="permissions/set_alarm" type="bool" setter="" getter=""> + Allows an application to broadcast an Intent to set an alarm for the user. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_ALARM]SET_ALARM[/url]. + </member> + <member name="permissions/set_always_finish" type="bool" setter="" getter=""> + Allows an application to control whether activities are immediately finished when put in the background. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_ALWAYS_FINISH]SET_ALWAYS_FINISH[/url]. + </member> + <member name="permissions/set_animation_scale" type="bool" setter="" getter=""> + Allows to modify the global animation scaling factor. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_ANIMATION_SCALE]SET_ANIMATION_SCALE[/url]. + </member> + <member name="permissions/set_debug_app" type="bool" setter="" getter=""> + Configure an application for debugging. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_DEBUG_APP]SET_DEBUG_APP[/url]. + </member> + <member name="permissions/set_orientation" type="bool" setter="" getter=""> + Allows low-level access to setting the orientation (actually rotation) of the screen. + </member> + <member name="permissions/set_pointer_speed" type="bool" setter="" getter=""> + Allows low-level access to setting the pointer speed. + </member> + <member name="permissions/set_preferred_applications" type="bool" setter="" getter=""> + Deprecated in API level 15. + </member> + <member name="permissions/set_process_limit" type="bool" setter="" getter=""> + Allows an application to set the maximum number of (not needed) application processes that can be running. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_PROCESS_LIMIT]SET_PROCESS_LIMIT[/url]. + </member> + <member name="permissions/set_time" type="bool" setter="" getter=""> + Allows applications to set the system time directly. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_TIME]SET_TIME[/url]. + </member> + <member name="permissions/set_time_zone" type="bool" setter="" getter=""> + Allows applications to set the system time zone directly. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_TIME_ZONE]SET_TIME_ZONE[/url]. + </member> + <member name="permissions/set_wallpaper" type="bool" setter="" getter=""> + Allows applications to set the wallpaper. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_WALLPAPER]SET_WALLPAPER[/url]. + </member> + <member name="permissions/set_wallpaper_hints" type="bool" setter="" getter=""> + Allows applications to set the wallpaper hints. See [url=https://developer.android.com/reference/android/Manifest.permission#SET_WALLPAPER_HINTS]SET_WALLPAPER_HINTS[/url]. + </member> + <member name="permissions/signal_persistent_processes" type="bool" setter="" getter=""> + Allow an application to request that a signal be sent to all persistent processes. See [url=https://developer.android.com/reference/android/Manifest.permission#SIGNAL_PERSISTENT_PROCESSES]SIGNAL_PERSISTENT_PROCESSES[/url]. + </member> + <member name="permissions/status_bar" type="bool" setter="" getter=""> + Allows an application to open, close, or disable the status bar and its icons. See [url=https://developer.android.com/reference/android/Manifest.permission#STATUS_BAR]STATUS_BAR[/url]. + </member> + <member name="permissions/subscribed_feeds_read" type="bool" setter="" getter=""> + Allows an application to allow access the subscribed feeds ContentProvider. + </member> + <member name="permissions/subscribed_feeds_write" type="bool" setter="" getter=""> + Deprecated. + </member> + <member name="permissions/system_alert_window" type="bool" setter="" getter=""> + Allows an app to create windows using the type WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, shown on top of all other apps. See [url=https://developer.android.com/reference/android/Manifest.permission#SYSTEM_ALERT_WINDOW]SYSTEM_ALERT_WINDOW[/url]. + </member> + <member name="permissions/transmit_ir" type="bool" setter="" getter=""> + Allows using the device's IR transmitter, if available. See [url=https://developer.android.com/reference/android/Manifest.permission#TRANSMIT_IR]TRANSMIT_IR[/url]. + </member> + <member name="permissions/uninstall_shortcut" type="bool" setter="" getter=""> + Deprecated. + </member> + <member name="permissions/update_device_stats" type="bool" setter="" getter=""> + Allows an application to update device statistics. See [url=https://developer.android.com/reference/android/Manifest.permission#UPDATE_DEVICE_STATS]UPDATE_DEVICE_STATS[/url]. + </member> + <member name="permissions/use_credentials" type="bool" setter="" getter=""> + Allows an application to request authtokens from the AccountManager. + </member> + <member name="permissions/use_sip" type="bool" setter="" getter=""> + Allows an application to use SIP service. See [url=https://developer.android.com/reference/android/Manifest.permission#USE_SIP]USE_SIP[/url]. + </member> + <member name="permissions/vibrate" type="bool" setter="" getter=""> + Allows access to the vibrator. See [url=https://developer.android.com/reference/android/Manifest.permission#VIBRATE]VIBRATE[/url]. + </member> + <member name="permissions/wake_lock" type="bool" setter="" getter=""> + Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming. See [url=https://developer.android.com/reference/android/Manifest.permission#WAKE_LOCK]WAKE_LOCK[/url]. + </member> + <member name="permissions/write_apn_settings" type="bool" setter="" getter=""> + Allows applications to write the apn settings and read sensitive fields of an existing apn settings like user and password. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_APN_SETTINGS]WRITE_APN_SETTINGS[/url]. + </member> + <member name="permissions/write_calendar" type="bool" setter="" getter=""> + Allows an application to write the user's calendar data. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_CALENDAR]WRITE_CALENDAR[/url]. + </member> + <member name="permissions/write_call_log" type="bool" setter="" getter=""> + Allows an application to write (but not read) the user's call log data. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_CALL_LOG]WRITE_CALL_LOG[/url]. + </member> + <member name="permissions/write_contacts" type="bool" setter="" getter=""> + Allows an application to write the user's contacts data. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_CONTACTS]WRITE_CONTACTS[/url]. + </member> + <member name="permissions/write_external_storage" type="bool" setter="" getter=""> + Allows an application to write to external storage. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE]WRITE_EXTERNAL_STORAGE[/url]. + </member> + <member name="permissions/write_gservices" type="bool" setter="" getter=""> + Allows an application to modify the Google service map. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_GSERVICES]WRITE_GSERVICES[/url]. + </member> + <member name="permissions/write_history_bookmarks" type="bool" setter="" getter=""> + Allows an application to write (but not read) the user's browsing history and bookmarks. + </member> + <member name="permissions/write_profile" type="bool" setter="" getter=""> + Allows an application to write (but not read) the user's personal profile data. + </member> + <member name="permissions/write_secure_settings" type="bool" setter="" getter=""> + Allows an application to read or write the secure system settings. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_SECURE_SETTINGS]WRITE_SECURE_SETTINGS[/url]. + </member> + <member name="permissions/write_settings" type="bool" setter="" getter=""> + Allows an application to read or write the system settings. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_SETTINGS]WRITE_SETTINGS[/url]. + </member> + <member name="permissions/write_sms" type="bool" setter="" getter=""> + Allows an application to write SMS messages. + </member> + <member name="permissions/write_social_stream" type="bool" setter="" getter=""> + Allows an application to write (but not read) the user's social stream data. + </member> + <member name="permissions/write_sync_settings" type="bool" setter="" getter=""> + Allows applications to write the sync settings. See [url=https://developer.android.com/reference/android/Manifest.permission#WRITE_SYNC_SETTINGS]WRITE_SYNC_SETTINGS[/url]. + </member> + <member name="permissions/write_user_dictionary" type="bool" setter="" getter=""> + Allows an application to write to the user dictionary. + </member> + <member name="screen/immersive_mode" type="bool" setter="" getter=""> + If [code]true[/code], hides navigation and status bar. + </member> + <member name="screen/support_large" type="bool" setter="" getter=""> + Indicates whether the application supports larger screen form-factors. + </member> + <member name="screen/support_normal" type="bool" setter="" getter=""> + Indicates whether an application supports the "normal" screen form-factors. + </member> + <member name="screen/support_small" type="bool" setter="" getter=""> + Indicates whether the application supports smaller screen form-factors. + </member> + <member name="screen/support_xlarge" type="bool" setter="" getter=""> + Indicates whether the application supports extra large screen form-factors. + </member> + <member name="user_data_backup/allow" type="bool" setter="" getter=""> + If [code]true[/code], allows the application to participate in the backup and restore infrastructure. + </member> + <member name="version/code" type="int" setter="" getter=""> + Machine-readable application version. + </member> + <member name="version/name" type="String" setter="" getter=""> + Application version visible to the user. + </member> + <member name="xr_features/hand_tracking" type="int" setter="" getter=""> + </member> + <member name="xr_features/hand_tracking_frequency" type="int" setter="" getter=""> + </member> + <member name="xr_features/passthrough" type="int" setter="" getter=""> + </member> + <member name="xr_features/xr_mode" type="int" setter="" getter=""> + </member> + </members> +</class> diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index d7fe89d97d..1e048100d1 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -35,6 +35,10 @@ #include "editor/export/editor_export.h" #include "export_plugin.h" +void register_android_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformAndroid); +} + void register_android_exporter() { #ifndef ANDROID_ENABLED EDITOR_DEF("export/android/android_sdk_path", ""); diff --git a/platform/android/export/export.h b/platform/android/export/export.h index 7ada91aec9..bd689a400d 100644 --- a/platform/android/export/export.h +++ b/platform/android/export/export.h @@ -31,6 +31,7 @@ #ifndef ANDROID_EXPORT_H #define ANDROID_EXPORT_H +void register_android_exporter_types(); void register_android_exporter(); #endif // ANDROID_EXPORT_H diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index fa97a095ab..0f0132a5d1 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1703,16 +1703,109 @@ void EditorExportPlatformAndroid::get_preset_features(const Ref<EditorExportPres } } -void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_options) { +String EditorExportPlatformAndroid::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { + if (p_preset) { + if (p_name == ("apk_expansion/public_key")) { + bool apk_expansion = p_preset->get("apk_expansion/enable"); + String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); + if (apk_expansion && apk_expansion_pkey.is_empty()) { + return TTR("Invalid public key for APK expansion."); + } + } else if (p_name == "package/unique_name") { + String pn = p_preset->get("package/unique_name"); + String pn_err; + + if (!is_package_name_valid(pn, &pn_err)) { + return TTR("Invalid package name:") + " " + pn_err; + } + } else if (p_name == "gradle_build/use_gradle_build") { + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + String enabled_plugins_names = PluginConfigAndroid::get_plugins_names(get_enabled_plugins(Ref<EditorExportPreset>(p_preset))); + if (!enabled_plugins_names.is_empty() && !gradle_build_enabled) { + return TTR("\"Use Gradle Build\" must be enabled to use the plugins."); + } + } else if (p_name == "xr_features/xr_mode") { + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + if (xr_mode_index == XR_MODE_OPENXR && !gradle_build_enabled) { + return TTR("OpenXR requires \"Use Gradle Build\" to be enabled"); + } + } else if (p_name == "xr_features/hand_tracking") { + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + int hand_tracking = p_preset->get("xr_features/hand_tracking"); + if (xr_mode_index != XR_MODE_OPENXR) { + if (hand_tracking > XR_HAND_TRACKING_NONE) { + return TTR("\"Hand Tracking\" is only valid when \"XR Mode\" is \"OpenXR\"."); + } + } + } else if (p_name == "xr_features/passthrough") { + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + int passthrough_mode = p_preset->get("xr_features/passthrough"); + if (xr_mode_index != XR_MODE_OPENXR) { + if (passthrough_mode > XR_PASSTHROUGH_NONE) { + return TTR("\"Passthrough\" is only valid when \"XR Mode\" is \"OpenXR\"."); + } + } + } else if (p_name == "gradle_build/export_format") { + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + if (int(p_preset->get("gradle_build/export_format")) == EXPORT_FORMAT_AAB && !gradle_build_enabled) { + return TTR("\"Export AAB\" is only valid when \"Use Gradle Build\" is enabled."); + } + } else if (p_name == "gradle_build/min_sdk") { + String min_sdk_str = p_preset->get("gradle_build/min_sdk"); + int min_sdk_int = VULKAN_MIN_SDK_VERSION; + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + if (!min_sdk_str.is_empty()) { // Empty means no override, nothing to do. + if (!gradle_build_enabled) { + return TTR("\"Min SDK\" can only be overridden when \"Use Gradle Build\" is enabled."); + } + if (!min_sdk_str.is_valid_int()) { + return vformat(TTR("\"Min SDK\" should be a valid integer, but got \"%s\" which is invalid."), min_sdk_str); + } else { + min_sdk_int = min_sdk_str.to_int(); + if (min_sdk_int < OPENGL_MIN_SDK_VERSION) { + return vformat(TTR("\"Min SDK\" cannot be lower than %d, which is the version needed by the Godot library."), OPENGL_MIN_SDK_VERSION); + } + } + } + } else if (p_name == "gradle_build/target_sdk") { + String target_sdk_str = p_preset->get("gradle_build/target_sdk"); + int target_sdk_int = DEFAULT_TARGET_SDK_VERSION; + + String min_sdk_str = p_preset->get("gradle_build/min_sdk"); + int min_sdk_int = VULKAN_MIN_SDK_VERSION; + if (min_sdk_str.is_valid_int()) { + min_sdk_int = min_sdk_str.to_int(); + } + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + if (!target_sdk_str.is_empty()) { // Empty means no override, nothing to do. + if (!gradle_build_enabled) { + return TTR("\"Target SDK\" can only be overridden when \"Use Gradle Build\" is enabled."); + } + if (!target_sdk_str.is_valid_int()) { + return vformat(TTR("\"Target SDK\" should be a valid integer, but got \"%s\" which is invalid."), target_sdk_str); + } else { + target_sdk_int = target_sdk_str.to_int(); + if (target_sdk_int < min_sdk_int) { + return TTR("\"Target SDK\" version must be greater or equal to \"Min SDK\" version."); + } + } + } + } + } + return String(); +} + +void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_options) const { r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "gradle_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "gradle_build/use_gradle_build"), false, false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "gradle_build/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK, false, true)); // Using String instead of int to default to an empty string (no override) with placeholder for instructions (see GH-62465). // This implies doing validation that the string is a proper int. - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", VULKAN_MIN_SDK_VERSION)), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", VULKAN_MIN_SDK_VERSION)), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "", false, true)); Vector<PluginConfigAndroid> plugins_configs = get_plugins(); for (int i = 0; i < plugins_configs.size(); i++) { @@ -1742,7 +1835,7 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "package/app_category", PROPERTY_HINT_ENUM, "Accessibility,Audio,Game,Image,Maps,News,Productivity,Social,Video"), APP_CATEGORY_GAME)); @@ -1755,10 +1848,10 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/opengl_debug"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,OpenXR"), XR_MODE_REGULAR)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_HAND_TRACKING_NONE)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,OpenXR"), XR_MODE_REGULAR, false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_HAND_TRACKING_NONE, false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking_frequency", PROPERTY_HINT_ENUM, "Low,High"), XR_HAND_TRACKING_FREQUENCY_LOW)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/passthrough", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_PASSTHROUGH_NONE)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/passthrough", PROPERTY_HINT_ENUM, "None,Optional,Required"), XR_PASSTHROUGH_NONE, false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true)); @@ -1770,9 +1863,9 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false, false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/SALT"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/public_key", PROPERTY_HINT_MULTILINE_TEXT), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/public_key", PROPERTY_HINT_MULTILINE_TEXT), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "permissions/custom_permissions"), PackedStringArray())); @@ -2272,111 +2365,39 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { String err; bool valid = true; - const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); - - // Validate the project configuration. - bool apk_expansion = p_preset->get("apk_expansion/enable"); - - if (apk_expansion) { - String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); - if (apk_expansion_pkey.is_empty()) { - valid = false; - - err += TTR("Invalid public key for APK expansion.") + "\n"; + List<ExportOption> options; + get_export_options(&options); + for (const EditorExportPlatform::ExportOption &E : options) { + if (get_export_option_visibility(p_preset.ptr(), E.option.name)) { + String warn = get_export_option_warning(p_preset.ptr(), E.option.name); + if (!warn.is_empty()) { + err += warn + "\n"; + if (E.required) { + valid = false; + } + } } } - String pn = p_preset->get("package/unique_name"); - String pn_err; - - if (!is_package_name_valid(pn, &pn_err)) { - valid = false; - err += TTR("Invalid package name:") + " " + pn_err + "\n"; - } - String etc_error = test_etc2(); if (!etc_error.is_empty()) { valid = false; err += etc_error; } - // Ensure that `Use Gradle Build` is enabled if a plugin is selected. - String enabled_plugins_names = PluginConfigAndroid::get_plugins_names(get_enabled_plugins(p_preset)); - if (!enabled_plugins_names.is_empty() && !gradle_build_enabled) { - valid = false; - err += TTR("\"Use Gradle Build\" must be enabled to use the plugins."); - err += "\n"; - } - - // Validate the Xr features are properly populated - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - int hand_tracking = p_preset->get("xr_features/hand_tracking"); - int passthrough_mode = p_preset->get("xr_features/passthrough"); - if (xr_mode_index == XR_MODE_OPENXR && !gradle_build_enabled) { - valid = false; - err += TTR("OpenXR requires \"Use Gradle Build\" to be enabled"); - err += "\n"; - } - - if (xr_mode_index != XR_MODE_OPENXR) { - if (hand_tracking > XR_HAND_TRACKING_NONE) { - valid = false; - err += TTR("\"Hand Tracking\" is only valid when \"XR Mode\" is \"OpenXR\"."); - err += "\n"; - } - - if (passthrough_mode > XR_PASSTHROUGH_NONE) { - valid = false; - err += TTR("\"Passthrough\" is only valid when \"XR Mode\" is \"OpenXR\"."); - err += "\n"; - } - } - - if (int(p_preset->get("gradle_build/export_format")) == EXPORT_FORMAT_AAB && - !gradle_build_enabled) { - valid = false; - err += TTR("\"Export AAB\" is only valid when \"Use Gradle Build\" is enabled."); - err += "\n"; - } - - // Check the min sdk version. String min_sdk_str = p_preset->get("gradle_build/min_sdk"); int min_sdk_int = VULKAN_MIN_SDK_VERSION; if (!min_sdk_str.is_empty()) { // Empty means no override, nothing to do. - if (!gradle_build_enabled) { - valid = false; - err += TTR("\"Min SDK\" can only be overridden when \"Use Gradle Build\" is enabled."); - err += "\n"; - } - if (!min_sdk_str.is_valid_int()) { - valid = false; - err += vformat(TTR("\"Min SDK\" should be a valid integer, but got \"%s\" which is invalid."), min_sdk_str); - err += "\n"; - } else { + if (min_sdk_str.is_valid_int()) { min_sdk_int = min_sdk_str.to_int(); - if (min_sdk_int < OPENGL_MIN_SDK_VERSION) { - valid = false; - err += vformat(TTR("\"Min SDK\" cannot be lower than %d, which is the version needed by the Godot library."), OPENGL_MIN_SDK_VERSION); - err += "\n"; - } } } - // Check the target sdk version. String target_sdk_str = p_preset->get("gradle_build/target_sdk"); int target_sdk_int = DEFAULT_TARGET_SDK_VERSION; if (!target_sdk_str.is_empty()) { // Empty means no override, nothing to do. - if (!gradle_build_enabled) { - valid = false; - err += TTR("\"Target SDK\" can only be overridden when \"Use Gradle Build\" is enabled."); - err += "\n"; - } - if (!target_sdk_str.is_valid_int()) { - valid = false; - err += vformat(TTR("\"Target SDK\" should be a valid integer, but got \"%s\" which is invalid."), target_sdk_str); - err += "\n"; - } else { + if (target_sdk_str.is_valid_int()) { target_sdk_int = target_sdk_str.to_int(); if (target_sdk_int > DEFAULT_TARGET_SDK_VERSION) { // Warning only, so don't override `valid`. @@ -2386,18 +2407,13 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit } } - if (target_sdk_int < min_sdk_int) { - valid = false; - err += TTR("\"Target SDK\" version must be greater or equal to \"Min SDK\" version."); - err += "\n"; - } - String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile"); if (current_renderer == "forward_plus") { // Warning only, so don't override `valid`. err += vformat(TTR("The \"%s\" renderer is designed for Desktop devices, and is not suitable for Android devices."), current_renderer); err += "\n"; } + if (_uses_vulkan() && min_sdk_int < VULKAN_MIN_SDK_VERSION) { // Warning only, so don't override `valid`. err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), VULKAN_MIN_SDK_VERSION, current_renderer); @@ -3266,23 +3282,25 @@ void EditorExportPlatformAndroid::resolve_platform_feature_priorities(const Ref< } EditorExportPlatformAndroid::EditorExportPlatformAndroid() { + if (EditorNode::get_singleton()) { #ifdef MODULE_SVG_ENABLED - Ref<Image> img = memnew(Image); - const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + Ref<Image> img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _android_logo_svg, EDSCALE, upsample, false); - logo = ImageTexture::create_from_image(img); + ImageLoaderSVG img_loader; + img_loader.create_image_from_string(img, _android_logo_svg, EDSCALE, upsample, false); + logo = ImageTexture::create_from_image(img); - img_loader.create_image_from_string(img, _android_run_icon_svg, EDSCALE, upsample, false); - run_icon = ImageTexture::create_from_image(img); + img_loader.create_image_from_string(img, _android_run_icon_svg, EDSCALE, upsample, false); + run_icon = ImageTexture::create_from_image(img); #endif - devices_changed.set(); - plugins_changed.set(); + devices_changed.set(); + plugins_changed.set(); #ifndef ANDROID_ENABLED - check_for_changes_thread.start(_check_for_changes_poll_thread, this); + check_for_changes_thread.start(_check_for_changes_poll_thread, this); #endif + } } EditorExportPlatformAndroid::~EditorExportPlatformAndroid() { diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 337a0228d0..f9dad5ce5e 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -72,10 +72,10 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { EditorProgress *ep = nullptr; }; - Vector<PluginConfigAndroid> plugins; + mutable Vector<PluginConfigAndroid> plugins; String last_plugin_names; uint64_t last_gradle_build_time = 0; - SafeFlag plugins_changed; + mutable SafeFlag plugins_changed; Mutex plugins_lock; Vector<Device> devices; SafeFlag devices_changed; @@ -180,7 +180,9 @@ public: public: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; - virtual void get_export_options(List<ExportOption> *r_options) override; + virtual void get_export_options(List<ExportOption> *r_options) const override; + + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; virtual String get_name() const override; diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index a6fa2271ee..4bac6c814a 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,5 +1,5 @@ ext.versions = [ - androidGradlePlugin: '7.3.0', + androidGradlePlugin: '7.2.1', compileSdk : 33, // Also update 'platform/android/export/export_plugin.cpp#OPENGL_MIN_SDK_VERSION' minSdk : 21, diff --git a/platform/ios/detect.py b/platform/ios/detect.py index 71612cf1fa..bab055dbd5 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -39,6 +39,16 @@ def get_opts(): ] +def get_doc_classes(): + return [ + "EditorExportPlatformIOS", + ] + + +def get_doc_path(): + return "doc_classes" + + def get_flags(): return [ ("arch", "arm64"), # Default for convenience. diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml new file mode 100644 index 0000000000..249ee4323c --- /dev/null +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -0,0 +1,181 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformIOS" inherits="EditorExportPlatform" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Exporter for iOS. + </brief_description> + <description> + </description> + <tutorials> + <link title="Exporting for iOS">$DOCS_URL/tutorials/export/exporting_for_ios.html</link> + </tutorials> + <members> + <member name="application/app_store_team_id" type="String" setter="" getter=""> + Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organisational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url]. + </member> + <member name="application/bundle_identifier" type="String" setter="" getter=""> + Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]). + </member> + <member name="application/code_sign_identity_debug" type="String" setter="" getter=""> + The "Full Name", "Common Name" or SHA-1 hash of the signing identity used for debug export. + </member> + <member name="application/code_sign_identity_release" type="String" setter="" getter=""> + The "Full Name", "Common Name" or SHA-1 hash of the signing identity used for release export. + </member> + <member name="application/export_method_debug" type="int" setter="" getter=""> + Application distribution target (debug export). + </member> + <member name="application/export_method_release" type="int" setter="" getter=""> + Application distribution target (release export). + </member> + <member name="application/icon_interpolation" type="int" setter="" getter=""> + Interpolation method used to resize application icon. + </member> + <member name="application/launch_screens_interpolation" type="int" setter="" getter=""> + Interpolation method used to resize launch screen images. + </member> + <member name="application/provisioning_profile_uuid_debug" type="String" setter="" getter=""> + UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. + </member> + <member name="application/provisioning_profile_uuid_release" type="String" setter="" getter=""> + UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. + </member> + <member name="application/short_version" type="String" setter="" getter=""> + Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + </member> + <member name="application/signature" type="String" setter="" getter=""> + A four-character creator code that is specific to the bundle. Optional. + </member> + <member name="application/targeted_device_family" type="int" setter="" getter=""> + Supported device family. + </member> + <member name="application/version" type="String" setter="" getter=""> + Machine-readable application version, in the [code]major.minor.patch[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + </member> + <member name="architectures/arm64" type="bool" setter="" getter=""> + If [code]true[/code], [code]arm64[/code] binaries are included into exported project. + </member> + <member name="capabilities/access_wifi" type="bool" setter="" getter=""> + If [code]true[/code], networking features related to Wi-Fi access are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url]. + </member> + <member name="capabilities/push_notifications" type="bool" setter="" getter=""> + If [code]true[/code], push notifications are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url]. + </member> + <member name="custom_template/debug" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="custom_template/release" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="icons/app_store_1024x1024" type="String" setter="" getter=""> + App Store application icon file. If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ipad_152x152" type="String" setter="" getter=""> + Home screen application icon file on iPad (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ipad_167x167" type="String" setter="" getter=""> + Home screen application icon file on iPad (3x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/ipad_76x76" type="String" setter="" getter=""> + Home screen application icon file on iPad (1x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/iphone_120x120" type="String" setter="" getter=""> + Home screen application icon file on iPhone (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/iphone_180x180" type="String" setter="" getter=""> + Home screen application icon file on iPhone (3x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_40x40" type="String" setter="" getter=""> + Notification icon file on iPad and iPhone (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/notification_60x60" type="String" setter="" getter=""> + Notification icon file on iPhone (3x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/settings_58x58" type="String" setter="" getter=""> + Application settings icon file on iPad and iPhone (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/settings_87x87" type="String" setter="" getter=""> + Application settings icon file on iPhone (3x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/spotlight_40x40" type="String" setter="" getter=""> + Spotlight icon file on iPad (1x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="icons/spotlight_80x80" type="String" setter="" getter=""> + Spotlight icon file on iPad and iPhone (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. + </member> + <member name="landscape_launch_screens/ipad_1024x768" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="landscape_launch_screens/ipad_2048x1536" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="landscape_launch_screens/iphone_2208x1242" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="landscape_launch_screens/iphone_2436x1125" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="portrait_launch_screens/ipad_1536x2048" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="portrait_launch_screens/ipad_768x1024" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="portrait_launch_screens/iphone_1125x2436" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="portrait_launch_screens/iphone_1242x2208" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="portrait_launch_screens/iphone_640x1136" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="portrait_launch_screens/iphone_640x960" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="portrait_launch_screens/iphone_750x1334" type="String" setter="" getter=""> + Application launch screen image file, if left empty project splash screen is used instead. + </member> + <member name="privacy/camera_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the device's camera (in English). + </member> + <member name="privacy/camera_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the device's camera (localized). + </member> + <member name="privacy/microphone_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the device's microphone (in English). + </member> + <member name="privacy/microphone_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the device's microphone (localized). + </member> + <member name="privacy/photolibrary_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's photo library (in English). + </member> + <member name="privacy/photolibrary_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's photo library (localized). + </member> + <member name="storyboard/custom_bg_color" type="Color" setter="" getter=""> + A custom background color of the storyboard launch screen. + </member> + <member name="storyboard/custom_image@2x" type="String" setter="" getter=""> + Application launch screen image file (2x DPI), if left empty project splash screen is used instead. + </member> + <member name="storyboard/custom_image@3x" type="String" setter="" getter=""> + Application launch screen image file (3x DPI), if left empty project splash screen is used instead. + </member> + <member name="storyboard/image_scale_mode" type="int" setter="" getter=""> + Launch screen image scaling mode. + </member> + <member name="storyboard/use_custom_bg_color" type="bool" setter="" getter=""> + If [code]true[/code], [member storyboard/custom_bg_color] is used as a launch screen background color, otherwise [code]application/boot_splash/bg_color[/code] project setting is used. + </member> + <member name="storyboard/use_launch_screen_storyboard" type="bool" setter="" getter=""> + If [code]true[/code], storyboard launch screen is used instead of launch screen images. + </member> + <member name="user_data/accessible_from_files_app" type="bool" setter="" getter=""> + If [code]true[/code], the app "Documents" folder can be accessed via "Files" app. See [url=https://developer.apple.com/documentation/bundleresources/information_property_list/lssupportsopeningdocumentsinplace]LSSupportsOpeningDocumentsInPlace[/url]. + </member> + <member name="user_data/accessible_from_itunes_sharing" type="bool" setter="" getter=""> + If [code]true[/code], the app "Documents" folder can be accessed via iTunes file sharing. See [url=https://developer.apple.com/documentation/bundleresources/information_property_list/uifilesharingenabled]UIFileSharingEnabled[/url]. + </member> + </members> +</class> diff --git a/platform/ios/export/export.cpp b/platform/ios/export/export.cpp index e35383206f..f4b90d8883 100644 --- a/platform/ios/export/export.cpp +++ b/platform/ios/export/export.cpp @@ -33,6 +33,10 @@ #include "editor/export/editor_export.h" #include "export_plugin.h" +void register_ios_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformIOS); +} + void register_ios_exporter() { Ref<EditorExportPlatformIOS> platform; platform.instantiate(); diff --git a/platform/ios/export/export.h b/platform/ios/export/export.h index 03e92bfccd..355811054a 100644 --- a/platform/ios/export/export.h +++ b/platform/ios/export/export.h @@ -31,6 +31,7 @@ #ifndef IOS_EXPORT_H #define IOS_EXPORT_H +void register_ios_exporter_types(); void register_ios_exporter(); #endif // IOS_EXPORT_H diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index c6f7ec09b1..b6d70048e3 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -120,7 +120,35 @@ static const LoadingScreenInfo loading_screen_infos[] = { { PNAME("portrait_launch_screens/iphone_1242x2208"), "Default-Portrait-736h@3x.png", 1242, 2208, true } }; -void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) { +String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { + if (p_preset) { + if (p_name == "application/app_store_team_id") { + String team_id = p_preset->get("application/app_store_team_id"); + if (team_id.is_empty()) { + return TTR("App Store Team ID not specified.") + "\n"; + } + } else if (p_name == "application/bundle_identifier") { + String identifier = p_preset->get("application/bundle_identifier"); + String pn_err; + if (!is_package_name_valid(identifier, &pn_err)) { + return TTR("Invalid Identifier:") + " " + pn_err; + } + } + } + return String(); +} + +bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + if (p_preset) { + bool sb = p_preset->get("storyboard/use_launch_screen_storyboard"); + if (!sb && p_option != "storyboard/use_launch_screen_storyboard" && p_option.begins_with("storyboard/")) { + return false; + } + } + return true; +} + +void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) const { 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"), "")); @@ -129,7 +157,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("architectures"), architectures[i].name)), architectures[i].is_default)); } - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); @@ -140,7 +168,7 @@ 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/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "", false, true)); 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")); @@ -197,7 +225,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, icon_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); } } - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); @@ -1907,17 +1935,18 @@ bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorEx // Validate the project configuration. - String team_id = p_preset->get("application/app_store_team_id"); - if (team_id.length() == 0) { - err += TTR("App Store Team ID not specified - cannot configure the project.") + "\n"; - valid = false; - } - - String identifier = p_preset->get("application/bundle_identifier"); - String pn_err; - if (!is_package_name_valid(identifier, &pn_err)) { - err += TTR("Invalid Identifier:") + " " + pn_err + "\n"; - valid = false; + List<ExportOption> options; + get_export_options(&options); + for (const EditorExportPlatform::ExportOption &E : options) { + if (get_export_option_visibility(p_preset.ptr(), E.option.name)) { + String warn = get_export_option_warning(p_preset.ptr(), E.option.name); + if (!warn.is_empty()) { + err += warn + "\n"; + if (E.required) { + valid = false; + } + } + } } const String etc_error = test_etc2(); @@ -1934,19 +1963,21 @@ bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorEx } EditorExportPlatformIOS::EditorExportPlatformIOS() { + if (EditorNode::get_singleton()) { #ifdef MODULE_SVG_ENABLED - Ref<Image> img = memnew(Image); - const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + Ref<Image> img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _ios_logo_svg, EDSCALE, upsample, false); - logo = ImageTexture::create_from_image(img); + ImageLoaderSVG img_loader; + img_loader.create_image_from_string(img, _ios_logo_svg, EDSCALE, upsample, false); + logo = ImageTexture::create_from_image(img); #endif - plugins_changed.set(); + plugins_changed.set(); #ifndef ANDROID_ENABLED - check_for_changes_thread.start(_check_for_changes_poll_thread, this); + check_for_changes_thread.start(_check_for_changes_poll_thread, this); #endif + } } EditorExportPlatformIOS::~EditorExportPlatformIOS() { diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index 628dae2e6f..0fde3b7c0b 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -55,13 +55,13 @@ class EditorExportPlatformIOS : public EditorExportPlatform { Ref<ImageTexture> logo; // Plugins - SafeFlag plugins_changed; + mutable SafeFlag plugins_changed; #ifndef ANDROID_ENABLED Thread check_for_changes_thread; SafeFlag quit_request; #endif Mutex plugins_lock; - Vector<PluginConfigIOS> plugins; + mutable Vector<PluginConfigIOS> plugins; typedef Error (*FileHandler)(String p_file, void *p_userdata); static Error _walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata); @@ -178,7 +178,9 @@ class EditorExportPlatformIOS : public EditorExportPlatform { protected: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; - virtual void get_export_options(List<ExportOption> *r_options) override; + virtual void get_export_options(List<ExportOption> *r_options) const override; + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; public: virtual String get_name() const override { return "iOS"; } diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 54351757cd..dadc03685b 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -56,6 +56,16 @@ def get_opts(): ] +def get_doc_classes(): + return [ + "EditorExportPlatformLinuxBSD", + ] + + +def get_doc_path(): + return "doc_classes" + + def get_flags(): return [ ("arch", detect_arch()), @@ -453,6 +463,9 @@ def configure(env: "Environment"): else: env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.legacy.ld"]) + if platform.system() == "FreeBSD": + env.Append(LINKFLAGS=["-lkvm"]) + ## Cross-compilation # TODO: Support cross-compilation on architectures other than x86. host_is_64_bit = sys.maxsize > 2**32 diff --git a/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml b/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml new file mode 100644 index 0000000000..4ab2464929 --- /dev/null +++ b/platform/linuxbsd/doc_classes/EditorExportPlatformLinuxBSD.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformLinuxBSD" inherits="EditorExportPlatformPC" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Exporter for Linux/BSD. + </brief_description> + <description> + </description> + <tutorials> + <link title="Exporting for Linux">$DOCS_URL/tutorials/export/exporting_for_linux.html</link> + </tutorials> + <members> + <member name="binary_format/architecture" type="String" setter="" getter=""> + Application executable architecture. + Supported architectures: [code]x86_32[/code], [code]x86_64[/code], [code]arm64[/code], [code]arm32[/code], [code]rv64[/code], [code]ppc64[/code], and [code]ppc32[/code]. + Official export templates include [code]x86_32[/code] and [code]x86_64[/code] binaries only. + </member> + <member name="binary_format/embed_pck" type="bool" setter="" getter=""> + If [code]true[/code], project resources are embedded into the executable. + </member> + <member name="custom_template/debug" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="custom_template/release" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="debug/export_console_script" type="int" setter="" getter=""> + If [code]true[/code], a console wrapper script is exported alongside the main executable, which allows running the project with enabled console output. + </member> + <member name="ssh_remote_deploy/cleanup_script" type="String" setter="" getter=""> + Script code to execute on the remote host when app is finished. + The following variables can be used in the script: + - [code]{temp_dir}[/code] - Path of temporary folder on the remote, used to upload app and scripts to. + - [code]{archive_name}[/code] - Name of the ZIP containing uploaded application. + - [code]{exe_name}[/code] - Name of application executable. + - [code]{cmd_args}[/code] - Array of the command line argument for the application. + </member> + <member name="ssh_remote_deploy/enabled" type="bool" setter="" getter=""> + Enables remote deploy using SSH/SCP. + </member> + <member name="ssh_remote_deploy/extra_args_scp" type="String" setter="" getter=""> + Array of the additional command line arguments passed to the SCP. + </member> + <member name="ssh_remote_deploy/extra_args_ssh" type="String" setter="" getter=""> + Array of the additional command line arguments passed to the SSH. + </member> + <member name="ssh_remote_deploy/host" type="String" setter="" getter=""> + Remote host SSH user name and address, in [code]user@address[/code] format. + </member> + <member name="ssh_remote_deploy/port" type="String" setter="" getter=""> + Remote host SSH port number. + </member> + <member name="ssh_remote_deploy/run_script" type="String" setter="" getter=""> + Script code to execute on the remote host when running the app. + The following variables can be used in the script: + - [code]{temp_dir}[/code] - Path of temporary folder on the remote, used to upload app and scripts to. + - [code]{archive_name}[/code] - Name of the ZIP containing uploaded application. + - [code]{exe_name}[/code] - Name of application executable. + - [code]{cmd_args}[/code] - Array of the command line argument for the application. + </member> + <member name="texture_format/bptc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the BPTC format. + </member> + <member name="texture_format/etc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the ETC format. + </member> + <member name="texture_format/etc2" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the ETC2 format. + </member> + <member name="texture_format/s3tc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the S3TC format. + </member> + </members> +</class> diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp index 2c5a945b6c..09f354246d 100644 --- a/platform/linuxbsd/export/export.cpp +++ b/platform/linuxbsd/export/export.cpp @@ -33,6 +33,10 @@ #include "editor/export/editor_export.h" #include "export_plugin.h" +void register_linuxbsd_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformLinuxBSD); +} + void register_linuxbsd_exporter() { Ref<EditorExportPlatformLinuxBSD> platform; platform.instantiate(); diff --git a/platform/linuxbsd/export/export.h b/platform/linuxbsd/export/export.h index a2d70a73e3..f493047815 100644 --- a/platform/linuxbsd/export/export.h +++ b/platform/linuxbsd/export/export.h @@ -31,6 +31,7 @@ #ifndef LINUXBSD_EXPORT_H #define LINUXBSD_EXPORT_H +void register_linuxbsd_exporter_types(); void register_linuxbsd_exporter(); #endif // LINUXBSD_EXPORT_H diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index 2528bb2b99..317c6575b3 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -142,7 +142,18 @@ List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<Edito return list; } -void EditorExportPlatformLinuxBSD::get_export_options(List<ExportOption> *r_options) { +bool EditorExportPlatformLinuxBSD::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + if (p_preset) { + // Hide SSH options. + bool ssh = p_preset->get("ssh_remote_deploy/enabled"); + if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/")) { + return false; + } + } + return true; +} + +void EditorExportPlatformLinuxBSD::get_export_options(List<ExportOption> *r_options) const { EditorExportPlatformPC::get_export_options(r_options); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64,arm32,rv64,ppc64,ppc32"), "x86_64")); @@ -156,7 +167,7 @@ void EditorExportPlatformLinuxBSD::get_export_options(List<ExportOption> *r_opti "kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")\n" "rm -rf \"{temp_dir}\""; - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host"), "user@host_ip")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port"), "22")); @@ -503,22 +514,24 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, } EditorExportPlatformLinuxBSD::EditorExportPlatformLinuxBSD() { + if (EditorNode::get_singleton()) { #ifdef MODULE_SVG_ENABLED - Ref<Image> img = memnew(Image); - const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + Ref<Image> img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _linuxbsd_logo_svg, EDSCALE, upsample, false); - set_logo(ImageTexture::create_from_image(img)); + ImageLoaderSVG img_loader; + img_loader.create_image_from_string(img, _linuxbsd_logo_svg, EDSCALE, upsample, false); + set_logo(ImageTexture::create_from_image(img)); - img_loader.create_image_from_string(img, _linuxbsd_run_icon_svg, EDSCALE, upsample, false); - run_icon = ImageTexture::create_from_image(img); + img_loader.create_image_from_string(img, _linuxbsd_run_icon_svg, EDSCALE, upsample, false); + run_icon = ImageTexture::create_from_image(img); #endif - Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); - if (theme.is_valid()) { - stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); - } else { - stop_icon.instantiate(); + Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); + if (theme.is_valid()) { + stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); + } else { + stop_icon.instantiate(); + } } } diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h index 4f860c3fd0..cef714e86e 100644 --- a/platform/linuxbsd/export/export_plugin.h +++ b/platform/linuxbsd/export/export_plugin.h @@ -37,6 +37,8 @@ #include "scene/resources/texture.h" class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC { + GDCLASS(EditorExportPlatformLinuxBSD, EditorExportPlatformPC); + HashMap<String, String> extensions; struct SSHCleanupCommand { @@ -69,8 +71,9 @@ class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC { Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); public: - virtual void get_export_options(List<ExportOption> *r_options) override; + virtual void get_export_options(List<ExportOption> *r_options) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) 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) override; diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index ec1fcf6698..b45b7e676d 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -138,6 +138,17 @@ FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { #else unsupported = false; #endif + bool ver_ok = false; + int version_major = 0; + int version_minor = 0; + int version_rev = 0; + dbus_get_version(&version_major, &version_minor, &version_rev); + ver_ok = (version_major == 1 && version_minor >= 10) || (version_major > 1); // 1.10.0 + print_verbose(vformat("PortalDesktop: DBus %d.%d.%d detected.", version_major, version_minor, version_rev)); + if (!ver_ok) { + print_verbose("PortalDesktop: Unsupported DBus library version!"); + unsupported = true; + } } #endif // DBUS_ENABLED diff --git a/platform/linuxbsd/freedesktop_screensaver.cpp b/platform/linuxbsd/freedesktop_screensaver.cpp index d07e781a5f..78f2337599 100644 --- a/platform/linuxbsd/freedesktop_screensaver.cpp +++ b/platform/linuxbsd/freedesktop_screensaver.cpp @@ -141,6 +141,17 @@ FreeDesktopScreenSaver::FreeDesktopScreenSaver() { #else unsupported = false; #endif + bool ver_ok = false; + int version_major = 0; + int version_minor = 0; + int version_rev = 0; + dbus_get_version(&version_major, &version_minor, &version_rev); + ver_ok = (version_major == 1 && version_minor >= 10) || (version_major > 1); // 1.10.0 + print_verbose(vformat("ScreenSaver: DBus %d.%d.%d detected.", version_major, version_minor, version_rev)); + if (!ver_ok) { + print_verbose("ScreenSaver:: Unsupported DBus library version!"); + unsupported = true; + } } #endif // DBUS_ENABLED diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 0256af0a59..5c623b8ba2 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -82,7 +82,13 @@ JoypadLinux::JoypadLinux(Input *in) { #endif use_udev = initialize_libudev(dylibloader_verbose) == 0; if (use_udev) { - print_verbose("JoypadLinux: udev enabled and loaded successfully."); + if (!udev_new || !udev_unref || !udev_enumerate_new || !udev_enumerate_add_match_subsystem || !udev_enumerate_scan_devices || !udev_enumerate_get_list_entry || !udev_list_entry_get_next || !udev_list_entry_get_name || !udev_device_new_from_syspath || !udev_device_get_devnode || !udev_device_get_action || !udev_device_unref || !udev_enumerate_unref || !udev_monitor_new_from_netlink || !udev_monitor_filter_add_match_subsystem_devtype || !udev_monitor_enable_receiving || !udev_monitor_get_fd || !udev_monitor_receive_device || !udev_monitor_unref) { + // There's no API to check version, check if functions are available instead. + use_udev = false; + print_verbose("JoypadLinux: Unsupported udev library version!"); + } else { + print_verbose("JoypadLinux: udev enabled and loaded successfully."); + } } else { print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads."); } diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 88c3d2cc14..11c81be4a9 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -1103,6 +1103,16 @@ OS_LinuxBSD::OS_LinuxBSD() { font_config_initialized = true; #endif if (font_config_initialized) { + bool ver_ok = false; + int version = FcGetVersion(); + ver_ok = ((version / 100 / 100) == 2 && (version / 100 % 100) >= 11) || ((version / 100 / 100) > 2); // 2.11.0 + print_verbose(vformat("FontConfig %d.%d.%d detected.", version / 100 / 100, version / 100 % 100, version % 100)); + if (!ver_ok) { + font_config_initialized = false; + } + } + + if (font_config_initialized) { config = FcInitLoadConfigAndFonts(); if (!config) { font_config_initialized = false; diff --git a/platform/linuxbsd/tts_linux.cpp b/platform/linuxbsd/tts_linux.cpp index ce0199e87f..a0cb4f5c6c 100644 --- a/platform/linuxbsd/tts_linux.cpp +++ b/platform/linuxbsd/tts_linux.cpp @@ -48,6 +48,11 @@ void TTS_Linux::speech_init_thread_func(void *p_userdata) { if (initialize_speechd(dylibloader_verbose) != 0) { print_verbose("Text-to-Speech: Cannot load Speech Dispatcher library!"); } else { + if (!spd_open || !spd_set_notification_on || !spd_list_synthesis_voices || !free_spd_voices || !spd_set_synthesis_voice || !spd_set_volume || !spd_set_voice_pitch || !spd_set_voice_rate || !spd_set_data_mode || !spd_say || !spd_pause || !spd_resume || !spd_cancel) { + // There's no API to check version, check if functions are available instead. + print_verbose("Text-to-Speech: Unsupported Speech Dispatcher library version!"); + return; + } #else { #endif diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 52ee9e8e6f..ac4fbe2068 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -753,23 +753,56 @@ int DisplayServerX11::get_screen_count() const { } int DisplayServerX11::get_primary_screen() const { - return XDefaultScreen(x11_display); + int event_base, error_base; + if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + return 0; + } else { + return XDefaultScreen(x11_display); + } } -Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { - Rect2i rect(0, 0, 0, 0); +int DisplayServerX11::get_keyboard_focus_screen() const { + int count = get_screen_count(); + if (count < 2) { + // Early exit with single monitor. + return 0; + } - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; + Window focus = 0; + int revert_to = 0; + + XGetInputFocus(x11_display, &focus, &revert_to); + if (focus) { + Window focus_child = 0; + int x = 0, y = 0; + XTranslateCoordinates(x11_display, focus, DefaultRootWindow(x11_display), 0, 0, &x, &y, &focus_child); + + XWindowAttributes xwa; + XGetWindowAttributes(x11_display, focus, &xwa); + Rect2i window_rect = Rect2i(x, y, xwa.width, xwa.height); + + // Find which monitor has the largest overlap with the given window. + int screen_index = 0; + int max_area = 0; + for (int i = 0; i < count; i++) { + Rect2i screen_rect = _screen_get_rect(i); + Rect2i intersection = screen_rect.intersection(window_rect); + int area = intersection.get_area(); + if (area > max_area) { + max_area = area; + screen_index = i; + } + } + return screen_index; } + return get_primary_screen(); +} + +Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { + Rect2i rect(0, 0, 0, 0); + + p_screen = _get_screen_index(p_screen); ERR_FAIL_COND_V(p_screen < 0, rect); // Using Xinerama Extension. @@ -834,17 +867,7 @@ int bad_window_error_handler(Display *display, XErrorEvent *error) { Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); int screen_count = get_screen_count(); // Check if screen is valid. @@ -1121,18 +1144,7 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { int DisplayServerX11::screen_get_dpi(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - - //invalid screen? + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX_V(p_screen, get_screen_count(), 0); //Get physical monitor Dimensions through XRandR and calculate dpi @@ -1196,18 +1208,7 @@ Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const { float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - - //invalid screen? + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK); //Use xrandr to get screen refresh rate. @@ -1354,7 +1355,7 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { } XDestroyWindow(x11_display, wd.x11_xim_window); #ifdef XKB_ENABLED - if (xkb_loaded) { + if (xkb_loaded_v05p) { if (wd.xkb_state) { xkb_compose_state_unref(wd.xkb_state); wd.xkb_state = nullptr; @@ -1570,18 +1571,7 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - - // Check if screen is valid + p_screen = _get_screen_index(p_screen); ERR_FAIL_INDEX(p_screen, get_screen_count()); if (window_get_current_screen(p_window) == p_screen) { @@ -2681,6 +2671,11 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu Ref<Image> image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); + if (image->is_compressed()) { + image = image->duplicate(true); + Error err = image->decompress(); + ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); + } // Create the cursor structure XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); @@ -2982,7 +2977,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, String keysym; #ifdef XKB_ENABLED - if (xkb_loaded) { + if (xkb_loaded_v08p) { KeySym keysym_unicode_nm = 0; // keysym used to find unicode XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_unicode_nm, nullptr); keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(keysym_unicode_nm))); @@ -3077,7 +3072,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, } while (status == XBufferOverflow); #endif #ifdef XKB_ENABLED - } else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded) { + } else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded_v05p) { xkb_compose_feed_result res = xkb_compose_state_feed(wd.xkb_state, keysym_unicode); if (res == XKB_COMPOSE_FEED_ACCEPTED) { if (xkb_compose_state_get_status(wd.xkb_state) == XKB_COMPOSE_COMPOSED) { @@ -5004,7 +4999,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime); #ifdef XKB_ENABLED - if (dead_tbl && xkb_loaded) { + if (dead_tbl && xkb_loaded_v05p) { wd.xkb_state = xkb_compose_state_new(dead_tbl, XKB_COMPOSE_STATE_NO_FLAGS); } #endif @@ -5288,9 +5283,16 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode ERR_FAIL_MSG("Can't load XCursor dynamically."); } #ifdef XKB_ENABLED - xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0); - if (!xkb_context_new || !xkb_compose_table_new_from_locale || !xkb_compose_table_unref || !xkb_context_unref || !xkb_compose_state_feed || !xkb_compose_state_unref || !xkb_compose_state_new || !xkb_compose_state_get_status || !xkb_compose_state_get_utf8 || !xkb_keysym_to_utf32 || !xkb_keysym_to_upper) { - xkb_loaded = false; + bool xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0); + xkb_loaded_v05p = xkb_loaded; + if (!xkb_context_new || !xkb_compose_table_new_from_locale || !xkb_compose_table_unref || !xkb_context_unref || !xkb_compose_state_feed || !xkb_compose_state_unref || !xkb_compose_state_new || !xkb_compose_state_get_status || !xkb_compose_state_get_utf8) { + xkb_loaded_v05p = false; + print_verbose("Detected XKBcommon library version older than 0.5, dead key composition and Unicode key labels disabled."); + } + xkb_loaded_v08p = xkb_loaded; + if (!xkb_keysym_to_utf32 || !xkb_keysym_to_upper) { + xkb_loaded_v08p = false; + print_verbose("Detected XKBcommon library version older than 0.8, Unicode key labels disabled."); } #endif if (initialize_xext(dylibloader_verbose) != 0) { @@ -5346,6 +5348,15 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode r_error = OK; + { + if (!XcursorImageCreate || !XcursorImageLoadCursor || !XcursorImageDestroy || !XcursorGetDefaultSize || !XcursorGetTheme || !XcursorLibraryLoadImage) { + // There's no API to check version, check if functions are available instead. + ERR_PRINT("Unsupported Xcursor library version."); + r_error = ERR_UNAVAILABLE; + return; + } + } + for (int i = 0; i < CURSOR_MAX; i++) { cursors[i] = None; img[i] = nullptr; @@ -5362,6 +5373,71 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode return; } + { + int version_major = 0; + int version_minor = 0; + int rc = XShapeQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xshape %d.%d detected.", version_major, version_minor)); + if (rc != 1 || version_major < 1) { + ERR_PRINT("Unsupported Xshape library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XineramaQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xinerama %d.%d detected.", version_major, version_minor)); + if (rc != 1 || version_major < 1) { + ERR_PRINT("Unsupported Xinerama library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XRRQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xrandr %d.%d detected.", version_major, version_minor)); + if (rc != 1 || (version_major == 1 && version_minor < 3) || (version_major < 1)) { + ERR_PRINT("Unsupported Xrandr library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 0; + int version_minor = 0; + int rc = XRenderQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xrender %d.%d detected.", version_major, version_minor)); + if (rc != 1 || (version_major == 0 && version_minor < 11)) { + ERR_PRINT("Unsupported Xrender library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + + { + int version_major = 2; // Report 2.2 as supported by engine, but should work with 2.1 or 2.0 library as well. + int version_minor = 2; + int rc = XIQueryVersion(x11_display, &version_major, &version_minor); + print_verbose(vformat("Xinput %d.%d detected.", version_major, version_minor)); + if (rc != Success || (version_major < 2)) { + ERR_PRINT("Unsupported Xinput2 library version."); + r_error = ERR_UNAVAILABLE; + XCloseDisplay(x11_display); + return; + } + } + char *modifiers = nullptr; Bool xkb_dar = False; XAutoRepeatOn(x11_display); @@ -5784,7 +5860,7 @@ DisplayServerX11::~DisplayServerX11() { } XDestroyWindow(x11_display, wd.x11_xim_window); #ifdef XKB_ENABLED - if (xkb_loaded) { + if (xkb_loaded_v05p) { if (wd.xkb_state) { xkb_compose_state_unref(wd.xkb_state); wd.xkb_state = nullptr; @@ -5796,7 +5872,7 @@ DisplayServerX11::~DisplayServerX11() { } #ifdef XKB_ENABLED - if (xkb_loaded) { + if (xkb_loaded_v05p) { if (dead_tbl) { xkb_compose_table_unref(dead_tbl); } diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index c19d2f441a..e8e0680c14 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -212,7 +212,8 @@ class DisplayServerX11 : public DisplayServer { String im_text; #ifdef XKB_ENABLED - bool xkb_loaded = false; + bool xkb_loaded_v05p = false; + bool xkb_loaded_v08p = false; xkb_context *xkb_ctx = nullptr; xkb_compose_table *dead_tbl = nullptr; #endif @@ -405,6 +406,7 @@ public: virtual int get_screen_count() const override; virtual int get_primary_screen() const override; + virtual int get_keyboard_focus_screen() const override; virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; diff --git a/platform/macos/detect.py b/platform/macos/detect.py index ae8749354e..1fefdb3c68 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -39,6 +39,16 @@ def get_opts(): ] +def get_doc_classes(): + return [ + "EditorExportPlatformMacOS", + ] + + +def get_doc_path(): + return "doc_classes" + + def get_flags(): return [ ("arch", detect_arch()), @@ -108,10 +118,10 @@ def configure(env: "Environment"): env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) elif env["arch"] == "x86_64": - print("Building for macOS 10.12+.") - env.Append(ASFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) - env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) - env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + print("Building for macOS 10.13+.") + env.Append(ASFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.13"]) + env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.13"]) + env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.13"]) env.Append(CCFLAGS=["-fobjc-arc"]) diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 638355a3a8..a71ac3a310 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -327,6 +327,7 @@ public: virtual int get_screen_count() const override; virtual int get_primary_screen() const override; + virtual int get_keyboard_focus_screen() const override; virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index ac3a861d84..5a7c309448 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2112,20 +2112,15 @@ int DisplayServerMacOS::get_primary_screen() const { return 0; } +int DisplayServerMacOS::get_keyboard_focus_screen() const { + const NSUInteger index = [[NSScreen screens] indexOfObject:[NSScreen mainScreen]]; + return (index == NSNotFound) ? 0 : index; +} + Point2i DisplayServerMacOS::screen_get_position(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); Point2i position = _get_native_screen_position(p_screen) - _get_screens_origin(); // macOS native y-coordinate relative to _get_screens_origin() is negative, // Godot expects a positive value. @@ -2136,17 +2131,7 @@ Point2i DisplayServerMacOS::screen_get_position(int p_screen) const { Size2i DisplayServerMacOS::screen_get_size(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { // Note: Use frame to get the whole screen size. @@ -2160,17 +2145,7 @@ Size2i DisplayServerMacOS::screen_get_size(int p_screen) const { int DisplayServerMacOS::screen_get_dpi(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; @@ -2191,17 +2166,7 @@ int DisplayServerMacOS::screen_get_dpi(int p_screen) const { float DisplayServerMacOS::screen_get_scale(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); if (OS::get_singleton()->is_hidpi_allowed()) { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { @@ -2224,17 +2189,7 @@ float DisplayServerMacOS::screen_get_max_scale() const { Rect2i DisplayServerMacOS::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { const float scale = screen_get_max_scale(); @@ -2282,17 +2237,7 @@ Color DisplayServerMacOS::screen_get_pixel(const Point2i &p_position) const { float DisplayServerMacOS::screen_get_refresh_rate(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; @@ -3355,6 +3300,11 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor, Ref<Image> image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); + if (image->is_compressed()) { + image = image->duplicate(true); + Error err = image->decompress(); + ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); + } NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nullptr @@ -3915,7 +3865,6 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM gl_manager = nullptr; r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Could not initialize OpenGL"); - return; } } #endif diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml new file mode 100644 index 0000000000..1a9fd431c7 --- /dev/null +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -0,0 +1,291 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformMacOS" inherits="EditorExportPlatform" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Exporter for macOS. + </brief_description> + <description> + </description> + <tutorials> + <link title="Exporting for macOS">$DOCS_URL/tutorials/export/exporting_for_macos.html</link> + <link title="Running Godot apps on macOS">$DOCS_URL/tutorials//export/running_on_macos.html</link> + </tutorials> + <members> + <member name="application/app_category" type="String" setter="" getter=""> + Application category for the App Store. + </member> + <member name="application/bundle_identifier" type="String" setter="" getter=""> + Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]). + </member> + <member name="application/copyright" type="String" setter="" getter=""> + Copyright notice for the bundle visible to the user (in English). + </member> + <member name="application/copyright_localized" type="Dictionary" setter="" getter=""> + Copyright notice for the bundle visible to the user (localized). + </member> + <member name="application/icon" type="String" setter="" getter=""> + Application icon file. If left empty, project icon is used instead. + </member> + <member name="application/icon_interpolation" type="int" setter="" getter=""> + Interpolation method used to resize application icon. + </member> + <member name="application/min_macos_version" type="String" setter="" getter=""> + Minimum version of macOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + </member> + <member name="application/short_version" type="String" setter="" getter=""> + Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + </member> + <member name="application/signature" type="String" setter="" getter=""> + A four-character creator code that is specific to the bundle. Optional. + </member> + <member name="application/version" type="String" setter="" getter=""> + Machine-readable application version, in the [code]major.minor.patch[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + </member> + <member name="binary_format/architecture" type="String" setter="" getter=""> + Application executable architecture. + Supported architectures: [code]x86_64[/code], [code]arm64[/code], and [code]universal[/code] ([code]x86_64 + arm64[/code]). + Official export templates include [code]universal[/code] binaries only. + </member> + <member name="codesign/apple_team_id" type="String" setter="" getter=""> + Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organisational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url]. + </member> + <member name="codesign/certificate_file" type="String" setter="" getter=""> + PKCS #12 certificate file used to sign [code].app[/code] bundle. + </member> + <member name="codesign/certificate_password" type="String" setter="" getter=""> + Password for the certificate file used to sign [code].app[/code] bundle. + </member> + <member name="codesign/codesign" type="int" setter="" getter=""> + Tool to use for code signing. + </member> + <member name="codesign/custom_options" type="PackedStringArray" setter="" getter=""> + Array of the additional command line arguments passed to the code signing tool. + </member> + <member name="codesign/entitlements/address_book" type="bool" setter="" getter=""> + Enable to allow access to contacts in the user's address book, if it's enabled you should also provide usage message in the [code]privacy/address_book_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_addressbook]com.apple.security.personal-information.addressbook[/url]. + </member> + <member name="codesign/entitlements/allow_dyld_environment_variables" type="bool" setter="" getter=""> + Allows app to use dynamic linker environment variables to inject code. If you are using add-ons with dynamic or self-modifying native code, enable them according to the add-on documentation. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-dyld-environment-variables]com.apple.security.cs.allow-dyld-environment-variables[/url]. + </member> + <member name="codesign/entitlements/allow_jit_code_execution" type="bool" setter="" getter=""> + Allows creating writable and executable memory for JIT code. If you are using add-ons with dynamic or self-modifying native code, enable them according to the add-on documentation. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-jit]com.apple.security.cs.allow-jit[/url]. + </member> + <member name="codesign/entitlements/allow_unsigned_executable_memory" type="bool" setter="" getter=""> + Allows creating writable and executable memory without JIT restrictions. If you are using add-ons with dynamic or self-modifying native code, enable them according to the add-on documentation. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-unsigned-executable-memory]com.apple.security.cs.allow-unsigned-executable-memory[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/device_bluetooth" type="bool" setter="" getter=""> + Enable to allow app to interact with Bluetooth devices. This entitlement is required to use wireless controllers. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_bluetooth]com.apple.security.device.bluetooth[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/device_usb" type="bool" setter="" getter=""> + Enable to allow app to interact with USB devices. This entitlement is required to use wired controllers. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_usb]com.apple.security.device.usb[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/enabled" type="bool" setter="" getter=""> + Enables App Sandbox. The App Sandbox restricts access to user data, networking, and devices. Sandboxed apps can't access most of the file system, can't use custom file dialogs and execute binaries outside the .app bundle. See [url=https://developer.apple.com/documentation/security/app_sandbox]App Sandbox[/url]. + [b]Note:[/b] To distribute an app through the App Store, you must enable the App Sandbox. + </member> + <member name="codesign/entitlements/app_sandbox/files_downloads" type="int" setter="" getter=""> + Allows read or write access to the user's "Downloads" folder. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_files_downloads_read-write]com.apple.security.files.downloads.read-write[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/files_movies" type="int" setter="" getter=""> + Allows read or write access to the user's "Movies" folder. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_assets_movies_read-write]com.apple.security.files.movies.read-write[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/files_music" type="int" setter="" getter=""> + Allows read or write access to the user's "Music" folder. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_assets_music_read-write]com.apple.security.files.music.read-write[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/files_pictures" type="int" setter="" getter=""> + Allows read or write access to the user's "Pictures" folder. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_assets_pictures_read-write]com.apple.security.files.pictures.read-write[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/helper_executables" type="Array" setter="" getter=""> + List of helper executables to embedded to the app bundle. Sandboxed app are limited to execute only these executable. See [url=https://developer.apple.com/documentation/xcode/embedding-a-helper-tool-in-a-sandboxed-app]Embedding a command-line tool in a sandboxed app[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/network_client" type="bool" setter="" getter=""> + Enable to allow app to establish outgoing network connections. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_client]com.apple.security.network.client[/url]. + </member> + <member name="codesign/entitlements/app_sandbox/network_server" type="bool" setter="" getter=""> + Enable to allow app to listen for incoming network connections. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_server]com.apple.security.network.server[/url]. + </member> + <member name="codesign/entitlements/apple_events" type="bool" setter="" getter=""> + Enable to allow app to send Apple events to other apps. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_automation_apple-events]com.apple.security.automation.apple-events[/url]. + </member> + <member name="codesign/entitlements/audio_input" type="bool" setter="" getter=""> + Enable if you need to use the microphone or other audio input sources, if it's enabled you should also provide usage message in the [code]privacy/microphone_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_audio-input]com.apple.security.device.audio-input[/url]. + </member> + <member name="codesign/entitlements/calendars" type="bool" setter="" getter=""> + Enable to allow access to the user's calendar, if it's enabled you should also provide usage message in the [code]privacy/calendar_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_calendars]com.apple.security.personal-information.calendars[/url]. + </member> + <member name="codesign/entitlements/camera" type="bool" setter="" getter=""> + Enable if you need to use the camera, if it's enabled you should also provide usage message in the [code]privacy/camera_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_camera]com.apple.security.device.camera[/url]. + </member> + <member name="codesign/entitlements/custom_file" type="String" setter="" getter=""> + Custom entitlements [code].plist[/code] file, if specified the rest of entitlements in the export config are ignored. + </member> + <member name="codesign/entitlements/debugging" type="bool" setter="" getter=""> + You can temporarily enable this entitlement to use native debugger (GDB, LLDB) with the exported app. This entitlement should be disabled for production export. See [url=https://developer.apple.com/documentation/xcode/embedding-a-helper-tool-in-a-sandboxed-app]Embedding a command-line tool in a sandboxed app[/url]. + </member> + <member name="codesign/entitlements/disable_library_validation" type="bool" setter="" getter=""> + Allows app to load arbitrary libraries and frameworks (not signed with the same Team ID as the main executable or by Apple). Enable it if you are using GDExtension add-ons or ad-hoc signing, or want to support user-provided external add-ons. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation]com.apple.security.cs.disable-library-validation[/url]. + </member> + <member name="codesign/entitlements/location" type="bool" setter="" getter=""> + Enable if you need to use location information from Location Services, if it's enabled you should also provide usage message in the [code]privacy/location_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_location]com.apple.security.personal-information.location[/url]. + </member> + <member name="codesign/entitlements/photos_library" type="bool" setter="" getter=""> + Enable to allow access to the user's Photos library, if it's enabled you should also provide usage message in the [code]privacy/photos_library_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_photos-library]com.apple.security.personal-information.photos-library[/url]. + </member> + <member name="codesign/identity" type="String" setter="" getter=""> + The "Full Name", "Common Name" or SHA-1 hash of the signing identity used to sign [code].app[/code] bundle. + </member> + <member name="codesign/installer_identity" type="String" setter="" getter=""> + The "Full Name", "Common Name" or SHA-1 hash of the signing identity used to sign [code].pkg[/code] installer package for App Store distribution, use [code]3rd Party Mac Developer Installer: Name.[/code] identity. + </member> + <member name="codesign/provisioning_profile" type="String" setter="" getter=""> + Provisioning profile file downloaded from Apple developer account dashboard. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. + </member> + <member name="custom_template/debug" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="custom_template/release" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="debug/export_console_script" type="int" setter="" getter=""> + If enabled, a script file that can be used to run the application with console output is created alongside the exported application. + </member> + <member name="display/high_res" type="bool" setter="" getter=""> + If [code]true[/code], the application is rendered at native display resolution, otherwise it is always rendered at loHPI resolution and upscaled by OS when required. + </member> + <member name="export/distribution_type" type="int" setter="" getter=""> + Application distribution target. + </member> + <member name="notarization/api_key" type="String" setter="" getter=""> + Apple App Store Connect API issuer key file. + </member> + <member name="notarization/api_key_id" type="String" setter="" getter=""> + Apple App Store Connect API issuer key ID. + </member> + <member name="notarization/api_uuid" type="String" setter="" getter=""> + Apple App Store Connect API issuer UUID. + </member> + <member name="notarization/apple_id_name" type="String" setter="" getter=""> + Apple ID account name (email address). + </member> + <member name="notarization/apple_id_password" type="String" setter="" getter=""> + Apple ID app-specific password. + </member> + <member name="notarization/notarization" type="int" setter="" getter=""> + Tool to use for notarization. + </member> + <member name="privacy/address_book_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's contacts (in English). + </member> + <member name="privacy/address_book_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's contacts (localized). + </member> + <member name="privacy/calendar_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's calendar data (in English). + </member> + <member name="privacy/calendar_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's calendar data (localized). + </member> + <member name="privacy/camera_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the device's camera (in English). + </member> + <member name="privacy/camera_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the device's camera (localized). + </member> + <member name="privacy/desktop_folder_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's "Desktop" folder (in English). + </member> + <member name="privacy/desktop_folder_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's "Desktop" folder (localized). + </member> + <member name="privacy/documents_folder_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's "Documents" folder (in English). + </member> + <member name="privacy/documents_folder_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's "Documents" folder (localized). + </member> + <member name="privacy/downloads_folder_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's "Downloads" folder (in English). + </member> + <member name="privacy/downloads_folder_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's "Downloads" folder (localized). + </member> + <member name="privacy/location_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's location information (in English). + </member> + <member name="privacy/location_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's location information (localized). + </member> + <member name="privacy/microphone_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the device's microphone (in English). + </member> + <member name="privacy/microphone_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the device's microphone (localized). + </member> + <member name="privacy/network_volumes_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's network drives (in English). + </member> + <member name="privacy/network_volumes_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's network drives (localized). + </member> + <member name="privacy/photos_library_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's photo library (in English). + </member> + <member name="privacy/photos_library_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's photo library (localized). + </member> + <member name="privacy/removable_volumes_usage_description" type="String" setter="" getter=""> + A message displayed when requesting access to the user's removable drives (in English). + </member> + <member name="privacy/removable_volumes_usage_description_localized" type="Dictionary" setter="" getter=""> + A message displayed when requesting access to the user's removable drives (localized). + </member> + <member name="ssh_remote_deploy/cleanup_script" type="String" setter="" getter=""> + Script code to execute on the remote host when app is finished. + The following variables can be used in the script: + - [code]{temp_dir}[/code] - Path of temporary folder on the remote, used to upload app and scripts to. + - [code]{archive_name}[/code] - Name of the ZIP containing uploaded application. + - [code]{exe_name}[/code] - Name of application executable. + - [code]{cmd_args}[/code] - Array of the command line argument for the application. + </member> + <member name="ssh_remote_deploy/enabled" type="bool" setter="" getter=""> + Enables remote deploy using SSH/SCP. + </member> + <member name="ssh_remote_deploy/extra_args_scp" type="String" setter="" getter=""> + Array of the additional command line arguments passed to the SCP. + </member> + <member name="ssh_remote_deploy/extra_args_ssh" type="String" setter="" getter=""> + Array of the additional command line arguments passed to the SSH. + </member> + <member name="ssh_remote_deploy/host" type="String" setter="" getter=""> + Remote host SSH user name and address, in [code]user@address[/code] format. + </member> + <member name="ssh_remote_deploy/port" type="String" setter="" getter=""> + Remote host SSH port number. + </member> + <member name="ssh_remote_deploy/run_script" type="String" setter="" getter=""> + Script code to execute on the remote host when running the app. + The following variables can be used in the script: + - [code]{temp_dir}[/code] - Path of temporary folder on the remote, used to upload app and scripts to. + - [code]{archive_name}[/code] - Name of the ZIP containing uploaded application. + - [code]{exe_name}[/code] - Name of application executable. + - [code]{cmd_args}[/code] - Array of the command line argument for the application. + </member> + <member name="xcode/platform_build" type="String" setter="" getter=""> + macOS build number used to build application executable. + </member> + <member name="xcode/sdk_build" type="String" setter="" getter=""> + macOS SDK build number used to build application executable. + </member> + <member name="xcode/sdk_name" type="String" setter="" getter=""> + macOS SDK name used to build application executable. + </member> + <member name="xcode/sdk_version" type="String" setter="" getter=""> + macOS SDK version used to build application executable in the [code]major.minor[/code] format. + </member> + <member name="xcode/xcode_build" type="String" setter="" getter=""> + Xcode build number used to build application executable. + </member> + <member name="xcode/xcode_version" type="String" setter="" getter=""> + Xcode version used to build application executable. + </member> + </members> +</class> diff --git a/platform/macos/export/export.cpp b/platform/macos/export/export.cpp index 5a2850a6b7..8930974df9 100644 --- a/platform/macos/export/export.cpp +++ b/platform/macos/export/export.cpp @@ -32,6 +32,10 @@ #include "export_plugin.h" +void register_macos_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformMacOS); +} + void register_macos_exporter() { #ifndef ANDROID_ENABLED EDITOR_DEF("export/macos/rcodesign", ""); diff --git a/platform/macos/export/export.h b/platform/macos/export/export.h index 384dab826e..a7a7dcad16 100644 --- a/platform/macos/export/export.h +++ b/platform/macos/export/export.h @@ -31,6 +31,7 @@ #ifndef MACOS_EXPORT_H #define MACOS_EXPORT_H +void register_macos_exporter_types(); void register_macos_exporter(); #endif // MACOS_EXPORT_H diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index ab76d9b273..cd9d17dd4f 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -62,13 +62,191 @@ void EditorExportPlatformMacOS::get_preset_features(const Ref<EditorExportPreset } } -bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const { +String EditorExportPlatformMacOS::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { + if (p_preset) { + int dist_type = p_preset->get("export/distribution_type"); + bool ad_hoc = false; + int codesign_tool = p_preset->get("codesign/codesign"); + int notary_tool = p_preset->get("notarization/notarization"); + switch (codesign_tool) { + case 1: { // built-in ad-hoc + ad_hoc = true; + } break; + case 2: { // "rcodesign" + ad_hoc = p_preset->get("codesign/certificate_file").operator String().is_empty() || p_preset->get("codesign/certificate_password").operator String().is_empty(); + } break; +#ifdef MACOS_ENABLED + case 3: { // "codesign" + ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-"); + } break; +#endif + default: { + }; + } + + if (p_name == "application/bundle_identifier") { + String identifier = p_preset->get("application/bundle_identifier"); + String pn_err; + if (!is_package_name_valid(identifier, &pn_err)) { + return TTR("Invalid bundle identifier:") + " " + pn_err; + } + } + + if (p_name == "codesign/certificate_file" || p_name == "codesign/certificate_password" || p_name == "codesign/identity") { + if (dist_type == 2) { + if (ad_hoc) { + return TTR("App Store distribution with ad-hoc code signing is not supported."); + } + } else if (notary_tool > 0 && ad_hoc) { + return TTR("Notarization with an ad-hoc signature is not supported."); + } + } + + if (p_name == "codesign/apple_team_id") { + String team_id = p_preset->get("codesign/apple_team_id"); + if (team_id.is_empty()) { + if (dist_type == 2) { + return TTR("Apple Team ID is required for App Store distribution."); + } else if (notary_tool > 0) { + return TTR("Apple Team ID is required for notarization."); + } + } + } + + if (p_name == "codesign/provisioning_profile" && dist_type == 2) { + String pprof = p_preset->get("codesign/provisioning_profile"); + if (pprof.is_empty()) { + return TTR("Provisioning profile is required for App Store distribution."); + } + } + + if (p_name == "codesign/installer_identity" && dist_type == 2) { + String ident = p_preset->get("codesign/installer_identity"); + if (ident.is_empty()) { + return TTR("Installer signing identity is required for App Store distribution."); + } + } + + if (p_name == "codesign/entitlements/app_sandbox/enabled" && dist_type == 2) { + bool sandbox = p_preset->get("codesign/entitlements/app_sandbox/enabled"); + if (!sandbox) { + return TTR("App sandbox is required for App Store distribution."); + } + } + + if (p_name == "codesign/codesign") { + if (dist_type == 2) { + if (codesign_tool == 0) { + return TTR("Code signing is required for App Store distribution."); + } + if (codesign_tool == 1) { + return TTR("App Store distribution with ad-hoc code signing is not supported."); + } + } else if (notary_tool > 0) { + if (codesign_tool == 0) { + return TTR("Code signing is required for notarization."); + } + if (codesign_tool == 1) { + return TTR("Notarization with an ad-hoc signature is not supported."); + } + } + } + + if (notary_tool == 2 || notary_tool == 3) { + if (p_name == "notarization/apple_id_name" || p_name == "notarization/api_uuid") { + String apple_id = p_preset->get("notarization/apple_id_name"); + String api_uuid = p_preset->get("notarization/api_uuid"); + if (apple_id.is_empty() && api_uuid.is_empty()) { + return TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified."); + } + if (!apple_id.is_empty() && !api_uuid.is_empty()) { + return TTR("Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time."); + } + } + if (p_name == "notarization/apple_id_password") { + String apple_id = p_preset->get("notarization/apple_id_name"); + String apple_pass = p_preset->get("notarization/apple_id_password"); + if (!apple_id.is_empty() && apple_pass.is_empty()) { + return TTR("Apple ID password not specified."); + } + } + if (p_name == "notarization/api_key_id") { + String api_uuid = p_preset->get("notarization/api_uuid"); + String api_key = p_preset->get("notarization/api_key_id"); + if (!api_uuid.is_empty() && api_key.is_empty()) { + return TTR("App Store Connect API key ID not specified."); + } + } + } else if (notary_tool == 1) { + if (p_name == "notarization/api_uuid") { + String api_uuid = p_preset->get("notarization/api_uuid"); + if (api_uuid.is_empty()) { + return TTR("App Store Connect issuer ID name not specified."); + } + } + if (p_name == "notarization/api_key_id") { + String api_key = p_preset->get("notarization/api_key_id"); + if (api_key.is_empty()) { + return TTR("App Store Connect API key ID not specified."); + } + } + } + + if (codesign_tool > 0) { + if (p_name == "privacy/microphone_usage_description") { + String discr = p_preset->get("privacy/microphone_usage_description"); + bool enabled = p_preset->get("codesign/entitlements/audio_input"); + if (enabled && discr.is_empty()) { + return TTR("Microphone access is enabled, but usage description is not specified."); + } + } + if (p_name == "privacy/camera_usage_description") { + String discr = p_preset->get("privacy/camera_usage_description"); + bool enabled = p_preset->get("codesign/entitlements/camera"); + if (enabled && discr.is_empty()) { + return TTR("Camera access is enabled, but usage description is not specified."); + } + } + if (p_name == "privacy/location_usage_description") { + String discr = p_preset->get("privacy/location_usage_description"); + bool enabled = p_preset->get("codesign/entitlements/location"); + if (enabled && discr.is_empty()) { + return TTR("Location information access is enabled, but usage description is not specified."); + } + } + if (p_name == "privacy/address_book_usage_description") { + String discr = p_preset->get("privacy/address_book_usage_description"); + bool enabled = p_preset->get("codesign/entitlements/address_book"); + if (enabled && discr.is_empty()) { + return TTR("Address book access is enabled, but usage description is not specified."); + } + } + if (p_name == "privacy/calendar_usage_description") { + String discr = p_preset->get("privacy/calendar_usage_description"); + bool enabled = p_preset->get("codesign/entitlements/calendars"); + if (enabled && discr.is_empty()) { + return TTR("Calendar access is enabled, but usage description is not specified."); + } + } + if (p_name == "privacy/photos_library_usage_description") { + String discr = p_preset->get("privacy/photos_library_usage_description"); + bool enabled = p_preset->get("codesign/entitlements/photos_library"); + if (enabled && discr.is_empty()) { + return TTR("Photo library access is enabled, but usage description is not specified."); + } + } + } + } + return String(); +} + +bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { // Hide irrelevant code signing options. if (p_preset) { int codesign_tool = p_preset->get("codesign/codesign"); switch (codesign_tool) { case 1: { // built-in ad-hoc - if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options") { + if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options" || p_option == "codesign/team_id") { return false; } } break; @@ -85,17 +263,48 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP } break; #endif default: { // disabled - if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options" || p_option.begins_with("codesign/entitlements")) { + if (p_option == "codesign/identity" || p_option == "codesign/certificate_file" || p_option == "codesign/certificate_password" || p_option == "codesign/custom_options" || p_option.begins_with("codesign/entitlements") || p_option == "codesign/team_id") { return false; } } break; } + // Distribution type. + int dist_type = p_preset->get("export/distribution_type"); + if (dist_type != 2 && p_option == "codesign/installer_identity") { + return false; + } + + if (dist_type == 2 && p_option.begins_with("notarization/")) { + return false; + } + + if (dist_type != 2 && p_option == "codesign/provisioning_profile") { + return false; + } + + String custom_prof = p_preset->get("codesign/entitlements/custom_file"); + if (!custom_prof.is_empty() && p_option != "codesign/entitlements/custom_file" && p_option.begins_with("codesign/entitlements/")) { + return false; + } + + // Hide sandbox entitlements. + bool sandbox = p_preset->get("codesign/entitlements/app_sandbox/enabled"); + if (!sandbox && p_option != "codesign/entitlements/app_sandbox/enabled" && p_option.begins_with("codesign/entitlements/app_sandbox/")) { + return false; + } + + // Hide SSH options. + bool ssh = p_preset->get("ssh_remote_deploy/enabled"); + if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/")) { + return false; + } + // Hide irrelevant notarization options. int notary_tool = p_preset->get("notarization/notarization"); switch (notary_tool) { case 1: { // "rcodesign" - if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id") { + if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password") { return false; } } break; @@ -106,7 +315,7 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP // All options are visible. } break; default: { // disabled - if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key" || p_option == "notarization/api_key_id") { + if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key" || p_option == "notarization/api_key_id") { return false; } } break; @@ -122,7 +331,39 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP return true; } -void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options) { +List<String> EditorExportPlatformMacOS::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + List<String> list; + + if (p_preset.is_valid()) { + int dist_type = p_preset->get("export/distribution_type"); + if (dist_type == 0) { +#ifdef MACOS_ENABLED + list.push_back("dmg"); +#endif + list.push_back("zip"); + list.push_back("app"); + } else if (dist_type == 1) { +#ifdef MACOS_ENABLED + list.push_back("dmg"); +#endif + list.push_back("zip"); + } else if (dist_type == 2) { +#ifdef MACOS_ENABLED + list.push_back("pkg"); +#endif + } + } + + return list; +} + +void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options) const { +#ifdef MACOS_ENABLED + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "export/distribution_type", PROPERTY_HINT_ENUM, "Testing,Distribution,App Store"), 1, true)); +#else + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "export/distribution_type", PROPERTY_HINT_ENUM, "Testing,Distribution"), 1, true)); +#endif + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "universal,x86_64,arm64", PROPERTY_USAGE_STORAGE), "universal")); 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"), "")); @@ -130,27 +371,39 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options 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, "*.icns,*.png,*.webp,*.svg"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_category", PROPERTY_HINT_ENUM, "Business,Developer-tools,Education,Entertainment,Finance,Games,Action-games,Adventure-games,Arcade-games,Board-games,Card-games,Casino-games,Dice-games,Educational-games,Family-games,Kids-games,Music-games,Puzzle-games,Racing-games,Role-playing-games,Simulation-games,Sports-games,Strategy-games,Trivia-games,Word-games,Graphics-design,Healthcare-fitness,Lifestyle,Medical,Music,News,Photography,Productivity,Reference,Social-networking,Sports,Travel,Utilities,Video,Weather"), "Games")); 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::STRING, "application/min_macos_version"), "10.12")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/platform_build"), "14C18")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_version"), "13.1")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_build"), "22C55")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_name"), "macosx13.1")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/xcode_version"), "1420")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/xcode_build"), "14C18")); + #ifdef MACOS_ENABLED r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign,Xcode codesign"), 3, true)); #else - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign"), 1, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign"), 1, true, true)); #endif + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/installer_identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "3rd Party Mac Developer Installer: (ID)"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "ID"), "", false, true)); // "codesign" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); // "rcodesign" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_file", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/certificate_password", PROPERTY_HINT_PASSWORD), "")); // "codesign" and "rcodesign" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/provisioning_profile", PROPERTY_HINT_GLOBAL_FILE, "*.provisionprofile"), "", false, true)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/custom_file", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "", true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_jit_code_execution"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_unsigned_executable_memory"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/allow_dyld_environment_variables"), false)); @@ -163,7 +416,7 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/photos_library"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/apple_events"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/debugging"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/enabled"), false, true, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_server"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/network_client"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/entitlements/app_sandbox/device_usb"), false)); @@ -181,35 +434,34 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,rcodesign"), 0, true)); #endif // "altool" and "notarytool" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PASSWORD, "Enable two-factor authentication and provide app-specific password"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PASSWORD, "Enable two-factor authentication and provide app-specific password"), "", false, true)); // "altool", "notarytool" and "rcodesign" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID UUID"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_GLOBAL_FILE, "*.p8"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID UUID"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_GLOBAL_FILE, "*.p8"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "", false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "", false, true)); 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::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), "", false, true)); 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::STRING, "privacy/location_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the location information"), "", false, true)); 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::STRING, "privacy/address_book_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the address book"), "", false, true)); 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::STRING, "privacy/calendar_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the calendar"), "", false, true)); 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::STRING, "privacy/photos_library_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the photo library"), "", false, true)); 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::STRING, "privacy/desktop_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Desktop folder"), "", false, true)); 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::STRING, "privacy/documents_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Documents folder"), "", false, true)); 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::STRING, "privacy/downloads_folder_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use Downloads folder"), "", false, true)); 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::STRING, "privacy/network_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use network volumes"), "", false, true)); 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::STRING, "privacy/removable_volumes_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use removable volumes"), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/removable_volumes_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); String run_script = "#!/usr/bin/env bash\n" @@ -220,7 +472,7 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options "kill $(pgrep -x -f \"{temp_dir}/{exe_name}.app/Contents/MacOS/{exe_name} {cmd_args}\")\n" "rm -rf \"{temp_dir}\""; - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host"), "user@host_ip")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port"), "22")); @@ -424,8 +676,22 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres strnew += lines[i].replace("$app_category", cat.to_lower()) + "\n"; } else if (lines[i].find("$copyright") != -1) { strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; + } else if (lines[i].find("$min_version") != -1) { + strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version")) + "\n"; } else if (lines[i].find("$highres") != -1) { strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n"; + } else if (lines[i].find("$platfbuild") != -1) { + strnew += lines[i].replace("$platfbuild", p_preset->get("xcode/platform_build")) + "\n"; + } else if (lines[i].find("$sdkver") != -1) { + strnew += lines[i].replace("$sdkver", p_preset->get("xcode/sdk_version")) + "\n"; + } else if (lines[i].find("$sdkname") != -1) { + strnew += lines[i].replace("$sdkname", p_preset->get("xcode/sdk_name")) + "\n"; + } else if (lines[i].find("$sdkbuild") != -1) { + strnew += lines[i].replace("$sdkbuild", p_preset->get("xcode/sdk_build")) + "\n"; + } else if (lines[i].find("$xcodever") != -1) { + strnew += lines[i].replace("$xcodever", p_preset->get("xcode/xcode_version")) + "\n"; + } else if (lines[i].find("$xcodebuild") != -1) { + strnew += lines[i].replace("$xcodebuild", p_preset->get("xcode/xcode_build")) + "\n"; } else if (lines[i].find("$usage_descriptions") != -1) { String descriptions; if (!((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { @@ -612,9 +878,9 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back("--no-progress"); - if (p_preset->get("notarization/apple_team_id")) { + if (p_preset->get("codesign/apple_team_id")) { args.push_back("--team-id"); - args.push_back(p_preset->get("notarization/apple_team_id")); + args.push_back(p_preset->get("codesign/apple_team_id")); } String str; @@ -693,9 +959,9 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back("--type"); args.push_back("osx"); - if (p_preset->get("notarization/apple_team_id")) { + if (p_preset->get("codesign/apple_team_id")) { args.push_back("--asc-provider"); - args.push_back(p_preset->get("notarization/apple_team_id")); + args.push_back(p_preset->get("codesign/apple_team_id")); } args.push_back("--file"); @@ -978,6 +1244,42 @@ Error EditorExportPlatformMacOS::_export_macos_plugins_for(Ref<EditorExportPlugi return error; } +Error EditorExportPlatformMacOS::_create_pkg(const Ref<EditorExportPreset> &p_preset, const String &p_pkg_path, const String &p_app_path_name) { + List<String> args; + + if (FileAccess::exists(p_pkg_path)) { + OS::get_singleton()->move_to_trash(p_pkg_path); + } + + args.push_back("productbuild"); + args.push_back("--component"); + args.push_back(p_app_path_name); + args.push_back("/Applications"); + String ident = p_preset->get("codesign/installer_identity"); + if (!ident.is_empty()) { + args.push_back("--timestamp"); + args.push_back("--sign"); + args.push_back(ident); + } + args.push_back("--quiet"); + args.push_back(p_pkg_path); + + String str; + Error err = OS::get_singleton()->execute("xcrun", args, &str, nullptr, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PKG Creation"), TTR("Could not start productbuild executable.")); + return err; + } + + print_verbose("productbuild returned: " + str); + if (str.find("productbuild: error:") != -1) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PKG Creation"), TTR("`productbuild` failed.")); + return FAILED; + } + + return OK; +} + Error EditorExportPlatformMacOS::_create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name) { List<String> args; @@ -1106,12 +1408,16 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p pkg_name = OS::get_singleton()->get_safe_dir_name(pkg_name); String export_format; - if (use_dmg() && p_path.ends_with("dmg")) { - export_format = "dmg"; - } else if (p_path.ends_with("zip")) { + if (p_path.ends_with("zip")) { export_format = "zip"; } else if (p_path.ends_with("app")) { export_format = "app"; +#ifdef MACOS_ENABLED + } else if (p_path.ends_with("dmg")) { + export_format = "dmg"; + } else if (p_path.ends_with("pkg")) { + export_format = "pkg"; +#endif } else { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid export format.")); return ERR_CANT_CREATE; @@ -1549,6 +1855,19 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p ent_f->store_line("<true/>"); } + int dist_type = p_preset->get("export/distribution_type"); + if (dist_type == 2) { + String pprof = p_preset->get("codesign/provisioning_profile"); + String teamid = p_preset->get("codesign/apple_team_id"); + String bid = p_preset->get("application/bundle_identifier"); + if (!pprof.is_empty() && !teamid.is_empty()) { + ent_f->store_line("<key>com.apple.developer.team-identifier</key>"); + ent_f->store_line("<string>" + teamid + "</string>"); + ent_f->store_line("<key>com.apple.application-identifier</key>"); + ent_f->store_line("<string>" + teamid + "." + bid + "</string>"); + } + } + if ((bool)p_preset->get("codesign/entitlements/app_sandbox/enabled")) { ent_f->store_line("<key>com.apple.security.app-sandbox</key>"); ent_f->store_line("<true/>"); @@ -1669,6 +1988,15 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p } if (err == OK && sign_enabled) { + int dist_type = p_preset->get("export/distribution_type"); + if (dist_type == 2) { + String pprof = p_preset->get("codesign/provisioning_profile").operator String(); + if (!pprof.is_empty()) { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + err = da->copy(pprof, tmp_app_path_name + "/Contents/embedded.provisionprofile"); + } + } + if (ep.step(TTR("Code signing bundle"), 2)) { return ERR_SKIP; } @@ -1690,6 +2018,14 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p } err = _code_sign(p_preset, p_path, ent_path, false); } + } else if (export_format == "pkg") { + // Create a Installer. + if (err == OK) { + if (ep.step(TTR("Making PKG installer"), 3)) { + return ERR_SKIP; + } + err = _create_pkg(p_preset, p_path, tmp_app_path_name); + } } else if (export_format == "zip") { // Create ZIP. if (err == OK) { @@ -1712,7 +2048,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p bool noto_enabled = (p_preset->get("notarization/notarization").operator int() > 0); if (err == OK && noto_enabled) { - if (export_format == "app") { + if (export_format == "app" || export_format == "pkg") { add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.")); } else { if (ep.step(TTR("Sending archive for notarization"), 4)) { @@ -1802,15 +2138,10 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor String err; bool valid = true; - String identifier = p_preset->get("application/bundle_identifier"); - String pn_err; - if (!is_package_name_valid(identifier, &pn_err)) { - err += TTR("Invalid bundle identifier:") + " " + pn_err + "\n"; - valid = false; - } - + int dist_type = p_preset->get("export/distribution_type"); bool ad_hoc = false; int codesign_tool = p_preset->get("codesign/codesign"); + int notary_tool = p_preset->get("notarization/notarization"); switch (codesign_tool) { case 1: { // built-in ad-hoc ad_hoc = true; @@ -1826,67 +2157,41 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor default: { }; } - int notary_tool = p_preset->get("notarization/notarization"); - if (notary_tool > 0) { - if (ad_hoc) { - err += TTR("Notarization: Notarization with an ad-hoc signature is not supported.") + "\n"; - valid = false; - } - if (codesign_tool == 0) { - err += TTR("Notarization: Code signing is required for notarization.") + "\n"; - valid = false; - } - if (notary_tool == 2 || notary_tool == 3) { - if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { - err += TTR("Notarization: Xcode command line tools are not installed.") + "\n"; - valid = false; + List<ExportOption> options; + get_export_options(&options); + for (const EditorExportPlatform::ExportOption &E : options) { + if (get_export_option_visibility(p_preset.ptr(), E.option.name)) { + String warn = get_export_option_warning(p_preset.ptr(), E.option.name); + if (!warn.is_empty()) { + err += warn + "\n"; + if (E.required) { + valid = false; + } } - if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") { - err += TTR("Notarization: Neither Apple ID name nor App Store Connect issuer ID name not specified.") + "\n"; - valid = false; - } else if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") { - err += TTR("Notarization: Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time.") + "\n"; - valid = false; - } else { - if (p_preset->get("notarization/apple_id_name") != "") { - if (p_preset->get("notarization/apple_id_password") == "") { - err += TTR("Notarization: Apple ID password not specified.") + "\n"; - valid = false; - } + } + } + + if (dist_type != 2) { + if (notary_tool > 0) { + if (notary_tool == 2 || notary_tool == 3) { + if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { + err += TTR("Notarization: Xcode command line tools are not installed.") + "\n"; + valid = false; } - if (p_preset->get("notarization/api_uuid") != "") { - if (p_preset->get("notarization/api_key_id") == "") { - err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n"; - valid = false; - } + } else if (notary_tool == 1) { + String rcodesign = EDITOR_GET("export/macos/rcodesign").operator String(); + if (rcodesign.is_empty()) { + err += TTR("Notarization: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign).") + "\n"; + valid = false; } } - if (notary_tool == 2 && p_preset->get("notarization/apple_team_id") == "") { - err += TTR("Notarization: Apple Team ID not specified.") + "\n"; - valid = false; - } - } else if (notary_tool == 1) { - if (p_preset->get("notarization/api_uuid") == "") { - err += TTR("Notarization: App Store Connect issuer ID name not specified.") + "\n"; - valid = false; - } - if (p_preset->get("notarization/api_key_id") == "") { - err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n"; - valid = false; - } - - String rcodesign = EDITOR_GET("export/macos/rcodesign").operator String(); - if (rcodesign.is_empty()) { - err += TTR("Notarization: rcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign).") + "\n"; - valid = false; + } else { + err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n"; + if (codesign_tool == 0) { + err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n"; } } - } else { - err += TTR("Warning: Notarization is disabled. The exported project will be blocked by Gatekeeper if it's downloaded from an unknown source.") + "\n"; - if (codesign_tool == 0) { - err += TTR("Code signing is disabled. The exported project will not run on Macs with enabled Gatekeeper and Apple Silicon powered Macs.") + "\n"; - } } if (codesign_tool > 0) { @@ -1905,30 +2210,6 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor valid = false; } } - if ((bool)p_preset->get("codesign/entitlements/audio_input") && ((String)p_preset->get("privacy/microphone_usage_description")).is_empty()) { - err += TTR("Privacy: Microphone access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/camera") && ((String)p_preset->get("privacy/camera_usage_description")).is_empty()) { - err += TTR("Privacy: Camera access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/location") && ((String)p_preset->get("privacy/location_usage_description")).is_empty()) { - err += TTR("Privacy: Location information access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/address_book") && ((String)p_preset->get("privacy/address_book_usage_description")).is_empty()) { - err += TTR("Privacy: Address book access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/calendars") && ((String)p_preset->get("privacy/calendar_usage_description")).is_empty()) { - err += TTR("Privacy: Calendar access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } - if ((bool)p_preset->get("codesign/entitlements/photos_library") && ((String)p_preset->get("privacy/photos_library_usage_description")).is_empty()) { - err += TTR("Privacy: Photo library access is enabled, but usage description is not specified.") + "\n"; - valid = false; - } } if (!err.is_empty()) { @@ -2157,22 +2438,24 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in } EditorExportPlatformMacOS::EditorExportPlatformMacOS() { + if (EditorNode::get_singleton()) { #ifdef MODULE_SVG_ENABLED - Ref<Image> img = memnew(Image); - const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + Ref<Image> img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _macos_logo_svg, EDSCALE, upsample, false); - logo = ImageTexture::create_from_image(img); + ImageLoaderSVG img_loader; + img_loader.create_image_from_string(img, _macos_logo_svg, EDSCALE, upsample, false); + logo = ImageTexture::create_from_image(img); - img_loader.create_image_from_string(img, _macos_run_icon_svg, EDSCALE, upsample, false); - run_icon = ImageTexture::create_from_image(img); + img_loader.create_image_from_string(img, _macos_run_icon_svg, EDSCALE, upsample, false); + run_icon = ImageTexture::create_from_image(img); #endif - Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); - if (theme.is_valid()) { - stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); - } else { - stop_icon.instantiate(); + Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); + if (theme.is_valid()) { + stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); + } else { + stop_icon.instantiate(); + } } } diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h index c10192949c..a8caf535c4 100644 --- a/platform/macos/export/export_plugin.h +++ b/platform/macos/export/export_plugin.h @@ -87,18 +87,10 @@ class EditorExportPlatformMacOS : public EditorExportPlatform { Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path); Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); + Error _create_pkg(const Ref<EditorExportPreset> &p_preset, const String &p_pkg_path, const String &p_app_path_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 MACOS_ENABLED - bool use_dmg() const { - return true; - } -#else - bool use_dmg() const { - return false; - } -#endif bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { String pname = p_package; @@ -126,8 +118,9 @@ class EditorExportPlatformMacOS : public EditorExportPlatform { protected: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; - virtual void get_export_options(List<ExportOption> *r_options) override; - virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const override; + virtual void get_export_options(List<ExportOption> *r_options) const override; + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; public: virtual String get_name() const override { @@ -141,15 +134,7 @@ public: } virtual bool is_executable(const String &p_path) const override; - virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { - List<String> list; - if (use_dmg()) { - list.push_back("dmg"); - } - list.push_back("zip"); - list.push_back("app"); - return list; - } + 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 bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm index 485f80a22e..9f5bb2ef5c 100644 --- a/platform/macos/godot_content_view.mm +++ b/platform/macos/godot_content_view.mm @@ -115,13 +115,7 @@ self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; self.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft; - if (@available(macOS 10.13, *)) { - [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; -#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. - } else { - [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; -#endif - } + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; marked_text = [[NSMutableAttributedString alloc] init]; return self; } @@ -321,20 +315,11 @@ Vector<String> files; NSPasteboard *pboard = [sender draggingPasteboard]; - if (@available(macOS 10.13, *)) { - NSArray *items = pboard.pasteboardItems; - for (NSPasteboardItem *item in items) { - NSString *url = [item stringForType:NSPasteboardTypeFileURL]; - NSString *file = [NSURL URLWithString:url].path; - files.push_back(String::utf8([file UTF8String])); - } -#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. - } else { - NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; - for (NSString *file in filenames) { - files.push_back(String::utf8([file UTF8String])); - } -#endif + NSArray *items = pboard.pasteboardItems; + for (NSPasteboardItem *item in items) { + NSString *url = [item stringForType:NSPasteboardTypeFileURL]; + NSString *file = [NSURL URLWithString:url].path; + files.push_back(String::utf8([file UTF8String])); } Variant v = files; diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index f2b2fa30ce..85964c53e6 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -33,6 +33,10 @@ #include "editor/editor_settings.h" #include "export_plugin.h" +void register_uwp_exporter_types() { + // GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformUWP); +} + void register_uwp_exporter() { #ifdef WINDOWS_ENABLED EDITOR_DEF("export/uwp/signtool", ""); diff --git a/platform/uwp/export/export.h b/platform/uwp/export/export.h index 09449fc53b..d2053e02b1 100644 --- a/platform/uwp/export/export.h +++ b/platform/uwp/export/export.h @@ -31,6 +31,7 @@ #ifndef UWP_EXPORT_H #define UWP_EXPORT_H +void register_uwp_exporter_types(); void register_uwp_exporter(); #endif // UWP_EXPORT_H diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp index f810cb0ca9..163236e506 100644 --- a/platform/uwp/export/export_plugin.cpp +++ b/platform/uwp/export/export_plugin.cpp @@ -62,7 +62,7 @@ void EditorExportPlatformUWP::get_preset_features(const Ref<EditorExportPreset> r_features->push_back(p_preset->get("binary_format/architecture")); } -void EditorExportPlatformUWP::get_export_options(List<ExportOption> *r_options) { +void EditorExportPlatformUWP::get_export_options(List<ExportOption> *r_options) const { 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"), "")); diff --git a/platform/uwp/export/export_plugin.h b/platform/uwp/export/export_plugin.h index d015fcabcd..37a32b1f7f 100644 --- a/platform/uwp/export/export_plugin.h +++ b/platform/uwp/export/export_plugin.h @@ -427,7 +427,7 @@ public: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; - virtual void get_export_options(List<ExportOption> *r_options) override; + virtual void get_export_options(List<ExportOption> *r_options) const override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; diff --git a/platform/web/detect.py b/platform/web/detect.py index 08c1ff7b4a..419d8918f2 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -48,6 +48,16 @@ def get_opts(): ] +def get_doc_classes(): + return [ + "EditorExportPlatformWeb", + ] + + +def get_doc_path(): + return "doc_classes" + + def get_flags(): return [ ("arch", "wasm32"), diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index da55e8560a..28f1914c37 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -426,7 +426,12 @@ void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu ERR_FAIL_COND(!image.is_valid()); - image = image->duplicate(); + image = image->duplicate(true); + + if (image->is_compressed()) { + Error err = image->decompress(); + ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); + } if (atlas_texture.is_valid()) { image->crop_from_point( diff --git a/platform/web/doc_classes/EditorExportPlatformWeb.xml b/platform/web/doc_classes/EditorExportPlatformWeb.xml new file mode 100644 index 0000000000..6e5a2ac078 --- /dev/null +++ b/platform/web/doc_classes/EditorExportPlatformWeb.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformWeb" inherits="EditorExportPlatform" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Exporter for the Web. + </brief_description> + <description> + </description> + <tutorials> + <link title="Exporting for the Web">$DOCS_URL/tutorials/export/exporting_for_web.html</link> + </tutorials> + <members> + <member name="custom_template/debug" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="custom_template/release" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="html/canvas_resize_policy" type="int" setter="" getter=""> + The canvas resize policy determines how the canvas should be resized by Godot. + </member> + <member name="html/custom_html_shell" type="String" setter="" getter=""> + </member> + <member name="html/experimental_virtual_keyboard" type="bool" setter="" getter=""> + </member> + <member name="html/export_icon" type="bool" setter="" getter=""> + </member> + <member name="html/focus_canvas_on_start" type="bool" setter="" getter=""> + </member> + <member name="html/head_include" type="String" setter="" getter=""> + </member> + <member name="progressive_web_app/background_color" type="Color" setter="" getter=""> + </member> + <member name="progressive_web_app/display" type="int" setter="" getter=""> + </member> + <member name="progressive_web_app/enabled" type="bool" setter="" getter=""> + </member> + <member name="progressive_web_app/icon_144x144" type="String" setter="" getter=""> + </member> + <member name="progressive_web_app/icon_180x180" type="String" setter="" getter=""> + </member> + <member name="progressive_web_app/icon_512x512" type="String" setter="" getter=""> + </member> + <member name="progressive_web_app/offline_page" type="String" setter="" getter=""> + </member> + <member name="progressive_web_app/orientation" type="int" setter="" getter=""> + </member> + <member name="variant/extensions_support" type="bool" setter="" getter=""> + </member> + <member name="vram_texture_compression/for_desktop" type="bool" setter="" getter=""> + </member> + <member name="vram_texture_compression/for_mobile" type="bool" setter="" getter=""> + </member> + </members> +</class> diff --git a/platform/web/export/export.cpp b/platform/web/export/export.cpp index 11e728ea16..54d9774da5 100644 --- a/platform/web/export/export.cpp +++ b/platform/web/export/export.cpp @@ -33,6 +33,10 @@ #include "editor/editor_settings.h" #include "export_plugin.h" +void register_web_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformWeb); +} + void register_web_exporter() { #ifndef ANDROID_ENABLED EDITOR_DEF("export/web/http_host", "localhost"); diff --git a/platform/web/export/export.h b/platform/web/export/export.h index 8d2bbfff26..da02bd8d93 100644 --- a/platform/web/export/export.h +++ b/platform/web/export/export.h @@ -31,6 +31,7 @@ #ifndef WEB_EXPORT_H #define WEB_EXPORT_H +void register_web_exporter_types(); void register_web_exporter(); #endif // WEB_EXPORT_H diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index d8e04904c7..7a62cd2a4a 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -320,7 +320,7 @@ void EditorExportPlatformWeb::get_preset_features(const Ref<EditorExportPreset> r_features->push_back("wasm32"); } -void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options) { +void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options) const { 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"), "")); @@ -656,31 +656,35 @@ void EditorExportPlatformWeb::_server_thread_poll(void *data) { } EditorExportPlatformWeb::EditorExportPlatformWeb() { - server.instantiate(); - server_thread.start(_server_thread_poll, this); + if (EditorNode::get_singleton()) { + server.instantiate(); + server_thread.start(_server_thread_poll, this); #ifdef MODULE_SVG_ENABLED - Ref<Image> img = memnew(Image); - const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + Ref<Image> img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _web_logo_svg, EDSCALE, upsample, false); - logo = ImageTexture::create_from_image(img); + ImageLoaderSVG img_loader; + img_loader.create_image_from_string(img, _web_logo_svg, EDSCALE, upsample, false); + logo = ImageTexture::create_from_image(img); - img_loader.create_image_from_string(img, _web_run_icon_svg, EDSCALE, upsample, false); - run_icon = ImageTexture::create_from_image(img); + img_loader.create_image_from_string(img, _web_run_icon_svg, EDSCALE, upsample, false); + run_icon = ImageTexture::create_from_image(img); #endif - Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); - if (theme.is_valid()) { - stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); - } else { - stop_icon.instantiate(); + Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); + if (theme.is_valid()) { + stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); + } else { + stop_icon.instantiate(); + } } } EditorExportPlatformWeb::~EditorExportPlatformWeb() { - server->stop(); + if (server.is_valid()) { + server->stop(); + } server_quit = true; server_thread.wait_to_finish(); } diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index e74c945837..334f12d64d 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -99,7 +99,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform { public: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; - virtual void get_export_options(List<ExportOption> *r_options) override; + virtual void get_export_options(List<ExportOption> *r_options) const override; virtual String get_name() const override; virtual String get_os_name() const override; diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 867f1d537c..e32fbd47dd 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -47,9 +47,6 @@ #include <psapi.h> -#pragma comment(lib, "psapi.lib") -#pragma comment(lib, "dbghelp.lib") - // Some versions of imagehlp.dll lack the proper packing directives themselves // so we need to do it. #pragma pack(push, before_imagehlp, 8) diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 1b55574b19..cd6d461a93 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -192,6 +192,16 @@ def get_opts(): ] +def get_doc_classes(): + return [ + "EditorExportPlatformWindows", + ] + + +def get_doc_path(): + return "doc_classes" + + def get_flags(): arch = detect_build_env_arch() or detect_arch() @@ -409,6 +419,9 @@ def configure_msvc(env, vcvars_msvc_config): "wbemuuid", ] + if env.debug_features: + LIBS += ["psapi", "dbghelp"] + if env["vulkan"]: env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED"]) if not env["use_volk"]: @@ -587,6 +600,9 @@ def configure_mingw(env): ] ) + if env.debug_features: + env.Append(LIBS=["psapi", "dbghelp"]) + if env["vulkan"]: env.Append(CPPDEFINES=["VULKAN_ENABLED"]) if not env["use_volk"]: diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 6ff928580a..02bb30ed38 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -388,6 +388,17 @@ int DisplayServerWindows::get_primary_screen() const { return data.screen; } +int DisplayServerWindows::get_keyboard_focus_screen() const { + HWND hwnd = GetForegroundWindow(); + if (hwnd) { + EnumScreenData data = { 0, 0, MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }; + EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcScreen, (LPARAM)&data); + return data.screen; + } else { + return get_primary_screen(); + } +} + typedef struct { int count; int screen; @@ -424,17 +435,7 @@ Point2i DisplayServerWindows::_get_screens_origin() const { Point2i DisplayServerWindows::screen_get_position(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); EnumPosData data = { 0, p_screen, Point2() }; EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcPos, (LPARAM)&data); return data.pos - _get_screens_origin(); @@ -472,17 +473,7 @@ static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPR Size2i DisplayServerWindows::screen_get_size(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); EnumSizeData data = { 0, p_screen, Size2() }; EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcSize, (LPARAM)&data); return data.size; @@ -529,17 +520,7 @@ static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonit Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); EnumRectData data = { 0, p_screen, Rect2i() }; EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcUsableSize, (LPARAM)&data); data.rect.position -= _get_screens_origin(); @@ -617,17 +598,7 @@ static BOOL CALLBACK _MonitorEnumProcDpi(HMONITOR hMonitor, HDC hdcMonitor, LPRE int DisplayServerWindows::screen_get_dpi(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); EnumDpiData data = { 0, p_screen, 72 }; EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data); return data.dpi; @@ -655,17 +626,7 @@ Color DisplayServerWindows::screen_get_pixel(const Point2i &p_position) const { float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const { _THREAD_SAFE_METHOD_ - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; - } - + p_screen = _get_screen_index(p_screen); EnumRefreshRateData data = { 0, p_screen, SCREEN_REFRESH_RATE_FALLBACK }; EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data); return data.rate; @@ -1781,6 +1742,11 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor Ref<Image> image = texture->get_image(); ERR_FAIL_COND(!image.is_valid()); + if (image->is_compressed()) { + image = image->duplicate(true); + Error err = image->decompress(); + ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); + } UINT image_size = texture_size.width * texture_size.height; @@ -4406,5 +4372,4 @@ DisplayServerWindows::~DisplayServerWindows() { if (tts) { memdelete(tts); } - CoUninitialize(); } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 1b36b0951e..80c75c63b5 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -523,6 +523,7 @@ public: virtual int get_screen_count() const override; virtual int get_primary_screen() const override; + virtual int get_keyboard_focus_screen() const override; virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; diff --git a/platform/windows/doc_classes/EditorExportPlatformWindows.xml b/platform/windows/doc_classes/EditorExportPlatformWindows.xml new file mode 100644 index 0000000000..fee8a118bc --- /dev/null +++ b/platform/windows/doc_classes/EditorExportPlatformWindows.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformWindows" inherits="EditorExportPlatformPC" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Exporter for Windows. + </brief_description> + <description> + </description> + <tutorials> + <link title="Exporting for Windows">$DOCS_URL/tutorials/export/exporting_for_windows.html</link> + </tutorials> + <members> + <member name="application/company_name" type="String" setter="" getter=""> + Company that produced the application. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url]. + </member> + <member name="application/console_wrapper_icon" type="String" setter="" getter=""> + Console wrapper icon file. If left empty, application icon is used instead. + </member> + <member name="application/copyright" type="String" setter="" getter=""> + Copyright notice for the bundle visible to the user. Optional. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url]. + </member> + <member name="application/file_description" type="String" setter="" getter=""> + File description to be presented to users. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url]. + </member> + <member name="application/file_version" type="String" setter="" getter=""> + Version number of the file. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url]. + </member> + <member name="application/icon" type="String" setter="" getter=""> + Application icon file. If left empty, project icon is used instead. + </member> + <member name="application/icon_interpolation" type="int" setter="" getter=""> + Interpolation method used to resize application icon. + </member> + <member name="application/modify_resources" type="bool" setter="" getter=""> + If enabled, icon and metadata of the exported executable is set according to the other [code]application/*[/code] values. + </member> + <member name="application/product_name" type="String" setter="" getter=""> + Name of the application. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url]. + </member> + <member name="application/product_version" type="String" setter="" getter=""> + Application version visible to the user. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url]. + </member> + <member name="application/trademarks" type="String" setter="" getter=""> + Trademarks and registered trademarks that apply to the file. Optional. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url]. + </member> + <member name="binary_format/architecture" type="String" setter="" getter=""> + Application executable architecture. + Supported architectures: [code]x86_32[/code], [code]x86_64[/code], and [code]arm64[/code]. + Official export templates include [code]x86_32[/code] and [code]x86_64[/code] binaries only. + </member> + <member name="binary_format/embed_pck" type="bool" setter="" getter=""> + If [code]true[/code], project resources are embedded into the executable. + </member> + <member name="codesign/custom_options" type="PackedStringArray" setter="" getter=""> + Array of the additional command line arguments passed to the code signing tool. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + </member> + <member name="codesign/description" type="String" setter="" getter=""> + Description of the signed content. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + </member> + <member name="codesign/digest_algorithm" type="int" setter="" getter=""> + Digest algorithm to use for creating signature. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + </member> + <member name="codesign/enable" type="bool" setter="" getter=""> + If [code]true[/code], executable signing is enabled. + </member> + <member name="codesign/identity" type="String" setter="" getter=""> + PKCS #12 certificate file used to sign executable or certificate SHA-1 hash (if [member codesign/identity_type] is set to "Use certificate store"). See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + </member> + <member name="codesign/identity_type" type="int" setter="" getter=""> + Type of identity to use. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + </member> + <member name="codesign/password" type="String" setter="" getter=""> + Password for the certificate file used to sign executable. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + </member> + <member name="codesign/timestamp" type="bool" setter="" getter=""> + If [code]true[/code], time-stamp is added to the signature. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + </member> + <member name="codesign/timestamp_server_url" type="String" setter="" getter=""> + URL of the time stamp server. If left empty, the default server is used. See [url=https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe]Sign Tool[/url]. + </member> + <member name="custom_template/debug" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="custom_template/release" type="String" setter="" getter=""> + Path to the custom export template. If left empty, default template is used. + </member> + <member name="debug/export_console_script" type="int" setter="" getter=""> + If [code]true[/code], a console wrapper executable is exported alongside the main executable, which allows running the project with enabled console output. + </member> + <member name="ssh_remote_deploy/cleanup_script" type="String" setter="" getter=""> + Script code to execute on the remote host when app is finished. + The following variables can be used in the script: + - [code]{temp_dir}[/code] - Path of temporary folder on the remote, used to upload app and scripts to. + - [code]{archive_name}[/code] - Name of the ZIP containing uploaded application. + - [code]{exe_name}[/code] - Name of application executable. + - [code]{cmd_args}[/code] - Array of the command line argument for the application. + </member> + <member name="ssh_remote_deploy/enabled" type="bool" setter="" getter=""> + Enables remote deploy using SSH/SCP. + </member> + <member name="ssh_remote_deploy/extra_args_scp" type="String" setter="" getter=""> + Array of the additional command line arguments passed to the SCP. + </member> + <member name="ssh_remote_deploy/extra_args_ssh" type="String" setter="" getter=""> + Array of the additional command line arguments passed to the SSH. + </member> + <member name="ssh_remote_deploy/host" type="String" setter="" getter=""> + Remote host SSH user name and address, in [code]user@address[/code] format. + </member> + <member name="ssh_remote_deploy/port" type="String" setter="" getter=""> + Remote host SSH port number. + </member> + <member name="ssh_remote_deploy/run_script" type="String" setter="" getter=""> + Script code to execute on the remote host when running the app. + The following variables can be used in the script: + - [code]{temp_dir}[/code] - Path of temporary folder on the remote, used to upload app and scripts to. + - [code]{archive_name}[/code] - Name of the ZIP containing uploaded application. + - [code]{exe_name}[/code] - Name of application executable. + - [code]{cmd_args}[/code] - Array of the command line argument for the application. + </member> + <member name="texture_format/bptc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the BPTC format. + </member> + <member name="texture_format/etc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the ETC format. + </member> + <member name="texture_format/etc2" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the ETC2 format. + </member> + <member name="texture_format/s3tc" type="bool" setter="" getter=""> + If [code]true[/code], project textures are exported in the S3TC format. + </member> + </members> +</class> diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 4112bb84b5..08c620e1e9 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -33,6 +33,10 @@ #include "editor/export/editor_export.h" #include "export_plugin.h" +void register_windows_exporter_types() { + GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformWindows); +} + void register_windows_exporter() { #ifndef ANDROID_ENABLED EDITOR_DEF("export/windows/rcedit", ""); diff --git a/platform/windows/export/export.h b/platform/windows/export/export.h index f5bf83bb48..6c4020f0c8 100644 --- a/platform/windows/export/export.h +++ b/platform/windows/export/export.h @@ -31,6 +31,7 @@ #ifndef WINDOWS_EXPORT_H #define WINDOWS_EXPORT_H +void register_windows_exporter_types(); void register_windows_exporter(); #endif // WINDOWS_EXPORT_H diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 941e72c8f3..05b25eae03 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -262,21 +262,72 @@ List<String> EditorExportPlatformWindows::get_binary_extensions(const Ref<Editor return list; } -bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const { +String EditorExportPlatformWindows::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { + if (p_preset) { + if (p_name == "application/icon") { + String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon")); + if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) { + return TTR("Invalid icon path."); + } + } else if (p_name == "application/file_version") { + String file_version = p_preset->get("application/file_version"); + if (!file_version.is_empty()) { + PackedStringArray version_array = file_version.split(".", false); + if (version_array.size() != 4 || !version_array[0].is_valid_int() || + !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || + !version_array[3].is_valid_int() || file_version.find("-") > -1) { + return TTR("Invalid file version."); + } + } + } else if (p_name == "application/product_version") { + String product_version = p_preset->get("application/product_version"); + if (!product_version.is_empty()) { + PackedStringArray version_array = product_version.split(".", false); + if (version_array.size() != 4 || !version_array[0].is_valid_int() || + !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || + !version_array[3].is_valid_int() || product_version.find("-") > -1) { + return TTR("Invalid product version."); + } + } + } + } + return EditorExportPlatformPC::get_export_option_warning(p_preset, p_name); +} + +bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { // This option is not supported by "osslsigncode", used on non-Windows host. if (!OS::get_singleton()->has_feature("windows") && p_option == "codesign/identity_type") { return false; } + + // Hide codesign. + bool codesign = p_preset->get("codesign/enable"); + if (!codesign && p_option != "codesign/enable" && p_option.begins_with("codesign/")) { + return false; + } + + // Hide resources. + bool mod_res = p_preset->get("application/modify_resources"); + if (!mod_res && p_option != "application/modify_resources" && p_option.begins_with("application/")) { + return false; + } + + // Hide SSH options. + bool ssh = p_preset->get("ssh_remote_deploy/enabled"); + if (!ssh && p_option != "ssh_remote_deploy/enabled" && p_option.begins_with("ssh_remote_deploy/")) { + return false; + } + return true; } -void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) { +void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) const { EditorExportPlatformPC::get_export_options(r_options); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "binary_format/architecture", PROPERTY_HINT_ENUM, "x86_64,x86_32,arm64"), "x86_64")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA1 hash)"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA-1 hash)"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password", PROPERTY_HINT_PASSWORD), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); @@ -285,12 +336,12 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/description"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/modify_resources"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/modify_resources"), true, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg"), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/console_wrapper_icon", PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "", false, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), "")); @@ -311,7 +362,7 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio "Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue\n" "Remove-Item -Recurse -Force '{temp_dir}'"; - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "ssh_remote_deploy/enabled"), false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/host"), "user@host_ip")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "ssh_remote_deploy/port"), "22")); @@ -632,30 +683,17 @@ bool EditorExportPlatformWindows::has_valid_project_configuration(const Ref<Edit String err = ""; bool valid = true; - String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon")); - if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) { - err += TTR("Invalid icon path:") + " " + icon_path + "\n"; - } - - // Only non-negative integers can exist in the version string. - - String file_version = p_preset->get("application/file_version"); - if (!file_version.is_empty()) { - PackedStringArray version_array = file_version.split(".", false); - if (version_array.size() != 4 || !version_array[0].is_valid_int() || - !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || - !version_array[3].is_valid_int() || file_version.find("-") > -1) { - err += TTR("Invalid file version:") + " " + file_version + "\n"; - } - } - - String product_version = p_preset->get("application/product_version"); - if (!product_version.is_empty()) { - PackedStringArray version_array = product_version.split(".", false); - if (version_array.size() != 4 || !version_array[0].is_valid_int() || - !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || - !version_array[3].is_valid_int() || product_version.find("-") > -1) { - err += TTR("Invalid product version:") + " " + product_version + "\n"; + List<ExportOption> options; + get_export_options(&options); + for (const EditorExportPlatform::ExportOption &E : options) { + if (get_export_option_visibility(p_preset.ptr(), E.option.name)) { + String warn = get_export_option_warning(p_preset.ptr(), E.option.name); + if (!warn.is_empty()) { + err += warn + "\n"; + if (E.required) { + valid = false; + } + } } } @@ -957,22 +995,24 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, } EditorExportPlatformWindows::EditorExportPlatformWindows() { + if (EditorNode::get_singleton()) { #ifdef MODULE_SVG_ENABLED - Ref<Image> img = memnew(Image); - const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); + Ref<Image> img = memnew(Image); + const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _windows_logo_svg, EDSCALE, upsample, false); - set_logo(ImageTexture::create_from_image(img)); + ImageLoaderSVG img_loader; + img_loader.create_image_from_string(img, _windows_logo_svg, EDSCALE, upsample, false); + set_logo(ImageTexture::create_from_image(img)); - img_loader.create_image_from_string(img, _windows_run_icon_svg, EDSCALE, upsample, false); - run_icon = ImageTexture::create_from_image(img); + img_loader.create_image_from_string(img, _windows_run_icon_svg, EDSCALE, upsample, false); + run_icon = ImageTexture::create_from_image(img); #endif - Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); - if (theme.is_valid()) { - stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); - } else { - stop_icon.instantiate(); + Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); + if (theme.is_valid()) { + stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); + } else { + stop_icon.instantiate(); + } } } diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index fa75a17a1f..c466971202 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -37,6 +37,8 @@ #include "editor/export/editor_export_platform_pc.h" class EditorExportPlatformWindows : public EditorExportPlatformPC { + GDCLASS(EditorExportPlatformWindows, EditorExportPlatformPC); + struct SSHCleanupCommand { String host; String port; @@ -70,10 +72,12 @@ public: virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) 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 void get_export_options(List<ExportOption> *r_options) const override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; - virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option, const HashMap<StringName, Variant> &p_options) const override; + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; + virtual String get_template_file_name(const String &p_target, const String &p_arch) const override; virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 6e219fb929..5a749a9d6f 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -51,10 +51,17 @@ #include <direct.h> #include <knownfolders.h> #include <process.h> +#include <psapi.h> #include <regstr.h> #include <shlobj.h> #include <wbemcli.h> +#ifdef DEBUG_ENABLED +#pragma pack(push, before_imagehlp, 8) +#include <imagehlp.h> +#pragma pack(pop, before_imagehlp) +#endif + extern "C" { __declspec(dllexport) DWORD NvOptimusEnablement = 1; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; @@ -88,7 +95,7 @@ static String format_error_message(DWORD id) { LocalFree(messageBuffer); - return msg; + return msg.replace("\r", "").replace("\n", ""); } void RedirectStream(const char *p_file_name, const char *p_mode, FILE *p_cpp_stream, const DWORD p_std_handle) { @@ -195,7 +202,6 @@ void OS_Windows::initialize() { IPUnix::make_default(); main_loop = nullptr; - CoInitialize(nullptr); HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown **>(&dwrite_factory)); if (SUCCEEDED(hr)) { hr = dwrite_factory->GetSystemFontCollection(&font_collection, false); @@ -277,6 +283,73 @@ Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) { return OK; } +#ifdef DEBUG_ENABLED +void debug_dynamic_library_check_dependencies(const String &p_root_path, const String &p_path, HashSet<String> &r_checked, HashSet<String> &r_missing) { + if (r_checked.has(p_path)) { + return; + } + r_checked.insert(p_path); + + LOADED_IMAGE loaded_image; + HANDLE file = CreateFileW((LPCWSTR)p_path.utf16().get_data(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); + if (file != INVALID_HANDLE_VALUE) { + HANDLE file_mapping = CreateFileMappingW(file, nullptr, PAGE_READONLY | SEC_COMMIT, 0, 0, nullptr); + if (file_mapping != INVALID_HANDLE_VALUE) { + PVOID mapping = MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, 0); + if (mapping) { + PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)mapping; + PIMAGE_NT_HEADERS nt_header = nullptr; + if (dos_header->e_magic == IMAGE_DOS_SIGNATURE) { + PCHAR nt_header_ptr; + nt_header_ptr = ((PCHAR)mapping) + dos_header->e_lfanew; + nt_header = (PIMAGE_NT_HEADERS)nt_header_ptr; + if (nt_header->Signature != IMAGE_NT_SIGNATURE) { + nt_header = nullptr; + } + } + if (nt_header) { + loaded_image.ModuleName = nullptr; + loaded_image.hFile = file; + loaded_image.MappedAddress = (PUCHAR)mapping; + loaded_image.FileHeader = nt_header; + loaded_image.Sections = (PIMAGE_SECTION_HEADER)((LPBYTE)&nt_header->OptionalHeader + nt_header->FileHeader.SizeOfOptionalHeader); + loaded_image.NumberOfSections = nt_header->FileHeader.NumberOfSections; + loaded_image.SizeOfImage = GetFileSize(file, nullptr); + loaded_image.Characteristics = nt_header->FileHeader.Characteristics; + loaded_image.LastRvaSection = loaded_image.Sections; + loaded_image.fSystemImage = false; + loaded_image.fDOSImage = false; + loaded_image.Links.Flink = &loaded_image.Links; + loaded_image.Links.Blink = &loaded_image.Links; + + ULONG size = 0; + const IMAGE_IMPORT_DESCRIPTOR *import_desc = (const IMAGE_IMPORT_DESCRIPTOR *)ImageDirectoryEntryToData((HMODULE)loaded_image.MappedAddress, false, IMAGE_DIRECTORY_ENTRY_IMPORT, &size); + if (import_desc) { + for (; import_desc->Name && import_desc->FirstThunk; import_desc++) { + char16_t full_name_wc[MAX_PATH]; + const char *name_cs = (const char *)ImageRvaToVa(loaded_image.FileHeader, loaded_image.MappedAddress, import_desc->Name, 0); + String name = String(name_cs); + if (name.begins_with("api-ms-win-")) { + r_checked.insert(name); + } else if (SearchPathW(nullptr, (LPCWSTR)name.utf16().get_data(), nullptr, MAX_PATH, (LPWSTR)full_name_wc, nullptr)) { + debug_dynamic_library_check_dependencies(p_root_path, String::utf16(full_name_wc), r_checked, r_missing); + } else if (SearchPathW((LPCWSTR)(p_path.get_base_dir().utf16().get_data()), (LPCWSTR)name.utf16().get_data(), nullptr, MAX_PATH, (LPWSTR)full_name_wc, nullptr)) { + debug_dynamic_library_check_dependencies(p_root_path, String::utf16(full_name_wc), r_checked, r_missing); + } else { + r_missing.insert(name); + } + } + } + } + UnmapViewOfFile(mapping); + } + CloseHandle(file_mapping); + } + CloseHandle(file); + } +} +#endif + Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) { String path = p_path.replace("/", "\\"); @@ -299,7 +372,29 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han } p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); - ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + format_error_message(GetLastError()) + "."); +#ifdef DEBUG_ENABLED + if (!p_library_handle) { + DWORD err_code = GetLastError(); + + HashSet<String> checekd_libs; + HashSet<String> missing_libs; + debug_dynamic_library_check_dependencies(path, path, checekd_libs, missing_libs); + if (!missing_libs.is_empty()) { + String missing; + for (const String &E : missing_libs) { + if (!missing.is_empty()) { + missing += ", "; + } + missing += E; + } + ERR_FAIL_V_MSG(ERR_CANT_OPEN, vformat("Can't open dynamic library: %s, missing dependencies: (%s), error: \"%s\".", p_path, missing, format_error_message(err_code))); + } else { + ERR_FAIL_V_MSG(ERR_CANT_OPEN, vformat("Can't open dynamic library: %s, error: \"%s\"." + p_path, format_error_message(err_code))); + } + } +#else + ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s, error: \"%s\"." + p_path, format_error_message(GetLastError()))); +#endif if (cookie) { remove_dll_directory(cookie); @@ -323,7 +418,7 @@ Error OS_Windows::get_dynamic_library_symbol_handle(void *p_library_handle, cons p_symbol_handle = (void *)GetProcAddress((HMODULE)p_library_handle, p_name.utf8().get_data()); if (!p_symbol_handle) { if (!p_optional) { - ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Can't resolve symbol " + p_name + ", error: " + String::num(GetLastError()) + "."); + ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, vformat("Can't resolve symbol %s, error: \"%s\".", p_name, format_error_message(GetLastError()))); } else { return ERR_CANT_RESOLVE; } @@ -373,8 +468,6 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const { return Vector<String>(); } - CoInitialize(nullptr); - HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator); if (hr != S_OK) { return Vector<String>(); @@ -591,6 +684,43 @@ static void _append_to_pipe(char *p_bytes, int p_size, String *r_pipe, Mutex *p_ } } +Dictionary OS_Windows::get_memory_info() const { + Dictionary meminfo; + + meminfo["physical"] = -1; + meminfo["free"] = -1; + meminfo["available"] = -1; + meminfo["stack"] = -1; + + PERFORMANCE_INFORMATION pref_info; + pref_info.cb = sizeof(pref_info); + GetPerformanceInfo(&pref_info, sizeof(pref_info)); + + typedef void(WINAPI * PGetCurrentThreadStackLimits)(PULONG_PTR, PULONG_PTR); + PGetCurrentThreadStackLimits GetCurrentThreadStackLimits = (PGetCurrentThreadStackLimits)GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetCurrentThreadStackLimits"); + + ULONG_PTR LowLimit = 0; + ULONG_PTR HighLimit = 0; + if (GetCurrentThreadStackLimits) { + GetCurrentThreadStackLimits(&LowLimit, &HighLimit); + } + + if (pref_info.PhysicalTotal * pref_info.PageSize != 0) { + meminfo["physical"] = pref_info.PhysicalTotal * pref_info.PageSize; + } + if (pref_info.PhysicalAvailable * pref_info.PageSize != 0) { + meminfo["free"] = pref_info.PhysicalAvailable * pref_info.PageSize; + } + if (pref_info.CommitLimit * pref_info.PageSize != 0) { + meminfo["available"] = pref_info.CommitLimit * pref_info.PageSize; + } + if (HighLimit - LowLimit != 0) { + meminfo["stack"] = HighLimit - LowLimit; + } + + return meminfo; +} + Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { String path = p_path.replace("/", "\\"); String command = _quote_command_line_argument(path); @@ -1221,6 +1351,51 @@ Error OS_Windows::shell_open(String p_uri) { } } +Error OS_Windows::shell_show_in_file_manager(String p_path, bool p_open_folder) { + p_path = p_path.trim_suffix("file://"); + + bool open_folder = false; + if (DirAccess::dir_exists_absolute(p_path) && p_open_folder) { + open_folder = true; + } + + if (p_path.begins_with("\"")) { + p_path = String("\"") + p_path; + } + if (p_path.ends_with("\"")) { + p_path = p_path + String("\""); + } + p_path = p_path.replace("/", "\\"); + + INT_PTR ret = OK; + if (open_folder) { + ret = (INT_PTR)ShellExecuteW(nullptr, nullptr, L"explorer.exe", LPCWSTR(p_path.utf16().get_data()), nullptr, SW_SHOWNORMAL); + } else { + ret = (INT_PTR)ShellExecuteW(nullptr, nullptr, L"explorer.exe", LPCWSTR((String("/select,") + p_path).utf16().get_data()), nullptr, SW_SHOWNORMAL); + } + + if (ret > 32) { + return OK; + } else { + switch (ret) { + case ERROR_FILE_NOT_FOUND: + case SE_ERR_DLLNOTFOUND: + return ERR_FILE_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: + return ERR_FILE_BAD_PATH; + case ERROR_BAD_FORMAT: + return ERR_FILE_CORRUPT; + case SE_ERR_ACCESSDENIED: + return ERR_UNAUTHORIZED; + case 0: + case SE_ERR_OOM: + return ERR_OUT_OF_MEMORY; + default: + return FAILED; + } + } +} + String OS_Windows::get_locale() const { const _WinLocale *wl = &_win_locales[0]; @@ -1505,6 +1680,8 @@ Error OS_Windows::move_to_trash(const String &p_path) { OS_Windows::OS_Windows(HINSTANCE _hInstance) { hInstance = _hInstance; + CoInitializeEx(nullptr, COINIT_MULTITHREADED); + #ifdef WASAPI_ENABLED AudioDriverManager::add_driver(&driver_wasapi); #endif @@ -1532,4 +1709,5 @@ OS_Windows::OS_Windows(HINSTANCE _hInstance) { } OS_Windows::~OS_Windows() { + CoUninitialize(); } diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 05110c2614..960c3f30a9 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -178,6 +178,8 @@ public: virtual void delay_usec(uint32_t p_usec) const override; virtual uint64_t get_ticks_usec() const override; + virtual Dictionary get_memory_info() const override; + virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override; @@ -212,6 +214,7 @@ public: virtual String get_unique_id() const override; virtual Error shell_open(String p_uri) override; + virtual Error shell_show_in_file_manager(String p_path, bool p_open_folder) override; void run(); diff --git a/platform/windows/tts_windows.cpp b/platform/windows/tts_windows.cpp index 54ab93ee01..907096d890 100644 --- a/platform/windows/tts_windows.cpp +++ b/platform/windows/tts_windows.cpp @@ -118,7 +118,7 @@ bool TTS_Windows::is_speaking() const { SPVOICESTATUS status; synth->GetStatus(&status, nullptr); - return (status.dwRunningState == SPRS_IS_SPEAKING); + return (status.dwRunningState == SPRS_IS_SPEAKING || status.dwRunningState == 0 /* Waiting To Speak */); } bool TTS_Windows::is_paused() const { @@ -251,7 +251,6 @@ TTS_Windows *TTS_Windows::get_singleton() { TTS_Windows::TTS_Windows() { singleton = this; - CoInitialize(nullptr); if (SUCCEEDED(CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL, IID_ISpVoice, (void **)&synth))) { ULONGLONG event_mask = SPFEI(SPEI_END_INPUT_STREAM) | SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_WORD_BOUNDARY); |
