summaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/SCsub9
-rw-r--r--platform/android/api/java_class_wrapper.h3
-rw-r--r--platform/android/api/jni_singleton.h11
-rw-r--r--platform/android/dir_access_jandroid.cpp8
-rw-r--r--platform/android/dir_access_jandroid.h1
-rw-r--r--platform/android/display_server_android.cpp15
-rw-r--r--platform/android/display_server_android.h1
-rw-r--r--platform/android/doc_classes/EditorExportPlatformAndroid.xml60
-rw-r--r--platform/android/export/export.cpp7
-rw-r--r--platform/android/export/export_plugin.cpp156
-rw-r--r--platform/android/export/export_plugin.h12
-rw-r--r--platform/android/file_access_android.cpp17
-rw-r--r--platform/android/file_access_android.h11
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp36
-rw-r--r--platform/android/file_access_filesystem_jandroid.h3
-rw-r--r--platform/android/java/app/build.gradle30
-rw-r--r--platform/android/java/app/config.gradle8
-rw-r--r--platform/android/java/editor/build.gradle2
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt2
-rw-r--r--platform/android/java/lib/build.gradle5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt171
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt31
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java19
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt17
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt39
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt53
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt83
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java134
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java10
-rw-r--r--platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt104
-rw-r--r--platform/android/java_class_wrapper.cpp18
-rw-r--r--platform/android/java_godot_io_wrapper.cpp11
-rw-r--r--platform/android/java_godot_lib_jni.cpp14
-rw-r--r--platform/android/java_godot_view_wrapper.cpp1
-rw-r--r--platform/android/java_godot_wrapper.cpp20
-rw-r--r--platform/android/net_socket_android.cpp8
-rw-r--r--platform/android/net_socket_android.h1
-rw-r--r--platform/android/os_android.cpp76
-rw-r--r--platform/android/os_android.h4
-rw-r--r--platform/android/tts_android.cpp10
-rw-r--r--platform/android/tts_android.h1
-rw-r--r--platform/ios/SCsub2
-rw-r--r--platform/ios/app_delegate.mm14
-rw-r--r--platform/ios/detect.py2
-rw-r--r--platform/ios/display_server_ios.h1
-rw-r--r--platform/ios/display_server_ios.mm14
-rw-r--r--platform/ios/doc_classes/EditorExportPlatformIOS.xml2
-rw-r--r--platform/ios/export/export_plugin.cpp15
-rw-r--r--platform/ios/godot_ios.mm11
-rw-r--r--platform/ios/os_ios.h2
-rw-r--r--platform/ios/os_ios.mm20
-rw-r--r--platform/linuxbsd/SCsub3
-rw-r--r--platform/linuxbsd/crash_handler_linuxbsd.cpp4
-rw-r--r--platform/linuxbsd/detect.py18
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp19
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp143
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h4
-rw-r--r--platform/linuxbsd/godot_linuxbsd.cpp13
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp26
-rw-r--r--platform/linuxbsd/platform_linuxbsd_builders.py17
-rw-r--r--platform/linuxbsd/wayland/detect_prime_egl.cpp2
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp77
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.h4
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.cpp304
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.h20
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp95
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h2
-rw-r--r--platform/linuxbsd/x11/gl_manager_x11.cpp14
-rw-r--r--platform/linuxbsd/x11/gl_manager_x11.h1
-rw-r--r--platform/macos/SCsub9
-rw-r--r--platform/macos/detect.py15
-rw-r--r--platform/macos/dir_access_macos.mm7
-rw-r--r--platform/macos/display_server_macos.h92
-rw-r--r--platform/macos/display_server_macos.mm1375
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml2
-rw-r--r--platform/macos/export/export_plugin.cpp6
-rw-r--r--platform/macos/gl_manager_macos_legacy.h1
-rw-r--r--platform/macos/gl_manager_macos_legacy.mm13
-rw-r--r--platform/macos/godot_application.mm2
-rw-r--r--platform/macos/godot_application_delegate.mm10
-rw-r--r--platform/macos/godot_main_macos.mm19
-rw-r--r--platform/macos/godot_menu_delegate.mm13
-rw-r--r--platform/macos/godot_menu_item.h3
-rw-r--r--platform/macos/godot_open_save_delegate.mm8
-rw-r--r--platform/macos/joypad_macos.cpp619
-rw-r--r--platform/macos/joypad_macos.h111
-rw-r--r--platform/macos/joypad_macos.mm612
-rw-r--r--platform/macos/native_menu_macos.h157
-rw-r--r--platform/macos/native_menu_macos.mm1363
-rw-r--r--platform/macos/os_macos.h4
-rw-r--r--platform/macos/os_macos.mm16
-rw-r--r--platform/macos/platform_config.h7
-rw-r--r--platform/macos/platform_macos_builders.py17
-rw-r--r--platform/web/detect.py8
-rw-r--r--platform/web/display_server_web.cpp53
-rw-r--r--platform/web/display_server_web.h1
-rw-r--r--platform/web/doc_classes/EditorExportPlatformWeb.xml7
-rw-r--r--platform/web/os_web.cpp14
-rw-r--r--platform/web/os_web.h4
-rwxr-xr-xplatform/web/serve.py13
-rw-r--r--platform/web/web_main.cpp16
-rw-r--r--platform/windows/SCsub7
-rw-r--r--platform/windows/crash_handler_windows.cpp2
-rw-r--r--platform/windows/detect.py46
-rw-r--r--platform/windows/display_server_windows.cpp716
-rw-r--r--platform/windows/display_server_windows.h26
-rw-r--r--platform/windows/doc_classes/EditorExportPlatformWindows.xml1
-rw-r--r--platform/windows/export/export_plugin.cpp15
-rw-r--r--platform/windows/gl_manager_windows_native.cpp44
-rw-r--r--platform/windows/gl_manager_windows_native.h4
-rw-r--r--platform/windows/godot_res.rc5
-rw-r--r--platform/windows/godot_windows.cpp8
-rw-r--r--platform/windows/native_menu_windows.cpp1165
-rw-r--r--platform/windows/native_menu_windows.h151
-rw-r--r--platform/windows/os_windows.cpp241
-rw-r--r--platform/windows/os_windows.h12
-rw-r--r--platform/windows/platform_windows_builders.py25
-rw-r--r--platform/windows/tts_windows.cpp4
-rw-r--r--platform/windows/wgl_detect_version.cpp2
-rw-r--r--platform/windows/windows_utils.cpp280
-rw-r--r--platform/windows/windows_utils.h49
122 files changed, 6319 insertions, 3158 deletions
diff --git a/platform/SCsub b/platform/SCsub
index 5194a19518..ca282e3e68 100644
--- a/platform/SCsub
+++ b/platform/SCsub
@@ -1,5 +1,7 @@
#!/usr/bin/env python
+import methods
+
Import("env")
env.platform_sources = []
@@ -18,12 +20,7 @@ reg_apis_inc += "\n"
reg_apis += "}\n\n"
unreg_apis += "}\n"
-# NOTE: It is safe to generate this file here, since this is still execute serially
-with open("register_platform_apis.gen.cpp", "w", encoding="utf-8") as f:
- f.write(reg_apis_inc)
- f.write(reg_apis)
- f.write(unreg_apis)
-
+methods.write_file_if_needed("register_platform_apis.gen.cpp", reg_apis_inc + reg_apis + unreg_apis)
env.add_source_files(env.platform_sources, "register_platform_apis.gen.cpp")
lib = env.add_library("platform", env.platform_sources)
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index b1481ebf7b..e21a331ab9 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -209,8 +209,6 @@ class JavaClassWrapper : public Object {
#ifdef ANDROID_ENABLED
RBMap<String, Ref<JavaClass>> class_cache;
friend class JavaClass;
- jclass activityClass;
- jmethodID findClass;
jmethodID getDeclaredMethods;
jmethodID getFields;
jmethodID getParameterTypes;
@@ -229,7 +227,6 @@ class JavaClassWrapper : public Object {
jmethodID Long_longValue;
jmethodID Float_floatValue;
jmethodID Double_doubleValue;
- jobject classLoader;
bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig);
#endif
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
index a2d1c08168..5b30c392e7 100644
--- a/platform/android/api/jni_singleton.h
+++ b/platform/android/api/jni_singleton.h
@@ -241,6 +241,17 @@ public:
instance = nullptr;
#endif
}
+
+ ~JNISingleton() {
+#ifdef ANDROID_ENABLED
+ if (instance) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(instance);
+ }
+#endif
+ }
};
#endif // JNI_SINGLETON_H
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 972a7dbe6a..ab90527bfa 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -321,6 +321,14 @@ void DirAccessJAndroid::setup(jobject p_dir_access_handler) {
_current_is_hidden = env->GetMethodID(cls, "isCurrentHidden", "(II)Z");
}
+void DirAccessJAndroid::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(dir_access_handler);
+}
+
DirAccessJAndroid::DirAccessJAndroid() {
}
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index 9aaa78f38c..68578b0fa9 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -89,6 +89,7 @@ public:
virtual uint64_t get_space_left() override;
static void setup(jobject p_dir_access_handler);
+ static void terminate();
DirAccessJAndroid();
~DirAccessJAndroid();
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 01ecbc7164..c6f2f82117 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -58,15 +58,21 @@ DisplayServerAndroid *DisplayServerAndroid::get_singleton() {
bool DisplayServerAndroid::has_feature(Feature p_feature) const {
switch (p_feature) {
+#ifndef DISABLE_DEPRECATED
+ case FEATURE_GLOBAL_MENU: {
+ return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
+ } break;
+#endif
case FEATURE_CURSOR_SHAPE:
//case FEATURE_CUSTOM_CURSOR_SHAPE:
- //case FEATURE_GLOBAL_MENU:
//case FEATURE_HIDPI:
//case FEATURE_ICON:
//case FEATURE_IME:
case FEATURE_MOUSE:
//case FEATURE_MOUSE_WARP:
//case FEATURE_NATIVE_DIALOG:
+ //case FEATURE_NATIVE_DIALOG_INPUT:
+ //case FEATURE_NATIVE_DIALOG_FILE:
//case FEATURE_NATIVE_ICON:
//case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
@@ -578,6 +584,8 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
keep_screen_on = GLOBAL_GET("display/window/energy_saving/keep_screen_on");
+ native_menu = memnew(NativeMenu);
+
#if defined(GLES3_ENABLED)
if (rendering_driver == "opengl3") {
RasterizerGLES3::make_current(false);
@@ -641,6 +649,11 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
}
DisplayServerAndroid::~DisplayServerAndroid() {
+ if (native_menu) {
+ memdelete(native_menu);
+ native_menu = nullptr;
+ }
+
#if defined(RD_ENABLED)
if (rendering_device) {
memdelete(rendering_device);
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index c95eaddf93..e1914f4d18 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -76,6 +76,7 @@ class DisplayServerAndroid : public DisplayServer {
RenderingContextDriver *rendering_context = nullptr;
RenderingDevice *rendering_device = nullptr;
#endif
+ NativeMenu *native_menu = nullptr;
ObjectID window_attached_instance_id;
diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
index 64485afeb0..020e432155 100644
--- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -15,11 +15,11 @@
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.
+ If [code]true[/code], project resources are stored in the separate APK expansion file, instead of the APK.
+ [b]Note:[/b] APK expansion should be enabled to use PCK encryption. See [url=https://developer.android.com/google/play/expansion-files]APK Expansion Files[/url]
</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".
+ Base64 encoded RSA public key for your publisher account, available from the profile page on the "Google 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.
@@ -34,7 +34,7 @@
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.
+ A list of additional command line arguments, separated by space, which the exported project will receive when started.
</member>
<member name="custom_template/debug" type="String" setter="" getter="">
Path to an APK file to use as a custom export template for debug exports. If left empty, default template is used.
@@ -52,16 +52,16 @@
[b]Note:[/b] Although your binary may be smaller, your application may load slower because the native libraries are not loaded directly from the binary at runtime.
</member>
<member name="gradle_build/export_format" type="int" setter="" getter="">
- Export format for Gradle build.
+ Application export format (*.apk or *.aab).
</member>
<member name="gradle_build/gradle_build_directory" type="String" setter="" getter="">
Path to the Gradle build directory. If left empty, then [code]res://android[/code] will be used.
</member>
<member name="gradle_build/min_sdk" type="String" setter="" getter="">
- Minimal Android SDK version for Gradle build.
+ Minimum Android API level required for the application to run (used during Gradle build). See [url=https://developer.android.com/guide/topics/manifest/uses-sdk-element#uses]android:minSdkVersion[/url].
</member>
<member name="gradle_build/target_sdk" type="String" setter="" getter="">
- Target Android SDK version for Gradle build.
+ The Android API level on which the application is designed to run (used during Gradle build). See [url=https://developer.android.com/guide/topics/manifest/uses-sdk-element#uses]android:targetSdkVersion[/url].
</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.
@@ -97,25 +97,25 @@
Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_RELEASE_USER[/code].
</member>
<member name="launcher_icons/adaptive_background_432x432" type="String" setter="" getter="">
- Background layer of the application adaptive icon file.
+ Background layer of the application adaptive icon file. See [url=https://developer.android.com/develop/ui/views/launch/icon_design_adaptive#design-adaptive-icons]Design adaptive icons[/url].
</member>
<member name="launcher_icons/adaptive_foreground_432x432" type="String" setter="" getter="">
- Foreground layer of the application adaptive icon file.
+ Foreground layer of the application adaptive icon file. See [url=https://developer.android.com/develop/ui/views/launch/icon_design_adaptive#design-adaptive-icons]Design adaptive icons[/url].
</member>
<member name="launcher_icons/main_192x192" type="String" setter="" getter="">
Application icon file. If left empty, it will fallback to [member ProjectSettings.application/config/icon].
</member>
<member name="package/app_category" type="int" setter="" getter="">
- Application category for the Play Store.
+ Application category for the Google Play Store. Only define this if your application fits one of the categories well. See [url=https://developer.android.com/guide/topics/manifest/application-element#appCategory]android:appCategory[/url].
</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.
+ If [code]true[/code], task initiated by main activity will be excluded from the list of recently used applications. See [url=https://developer.android.com/guide/topics/manifest/activity-element#exclude]android:excludeFromRecents[/url].
</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.
+ If [code]true[/code], when the user uninstalls an app, a prompt to keep the app's data will be shown. See [url=https://developer.android.com/guide/topics/manifest/application-element#fragileuserdata]android:hasFragileUserData[/url].
</member>
<member name="package/show_as_launcher_app" type="bool" setter="" getter="">
If [code]true[/code], the user will be able to set this app as the system launcher in Android preferences.
@@ -274,8 +274,7 @@
<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 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].
@@ -310,8 +309,7 @@
<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 name="permissions/get_tasks" type="bool" setter="" getter="" deprecated="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.
@@ -379,13 +377,14 @@
<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 name="permissions/persistent_activity" type="bool" setter="" getter="" deprecated="Deprecated in API level 15.">
+ Allows an application to make its activities persistent.
</member>
- <member name="permissions/process_outgoing_calls" type="bool" setter="" getter="">
+ <member name="permissions/post_notifications" type="bool" setter="" getter="">
+ Allows an application to post notifications. Added in API level 33. See [url=https://developer.android.com/develop/ui/views/notifications/notification-permission]Notification runtime permission[/url].
+ </member>
+ <member name="permissions/process_outgoing_calls" type="bool" setter="" getter="" deprecated="Deprecated in API level 29.">
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].
@@ -396,9 +395,8 @@
<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="">
+ <member name="permissions/read_external_storage" type="bool" setter="" getter="" deprecated="Deprecated in API level 33.">
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.
@@ -406,8 +404,7 @@
<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 name="permissions/read_input_state" type="bool" setter="" getter="" deprecated="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].
@@ -454,8 +451,7 @@
<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 name="permissions/restart_packages" type="bool" setter="" getter="" deprecated="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].
@@ -484,8 +480,7 @@
<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 name="permissions/set_preferred_applications" type="bool" setter="" getter="" deprecated="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].
@@ -511,8 +506,7 @@
<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 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].
@@ -520,8 +514,7 @@
<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 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].
@@ -605,6 +598,7 @@
Application version visible to the user. Falls back to [member ProjectSettings.application/config/version] if left empty.
</member>
<member name="xr_features/xr_mode" type="int" setter="" getter="">
+ The extended reality (XR) mode for this application.
</member>
</members>
</class>
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 138714634f..6a6d7149ff 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -33,6 +33,7 @@
#include "export_plugin.h"
#include "core/os/os.h"
+#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
#include "editor/export/editor_export.h"
@@ -46,10 +47,10 @@ void register_android_exporter() {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME"));
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
- EDITOR_DEF("export/android/debug_keystore", "");
+ EDITOR_DEF("export/android/debug_keystore", EditorPaths::get_singleton()->get_debug_keystore_path());
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"));
- EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey");
- EDITOR_DEF("export/android/debug_keystore_pass", "android");
+ EDITOR_DEF("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER);
+ EDITOR_DEF("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD));
EDITOR_DEF("export/android/force_system_user", false);
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index a485b57a64..3b1a534daf 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -141,6 +141,7 @@ static const char *android_perms[] = {
"MOUNT_UNMOUNT_FILESYSTEMS",
"NFC",
"PERSISTENT_ACTIVITY",
+ "POST_NOTIFICATIONS",
"PROCESS_OUTGOING_CALLS",
"READ_CALENDAR",
"READ_CALL_LOG",
@@ -379,14 +380,15 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
} else if (p.begins_with("ro.build.version.sdk=")) {
d.api_level = p.get_slice("=", 1).to_int();
} else if (p.begins_with("ro.product.cpu.abi=")) {
- d.description += "CPU: " + p.get_slice("=", 1).strip_edges() + "\n";
+ d.architecture = p.get_slice("=", 1).strip_edges();
+ d.description += "CPU: " + d.architecture + "\n";
} else if (p.begins_with("ro.product.manufacturer=")) {
d.description += "Manufacturer: " + p.get_slice("=", 1).strip_edges() + "\n";
} else if (p.begins_with("ro.board.platform=")) {
d.description += "Chipset: " + p.get_slice("=", 1).strip_edges() + "\n";
} else if (p.begins_with("ro.opengles.version=")) {
uint32_t opengl = p.get_slice("=", 1).to_int();
- d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl)&0xFF) + "\n";
+ d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl) & 0xFF) + "\n";
}
}
@@ -415,7 +417,7 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
}
}
- if (EDITOR_GET("export/android/shutdown_adb_on_exit")) {
+ if (ea->has_runnable_preset.is_set() && EDITOR_GET("export/android/shutdown_adb_on_exit")) {
String adb = get_adb_path();
if (!FileAccess::exists(adb)) {
return; //adb not configured
@@ -829,13 +831,82 @@ bool EditorExportPlatformAndroid::_uses_vulkan() {
void EditorExportPlatformAndroid::_notification(int p_what) {
#ifndef ANDROID_ENABLED
- if (p_what == NOTIFICATION_POSTINITIALIZE) {
- ERR_FAIL_NULL(EditorExport::get_singleton());
- EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status));
+ switch (p_what) {
+ case NOTIFICATION_POSTINITIALIZE: {
+ if (EditorExport::get_singleton()) {
+ EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAndroid::_update_preset_status));
+ }
+ } break;
+
+ case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+ if (EditorSettings::get_singleton()->check_changed_settings_in_group("export/android")) {
+ _create_editor_debug_keystore_if_needed();
+ }
+ } break;
}
#endif
}
+void EditorExportPlatformAndroid::_create_editor_debug_keystore_if_needed() {
+ // Check if we have a valid keytool path.
+ String keytool_path = get_keytool_path();
+ if (!FileAccess::exists(keytool_path)) {
+ return;
+ }
+
+ // Check if the current editor debug keystore exists.
+ String editor_debug_keystore = EDITOR_GET("export/android/debug_keystore");
+ if (FileAccess::exists(editor_debug_keystore)) {
+ return;
+ }
+
+ // Generate the debug keystore.
+ String keystore_path = EditorPaths::get_singleton()->get_debug_keystore_path();
+ String keystores_dir = keystore_path.get_base_dir();
+ if (!DirAccess::exists(keystores_dir)) {
+ Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Error err = dir_access->make_dir_recursive(keystores_dir);
+ if (err != OK) {
+ WARN_PRINT(TTR("Error creating keystores directory:") + "\n" + keystores_dir);
+ return;
+ }
+ }
+
+ if (!FileAccess::exists(keystore_path)) {
+ String output;
+ List<String> args;
+ args.push_back("-genkey");
+ args.push_back("-keystore");
+ args.push_back(keystore_path);
+ args.push_back("-storepass");
+ args.push_back("android");
+ args.push_back("-alias");
+ args.push_back(DEFAULT_ANDROID_KEYSTORE_DEBUG_USER);
+ args.push_back("-keypass");
+ args.push_back(DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD);
+ args.push_back("-keyalg");
+ args.push_back("RSA");
+ args.push_back("-keysize");
+ args.push_back("2048");
+ args.push_back("-validity");
+ args.push_back("10000");
+ args.push_back("-dname");
+ args.push_back("cn=Godot, ou=Godot Engine, o=Stichting Godot, c=NL");
+ Error error = OS::get_singleton()->execute(keytool_path, args, &output, nullptr, true);
+ print_verbose(output);
+ if (error != OK) {
+ WARN_PRINT("Error: Unable to create debug keystore");
+ return;
+ }
+ }
+
+ // Update the editor settings.
+ EditorSettings::get_singleton()->set("export/android/debug_keystore", keystore_path);
+ EditorSettings::get_singleton()->set("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER);
+ EditorSettings::get_singleton()->set("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD);
+ print_verbose("Updated editor debug keystore to " + keystore_path);
+}
+
void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
const char **aperms = android_perms;
while (*aperms) {
@@ -1390,6 +1461,14 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
p_manifest = ret;
}
+String EditorExportPlatformAndroid::_get_keystore_path(const Ref<EditorExportPreset> &p_preset, bool p_debug) {
+ String keystore_preference = p_debug ? "keystore/debug" : "keystore/release";
+ String keystore_env_variable = p_debug ? ENV_ANDROID_KEYSTORE_DEBUG_PATH : ENV_ANDROID_KEYSTORE_RELEASE_PATH;
+ String keystore_path = p_preset->get_or_env(keystore_preference, keystore_env_variable);
+
+ return ProjectSettings::get_singleton()->globalize_path(keystore_path).simplify_path();
+}
+
String EditorExportPlatformAndroid::_parse_string(const uint8_t *p_bytes, bool p_utf8) {
uint32_t offset = 0;
uint32_t len = 0;
@@ -1918,7 +1997,15 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor
bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
if (p_option == "graphics/opengl_debug" ||
p_option == "command_line/extra_args" ||
- p_option == "permissions/custom_permissions") {
+ p_option == "permissions/custom_permissions" ||
+ p_option == "gradle_build/compress_native_libraries" ||
+ p_option == "package/retain_data_on_uninstall" ||
+ p_option == "package/exclude_from_recents" ||
+ p_option == "package/show_in_app_library" ||
+ p_option == "package/show_as_launcher_app" ||
+ p_option == "apk_expansion/enable" ||
+ p_option == "apk_expansion/SALT" ||
+ p_option == "apk_expansion/public_key") {
return advanced_options_enabled;
}
if (p_option == "gradle_build/gradle_build_directory" || p_option == "gradle_build/android_source_template") {
@@ -1928,6 +2015,12 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor
// The APK templates are ignored if Gradle build is enabled.
return advanced_options_enabled && !bool(p_preset->get("gradle_build/use_gradle_build"));
}
+
+ // Hide .NET embedding option (always enabled).
+ if (p_option == "dotnet/embed_build_outputs") {
+ return false;
+ }
+
return true;
}
@@ -1991,6 +2084,12 @@ String EditorExportPlatformAndroid::get_option_tooltip(int p_index) const {
return s;
}
+String EditorExportPlatformAndroid::get_device_architecture(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, devices.size(), "");
+ MutexLock lock(device_lock);
+ return devices[p_index].architecture;
+}
+
Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
@@ -2179,6 +2278,15 @@ String EditorExportPlatformAndroid::get_java_path() {
return java_sdk_path.path_join("bin/java" + exe_ext);
}
+String EditorExportPlatformAndroid::get_keytool_path() {
+ String exe_ext;
+ if (OS::get_singleton()->get_name() == "Windows") {
+ exe_ext = ".exe";
+ }
+ String java_sdk_path = EDITOR_GET("export/android/java_sdk_path");
+ return java_sdk_path.path_join("bin/keytool" + exe_ext);
+}
+
String EditorExportPlatformAndroid::get_adb_path() {
String exe_ext;
if (OS::get_singleton()->get_name() == "Windows") {
@@ -2324,10 +2432,10 @@ static bool has_valid_keystore_credentials(String &r_error_str, const String &p_
}
bool EditorExportPlatformAndroid::has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error) {
- String dk = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH);
+ String dk = _get_keystore_path(p_preset, true);
String dk_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER);
String dk_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS);
- String rk = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH);
+ String rk = _get_keystore_path(p_preset, false);
String rk_user = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER);
String rk_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS);
@@ -2400,9 +2508,22 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
err += template_err;
}
} else {
+ // Validate the custom gradle android source template.
+ bool android_source_template_valid = false;
+ const String android_source_template = p_preset->get("gradle_build/android_source_template");
+ if (!android_source_template.is_empty()) {
+ android_source_template_valid = FileAccess::exists(android_source_template);
+ if (!android_source_template_valid) {
+ err += TTR("Custom Android source template not found.") + "\n";
+ }
+ }
+
+ // Validate the installed build template.
bool installed_android_build_template = FileAccess::exists(ExportTemplateManager::get_android_build_directory(p_preset).path_join("build.gradle"));
if (!installed_android_build_template) {
- r_missing_templates = !exists_export_template("android_source.zip", &err);
+ if (!android_source_template_valid) {
+ r_missing_templates = !exists_export_template("android_source.zip", &err);
+ }
err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
} else {
r_missing_templates = false;
@@ -2413,7 +2534,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
// Validate the rest of the export configuration.
- String dk = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH);
+ String dk = _get_keystore_path(p_preset, true);
String dk_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER);
String dk_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS);
@@ -2431,7 +2552,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
}
}
- String rk = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH);
+ String rk = _get_keystore_path(p_preset, false);
String rk_user = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER);
String rk_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS);
@@ -2688,7 +2809,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep) {
int export_format = int(p_preset->get("gradle_build/export_format"));
String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK";
- String release_keystore = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH);
+ String release_keystore = _get_keystore_path(p_preset, false);
String release_username = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER);
String release_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS);
String target_sdk_version = p_preset->get("gradle_build/target_sdk");
@@ -2710,7 +2831,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
String password;
String user;
if (p_debug) {
- keystore = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH);
+ keystore = _get_keystore_path(p_preset, true);
password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS);
user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER);
@@ -3075,7 +3196,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
return err;
}
if (user_data.libs.size() > 0) {
- Ref<FileAccess> fa = FileAccess::open(GDEXTENSION_LIBS_PATH, FileAccess::WRITE);
+ Ref<FileAccess> fa = FileAccess::open(gdextension_libs_path, FileAccess::WRITE);
fa->store_string(JSON::stringify(user_data.libs, "\t"));
}
} else {
@@ -3195,7 +3316,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
if (should_sign) {
if (p_debug) {
- String debug_keystore = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH);
+ String debug_keystore = _get_keystore_path(p_preset, true);
String debug_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS);
String debug_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER);
@@ -3217,7 +3338,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
cmdline.push_back("-Pdebug_keystore_password=" + debug_password); // argument to specify the debug keystore password.
} else {
// Pass the release keystore info as well
- String release_keystore = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH);
+ String release_keystore = _get_keystore_path(p_preset, false);
String release_username = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER);
String release_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS);
if (release_keystore.is_relative_path()) {
@@ -3611,6 +3732,7 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
android_plugins_changed.set();
#endif // DISABLE_DEPRECATED
#ifndef ANDROID_ENABLED
+ _create_editor_debug_keystore_if_needed();
_update_preset_status();
check_for_changes_thread.start(_check_for_changes_poll_thread, this);
#endif
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index e25655c6cc..679afdc50f 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -60,6 +60,9 @@ const String ENV_ANDROID_KEYSTORE_RELEASE_PATH = "GODOT_ANDROID_KEYSTORE_RELEASE
const String ENV_ANDROID_KEYSTORE_RELEASE_USER = "GODOT_ANDROID_KEYSTORE_RELEASE_USER";
const String ENV_ANDROID_KEYSTORE_RELEASE_PASS = "GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD";
+const String DEFAULT_ANDROID_KEYSTORE_DEBUG_USER = "androiddebugkey";
+const String DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD = "android";
+
struct LauncherIcon {
const char *export_path;
int dimensions = 0;
@@ -76,6 +79,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
String name;
String description;
int api_level = 0;
+ String architecture;
};
struct APKExportData {
@@ -165,6 +169,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet);
+ static String _get_keystore_path(const Ref<EditorExportPreset> &p_preset, bool p_debug);
+
static String _parse_string(const uint8_t *p_bytes, bool p_utf8);
void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &r_manifest);
@@ -185,6 +191,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
const Ref<Image> &foreground,
const Ref<Image> &background);
+ static void _create_editor_debug_keystore_if_needed();
+
static Vector<ABI> get_enabled_abis(const Ref<EditorExportPreset> &p_preset);
static bool _uses_vulkan();
@@ -221,6 +229,8 @@ public:
virtual String get_option_tooltip(int p_index) const override;
+ virtual String get_device_architecture(int p_index) const override;
+
virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
virtual Ref<Texture2D> get_run_icon() const override;
@@ -231,6 +241,8 @@ public:
static String get_java_path();
+ static String get_keytool_path();
+
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
static bool has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error);
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index f56eda4694..ae336d6f9d 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -31,8 +31,12 @@
#include "file_access_android.h"
#include "core/string/print_string.h"
+#include "thread_jandroid.h"
+
+#include <android/asset_manager_jni.h>
AAssetManager *FileAccessAndroid::asset_manager = nullptr;
+jobject FileAccessAndroid::j_asset_manager = nullptr;
String FileAccessAndroid::get_path() const {
return path_src;
@@ -257,3 +261,16 @@ void FileAccessAndroid::close() {
FileAccessAndroid::~FileAccessAndroid() {
_close();
}
+
+void FileAccessAndroid::setup(jobject p_asset_manager) {
+ JNIEnv *env = get_jni_env();
+ j_asset_manager = env->NewGlobalRef(p_asset_manager);
+ asset_manager = AAssetManager_fromJava(env, j_asset_manager);
+}
+
+void FileAccessAndroid::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(j_asset_manager);
+}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index ec613b6687..e79daeafb3 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -35,9 +35,13 @@
#include <android/asset_manager.h>
#include <android/log.h>
+#include <jni.h>
#include <stdio.h>
class FileAccessAndroid : public FileAccess {
+ static AAssetManager *asset_manager;
+ static jobject j_asset_manager;
+
mutable AAsset *asset = nullptr;
mutable uint64_t len = 0;
mutable uint64_t pos = 0;
@@ -48,8 +52,6 @@ class FileAccessAndroid : public FileAccess {
void _close();
public:
- static AAssetManager *asset_manager;
-
virtual Error open_internal(const String &p_path, int p_mode_flags) override; // open a file
virtual bool is_open() const override; // true when file is open
@@ -65,6 +67,7 @@ public:
virtual bool eof_reached() const override; // reading passed EOF
+ virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual uint8_t get_8() const override; // get a byte
virtual uint16_t get_16() const override;
virtual uint32_t get_32() const override;
@@ -92,6 +95,10 @@ public:
virtual void close() override;
+ static void setup(jobject p_asset_manager);
+
+ static void terminate();
+
~FileAccessAndroid();
};
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index 46d9728632..f28d469d07 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -53,6 +53,7 @@ jmethodID FileAccessFilesystemJAndroid::_file_write = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_flush = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_exists = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_last_modified = nullptr;
+jmethodID FileAccessFilesystemJAndroid::_file_resize = nullptr;
String FileAccessFilesystemJAndroid::get_path() const {
return path_src;
@@ -82,7 +83,7 @@ Error FileAccessFilesystemJAndroid::open_internal(const String &p_path, int p_mo
default:
return ERR_FILE_CANT_OPEN;
- case -1:
+ case -2:
return ERR_FILE_NOT_FOUND;
}
}
@@ -324,6 +325,30 @@ Error FileAccessFilesystemJAndroid::get_error() const {
return OK;
}
+Error FileAccessFilesystemJAndroid::resize(int64_t p_length) {
+ if (_file_resize) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, FAILED);
+ ERR_FAIL_COND_V_MSG(!is_open(), FAILED, "File must be opened before use.");
+ int res = env->CallIntMethod(file_access_handler, _file_resize, id, p_length);
+ switch (res) {
+ case 0:
+ return OK;
+ case -4:
+ return ERR_INVALID_PARAMETER;
+ case -3:
+ return ERR_FILE_CANT_OPEN;
+ case -2:
+ return ERR_FILE_NOT_FOUND;
+ case -1:
+ default:
+ return FAILED;
+ }
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+}
+
void FileAccessFilesystemJAndroid::flush() {
if (_file_flush) {
JNIEnv *env = get_jni_env();
@@ -383,6 +408,15 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) {
_file_flush = env->GetMethodID(cls, "fileFlush", "(I)V");
_file_exists = env->GetMethodID(cls, "fileExists", "(Ljava/lang/String;)Z");
_file_last_modified = env->GetMethodID(cls, "fileLastModified", "(Ljava/lang/String;)J");
+ _file_resize = env->GetMethodID(cls, "fileResize", "(IJ)I");
+}
+
+void FileAccessFilesystemJAndroid::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(file_access_handler);
}
void FileAccessFilesystemJAndroid::close() {
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index f33aa64ebe..6a8fc524b7 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -52,6 +52,7 @@ class FileAccessFilesystemJAndroid : public FileAccess {
static jmethodID _file_close;
static jmethodID _file_exists;
static jmethodID _file_last_modified;
+ static jmethodID _file_resize;
int id;
String absolute_path;
@@ -76,6 +77,7 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
+ virtual Error resize(int64_t p_length) override;
virtual uint8_t get_8() const override; ///< get a byte
virtual uint16_t get_16() const override;
virtual uint32_t get_32() const override;
@@ -95,6 +97,7 @@ public:
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
static void setup(jobject p_file_access_handler);
+ static void terminate();
virtual uint64_t _get_modified_time(const String &p_file) override;
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 7797f4bc9d..b83ef1471c 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -205,36 +205,66 @@ android {
}
task copyAndRenameDebugApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/debug/android_debug.apk"
into getExportPath()
rename "android_debug.apk", getExportFilename()
}
task copyAndRenameDevApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/dev/android_dev.apk"
into getExportPath()
rename "android_dev.apk", getExportFilename()
}
task copyAndRenameReleaseApk(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/apk/release/android_release.apk"
into getExportPath()
rename "android_release.apk", getExportFilename()
}
task copyAndRenameDebugAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/debug/build-debug.aab"
into getExportPath()
rename "build-debug.aab", getExportFilename()
}
task copyAndRenameDevAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/dev/build-dev.aab"
into getExportPath()
rename "build-dev.aab", getExportFilename()
}
task copyAndRenameReleaseAab(type: Copy) {
+ // The 'doNotTrackState' is added to disable gradle's up-to-date checks for output files
+ // and directories. Otherwise this check may cause permissions access failures on Windows
+ // machines.
+ doNotTrackState("No need for up-to-date checks for the copy-and-rename operation")
+
from "$buildDir/outputs/bundle/release/build-release.aab"
into getExportPath()
rename "build-release.aab", getExportFilename()
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index f2c4a5d1b6..d27e75b07a 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -194,17 +194,17 @@ final String VALUE_SEPARATOR_REGEX = "\\|"
// get the list of ABIs the project should be exported to
ext.getExportEnabledABIs = { ->
- String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : "";
+ String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : ""
if (enabledABIs == null || enabledABIs.isEmpty()) {
enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|"
}
- Set<String> exportAbiFilter = [];
+ Set<String> exportAbiFilter = []
for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) {
if (!abi_name.trim().isEmpty()){
- exportAbiFilter.add(abi_name);
+ exportAbiFilter.add(abi_name)
}
}
- return exportAbiFilter;
+ return exportAbiFilter
}
ext.getExportPath = {
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index 0f7ffeecae..c5ef086152 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -20,7 +20,7 @@ ext {
String versionStatus = System.getenv("GODOT_VERSION_STATUS")
if (versionStatus != null && !versionStatus.isEmpty()) {
try {
- buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", ""));
+ buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", ""))
} catch (NumberFormatException ignored) {
buildNumber = 0
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index caf64bc933..c9a62d24b7 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -127,7 +127,7 @@ open class GodotEditor : GodotActivity() {
*/
protected open fun checkForProjectPermissionsToEnable() {
// Check for RECORD_AUDIO permission
- val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"));
+ val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"))
if (audioInputEnabled) {
PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this)
}
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index ed967b9660..81ab598b90 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -11,6 +11,8 @@ apply from: "../scripts/publish-module.gradle"
dependencies {
implementation "androidx.fragment:fragment:$versions.fragmentVersion"
+
+ testImplementation "junit:junit:4.13.2"
}
def pathToRootDir = "../../../../"
@@ -74,6 +76,7 @@ android {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
+ test.java.srcDirs = ['srcTest/java']
res.srcDirs = ['res']
aidl.srcDirs = ['aidl']
assets.srcDirs = ['assets']
@@ -118,7 +121,7 @@ android {
case "dev":
default:
sconsTarget += "_debug"
- break;
+ break
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index e2e77e7796..ce53aeebcb 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -56,6 +56,7 @@ import org.godotengine.godot.io.directory.DirectoryAccessHandler
import org.godotengine.godot.io.file.FileAccessHandler
import org.godotengine.godot.plugin.GodotPluginRegistry
import org.godotengine.godot.tts.GodotTTS
+import org.godotengine.godot.utils.CommandLineFileParser
import org.godotengine.godot.utils.GodotNetUtils
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.PermissionsUtil.requestPermission
@@ -68,7 +69,7 @@ import org.godotengine.godot.xr.XRMode
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
-import java.nio.charset.StandardCharsets
+import java.lang.Exception
import java.security.MessageDigest
import java.util.*
@@ -84,6 +85,9 @@ class Godot(private val context: Context) : SensorEventListener {
private val TAG = Godot::class.java.simpleName
}
+ private val windowManager: WindowManager by lazy {
+ requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ }
private val pluginRegistry: GodotPluginRegistry by lazy {
GodotPluginRegistry.getPluginRegistry()
}
@@ -120,6 +124,7 @@ class Godot(private val context: Context) : SensorEventListener {
val directoryAccessHandler = DirectoryAccessHandler(context)
val fileAccessHandler = FileAccessHandler(context)
val netUtils = GodotNetUtils(context)
+ private val commandLineFileParser = CommandLineFileParser()
/**
* Tracks whether [onCreate] was completed successfully.
@@ -150,7 +155,7 @@ class Godot(private val context: Context) : SensorEventListener {
private var useApkExpansion = false
private var useImmersive = false
private var useDebugOpengl = false
- private var darkMode = false;
+ private var darkMode = false
private var containerLayout: FrameLayout? = null
var renderView: GodotRenderView? = null
@@ -290,7 +295,7 @@ class Godot(private val context: Context) : SensorEventListener {
initializationStarted = false
throw e
} finally {
- endBenchmarkMeasure("Startup", "Godot::onCreate");
+ endBenchmarkMeasure("Startup", "Godot::onCreate")
}
}
@@ -396,16 +401,19 @@ class Godot(private val context: Context) : SensorEventListener {
}
if (host == primaryHost) {
- renderView!!.startRenderer()
+ renderView?.startRenderer()
}
- val view: View = renderView!!.view
- containerLayout?.addView(
- view,
+
+ renderView?.let {
+ containerLayout?.addView(
+ it.view,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
- )
+ )
+ }
+
editText.setView(renderView)
io?.setEdit(editText)
@@ -448,20 +456,23 @@ class Godot(private val context: Context) : SensorEventListener {
})
} else {
// Infer the virtual keyboard height using visible area.
- view.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
+ renderView?.view?.viewTreeObserver?.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
// Don't allocate a new Rect every time the callback is called.
val visibleSize = Rect()
override fun onGlobalLayout() {
- val surfaceView = renderView!!.view
- surfaceView.getWindowVisibleDisplayFrame(visibleSize)
- val keyboardHeight = surfaceView.height - visibleSize.bottom
- GodotLib.setVirtualKeyboardHeight(keyboardHeight)
+ renderView?.let {
+ val surfaceView = it.view
+
+ surfaceView.getWindowVisibleDisplayFrame(visibleSize)
+ val keyboardHeight = surfaceView.height - visibleSize.bottom
+ GodotLib.setVirtualKeyboardHeight(keyboardHeight)
+ }
}
})
}
if (host == primaryHost) {
- renderView!!.queueOnRenderThread {
+ renderView?.queueOnRenderThread {
for (plugin in pluginRegistry.allPlugins) {
plugin.onRegisterPluginWithGodotNative()
}
@@ -495,7 +506,7 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
- renderView!!.onActivityStarted()
+ renderView?.onActivityStarted()
}
fun onResume(host: GodotHost) {
@@ -503,7 +514,7 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
- renderView!!.onActivityResumed()
+ renderView?.onActivityResumed()
if (mAccelerometer != null) {
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
}
@@ -535,7 +546,7 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
- renderView!!.onActivityPaused()
+ renderView?.onActivityPaused()
mSensorManager.unregisterListener(this)
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainPause()
@@ -547,7 +558,7 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
- renderView!!.onActivityStopped()
+ renderView?.onActivityStopped()
}
fun onDestroy(primaryHost: GodotHost) {
@@ -569,7 +580,7 @@ class Godot(private val context: Context) : SensorEventListener {
* Configuration change callback
*/
fun onConfigurationChanged(newConfig: Configuration) {
- var newDarkMode = newConfig.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
+ val newDarkMode = newConfig.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
if (darkMode != newDarkMode) {
darkMode = newDarkMode
GodotLib.onNightModeChanged()
@@ -613,7 +624,7 @@ class Godot(private val context: Context) : SensorEventListener {
// These properties are defined after Godot setup completion, so we retrieve them here.
val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click"))
val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
- val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis"));
+ val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis"))
runOnUiThread {
renderView?.inputHandler?.apply {
@@ -686,9 +697,7 @@ class Godot(private val context: Context) : SensorEventListener {
* This must be called after the render thread has started.
*/
fun runOnRenderThread(action: Runnable) {
- if (renderView != null) {
- renderView!!.queueOnRenderThread(action)
- }
+ renderView?.queueOnRenderThread(action)
}
/**
@@ -765,7 +774,7 @@ class Godot(private val context: Context) : SensorEventListener {
return mClipboard.hasPrimaryClip()
}
- fun getClipboard(): String? {
+ fun getClipboard(): String {
val clipData = mClipboard.primaryClip ?: return ""
val text = clipData.getItemAt(0).text ?: return ""
return text.toString()
@@ -782,15 +791,14 @@ class Godot(private val context: Context) : SensorEventListener {
@Keep
private fun forceQuit(instanceId: Int): Boolean {
- if (primaryHost == null) {
- return false
- }
- return if (instanceId == 0) {
- primaryHost!!.onGodotForceQuit(this)
- true
- } else {
- primaryHost!!.onGodotForceQuit(instanceId)
- }
+ primaryHost?.let {
+ if (instanceId == 0) {
+ it.onGodotForceQuit(this)
+ return true
+ } else {
+ return it.onGodotForceQuit(instanceId)
+ }
+ } ?: return false
}
fun onBackPressed(host: GodotHost) {
@@ -804,20 +812,17 @@ class Godot(private val context: Context) : SensorEventListener {
shouldQuit = false
}
}
- if (shouldQuit && renderView != null) {
- renderView!!.queueOnRenderThread { GodotLib.back() }
+ if (shouldQuit) {
+ renderView?.queueOnRenderThread { GodotLib.back() }
}
}
private fun getRotatedValues(values: FloatArray?): FloatArray? {
if (values == null || values.size != 3) {
- return values
+ return null
}
- val display =
- (requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
- val displayRotation = display.rotation
val rotatedValues = FloatArray(3)
- when (displayRotation) {
+ when (windowManager.defaultDisplay.rotation) {
Surface.ROTATION_0 -> {
rotatedValues[0] = values[0]
rotatedValues[1] = values[1]
@@ -846,37 +851,36 @@ class Godot(private val context: Context) : SensorEventListener {
if (renderView == null) {
return
}
+
+ val rotatedValues = getRotatedValues(event.values)
+
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
- val rotatedValues = getRotatedValues(event.values)
- renderView!!.queueOnRenderThread {
- GodotLib.accelerometer(
- -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
- )
+ rotatedValues?.let {
+ renderView?.queueOnRenderThread {
+ GodotLib.accelerometer(-it[0], -it[1], -it[2])
+ }
}
}
Sensor.TYPE_GRAVITY -> {
- val rotatedValues = getRotatedValues(event.values)
- renderView!!.queueOnRenderThread {
- GodotLib.gravity(
- -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
- )
+ rotatedValues?.let {
+ renderView?.queueOnRenderThread {
+ GodotLib.gravity(-it[0], -it[1], -it[2])
+ }
}
}
Sensor.TYPE_MAGNETIC_FIELD -> {
- val rotatedValues = getRotatedValues(event.values)
- renderView!!.queueOnRenderThread {
- GodotLib.magnetometer(
- -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
- )
+ rotatedValues?.let {
+ renderView?.queueOnRenderThread {
+ GodotLib.magnetometer(-it[0], -it[1], -it[2])
+ }
}
}
Sensor.TYPE_GYROSCOPE -> {
- val rotatedValues = getRotatedValues(event.values)
- renderView!!.queueOnRenderThread {
- GodotLib.gyroscope(
- rotatedValues!![0], rotatedValues[1], rotatedValues[2]
- )
+ rotatedValues?.let {
+ renderView?.queueOnRenderThread {
+ GodotLib.gyroscope(it[0], it[1], it[2])
+ }
}
}
}
@@ -908,47 +912,18 @@ class Godot(private val context: Context) : SensorEventListener {
}
private fun getCommandLine(): MutableList<String> {
- val original: MutableList<String> = parseCommandLine()
+ val commandLine = try {
+ commandLineFileParser.parseCommandLine(requireActivity().assets.open("_cl_"))
+ } catch (ignored: Exception) {
+ mutableListOf()
+ }
+
val hostCommandLine = primaryHost?.commandLine
if (!hostCommandLine.isNullOrEmpty()) {
- original.addAll(hostCommandLine)
+ commandLine.addAll(hostCommandLine)
}
- return original
- }
- private fun parseCommandLine(): MutableList<String> {
- val inputStream: InputStream
- return try {
- inputStream = requireActivity().assets.open("_cl_")
- val len = ByteArray(4)
- var r = inputStream.read(len)
- if (r < 4) {
- return mutableListOf()
- }
- val argc =
- (len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF)
- val cmdline = ArrayList<String>(argc)
- for (i in 0 until argc) {
- r = inputStream.read(len)
- if (r < 4) {
- return mutableListOf()
- }
- val strlen =
- (len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF)
- if (strlen > 65535) {
- return mutableListOf()
- }
- val arg = ByteArray(strlen)
- r = inputStream.read(arg)
- if (r == strlen) {
- cmdline.add(String(arg, StandardCharsets.UTF_8))
- }
- }
- cmdline
- } catch (e: Exception) {
- // The _cl_ file can be missing with no adverse effect
- mutableListOf()
- }
+ return commandLine
}
/**
@@ -1039,7 +1014,7 @@ class Godot(private val context: Context) : SensorEventListener {
@Keep
private fun initInputDevices() {
- renderView!!.initInputDevices()
+ renderView?.initInputDevices()
}
@Keep
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
index e01c5481d5..7b8fad8952 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -83,8 +83,9 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
override fun onDestroy() {
Log.v(TAG, "Destroying Godot app...")
super.onDestroy()
- if (godotFragment != null) {
- terminateGodotInstance(godotFragment!!.godot)
+
+ godotFragment?.let {
+ terminateGodotInstance(it.godot)
}
}
@@ -93,22 +94,26 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
}
private fun terminateGodotInstance(instance: Godot) {
- if (godotFragment != null && instance === godotFragment!!.godot) {
- Log.v(TAG, "Force quitting Godot instance")
- ProcessPhoenix.forceQuit(this)
+ godotFragment?.let {
+ if (instance === it.godot) {
+ Log.v(TAG, "Force quitting Godot instance")
+ ProcessPhoenix.forceQuit(this)
+ }
}
}
override fun onGodotRestartRequested(instance: Godot) {
runOnUiThread {
- if (godotFragment != null && instance === godotFragment!!.godot) {
- // It's very hard to properly de-initialize Godot on Android to restart the game
- // from scratch. Therefore, we need to kill the whole app process and relaunch it.
- //
- // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
- // releasing and reloading native libs or resetting their state somehow and clearing static data).
- Log.v(TAG, "Restarting Godot instance...")
- ProcessPhoenix.triggerRebirth(this)
+ godotFragment?.let {
+ if (instance === it.godot) {
+ // It's very hard to properly de-initialize Godot on Android to restart the game
+ // from scratch. Therefore, we need to kill the whole app process and relaunch it.
+ //
+ // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
+ // releasing and reloading native libs or resetting their state somehow and clearing static data).
+ Log.v(TAG, "Restarting Godot instance...")
+ ProcessPhoenix.triggerRebirth(this)
+ }
}
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
index ef97aaeab9..bd8c58ad69 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
@@ -1673,7 +1673,24 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
mWantRenderNotification = true;
mRequestRender = true;
mRenderComplete = false;
- mFinishDrawingRunnable = finishDrawing;
+
+ // fix lost old callback when continuous call requestRenderAndNotify
+ //
+ // If continuous call requestRenderAndNotify before trigger old
+ // callback, old callback will lose, cause VRI will wait for SV's
+ // draw to finish forever not calling finishDraw.
+ // https://android.googlesource.com/platform/frameworks/base/+/044fce0b826f2da3a192aac56785b5089143e693%5E%21/
+ //+++++++++++++++++++++++++++++++++++++++++++++++++++
+ final Runnable oldCallback = mFinishDrawingRunnable;
+ mFinishDrawingRunnable = () -> {
+ if (oldCallback != null) {
+ oldCallback.run();
+ }
+ if (finishDrawing != null) {
+ finishDrawing.run();
+ }
+ };
+ //----------------------------------------------------
sGLThreadManager.notifyAll();
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
index 0f447f0b05..11cf7b3566 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
@@ -36,7 +36,9 @@ import android.util.Log
import org.godotengine.godot.io.StorageScope
import java.io.IOException
import java.nio.ByteBuffer
+import java.nio.channels.ClosedChannelException
import java.nio.channels.FileChannel
+import java.nio.channels.NonWritableChannelException
import kotlin.math.max
/**
@@ -135,6 +137,21 @@ internal abstract class DataAccess(private val filePath: String) {
seek(positionFromBeginning)
}
+ fun resize(length: Long): Int {
+ return try {
+ fileChannel.truncate(length)
+ FileErrors.OK.nativeValue
+ } catch (e: NonWritableChannelException) {
+ FileErrors.FILE_CANT_OPEN.nativeValue
+ } catch (e: ClosedChannelException) {
+ FileErrors.FILE_CANT_OPEN.nativeValue
+ } catch (e: IllegalArgumentException) {
+ FileErrors.INVALID_PARAMETER.nativeValue
+ } catch (e: IOException) {
+ FileErrors.FAILED.nativeValue
+ }
+ }
+
fun position(): Long {
return try {
fileChannel.position()
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
index 984bf607d0..1d773467e8 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
@@ -45,7 +45,6 @@ class FileAccessHandler(val context: Context) {
companion object {
private val TAG = FileAccessHandler::class.java.simpleName
- private const val FILE_NOT_FOUND_ERROR_ID = -1
internal const val INVALID_FILE_ID = 0
private const val STARTING_FILE_ID = 1
@@ -56,7 +55,9 @@ class FileAccessHandler(val context: Context) {
}
return try {
- DataAccess.fileExists(storageScope, context, path!!)
+ path?.let {
+ DataAccess.fileExists(storageScope, context, it)
+ } ?: false
} catch (e: SecurityException) {
false
}
@@ -69,20 +70,22 @@ class FileAccessHandler(val context: Context) {
}
return try {
- DataAccess.removeFile(storageScope, context, path!!)
+ path?.let {
+ DataAccess.removeFile(storageScope, context, it)
+ } ?: false
} catch (e: Exception) {
false
}
}
- internal fun renameFile(context: Context, storageScopeIdentifier: StorageScope.Identifier, from: String?, to: String?): Boolean {
+ internal fun renameFile(context: Context, storageScopeIdentifier: StorageScope.Identifier, from: String, to: String): Boolean {
val storageScope = storageScopeIdentifier.identifyStorageScope(from)
if (storageScope == StorageScope.UNKNOWN) {
return false
}
return try {
- DataAccess.renameFile(storageScope, context, from!!, to!!)
+ DataAccess.renameFile(storageScope, context, from, to)
} catch (e: Exception) {
false
}
@@ -106,16 +109,18 @@ class FileAccessHandler(val context: Context) {
return INVALID_FILE_ID
}
- try {
- val dataAccess = DataAccess.generateDataAccess(storageScope, context, path!!, accessFlag) ?: return INVALID_FILE_ID
+ return try {
+ path?.let {
+ val dataAccess = DataAccess.generateDataAccess(storageScope, context, it, accessFlag) ?: return INVALID_FILE_ID
- files.put(++lastFileId, dataAccess)
- return lastFileId
+ files.put(++lastFileId, dataAccess)
+ lastFileId
+ } ?: INVALID_FILE_ID
} catch (e: FileNotFoundException) {
- return FILE_NOT_FOUND_ERROR_ID
+ FileErrors.FILE_NOT_FOUND.nativeValue
} catch (e: Exception) {
Log.w(TAG, "Error while opening $path", e)
- return INVALID_FILE_ID
+ INVALID_FILE_ID
}
}
@@ -176,12 +181,22 @@ class FileAccessHandler(val context: Context) {
}
return try {
- DataAccess.fileLastModified(storageScope, context, filepath!!)
+ filepath?.let {
+ DataAccess.fileLastModified(storageScope, context, it)
+ } ?: 0L
} catch (e: SecurityException) {
0L
}
}
+ fun fileResize(fileId: Int, length: Long): Int {
+ if (!hasFileId(fileId)) {
+ return FileErrors.FAILED.nativeValue
+ }
+
+ return files[fileId].resize(length)
+ }
+
fun fileGetPosition(fileId: Int): Long {
if (!hasFileId(fileId)) {
return 0L
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt
new file mode 100644
index 0000000000..2df0195de7
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileErrors.kt
@@ -0,0 +1,53 @@
+/**************************************************************************/
+/* FileErrors.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.io.file
+
+/**
+ * Set of errors that may occur when performing data access.
+ */
+internal enum class FileErrors(val nativeValue: Int) {
+ OK(0),
+ FAILED(-1),
+ FILE_NOT_FOUND(-2),
+ FILE_CANT_OPEN(-3),
+ INVALID_PARAMETER(-4);
+
+ companion object {
+ fun fromNativeError(error: Int): FileErrors? {
+ for (fileError in entries) {
+ if (fileError.nativeValue == error) {
+ return fileError
+ }
+ }
+ return null
+ }
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt
new file mode 100644
index 0000000000..ce5c5b6714
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt
@@ -0,0 +1,83 @@
+/**************************************************************************/
+/* CommandLineFileParser.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.utils
+
+import java.io.InputStream
+import java.nio.charset.StandardCharsets
+import java.util.ArrayList
+
+/**
+ * A class that parses the content of file storing command line params. Usually, this file is saved
+ * in `assets/_cl_` on exporting an apk
+ *
+ * Returns a mutable list of command lines
+ */
+internal class CommandLineFileParser {
+ fun parseCommandLine(inputStream: InputStream): MutableList<String> {
+ return try {
+ val headerBytes = ByteArray(4)
+ var argBytes = inputStream.read(headerBytes)
+ if (argBytes < 4) {
+ return mutableListOf()
+ }
+ val argc = decodeHeaderIntValue(headerBytes)
+
+ val cmdline = ArrayList<String>(argc)
+ for (i in 0 until argc) {
+ argBytes = inputStream.read(headerBytes)
+ if (argBytes < 4) {
+ return mutableListOf()
+ }
+ val strlen = decodeHeaderIntValue(headerBytes)
+
+ if (strlen > 65535) {
+ return mutableListOf()
+ }
+
+ val arg = ByteArray(strlen)
+ argBytes = inputStream.read(arg)
+ if (argBytes == strlen) {
+ cmdline.add(String(arg, StandardCharsets.UTF_8))
+ }
+ }
+ cmdline
+ } catch (e: Exception) {
+ // The _cl_ file can be missing with no adverse effect
+ mutableListOf()
+ }
+ }
+
+ private fun decodeHeaderIntValue(headerBytes: ByteArray): Int =
+ (headerBytes[3].toInt() and 0xFF) shl 24 or
+ ((headerBytes[2].toInt() and 0xFF) shl 16) or
+ ((headerBytes[1].toInt() and 0xFF) shl 8) or
+ (headerBytes[0].toInt() and 0xFF)
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index 737b4ac20b..9df890e6bd 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -41,12 +41,16 @@ import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -67,12 +71,74 @@ public final class PermissionsUtil {
}
/**
- * Request a dangerous permission. name must be specified in <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/AndroidManifest.xml">this</a>
- * @param permissionName the name of the requested permission.
+ * Request a list of dangerous permissions. The requested permissions must be included in the app's AndroidManifest
+ * @param permissions list of the permissions to request.
+ * @param activity the caller activity for this method.
+ * @return true/false. "true" if permissions are already granted, "false" if a permissions request was dispatched.
+ */
+ public static boolean requestPermissions(Activity activity, List<String> permissions) {
+ if (activity == null) {
+ return false;
+ }
+
+ if (permissions == null || permissions.isEmpty()) {
+ return true;
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ // Not necessary, asked on install already
+ return true;
+ }
+
+ Set<String> requestedPermissions = new HashSet<>();
+ for (String permission : permissions) {
+ try {
+ if (permission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
+ Log.d(TAG, "Requesting permission " + permission);
+ try {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+ intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
+ activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE);
+ } catch (Exception ignored) {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
+ activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE);
+ }
+ }
+ } else {
+ PermissionInfo permissionInfo = getPermissionInfo(activity, permission);
+ int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
+ if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Requesting permission " + permission);
+ requestedPermissions.add(permission);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Skip this permission and continue.
+ Log.w(TAG, "Unable to identify permission " + permission, e);
+ }
+ }
+
+ if (requestedPermissions.isEmpty()) {
+ // If list is empty, all of dangerous permissions were granted.
+ return true;
+ }
+
+ activity.requestPermissions(requestedPermissions.toArray(new String[0]), REQUEST_ALL_PERMISSION_REQ_CODE);
+ return true;
+ }
+
+ /**
+ * Request a dangerous permission. The requested permission must be included in the app's AndroidManifest
+ * @param permissionName the name of the permission to request.
* @param activity the caller activity for this method.
* @return true/false. "true" if permission is already granted, "false" if a permission request was dispatched.
*/
public static boolean requestPermission(String permissionName, Activity activity) {
+ if (activity == null || TextUtils.isEmpty(permissionName)) {
+ return false;
+ }
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Not necessary, asked on install already
return true;
@@ -137,11 +203,15 @@ public final class PermissionsUtil {
* @return true/false. "true" if all permissions were already granted, returns "false" if permissions requests were dispatched.
*/
public static boolean requestManifestPermissions(Activity activity, @Nullable Set<String> excludes) {
+ if (activity == null) {
+ return false;
+ }
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
- String[] manifestPermissions;
+ List<String> manifestPermissions;
try {
manifestPermissions = getManifestPermissions(activity);
} catch (PackageManager.NameNotFoundException e) {
@@ -149,48 +219,17 @@ public final class PermissionsUtil {
return false;
}
- if (manifestPermissions.length == 0)
+ if (manifestPermissions.isEmpty()) {
return true;
-
- List<String> requestedPermissions = new ArrayList<>();
- for (String manifestPermission : manifestPermissions) {
- if (excludes != null && excludes.contains(manifestPermission)) {
- continue;
- }
- try {
- if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
- Log.d(TAG, "Requesting permission " + manifestPermission);
- try {
- Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
- intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
- activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE);
- } catch (Exception ignored) {
- Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
- activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE);
- }
- }
- } else {
- PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission);
- int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
- if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) {
- Log.d(TAG, "Requesting permission " + manifestPermission);
- requestedPermissions.add(manifestPermission);
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Skip this permission and continue.
- Log.w(TAG, "Unable to identify permission " + manifestPermission, e);
- }
}
- if (requestedPermissions.isEmpty()) {
- // If list is empty, all of dangerous permissions were granted.
- return true;
+ if (excludes != null && !excludes.isEmpty()) {
+ for (String excludedPermission : excludes) {
+ manifestPermissions.remove(excludedPermission);
+ }
}
- activity.requestPermissions(requestedPermissions.toArray(new String[0]), REQUEST_ALL_PERMISSION_REQ_CODE);
- return false;
+ return requestPermissions(activity, manifestPermissions);
}
/**
@@ -199,15 +238,16 @@ public final class PermissionsUtil {
* @return granted permissions list
*/
public static String[] getGrantedPermissions(Context context) {
- String[] manifestPermissions;
+ List<String> manifestPermissions;
try {
manifestPermissions = getManifestPermissions(context);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return new String[0];
}
- if (manifestPermissions.length == 0)
- return manifestPermissions;
+ if (manifestPermissions.isEmpty()) {
+ return new String[0];
+ }
List<String> grantedPermissions = new ArrayList<>();
for (String manifestPermission : manifestPermissions) {
@@ -253,15 +293,15 @@ public final class PermissionsUtil {
/**
* Returns the permissions defined in the AndroidManifest.xml file.
* @param context the caller context for this method.
- * @return manifest permissions list
+ * @return mutable copy of manifest permissions list
* @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found.
*/
- private static String[] getManifestPermissions(Context context) throws PackageManager.NameNotFoundException {
+ public static ArrayList<String> getManifestPermissions(Context context) throws PackageManager.NameNotFoundException {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
if (packageInfo.requestedPermissions == null)
- return new String[0];
- return packageInfo.requestedPermissions;
+ return new ArrayList<String>();
+ return new ArrayList<>(Arrays.asList(packageInfo.requestedPermissions));
}
/**
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
index 4aba0c370d..8c0065b31e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
@@ -142,7 +142,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
fun onSurfaceChanged(width: Int, height: Int) {
lock.withLock {
hasSurface = true
- surfaceChanged = true;
+ surfaceChanged = true
this.width = width
this.height = height
@@ -179,7 +179,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
// blocking the thread lifecycle by holding onto the lock.
if (eventQueue.isNotEmpty()) {
event = eventQueue.removeAt(0)
- break;
+ break
}
if (readyToDraw) {
@@ -199,7 +199,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
}
// Break out of the loop so drawing can occur without holding onto the lock.
- break;
+ break
} else if (rendererResumed) {
// If we aren't ready to draw but are resumed, that means we either lost a surface
// or the app was paused.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
index 01ee41e30b..1f0d8592b3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
@@ -66,11 +66,15 @@ public class RegularContextFactory implements GLSurfaceView.EGLContextFactory {
GLUtils.checkEglError(TAG, "Before eglCreateContext", egl);
EGLContext context;
+ int[] debug_attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
+ int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE };
if (mUseDebugOpengl) {
- int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
- context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+ context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, debug_attrib_list);
+ if (context == null || context == EGL10.EGL_NO_CONTEXT) {
+ Log.w(TAG, "creating 'OpenGL Debug' context failed");
+ context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+ }
} else {
- int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE };
context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
}
GLUtils.checkEglError(TAG, "After eglCreateContext", egl);
diff --git a/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt b/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt
new file mode 100644
index 0000000000..8b0466848a
--- /dev/null
+++ b/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt
@@ -0,0 +1,104 @@
+/**************************************************************************/
+/* CommandLineFileParserTest.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.utils
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+
+// Godot saves command line params in the `assets/_cl_` file on exporting an apk. By default,
+// without any other commands specified in `command_line/extra_args` in Export window, the content
+// of that _cl_ file consists of only the `--xr_mode_regular` and `--use_immersive` flags.
+// The `CL_` prefix here refers to that file
+private val CL_DEFAULT_NO_EXTRA_ARGS = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+private val CL_ONE_EXTRA_ARG = byteArrayOf(3, 0, 0, 0, 15, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+private val CL_TWO_EXTRA_ARGS = byteArrayOf(4, 0, 0, 0, 16, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 49, 16, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 50, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+private val CL_EMPTY = byteArrayOf()
+private val CL_HEADER_TOO_SHORT = byteArrayOf(0, 0, 0)
+private val CL_INCOMPLETE_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0)
+private val CL_LENGTH_TOO_LONG_IN_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+private val CL_MISMATCHED_ARG_LENGTH_AND_HEADER_ONE_ARG = byteArrayOf(2, 0, 0, 0, 10, 0, 0, 0, 45, 45, 120, 114)
+private val CL_MISMATCHED_ARG_LENGTH_AND_HEADER_IN_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101)
+
+@RunWith(Parameterized::class)
+class CommandLineFileParserTest(
+ private val inputStreamArg: InputStream,
+ private val expectedResult: List<String>,
+) {
+
+ private val commandLineFileParser = CommandLineFileParser()
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data() = listOf(
+ arrayOf(ByteArrayInputStream(CL_EMPTY), listOf<String>()),
+ arrayOf(ByteArrayInputStream(CL_HEADER_TOO_SHORT), listOf<String>()),
+
+ arrayOf(ByteArrayInputStream(CL_DEFAULT_NO_EXTRA_ARGS), listOf(
+ "--xr_mode_regular",
+ "--use_immersive",
+ )),
+
+ arrayOf(ByteArrayInputStream(CL_ONE_EXTRA_ARG), listOf(
+ "--unit_test_arg",
+ "--xr_mode_regular",
+ "--use_immersive",
+ )),
+
+ arrayOf(ByteArrayInputStream(CL_TWO_EXTRA_ARGS), listOf(
+ "--unit_test_arg1",
+ "--unit_test_arg2",
+ "--xr_mode_regular",
+ "--use_immersive",
+ )),
+
+ arrayOf(ByteArrayInputStream(CL_INCOMPLETE_FIRST_ARG), listOf<String>()),
+ arrayOf(ByteArrayInputStream(CL_LENGTH_TOO_LONG_IN_FIRST_ARG), listOf<String>()),
+ arrayOf(ByteArrayInputStream(CL_MISMATCHED_ARG_LENGTH_AND_HEADER_ONE_ARG), listOf<String>()),
+ arrayOf(ByteArrayInputStream(CL_MISMATCHED_ARG_LENGTH_AND_HEADER_IN_FIRST_ARG), listOf<String>()),
+ )
+ }
+
+ @Test
+ fun `Given inputStream, When parsing command line, Then a correct list is returned`() {
+ // given
+ val inputStream = inputStreamArg
+
+ // when
+ val result = commandLineFileParser.parseCommandLine(inputStream)
+
+ // then
+ assert(result == expectedResult) { "Expected: $expectedResult Actual: $result" }
+ }
+}
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index d6455cbf1c..a309a6ab74 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -1157,50 +1157,54 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL(env);
- jclass activity = env->FindClass("android/app/Activity");
- jmethodID getClassLoader = env->GetMethodID(activity, "getClassLoader", "()Ljava/lang/ClassLoader;");
- classLoader = env->CallObjectMethod(p_activity, getClassLoader);
- classLoader = (jclass)env->NewGlobalRef(classLoader);
- jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
- findClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
-
jclass bclass = env->FindClass("java/lang/Class");
getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/reflect/Method");
getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;");
getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/reflect/Field");
Field_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
Field_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
Field_get = env->GetMethodID(bclass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Boolean");
Boolean_booleanValue = env->GetMethodID(bclass, "booleanValue", "()Z");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Byte");
Byte_byteValue = env->GetMethodID(bclass, "byteValue", "()B");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Character");
Character_characterValue = env->GetMethodID(bclass, "charValue", "()C");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Short");
Short_shortValue = env->GetMethodID(bclass, "shortValue", "()S");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Integer");
Integer_integerValue = env->GetMethodID(bclass, "intValue", "()I");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Long");
Long_longValue = env->GetMethodID(bclass, "longValue", "()J");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Float");
Float_floatValue = env->GetMethodID(bclass, "floatValue", "()F");
+ env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/Double");
Double_doubleValue = env->GetMethodID(bclass, "doubleValue", "()D");
+ env->DeleteLocalRef(bclass);
}
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 10716a5c79..49913b9c30 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -70,7 +70,11 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
}
GodotIOJavaWrapper::~GodotIOJavaWrapper() {
- // nothing to do here for now
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(godot_io_instance);
}
jobject GodotIOJavaWrapper::get_instance() {
@@ -82,7 +86,9 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, ERR_UNAVAILABLE);
jstring jStr = env->NewStringUTF(p_uri.utf8().get_data());
- return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK;
+ Error result = env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK;
+ env->DeleteLocalRef(jStr);
+ return result;
} else {
return ERR_UNAVAILABLE;
}
@@ -220,6 +226,7 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_type, int p_max
ERR_FAIL_NULL(env);
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_type, p_max_input_length, p_cursor_start, p_cursor_end);
+ env->DeleteLocalRef(jStr);
}
}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 85d5cf2796..6cab7e74fd 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -95,6 +95,13 @@ static void _terminate(JNIEnv *env, bool p_restart = false) {
if (godot_io_java) {
delete godot_io_java;
}
+
+ TTS_Android::terminate();
+ FileAccessAndroid::terminate();
+ DirAccessJAndroid::terminate();
+ FileAccessFilesystemJAndroid::terminate();
+ NetSocketAndroid::terminate();
+
if (godot_java) {
if (!restart_on_cleanup) {
if (p_restart) {
@@ -125,10 +132,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv
init_thread_jandroid(jvm, env);
- jobject amgr = env->NewGlobalRef(p_asset_manager);
-
- FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr);
-
+ FileAccessAndroid::setup(p_asset_manager);
DirAccessJAndroid::setup(p_directory_access_handler);
FileAccessFilesystemJAndroid::setup(p_file_access_handler);
NetSocketAndroid::setup(p_net_utils);
@@ -250,7 +254,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
}
if (step.get() == 1) {
- if (!Main::start()) {
+ if (Main::start() != EXIT_SUCCESS) {
return true; // should exit instead and print the error
}
diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp
index a95f762e01..04424c1179 100644
--- a/platform/android/java_godot_view_wrapper.cpp
+++ b/platform/android/java_godot_view_wrapper.cpp
@@ -95,6 +95,7 @@ void GodotJavaViewWrapper::configure_pointer_icon(int pointer_type, const String
jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data());
env->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y);
+ env->DeleteLocalRef(jImagePath);
}
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 3c950bb1b1..61be6fc5db 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -172,6 +172,8 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) {
jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data());
jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data());
env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle);
+ env->DeleteLocalRef(jStrMessage);
+ env->DeleteLocalRef(jStrTitle);
}
}
@@ -231,6 +233,7 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) {
ERR_FAIL_NULL(env);
jstring jStr = env->NewStringUTF(p_text.utf8().get_data());
env->CallVoidMethod(godot_instance, _set_clipboard, jStr);
+ env->DeleteLocalRef(jStr);
}
}
@@ -253,7 +256,9 @@ bool GodotJavaWrapper::request_permission(const String &p_name) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, false);
jstring jStrName = env->NewStringUTF(p_name.utf8().get_data());
- return env->CallBooleanMethod(godot_instance, _request_permission, jStrName);
+ bool result = env->CallBooleanMethod(godot_instance, _request_permission, jStrName);
+ env->DeleteLocalRef(jStrName);
+ return result;
} else {
return false;
}
@@ -340,7 +345,9 @@ int GodotJavaWrapper::create_new_godot_instance(const List<String> &args) {
ERR_FAIL_NULL_V(env, 0);
jobjectArray jargs = env->NewObjectArray(args.size(), env->FindClass("java/lang/String"), env->NewStringUTF(""));
for (int i = 0; i < args.size(); i++) {
- env->SetObjectArrayElement(jargs, i, env->NewStringUTF(args[i].utf8().get_data()));
+ jstring j_arg = env->NewStringUTF(args[i].utf8().get_data());
+ env->SetObjectArrayElement(jargs, i, j_arg);
+ env->DeleteLocalRef(j_arg);
}
return env->CallIntMethod(godot_instance, _create_new_godot_instance, jargs);
} else {
@@ -355,6 +362,8 @@ void GodotJavaWrapper::begin_benchmark_measure(const String &p_context, const St
jstring j_context = env->NewStringUTF(p_context.utf8().get_data());
jstring j_label = env->NewStringUTF(p_label.utf8().get_data());
env->CallVoidMethod(godot_instance, _begin_benchmark_measure, j_context, j_label);
+ env->DeleteLocalRef(j_context);
+ env->DeleteLocalRef(j_label);
}
}
@@ -365,6 +374,8 @@ void GodotJavaWrapper::end_benchmark_measure(const String &p_context, const Stri
jstring j_context = env->NewStringUTF(p_context.utf8().get_data());
jstring j_label = env->NewStringUTF(p_label.utf8().get_data());
env->CallVoidMethod(godot_instance, _end_benchmark_measure, j_context, j_label);
+ env->DeleteLocalRef(j_context);
+ env->DeleteLocalRef(j_label);
}
}
@@ -374,6 +385,7 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) {
ERR_FAIL_NULL(env);
jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data());
env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file);
+ env->DeleteLocalRef(j_benchmark_file);
}
}
@@ -383,7 +395,9 @@ bool GodotJavaWrapper::has_feature(const String &p_feature) const {
ERR_FAIL_NULL_V(env, false);
jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data());
- return env->CallBooleanMethod(godot_instance, _has_feature, j_feature);
+ bool result = env->CallBooleanMethod(godot_instance, _has_feature, j_feature);
+ env->DeleteLocalRef(j_feature);
+ return result;
} else {
return false;
}
diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp
index a2befdc9be..8f0ee51fac 100644
--- a/platform/android/net_socket_android.cpp
+++ b/platform/android/net_socket_android.cpp
@@ -49,6 +49,14 @@ void NetSocketAndroid::setup(jobject p_net_utils) {
_multicast_lock_release = env->GetMethodID(cls, "multicastLockRelease", "()V");
}
+void NetSocketAndroid::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(net_utils);
+}
+
void NetSocketAndroid::multicast_lock_acquire() {
if (_multicast_lock_acquire) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h
index e5f46d3236..26cb2d4e3d 100644
--- a/platform/android/net_socket_android.h
+++ b/platform/android/net_socket_android.h
@@ -63,6 +63,7 @@ protected:
public:
static void make_default();
static void setup(jobject p_net_utils);
+ static void terminate();
virtual void close();
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 82e7fdb320..463a307854 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -162,7 +162,39 @@ Vector<String> OS_Android::get_granted_permissions() const {
return godot_java->get_granted_permissions();
}
-Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
+bool OS_Android::copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path) {
+ if (!FileAccess::exists(p_library_path)) {
+ return false;
+ }
+
+ Ref<DirAccess> da_ref = DirAccess::create_for_path(p_library_path);
+ if (!da_ref.is_valid()) {
+ return false;
+ }
+
+ String copy_path = p_target_dir.path_join(p_library_path.get_file());
+ bool copy_exists = FileAccess::exists(copy_path);
+ if (copy_exists) {
+ print_verbose("Deleting existing library copy " + copy_path);
+ if (da_ref->remove(copy_path) != OK) {
+ print_verbose("Unable to delete " + copy_path);
+ }
+ }
+
+ print_verbose("Copying " + p_library_path + " to " + p_target_dir);
+ Error create_dir_result = da_ref->make_dir_recursive(p_target_dir);
+ if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) {
+ copy_exists = da_ref->copy(p_library_path, copy_path) == OK;
+ }
+
+ if (copy_exists && r_copy_path != nullptr) {
+ *r_copy_path = copy_path;
+ }
+
+ return copy_exists;
+}
+
+Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path;
bool so_file_exists = true;
if (!FileAccess::exists(path)) {
@@ -172,24 +204,32 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
if (!p_library_handle && so_file_exists) {
- // The library may be on the sdcard and thus inaccessible. Try to copy it to the internal
- // directory.
- uint64_t so_modified_time = FileAccess::get_modified_time(p_path);
- String dynamic_library_path = get_dynamic_libraries_path().path_join(String::num_uint64(so_modified_time));
- String internal_path = dynamic_library_path.path_join(p_path.get_file());
-
- bool internal_so_file_exists = FileAccess::exists(internal_path);
- if (!internal_so_file_exists) {
- Ref<DirAccess> da_ref = DirAccess::create_for_path(p_path);
- if (da_ref.is_valid()) {
- Error create_dir_result = da_ref->make_dir_recursive(dynamic_library_path);
- if (create_dir_result == OK || create_dir_result == ERR_ALREADY_EXISTS) {
- internal_so_file_exists = da_ref->copy(path, internal_path) == OK;
+ // The library (and its dependencies) may be on the sdcard and thus inaccessible.
+ // Try to copy to the internal directory for access.
+ const String dynamic_library_path = get_dynamic_libraries_path();
+
+ if (p_data != nullptr && p_data->library_dependencies != nullptr && !p_data->library_dependencies->is_empty()) {
+ // Copy the library dependencies
+ print_verbose("Copying library dependencies..");
+ for (const String &library_dependency_path : *p_data->library_dependencies) {
+ String internal_library_dependency_path;
+ if (!copy_dynamic_library(library_dependency_path, dynamic_library_path.path_join(library_dependency_path.get_base_dir()), &internal_library_dependency_path)) {
+ ERR_PRINT(vformat("Unable to copy library dependency %s", library_dependency_path));
+ } else {
+ void *lib_dependency_handle = dlopen(internal_library_dependency_path.utf8().get_data(), RTLD_NOW);
+ if (!lib_dependency_handle) {
+ ERR_PRINT(vformat("Can't open dynamic library dependency: %s. Error: %s.", internal_library_dependency_path, dlerror()));
+ }
}
}
}
+ String internal_path;
+ print_verbose("Copying library " + p_path);
+ const bool internal_so_file_exists = copy_dynamic_library(p_path, dynamic_library_path.path_join(p_path.get_base_dir()), &internal_path);
+
if (internal_so_file_exists) {
+ print_verbose("Opening library " + internal_path);
p_library_handle = dlopen(internal_path.utf8().get_data(), RTLD_NOW);
if (p_library_handle) {
path = internal_path;
@@ -199,8 +239,8 @@ Error OS_Android::open_dynamic_library(const String &p_path, void *&p_library_ha
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
@@ -736,6 +776,10 @@ void OS_Android::benchmark_dump() {
}
bool OS_Android::_check_internal_feature_support(const String &p_feature) {
+ if (p_feature == "macos" || p_feature == "web_ios" || p_feature == "web_macos" || p_feature == "windows") {
+ return false;
+ }
+
if (p_feature == "system_fonts") {
return true;
}
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index 31ee7389df..7bdbeef77a 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -113,7 +113,7 @@ public:
virtual void alert(const String &p_alert, const String &p_title) override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual String get_name() const override;
virtual String get_distribution_name() const override;
@@ -178,6 +178,8 @@ public:
private:
// Location where we relocate external dynamic libraries to make them accessible.
String get_dynamic_libraries_path() const;
+ // Copy a dynamic library to the given location to make it accessible for loading.
+ bool copy_dynamic_library(const String &p_library_path, const String &p_target_dir, String *r_copy_path = nullptr);
};
#endif // OS_ANDROID_H
diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp
index 93517d8045..be85e47972 100644
--- a/platform/android/tts_android.cpp
+++ b/platform/android/tts_android.cpp
@@ -77,6 +77,14 @@ void TTS_Android::setup(jobject p_tts) {
}
}
+void TTS_Android::terminate() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(cls);
+ env->DeleteGlobalRef(tts);
+}
+
void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) {
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (ids.has(p_id)) {
@@ -170,6 +178,8 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum
jstring jStrT = env->NewStringUTF(p_text.utf8().get_data());
jstring jStrV = env->NewStringUTF(p_voice.utf8().get_data());
env->CallVoidMethod(tts, _speak, jStrT, jStrV, CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, p_interrupt);
+ env->DeleteLocalRef(jStrT);
+ env->DeleteLocalRef(jStrV);
}
}
diff --git a/platform/android/tts_android.h b/platform/android/tts_android.h
index 39efef6ed1..4cc7c12846 100644
--- a/platform/android/tts_android.h
+++ b/platform/android/tts_android.h
@@ -57,6 +57,7 @@ class TTS_Android {
public:
static void setup(jobject p_tts);
+ static void terminate();
static void _java_utterance_callback(int p_event, int p_id, int p_pos);
static bool is_speaking();
diff --git a/platform/ios/SCsub b/platform/ios/SCsub
index 5a57f3840b..f38914434b 100644
--- a/platform/ios/SCsub
+++ b/platform/ios/SCsub
@@ -3,7 +3,7 @@
Import("env")
import os, json
-from platform_methods import run_in_subprocess, architectures, lipo, get_build_version, detect_mvk
+from platform_methods import architectures, lipo, get_build_version, detect_mvk
import subprocess
import shutil
diff --git a/platform/ios/app_delegate.mm b/platform/ios/app_delegate.mm
index 32ebf7be44..37d2696434 100644
--- a/platform/ios/app_delegate.mm
+++ b/platform/ios/app_delegate.mm
@@ -105,11 +105,19 @@ static ViewController *mainViewController = nil;
// Initialize with default Ambient category.
AVAudioSessionCategory category = AVAudioSessionCategoryAmbient;
+ AVAudioSessionCategoryOptions options = 0;
+
+ if (GLOBAL_GET("audio/general/ios/mix_with_others")) {
+ options |= AVAudioSessionCategoryOptionMixWithOthers;
+ }
if (sessionCategorySetting == SESSION_CATEGORY_MULTI_ROUTE) {
category = AVAudioSessionCategoryMultiRoute;
} else if (sessionCategorySetting == SESSION_CATEGORY_PLAY_AND_RECORD) {
category = AVAudioSessionCategoryPlayAndRecord;
+ options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
+ options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
+ options |= AVAudioSessionCategoryOptionAllowAirPlay;
} else if (sessionCategorySetting == SESSION_CATEGORY_PLAYBACK) {
category = AVAudioSessionCategoryPlayback;
} else if (sessionCategorySetting == SESSION_CATEGORY_RECORD) {
@@ -118,11 +126,7 @@ static ViewController *mainViewController = nil;
category = AVAudioSessionCategorySoloAmbient;
}
- if (GLOBAL_GET("audio/general/ios/mix_with_others")) {
- [[AVAudioSession sharedInstance] setCategory:category withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
- } else {
- [[AVAudioSession sharedInstance] setCategory:category error:nil];
- }
+ [[AVAudioSession sharedInstance] setCategory:category withOptions:options error:nil];
return YES;
}
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 4d6e3ae9ba..0c9b7b3204 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -52,6 +52,7 @@ def get_flags():
("target", "template_debug"),
("use_volk", False),
("supported", ["mono"]),
+ ("builtin_pcre2_with_jit", False),
]
@@ -139,7 +140,6 @@ def configure(env: "SConsEnvironment"):
)
)
env.Append(ASFLAGS=["-arch", "arm64"])
- env.Append(CPPDEFINES=["NEED_LONG_INT"])
# Temp fix for ABS/MAX/MIN macros in iOS SDK blocking compilation
env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h
index 6f66783a47..3f9211c572 100644
--- a/platform/ios/display_server_ios.h
+++ b/platform/ios/display_server_ios.h
@@ -65,6 +65,7 @@ class DisplayServerIOS : public DisplayServer {
RenderingContextDriver *rendering_context = nullptr;
RenderingDevice *rendering_device = nullptr;
#endif
+ NativeMenu *native_menu = nullptr;
id tts = nullptr;
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index ed69b91fdd..f84fb01ad0 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -61,6 +61,7 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode
if (tts_enabled) {
tts = [[TTS_IOS alloc] init];
}
+ native_menu = memnew(NativeMenu);
#if defined(RD_ENABLED)
rendering_context = nullptr;
@@ -134,6 +135,11 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode
}
DisplayServerIOS::~DisplayServerIOS() {
+ if (native_menu) {
+ memdelete(native_menu);
+ native_menu = nullptr;
+ }
+
#if defined(RD_ENABLED)
if (rendering_device) {
rendering_device->screen_free(MAIN_WINDOW_ID);
@@ -309,15 +315,21 @@ void DisplayServerIOS::update_gyroscope(float p_x, float p_y, float p_z) {
bool DisplayServerIOS::has_feature(Feature p_feature) const {
switch (p_feature) {
+#ifndef DISABLE_DEPRECATED
+ case FEATURE_GLOBAL_MENU: {
+ return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
+ } break;
+#endif
// case FEATURE_CURSOR_SHAPE:
// case FEATURE_CUSTOM_CURSOR_SHAPE:
- // case FEATURE_GLOBAL_MENU:
// case FEATURE_HIDPI:
// case FEATURE_ICON:
// case FEATURE_IME:
// case FEATURE_MOUSE:
// case FEATURE_MOUSE_WARP:
// case FEATURE_NATIVE_DIALOG:
+ // case FEATURE_NATIVE_DIALOG_INPUT:
+ // case FEATURE_NATIVE_DIALOG_FILE:
// case FEATURE_NATIVE_ICON:
// case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
index 35ef6d6a78..0c0ded5fea 100644
--- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml
+++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
@@ -12,7 +12,7 @@
<members>
<member name="application/additional_plist_content" type="String" setter="" getter="">
Additional data added to the root [code]&lt;dict&gt;[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.:
- [codeblock]
+ [codeblock lang=text]
&lt;key&gt;key_name&lt;/key&gt;
&lt;string&gt;value&lt;/string&gt;
[/codeblock]
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index ea2b23cfb9..33389129b7 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -127,12 +127,19 @@ String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPres
void EditorExportPlatformIOS::_notification(int p_what) {
#ifdef MACOS_ENABLED
if (p_what == NOTIFICATION_POSTINITIALIZE) {
- EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformIOS::_update_preset_status));
+ if (EditorExport::get_singleton()) {
+ EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformIOS::_update_preset_status));
+ }
}
#endif
}
bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {
+ // Hide unsupported .NET embedding option.
+ if (p_option == "dotnet/embed_build_outputs") {
+ return false;
+ }
+
return true;
}
@@ -414,6 +421,10 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
} else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) {
String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };";
strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n";
+#ifndef DISABLE_DEPRECATED
+ } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) {
+ strnew += lines[i].replace("$pbx_launch_image_usage_setting", "") + "\n";
+#endif
} else if (lines[i].find("$launch_screen_image_mode") != -1) {
int image_scale_mode = p_preset->get("storyboard/image_scale_mode");
String value;
@@ -1310,7 +1321,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const Ref<EditorExportP
if (asset.begins_with("res://")) {
Error err = _copy_asset(p_preset, p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
ERR_FAIL_COND_V(err != OK, err);
- } else if (ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) {
+ } else if (asset.is_absolute_path() && ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) {
Error err = _copy_asset(p_preset, p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets);
ERR_FAIL_COND_V(err != OK, err);
} else {
diff --git a/platform/ios/godot_ios.mm b/platform/ios/godot_ios.mm
index 5e66c8b47b..9d35d43344 100644
--- a/platform/ios/godot_ios.mm
+++ b/platform/ios/godot_ios.mm
@@ -102,15 +102,16 @@ int ios_main(int argc, char **argv) {
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
- if (err == ERR_HELP) { // Returned by --help and --version, so success.
- return 0;
- } else if (err != OK) {
- return 255;
+ if (err != OK) {
+ if (err == ERR_HELP) { // Returned by --help and --version, so success.
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
}
os->initialize_modules();
- return 0;
+ return os->get_exit_code();
}
void ios_finish() {
diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h
index 10ecd08a89..c4782a4768 100644
--- a/platform/ios/os_ios.h
+++ b/platform/ios/os_ios.h
@@ -103,7 +103,7 @@ public:
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;
diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm
index 23614af366..52d496d641 100644
--- a/platform/ios/os_ios.mm
+++ b/platform/ios/os_ios.mm
@@ -149,10 +149,6 @@ void OS_IOS::deinitialize_modules() {
void OS_IOS::set_main_loop(MainLoop *p_main_loop) {
main_loop = p_main_loop;
-
- if (main_loop) {
- main_loop->initialize();
- }
}
MainLoop *OS_IOS::get_main_loop() const {
@@ -181,7 +177,9 @@ bool OS_IOS::iterate() {
}
void OS_IOS::start() {
- Main::start();
+ if (Main::start() == EXIT_SUCCESS) {
+ main_loop->initialize();
+ }
if (joypad_ios) {
joypad_ios->start_processing();
@@ -219,13 +217,13 @@ _FORCE_INLINE_ String OS_IOS::get_framework_executable(const String &p_path) {
return p_path;
}
-Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
+Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
if (p_path.length() == 0) {
// Static xcframework.
p_library_handle = RTLD_SELF;
- if (r_resolved_path != nullptr) {
- *r_resolved_path = p_path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = p_path;
}
return OK;
@@ -258,8 +256,8 @@ Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
@@ -361,7 +359,7 @@ String OS_IOS::get_unique_id() const {
String OS_IOS::get_processor_name() const {
char buffer[256];
size_t buffer_len = 256;
- if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) {
+ if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) {
return String::utf8(buffer, buffer_len);
}
ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub
index a3ce773ac2..0802b528f4 100644
--- a/platform/linuxbsd/SCsub
+++ b/platform/linuxbsd/SCsub
@@ -2,7 +2,6 @@
Import("env")
-from platform_methods import run_in_subprocess
import platform_linuxbsd_builders
common_linuxbsd = [
@@ -42,4 +41,4 @@ if env["dbus"]:
prog = env.add_program("#bin/godot", ["godot_linuxbsd.cpp"] + common_linuxbsd)
if env["debug_symbols"] and env["separate_debug_symbols"]:
- env.AddPostAction(prog, run_in_subprocess(platform_linuxbsd_builders.make_debug_linuxbsd))
+ env.AddPostAction(prog, env.Run(platform_linuxbsd_builders.make_debug_linuxbsd))
diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp
index fd4bcf92be..446fe5c7a1 100644
--- a/platform/linuxbsd/crash_handler_linuxbsd.cpp
+++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp
@@ -36,8 +36,8 @@
#include "core/version.h"
#include "main/main.h"
-#ifdef DEBUG_ENABLED
-#define CRASH_HANDLER_ENABLED 1
+#ifndef DEBUG_ENABLED
+#undef CRASH_HANDLER_ENABLED
#endif
#ifdef CRASH_HANDLER_ENABLED
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index 4856076436..27dec73b65 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -50,7 +50,7 @@ def get_opts():
BoolVariable("wayland", "Enable Wayland display", True),
BoolVariable("libdecor", "Enable libdecor support", True),
BoolVariable("touch", "Enable touch events", True),
- BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False),
+ BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", None),
]
@@ -488,14 +488,20 @@ def configure(env: "SConsEnvironment"):
if platform.system() == "Linux":
env.Append(LIBS=["dl"])
- if not env["execinfo"] and platform.libc_ver()[0] != "glibc":
+ if platform.libc_ver()[0] != "glibc":
# The default crash handler depends on glibc, so if the host uses
# a different libc (BSD libc, musl), fall back to libexecinfo.
- print("Note: Using `execinfo=yes` for the crash handler as required on platforms where glibc is missing.")
- env["execinfo"] = True
+ if not "execinfo" in env:
+ print("Note: Using `execinfo=yes` for the crash handler as required on platforms where glibc is missing.")
+ env["execinfo"] = True
- if env["execinfo"]:
- env.Append(LIBS=["execinfo"])
+ if env["execinfo"]:
+ env.Append(LIBS=["execinfo"])
+ env.Append(CPPDEFINES=["CRASH_HANDLER_ENABLED"])
+ else:
+ print("Note: Using `execinfo=no` disables the crash handler on platforms where glibc is missing.")
+ else:
+ env.Append(CPPDEFINES=["CRASH_HANDLER_ENABLED"])
if platform.system() == "FreeBSD":
env.Append(LINKFLAGS=["-lkvm"])
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
index 773b124c6a..936adddda3 100644
--- a/platform/linuxbsd/export/export_plugin.cpp
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -146,12 +146,19 @@ List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<Edito
}
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;
- }
+ if (p_preset == nullptr) {
+ return true;
+ }
+
+ bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
+
+ // 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;
+ }
+ if (p_option == "dotnet/embed_build_outputs") {
+ return advanced_options_enabled;
}
return true;
}
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index cdebed58b2..214725832f 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -367,6 +367,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
}
ERR_FAIL_INDEX_V(int(p_mode), DisplayServer::FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+ ERR_FAIL_NULL_V(monitor_connection, FAILED);
Vector<String> filter_names;
Vector<String> filter_exts;
@@ -406,24 +407,16 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
Error rng_err = rng.get_random_bytes(uuid, 64);
ERR_FAIL_COND_V_MSG(rng_err, rng_err, "Failed to generate unique token.");
- fd.connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
- if (dbus_error_is_set(&err)) {
- ERR_PRINT(vformat("Failed to open DBus connection: %s", err.message));
- dbus_error_free(&err);
- unsupported = true;
- return FAILED;
- }
-
- String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(fd.connection));
+ String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(monitor_connection));
String token = String::hex_encode_buffer(uuid, 64);
String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace(".", "_").replace(":", ""), token);
- fd.path = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name);
- dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err);
+ fd.path = path;
+ fd.filter = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name);
+ dbus_bus_add_match(monitor_connection, fd.filter.utf8().get_data(), &err);
if (dbus_error_is_set(&err)) {
ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));
dbus_error_free(&err);
- dbus_connection_unref(fd.connection);
return FAILED;
}
@@ -460,14 +453,13 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
dbus_message_iter_close_container(&iter, &arr_iter);
}
- DBusMessage *reply = dbus_connection_send_with_reply_and_block(fd.connection, message, DBUS_TIMEOUT_INFINITE, &err);
+ DBusMessage *reply = dbus_connection_send_with_reply_and_block(monitor_connection, message, DBUS_TIMEOUT_INFINITE, &err);
dbus_message_unref(message);
if (!reply || dbus_error_is_set(&err)) {
ERR_PRINT(vformat("Failed to send DBus message: %s", err.message));
dbus_error_free(&err);
- dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err);
- dbus_connection_unref(fd.connection);
+ dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);
return FAILED;
}
@@ -479,19 +471,17 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
const char *new_path = nullptr;
dbus_message_iter_get_basic(&iter, &new_path);
if (String::utf8(new_path) != path) {
- dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err);
+ dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);
if (dbus_error_is_set(&err)) {
ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message));
dbus_error_free(&err);
- dbus_connection_unref(fd.connection);
return FAILED;
}
- fd.path = String::utf8(new_path);
- dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err);
+ fd.filter = String::utf8(new_path);
+ dbus_bus_add_match(monitor_connection, fd.filter.utf8().get_data(), &err);
if (dbus_error_is_set(&err)) {
ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));
dbus_error_free(&err);
- dbus_connection_unref(fd.connection);
return FAILED;
}
}
@@ -532,57 +522,9 @@ void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) {
FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud;
while (!portal->monitor_thread_abort.is_set()) {
- {
- MutexLock lock(portal->file_dialog_mutex);
- for (int i = portal->file_dialogs.size() - 1; i >= 0; i--) {
- bool remove = false;
- {
- FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i];
- if (fd.connection) {
- while (true) {
- DBusMessage *msg = dbus_connection_pop_message(fd.connection);
- if (!msg) {
- break;
- } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
- DBusMessageIter iter;
- if (dbus_message_iter_init(msg, &iter)) {
- bool cancel = false;
- Vector<String> uris;
- Dictionary options;
- int index = 0;
- file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index, options);
-
- if (fd.callback.is_valid()) {
- callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index, options, fd.opt_in_cb);
- }
- if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
- callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
- }
- }
- dbus_message_unref(msg);
-
- DBusError err;
- dbus_error_init(&err);
- dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err);
- dbus_error_free(&err);
- dbus_connection_unref(fd.connection);
- remove = true;
- break;
- }
- dbus_message_unref(msg);
- }
- dbus_connection_read_write(fd.connection, 0);
- }
- }
- if (remove) {
- portal->file_dialogs.remove_at(i);
- }
- }
- }
-
- if (portal->theme_connection) {
+ if (portal->monitor_connection) {
while (true) {
- DBusMessage *msg = dbus_connection_pop_message(portal->theme_connection);
+ DBusMessage *msg = dbus_connection_pop_message(portal->monitor_connection);
if (!msg) {
break;
} else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Settings", "SettingChanged")) {
@@ -599,12 +541,41 @@ void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) {
callable_mp(portal, &FreeDesktopPortalDesktop::_system_theme_changed_callback).call_deferred();
}
}
- dbus_message_unref(msg);
- break;
+ } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
+ String path = String::utf8(dbus_message_get_path(msg));
+ MutexLock lock(portal->file_dialog_mutex);
+ for (int i = 0; i < portal->file_dialogs.size(); i++) {
+ FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i];
+ if (fd.path == path) {
+ DBusMessageIter iter;
+ if (dbus_message_iter_init(msg, &iter)) {
+ bool cancel = false;
+ Vector<String> uris;
+ Dictionary options;
+ int index = 0;
+ file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index, options);
+
+ if (fd.callback.is_valid()) {
+ callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index, options, fd.opt_in_cb);
+ }
+ if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
+ }
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+ dbus_bus_remove_match(portal->monitor_connection, fd.filter.utf8().get_data(), &err);
+ dbus_error_free(&err);
+
+ portal->file_dialogs.remove_at(i);
+ break;
+ }
+ }
}
dbus_message_unref(msg);
}
- dbus_connection_read_write(portal->theme_connection, 0);
+ dbus_connection_read_write(portal->monitor_connection, 0);
}
usleep(50000);
@@ -647,18 +618,18 @@ FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() {
DBusError err;
dbus_error_init(&err);
- theme_connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
+ monitor_connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
dbus_error_free(&err);
} else {
theme_path = "type='signal',sender='org.freedesktop.portal.Desktop',interface='org.freedesktop.portal.Settings',member='SettingChanged'";
- dbus_bus_add_match(theme_connection, theme_path.utf8().get_data(), &err);
+ dbus_bus_add_match(monitor_connection, theme_path.utf8().get_data(), &err);
if (dbus_error_is_set(&err)) {
dbus_error_free(&err);
- dbus_connection_unref(theme_connection);
- theme_connection = nullptr;
+ dbus_connection_unref(monitor_connection);
+ monitor_connection = nullptr;
}
- dbus_connection_read_write(theme_connection, 0);
+ dbus_connection_read_write(monitor_connection, 0);
}
if (!unsupported) {
@@ -673,21 +644,17 @@ FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() {
monitor_thread.wait_to_finish();
}
- for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) {
- if (fd.connection) {
- DBusError err;
+ if (monitor_connection) {
+ DBusError err;
+ for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) {
dbus_error_init(&err);
- dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err);
+ dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);
dbus_error_free(&err);
- dbus_connection_unref(fd.connection);
}
- }
- if (theme_connection) {
- DBusError err;
dbus_error_init(&err);
- dbus_bus_remove_match(theme_connection, theme_path.utf8().get_data(), &err);
+ dbus_bus_remove_match(monitor_connection, theme_path.utf8().get_data(), &err);
dbus_error_free(&err);
- dbus_connection_unref(theme_connection);
+ dbus_connection_unref(monitor_connection);
}
}
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h
index 75afe02a26..ba9c352e62 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.h
+++ b/platform/linuxbsd/freedesktop_portal_desktop.h
@@ -60,9 +60,9 @@ private:
struct FileDialogData {
Vector<String> filter_names;
- DBusConnection *connection = nullptr;
DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID;
Callable callback;
+ String filter;
String path;
bool opt_in_cb = false;
};
@@ -71,8 +71,8 @@ private:
Vector<FileDialogData> file_dialogs;
Thread monitor_thread;
SafeFlag monitor_thread_abort;
+ DBusConnection *monitor_connection = nullptr;
- DBusConnection *theme_connection = nullptr;
String theme_path;
Callable system_theme_changed;
void _system_theme_changed_callback();
diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp
index a2b6fbeb25..b0880c86b8 100644
--- a/platform/linuxbsd/godot_linuxbsd.cpp
+++ b/platform/linuxbsd/godot_linuxbsd.cpp
@@ -72,18 +72,19 @@ int main(int argc, char *argv[]) {
char *ret = getcwd(cwd, PATH_MAX);
Error err = Main::setup(argv[0], argc - 1, &argv[1]);
+
if (err != OK) {
free(cwd);
-
if (err == ERR_HELP) { // Returned by --help and --version, so success.
- return 0;
+ return EXIT_SUCCESS;
}
- return 255;
+ return EXIT_FAILURE;
}
- if (Main::start()) {
- os.set_exit_code(EXIT_SUCCESS);
- os.run(); // it is actually the OS that decides how to run
+ if (Main::start() == EXIT_SUCCESS) {
+ os.run();
+ } else {
+ os.set_exit_code(EXIT_FAILURE);
}
Main::cleanup();
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index f29275c910..68b4cd7f5a 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -62,6 +62,10 @@
#include <mntent.h>
#endif
+#if defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#endif
+
void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) {
const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" };
@@ -145,17 +149,36 @@ void OS_LinuxBSD::initialize_joypads() {
String OS_LinuxBSD::get_unique_id() const {
static String machine_id;
if (machine_id.is_empty()) {
+#if defined(__FreeBSD__)
+ const int mib[2] = { CTL_KERN, KERN_HOSTUUID };
+ char buf[4096];
+ memset(buf, 0, sizeof(buf));
+ size_t len = sizeof(buf) - 1;
+ if (sysctl(mib, 2, buf, &len, 0x0, 0) != -1) {
+ machine_id = String::utf8(buf).replace("-", "");
+ }
+#else
Ref<FileAccess> f = FileAccess::open("/etc/machine-id", FileAccess::READ);
if (f.is_valid()) {
while (machine_id.is_empty() && !f->eof_reached()) {
machine_id = f->get_line().strip_edges();
}
}
+#endif
}
return machine_id;
}
String OS_LinuxBSD::get_processor_name() const {
+#if defined(__FreeBSD__)
+ const int mib[2] = { CTL_HW, HW_MODEL };
+ char buf[4096];
+ memset(buf, 0, sizeof(buf));
+ size_t len = sizeof(buf) - 1;
+ if (sysctl(mib, 2, buf, &len, 0x0, 0) != -1) {
+ return String::utf8(buf);
+ }
+#else
Ref<FileAccess> f = FileAccess::open("/proc/cpuinfo", FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), "", String("Couldn't open `/proc/cpuinfo` to get the CPU model name. Returning an empty string."));
@@ -165,8 +188,9 @@ String OS_LinuxBSD::get_processor_name() const {
return line.split(":")[1].strip_edges();
}
}
+#endif
- ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name from `/proc/cpuinfo`. Returning an empty string."));
+ ERR_FAIL_V_MSG("", String("Couldn't get the CPU model. Returning an empty string."));
}
bool OS_LinuxBSD::is_sandboxed() const {
diff --git a/platform/linuxbsd/platform_linuxbsd_builders.py b/platform/linuxbsd/platform_linuxbsd_builders.py
index 58234f3748..46fa1947e8 100644
--- a/platform/linuxbsd/platform_linuxbsd_builders.py
+++ b/platform/linuxbsd/platform_linuxbsd_builders.py
@@ -1,17 +1,10 @@
-"""Functions used to generate source files during build time
+"""Functions used to generate source files during build time"""
-All such functions are invoked in a subprocess on Windows to prevent build flakiness.
-
-"""
import os
-from platform_methods import subprocess_main
def make_debug_linuxbsd(target, source, env):
- os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
- os.system("strip --strip-debug --strip-unneeded {0}".format(target[0]))
- os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
-
-
-if __name__ == "__main__":
- subprocess_main(globals())
+ dst = str(target[0])
+ os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst))
+ os.system("strip --strip-debug --strip-unneeded {0}".format(dst))
+ os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst))
diff --git a/platform/linuxbsd/wayland/detect_prime_egl.cpp b/platform/linuxbsd/wayland/detect_prime_egl.cpp
index 4bee32ae3a..4c97a80039 100644
--- a/platform/linuxbsd/wayland/detect_prime_egl.cpp
+++ b/platform/linuxbsd/wayland/detect_prime_egl.cpp
@@ -69,7 +69,7 @@ void DetectPrimeEGL::create_context() {
EGLConfig egl_config;
EGLContext egl_context = EGL_NO_CONTEXT;
- eglInitialize(egl_display, NULL, NULL);
+ eglInitialize(egl_display, nullptr, nullptr);
#if defined(GLAD_ENABLED)
if (!gladLoaderLoadEGL(egl_display)) {
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index c957dea32d..a815db1c05 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -192,19 +192,37 @@ void DisplayServerWayland::_show_window() {
bool DisplayServerWayland::has_feature(Feature p_feature) const {
switch (p_feature) {
+#ifndef DISABLE_DEPRECATED
+ case FEATURE_GLOBAL_MENU: {
+ return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
+ } break;
+#endif
case FEATURE_MOUSE:
+ case FEATURE_MOUSE_WARP:
case FEATURE_CLIPBOARD:
case FEATURE_CURSOR_SHAPE:
+ case FEATURE_CUSTOM_CURSOR_SHAPE:
case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_HIDPI:
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
- case FEATURE_CLIPBOARD_PRIMARY:
+ case FEATURE_CLIPBOARD_PRIMARY: {
+ return true;
+ } break;
+
+ //case FEATURE_NATIVE_DIALOG:
+ //case FEATURE_NATIVE_DIALOG_INPUT:
#ifdef DBUS_ENABLED
- case FEATURE_NATIVE_DIALOG:
+ case FEATURE_NATIVE_DIALOG_FILE: {
+ return true;
+ } break;
#endif
- case FEATURE_HIDPI: {
+
+#ifdef SPEECHD_ENABLED
+ case FEATURE_TEXT_TO_SPEECH: {
return true;
} break;
+#endif
default: {
return false;
@@ -569,9 +587,9 @@ void DisplayServerWayland::screen_set_keep_on(bool p_enable) {
bool DisplayServerWayland::screen_is_kept_on() const {
#ifdef DBUS_ENABLED
return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID) || screensaver_inhibited;
-#endif
-
+#else
return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID);
+#endif
}
Vector<DisplayServer::WindowID> DisplayServerWayland::get_window_list() const {
@@ -990,36 +1008,9 @@ void DisplayServerWayland::cursor_set_custom_image(const Ref<Resource> &p_cursor
wayland_thread.cursor_shape_clear_custom_image(p_shape);
}
- Ref<Texture2D> texture = p_cursor;
- ERR_FAIL_COND(!texture.is_valid());
- Size2i texture_size;
-
- Ref<AtlasTexture> atlas_texture = texture;
-
- if (atlas_texture.is_valid()) {
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
-
- // NOTE: The Wayland protocol says nothing about cursor size limits, yet if
- // the texture is larger than 256x256 it won't show at least on sway.
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
- ERR_FAIL_COND(texture_size.height == 0 || texture_size.width == 0);
-
- 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.");
- }
+ Rect2 atlas_rect;
+ Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
+ ERR_FAIL_COND(image.is_null());
CustomCursor &cursor = custom_cursors[p_shape];
@@ -1183,14 +1174,6 @@ void DisplayServerWayland::release_rendering_thread() {
#endif
}
-void DisplayServerWayland::make_rendering_thread() {
-#ifdef GLES3_ENABLED
- if (egl_manager) {
- egl_manager->make_current();
- }
-#endif
-}
-
void DisplayServerWayland::swap_buffers() {
#ifdef GLES3_ENABLED
if (egl_manager) {
@@ -1258,6 +1241,8 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
// Input.
Input::get_singleton()->set_event_dispatch_function(dispatch_input_events);
+ native_menu = memnew(NativeMenu);
+
#ifdef SPEECHD_ENABLED
// Init TTS
tts = memnew(TTS_Linux);
@@ -1382,6 +1367,12 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
DisplayServerWayland::~DisplayServerWayland() {
// TODO: Multiwindow support.
+
+ if (native_menu) {
+ memdelete(native_menu);
+ native_menu = nullptr;
+ }
+
if (main_window.visible) {
#ifdef VULKAN_ENABLED
if (rendering_device) {
diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h
index e42967eb7b..368f1b402b 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.h
+++ b/platform/linuxbsd/wayland/display_server_wayland.h
@@ -59,8 +59,6 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
-#include "scene/resources/atlas_texture.h"
-#include "scene/resources/texture.h"
#include "servers/display_server.h"
#include <limits.h>
@@ -134,6 +132,7 @@ class DisplayServerWayland : public DisplayServer {
#ifdef SPEECHD_ENABLED
TTS_Linux *tts = nullptr;
#endif
+ NativeMenu *native_menu = nullptr;
#if DBUS_ENABLED
FreeDesktopPortalDesktop *portal_desktop = nullptr;
@@ -277,7 +276,6 @@ public:
virtual void process_events() override;
virtual void release_rendering_thread() override;
- virtual void make_rendering_thread() override;
virtual void swap_buffers() override;
virtual void set_context(Context p_context) override;
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
index ebb21722e2..7f9008e952 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -448,14 +448,11 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re
zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);
}
-#if 0
- // FIXME: Broken.
if (!ss->wp_tablet_seat && registry->wp_tablet_manager) {
// Tablet.
ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);
zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);
}
-#endif
registry->wl_seats.push_back(wl_seat);
@@ -541,13 +538,11 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re
return;
}
-#if 0
- // FIXME: Broken.
if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) {
registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1);
registry->wp_tablet_manager_name = name;
- // This global creates some seats data. Let's do that for the ones already available.
+ // This global creates some seat data. Let's do that for the ones already available.
for (struct wl_seat *wl_seat : registry->wl_seats) {
SeatState *ss = wl_seat_get_seat_state(wl_seat);
ERR_FAIL_NULL(ss);
@@ -558,7 +553,6 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re
return;
}
-#endif
}
void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) {
@@ -820,8 +814,6 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
return;
}
-#if 0
- // FIXME: Broken.
if (name == registry->wp_tablet_manager_name) {
if (registry->wp_tablet_manager) {
zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager);
@@ -835,25 +827,27 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
SeatState *ss = wl_seat_get_seat_state(wl_seat);
ERR_FAIL_NULL(ss);
- List<struct zwp_tablet_tool_v2 *>::Element *it = ss->tablet_tools.front();
-
- while (it) {
- zwp_tablet_tool_v2_destroy(it->get());
- ss->tablet_tools.erase(it);
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ TabletToolState *state = wp_tablet_tool_get_state(tool);
+ if (state) {
+ memdelete(state);
+ }
- it = it->next();
+ zwp_tablet_tool_v2_destroy(tool);
}
+
+ ss->tablet_tools.clear();
}
return;
}
-#endif
{
// Iterate through all of the seats to find if any got removed.
- List<struct wl_seat *>::Element *it = registry->wl_seats.front();
- while (it) {
- struct wl_seat *wl_seat = it->get();
+ List<struct wl_seat *>::Element *E = registry->wl_seats.front();
+ while (E) {
+ struct wl_seat *wl_seat = E->get();
+ List<struct wl_seat *>::Element *N = E->next();
SeatState *ss = wl_seat_get_seat_state(wl_seat);
ERR_FAIL_NULL(ss);
@@ -867,28 +861,26 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
wl_data_device_destroy(ss->wl_data_device);
}
-#if 0
- // FIXME: Broken.
if (ss->wp_tablet_seat) {
zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);
for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ TabletToolState *state = wp_tablet_tool_get_state(tool);
+ if (state) {
+ memdelete(state);
+ }
+
zwp_tablet_tool_v2_destroy(tool);
}
}
- // Let's destroy all tools.
- for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
- zwp_tablet_tool_v2_destroy(tool);
- }
-
memdelete(ss);
- registry->wl_seats.erase(it);
-#endif
+
+ registry->wl_seats.erase(E);
return;
}
- it = it->next();
+ E = N;
}
}
@@ -1239,8 +1231,6 @@ void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat
// Pointer handling.
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor);
- ss->cursor_frame_callback = wl_surface_frame(ss->cursor_surface);
- wl_callback_add_listener(ss->cursor_frame_callback, &cursor_frame_callback_listener, ss);
wl_surface_commit(ss->cursor_surface);
ss->wl_pointer = wl_seat_get_pointer(wl_seat);
@@ -1321,11 +1311,9 @@ void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callbac
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- ss->cursor_time_ms = time_ms;
+ ss->cursor_frame_callback = nullptr;
- ss->cursor_frame_callback = wl_surface_frame(ss->cursor_surface);
- wl_callback_add_listener(ss->cursor_frame_callback, &cursor_frame_callback_listener, ss);
- wl_surface_commit(ss->cursor_surface);
+ ss->cursor_time_ms = time_ms;
seat_state_update_cursor(ss);
}
@@ -1575,7 +1563,7 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
}
if (old_pd.pressed_button_mask != pd.pressed_button_mask) {
- BitField<MouseButtonMask> pressed_mask_delta = BitField<MouseButtonMask>((uint32_t)old_pd.pressed_button_mask ^ (uint32_t)pd.pressed_button_mask);
+ BitField<MouseButtonMask> pressed_mask_delta = old_pd.pressed_button_mask ^ pd.pressed_button_mask;
const MouseButton buttons_to_test[] = {
MouseButton::LEFT,
@@ -1736,7 +1724,7 @@ void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_ke
ss->keymap_buffer = nullptr;
}
- ss->keymap_buffer = (const char *)mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ ss->keymap_buffer = (const char *)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
ss->keymap_buffer_size = size;
xkb_keymap_unref(ss->xkb_keymap);
@@ -2160,82 +2148,90 @@ void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct
}
void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_v2 *id) {
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on tablet %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
}
void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) {
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- ss->tablet_tools.push_back(id);
+ TabletToolState *state = memnew(TabletToolState);
+ state->wl_seat = ss->wl_seat;
- zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, ss);
+ wl_proxy_tag_godot((struct wl_proxy *)id);
+ zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, state);
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on tool %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
+ ss->tablet_tools.push_back(id);
}
void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) {
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on pad %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
}
void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t tool_type) {
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on type %d", (size_t)zwp_tablet_tool_v2, tool_type));
+ TabletToolState *state = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (state && tool_type == ZWP_TABLET_TOOL_V2_TYPE_ERASER) {
+ state->is_eraser = true;
+ }
}
void WaylandThread::_wp_tablet_tool_on_hardware_serial(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) {
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on hardware serial %x%x", (size_t)zwp_tablet_tool_v2, hardware_serial_hi, hardware_serial_lo));
}
void WaylandThread::_wp_tablet_tool_on_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_id_hi, uint32_t hardware_id_lo) {
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on hardware id wacom hardware id %x%x", (size_t)zwp_tablet_tool_v2, hardware_id_hi, hardware_id_lo));
}
void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t capability) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
-
- if (capability == ZWP_TABLET_TOOL_V2_TYPE_ERASER) {
- ss->tablet_tool_data_buffer.is_eraser = true;
- }
-
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on capability %d", (size_t)zwp_tablet_tool_v2, capability));
}
void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on done", (size_t)zwp_tablet_tool_v2));
}
void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
- List<struct zwp_tablet_tool_v2 *>::Element *it = ss->tablet_tools.front();
+ if (!ts) {
+ return;
+ }
- while (it) {
- struct zwp_tablet_tool_v2 *tool = it->get();
+ SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
- if (tool == zwp_tablet_tool_v2) {
- zwp_tablet_tool_v2_destroy(tool);
- ss->tablet_tools.erase(it);
- break;
- }
+ if (!ss) {
+ return;
}
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on removed", (size_t)zwp_tablet_tool_v2));
+ List<struct zwp_tablet_tool_v2 *>::Element *E = ss->tablet_tools.find(zwp_tablet_tool_v2);
+
+ if (E && E->get()) {
+ struct zwp_tablet_tool_v2 *tool = E->get();
+ TabletToolState *state = wp_tablet_tool_get_state(tool);
+ if (state) {
+ memdelete(state);
+ }
+
+ zwp_tablet_tool_v2_destroy(tool);
+ ss->tablet_tools.erase(E);
+ }
}
void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (!ts) {
+ return;
+ }
+
+ SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
+
+ if (!ss) {
+ return;
+ }
WaylandThread *wayland_thread = ss->wayland_thread;
ERR_FAIL_NULL(wayland_thread);
- ss->tablet_tool_data_buffer.in_proximity = true;
-
- ss->pointer_enter_serial = serial;
- ss->pointed_surface = surface;
- ss->last_pointed_surface = surface;
+ ts->data_pending.proximity_serial = serial;
+ ts->data_pending.proximal_surface = surface;
+ ts->last_surface = surface;
Ref<WindowEventMessage> msg;
msg.instantiate();
@@ -2243,21 +2239,25 @@ void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_table
wayland_thread->push_message(msg);
DEBUG_LOG_WAYLAND_THREAD("Tablet tool entered window.");
-
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on proximity in serial %d tablet %x surface %x", (size_t)zwp_tablet_tool_v2, serial, (size_t)tablet, (size_t)surface));
}
void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (!ts) {
+ return;
+ }
+
+ SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
+
+ if (!ss) {
+ return;
+ }
WaylandThread *wayland_thread = ss->wayland_thread;
ERR_FAIL_NULL(wayland_thread);
- ss->pointed_surface = nullptr;
- ss->tablet_tool_data_buffer.in_proximity = false;
-
- DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window.");
+ ts->data_pending.proximal_surface = nullptr;
Ref<WindowEventMessage> msg;
msg.instantiate();
@@ -2265,16 +2265,18 @@ void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tabl
wayland_thread->push_message(msg);
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on proximity out", (size_t)zwp_tablet_tool_v2));
+ DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window.");
}
void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (!ts) {
+ return;
+ }
- TabletToolData &td = ss->tablet_tool_data_buffer;
+ TabletToolData &td = ts->data_pending;
- td.touching = true;
td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT));
td.last_button_pressed = MouseButton::LEFT;
td.double_click_begun = true;
@@ -2282,45 +2284,53 @@ void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v
// The protocol doesn't cover this, but we can use this funky hack to make
// double clicking work.
td.button_time = OS::get_singleton()->get_ticks_msec();
-
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on down serial %x", (size_t)zwp_tablet_tool_v2, serial));
}
void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (!ts) {
+ return;
+ }
- TabletToolData &td = ss->tablet_tool_data_buffer;
+ TabletToolData &td = ts->data_pending;
- td.touching = false;
td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT));
// The protocol doesn't cover this, but we can use this funky hack to make
// double clicking work.
td.button_time = OS::get_singleton()->get_ticks_msec();
-
- DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on up", (size_t)zwp_tablet_tool_v2));
}
void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
- WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ if (!ts) {
+ return;
+ }
+
+ WindowState *ws = wl_surface_get_window_state(ts->data_pending.proximal_surface);
ERR_FAIL_NULL(ws);
- double scale_factor = window_state_get_scale_factor(ws);
+ TabletToolData &td = ts->data_pending;
- TabletToolData &td = ss->tablet_tool_data_buffer;
+ double scale_factor = window_state_get_scale_factor(ws);
+ td.position.x = wl_fixed_to_int(x);
+ td.position.y = wl_fixed_to_int(y);
td.position = scale_vector2i(td.position, scale_factor);
+
+ td.motion_time = OS::get_singleton()->get_ticks_msec();
}
void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t pressure) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (!ts) {
+ return;
+ }
- ss->tablet_tool_data_buffer.pressure = pressure;
+ ts->data_pending.pressure = pressure;
}
void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t distance) {
@@ -2328,11 +2338,16 @@ void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_to
}
void WaylandThread::_wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (!ts) {
+ return;
+ }
- ss->tablet_tool_data_buffer.tilt.x = wl_fixed_to_double(tilt_x);
- ss->tablet_tool_data_buffer.tilt.y = wl_fixed_to_double(tilt_y);
+ TabletToolData &td = ts->data_pending;
+
+ td.tilt.x = wl_fixed_to_double(tilt_x);
+ td.tilt.y = wl_fixed_to_double(tilt_y);
}
void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees) {
@@ -2348,10 +2363,13 @@ void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_
}
void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (!ts) {
+ return;
+ }
- TabletToolData &td = ss->tablet_tool_data_buffer;
+ TabletToolData &td = ts->data_pending;
MouseButton mouse_button = MouseButton::NONE;
@@ -2381,14 +2399,23 @@ void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool
}
void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time) {
- SeatState *ss = (SeatState *)data;
- ERR_FAIL_NULL(ss);
+ TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2);
+
+ if (!ts) {
+ return;
+ }
+
+ SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
+
+ if (!ss) {
+ return;
+ }
WaylandThread *wayland_thread = ss->wayland_thread;
ERR_FAIL_NULL(wayland_thread);
- TabletToolData &old_td = ss->tablet_tool_data;
- TabletToolData &td = ss->tablet_tool_data_buffer;
+ TabletToolData &old_td = ts->data;
+ TabletToolData &td = ts->data_pending;
if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) {
Ref<InputEventMouseMotion> mm;
@@ -2411,25 +2438,24 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_
// straight from the compositor, so we have to normalize them here.
// According to the tablet proto spec, tilt is expressed in degrees relative
- // to the Z axis of the tablet, so it shouldn't go over 90 degrees, I think.
- // TODO: Investigate whether the tilt can go over 90 degrees (it shouldn't).
+ // to the Z axis of the tablet, so it shouldn't go over 90 degrees either way,
+ // I think. We'll clamp it just in case.
+ td.tilt = td.tilt.clamp(Vector2(-90, -90), Vector2(90, 90));
+
mm->set_tilt(td.tilt / 90);
// The tablet proto spec explicitly says that pressure is defined as a value
// between 0 to 65535.
mm->set_pressure(td.pressure / (float)65535);
- // FIXME: Tool handling is broken.
- mm->set_pen_inverted(td.is_eraser);
+ mm->set_pen_inverted(ts->is_eraser);
mm->set_relative(td.position - old_td.position);
mm->set_relative_screen_position(mm->get_relative());
- // FIXME: Stop doing this to calculate speed.
- // FIXME2: It has been done, port this from the pointer logic once this works again.
- Input::get_singleton()->set_mouse_position(td.position);
- mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
- mm->set_screen_velocity(mm->get_velocity());
+ Vector2i pos_delta = td.position - old_td.position;
+ uint32_t time_delta = td.motion_time - old_td.motion_time;
+ mm->set_velocity((Vector2)pos_delta / time_delta);
Ref<InputEventMessage> inputev_msg;
inputev_msg.instantiate();
@@ -2618,6 +2644,15 @@ WaylandThread::SeatState *WaylandThread::wl_seat_get_seat_state(struct wl_seat *
return nullptr;
}
+// Returns the wp_tablet_tool's `TabletToolState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::TabletToolState *WaylandThread::wp_tablet_tool_get_state(struct zwp_tablet_tool_v2 *p_tool) {
+ if (p_tool && wl_proxy_is_godot((wl_proxy *)p_tool)) {
+ return (TabletToolState *)zwp_tablet_tool_v2_get_user_data(p_tool);
+ }
+
+ return nullptr;
+}
// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.
// NOTE: This will fail if the output isn't tagged as ours.
WaylandThread::OfferState *WaylandThread::wl_data_offer_get_offer_state(struct wl_data_offer *p_offer) {
@@ -2819,7 +2854,7 @@ void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {
ERR_FAIL_NULL(locked_surface);
- p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
}
}
@@ -2851,7 +2886,7 @@ void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) {
ERR_FAIL_NULL(confined_surface);
- p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, confined_surface, p_ss->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, confined_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
}
}
@@ -2880,7 +2915,18 @@ void WaylandThread::seat_state_update_cursor(SeatState *p_ss) {
// compositor do it for us (badly).
scale = 1;
} else if (wl_cursor) {
- int frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms);
+ int frame_idx = 0;
+
+ if (wl_cursor->image_count > 1) {
+ // The cursor is animated.
+ frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms);
+
+ if (!p_ss->cursor_frame_callback) {
+ // Since it's animated, we'll re-update it the next frame.
+ p_ss->cursor_frame_callback = wl_surface_frame(p_ss->cursor_surface);
+ wl_callback_add_listener(p_ss->cursor_frame_callback, &cursor_frame_callback_listener, p_ss);
+ }
+ }
struct wl_cursor_image *wl_cursor_image = wl_cursor->images[frame_idx];
@@ -3614,7 +3660,7 @@ void WaylandThread::cursor_shape_set_custom_image(DisplayServer::CursorShape p_c
munmap(cursor.buffer_data, cursor.buffer_data_size);
}
- cursor.buffer_data = (uint32_t *)mmap(NULL, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ cursor.buffer_data = (uint32_t *)mmap(nullptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (cursor.wl_buffer) {
// Clean up the old Wayland buffer.
@@ -4070,14 +4116,16 @@ void WaylandThread::destroy() {
zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
}
-#if 0
- // FIXME: Broken.
if (ss->wp_tablet_seat) {
zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);
}
-#endif
for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ TabletToolState *state = wp_tablet_tool_get_state(tool);
+ if (state) {
+ memdelete(state);
+ }
+
zwp_tablet_tool_v2_destroy(tool);
}
diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h
index f3e3c3a2ac..d49f0c9d34 100644
--- a/platform/linuxbsd/wayland/wayland_thread.h
+++ b/platform/linuxbsd/wayland/wayland_thread.h
@@ -308,7 +308,7 @@ public:
struct TabletToolData {
Point2i position;
- Vector2i tilt;
+ Vector2 tilt;
uint32_t pressure = 0;
BitField<MouseButtonMask> pressed_button_mask;
@@ -322,10 +322,20 @@ public:
// be used as a mouse...), but we'll hack one in with the current ticks.
uint64_t button_time = 0;
+ uint64_t motion_time = 0;
+
+ uint32_t proximity_serial = 0;
+ struct wl_surface *proximal_surface = nullptr;
+ };
+
+ struct TabletToolState {
+ struct wl_seat *wl_seat = nullptr;
+
+ struct wl_surface *last_surface = nullptr;
bool is_eraser = false;
- bool in_proximity = false;
- bool touching = false;
+ TabletToolData data_pending;
+ TabletToolData data;
};
struct OfferState {
@@ -425,9 +435,6 @@ public:
struct zwp_tablet_seat_v2 *wp_tablet_seat = nullptr;
List<struct zwp_tablet_tool_v2 *> tablet_tools;
-
- TabletToolData tablet_tool_data_buffer;
- TabletToolData tablet_tool_data;
};
struct CustomCursor {
@@ -855,6 +862,7 @@ public:
static WindowState *wl_surface_get_window_state(struct wl_surface *p_surface);
static ScreenState *wl_output_get_screen_state(struct wl_output *p_output);
static SeatState *wl_seat_get_seat_state(struct wl_seat *p_seat);
+ static TabletToolState *wp_tablet_tool_get_state(struct zwp_tablet_tool_v2 *p_tool);
static OfferState *wl_data_offer_get_offer_state(struct wl_data_offer *p_offer);
static OfferState *wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer);
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 35bfe81827..4c7dfbb107 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -41,7 +41,6 @@
#include "core/string/ustring.h"
#include "drivers/png/png_driver_common.h"
#include "main/main.h"
-#include "scene/resources/atlas_texture.h"
#if defined(VULKAN_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
@@ -110,6 +109,11 @@ static String get_atom_name(Display *p_disp, Atom p_atom) {
bool DisplayServerX11::has_feature(Feature p_feature) const {
switch (p_feature) {
+#ifndef DISABLE_DEPRECATED
+ case FEATURE_GLOBAL_MENU: {
+ return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
+ } break;
+#endif
case FEATURE_SUBWINDOWS:
#ifdef TOUCH_ENABLED
case FEATURE_TOUCHSCREEN:
@@ -124,8 +128,10 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
//case FEATURE_HIDPI:
case FEATURE_ICON:
#ifdef DBUS_ENABLED
- case FEATURE_NATIVE_DIALOG:
+ case FEATURE_NATIVE_DIALOG_FILE:
#endif
+ //case FEATURE_NATIVE_DIALOG:
+ //case FEATURE_NATIVE_DIALOG_INPUT:
//case FEATURE_NATIVE_ICON:
case FEATURE_SWAP_BUFFERS:
#ifdef DBUS_ENABLED
@@ -1991,8 +1997,7 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window
Size2i wsize = window_get_size(p_window);
wpos += srect.position;
if (srect != Rect2i()) {
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);
}
window_set_position(wpos, p_window);
}
@@ -2220,8 +2225,7 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
Size2i size = p_size;
- size.x = MAX(1, size.x);
- size.y = MAX(1, size.y);
+ size = size.max(Size2i(1, 1));
WindowData &wd = windows[p_window];
@@ -3064,39 +3068,10 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu
cursors_cache.erase(p_shape);
}
- Ref<Texture2D> texture = p_cursor;
- ERR_FAIL_COND(!texture.is_valid());
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Size2i texture_size;
- Rect2i atlas_rect;
-
- if (atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
-
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- 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.");
- }
+ Rect2 atlas_rect;
+ Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
+ ERR_FAIL_COND(image.is_null());
+ Vector2i texture_size = image->get_size();
// Create the cursor structure
XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);
@@ -3115,7 +3090,7 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu
int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
- if (atlas_texture.is_valid()) {
+ if (atlas_rect.has_area()) {
column_index = MIN(column_index, atlas_rect.size.width - 1);
row_index = MIN(row_index, atlas_rect.size.height - 1);
}
@@ -4293,7 +4268,7 @@ bool DisplayServerX11::_window_focus_check() {
}
void DisplayServerX11::process_events() {
- _THREAD_SAFE_METHOD_
+ _THREAD_SAFE_LOCK_
#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED
static int frame = 0;
@@ -4731,19 +4706,6 @@ void DisplayServerX11::process_events() {
break;
}
- const WindowData &wd = windows[window_id];
-
- XWindowAttributes xwa;
- XSync(x11_display, False);
- XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
-
- // Set focus when menu window is re-used.
- // RevertToPointerRoot is used to make sure we don't lose all focus in case
- // a subwindow and its parent are both destroyed.
- if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) {
- _set_input_focus(wd.x11_window, RevertToPointerRoot);
- }
-
_window_changed(&event);
} break;
@@ -5135,6 +5097,8 @@ void DisplayServerX11::process_events() {
*/
}
+ _THREAD_SAFE_UNLOCK_
+
Input::get_singleton()->flush_buffered_events();
}
@@ -5149,17 +5113,6 @@ void DisplayServerX11::release_rendering_thread() {
#endif
}
-void DisplayServerX11::make_rendering_thread() {
-#if defined(GLES3_ENABLED)
- if (gl_manager) {
- gl_manager->make_current();
- }
- if (gl_manager_egl) {
- gl_manager_egl->make_current();
- }
-#endif
-}
-
void DisplayServerX11::swap_buffers() {
#if defined(GLES3_ENABLED)
if (gl_manager) {
@@ -5250,7 +5203,7 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) {
if (g_set_icon_error) {
g_set_icon_error = false;
- WARN_PRINT("Icon too large, attempting to resize icon.");
+ WARN_PRINT(vformat("Icon too large (%dx%d), attempting to downscale icon.", w, h));
int new_width, new_height;
if (w > h) {
@@ -5489,8 +5442,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
} else {
Rect2i srect = screen_get_usable_rect(rq_screen);
Point2i wpos = p_rect.position;
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);
win_rect.position = wpos;
}
@@ -5795,6 +5747,8 @@ static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMSt
DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) {
KeyMappingX11::initialize();
+ native_menu = memnew(NativeMenu);
+
#ifdef SOWRAP_ENABLED
#ifdef DEBUG_ENABLED
int dylibloader_verbose = 1;
@@ -6387,6 +6341,11 @@ DisplayServerX11::~DisplayServerX11() {
events_thread_done.set();
events_thread.wait_to_finish();
+ if (native_menu) {
+ memdelete(native_menu);
+ native_menu = nullptr;
+ }
+
//destroy all windows
for (KeyValue<WindowID, WindowData> &E : windows) {
#if defined(RD_ENABLED)
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index a5cbe34d26..8a7062857c 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -156,6 +156,7 @@ class DisplayServerX11 : public DisplayServer {
#ifdef SPEECHD_ENABLED
TTS_Linux *tts = nullptr;
#endif
+ NativeMenu *native_menu = nullptr;
#if defined(DBUS_ENABLED)
FreeDesktopPortalDesktop *portal_desktop = nullptr;
@@ -525,7 +526,6 @@ public:
virtual void process_events() override;
virtual void release_rendering_thread() override;
- virtual void make_rendering_thread() override;
virtual void swap_buffers() override;
virtual void set_context(Context p_context) override;
diff --git a/platform/linuxbsd/x11/gl_manager_x11.cpp b/platform/linuxbsd/x11/gl_manager_x11.cpp
index 602dd784e0..febb7ae584 100644
--- a/platform/linuxbsd/x11/gl_manager_x11.cpp
+++ b/platform/linuxbsd/x11/gl_manager_x11.cpp
@@ -311,20 +311,6 @@ void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) {
_internal_set_current_window(&win);
}
-void GLManager_X11::make_current() {
- if (!_current_window) {
- return;
- }
- if (!_current_window->in_use) {
- WARN_PRINT("current window not in use!");
- return;
- }
- const GLDisplay &disp = get_current_display();
- if (!glXMakeCurrent(_x_windisp.x11_display, _x_windisp.x11_window, disp.context->glx_context)) {
- ERR_PRINT("glXMakeCurrent failed");
- }
-}
-
void GLManager_X11::swap_buffers() {
if (!_current_window) {
return;
diff --git a/platform/linuxbsd/x11/gl_manager_x11.h b/platform/linuxbsd/x11/gl_manager_x11.h
index 235c7d22f9..06e147e39f 100644
--- a/platform/linuxbsd/x11/gl_manager_x11.h
+++ b/platform/linuxbsd/x11/gl_manager_x11.h
@@ -117,7 +117,6 @@ public:
void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height);
void release_current();
- void make_current();
void swap_buffers();
void window_make_current(DisplayServer::WindowID p_window_id);
diff --git a/platform/macos/SCsub b/platform/macos/SCsub
index 08783ee14a..59ef4ee85c 100644
--- a/platform/macos/SCsub
+++ b/platform/macos/SCsub
@@ -3,7 +3,7 @@
Import("env")
import os, json
-from platform_methods import run_in_subprocess, architectures, lipo, get_build_version
+from platform_methods import architectures, lipo, get_build_version
import platform_macos_builders
import subprocess
import shutil
@@ -36,7 +36,7 @@ def generate_bundle(target, source, env):
version = get_build_version(False)
short_version = get_build_version(True)
with open(Dir("#misc/dist/macos").abspath + "/editor_info_plist.template", "rt") as fin:
- with open(app_dir + "/Contents/Info.plist", "wt") as fout:
+ with open(app_dir + "/Contents/Info.plist", "wt", encoding="utf-8", newline="\n") as fout:
for line in fin:
line = line.replace("$version", version)
line = line.replace("$short_version", short_version)
@@ -113,9 +113,10 @@ files = [
"godot_menu_delegate.mm",
"godot_menu_item.mm",
"godot_open_save_delegate.mm",
+ "native_menu_macos.mm",
"dir_access_macos.mm",
"tts_macos.mm",
- "joypad_macos.cpp",
+ "joypad_macos.mm",
"rendering_context_driver_vulkan_macos.mm",
"gl_manager_macos_angle.mm",
"gl_manager_macos_legacy.mm",
@@ -124,7 +125,7 @@ files = [
prog = env.add_program("#bin/godot", files)
if env["debug_symbols"] and env["separate_debug_symbols"]:
- env.AddPostAction(prog, run_in_subprocess(platform_macos_builders.make_debug_macos))
+ env.AddPostAction(prog, env.Run(platform_macos_builders.make_debug_macos))
if env["generate_bundle"]:
generate_bundle_command = env.Command("generate_bundle", [], generate_bundle)
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index 9150a527a5..3c8b1ebee1 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -208,7 +208,9 @@ def configure(env: "SConsEnvironment"):
"-framework",
"IOKit",
"-framework",
- "ForceFeedback",
+ "GameController",
+ "-framework",
+ "CoreHaptics",
"-framework",
"CoreVideo",
"-framework",
@@ -240,10 +242,17 @@ def configure(env: "SConsEnvironment"):
env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "IOSurface"])
if not env["use_volk"]:
env.Append(LINKFLAGS=["-lMoltenVK"])
- mvk_path = detect_mvk(env, "macos-arm64_x86_64")
+
+ mvk_path = ""
+ arch_variants = ["macos-arm64_x86_64", "macos-" + env["arch"]]
+ for arch in arch_variants:
+ mvk_path = detect_mvk(env, arch)
+ if mvk_path != "":
+ mvk_path = os.path.join(mvk_path, arch)
+ break
if mvk_path != "":
- env.Append(LINKFLAGS=["-L" + os.path.join(mvk_path, "macos-arm64_x86_64")])
+ env.Append(LINKFLAGS=["-L" + mvk_path])
else:
print(
"MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path."
diff --git a/platform/macos/dir_access_macos.mm b/platform/macos/dir_access_macos.mm
index 66d81f2687..37f717c9de 100644
--- a/platform/macos/dir_access_macos.mm
+++ b/platform/macos/dir_access_macos.mm
@@ -41,9 +41,10 @@
String DirAccessMacOS::fix_unicode_name(const char *p_name) const {
String fname;
- NSString *nsstr = [[NSString stringWithUTF8String:p_name] precomposedStringWithCanonicalMapping];
-
- fname.parse_utf8([nsstr UTF8String]);
+ if (p_name != nullptr) {
+ NSString *nsstr = [[NSString stringWithUTF8String:p_name] precomposedStringWithCanonicalMapping];
+ fname.parse_utf8([nsstr UTF8String]);
+ }
return fname;
}
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index 7373a40237..5d38bf55ea 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -39,6 +39,8 @@
#include "gl_manager_macos_legacy.h"
#endif // GLES3_ENABLED
+#include "native_menu_macos.h"
+
#if defined(RD_ENABLED)
#include "servers/rendering/rendering_device.h"
@@ -142,19 +144,6 @@ private:
#endif
String rendering_driver;
- NSMenu *apple_menu = nullptr;
- NSMenu *window_menu = nullptr;
- NSMenu *help_menu = nullptr;
- NSMenu *dock_menu = nullptr;
- struct MenuData {
- Callable open;
- Callable close;
- NSMenu *menu = nullptr;
- bool is_open = false;
- };
- HashMap<String, MenuData> submenu;
- HashMap<NSMenu *, String> submenu_inv;
-
struct WarpEvent {
NSTimeInterval timestamp;
NSPoint delta;
@@ -168,6 +157,7 @@ private:
id tts = nullptr;
id menu_delegate = nullptr;
+ NativeMenuMacOS *native_menu = nullptr;
Point2i im_selection;
String im_text;
@@ -222,15 +212,10 @@ private:
Callable system_theme_changed;
- const NSMenu *_get_menu_root(const String &p_menu_root) const;
- NSMenu *_get_menu_root(const String &p_menu_root);
- bool _is_menu_opened(NSMenu *p_menu) const;
-
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect);
void _update_window_style(WindowData p_wd);
void _update_displays_arrangement();
- Point2i _get_screens_origin() const;
Point2i _get_native_screen_position(int p_screen) const;
static void _displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info);
@@ -240,27 +225,24 @@ private:
void _process_key_events();
void _update_keyboard_layouts();
static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info);
- NSImage *_convert_to_nsimg(Ref<Image> &p_image) const;
static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);
- int _get_system_menu_start(const NSMenu *p_menu) const;
- int _get_system_menu_count(const NSMenu *p_menu) const;
- NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out);
-
Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb);
public:
- NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
- void menu_open(NSMenu *p_menu);
- void menu_close(NSMenu *p_menu);
void emit_system_theme_changed();
bool has_window(WindowID p_window) const;
WindowData &get_window(WindowID p_window);
+ NSImage *_convert_to_nsimg(Ref<Image> &p_image) const;
+ Point2i _get_screens_origin() const;
+
+ void set_menu_delegate(NSMenu *p_menu);
+
void send_event(NSEvent *p_event);
void send_window_event(const WindowData &p_wd, WindowEvent p_event);
void release_pressed_events();
@@ -293,63 +275,6 @@ public:
Callable _help_get_search_callback() const;
Callable _help_get_action_callback() const;
- virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override;
-
- virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
- virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
-
- virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override;
- virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const override;
-
- virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const override;
- virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override;
- virtual bool global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const override;
- virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) const override;
- virtual Callable global_menu_get_item_key_callback(const String &p_menu_root, int p_idx) const override;
- virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const override;
- virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const override;
- virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override;
- virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override;
- virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override;
- virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const override;
- virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override;
- virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override;
- virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override;
- virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const override;
- virtual int global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const override;
-
- virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override;
- virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
- virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
- virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
- virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) override;
- virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
- virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override;
- virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override;
- virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override;
- virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override;
- virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override;
- virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) override;
- virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override;
- virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override;
- virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override;
- virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) override;
- virtual void global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) override;
-
- virtual int global_menu_get_item_count(const String &p_menu_root) const override;
-
- virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override;
- virtual void global_menu_clear(const String &p_menu_root) override;
-
- virtual Dictionary global_menu_get_system_menu_roots() const override;
-
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
virtual TypedArray<Dictionary> tts_get_voices() const override;
@@ -501,7 +426,6 @@ public:
virtual void force_process_and_drop_events() override;
virtual void release_rendering_thread() override;
- virtual void make_rendering_thread() override;
virtual void swap_buffers() override;
virtual void set_native_icon(const String &p_filename) override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index d8e546f571..cfe925e79b 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -48,7 +48,6 @@
#include "core/os/keyboard.h"
#include "drivers/png/png_driver_common.h"
#include "main/main.h"
-#include "scene/resources/atlas_texture.h"
#include "scene/resources/image_texture.h"
#if defined(GLES3_ENABLED)
@@ -66,66 +65,6 @@
#import <IOKit/hid/IOHIDKeys.h>
#import <IOKit/hid/IOHIDLib.h>
-#define MENU_TAG_START 0x00004447
-#define MENU_TAG_END 0xFFFF4447
-
-const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) const {
- const NSMenu *menu = nullptr;
- if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
- // Main menu.
- menu = [NSApp mainMenu];
- } else if (p_menu_root.to_lower() == "_dock") {
- // macOS dock menu.
- menu = dock_menu;
- } else if (p_menu_root.to_lower() == "_apple") {
- // macOS Apple menu.
- menu = apple_menu;
- } else if (p_menu_root.to_lower() == "_window") {
- // macOS Window menu.
- menu = window_menu;
- } else if (p_menu_root.to_lower() == "_help") {
- // macOS Help menu.
- menu = help_menu;
- } else {
- // Submenu.
- if (submenu.has(p_menu_root)) {
- menu = submenu[p_menu_root].menu;
- }
- }
- return menu;
-}
-
-NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
- NSMenu *menu = nullptr;
- if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
- // Main menu.
- menu = [NSApp mainMenu];
- } else if (p_menu_root.to_lower() == "_dock") {
- // macOS dock menu.
- menu = dock_menu;
- } else if (p_menu_root.to_lower() == "_apple") {
- // macOS Apple menu.
- menu = apple_menu;
- } else if (p_menu_root.to_lower() == "_window") {
- // macOS Window menu.
- menu = window_menu;
- } else if (p_menu_root.to_lower() == "_help") {
- // macOS Help menu.
- menu = help_menu;
- } else {
- // Submenu.
- if (!submenu.has(p_menu_root)) {
- NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
- [n_menu setAutoenablesItems:NO];
- [n_menu setDelegate:menu_delegate];
- submenu[p_menu_root].menu = n_menu;
- submenu_inv[n_menu] = p_menu_root;
- }
- menu = submenu[p_menu_root].menu;
- }
- return menu;
-}
-
DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect) {
WindowID id;
const float scale = screen_get_max_scale();
@@ -144,8 +83,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
Rect2i srect = screen_get_usable_rect(rq_screen);
Point2i wpos = p_rect.position;
if (srect != Rect2i()) {
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);
}
// macOS native y-coordinate relative to _get_screens_origin() is negative,
// Godot passes a positive value.
@@ -352,6 +290,10 @@ void DisplayServerMacOS::_update_displays_arrangement() {
displays_arrangement_dirty = false;
}
+void DisplayServerMacOS::set_menu_delegate(NSMenu *p_menu) {
+ [p_menu setDelegate:menu_delegate];
+}
+
Point2i DisplayServerMacOS::_get_screens_origin() const {
// Returns the native top-left screen coordinate of the smallest rectangle
// that encompasses all screens. Needed in get_screen_position(),
@@ -416,7 +358,6 @@ void DisplayServerMacOS::_dispatch_input_events(const Ref<InputEvent> &p_event)
}
void DisplayServerMacOS::_dispatch_input_event(const Ref<InputEvent> &p_event) {
- _THREAD_SAFE_METHOD_
if (!in_dispatch_input_event) {
in_dispatch_input_event = true;
@@ -576,7 +517,7 @@ void DisplayServerMacOS::_keyboard_layout_changed(CFNotificationCenterRef center
NSImage *DisplayServerMacOS::_convert_to_nsimg(Ref<Image> &p_image) const {
p_image->convert(Image::FORMAT_RGBA8);
NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
- initWithBitmapDataPlanes:NULL
+ initWithBitmapDataPlanes:nullptr
pixelsWide:p_image->get_width()
pixelsHigh:p_image->get_height()
bitsPerSample:8
@@ -621,42 +562,6 @@ NSCursor *DisplayServerMacOS::_cursor_from_selector(SEL p_selector, SEL p_fallba
return [NSCursor arrowCursor];
}
-NSMenu *DisplayServerMacOS::get_dock_menu() const {
- return dock_menu;
-}
-
-void DisplayServerMacOS::menu_open(NSMenu *p_menu) {
- if (submenu_inv.has(p_menu)) {
- MenuData &md = submenu[submenu_inv[p_menu]];
- md.is_open = true;
- if (md.open.is_valid()) {
- Variant ret;
- Callable::CallError ce;
-
- md.open.callp(nullptr, 0, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute menu open callback: %s.", Variant::get_callable_error_text(md.open, nullptr, 0, ce)));
- }
- }
- }
-}
-
-void DisplayServerMacOS::menu_close(NSMenu *p_menu) {
- if (submenu_inv.has(p_menu)) {
- MenuData &md = submenu[submenu_inv[p_menu]];
- md.is_open = false;
- if (md.close.is_valid()) {
- Variant ret;
- Callable::CallError ce;
-
- md.close.callp(nullptr, 0, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md.close, nullptr, 0, ce)));
- }
- }
- }
-}
-
void DisplayServerMacOS::menu_callback(id p_sender) {
if (![p_sender representedObject]) {
return;
@@ -840,7 +745,11 @@ void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_hei
bool DisplayServerMacOS::has_feature(Feature p_feature) const {
switch (p_feature) {
- case FEATURE_GLOBAL_MENU:
+#ifndef DISABLE_DEPRECATED
+ case FEATURE_GLOBAL_MENU: {
+ return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
+ } break;
+#endif
case FEATURE_SUBWINDOWS:
//case FEATURE_TOUCHSCREEN:
case FEATURE_MOUSE:
@@ -849,6 +758,8 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_CURSOR_SHAPE:
case FEATURE_CUSTOM_CURSOR_SHAPE:
case FEATURE_NATIVE_DIALOG:
+ case FEATURE_NATIVE_DIALOG_INPUT:
+ case FEATURE_NATIVE_DIALOG_FILE:
case FEATURE_IME:
case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_HIDPI:
@@ -885,1135 +796,6 @@ Callable DisplayServerMacOS::_help_get_action_callback() const {
return help_action_callback;
}
-bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const {
- if (submenu_inv.has(p_menu)) {
- const MenuData &md = submenu[submenu_inv[p_menu]];
- if (md.is_open) {
- return true;
- }
- }
- for (NSInteger i = (p_menu == [NSApp mainMenu]) ? 1 : 0; i < [p_menu numberOfItems]; i++) {
- const NSMenuItem *menu_item = [p_menu itemAtIndex:i];
- if ([menu_item submenu]) {
- if (_is_menu_opened([menu_item submenu])) {
- return true;
- }
- }
- }
- return false;
-}
-
-int DisplayServerMacOS::_get_system_menu_start(const NSMenu *p_menu) const {
- if (p_menu == [NSApp mainMenu]) { // Skip Apple menu.
- return 1;
- }
- if (p_menu == apple_menu || p_menu == window_menu || p_menu == help_menu) {
- int count = [p_menu numberOfItems];
- for (int i = 0; i < count; i++) {
- NSMenuItem *menu_item = [p_menu itemAtIndex:i];
- if (menu_item.tag == MENU_TAG_START) {
- return i + 1;
- }
- }
- }
- return 0;
-}
-
-int DisplayServerMacOS::_get_system_menu_count(const NSMenu *p_menu) const {
- if (p_menu == [NSApp mainMenu]) { // Skip Apple, Window and Help menu.
- return [p_menu numberOfItems] - 3;
- }
- if (p_menu == apple_menu || p_menu == window_menu || p_menu == help_menu) {
- int start = 0;
- int count = [p_menu numberOfItems];
- for (int i = 0; i < count; i++) {
- NSMenuItem *menu_item = [p_menu itemAtIndex:i];
- if (menu_item.tag == MENU_TAG_START) {
- start = i + 1;
- }
- if (menu_item.tag == MENU_TAG_END) {
- return i - start;
- }
- }
- }
- return [p_menu numberOfItems];
-}
-
-NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) {
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- if (p_index < 0) {
- p_index = item_start + item_count;
- } else {
- p_index += item_start;
- p_index = CLAMP(p_index, item_start, item_start + item_count);
- }
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- *r_out = p_index - item_start;
- return menu_item;
- }
- return nullptr;
-}
-
-int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
-
- int out = -1;
- NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
- if (menu_item) {
- GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = p_callback;
- obj->key_callback = p_key_callback;
- obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_NONE;
- obj->max_states = 0;
- obj->state = 0;
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
- [menu_item setRepresentedObject:obj];
- }
- return out;
-}
-
-int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
-
- int out = -1;
- NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
- if (menu_item) {
- GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = p_callback;
- obj->key_callback = p_key_callback;
- obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
- obj->max_states = 0;
- obj->state = 0;
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
- [menu_item setRepresentedObject:obj];
- }
- return out;
-}
-
-int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
-
- int out = -1;
- NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
- if (menu_item) {
- GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = p_callback;
- obj->key_callback = p_key_callback;
- obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_NONE;
- obj->max_states = 0;
- obj->state = 0;
- if (p_icon.is_valid()) {
- obj->img = p_icon->get_image();
- obj->img = obj->img->duplicate();
- if (obj->img->is_compressed()) {
- obj->img->decompress();
- }
- obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS);
- [menu_item setImage:_convert_to_nsimg(obj->img)];
- }
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
- [menu_item setRepresentedObject:obj];
- }
- return out;
-}
-
-int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
-
- int out = -1;
- NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
- if (menu_item) {
- GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = p_callback;
- obj->key_callback = p_key_callback;
- obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
- obj->max_states = 0;
- obj->state = 0;
- if (p_icon.is_valid()) {
- obj->img = p_icon->get_image();
- obj->img = obj->img->duplicate();
- if (obj->img->is_compressed()) {
- obj->img->decompress();
- }
- obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS);
- [menu_item setImage:_convert_to_nsimg(obj->img)];
- }
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
- [menu_item setRepresentedObject:obj];
- }
- return out;
-}
-
-int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
-
- int out = -1;
- NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
- if (menu_item) {
- GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = p_callback;
- obj->key_callback = p_key_callback;
- obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
- obj->max_states = 0;
- obj->state = 0;
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
- [menu_item setRepresentedObject:obj];
- }
- return out;
-}
-
-int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
-
- int out = -1;
- NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
- if (menu_item) {
- GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = p_callback;
- obj->key_callback = p_key_callback;
- obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
- obj->max_states = 0;
- obj->state = 0;
- if (p_icon.is_valid()) {
- obj->img = p_icon->get_image();
- obj->img = obj->img->duplicate();
- if (obj->img->is_compressed()) {
- obj->img->decompress();
- }
- obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS);
- [menu_item setImage:_convert_to_nsimg(obj->img)];
- }
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
- [menu_item setRepresentedObject:obj];
- }
- return out;
-}
-
-int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
-
- int out = -1;
- NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
- if (menu_item) {
- GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = p_callback;
- obj->key_callback = p_key_callback;
- obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_NONE;
- obj->max_states = p_max_states;
- obj->state = p_default_state;
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
- [menu_item setRepresentedObject:obj];
- }
- return out;
-}
-
-void DisplayServerMacOS::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback, const Callable &p_close_callback) {
- _THREAD_SAFE_METHOD_
-
- if (p_menu_root != "" && p_menu_root.to_lower() != "_main" && p_menu_root.to_lower() != "_dock") {
- // Submenu.
- if (!submenu.has(p_menu_root)) {
- NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
- [n_menu setAutoenablesItems:NO];
- [n_menu setDelegate:menu_delegate];
- submenu[p_menu_root].menu = n_menu;
- submenu_inv[n_menu] = p_menu_root;
- }
- submenu[p_menu_root].open = p_open_callback;
- submenu[p_menu_root].close = p_close_callback;
- }
-}
-
-int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- NSMenu *sub_menu = _get_menu_root(p_submenu);
- int out = -1;
- if (menu && sub_menu) {
- if (sub_menu == menu) {
- ERR_PRINT("Can't set submenu to self!");
- return -1;
- }
- if ([sub_menu supermenu]) {
- ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
- return -1;
- }
- NSMenuItem *menu_item;
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- if (p_index < 0) {
- p_index = item_start + item_count;
- } else {
- p_index += item_start;
- p_index = CLAMP(p_index, item_start, item_start + item_count);
- }
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
- out = p_index - item_start;
-
- GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = Callable();
- obj->checkable_type = CHECKABLE_TYPE_NONE;
- obj->max_states = 0;
- obj->state = 0;
- [menu_item setRepresentedObject:obj];
-
- [sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
- [menu setSubmenu:sub_menu forItem:menu_item];
- }
- return out;
-}
-
-int DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- if (menu == [NSApp mainMenu]) { // Do not add separators into main menu.
- return -1;
- }
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- if (p_index < 0) {
- p_index = item_start + item_count;
- } else {
- p_index += item_start;
- p_index = CLAMP(p_index, item_start, item_start + item_count);
- }
- [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
- return p_index - item_start;
- }
- return -1;
-}
-
-int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- int item_start = _get_system_menu_start(menu);
- return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - item_start;
- }
-
- return -1;
-}
-
-int DisplayServerMacOS::global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- for (NSInteger i = item_start; i < item_start + item_count; i++) {
- const NSMenuItem *menu_item = [menu itemAtIndex:i];
- if (menu_item) {
- const GodotMenuItem *obj = [menu_item representedObject];
- if (obj && obj->meta == p_tag) {
- return i - item_start;
- }
- }
- }
- }
-
- return -1;
-}
-
-bool DisplayServerMacOS::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, false);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- return ([menu_item state] == NSControlStateValueOn);
- }
- }
- return false;
-}
-
-bool DisplayServerMacOS::global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, false);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX;
- }
- }
- }
- return false;
-}
-
-bool DisplayServerMacOS::global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, false);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON;
- }
- }
- }
- return false;
-}
-
-Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, Callable());
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- return obj->callback;
- }
- }
- }
- return Callable();
-}
-
-Callable DisplayServerMacOS::global_menu_get_item_key_callback(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, Callable());
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- return obj->key_callback;
- }
- }
- }
- return Callable();
-}
-
-Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, Variant());
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, Variant());
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- return obj->meta;
- }
- }
- }
- return Variant();
-}
-
-String DisplayServerMacOS::global_menu_get_item_text(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, String());
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- return String::utf8([[menu_item title] UTF8String]);
- }
- }
- return String();
-}
-
-String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, String());
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- NSMenu *sub_menu = [menu_item submenu];
- if (sub_menu && submenu_inv.has(sub_menu)) {
- return submenu_inv[sub_menu];
- }
- }
- }
- return String();
-}
-
-Key DisplayServerMacOS::global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, Key::NONE);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, Key::NONE);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
- Key keycode = find_keycode(ret);
- NSUInteger mask = [menu_item keyEquivalentModifierMask];
- if (mask & NSEventModifierFlagControl) {
- keycode |= KeyModifierMask::CTRL;
- }
- if (mask & NSEventModifierFlagOption) {
- keycode |= KeyModifierMask::ALT;
- }
- if (mask & NSEventModifierFlagShift) {
- keycode |= KeyModifierMask::SHIFT;
- }
- if (mask & NSEventModifierFlagCommand) {
- keycode |= KeyModifierMask::META;
- }
- if (mask & NSEventModifierFlagNumericPad) {
- keycode |= KeyModifierMask::KPAD;
- }
- return keycode;
- }
- }
- return Key::NONE;
-}
-
-bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, false);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- return ![menu_item isEnabled];
- }
- }
- return false;
-}
-
-bool DisplayServerMacOS::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, false);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- return [menu_item isHidden];
- }
- }
- return false;
-}
-
-String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, String());
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- return String::utf8([[menu_item toolTip] UTF8String]);
- }
- }
- return String();
-}
-
-int DisplayServerMacOS::global_menu_get_item_state(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- return obj->state;
- }
- }
- }
- return 0;
-}
-
-int DisplayServerMacOS::global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- return obj->max_states;
- }
- }
- }
- return 0;
-}
-
-Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, Ref<Texture2D>());
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- if (obj->img.is_valid()) {
- return ImageTexture::create_from_image(obj->img);
- }
- }
- }
- }
- return Ref<Texture2D>();
-}
-
-int DisplayServerMacOS::global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND_V(p_idx < 0, 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
- const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- return [menu_item indentationLevel];
- }
- }
- return 0;
-}
-
-void DisplayServerMacOS::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- if (p_checked) {
- [menu_item setState:NSControlStateValueOn];
- } else {
- [menu_item setState:NSControlStateValueOff];
- }
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE;
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- obj->callback = p_callback;
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- obj->hover_callback = p_callback;
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- obj->key_callback = p_key_callback;
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- obj->meta = p_tag;
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
- NSMenu *sub_menu = [menu_item submenu];
- if (sub_menu) {
- [sub_menu setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
- }
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu && p_submenu.is_empty()) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
- ERR_PRINT("Can't remove open menu!");
- return;
- }
- [menu setSubmenu:nil forItem:menu_item];
- }
- return;
- }
-
- NSMenu *sub_menu = _get_menu_root(p_submenu);
- if (menu && sub_menu) {
- if (sub_menu == menu) {
- ERR_PRINT("Can't set submenu to self!");
- return;
- }
- if ([sub_menu supermenu]) {
- ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
- return;
- }
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- [menu setSubmenu:sub_menu forItem:menu_item];
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- if (p_keycode == Key::NONE) {
- [menu_item setKeyEquivalent:@""];
- } else {
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
- [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- [menu_item setEnabled:(!p_disabled)];
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- [menu_item setHidden:p_hidden];
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- obj->state = p_state;
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- obj->max_states = p_max_states;
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_NULL(obj);
- if (p_icon.is_valid()) {
- obj->img = p_icon->get_image();
- obj->img = obj->img->duplicate();
- if (obj->img->is_compressed()) {
- obj->img->decompress();
- }
- obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS);
- [menu_item setImage:_convert_to_nsimg(obj->img)];
- } else {
- obj->img = Ref<Image>();
- [menu_item setImage:nil];
- }
- }
- }
-}
-
-void DisplayServerMacOS::global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if (menu_item) {
- [menu_item setIndentationLevel:p_level];
- }
- }
-}
-
-int DisplayServerMacOS::global_menu_get_item_count(const String &p_menu_root) const {
- _THREAD_SAFE_METHOD_
-
- const NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- return _get_system_menu_count(menu);
- } else {
- return 0;
- }
-}
-
-void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int p_idx) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- ERR_FAIL_COND(p_idx < 0);
- int item_start = _get_system_menu_start(menu);
- int item_count = _get_system_menu_count(menu);
- p_idx += item_start;
- ERR_FAIL_COND(p_idx >= item_start + item_count);
- NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
- if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
- ERR_PRINT("Can't remove open menu!");
- return;
- }
- [menu removeItemAtIndex:p_idx];
- }
-}
-
-void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
- _THREAD_SAFE_METHOD_
-
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- if (_is_menu_opened(menu)) {
- ERR_PRINT("Can't remove open menu!");
- return;
- }
-
- if (menu == apple_menu) {
- int start = _get_system_menu_start(apple_menu);
- int count = _get_system_menu_count(apple_menu);
- for (int i = start + count - 1; i >= start; i--) {
- [apple_menu removeItemAtIndex:i];
- }
- } else if (menu == window_menu) {
- int start = _get_system_menu_start(window_menu);
- int count = _get_system_menu_count(window_menu);
- for (int i = start + count - 1; i >= start; i--) {
- [window_menu removeItemAtIndex:i];
- }
- } else if (menu == help_menu) {
- int start = _get_system_menu_start(help_menu);
- int count = _get_system_menu_count(help_menu);
- for (int i = start + count - 1; i >= start; i--) {
- [help_menu removeItemAtIndex:i];
- }
- } else {
- [menu removeAllItems];
- }
-
- // Restore Apple, Window and Help menu.
- if (menu == [NSApp mainMenu]) {
- NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
- [menu setSubmenu:apple_menu forItem:menu_item];
-
- menu_item = [menu addItemWithTitle:@"Window" action:nil keyEquivalent:@""];
- [menu setSubmenu:window_menu forItem:menu_item];
-
- menu_item = [menu addItemWithTitle:@"Help" action:nil keyEquivalent:@""];
- [menu setSubmenu:help_menu forItem:menu_item];
- }
-
- if (submenu.has(p_menu_root)) {
- submenu_inv.erase(submenu[p_menu_root].menu);
- submenu.erase(p_menu_root);
- }
- }
-}
-
-Dictionary DisplayServerMacOS::global_menu_get_system_menu_roots() const {
- Dictionary out;
- out["_dock"] = "@Dock";
- out["_apple"] = "@Apple";
- out["_window"] = "Window";
- out["_help"] = "Help";
- return out;
-}
-
bool DisplayServerMacOS::tts_is_speaking() const {
ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
return [tts isSpeaking];
@@ -2686,7 +1468,7 @@ Ref<Image> DisplayServerMacOS::clipboard_get_image() const {
return image;
}
NSBitmapImageRep *bitmap = [NSBitmapImageRep imageRepWithData:data];
- NSData *pngData = [bitmap representationUsingType:NSPNGFileType properties:@{}];
+ NSData *pngData = [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
image.instantiate();
PNGDriverCommon::png_to_image((const uint8_t *)pngData.bytes, pngData.length, false, image);
return image;
@@ -3099,8 +1881,7 @@ void DisplayServerMacOS::window_set_current_screen(int p_screen, WindowID p_wind
Size2i wsize = window_get_size(p_window);
wpos += srect.position;
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);
window_set_position(wpos, p_window);
if (was_fullscreen) {
@@ -3213,7 +1994,7 @@ void DisplayServerMacOS::window_set_position(const Point2i &p_position, WindowID
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
- if (NSEqualRects([wd.window_object frame], [[wd.window_object screen] visibleFrame])) {
+ if (wd.fullscreen) {
return;
}
@@ -3302,12 +2083,21 @@ Size2i DisplayServerMacOS::window_get_max_size(WindowID p_window) const {
}
void DisplayServerMacOS::update_presentation_mode() {
+ bool has_fs_windows = false;
for (const KeyValue<WindowID, WindowData> &wd : windows) {
- if (wd.value.fullscreen && wd.value.exclusive_fullscreen) {
- return;
+ if (wd.value.fullscreen) {
+ if (wd.value.exclusive_fullscreen) {
+ return;
+ } else {
+ has_fs_windows = true;
+ }
}
}
- [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ if (has_fs_windows) {
+ [NSApp setPresentationOptions:NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock | NSApplicationPresentationFullScreen];
+ } else {
+ [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ }
}
void DisplayServerMacOS::window_set_min_size(const Size2i p_size, WindowID p_window) {
@@ -3531,8 +2321,7 @@ void DisplayServerMacOS::window_set_window_buttons_offset(const Vector2i &p_offs
WindowData &wd = windows[p_window];
float scale = screen_get_max_scale();
wd.wb_offset = p_offset / scale;
- wd.wb_offset.x = MAX(wd.wb_offset.x, 12);
- wd.wb_offset.y = MAX(wd.wb_offset.y, 12);
+ wd.wb_offset = wd.wb_offset.max(Vector2i(12, 12));
if (wd.window_button_view) {
[wd.window_button_view setOffset:NSMakePoint(wd.wb_offset.x, wd.wb_offset.y)];
}
@@ -4037,39 +2826,10 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor,
cursors_cache.erase(p_shape);
}
- Ref<Texture2D> texture = p_cursor;
- ERR_FAIL_COND(!texture.is_valid());
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Size2 texture_size;
Rect2 atlas_rect;
-
- if (atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
-
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- 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.");
- }
+ Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
+ ERR_FAIL_COND(image.is_null());
+ Vector2i texture_size = image->get_size();
NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:nullptr
@@ -4092,7 +2852,7 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor,
int row_index = floor(i / texture_size.width) + atlas_rect.position.y;
int column_index = (i % int(texture_size.width)) + atlas_rect.position.x;
- if (atlas_texture.is_valid()) {
+ if (atlas_rect.has_area()) {
column_index = MIN(column_index, atlas_rect.size.width - 1);
row_index = MIN(row_index, atlas_rect.size.height - 1);
}
@@ -4102,7 +2862,7 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor,
uint8_t alpha = (color >> 24) & 0xFF;
pixels[i * 4 + 0] = ((color >> 16) & 0xFF) * alpha / 255;
pixels[i * 4 + 1] = ((color >> 8) & 0xFF) * alpha / 255;
- pixels[i * 4 + 2] = ((color)&0xFF) * alpha / 255;
+ pixels[i * 4 + 2] = ((color) & 0xFF) * alpha / 255;
pixels[i * 4 + 3] = alpha;
}
@@ -4225,7 +2985,7 @@ Key DisplayServerMacOS::keyboard_get_label_from_physical(Key p_keycode) const {
}
void DisplayServerMacOS::process_events() {
- _THREAD_SAFE_METHOD_
+ _THREAD_SAFE_LOCK_
while (true) {
NSEvent *event = [NSApp
@@ -4258,7 +3018,9 @@ void DisplayServerMacOS::process_events() {
if (!drop_events) {
_process_key_events();
+ _THREAD_SAFE_UNLOCK_
Input::get_singleton()->flush_buffered_events();
+ _THREAD_SAFE_LOCK_
}
for (KeyValue<WindowID, WindowData> &E : windows) {
@@ -4284,6 +3046,8 @@ void DisplayServerMacOS::process_events() {
}
}
}
+
+ _THREAD_SAFE_UNLOCK_
}
void DisplayServerMacOS::force_process_and_drop_events() {
@@ -4295,9 +3059,14 @@ void DisplayServerMacOS::force_process_and_drop_events() {
}
void DisplayServerMacOS::release_rendering_thread() {
-}
-
-void DisplayServerMacOS::make_rendering_thread() {
+#if defined(GLES3_ENABLED)
+ if (gl_manager_angle) {
+ gl_manager_angle->release_current();
+ }
+ if (gl_manager_legacy) {
+ gl_manager_legacy->release_current();
+ }
+#endif
}
void DisplayServerMacOS::swap_buffers() {
@@ -4720,6 +3489,8 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
tts = [[TTS_MacOS alloc] init];
}
+ native_menu = memnew(NativeMenuMacOS);
+
NSMenuItem *menu_item;
NSString *title;
@@ -4731,47 +3502,47 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
menu_delegate = [[GodotMenuDelegate alloc] init];
// Setup Dock menu.
- dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
+ NSMenu *dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
[dock_menu setAutoenablesItems:NO];
[dock_menu setDelegate:menu_delegate];
// Setup Apple menu.
- apple_menu = [[NSMenu alloc] initWithTitle:@""];
+ NSMenu *application_menu = [[NSMenu alloc] initWithTitle:@""];
title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
- [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
- [apple_menu setAutoenablesItems:NO];
- [apple_menu setDelegate:menu_delegate];
+ [application_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
+ [application_menu setAutoenablesItems:NO];
+ [application_menu setDelegate:menu_delegate];
- [apple_menu addItem:[NSMenuItem separatorItem]];
+ [application_menu addItem:[NSMenuItem separatorItem]];
- menu_item = [apple_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""];
+ menu_item = [application_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""];
menu_item.hidden = YES;
menu_item.tag = MENU_TAG_START;
- menu_item = [apple_menu addItemWithTitle:@"_end_" action:nil keyEquivalent:@""];
+ menu_item = [application_menu addItemWithTitle:@"_end_" action:nil keyEquivalent:@""];
menu_item.hidden = YES;
menu_item.tag = MENU_TAG_END;
NSMenu *services = [[NSMenu alloc] initWithTitle:@""];
- menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""];
- [apple_menu setSubmenu:services forItem:menu_item];
+ menu_item = [application_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""];
+ [application_menu setSubmenu:services forItem:menu_item];
[NSApp setServicesMenu:services];
- [apple_menu addItem:[NSMenuItem separatorItem]];
+ [application_menu addItem:[NSMenuItem separatorItem]];
title = [NSString stringWithFormat:NSLocalizedString(@"Hide %@", nil), nsappname];
- [apple_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
+ [application_menu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
- menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+ menu_item = [application_menu addItemWithTitle:NSLocalizedString(@"Hide Others", nil) action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
[menu_item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | NSEventModifierFlagCommand)];
- [apple_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""];
+ [application_menu addItemWithTitle:NSLocalizedString(@"Show all", nil) action:@selector(unhideAllApplications:) keyEquivalent:@""];
- [apple_menu addItem:[NSMenuItem separatorItem]];
+ [application_menu addItem:[NSMenuItem separatorItem]];
title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
- [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+ [application_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
- window_menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Window", nil)];
+ NSMenu *window_menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Window", nil)];
[window_menu addItemWithTitle:NSLocalizedString(@"Minimize", nil) action:@selector(performMiniaturize:) keyEquivalent:@"m"];
[window_menu addItemWithTitle:NSLocalizedString(@"Zoom", nil) action:@selector(performZoom:) keyEquivalent:@""];
[window_menu addItem:[NSMenuItem separatorItem]];
@@ -4784,7 +3555,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
menu_item.hidden = YES;
menu_item.tag = MENU_TAG_END;
- help_menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Help", nil)];
+ NSMenu *help_menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Help", nil)];
menu_item = [help_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""];
menu_item.hidden = YES;
menu_item.tag = MENU_TAG_START;
@@ -4798,7 +3569,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
// Add items to the menu bar.
NSMenu *main_menu = [NSApp mainMenu];
menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
- [main_menu setSubmenu:apple_menu forItem:menu_item];
+ [main_menu setSubmenu:application_menu forItem:menu_item];
menu_item = [main_menu addItemWithTitle:NSLocalizedString(@"Window", nil) action:nil keyEquivalent:@""];
[main_menu setSubmenu:window_menu forItem:menu_item];
@@ -4808,6 +3579,8 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
[main_menu setAutoenablesItems:NO];
+ native_menu->_register_system_menus(main_menu, application_menu, window_menu, help_menu, dock_menu);
+
//!!!!!!!!!!!!!!!!!!!!!!!!!!
//TODO - do Vulkan and OpenGL support checks, driver selection and fallback
rendering_driver = p_rendering_driver;
@@ -4905,10 +3678,16 @@ DisplayServerMacOS::~DisplayServerMacOS() {
}
// Destroy all status indicators.
- for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
+ for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E; ++E) {
[[NSStatusBar systemStatusBar] removeStatusItem:E->value.item];
}
+ // Destroy native menu.
+ if (native_menu) {
+ memdelete(native_menu);
+ native_menu = nullptr;
+ }
+
// Destroy all windows.
for (HashMap<WindowID, WindowData>::Iterator E = windows.begin(); E;) {
HashMap<WindowID, WindowData>::Iterator F = E;
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index 506b0dffb8..7355042a48 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -12,7 +12,7 @@
<members>
<member name="application/additional_plist_content" type="String" setter="" getter="">
Additional data added to the root [code]&lt;dict&gt;[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.:
- [codeblock]
+ [codeblock lang=text]
&lt;key&gt;key_name&lt;/key&gt;
&lt;string&gt;value&lt;/string&gt;
[/codeblock]
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 9cc57e4066..d75def9b50 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -333,6 +333,12 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP
return false;
}
}
+
+ // Hide unsupported .NET embedding option.
+ if (p_option == "dotnet/embed_build_outputs") {
+ return false;
+ }
+
return true;
}
diff --git a/platform/macos/gl_manager_macos_legacy.h b/platform/macos/gl_manager_macos_legacy.h
index bafe825efb..af9be8f5ba 100644
--- a/platform/macos/gl_manager_macos_legacy.h
+++ b/platform/macos/gl_manager_macos_legacy.h
@@ -73,7 +73,6 @@ public:
void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height);
void release_current();
- void make_current();
void swap_buffers();
void window_make_current(DisplayServer::WindowID p_window_id);
diff --git a/platform/macos/gl_manager_macos_legacy.mm b/platform/macos/gl_manager_macos_legacy.mm
index 701de6df78..6ce3831d9c 100644
--- a/platform/macos/gl_manager_macos_legacy.mm
+++ b/platform/macos/gl_manager_macos_legacy.mm
@@ -117,6 +117,7 @@ void GLManagerLegacy_MacOS::release_current() {
}
[NSOpenGLContext clearCurrentContext];
+ current_window = DisplayServer::INVALID_WINDOW_ID;
}
void GLManagerLegacy_MacOS::window_make_current(DisplayServer::WindowID p_window_id) {
@@ -133,18 +134,6 @@ void GLManagerLegacy_MacOS::window_make_current(DisplayServer::WindowID p_window
current_window = p_window_id;
}
-void GLManagerLegacy_MacOS::make_current() {
- if (current_window == DisplayServer::INVALID_WINDOW_ID) {
- return;
- }
- if (!windows.has(current_window)) {
- return;
- }
-
- GLWindow &win = windows[current_window];
- [win.context makeCurrentContext];
-}
-
void GLManagerLegacy_MacOS::swap_buffers() {
GLWindow &win = windows[current_window];
[win.context flushBuffer];
diff --git a/platform/macos/godot_application.mm b/platform/macos/godot_application.mm
index e3a744caa2..f5e1bb43bf 100644
--- a/platform/macos/godot_application.mm
+++ b/platform/macos/godot_application.mm
@@ -100,7 +100,7 @@
}
- (void)sendEvent:(NSEvent *)event {
- if ([event type] == NSSystemDefined && [event subtype] == 8) {
+ if ([event type] == NSEventTypeSystemDefined && [event subtype] == 8) {
int keyCode = (([event data1] & 0xFFFF0000) >> 16);
int keyFlags = ([event data1] & 0x0000FFFF);
int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;
diff --git a/platform/macos/godot_application_delegate.mm b/platform/macos/godot_application_delegate.mm
index 2e76d4aa97..02466bab97 100644
--- a/platform/macos/godot_application_delegate.mm
+++ b/platform/macos/godot_application_delegate.mm
@@ -31,6 +31,7 @@
#include "godot_application_delegate.h"
#include "display_server_macos.h"
+#include "native_menu_macos.h"
#include "os_macos.h"
@implementation GodotApplicationDelegate
@@ -125,7 +126,8 @@
- (void)applicationDidFinishLaunching:(NSNotification *)notice {
NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
- NSString *nsbundleid_env = [NSString stringWithUTF8String:getenv("__CFBundleIdentifier")];
+ const char *bundled_id = getenv("__CFBundleIdentifier");
+ NSString *nsbundleid_env = [NSString stringWithUTF8String:(bundled_id != nullptr) ? bundled_id : ""];
NSString *nsbundleid = [[NSBundle mainBundle] bundleIdentifier];
if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO) || ![nsbundleid isEqualToString:nsbundleid_env]) {
// If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored).
@@ -210,9 +212,9 @@
}
- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
- DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
- if (ds) {
- return ds->get_dock_menu();
+ if (NativeMenu::get_singleton()) {
+ NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton();
+ return nmenu->_get_dock_menu();
} else {
return nullptr;
}
diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm
index 3959fb686c..942c351ac0 100644
--- a/platform/macos/godot_main_macos.mm
+++ b/platform/macos/godot_main_macos.mm
@@ -69,18 +69,21 @@ int main(int argc, char **argv) {
err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]);
}
- if (err == ERR_HELP) { // Returned by --help and --version, so success.
- return 0;
- } else if (err != OK) {
- return 255;
+ if (err != OK) {
+ if (err == ERR_HELP) { // Returned by --help and --version, so success.
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
}
- bool ok;
+ int ret;
@autoreleasepool {
- ok = Main::start();
+ ret = Main::start();
}
- if (ok) {
- os.run(); // It is actually the OS that decides how to run.
+ if (ret == EXIT_SUCCESS) {
+ os.run();
+ } else {
+ os.set_exit_code(EXIT_FAILURE);
}
@autoreleasepool {
diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm
index dd57d9f251..5c1e849715 100644
--- a/platform/macos/godot_menu_delegate.mm
+++ b/platform/macos/godot_menu_delegate.mm
@@ -33,6 +33,7 @@
#include "display_server_macos.h"
#include "godot_menu_item.h"
#include "key_mapping_macos.h"
+#include "native_menu_macos.h"
@implementation GodotMenuDelegate
@@ -40,16 +41,16 @@
}
- (void)menuNeedsUpdate:(NSMenu *)menu {
- if (DisplayServer::get_singleton()) {
- DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
- ds->menu_open(menu);
+ if (NativeMenu::get_singleton()) {
+ NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton();
+ nmenu->_menu_open(menu);
}
}
- (void)menuDidClose:(NSMenu *)menu {
- if (DisplayServer::get_singleton()) {
- DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
- ds->menu_close(menu);
+ if (NativeMenu::get_singleton()) {
+ NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton();
+ nmenu->_menu_close(menu);
}
}
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index b816ea1b3a..b6e2d41c08 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -36,6 +36,9 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
+#define MENU_TAG_START 0x00004447
+#define MENU_TAG_END 0xFFFF4447
+
enum GlobalMenuCheckType {
CHECKABLE_TYPE_NONE,
CHECKABLE_TYPE_CHECK_BOX,
diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm
index 6b55b70629..6ffd939545 100644
--- a/platform/macos/godot_open_save_delegate.mm
+++ b/platform/macos/godot_open_save_delegate.mm
@@ -177,14 +177,14 @@
if ([new_allowed_types count] > 0) {
NSMutableArray *type_filters = [new_allowed_types objectAtIndex:0];
if (type_filters && [type_filters count] == 1 && [[type_filters objectAtIndex:0] isEqualToString:@"*"]) {
- [p_panel setAllowedFileTypes:@[]];
+ [p_panel setAllowedFileTypes:nil];
[p_panel setAllowsOtherFileTypes:true];
} else {
[p_panel setAllowsOtherFileTypes:false];
[p_panel setAllowedFileTypes:type_filters];
}
} else {
- [p_panel setAllowedFileTypes:@[]];
+ [p_panel setAllowedFileTypes:nil];
[p_panel setAllowsOtherFileTypes:true];
}
}
@@ -248,7 +248,7 @@
if (allowed_types && index < [allowed_types count]) {
NSMutableArray *type_filters = [allowed_types objectAtIndex:index];
if (type_filters && [type_filters count] == 1 && [[type_filters objectAtIndex:0] isEqualToString:@"*"]) {
- [dialog setAllowedFileTypes:@[]];
+ [dialog setAllowedFileTypes:nil];
[dialog setAllowsOtherFileTypes:true];
} else {
[dialog setAllowsOtherFileTypes:false];
@@ -256,7 +256,7 @@
}
cur_index = index;
} else {
- [dialog setAllowedFileTypes:@[]];
+ [dialog setAllowedFileTypes:nil];
[dialog setAllowsOtherFileTypes:true];
cur_index = -1;
}
diff --git a/platform/macos/joypad_macos.cpp b/platform/macos/joypad_macos.cpp
deleted file mode 100644
index 1fcd636a4b..0000000000
--- a/platform/macos/joypad_macos.cpp
+++ /dev/null
@@ -1,619 +0,0 @@
-/**************************************************************************/
-/* joypad_macos.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "joypad_macos.h"
-
-#include <machine/endian.h>
-
-#define GODOT_JOY_LOOP_RUN_MODE CFSTR("GodotJoypad")
-
-static JoypadMacOS *self = nullptr;
-
-joypad::joypad() {
- ff_constant_force.lMagnitude = 10000;
- ff_effect.dwDuration = 0;
- ff_effect.dwSamplePeriod = 0;
- ff_effect.dwGain = 10000;
- ff_effect.dwFlags = FFEFF_OBJECTOFFSETS;
- ff_effect.dwTriggerButton = FFEB_NOTRIGGER;
- ff_effect.dwStartDelay = 0;
- ff_effect.dwTriggerRepeatInterval = 0;
- ff_effect.lpEnvelope = nullptr;
- ff_effect.cbTypeSpecificParams = sizeof(FFCONSTANTFORCE);
- ff_effect.lpvTypeSpecificParams = &ff_constant_force;
- ff_effect.dwSize = sizeof(ff_effect);
-}
-
-void joypad::free() {
- if (device_ref) {
- IOHIDDeviceUnscheduleFromRunLoop(device_ref, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE);
- }
- if (ff_device) {
- FFDeviceReleaseEffect(ff_device, ff_object);
- FFReleaseDevice(ff_device);
- ff_device = nullptr;
- memfree(ff_axes);
- memfree(ff_directions);
- }
-}
-
-bool joypad::has_element(IOHIDElementCookie p_cookie, Vector<rec_element> *p_list) const {
- for (int i = 0; i < p_list->size(); i++) {
- if (p_cookie == p_list->get(i).cookie) {
- return true;
- }
- }
- return false;
-}
-
-int joypad::get_hid_element_state(rec_element *p_element) const {
- int value = 0;
- if (p_element && p_element->ref) {
- IOHIDValueRef valueRef;
- if (IOHIDDeviceGetValue(device_ref, p_element->ref, &valueRef) == kIOReturnSuccess) {
- value = (SInt32)IOHIDValueGetIntegerValue(valueRef);
-
- // Record min and max for auto calibration.
- if (value < p_element->min) {
- p_element->min = value;
- }
- if (value > p_element->max) {
- p_element->max = value;
- }
- }
- }
- return value;
-}
-
-void joypad::add_hid_element(IOHIDElementRef p_element) {
- const CFTypeID elementTypeID = p_element ? CFGetTypeID(p_element) : 0;
-
- if (p_element && (elementTypeID == IOHIDElementGetTypeID())) {
- const IOHIDElementCookie cookie = IOHIDElementGetCookie(p_element);
- const uint32_t usagePage = IOHIDElementGetUsagePage(p_element);
- const uint32_t usage = IOHIDElementGetUsage(p_element);
- Vector<rec_element> *list = nullptr;
-
- switch (IOHIDElementGetType(p_element)) {
- case kIOHIDElementTypeInput_Misc:
- case kIOHIDElementTypeInput_Button:
- case kIOHIDElementTypeInput_Axis: {
- switch (usagePage) {
- case kHIDPage_GenericDesktop:
- switch (usage) {
- case kHIDUsage_GD_X:
- case kHIDUsage_GD_Y:
- case kHIDUsage_GD_Z:
- case kHIDUsage_GD_Rx:
- case kHIDUsage_GD_Ry:
- case kHIDUsage_GD_Rz:
- case kHIDUsage_GD_Slider:
- case kHIDUsage_GD_Dial:
- case kHIDUsage_GD_Wheel:
- if (!has_element(cookie, &axis_elements)) {
- list = &axis_elements;
- }
- break;
-
- case kHIDUsage_GD_Hatswitch:
- if (!has_element(cookie, &hat_elements)) {
- list = &hat_elements;
- }
- break;
- case kHIDUsage_GD_DPadUp:
- case kHIDUsage_GD_DPadDown:
- case kHIDUsage_GD_DPadRight:
- case kHIDUsage_GD_DPadLeft:
- case kHIDUsage_GD_Start:
- case kHIDUsage_GD_Select:
- if (!has_element(cookie, &button_elements)) {
- list = &button_elements;
- }
- break;
- }
- break;
-
- case kHIDPage_Simulation:
- switch (usage) {
- case kHIDUsage_Sim_Rudder:
- case kHIDUsage_Sim_Throttle:
- case kHIDUsage_Sim_Accelerator:
- case kHIDUsage_Sim_Brake:
- if (!has_element(cookie, &axis_elements)) {
- list = &axis_elements;
- }
- break;
-
- default:
- break;
- }
- break;
-
- case kHIDPage_Button:
- case kHIDPage_Consumer:
- if (!has_element(cookie, &button_elements)) {
- list = &button_elements;
- }
- break;
-
- default:
- break;
- }
- } break;
-
- case kIOHIDElementTypeCollection: {
- CFArrayRef array = IOHIDElementGetChildren(p_element);
- if (array) {
- add_hid_elements(array);
- }
- } break;
-
- default:
- break;
- }
-
- if (list) { // Add to list.
- rec_element element;
-
- element.ref = p_element;
- element.usage = usage;
-
- element.min = (SInt32)IOHIDElementGetLogicalMin(p_element);
- element.max = (SInt32)IOHIDElementGetLogicalMax(p_element);
- element.cookie = IOHIDElementGetCookie(p_element);
- list->push_back(element);
- list->sort_custom<rec_element::Comparator>();
- }
- }
-}
-
-static void hid_element_added(const void *p_value, void *p_parameter) {
- joypad *joy = static_cast<joypad *>(p_parameter);
- joy->add_hid_element((IOHIDElementRef)p_value);
-}
-
-void joypad::add_hid_elements(CFArrayRef p_array) {
- CFRange range = { 0, CFArrayGetCount(p_array) };
- CFArrayApplyFunction(p_array, range, hid_element_added, this);
-}
-
-static void joypad_removed_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) {
- self->_device_removed(res, ioHIDDeviceObject);
-}
-
-static void joypad_added_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) {
- self->_device_added(res, ioHIDDeviceObject);
-}
-
-static bool is_joypad(IOHIDDeviceRef p_device_ref) {
- int usage_page = 0;
- int usage = 0;
- CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsagePageKey));
- if (refCF) {
- CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage_page);
- }
- if (usage_page != kHIDPage_GenericDesktop) {
- return false;
- }
-
- refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsageKey));
- if (refCF) {
- CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage);
- }
- if ((usage != kHIDUsage_GD_Joystick &&
- usage != kHIDUsage_GD_GamePad &&
- usage != kHIDUsage_GD_MultiAxisController)) {
- return false;
- }
- return true;
-}
-
-void JoypadMacOS::_device_added(IOReturn p_res, IOHIDDeviceRef p_device) {
- if (p_res != kIOReturnSuccess || have_device(p_device)) {
- return;
- }
-
- joypad new_joypad;
- if (is_joypad(p_device)) {
- configure_joypad(p_device, &new_joypad);
-#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
- if (IOHIDDeviceGetService) {
-#endif
- const io_service_t ioservice = IOHIDDeviceGetService(p_device);
- if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK) && new_joypad.config_force_feedback(ioservice)) {
- new_joypad.ffservice = ioservice;
- }
-#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
- }
-#endif
- device_list.push_back(new_joypad);
- }
- IOHIDDeviceScheduleWithRunLoop(p_device, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE);
-}
-
-void JoypadMacOS::_device_removed(IOReturn p_res, IOHIDDeviceRef p_device) {
- int device = get_joy_ref(p_device);
- ERR_FAIL_COND(device == -1);
-
- input->joy_connection_changed(device_list[device].id, false, "");
- device_list.write[device].free();
- device_list.remove_at(device);
-}
-
-static String _hex_str(uint8_t p_byte) {
- static const char *dict = "0123456789abcdef";
- char ret[3];
- ret[2] = 0;
-
- ret[0] = dict[p_byte >> 4];
- ret[1] = dict[p_byte & 0xF];
-
- return ret;
-}
-
-bool JoypadMacOS::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) {
- p_joy->device_ref = p_device_ref;
- // Get device name.
- String name;
- char c_name[256];
- CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey));
- if (!refCF) {
- refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDManufacturerKey));
- }
- if ((!refCF) || (!CFStringGetCString((CFStringRef)refCF, c_name, sizeof(c_name), kCFStringEncodingUTF8))) {
- name = "Unidentified Joypad";
- } else {
- name = c_name;
- }
-
- int id = input->get_unused_joy_id();
- ERR_FAIL_COND_V(id == -1, false);
- p_joy->id = id;
- int vendor = 0;
- refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVendorIDKey));
- if (refCF) {
- CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &vendor);
- }
-
- int product_id = 0;
- refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductIDKey));
- if (refCF) {
- CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &product_id);
- }
-
- int version = 0;
- refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVersionNumberKey));
- if (refCF) {
- CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &version);
- }
-
- if (vendor && product_id) {
- char uid[128];
- snprintf(uid, 128, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version));
- input->joy_connection_changed(id, true, name, uid);
- } else {
- // Bluetooth device.
- String guid = "05000000";
- for (int i = 0; i < 12; i++) {
- if (i < name.size()) {
- guid += _hex_str(name[i]);
- } else {
- guid += "00";
- }
- }
- input->joy_connection_changed(id, true, name, guid);
- }
-
- CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, nullptr, kIOHIDOptionsTypeNone);
- if (array) {
- p_joy->add_hid_elements(array);
- CFRelease(array);
- }
- // Xbox controller hat values start at 1 rather than 0.
- p_joy->offset_hat = vendor == 0x45e &&
- (product_id == 0x0b05 ||
- product_id == 0x02e0 ||
- product_id == 0x02fd ||
- product_id == 0x0b13);
-
- return true;
-}
-
-#define FF_ERR() \
- { \
- if (ret != FF_OK) { \
- FFReleaseDevice(ff_device); \
- ff_device = nullptr; \
- return false; \
- } \
- }
-bool joypad::config_force_feedback(io_service_t p_service) {
- HRESULT ret = FFCreateDevice(p_service, &ff_device);
- ERR_FAIL_COND_V(ret != FF_OK, false);
-
- ret = FFDeviceSendForceFeedbackCommand(ff_device, FFSFFC_RESET);
- FF_ERR();
-
- ret = FFDeviceSendForceFeedbackCommand(ff_device, FFSFFC_SETACTUATORSON);
- FF_ERR();
-
- if (check_ff_features()) {
- ret = FFDeviceCreateEffect(ff_device, kFFEffectType_ConstantForce_ID, &ff_effect, &ff_object);
- FF_ERR();
- return true;
- }
- FFReleaseDevice(ff_device);
- ff_device = nullptr;
- return false;
-}
-#undef FF_ERR
-
-#define TEST_FF(ff) (features.supportedEffects & (ff))
-bool joypad::check_ff_features() {
- FFCAPABILITIES features;
- HRESULT ret = FFDeviceGetForceFeedbackCapabilities(ff_device, &features);
- if (ret == FF_OK && (features.supportedEffects & FFCAP_ET_CONSTANTFORCE)) {
- uint32_t val;
- ret = FFDeviceGetForceFeedbackProperty(ff_device, FFPROP_FFGAIN, &val, sizeof(val));
- if (ret != FF_OK) {
- return false;
- }
- int num_axes = features.numFfAxes;
- ff_axes = (DWORD *)memalloc(sizeof(DWORD) * num_axes);
- ff_directions = (LONG *)memalloc(sizeof(LONG) * num_axes);
-
- for (int i = 0; i < num_axes; i++) {
- ff_axes[i] = features.ffAxes[i];
- ff_directions[i] = 0;
- }
-
- ff_effect.cAxes = num_axes;
- ff_effect.rgdwAxes = ff_axes;
- ff_effect.rglDirection = ff_directions;
- return true;
- }
- return false;
-}
-
-static BitField<HatMask> process_hat_value(int p_min, int p_max, int p_value, bool p_offset_hat) {
- int range = (p_max - p_min + 1);
- int value = p_value - p_min;
- BitField<HatMask> hat_value;
- if (range == 4) {
- value *= 2;
- }
- if (p_offset_hat) {
- value -= 1;
- }
-
- switch (value) {
- case 0:
- hat_value.set_flag(HatMask::UP);
- break;
- case 1:
- hat_value.set_flag(HatMask::UP);
- hat_value.set_flag(HatMask::RIGHT);
- break;
- case 2:
- hat_value.set_flag(HatMask::RIGHT);
- break;
- case 3:
- hat_value.set_flag(HatMask::DOWN);
- hat_value.set_flag(HatMask::RIGHT);
- break;
- case 4:
- hat_value.set_flag(HatMask::DOWN);
- break;
- case 5:
- hat_value.set_flag(HatMask::DOWN);
- hat_value.set_flag(HatMask::LEFT);
- break;
- case 6:
- hat_value.set_flag(HatMask::LEFT);
- break;
- case 7:
- hat_value.set_flag(HatMask::UP);
- hat_value.set_flag(HatMask::LEFT);
- break;
- default:
- break;
- }
- return hat_value;
-}
-
-void JoypadMacOS::poll_joypads() const {
- while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
- // No-op. Pending callbacks will fire.
- }
-}
-
-static float axis_correct(int p_value, int p_min, int p_max) {
- // Convert to a value between -1.0f and 1.0f.
- return 2.0f * (p_value - p_min) / (p_max - p_min) - 1.0f;
-}
-
-void JoypadMacOS::process_joypads() {
- poll_joypads();
-
- for (int i = 0; i < device_list.size(); i++) {
- joypad &joy = device_list.write[i];
-
- for (int j = 0; j < joy.axis_elements.size(); j++) {
- rec_element &elem = joy.axis_elements.write[j];
- int value = joy.get_hid_element_state(&elem);
- input->joy_axis(joy.id, (JoyAxis)j, axis_correct(value, elem.min, elem.max));
- }
- for (int j = 0; j < joy.button_elements.size(); j++) {
- int value = joy.get_hid_element_state(&joy.button_elements.write[j]);
- input->joy_button(joy.id, (JoyButton)j, (value >= 1));
- }
- for (int j = 0; j < joy.hat_elements.size(); j++) {
- rec_element &elem = joy.hat_elements.write[j];
- int value = joy.get_hid_element_state(&elem);
- BitField<HatMask> hat_value = process_hat_value(elem.min, elem.max, value, joy.offset_hat);
- input->joy_hat(joy.id, hat_value);
- }
-
- if (joy.ffservice) {
- uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id);
- if (timestamp > joy.ff_timestamp) {
- Vector2 strength = input->get_joy_vibration_strength(joy.id);
- float duration = input->get_joy_vibration_duration(joy.id);
- if (strength.x == 0 && strength.y == 0) {
- joypad_vibration_stop(joy.id, timestamp);
- } else {
- float gain = MAX(strength.x, strength.y);
- joypad_vibration_start(joy.id, gain, duration, timestamp);
- }
- }
- }
- }
-}
-
-void JoypadMacOS::joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp) {
- joypad *joy = &device_list.write[get_joy_index(p_id)];
- joy->ff_timestamp = p_timestamp;
- joy->ff_effect.dwDuration = p_duration * FF_SECONDS;
- joy->ff_effect.dwGain = p_magnitude * FF_FFNOMINALMAX;
- FFEffectSetParameters(joy->ff_object, &joy->ff_effect, FFEP_DURATION | FFEP_GAIN);
- FFEffectStart(joy->ff_object, 1, 0);
-}
-
-void JoypadMacOS::joypad_vibration_stop(int p_id, uint64_t p_timestamp) {
- joypad *joy = &device_list.write[get_joy_index(p_id)];
- joy->ff_timestamp = p_timestamp;
- FFEffectStop(joy->ff_object);
-}
-
-int JoypadMacOS::get_joy_index(int p_id) const {
- for (int i = 0; i < device_list.size(); i++) {
- if (device_list[i].id == p_id) {
- return i;
- }
- }
- return -1;
-}
-
-int JoypadMacOS::get_joy_ref(IOHIDDeviceRef p_device) const {
- for (int i = 0; i < device_list.size(); i++) {
- if (device_list[i].device_ref == p_device) {
- return i;
- }
- }
- return -1;
-}
-
-bool JoypadMacOS::have_device(IOHIDDeviceRef p_device) const {
- for (int i = 0; i < device_list.size(); i++) {
- if (device_list[i].device_ref == p_device) {
- return true;
- }
- }
- return false;
-}
-
-static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 usage, int *okay) {
- CFDictionaryRef retval = nullptr;
- CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
- CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
-
- if (pageNumRef && usageNumRef) {
- const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) };
- const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef };
- retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
- }
-
- if (pageNumRef) {
- CFRelease(pageNumRef);
- }
- if (usageNumRef) {
- CFRelease(usageNumRef);
- }
-
- if (!retval) {
- *okay = 0;
- }
-
- return retval;
-}
-
-void JoypadMacOS::config_hid_manager(CFArrayRef p_matching_array) const {
- CFRunLoopRef runloop = CFRunLoopGetCurrent();
- IOReturn ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone);
- ERR_FAIL_COND(ret != kIOReturnSuccess);
-
- IOHIDManagerSetDeviceMatchingMultiple(hid_manager, p_matching_array);
- IOHIDManagerRegisterDeviceMatchingCallback(hid_manager, joypad_added_callback, nullptr);
- IOHIDManagerRegisterDeviceRemovalCallback(hid_manager, joypad_removed_callback, nullptr);
- IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE);
-
- while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
- // No-op. Callback fires once per existing device.
- }
-}
-
-JoypadMacOS::JoypadMacOS(Input *in) {
- self = this;
- input = in;
-
- int okay = 1;
- const void *vals[] = {
- (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
- (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
- (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
- };
- const size_t n_elements = sizeof(vals) / sizeof(vals[0]);
- CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, n_elements, &kCFTypeArrayCallBacks) : nullptr;
-
- for (size_t i = 0; i < n_elements; i++) {
- if (vals[i]) {
- CFRelease((CFTypeRef)vals[i]);
- }
- }
-
- if (array) {
- hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
- if (hid_manager) {
- config_hid_manager(array);
- }
- CFRelease(array);
- }
-}
-
-JoypadMacOS::~JoypadMacOS() {
- for (int i = 0; i < device_list.size(); i++) {
- device_list.write[i].free();
- }
-
- IOHIDManagerUnscheduleFromRunLoop(hid_manager, CFRunLoopGetCurrent(), GODOT_JOY_LOOP_RUN_MODE);
- IOHIDManagerClose(hid_manager, kIOHIDOptionsTypeNone);
- CFRelease(hid_manager);
- hid_manager = nullptr;
-}
diff --git a/platform/macos/joypad_macos.h b/platform/macos/joypad_macos.h
index 6a2af11b51..b37a1b24f3 100644
--- a/platform/macos/joypad_macos.h
+++ b/platform/macos/joypad_macos.h
@@ -28,93 +28,62 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef JOYPAD_MACOS_H
-#define JOYPAD_MACOS_H
-
#include "core/input/input.h"
-#import <ForceFeedback/ForceFeedback.h>
-#import <ForceFeedback/ForceFeedbackConstants.h>
-#import <IOKit/hid/IOHIDLib.h>
-#import <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
-
-struct rec_element {
- IOHIDElementRef ref;
- IOHIDElementCookie cookie;
+#define Key _QKey
+#import <CoreHaptics/CoreHaptics.h>
+#import <GameController/GameController.h>
+#undef Key
- uint32_t usage = 0;
+@interface JoypadMacOSObserver : NSObject
- int min = 0;
- int max = 0;
+- (void)startObserving;
+- (void)startProcessing;
+- (void)finishObserving;
- struct Comparator {
- bool operator()(const rec_element p_a, const rec_element p_b) const { return p_a.usage < p_b.usage; }
- };
-};
+@end
-struct joypad {
- IOHIDDeviceRef device_ref = nullptr;
+API_AVAILABLE(macosx(11))
+@interface RumbleMotor : NSObject
+@property(strong, nonatomic) CHHapticEngine *engine;
+@property(strong, nonatomic) id<CHHapticPatternPlayer> player;
+@end
- Vector<rec_element> axis_elements;
- Vector<rec_element> button_elements;
- Vector<rec_element> hat_elements;
+API_AVAILABLE(macosx(11))
+@interface RumbleContext : NSObject
+// High frequency motor, it's usually the right engine.
+@property(strong, nonatomic) RumbleMotor *weak_motor;
+// Low frequency motor, it's usually the left engine.
+@property(strong, nonatomic) RumbleMotor *strong_motor;
+@end
- int id = 0;
- bool offset_hat = false;
+// Controller support for macOS begins with macOS 10.9+,
+// however haptics (vibrations) are only supported in macOS 11+.
+@interface Joypad : NSObject
- io_service_t ffservice = 0; // Interface for force feedback, 0 = no ff.
- FFCONSTANTFORCE ff_constant_force;
- FFDeviceObjectReference ff_device = nullptr;
- FFEffectObjectReference ff_object = nullptr;
- uint64_t ff_timestamp = 0;
- LONG *ff_directions = nullptr;
- FFEFFECT ff_effect;
- DWORD *ff_axes = nullptr;
+@property(assign, nonatomic) BOOL force_feedback;
+@property(assign, nonatomic) NSInteger ff_effect_timestamp;
+@property(strong, nonatomic) GCController *controller;
+@property(strong, nonatomic) RumbleContext *rumble_context API_AVAILABLE(macosx(11));
- void add_hid_elements(CFArrayRef p_array);
- void add_hid_element(IOHIDElementRef p_element);
+- (instancetype)init;
+- (instancetype)init:(GCController *)controller;
- bool has_element(IOHIDElementCookie p_cookie, Vector<rec_element> *p_list) const;
- bool config_force_feedback(io_service_t p_service);
- bool check_ff_features();
-
- int get_hid_element_state(rec_element *p_element) const;
-
- void free();
- joypad();
-};
+@end
class JoypadMacOS {
- enum {
- JOYPADS_MAX = 16,
- };
-
private:
- Input *input = nullptr;
- IOHIDManagerRef hid_manager;
-
- Vector<joypad> device_list;
-
- bool have_device(IOHIDDeviceRef p_device) const;
- bool configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy);
-
- int get_joy_index(int p_id) const;
- int get_joy_ref(IOHIDDeviceRef p_device) const;
-
- void poll_joypads() const;
- void config_hid_manager(CFArrayRef p_matching_array) const;
-
- void joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp);
- void joypad_vibration_stop(int p_id, uint64_t p_timestamp);
+ JoypadMacOSObserver *observer;
public:
- void process_joypads();
+ JoypadMacOS();
+ ~JoypadMacOS();
- void _device_added(IOReturn p_res, IOHIDDeviceRef p_device);
- void _device_removed(IOReturn p_res, IOHIDDeviceRef p_device);
+ API_AVAILABLE(macosx(11))
+ void joypad_vibration_start(Joypad *p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp);
+ API_AVAILABLE(macosx(11))
+ void joypad_vibration_stop(Joypad *p_joypad, uint64_t p_timestamp);
- JoypadMacOS(Input *in);
- ~JoypadMacOS();
+ void start_processing();
+ void process_joypads();
};
-
-#endif // JOYPAD_MACOS_H
diff --git a/platform/macos/joypad_macos.mm b/platform/macos/joypad_macos.mm
new file mode 100644
index 0000000000..8cd5cdd9f2
--- /dev/null
+++ b/platform/macos/joypad_macos.mm
@@ -0,0 +1,612 @@
+/**************************************************************************/
+/* joypad_macos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "joypad_macos.h"
+
+#include <Foundation/Foundation.h>
+
+#import "os_macos.h"
+
+#include "core/config/project_settings.h"
+#include "core/os/keyboard.h"
+#include "core/string/ustring.h"
+#include "main/main.h"
+
+@implementation RumbleMotor
+
+- (instancetype)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality {
+ self = [super init];
+ self.engine = [controller.haptics createEngineWithLocality:locality];
+ self.player = nil;
+ return self;
+}
+
+- (void)execute_pattern:(CHHapticPattern *)pattern {
+ NSError *error;
+ id<CHHapticPatternPlayer> player = [self.engine createPlayerWithPattern:pattern error:&error];
+
+ // When all players have stopped for an engine, stop the engine.
+ [self.engine notifyWhenPlayersFinished:^CHHapticEngineFinishedAction(NSError *_Nullable error) {
+ return CHHapticEngineFinishedActionStopEngine;
+ }];
+
+ self.player = player;
+
+ // Starts the engine and returns if an error was encountered.
+ if (![self.engine startAndReturnError:&error]) {
+ print_verbose("Couldn't start controller haptic engine");
+ return;
+ }
+ if (![self.player startAtTime:0 error:&error]) {
+ print_verbose("Couldn't execute controller haptic pattern");
+ }
+}
+
+- (void)stop {
+ NSError *error;
+ [self.player stopAtTime:0 error:&error];
+ self.player = nil;
+}
+
+@end
+
+@implementation RumbleContext
+
+- (instancetype)init {
+ self = [super init];
+ self.weak_motor = nil;
+ self.strong_motor = nil;
+ return self;
+}
+
+- (bool)hasMotors {
+ return self.weak_motor != nil && self.strong_motor != nil;
+}
+- (bool)hasActivePlayers {
+ if (![self hasMotors]) {
+ return NO;
+ }
+ return self.weak_motor.player != nil && self.strong_motor.player != nil;
+}
+
+@end
+
+@implementation Joypad
+
+- (instancetype)init {
+ self = [super init];
+ return self;
+}
+- (instancetype)init:(GCController *)controller {
+ self = [super init];
+ self.controller = controller;
+
+ if (@available(macOS 11, *)) {
+ // Haptics within the controller is only available in macOS 11+.
+ self.rumble_context = [[RumbleContext alloc] init];
+
+ // Create Weak and Strong motors for controller.
+ self.rumble_context.weak_motor = [[RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle];
+ self.rumble_context.strong_motor = [[RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle];
+
+ // If the rumble motors aren't available, disable force feedback.
+ if (![self.rumble_context hasMotors]) {
+ self.force_feedback = NO;
+ } else {
+ self.force_feedback = YES;
+ }
+ } else {
+ self.force_feedback = NO;
+ }
+
+ self.ff_effect_timestamp = 0;
+
+ return self;
+}
+
+@end
+
+JoypadMacOS::JoypadMacOS() {
+ observer = [[JoypadMacOSObserver alloc] init];
+ [observer startObserving];
+
+ if (@available(macOS 11.3, *)) {
+ GCController.shouldMonitorBackgroundEvents = YES;
+ }
+}
+
+JoypadMacOS::~JoypadMacOS() {
+ if (observer) {
+ [observer finishObserving];
+ observer = nil;
+ }
+}
+
+void JoypadMacOS::start_processing() {
+ if (observer) {
+ [observer startProcessing];
+ }
+ process_joypads();
+}
+
+API_AVAILABLE(macosx(10.15))
+CHHapticPattern *get_vibration_pattern(float p_magnitude, float p_duration) {
+ // Creates a vibration pattern with an intensity and duration.
+ NSDictionary *hapticDict = @{
+ CHHapticPatternKeyPattern : @[
+ @{
+ CHHapticPatternKeyEvent : @{
+ CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous,
+ CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
+ CHHapticPatternKeyEventDuration : [NSNumber numberWithFloat:p_duration],
+
+ CHHapticPatternKeyEventParameters : @[
+ @{
+ CHHapticPatternKeyParameterID : CHHapticEventParameterIDHapticIntensity,
+ CHHapticPatternKeyParameterValue : [NSNumber numberWithFloat:p_magnitude]
+ },
+ ],
+ },
+ },
+ ],
+ };
+ NSError *error;
+ CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error];
+ return pattern;
+}
+
+void JoypadMacOS::joypad_vibration_start(Joypad *p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) {
+ if (!p_joypad.force_feedback || p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) {
+ return;
+ }
+
+ // If there is active vibration players, stop them.
+ if ([p_joypad.rumble_context hasActivePlayers]) {
+ joypad_vibration_stop(p_joypad, p_timestamp);
+ }
+
+ // Gets the default vibration pattern and creates a player for each motor.
+ CHHapticPattern *weak_pattern = get_vibration_pattern(p_weak_magnitude, p_duration);
+ CHHapticPattern *strong_pattern = get_vibration_pattern(p_strong_magnitude, p_duration);
+
+ RumbleMotor *weak_motor = p_joypad.rumble_context.weak_motor;
+ RumbleMotor *strong_motor = p_joypad.rumble_context.strong_motor;
+
+ [weak_motor execute_pattern:weak_pattern];
+ [strong_motor execute_pattern:strong_pattern];
+
+ p_joypad.ff_effect_timestamp = p_timestamp;
+}
+
+void JoypadMacOS::joypad_vibration_stop(Joypad *p_joypad, uint64_t p_timestamp) {
+ if (!p_joypad.force_feedback) {
+ return;
+ }
+ // If there is no active vibration players, exit.
+ if (![p_joypad.rumble_context hasActivePlayers]) {
+ return;
+ }
+
+ RumbleMotor *weak_motor = p_joypad.rumble_context.weak_motor;
+ RumbleMotor *strong_motor = p_joypad.rumble_context.strong_motor;
+
+ [weak_motor stop];
+ [strong_motor stop];
+
+ p_joypad.ff_effect_timestamp = p_timestamp;
+}
+
+@interface JoypadMacOSObserver ()
+
+@property(assign, nonatomic) BOOL isObserving;
+@property(assign, nonatomic) BOOL isProcessing;
+@property(strong, nonatomic) NSMutableDictionary<NSNumber *, Joypad *> *connectedJoypads;
+@property(strong, nonatomic) NSMutableArray<Joypad *> *joypadsQueue;
+
+@end
+
+@implementation JoypadMacOSObserver
+
+- (instancetype)init {
+ self = [super init];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.isObserving = NO;
+ self.isProcessing = NO;
+}
+
+- (void)startProcessing {
+ self.isProcessing = YES;
+
+ for (GCController *controller in self.joypadsQueue) {
+ [self addMacOSJoypad:controller];
+ }
+
+ [self.joypadsQueue removeAllObjects];
+}
+
+- (void)startObserving {
+ if (self.isObserving) {
+ return;
+ }
+
+ self.isObserving = YES;
+
+ self.connectedJoypads = [NSMutableDictionary dictionary];
+ self.joypadsQueue = [NSMutableArray array];
+
+ // Get told when controllers connect, this will be called right away for
+ // already connected controllers.
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasConnected:)
+ name:GCControllerDidConnectNotification
+ object:nil];
+
+ // Get told when controllers disconnect.
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasDisconnected:)
+ name:GCControllerDidDisconnectNotification
+ object:nil];
+}
+
+- (void)finishObserving {
+ if (self.isObserving) {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ }
+
+ self.isObserving = NO;
+ self.isProcessing = NO;
+
+ self.connectedJoypads = nil;
+ self.joypadsQueue = nil;
+}
+
+- (void)dealloc {
+ [self finishObserving];
+}
+
+- (NSArray<NSNumber *> *)getAllKeysForController:(GCController *)controller {
+ NSArray *keys = [self.connectedJoypads allKeys];
+ NSMutableArray *final_keys = [NSMutableArray array];
+
+ for (NSNumber *key in keys) {
+ Joypad *joypad = [self.connectedJoypads objectForKey:key];
+ if (joypad.controller == controller) {
+ [final_keys addObject:key];
+ }
+ }
+
+ return final_keys;
+}
+
+- (int)getJoyIdForController:(GCController *)controller {
+ NSArray *keys = [self getAllKeysForController:controller];
+
+ for (NSNumber *key in keys) {
+ int joy_id = [key intValue];
+ return joy_id;
+ }
+
+ return -1;
+}
+
+- (void)addMacOSJoypad:(GCController *)controller {
+ // Get a new id for our controller.
+ int joy_id = Input::get_singleton()->get_unused_joy_id();
+
+ if (joy_id == -1) {
+ print_verbose("Couldn't retrieve new joy ID.");
+ return;
+ }
+
+ // Assign our player index.
+ if (controller.playerIndex == GCControllerPlayerIndexUnset) {
+ controller.playerIndex = [self getFreePlayerIndex];
+ }
+
+ // Tell Godot about our new controller.
+ Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8([controller.vendorName UTF8String]));
+
+ Joypad *joypad = [[Joypad alloc] init:controller];
+
+ // Add it to our dictionary, this will retain our controllers.
+ [self.connectedJoypads setObject:joypad forKey:[NSNumber numberWithInt:joy_id]];
+
+ // Set our input handler.
+ [self setControllerInputHandler:controller];
+}
+
+- (void)controllerWasConnected:(NSNotification *)notification {
+ // Get our controller.
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ print_verbose("Couldn't retrieve new controller.");
+ return;
+ }
+
+ if ([[self getAllKeysForController:controller] count] > 0) {
+ print_verbose("Controller is already registered.");
+ } else if (!self.isProcessing) {
+ Joypad *joypad = [[Joypad alloc] init:controller];
+ [self.joypadsQueue addObject:joypad];
+ } else {
+ [self addMacOSJoypad:controller];
+ }
+}
+
+- (void)controllerWasDisconnected:(NSNotification *)notification {
+ // Find our joystick, there should be only one in our dictionary.
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ return;
+ }
+
+ NSArray *keys = [self getAllKeysForController:controller];
+ for (NSNumber *key in keys) {
+ // Tell Godot this joystick is no longer there.
+ int joy_id = [key intValue];
+ Input::get_singleton()->joy_connection_changed(joy_id, false, "");
+
+ // And remove it from our dictionary.
+ [self.connectedJoypads removeObjectForKey:key];
+ }
+}
+
+- (GCControllerPlayerIndex)getFreePlayerIndex {
+ bool have_player_1 = false;
+ bool have_player_2 = false;
+ bool have_player_3 = false;
+ bool have_player_4 = false;
+
+ if (self.connectedJoypads == nil) {
+ NSArray *keys = [self.connectedJoypads allKeys];
+ for (NSNumber *key in keys) {
+ Joypad *joypad = [self.connectedJoypads objectForKey:key];
+ GCController *controller = joypad.controller;
+ if (controller.playerIndex == GCControllerPlayerIndex1) {
+ have_player_1 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex2) {
+ have_player_2 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex3) {
+ have_player_3 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex4) {
+ have_player_4 = true;
+ }
+ }
+ }
+
+ if (!have_player_1) {
+ return GCControllerPlayerIndex1;
+ } else if (!have_player_2) {
+ return GCControllerPlayerIndex2;
+ } else if (!have_player_3) {
+ return GCControllerPlayerIndex3;
+ } else if (!have_player_4) {
+ return GCControllerPlayerIndex4;
+ } else {
+ return GCControllerPlayerIndexUnset;
+ }
+}
+
+- (void)setControllerInputHandler:(GCController *)controller {
+ // Hook in the callback handler for the correct gamepad profile.
+ // This is a bit of a weird design choice on Apples part.
+ // You need to select the most capable gamepad profile for the
+ // gamepad attached.
+ if (controller.extendedGamepad != nil) {
+ // The extended gamepad profile has all the input you could possibly find on
+ // a gamepad but will only be active if your gamepad actually has all of
+ // these...
+ _weakify(self);
+ _weakify(controller);
+
+ controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
+ _strongify(self);
+ _strongify(controller);
+
+ int joy_id = [self getJoyIdForController:controller];
+
+ if (element == gamepad.buttonA) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::A,
+ gamepad.buttonA.isPressed);
+ } else if (element == gamepad.buttonB) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::B,
+ gamepad.buttonB.isPressed);
+ } else if (element == gamepad.buttonX) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::X,
+ gamepad.buttonX.isPressed);
+ } else if (element == gamepad.buttonY) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::Y,
+ gamepad.buttonY.isPressed);
+ } else if (element == gamepad.leftShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::LEFT_SHOULDER,
+ gamepad.leftShoulder.isPressed);
+ } else if (element == gamepad.rightShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::RIGHT_SHOULDER,
+ gamepad.rightShoulder.isPressed);
+ } else if (element == gamepad.dpad) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP,
+ gamepad.dpad.up.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN,
+ gamepad.dpad.down.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT,
+ gamepad.dpad.left.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT,
+ gamepad.dpad.right.isPressed);
+ }
+
+ if (element == gamepad.leftThumbstick) {
+ float value = gamepad.leftThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_X, value);
+ value = -gamepad.leftThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::LEFT_Y, value);
+ } else if (element == gamepad.rightThumbstick) {
+ float value = gamepad.rightThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_X, value);
+ value = -gamepad.rightThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::RIGHT_Y, value);
+ } else if (element == gamepad.leftTrigger) {
+ float value = gamepad.leftTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_LEFT, value);
+ } else if (element == gamepad.rightTrigger) {
+ float value = gamepad.rightTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JoyAxis::TRIGGER_RIGHT, value);
+ }
+
+ if (@available(macOS 10.14.1, *)) {
+ if (element == gamepad.leftThumbstickButton) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::LEFT_STICK,
+ gamepad.leftThumbstickButton.isPressed);
+ } else if (element == gamepad.rightThumbstickButton) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::RIGHT_STICK,
+ gamepad.rightThumbstickButton.isPressed);
+ }
+ }
+
+ if (@available(macOS 10.15, *)) {
+ if (element == gamepad.buttonOptions) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::BACK,
+ gamepad.buttonOptions.isPressed);
+ } else if (element == gamepad.buttonMenu) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::START,
+ gamepad.buttonMenu.isPressed);
+ }
+ }
+
+ if (@available(macOS 11, *)) {
+ if (element == gamepad.buttonHome) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::GUIDE,
+ gamepad.buttonHome.isPressed);
+ }
+ if ([gamepad isKindOfClass:[GCXboxGamepad class]]) {
+ GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad;
+ if (element == xboxGamepad.paddleButton1) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::PADDLE1,
+ xboxGamepad.paddleButton1.isPressed);
+ } else if (element == xboxGamepad.paddleButton2) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::PADDLE2,
+ xboxGamepad.paddleButton2.isPressed);
+ } else if (element == xboxGamepad.paddleButton3) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::PADDLE3,
+ xboxGamepad.paddleButton3.isPressed);
+ } else if (element == xboxGamepad.paddleButton4) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::PADDLE4,
+ xboxGamepad.paddleButton4.isPressed);
+ }
+ }
+ }
+
+ if (@available(macOS 12, *)) {
+ if ([gamepad isKindOfClass:[GCXboxGamepad class]]) {
+ GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad;
+ if (element == xboxGamepad.buttonShare) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::MISC1,
+ xboxGamepad.buttonShare.isPressed);
+ }
+ }
+ }
+ };
+ } else if (controller.microGamepad != nil) {
+ // Micro gamepads were added in macOS 10.11 and feature just 2 buttons and a d-pad.
+ _weakify(self);
+ _weakify(controller);
+
+ controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
+ _strongify(self);
+ _strongify(controller);
+
+ int joy_id = [self getJoyIdForController:controller];
+
+ if (element == gamepad.buttonA) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::A,
+ gamepad.buttonA.isPressed);
+ } else if (element == gamepad.buttonX) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::X,
+ gamepad.buttonX.isPressed);
+ } else if (element == gamepad.dpad) {
+ Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_UP,
+ gamepad.dpad.up.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_DOWN,
+ gamepad.dpad.down.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_LEFT,
+ gamepad.dpad.left.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JoyButton::DPAD_RIGHT,
+ gamepad.dpad.right.isPressed);
+ }
+ };
+ }
+
+ // TODO: Need to add support for controller.motion which gives us access to
+ // the orientation of the device (if supported).
+}
+
+@end
+
+void JoypadMacOS::process_joypads() {
+ if (@available(macOS 11, *)) {
+ // Process vibrations in macOS 11+.
+ NSArray *keys = [observer.connectedJoypads allKeys];
+
+ for (NSNumber *key in keys) {
+ int id = key.intValue;
+ Joypad *joypad = [observer.connectedJoypads objectForKey:key];
+
+ if (joypad.force_feedback) {
+ Input *input = Input::get_singleton();
+ uint64_t timestamp = input->get_joy_vibration_timestamp(id);
+
+ if (timestamp > (unsigned)joypad.ff_effect_timestamp) {
+ Vector2 strength = input->get_joy_vibration_strength(id);
+ float duration = input->get_joy_vibration_duration(id);
+ if (duration == 0) {
+ duration = GCHapticDurationInfinite;
+ }
+
+ if (strength.x == 0 && strength.y == 0) {
+ joypad_vibration_stop(joypad, timestamp);
+ } else {
+ joypad_vibration_start(joypad, strength.x, strength.y, duration, timestamp);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h
new file mode 100644
index 0000000000..1d9feb64a7
--- /dev/null
+++ b/platform/macos/native_menu_macos.h
@@ -0,0 +1,157 @@
+/**************************************************************************/
+/* native_menu_macos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef NATIVE_MENU_MACOS_H
+#define NATIVE_MENU_MACOS_H
+
+#include "core/templates/hash_map.h"
+#include "core/templates/rid_owner.h"
+#include "servers/display/native_menu.h"
+
+#import <AppKit/AppKit.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+class NativeMenuMacOS : public NativeMenu {
+ GDCLASS(NativeMenuMacOS, NativeMenu)
+
+ struct MenuData {
+ NSMenu *menu = nullptr;
+
+ Callable open_cb;
+ Callable close_cb;
+ bool is_open = false;
+ bool is_system = false;
+ };
+
+ mutable RID_PtrOwner<MenuData> menus;
+ HashMap<NSMenu *, RID> menu_lookup;
+
+ NSMenu *main_menu_ns = nullptr;
+ NSMenu *application_menu_ns = nullptr;
+ NSMenu *window_menu_ns = nullptr;
+ NSMenu *help_menu_ns = nullptr;
+ NSMenu *dock_menu_ns = nullptr;
+
+ RID main_menu;
+ RID application_menu;
+ RID window_menu;
+ RID help_menu;
+ RID dock_menu;
+
+ int _get_system_menu_start(const NSMenu *p_menu) const;
+ int _get_system_menu_count(const NSMenu *p_menu) const;
+ bool _is_menu_opened(NSMenu *p_menu) const;
+ NSMenuItem *_menu_add_item(NSMenu *p_menu, const String &p_label, Key p_accel, int p_index, int *r_out);
+
+public:
+ void _register_system_menus(NSMenu *p_main_menu, NSMenu *p_application_menu, NSMenu *p_window_menu, NSMenu *p_help_menu, NSMenu *p_dock_menu);
+ NSMenu *_get_dock_menu();
+ void _menu_open(NSMenu *p_menu);
+ void _menu_close(NSMenu *p_menu);
+
+ virtual bool has_feature(Feature p_feature) const override;
+
+ virtual bool has_system_menu(SystemMenus p_menu_id) const override;
+ virtual RID get_system_menu(SystemMenus p_menu_id) const override;
+
+ virtual RID create_menu() override;
+ virtual bool has_menu(const RID &p_rid) const override;
+ virtual void free_menu(const RID &p_rid) override;
+
+ virtual Size2 get_size(const RID &p_rid) const override;
+ virtual void popup(const RID &p_rid, const Vector2i &p_position) override;
+
+ virtual void set_interface_direction(const RID &p_rid, bool p_is_rtl) override;
+ virtual void set_popup_open_callback(const RID &p_rid, const Callable &p_callback) override;
+ virtual Callable get_popup_open_callback(const RID &p_rid) const override;
+ virtual void set_popup_close_callback(const RID &p_rid, const Callable &p_callback) override;
+ virtual Callable get_popup_close_callback(const RID &p_rid) const override;
+ virtual void set_minimum_width(const RID &p_rid, float p_width) override;
+ virtual float get_minimum_width(const RID &p_rid) const override;
+
+ virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1) override;
+ virtual int add_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_radio_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_separator(const RID &p_rid, int p_index = -1) override;
+
+ virtual int find_item_index_with_text(const RID &p_rid, const String &p_text) const override;
+ virtual int find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const override;
+
+ virtual bool is_item_checked(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_checkable(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_radio_checkable(const RID &p_rid, int p_idx) const override;
+ virtual Callable get_item_callback(const RID &p_rid, int p_idx) const override;
+ virtual Callable get_item_key_callback(const RID &p_rid, int p_idx) const override;
+ virtual Variant get_item_tag(const RID &p_rid, int p_idx) const override;
+ virtual String get_item_text(const RID &p_rid, int p_idx) const override;
+ virtual RID get_item_submenu(const RID &p_rid, int p_idx) const override;
+ virtual Key get_item_accelerator(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_disabled(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_hidden(const RID &p_rid, int p_idx) const override;
+ virtual String get_item_tooltip(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_state(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_max_states(const RID &p_rid, int p_idx) const override;
+ virtual Ref<Texture2D> get_item_icon(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_indentation_level(const RID &p_rid, int p_idx) const override;
+
+ virtual void set_item_checked(const RID &p_rid, int p_idx, bool p_checked) override;
+ virtual void set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) override;
+ virtual void set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) override;
+ virtual void set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) override;
+ virtual void set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) override;
+ virtual void set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) override;
+ virtual void set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) override;
+ virtual void set_item_text(const RID &p_rid, int p_idx, const String &p_text) override;
+ virtual void set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) override;
+ virtual void set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) override;
+ virtual void set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) override;
+ virtual void set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) override;
+ virtual void set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) override;
+ virtual void set_item_state(const RID &p_rid, int p_idx, int p_state) override;
+ virtual void set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) override;
+ virtual void set_item_icon(const RID &p_rid, int p_idx, const Ref<Texture2D> &p_icon) override;
+ virtual void set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) override;
+
+ virtual int get_item_count(const RID &p_rid) const override;
+ virtual bool is_system_menu(const RID &p_rid) const override;
+
+ virtual void remove_item(const RID &p_rid, int p_idx) override;
+ virtual void clear(const RID &p_rid) override;
+
+ NativeMenuMacOS();
+ ~NativeMenuMacOS();
+};
+
+#endif // NATIVE_MENU_MACOS_H
diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm
new file mode 100644
index 0000000000..8c2dd98862
--- /dev/null
+++ b/platform/macos/native_menu_macos.mm
@@ -0,0 +1,1363 @@
+/**************************************************************************/
+/* native_menu_macos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "native_menu_macos.h"
+
+#include "display_server_macos.h"
+#include "godot_menu_item.h"
+#include "key_mapping_macos.h"
+
+#include "scene/resources/image_texture.h"
+
+void NativeMenuMacOS::_register_system_menus(NSMenu *p_main_menu, NSMenu *p_application_menu, NSMenu *p_window_menu, NSMenu *p_help_menu, NSMenu *p_dock_menu) {
+ {
+ MenuData *md = memnew(MenuData);
+ md->menu = p_main_menu;
+ md->is_system = true;
+ main_menu = menus.make_rid(md);
+ main_menu_ns = p_main_menu;
+ menu_lookup[md->menu] = main_menu;
+ }
+ {
+ MenuData *md = memnew(MenuData);
+ md->menu = p_application_menu;
+ md->is_system = true;
+ application_menu = menus.make_rid(md);
+ application_menu_ns = p_application_menu;
+ menu_lookup[md->menu] = application_menu;
+ }
+ {
+ MenuData *md = memnew(MenuData);
+ md->menu = p_window_menu;
+ md->is_system = true;
+ window_menu = menus.make_rid(md);
+ window_menu_ns = p_window_menu;
+ menu_lookup[md->menu] = window_menu;
+ }
+ {
+ MenuData *md = memnew(MenuData);
+ md->menu = p_help_menu;
+ md->is_system = true;
+ help_menu = menus.make_rid(md);
+ help_menu_ns = p_help_menu;
+ menu_lookup[md->menu] = help_menu;
+ }
+ {
+ MenuData *md = memnew(MenuData);
+ md->menu = p_dock_menu;
+ md->is_system = true;
+ dock_menu = menus.make_rid(md);
+ dock_menu_ns = p_dock_menu;
+ menu_lookup[md->menu] = dock_menu;
+ }
+}
+
+NSMenu *NativeMenuMacOS::_get_dock_menu() {
+ MenuData *md = menus.get_or_null(dock_menu);
+ if (md) {
+ return md->menu;
+ }
+ return nullptr;
+}
+
+void NativeMenuMacOS::_menu_open(NSMenu *p_menu) {
+ if (menu_lookup.has(p_menu)) {
+ MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
+ if (md) {
+ md->is_open = true;
+ if (md->open_cb.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+
+ md->open_cb.callp(nullptr, 0, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute menu open callback: %s.", Variant::get_callable_error_text(md->open_cb, nullptr, 0, ce)));
+ }
+ }
+ }
+ }
+}
+
+void NativeMenuMacOS::_menu_close(NSMenu *p_menu) {
+ if (menu_lookup.has(p_menu)) {
+ MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
+ if (md) {
+ md->is_open = false;
+ if (md->close_cb.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+
+ md->close_cb.callp(nullptr, 0, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md->close_cb, nullptr, 0, ce)));
+ }
+ }
+ }
+ }
+}
+
+bool NativeMenuMacOS::_is_menu_opened(NSMenu *p_menu) const {
+ if (menu_lookup.has(p_menu)) {
+ const MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
+ if (md && md->is_open) {
+ return true;
+ }
+ }
+ for (NSInteger i = (p_menu == [NSApp mainMenu]) ? 1 : 0; i < [p_menu numberOfItems]; i++) {
+ const NSMenuItem *menu_item = [p_menu itemAtIndex:i];
+ if ([menu_item submenu]) {
+ if (_is_menu_opened([menu_item submenu])) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+int NativeMenuMacOS::_get_system_menu_start(const NSMenu *p_menu) const {
+ if (p_menu == [NSApp mainMenu]) { // Skip Apple menu.
+ return 1;
+ }
+ if (p_menu == application_menu_ns || p_menu == window_menu_ns || p_menu == help_menu_ns) {
+ int count = [p_menu numberOfItems];
+ for (int i = 0; i < count; i++) {
+ NSMenuItem *menu_item = [p_menu itemAtIndex:i];
+ if (menu_item.tag == MENU_TAG_START) {
+ return i + 1;
+ }
+ }
+ }
+ return 0;
+}
+
+int NativeMenuMacOS::_get_system_menu_count(const NSMenu *p_menu) const {
+ if (p_menu == [NSApp mainMenu]) { // Skip Apple, Window and Help menu.
+ return [p_menu numberOfItems] - 3;
+ }
+ if (p_menu == application_menu_ns || p_menu == window_menu_ns || p_menu == help_menu_ns) {
+ int start = 0;
+ int count = [p_menu numberOfItems];
+ for (int i = 0; i < count; i++) {
+ NSMenuItem *menu_item = [p_menu itemAtIndex:i];
+ if (menu_item.tag == MENU_TAG_START) {
+ start = i + 1;
+ }
+ if (menu_item.tag == MENU_TAG_END) {
+ return i - start;
+ }
+ }
+ }
+ return [p_menu numberOfItems];
+}
+
+bool NativeMenuMacOS::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ case FEATURE_GLOBAL_MENU:
+ case FEATURE_POPUP_MENU:
+ case FEATURE_OPEN_CLOSE_CALLBACK:
+ case FEATURE_HOVER_CALLBACK:
+ case FEATURE_KEY_CALLBACK:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool NativeMenuMacOS::has_system_menu(SystemMenus p_menu_id) const {
+ switch (p_menu_id) {
+ case MAIN_MENU_ID:
+ case APPLICATION_MENU_ID:
+ case WINDOW_MENU_ID:
+ case HELP_MENU_ID:
+ case DOCK_MENU_ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+RID NativeMenuMacOS::get_system_menu(SystemMenus p_menu_id) const {
+ switch (p_menu_id) {
+ case MAIN_MENU_ID:
+ return main_menu;
+ case APPLICATION_MENU_ID:
+ return application_menu;
+ case WINDOW_MENU_ID:
+ return window_menu;
+ case HELP_MENU_ID:
+ return help_menu;
+ case DOCK_MENU_ID:
+ return dock_menu;
+ default:
+ return RID();
+ }
+}
+
+RID NativeMenuMacOS::create_menu() {
+ MenuData *md = memnew(MenuData);
+ md->menu = [[NSMenu alloc] initWithTitle:@""];
+ [md->menu setAutoenablesItems:NO];
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->set_menu_delegate(md->menu);
+ }
+ RID rid = menus.make_rid(md);
+ menu_lookup[md->menu] = rid;
+ return rid;
+}
+
+bool NativeMenuMacOS::has_menu(const RID &p_rid) const {
+ return menus.owns(p_rid);
+}
+
+void NativeMenuMacOS::free_menu(const RID &p_rid) {
+ MenuData *md = menus.get_or_null(p_rid);
+ if (md && !md->is_system) {
+ clear(p_rid);
+ menus.free(p_rid);
+ menu_lookup.erase(md->menu);
+ md->menu = nullptr;
+ memdelete(md);
+ }
+}
+
+Size2 NativeMenuMacOS::get_size(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Size2());
+
+ return Size2(md->menu.size.width, md->menu.size.height) * DisplayServer::get_singleton()->screen_get_max_scale();
+}
+
+void NativeMenuMacOS::popup(const RID &p_rid, const Vector2i &p_position) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds) {
+ Point2i position = p_position;
+ // macOS native y-coordinate relative to _get_screens_origin() is negative,
+ // Godot passes a positive value.
+ position.y *= -1;
+ position += ds->_get_screens_origin();
+ position /= ds->screen_get_max_scale();
+
+ [md->menu popUpMenuPositioningItem:nil atLocation:NSMakePoint(position.x, position.y) inView:nil];
+ }
+}
+
+void NativeMenuMacOS::set_interface_direction(const RID &p_rid, bool p_is_rtl) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ md->menu.userInterfaceLayoutDirection = p_is_rtl ? NSUserInterfaceLayoutDirectionRightToLeft : NSUserInterfaceLayoutDirectionLeftToRight;
+}
+
+void NativeMenuMacOS::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ md->open_cb = p_callback;
+}
+
+Callable NativeMenuMacOS::get_popup_open_callback(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Callable());
+
+ return md->open_cb;
+}
+
+void NativeMenuMacOS::set_popup_close_callback(const RID &p_rid, const Callable &p_callback) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ md->close_cb = p_callback;
+}
+
+Callable NativeMenuMacOS::get_popup_close_callback(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Callable());
+
+ return md->close_cb;
+}
+
+void NativeMenuMacOS::set_minimum_width(const RID &p_rid, float p_width) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ md->menu.minimumWidth = p_width / DisplayServer::get_singleton()->screen_get_max_scale();
+}
+
+float NativeMenuMacOS::get_minimum_width(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, 0.0);
+
+ return md->menu.minimumWidth * DisplayServer::get_singleton()->screen_get_max_scale();
+}
+
+int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ MenuData *md_sub = menus.get_or_null(p_submenu_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ ERR_FAIL_NULL_V(md_sub, -1);
+ ERR_FAIL_COND_V_MSG(md->menu == md_sub->menu, -1, "Can't set submenu to self!");
+ ERR_FAIL_COND_V_MSG([md_sub->menu supermenu], -1, "Can't set submenu to menu that is already a submenu of some other menu!");
+
+ NSMenuItem *menu_item;
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ if (p_index < 0) {
+ p_index = item_start + item_count;
+ } else {
+ p_index += item_start;
+ p_index = CLAMP(p_index, item_start, item_start + item_count);
+ }
+ menu_item = [md->menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
+
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = Callable();
+ obj->key_callback = Callable();
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setRepresentedObject:obj];
+
+ [md_sub->menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
+ [md->menu setSubmenu:md_sub->menu forItem:menu_item];
+
+ return p_index - item_start;
+}
+
+NSMenuItem *NativeMenuMacOS::_menu_add_item(NSMenu *p_menu, const String &p_label, Key p_accel, int p_index, int *r_out) {
+ if (p_menu) {
+ String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
+ NSMenuItem *menu_item;
+ int item_start = _get_system_menu_start(p_menu);
+ int item_count = _get_system_menu_count(p_menu);
+ if (p_index < 0) {
+ p_index = item_start + item_count;
+ } else {
+ p_index += item_start;
+ p_index = CLAMP(p_index, item_start, item_start + item_count);
+ }
+ menu_item = [p_menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ *r_out = p_index - item_start;
+ return menu_item;
+ }
+ return nullptr;
+}
+
+int NativeMenuMacOS::add_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
+ if (menu_item) {
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->key_callback = p_key_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+ return out;
+}
+
+int NativeMenuMacOS::add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
+ if (menu_item) {
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->key_callback = p_key_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+ return out;
+}
+
+int NativeMenuMacOS::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
+ if (menu_item) {
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->key_callback = p_key_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = 0;
+ obj->state = 0;
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && p_icon.is_valid()) {
+ obj->img = p_icon->get_image();
+ obj->img = obj->img->duplicate();
+ if (obj->img->is_compressed()) {
+ obj->img->decompress();
+ }
+ NSImage *image = ds->_convert_to_nsimg(obj->img);
+ [image setSize:NSMakeSize(16, 16)];
+ [menu_item setImage:image];
+ }
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+ return out;
+}
+
+int NativeMenuMacOS::add_icon_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
+ if (menu_item) {
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->key_callback = p_key_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ obj->max_states = 0;
+ obj->state = 0;
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && p_icon.is_valid()) {
+ obj->img = p_icon->get_image();
+ obj->img = obj->img->duplicate();
+ if (obj->img->is_compressed()) {
+ obj->img->decompress();
+ }
+ NSImage *image = ds->_convert_to_nsimg(obj->img);
+ [image setSize:NSMakeSize(16, 16)];
+ [menu_item setImage:image];
+ }
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+ return out;
+}
+
+int NativeMenuMacOS::add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
+ if (menu_item) {
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->key_callback = p_key_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+ return out;
+}
+
+int NativeMenuMacOS::add_icon_radio_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
+ if (menu_item) {
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->key_callback = p_key_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
+ obj->max_states = 0;
+ obj->state = 0;
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && p_icon.is_valid()) {
+ obj->img = p_icon->get_image();
+ obj->img = obj->img->duplicate();
+ if (obj->img->is_compressed()) {
+ obj->img->decompress();
+ }
+ NSImage *image = ds->_convert_to_nsimg(obj->img);
+ [image setSize:NSMakeSize(16, 16)];
+ [menu_item setImage:image];
+ }
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+ return out;
+}
+
+int NativeMenuMacOS::add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
+ if (menu_item) {
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = p_callback;
+ obj->key_callback = p_key_callback;
+ obj->meta = p_tag;
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = p_max_states;
+ obj->state = p_default_state;
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
+ [menu_item setRepresentedObject:obj];
+ }
+ return out;
+}
+
+int NativeMenuMacOS::add_separator(const RID &p_rid, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (md->menu == [NSApp mainMenu]) { // Do not add separators into main menu.
+ return -1;
+ }
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ if (p_index < 0) {
+ p_index = item_start + item_count;
+ } else {
+ p_index += item_start;
+ p_index = CLAMP(p_index, item_start, item_start + item_count);
+ }
+ [md->menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
+ return p_index - item_start;
+}
+
+int NativeMenuMacOS::find_item_index_with_text(const RID &p_rid, const String &p_text) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int index = [md->menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ if (index >= 0) {
+ return index - item_start;
+ }
+ return -1;
+}
+
+int NativeMenuMacOS::find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ for (NSInteger i = item_start; i < item_start + item_count; i++) {
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:i];
+ if (menu_item) {
+ const GodotMenuItem *obj = [menu_item representedObject];
+ if (obj && obj->meta == p_tag) {
+ return i - item_start;
+ }
+ }
+ }
+ return -1;
+}
+
+bool NativeMenuMacOS::is_item_checked(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return ([menu_item state] == NSControlStateValueOn);
+ }
+ return false;
+}
+
+bool NativeMenuMacOS::is_item_checkable(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX;
+ }
+ }
+ return false;
+}
+
+bool NativeMenuMacOS::is_item_radio_checkable(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON;
+ }
+ }
+ return false;
+}
+
+Callable NativeMenuMacOS::get_item_callback(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Callable());
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Callable());
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->callback;
+ }
+ }
+ return Callable();
+}
+
+Callable NativeMenuMacOS::get_item_key_callback(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Callable());
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Callable());
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->key_callback;
+ }
+ }
+ return Callable();
+}
+
+Variant NativeMenuMacOS::get_item_tag(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Variant());
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Variant());
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, Variant());
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->meta;
+ }
+ }
+ return Variant();
+}
+
+String NativeMenuMacOS::get_item_text(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, String());
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return String::utf8([[menu_item title] UTF8String]);
+ }
+ return String();
+}
+
+RID NativeMenuMacOS::get_item_submenu(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, RID());
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, RID());
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, RID());
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ NSMenu *sub_menu = [menu_item submenu];
+ if (sub_menu && menu_lookup.has(sub_menu)) {
+ return menu_lookup[sub_menu];
+ }
+ }
+ return RID();
+}
+
+Key NativeMenuMacOS::get_item_accelerator(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Key::NONE);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Key::NONE);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, Key::NONE);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
+ Key keycode = find_keycode(ret);
+ NSUInteger mask = [menu_item keyEquivalentModifierMask];
+ if (mask & NSEventModifierFlagControl) {
+ keycode |= KeyModifierMask::CTRL;
+ }
+ if (mask & NSEventModifierFlagOption) {
+ keycode |= KeyModifierMask::ALT;
+ }
+ if (mask & NSEventModifierFlagShift) {
+ keycode |= KeyModifierMask::SHIFT;
+ }
+ if (mask & NSEventModifierFlagCommand) {
+ keycode |= KeyModifierMask::META;
+ }
+ if (mask & NSEventModifierFlagNumericPad) {
+ keycode |= KeyModifierMask::KPAD;
+ }
+ return keycode;
+ }
+ return Key::NONE;
+}
+
+bool NativeMenuMacOS::is_item_disabled(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return ![menu_item isEnabled];
+ }
+ return false;
+}
+
+bool NativeMenuMacOS::is_item_hidden(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return [menu_item isHidden];
+ }
+ return false;
+}
+
+String NativeMenuMacOS::get_item_tooltip(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, String());
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return String::utf8([[menu_item toolTip] UTF8String]);
+ }
+ return String();
+}
+
+int NativeMenuMacOS::get_item_state(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, 0);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->state;
+ }
+ }
+ return 0;
+}
+
+int NativeMenuMacOS::get_item_max_states(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, 0);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->max_states;
+ }
+ }
+ return 0;
+}
+
+Ref<Texture2D> NativeMenuMacOS::get_item_icon(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Ref<Texture2D>());
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, Ref<Texture2D>());
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ if (obj->img.is_valid()) {
+ return ImageTexture::create_from_image(obj->img);
+ }
+ }
+ }
+ return Ref<Texture2D>();
+}
+
+int NativeMenuMacOS::get_item_indentation_level(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, 0);
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
+ const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return [menu_item indentationLevel];
+ }
+ return 0;
+}
+
+void NativeMenuMacOS::set_item_checked(const RID &p_rid, int p_idx, bool p_checked) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ if (p_checked) {
+ [menu_item setState:NSControlStateValueOn];
+ } else {
+ [menu_item setState:NSControlStateValueOff];
+ }
+ }
+}
+
+void NativeMenuMacOS::set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
+ }
+}
+
+void NativeMenuMacOS::set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE;
+ }
+}
+
+void NativeMenuMacOS::set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ obj->callback = p_callback;
+ }
+}
+
+void NativeMenuMacOS::set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ obj->key_callback = p_key_callback;
+ }
+}
+
+void NativeMenuMacOS::set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ obj->hover_callback = p_callback;
+ }
+}
+
+void NativeMenuMacOS::set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ obj->meta = p_tag;
+ }
+}
+
+void NativeMenuMacOS::set_item_text(const RID &p_rid, int p_idx, const String &p_text) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ NSMenu *sub_menu = [menu_item submenu];
+ if (sub_menu) {
+ [sub_menu setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ }
+ }
+}
+
+void NativeMenuMacOS::set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ if (p_submenu_rid.is_valid()) {
+ MenuData *md_sub = menus.get_or_null(p_submenu_rid);
+ ERR_FAIL_NULL(md_sub);
+ ERR_FAIL_COND_MSG(md->menu == md_sub->menu, "Can't set submenu to self!");
+ ERR_FAIL_COND_MSG([md_sub->menu supermenu], "Can't set submenu to menu that is already a submenu of some other menu!");
+
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [md->menu setSubmenu:md_sub->menu forItem:menu_item];
+ }
+ } else {
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
+ [md->menu setSubmenu:nil forItem:menu_item];
+ }
+ }
+}
+
+void NativeMenuMacOS::set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ if (p_keycode == Key::NONE) {
+ [menu_item setKeyEquivalent:@""];
+ } else {
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
+ String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
+ [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
+ }
+}
+
+void NativeMenuMacOS::set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setEnabled:(!p_disabled)];
+ }
+}
+
+void NativeMenuMacOS::set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setHidden:p_hidden];
+ }
+}
+
+void NativeMenuMacOS::set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
+ }
+}
+
+void NativeMenuMacOS::set_item_state(const RID &p_rid, int p_idx, int p_state) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ obj->state = p_state;
+ }
+}
+
+void NativeMenuMacOS::set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ obj->max_states = p_max_states;
+ }
+}
+
+void NativeMenuMacOS::set_item_icon(const RID &p_rid, int p_idx, const Ref<Texture2D> &p_icon) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_NULL(obj);
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && p_icon.is_valid()) {
+ obj->img = p_icon->get_image();
+ obj->img = obj->img->duplicate();
+ if (obj->img->is_compressed()) {
+ obj->img->decompress();
+ }
+ NSImage *image = ds->_convert_to_nsimg(obj->img);
+ [image setSize:NSMakeSize(16, 16)];
+ [menu_item setImage:image];
+ } else {
+ obj->img = Ref<Image>();
+ [menu_item setImage:nil];
+ }
+ }
+}
+
+void NativeMenuMacOS::set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setIndentationLevel:p_level];
+ }
+}
+
+int NativeMenuMacOS::get_item_count(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, 0);
+
+ return _get_system_menu_count(md->menu);
+}
+
+bool NativeMenuMacOS::is_system_menu(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+
+ return md->is_system;
+}
+
+void NativeMenuMacOS::remove_item(const RID &p_rid, int p_idx) {
+ ERR_FAIL_COND(p_idx < 0);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND(p_idx >= item_start + item_count);
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
+ [md->menu removeItemAtIndex:p_idx];
+}
+
+void NativeMenuMacOS::clear(const RID &p_rid) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ ERR_FAIL_COND_MSG(_is_menu_opened(md->menu), "Can't remove open menu!");
+
+ if (p_rid == application_menu || p_rid == window_menu || p_rid == help_menu) {
+ int start = _get_system_menu_start(md->menu);
+ int count = _get_system_menu_count(md->menu);
+ for (int i = start + count - 1; i >= start; i--) {
+ [md->menu removeItemAtIndex:i];
+ }
+ } else {
+ [md->menu removeAllItems];
+ }
+
+ if (p_rid == main_menu) {
+ // Restore Apple, Window and Help menu.
+ MenuData *md_app = menus.get_or_null(application_menu);
+ if (md_app) {
+ NSMenuItem *menu_item = [md->menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
+ [md->menu setSubmenu:md_app->menu forItem:menu_item];
+ }
+ MenuData *md_win = menus.get_or_null(window_menu);
+ if (md_win) {
+ NSMenuItem *menu_item = [md->menu addItemWithTitle:@"Window" action:nil keyEquivalent:@""];
+ [md->menu setSubmenu:md_win->menu forItem:menu_item];
+ }
+ MenuData *md_hlp = menus.get_or_null(help_menu);
+ if (md_hlp) {
+ NSMenuItem *menu_item = [md->menu addItemWithTitle:@"Help" action:nil keyEquivalent:@""];
+ [md->menu setSubmenu:md_hlp->menu forItem:menu_item];
+ }
+ }
+}
+
+NativeMenuMacOS::NativeMenuMacOS() {}
+
+NativeMenuMacOS::~NativeMenuMacOS() {
+ if (main_menu.is_valid()) {
+ MenuData *md = menus.get_or_null(main_menu);
+ if (md) {
+ clear(main_menu);
+ menus.free(main_menu);
+ menu_lookup.erase(md->menu);
+ md->menu = nullptr;
+ main_menu_ns = nullptr;
+ memdelete(md);
+ }
+ }
+ if (application_menu.is_valid()) {
+ MenuData *md = menus.get_or_null(application_menu);
+ if (md) {
+ clear(application_menu);
+ menus.free(application_menu);
+ menu_lookup.erase(md->menu);
+ md->menu = nullptr;
+ application_menu_ns = nullptr;
+ memdelete(md);
+ }
+ }
+ if (window_menu.is_valid()) {
+ MenuData *md = menus.get_or_null(window_menu);
+ if (md) {
+ clear(window_menu);
+ menus.free(window_menu);
+ menu_lookup.erase(md->menu);
+ md->menu = nullptr;
+ window_menu_ns = nullptr;
+ memdelete(md);
+ }
+ }
+ if (help_menu.is_valid()) {
+ MenuData *md = menus.get_or_null(help_menu);
+ if (md) {
+ clear(help_menu);
+ menus.free(help_menu);
+ menu_lookup.erase(md->menu);
+ md->menu = nullptr;
+ help_menu_ns = nullptr;
+ memdelete(md);
+ }
+ }
+ if (dock_menu.is_valid()) {
+ MenuData *md = menus.get_or_null(dock_menu);
+ if (md) {
+ clear(dock_menu);
+ menus.free(dock_menu);
+ menu_lookup.erase(md->menu);
+ md->menu = nullptr;
+ dock_menu_ns = nullptr;
+ memdelete(md);
+ }
+ }
+}
diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h
index 9b20e51f47..912a682a6b 100644
--- a/platform/macos/os_macos.h
+++ b/platform/macos/os_macos.h
@@ -32,7 +32,7 @@
#define OS_MACOS_H
#include "crash_handler_macos.h"
-#include "joypad_macos.h"
+#import "joypad_macos.h"
#include "core/input/input.h"
#import "drivers/coreaudio/audio_driver_coreaudio.h"
@@ -85,7 +85,7 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual MainLoop *get_main_loop() const override;
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index 934767db74..9f0bea5951 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -70,7 +70,7 @@ void OS_MacOS::initialize() {
String OS_MacOS::get_processor_name() const {
char buffer[256];
size_t buffer_len = 256;
- if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, NULL, 0) == 0) {
+ if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) {
return String::utf8(buffer, buffer_len);
}
ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
@@ -139,7 +139,7 @@ void OS_MacOS::finalize() {
}
void OS_MacOS::initialize_joypads() {
- joypad_macos = memnew(JoypadMacOS(Input::get_singleton()));
+ joypad_macos = memnew(JoypadMacOS());
}
void OS_MacOS::set_main_loop(MainLoop *p_main_loop) {
@@ -217,7 +217,7 @@ _FORCE_INLINE_ String OS_MacOS::get_framework_executable(const String &p_path) {
return p_path;
}
-Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
+Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = get_framework_executable(p_path);
if (!FileAccess::exists(path)) {
@@ -235,8 +235,8 @@ Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_hand
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
@@ -601,7 +601,9 @@ Error OS_MacOS::create_process(const String &p_path, const List<String> &p_argum
for (const String &arg : p_arguments) {
[arguments addObject:[NSString stringWithUTF8String:arg.utf8().get_data()]];
}
+#if defined(__x86_64__)
if (@available(macOS 10.15, *)) {
+#endif
NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init];
[configuration setArguments:arguments];
[configuration setCreatesNewApplicationInstance:YES];
@@ -630,6 +632,7 @@ Error OS_MacOS::create_process(const String &p_path, const List<String> &p_argum
}
return err;
+#if defined(__x86_64__)
} else {
Error err = ERR_TIMEOUT;
NSError *error = nullptr;
@@ -645,6 +648,7 @@ Error OS_MacOS::create_process(const String &p_path, const List<String> &p_argum
}
return err;
}
+#endif
} else {
return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console);
}
@@ -769,7 +773,7 @@ void OS_MacOS::run() {
if (DisplayServer::get_singleton()) {
DisplayServer::get_singleton()->process_events(); // Get rid of pending events.
}
- joypad_macos->process_joypads();
+ joypad_macos->start_processing();
if (Main::iteration()) {
quit = true;
diff --git a/platform/macos/platform_config.h b/platform/macos/platform_config.h
index 1a571b689a..01b0a12b5d 100644
--- a/platform/macos/platform_config.h
+++ b/platform/macos/platform_config.h
@@ -31,3 +31,10 @@
#include <alloca.h>
#define PTHREAD_RENAME_SELF
+
+#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
+#define _strongify(var) \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wshadow\"") \
+ __strong typeof(var) var = GDWeak_##var; \
+ _Pragma("clang diagnostic pop")
diff --git a/platform/macos/platform_macos_builders.py b/platform/macos/platform_macos_builders.py
index 3a1cc92bd2..eabe5aae2f 100644
--- a/platform/macos/platform_macos_builders.py
+++ b/platform/macos/platform_macos_builders.py
@@ -1,21 +1,14 @@
-"""Functions used to generate source files during build time
+"""Functions used to generate source files during build time"""
-All such functions are invoked in a subprocess on Windows to prevent build flakiness.
-
-"""
import os
-from platform_methods import subprocess_main
def make_debug_macos(target, source, env):
+ dst = str(target[0])
if env["macports_clang"] != "no":
mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local")
mpclangver = env["macports_clang"]
- os.system(mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-dsymutil {0} -o {0}.dSYM".format(target[0]))
+ os.system(mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-dsymutil {0} -o {0}.dSYM".format(dst))
else:
- os.system("dsymutil {0} -o {0}.dSYM".format(target[0]))
- os.system("strip -u -r {0}".format(target[0]))
-
-
-if __name__ == "__main__":
- subprocess_main(globals())
+ os.system("dsymutil {0} -o {0}.dSYM".format(dst))
+ os.system("strip -u -r {0}".format(dst))
diff --git a/platform/web/detect.py b/platform/web/detect.py
index 7d9b1de6b7..2d2cc288a1 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -86,7 +86,7 @@ def configure(env: "SConsEnvironment"):
supported_arches = ["wasm32"]
if env["arch"] not in supported_arches:
print(
- 'Unsupported CPU architecture "%s" for iOS. Supported architectures are: %s.'
+ 'Unsupported CPU architecture "%s" for Web. Supported architectures are: %s.'
% (env["arch"], ", ".join(supported_arches))
)
sys.exit()
@@ -253,6 +253,9 @@ def configure(env: "SConsEnvironment"):
env.Append(LINKFLAGS=["-fvisibility=hidden"])
env.extra_suffix = ".dlink" + env.extra_suffix
+ # WASM_BIGINT is needed since emscripten ≥ 3.1.41
+ needs_wasm_bigint = cc_semver >= (3, 1, 41)
+
# Run the main application in a web worker
if env["proxy_to_pthread"]:
env.Append(LINKFLAGS=["-s", "PROXY_TO_PTHREAD=1"])
@@ -261,6 +264,9 @@ def configure(env: "SConsEnvironment"):
# https://github.com/emscripten-core/emscripten/issues/18034#issuecomment-1277561925
env.Append(LINKFLAGS=["-s", "TEXTDECODER=0"])
# BigInt support to pass object pointers between contexts
+ needs_wasm_bigint = True
+
+ if needs_wasm_bigint:
env.Append(LINKFLAGS=["-s", "WASM_BIGINT"])
# Reduce code size by generating less support code (e.g. skip NodeJS support).
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index bc4c0d22f0..06f5eb82f7 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -36,7 +36,6 @@
#include "core/config/project_settings.h"
#include "core/object/callable_method_pointer.h"
-#include "scene/resources/atlas_texture.h"
#include "servers/rendering/dummy/rasterizer_dummy.h"
#ifdef GLES3_ENABLED
@@ -511,43 +510,12 @@ DisplayServer::CursorShape DisplayServerWeb::cursor_get_shape() const {
void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
if (p_cursor.is_valid()) {
- Ref<Texture2D> texture = p_cursor;
- ERR_FAIL_COND(!texture.is_valid());
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Size2 texture_size;
Rect2 atlas_rect;
+ Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
+ ERR_FAIL_COND(image.is_null());
+ Vector2i texture_size = image->get_size();
- if (atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
-
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- Ref<Image> image = texture->get_image();
-
- ERR_FAIL_COND(!image.is_valid());
-
- 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()) {
+ if (atlas_rect.has_area()) {
image->crop_from_point(
atlas_rect.position.x,
atlas_rect.position.y,
@@ -1062,6 +1030,7 @@ DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode
r_error = OK; // Always succeeds for now.
tts = GLOBAL_GET("audio/general/text_to_speech");
+ native_menu = memnew(NativeMenu); // Dummy native menu.
// Ensure the canvas ID.
godot_js_config_canvas_id_get(canvas_id, 256);
@@ -1130,6 +1099,10 @@ DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode
}
DisplayServerWeb::~DisplayServerWeb() {
+ if (native_menu) {
+ memdelete(native_menu);
+ native_menu = nullptr;
+ }
#ifdef GLES3_ENABLED
if (webgl_ctx) {
emscripten_webgl_commit_frame();
@@ -1140,7 +1113,11 @@ DisplayServerWeb::~DisplayServerWeb() {
bool DisplayServerWeb::has_feature(Feature p_feature) const {
switch (p_feature) {
- //case FEATURE_GLOBAL_MENU:
+#ifndef DISABLE_DEPRECATED
+ case FEATURE_GLOBAL_MENU: {
+ return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
+ } break;
+#endif
//case FEATURE_HIDPI:
case FEATURE_ICON:
case FEATURE_CLIPBOARD:
@@ -1151,6 +1128,8 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const {
return true;
//case FEATURE_MOUSE_WARP:
//case FEATURE_NATIVE_DIALOG:
+ //case FEATURE_NATIVE_DIALOG_INPUT:
+ //case FEATURE_NATIVE_DIALOG_FILE:
//case FEATURE_NATIVE_ICON:
//case FEATURE_WINDOW_TRANSPARENCY:
//case FEATURE_KEEP_SCREEN_ON:
diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h
index 682d10704f..49a017015a 100644
--- a/platform/web/display_server_web.h
+++ b/platform/web/display_server_web.h
@@ -104,6 +104,7 @@ private:
bool swap_cancel_ok = false;
bool tts = false;
+ NativeMenu *native_menu = nullptr;
// utilities
static void dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod, Key p_keycode);
diff --git a/platform/web/doc_classes/EditorExportPlatformWeb.xml b/platform/web/doc_classes/EditorExportPlatformWeb.xml
index 75125e2708..755308de9a 100644
--- a/platform/web/doc_classes/EditorExportPlatformWeb.xml
+++ b/platform/web/doc_classes/EditorExportPlatformWeb.xml
@@ -28,9 +28,8 @@
The custom HTML page that wraps the exported web build. If left empty, the default HTML shell is used.
For more information, see the [url=$DOCS_URL/tutorials/platform/web/customizing_html5_shell.html]Customizing HTML5 Shell[/url] tutorial.
</member>
- <member name="html/experimental_virtual_keyboard" type="bool" setter="" getter="">
+ <member name="html/experimental_virtual_keyboard" type="bool" setter="" getter="" experimental="">
If [code]true[/code], embeds support for a virtual keyboard into the web page, which is shown when necessary on touchscreen devices.
- [b]Warning:[/b] This feature is experimental and may be changed in a future release.
</member>
<member name="html/export_icon" type="bool" setter="" getter="">
If [code]true[/code], the project icon will be used as the favicon for this application's web page.
@@ -84,8 +83,8 @@
If [code]true[/code] enables [GDExtension] support for this web build.
</member>
<member name="variant/thread_support" type="bool" setter="" getter="">
- If enabled, the exported game will support threads. It requires [url=https://web.dev/articles/coop-coep]a "cross-origin isolated" website[/url], which can be difficult to setup and brings some limitations (e.g. not being able to communicate with third-party websites).
- If disabled, the exported game will not support threads. As a result, it is more prone to performance and audio issues, but will only require to be run on a HTTPS website.
+ If [code]true[/code], the exported game will support threads. It requires [url=https://web.dev/articles/coop-coep]a "cross-origin isolated" website[/url], which may be difficult to set up and is limited for security reasons (such as not being able to communicate with third-party websites).
+ If [code]false[/code], the exported game will not support threads. As a result, it is more prone to performance and audio issues, but will only require to be run on an HTTPS website.
</member>
<member name="vram_texture_compression/for_desktop" type="bool" setter="" getter="">
If [code]true[/code], allows textures to be optimized for desktop through the S3TC algorithm.
diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp
index 45671ca491..ab4e7f8470 100644
--- a/platform/web/os_web.cpp
+++ b/platform/web/os_web.cpp
@@ -105,6 +105,10 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str
return create_process(p_path, p_arguments);
}
+Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+ ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform.");
+}
+
Error OS_Web::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
Array args;
for (const String &E : p_arguments) {
@@ -128,6 +132,10 @@ bool OS_Web::is_process_running(const ProcessID &p_pid) const {
return false;
}
+int OS_Web::get_process_exit_code(const ProcessID &p_pid) const {
+ return -1;
+}
+
int OS_Web::get_processor_count() const {
return godot_js_os_hw_concurrency_get();
}
@@ -239,13 +247,13 @@ bool OS_Web::is_userfs_persistent() const {
return idb_available;
}
-Error OS_Web::open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
+Error OS_Web::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path.get_file();
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
}
return OK;
diff --git a/platform/web/os_web.h b/platform/web/os_web.h
index e578c93925..a825938e96 100644
--- a/platform/web/os_web.h
+++ b/platform/web/os_web.h
@@ -80,10 +80,12 @@ public:
bool main_loop_iterate();
Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
+ Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
Error kill(const ProcessID &p_pid) override;
int get_process_id() const override;
bool is_process_running(const ProcessID &p_pid) const override;
+ int get_process_exit_code(const ProcessID &p_pid) const override;
int get_processor_count() const override;
String get_unique_id() const override;
int get_default_thread_pool_size() const override { return 1; }
@@ -107,7 +109,7 @@ public:
void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
+ Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
void resume_audio();
diff --git a/platform/web/serve.py b/platform/web/serve.py
index 6a3efcc463..89dff63ca3 100755
--- a/platform/web/serve.py
+++ b/platform/web/serve.py
@@ -5,9 +5,20 @@ from pathlib import Path
import os
import sys
import argparse
+import contextlib
+import socket
import subprocess
+# See cpython GH-17851 and GH-17864.
+class DualStackServer(HTTPServer):
+ def server_bind(self):
+ # Suppress exception when protocol is IPv4.
+ with contextlib.suppress(Exception):
+ self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
+ return super().server_bind()
+
+
class CORSRequestHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
@@ -32,7 +43,7 @@ def serve(root, port, run_browser):
print("Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this).")
shell_open(f"http://127.0.0.1:{port}")
- test(CORSRequestHandler, HTTPServer, port=port)
+ test(CORSRequestHandler, DualStackServer, port=port)
if __name__ == "__main__":
diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp
index ad2a801881..04513f6d57 100644
--- a/platform/web/web_main.cpp
+++ b/platform/web/web_main.cpp
@@ -109,24 +109,22 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
// Proper shutdown in case of setup failure.
if (err != OK) {
- int exit_code = (int)err;
- if (err == ERR_HELP) {
- exit_code = 0; // Called with --help.
- }
- os->set_exit_code(exit_code);
// Will only exit after sync.
emscripten_set_main_loop(exit_callback, -1, false);
godot_js_os_finish_async(cleanup_after_sync);
- return exit_code;
+ if (err == ERR_HELP) { // Returned by --help and --version, so success.
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
}
- os->set_exit_code(0);
main_started = true;
// Ease up compatibility.
ResourceLoader::set_abort_on_missing_resources(false);
- Main::start();
+ int ret = Main::start();
+ os->set_exit_code(ret);
os->get_main_loop()->initialize();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) {
@@ -140,5 +138,5 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
// We are inside an animation frame, we want to immediately draw on the newly setup canvas.
main_loop_callback();
- return 0;
+ return os->get_exit_code();
}
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 34c8f8e7a1..159a273e70 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -4,7 +4,6 @@ Import("env")
import os
from pathlib import Path
-from platform_methods import run_in_subprocess
import platform_windows_builders
sources = []
@@ -18,6 +17,8 @@ common_win = [
"joypad_windows.cpp",
"tts_windows.cpp",
"windows_terminal_logger.cpp",
+ "windows_utils.cpp",
+ "native_menu_windows.cpp",
"gl_manager_windows_native.cpp",
"gl_manager_windows_angle.cpp",
"wgl_detect_version.cpp",
@@ -135,8 +136,8 @@ if env["d3d12"]:
if not os.getenv("VCINSTALLDIR"):
if env["debug_symbols"]:
- env.AddPostAction(prog, run_in_subprocess(platform_windows_builders.make_debug_mingw))
+ env.AddPostAction(prog, env.Run(platform_windows_builders.make_debug_mingw))
if env["windows_subsystem"] == "gui":
- env.AddPostAction(prog_wrap, run_in_subprocess(platform_windows_builders.make_debug_mingw))
+ env.AddPostAction(prog_wrap, env.Run(platform_windows_builders.make_debug_mingw))
env.platform_sources += sources
diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp
index e32fbd47dd..133d36aa0d 100644
--- a/platform/windows/crash_handler_windows.cpp
+++ b/platform/windows/crash_handler_windows.cpp
@@ -109,7 +109,7 @@ public:
ret.module_name = temp;
std::vector<char> img(ret.image_name.begin(), ret.image_name.end());
std::vector<char> mod(ret.module_name.begin(), ret.module_name.end());
- SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size);
+ SymLoadModule64(process, nullptr, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size);
return ret;
}
};
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 4585884859..f34d479345 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -202,6 +202,7 @@ def get_opts():
BoolVariable("use_asan", "Use address sanitizer (ASAN)", False),
BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False),
BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False),
+ BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting any errors to stderr.", True),
("angle_libs", "Path to the ANGLE static libraries", ""),
# Direct3D 12 support.
(
@@ -392,6 +393,39 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
## Compile/link flags
+ env["MAXLINELENGTH"] = 8192 # Windows Vista and beyond, so always applicable.
+
+ if env["silence_msvc"]:
+ from tempfile import mkstemp
+
+ old_spawn = env["SPAWN"]
+
+ def spawn_capture(sh, escape, cmd, args, env):
+ # We only care about cl/link, process everything else as normal.
+ if args[0] not in ["cl", "link"]:
+ return old_spawn(sh, escape, cmd, args, env)
+
+ tmp_stdout, tmp_stdout_name = mkstemp()
+ os.close(tmp_stdout)
+ args.append(f">{tmp_stdout_name}")
+ ret = old_spawn(sh, escape, cmd, args, env)
+
+ try:
+ with open(tmp_stdout_name, "rb") as tmp_stdout:
+ # First line is always bloat, subsequent lines are always errors. If content
+ # exists after discarding the first line, safely decode & send to stderr.
+ tmp_stdout.readline()
+ content = tmp_stdout.read()
+ if content:
+ sys.stderr.write(content.decode(sys.stdout.encoding, "replace"))
+ os.remove(tmp_stdout_name)
+ except OSError:
+ pass
+
+ return ret
+
+ env["SPAWN"] = spawn_capture
+
if env["debug_crt"]:
# Always use dynamic runtime, static debug CRT breaks thread_local.
env.AppendUnique(CCFLAGS=["/MDd"])
@@ -422,6 +456,10 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
else:
print("Missing environment variable: WindowsSdkDir")
+ if int(env["target_win_version"], 16) < 0x0601:
+ print("`target_win_version` should be 0x0601 or higher (Windows 7).")
+ sys.exit(255)
+
env.AppendUnique(
CPPDEFINES=[
"WINDOWS_ENABLED",
@@ -486,7 +524,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
sys.exit(255)
env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
- LIBS += ["d3d12", "dxgi", "dxguid"]
+ LIBS += ["dxgi", "dxguid"]
LIBS += ["version"] # Mesa dependency.
# Needed for avoiding C1128.
@@ -651,6 +689,10 @@ def configure_mingw(env: "SConsEnvironment"):
## Compile flags
+ if int(env["target_win_version"], 16) < 0x0601:
+ print("`target_win_version` should be 0x0601 or higher (Windows 7).")
+ sys.exit(255)
+
if not env["use_llvm"]:
env.Append(CCFLAGS=["-mwindows"])
@@ -711,7 +753,7 @@ def configure_mingw(env: "SConsEnvironment"):
sys.exit(255)
env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
- env.Append(LIBS=["d3d12", "dxgi", "dxguid"])
+ env.Append(LIBS=["dxgi", "dxguid"])
# PIX
if not env["arch"] in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]):
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 16eabf6855..b6b713687f 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -35,9 +35,9 @@
#include "core/config/project_settings.h"
#include "core/io/marshalls.h"
+#include "core/version.h"
#include "drivers/png/png_driver_common.h"
#include "main/main.h"
-#include "scene/resources/atlas_texture.h"
#if defined(VULKAN_ENABLED)
#include "rendering_context_driver_vulkan_windows.h"
@@ -51,6 +51,9 @@
#include <avrt.h>
#include <dwmapi.h>
+#include <propkey.h>
+#include <propvarutil.h>
+#include <shellapi.h>
#include <shlwapi.h>
#include <shobjidl.h>
#include <wbemcli.h>
@@ -93,6 +96,11 @@ static void track_mouse_leave_event(HWND hWnd) {
bool DisplayServerWindows::has_feature(Feature p_feature) const {
switch (p_feature) {
+#ifndef DISABLE_DEPRECATED
+ case FEATURE_GLOBAL_MENU: {
+ return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
+ } break;
+#endif
case FEATURE_SUBWINDOWS:
case FEATURE_TOUCHSCREEN:
case FEATURE_MOUSE:
@@ -106,6 +114,8 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_ICON:
case FEATURE_NATIVE_ICON:
case FEATURE_NATIVE_DIALOG:
+ case FEATURE_NATIVE_DIALOG_INPUT:
+ case FEATURE_NATIVE_DIALOG_FILE:
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_TEXT_TO_SPEECH:
@@ -194,8 +204,8 @@ void DisplayServerWindows::_register_raw_input_devices(WindowID p_target_window)
rid[1].hwndTarget = windows[p_target_window].hWnd;
} else {
// Follow the keyboard focus
- rid[0].hwndTarget = 0;
- rid[1].hwndTarget = 0;
+ rid[0].hwndTarget = nullptr;
+ rid[1].hwndTarget = nullptr;
}
if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE) {
@@ -264,7 +274,7 @@ public:
QITABENT(FileDialogEventHandler, IFileDialogEvents),
QITABENT(FileDialogEventHandler, IFileDialogControlEvents),
#endif
- { 0, 0 },
+ { nullptr, 0 },
};
return QISearch(this, qit, riid, ppv);
}
@@ -754,15 +764,15 @@ Ref<Image> DisplayServerWindows::clipboard_get_image() const {
}
} else if (IsClipboardFormatAvailable(CF_DIB)) {
HGLOBAL mem = GetClipboardData(CF_DIB);
- if (mem != NULL) {
+ if (mem != nullptr) {
BITMAPINFO *ptr = static_cast<BITMAPINFO *>(GlobalLock(mem));
- if (ptr != NULL) {
+ if (ptr != nullptr) {
BITMAPINFOHEADER *info = &ptr->bmiHeader;
void *dib_bits = (void *)(ptr->bmiColors);
// Draw DIB image to temporary DC surface and read it back as BGRA8.
- HDC dc = GetDC(0);
+ HDC dc = GetDC(nullptr);
if (dc) {
HDC hdc = CreateCompatibleDC(dc);
if (hdc) {
@@ -796,7 +806,7 @@ Ref<Image> DisplayServerWindows::clipboard_get_image() const {
}
DeleteDC(hdc);
}
- ReleaseDC(NULL, dc);
+ ReleaseDC(nullptr, dc);
}
GlobalUnlock(mem);
}
@@ -860,7 +870,7 @@ int DisplayServerWindows::get_screen_count() const {
}
int DisplayServerWindows::get_primary_screen() const {
- EnumScreenData data = { 0, 0, 0 };
+ EnumScreenData data = { 0, 0, nullptr };
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcPrim, (LPARAM)&data);
return data.screen;
}
@@ -895,8 +905,7 @@ static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRE
static BOOL CALLBACK _MonitorEnumProcOrigin(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumPosData *data = (EnumPosData *)dwData;
- data->pos.x = MIN(data->pos.x, lprcMonitor->left);
- data->pos.y = MIN(data->pos.y, lprcMonitor->top);
+ data->pos = data->pos.min(Point2(lprcMonitor->left, lprcMonitor->top));
return TRUE;
}
@@ -1108,16 +1117,16 @@ Color DisplayServerWindows::screen_get_pixel(const Point2i &p_position) const {
p.x = pos.x;
p.y = pos.y;
if (win81p_LogicalToPhysicalPointForPerMonitorDPI) {
- win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p);
+ win81p_LogicalToPhysicalPointForPerMonitorDPI(nullptr, &p);
}
- HDC dc = GetDC(0);
+ HDC dc = GetDC(nullptr);
if (dc) {
COLORREF col = GetPixel(dc, p.x, p.y);
if (col != CLR_INVALID) {
- ReleaseDC(NULL, dc);
+ ReleaseDC(nullptr, dc);
return Color(float(col & 0x000000FF) / 255.0f, float((col & 0x0000FF00) >> 8) / 255.0f, float((col & 0x00FF0000) >> 16) / 255.0f, 1.0f);
}
- ReleaseDC(NULL, dc);
+ ReleaseDC(nullptr, dc);
}
return Color();
@@ -1148,12 +1157,12 @@ Ref<Image> DisplayServerWindows::screen_get_image(int p_screen) const {
p2.x = pos.x + size.x;
p2.y = pos.y + size.y;
if (win81p_LogicalToPhysicalPointForPerMonitorDPI) {
- win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p1);
- win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p2);
+ win81p_LogicalToPhysicalPointForPerMonitorDPI(nullptr, &p1);
+ win81p_LogicalToPhysicalPointForPerMonitorDPI(nullptr, &p2);
}
Ref<Image> img;
- HDC dc = GetDC(0);
+ HDC dc = GetDC(nullptr);
if (dc) {
HDC hdc = CreateCompatibleDC(dc);
int width = p2.x - p1.x;
@@ -1186,7 +1195,7 @@ Ref<Image> DisplayServerWindows::screen_get_image(int p_screen) const {
}
DeleteDC(hdc);
}
- ReleaseDC(NULL, dc);
+ ReleaseDC(nullptr, dc);
}
return img;
@@ -1375,6 +1384,15 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
WindowData &wd = windows[p_window];
+ IPropertyStore *prop_store;
+ HRESULT hr = SHGetPropertyStoreForWindow(wd.hWnd, IID_IPropertyStore, (void **)&prop_store);
+ if (hr == S_OK) {
+ PROPVARIANT val;
+ PropVariantInit(&val);
+ prop_store->SetValue(PKEY_AppUserModel_ID, val);
+ prop_store->Release();
+ }
+
while (wd.transient_children.size()) {
window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);
}
@@ -1403,7 +1421,7 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window].wtctx) {
wintab_WTClose(windows[p_window].wtctx);
- windows[p_window].wtctx = 0;
+ windows[p_window].wtctx = nullptr;
}
DestroyWindow(windows[p_window].hWnd);
windows.erase(p_window);
@@ -1524,7 +1542,7 @@ Size2i DisplayServerWindows::window_get_title_size(const String &p_title, Window
return size;
}
- HDC hdc = GetDCEx(wd.hWnd, NULL, DCX_WINDOW);
+ HDC hdc = GetDCEx(wd.hWnd, nullptr, DCX_WINDOW);
if (hdc) {
Char16String s = p_title.utf16();
SIZE text_size;
@@ -1542,8 +1560,8 @@ Size2i DisplayServerWindows::window_get_title_size(const String &p_title, Window
ClientToScreen(wd.hWnd, (POINT *)&rect.right);
if (win81p_PhysicalToLogicalPointForPerMonitorDPI) {
- win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.left);
- win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.right);
+ win81p_PhysicalToLogicalPointForPerMonitorDPI(nullptr, (POINT *)&rect.left);
+ win81p_PhysicalToLogicalPointForPerMonitorDPI(nullptr, (POINT *)&rect.right);
}
size.x += (rect.right - rect.left);
@@ -1620,8 +1638,7 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi
Size2i wsize = window_get_size(p_window);
wpos += srect.position;
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);
window_set_position(wpos, p_window);
}
}
@@ -1878,7 +1895,7 @@ Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window)
return Size2();
}
-void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
+void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
// Windows docs for window styles:
// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
@@ -1892,7 +1909,17 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
if (p_fullscreen || p_borderless) {
r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past.
- if ((p_fullscreen && p_multiwindow_fs) || p_maximized) {
+ if (p_maximized) {
+ r_style |= WS_MAXIMIZE;
+ }
+ if (!p_fullscreen) {
+ r_style |= WS_SYSMENU | WS_MINIMIZEBOX;
+
+ if (p_resizable) {
+ r_style |= WS_MAXIMIZEBOX;
+ }
+ }
+ if ((p_fullscreen && p_multiwindow_fs) || p_maximized_fs) {
r_style |= WS_BORDER; // Allows child windows to be displayed on top of full screen.
}
} else {
@@ -1928,7 +1955,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain
DWORD style = 0;
DWORD style_ex = 0;
- _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus || wd.is_popup, style, style_ex);
+ _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex);
SetWindowLongPtr(wd.hWnd, GWL_STYLE, style);
SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex);
@@ -1971,10 +1998,11 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
wd.pre_fs_valid = true;
}
+ ShowWindow(wd.hWnd, SW_RESTORE);
MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
if (restore_mouse_trails > 1) {
- SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, 0, 0);
+ SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, nullptr, 0);
restore_mouse_trails = 0;
}
}
@@ -2006,7 +2034,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
}
if ((p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) && !wd.fullscreen) {
- if (wd.minimized) {
+ if (wd.minimized || wd.maximized) {
ShowWindow(wd.hWnd, SW_RESTORE);
}
wd.was_maximized = wd.maximized;
@@ -2031,7 +2059,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
// Save number of trails so we can restore when exiting, then turn off mouse trails
SystemParametersInfoA(SPI_GETMOUSETRAILS, 0, &restore_mouse_trails, 0);
if (restore_mouse_trails > 1) {
- SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, 0, 0);
+ SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, nullptr, 0);
}
}
}
@@ -2286,10 +2314,10 @@ void DisplayServerWindows::window_set_ime_active(const bool p_active, WindowID p
if (p_active) {
wd.ime_active = true;
ImmAssociateContext(wd.hWnd, wd.im_himc);
- CreateCaret(wd.hWnd, NULL, 1, 1);
+ CreateCaret(wd.hWnd, nullptr, 1, 1);
window_set_ime_position(wd.im_position, p_window);
} else {
- ImmAssociateContext(wd.hWnd, (HIMC)0);
+ ImmAssociateContext(wd.hWnd, (HIMC) nullptr);
DestroyCaret();
wd.ime_active = false;
}
@@ -2304,7 +2332,7 @@ void DisplayServerWindows::window_set_ime_position(const Point2i &p_pos, WindowI
wd.im_position = p_pos;
HIMC himc = ImmGetContext(wd.hWnd);
- if (himc == (HIMC)0) {
+ if (himc == (HIMC) nullptr) {
return;
}
@@ -2380,38 +2408,10 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor
cursors_cache.erase(p_shape);
}
- Ref<Texture2D> texture = p_cursor;
- ERR_FAIL_COND(!texture.is_valid());
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Size2 texture_size;
Rect2 atlas_rect;
-
- if (atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- 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.");
- }
+ Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
+ ERR_FAIL_COND(image.is_null());
+ Vector2i texture_size = image->get_size();
UINT image_size = texture_size.width * texture_size.height;
@@ -2440,7 +2440,7 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor
int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
- if (atlas_texture.is_valid()) {
+ if (atlas_rect.has_area()) {
column_index = MIN(column_index, atlas_rect.size.width - 1);
row_index = MIN(row_index, atlas_rect.size.height - 1);
}
@@ -2506,6 +2506,298 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) {
AllowSetForegroundWindow(pid);
}
+static HRESULT CALLBACK win32_task_dialog_callback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) {
+ if (msg == TDN_CREATED) {
+ // To match the input text dialog.
+ SendMessageW(hwnd, WM_SETICON, ICON_BIG, 0);
+ SendMessageW(hwnd, WM_SETICON, ICON_SMALL, 0);
+ }
+
+ return 0;
+}
+
+Error DisplayServerWindows::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
+
+ TASKDIALOGCONFIG config;
+ ZeroMemory(&config, sizeof(TASKDIALOGCONFIG));
+ config.cbSize = sizeof(TASKDIALOGCONFIG);
+
+ Char16String title = p_title.utf16();
+ Char16String message = p_description.utf16();
+ List<Char16String> buttons;
+ for (String s : p_buttons) {
+ buttons.push_back(s.utf16());
+ }
+
+ config.pszWindowTitle = (LPCWSTR)(title.get_data());
+ config.pszContent = (LPCWSTR)(message.get_data());
+
+ const int button_count = MIN(buttons.size(), 8);
+ config.cButtons = button_count;
+
+ // No dynamic stack array size :(
+ TASKDIALOG_BUTTON *tbuttons = button_count != 0 ? (TASKDIALOG_BUTTON *)alloca(sizeof(TASKDIALOG_BUTTON) * button_count) : nullptr;
+ if (tbuttons) {
+ for (int i = 0; i < button_count; i++) {
+ tbuttons[i].nButtonID = i;
+ tbuttons[i].pszButtonText = (LPCWSTR)(buttons[i].get_data());
+ }
+ }
+ config.pButtons = tbuttons;
+ config.pfCallback = win32_task_dialog_callback;
+
+ Error result = FAILED;
+ HMODULE comctl = LoadLibraryW(L"comctl32.dll");
+ if (comctl) {
+ typedef HRESULT(WINAPI * TaskDialogIndirectPtr)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
+
+ TaskDialogIndirectPtr task_dialog_indirect = (TaskDialogIndirectPtr)GetProcAddress(comctl, "TaskDialogIndirect");
+ int button_pressed;
+
+ if (task_dialog_indirect && SUCCEEDED(task_dialog_indirect(&config, &button_pressed, nullptr, nullptr))) {
+ if (!p_callback.is_null()) {
+ Variant button = button_pressed;
+ const Variant *args[1] = { &button };
+ Variant ret;
+ Callable::CallError ce;
+ p_callback.callp(args, 1, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute dialog callback: %s.", Variant::get_callable_error_text(p_callback, args, 1, ce)));
+ }
+ }
+
+ result = OK;
+ }
+ FreeLibrary(comctl);
+ } else {
+ ERR_PRINT("Unable to create native dialog.");
+ }
+
+ return result;
+}
+
+struct Win32InputTextDialogInit {
+ const char16_t *title;
+ const char16_t *description;
+ const char16_t *partial;
+ const Callable &callback;
+};
+
+static int scale_with_dpi(int p_pos, int p_dpi) {
+ return IsProcessDPIAware() ? (p_pos * p_dpi / 96) : p_pos;
+}
+
+static INT_PTR input_text_dialog_init(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) {
+ Win32InputTextDialogInit init = *(Win32InputTextDialogInit *)lParam;
+ SetWindowLongPtrW(hWnd, GWLP_USERDATA, (LONG_PTR)&init.callback); // Set dialog callback.
+
+ SetWindowTextW(hWnd, (LPCWSTR)init.title);
+
+ const int dpi = DisplayServerWindows::get_singleton()->screen_get_dpi();
+
+ const int margin = scale_with_dpi(7, dpi);
+ const SIZE dlg_size = { scale_with_dpi(300, dpi), scale_with_dpi(50, dpi) };
+
+ int str_len = lstrlenW((LPCWSTR)init.description);
+ SIZE str_size = { dlg_size.cx, 0 };
+ if (str_len > 0) {
+ HDC hdc = GetDC(nullptr);
+ RECT trect = { margin, margin, margin + dlg_size.cx, margin + dlg_size.cy };
+ SelectObject(hdc, (HFONT)SendMessageW(hWnd, WM_GETFONT, 0, 0));
+
+ // `+ margin` adds some space between the static text and the edit field.
+ // Don't scale this with DPI because DPI is already handled by DrawText.
+ str_size.cy = DrawTextW(hdc, (LPCWSTR)init.description, str_len, &trect, DT_LEFT | DT_WORDBREAK | DT_CALCRECT) + margin;
+
+ ReleaseDC(nullptr, hdc);
+ }
+
+ RECT crect, wrect;
+ GetClientRect(hWnd, &crect);
+ GetWindowRect(hWnd, &wrect);
+ int sw = GetSystemMetrics(SM_CXSCREEN);
+ int sh = GetSystemMetrics(SM_CYSCREEN);
+ int new_width = dlg_size.cx + margin * 2 + wrect.right - wrect.left - crect.right;
+ int new_height = dlg_size.cy + margin * 2 + wrect.bottom - wrect.top - crect.bottom + str_size.cy;
+
+ MoveWindow(hWnd, (sw - new_width) / 2, (sh - new_height) / 2, new_width, new_height, true);
+
+ HWND ok_button = GetDlgItem(hWnd, 1);
+ MoveWindow(ok_button,
+ dlg_size.cx + margin - scale_with_dpi(65, dpi),
+ dlg_size.cy + str_size.cy + margin - scale_with_dpi(20, dpi),
+ scale_with_dpi(65, dpi), scale_with_dpi(20, dpi), true);
+
+ HWND description = GetDlgItem(hWnd, 3);
+ MoveWindow(description, margin, margin, dlg_size.cx, str_size.cy, true);
+ SetWindowTextW(description, (LPCWSTR)init.description);
+
+ HWND text_edit = GetDlgItem(hWnd, 2);
+ MoveWindow(text_edit, margin, str_size.cy + margin, dlg_size.cx, scale_with_dpi(20, dpi), true);
+ SetWindowTextW(text_edit, (LPCWSTR)init.partial);
+
+ return TRUE;
+}
+
+static INT_PTR input_text_dialog_cmd_proc(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) {
+ if (LOWORD(wParam) == 1) {
+ HWND text_edit = GetDlgItem(hWnd, 2);
+ ERR_FAIL_NULL_V(text_edit, false);
+
+ Char16String text;
+ text.resize(GetWindowTextLengthW(text_edit) + 1);
+ GetWindowTextW(text_edit, (LPWSTR)text.get_data(), text.size());
+
+ const Callable *callback = (const Callable *)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
+ if (callback && callback->is_valid()) {
+ Variant v_result = String((const wchar_t *)text.get_data());
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[1] = { &v_result };
+
+ callback->callp(args, 1, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute input dialog callback: %s.", Variant::get_callable_error_text(*callback, args, 1, ce)));
+ }
+ }
+
+ return EndDialog(hWnd, 0);
+ }
+
+ return false;
+}
+
+static INT_PTR CALLBACK input_text_dialog_proc(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) {
+ switch (code) {
+ case WM_INITDIALOG:
+ return input_text_dialog_init(hWnd, code, wParam, lParam);
+
+ case WM_COMMAND:
+ return input_text_dialog_cmd_proc(hWnd, code, wParam, lParam);
+
+ default:
+ return FALSE;
+ }
+}
+
+Error DisplayServerWindows::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) {
+#pragma pack(push, 1)
+
+ // NOTE: Use default/placeholder coordinates here. Windows uses its own coordinate system
+ // specifically for dialogs which relies on font sizes instead of pixels.
+ const struct {
+ WORD dlgVer; // must be 1
+ WORD signature; // must be 0xFFFF
+ DWORD helpID;
+ DWORD exStyle;
+ DWORD style;
+ WORD cDlgItems;
+ short x;
+ short y;
+ short cx;
+ short cy;
+ WCHAR menu[1]; // must be 0
+ WCHAR windowClass[7]; // must be "#32770" -- the default window class for dialogs
+ WCHAR title[1]; // must be 0
+ WORD pointsize;
+ WORD weight;
+ BYTE italic;
+ BYTE charset;
+ WCHAR font[13]; // must be "MS Shell Dlg"
+ } template_base = {
+ 1, 0xFFFF, 0, 0,
+ DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU,
+ 3, 0, 0, 20, 20, L"", L"#32770", L"", 8, FW_NORMAL, 0, DEFAULT_CHARSET, L"MS Shell Dlg"
+ };
+
+ const struct {
+ DWORD helpID;
+ DWORD exStyle;
+ DWORD style;
+ short x;
+ short y;
+ short cx;
+ short cy;
+ DWORD id;
+ WCHAR windowClass[7]; // must be "Button"
+ WCHAR title[3]; // must be "OK"
+ WORD extraCount;
+ } ok_button = {
+ 0, 0, WS_VISIBLE | BS_DEFPUSHBUTTON, 0, 0, 50, 14, 1, WC_BUTTONW, L"OK", 0
+ };
+ const struct {
+ DWORD helpID;
+ DWORD exStyle;
+ DWORD style;
+ short x;
+ short y;
+ short cx;
+ short cy;
+ DWORD id;
+ WCHAR windowClass[5]; // must be "Edit"
+ WCHAR title[1]; // must be 0
+ WORD extraCount;
+ } text_field = {
+ 0, 0, WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 0, 0, 250, 14, 2, WC_EDITW, L"", 0
+ };
+ const struct {
+ DWORD helpID;
+ DWORD exStyle;
+ DWORD style;
+ short x;
+ short y;
+ short cx;
+ short cy;
+ DWORD id;
+ WCHAR windowClass[7]; // must be "Static"
+ WCHAR title[1]; // must be 0
+ WORD extraCount;
+ } static_text = {
+ 0, 0, WS_VISIBLE, 0, 0, 250, 14, 3, WC_STATICW, L"", 0
+ };
+
+#pragma pack(pop)
+
+ // Dialog template
+ const size_t data_size = sizeof(template_base) + (sizeof(template_base) % 4) +
+ sizeof(ok_button) + (sizeof(ok_button) % 4) +
+ sizeof(text_field) + (sizeof(text_field) % 4) +
+ sizeof(static_text) + (sizeof(static_text) % 4);
+
+ void *data_template = memalloc(data_size);
+ ERR_FAIL_NULL_V_MSG(data_template, FAILED, "Unable to allocate memory for the dialog template.");
+ ZeroMemory(data_template, data_size);
+
+ char *current_block = (char *)data_template;
+ CopyMemory(current_block, &template_base, sizeof(template_base));
+ current_block += sizeof(template_base) + (sizeof(template_base) % 4);
+ CopyMemory(current_block, &ok_button, sizeof(ok_button));
+ current_block += sizeof(ok_button) + (sizeof(ok_button) % 4);
+ CopyMemory(current_block, &text_field, sizeof(text_field));
+ current_block += sizeof(text_field) + (sizeof(text_field) % 4);
+ CopyMemory(current_block, &static_text, sizeof(static_text));
+
+ Char16String title16 = p_title.utf16();
+ Char16String description16 = p_description.utf16();
+ Char16String partial16 = p_partial.utf16();
+
+ Win32InputTextDialogInit init = {
+ title16.get_data(), description16.get_data(), partial16.get_data(), p_callback
+ };
+
+ // No modal dialogs for specific windows? Assume main window here.
+ INT_PTR ret = DialogBoxIndirectParamW(hInstance, (LPDLGTEMPLATEW)data_template, nullptr, (DLGPROC)input_text_dialog_proc, (LPARAM)(&init));
+
+ Error result = ret != -1 ? OK : FAILED;
+ memfree(data_template);
+
+ if (result == FAILED) {
+ ERR_PRINT("Unable to create native dialog.");
+ }
+ return result;
+}
+
int DisplayServerWindows::keyboard_get_layout_count() const {
return GetKeyboardLayoutList(0, nullptr);
}
@@ -2670,7 +2962,7 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const {
}
void DisplayServerWindows::process_events() {
- _THREAD_SAFE_METHOD_
+ _THREAD_SAFE_LOCK_
MSG msg;
@@ -2685,7 +2977,10 @@ void DisplayServerWindows::process_events() {
if (!drop_events) {
_process_key_events();
+ _THREAD_SAFE_UNLOCK_
Input::get_singleton()->flush_buffered_events();
+ } else {
+ _THREAD_SAFE_UNLOCK_
}
}
@@ -2698,9 +2993,14 @@ void DisplayServerWindows::force_process_and_drop_events() {
}
void DisplayServerWindows::release_rendering_thread() {
-}
-
-void DisplayServerWindows::make_rendering_thread() {
+#if defined(GLES3_ENABLED)
+ if (gl_manager_angle) {
+ gl_manager_angle->release_current();
+ }
+ if (gl_manager_native) {
+ gl_manager_native->release_current();
+ }
+#endif
}
void DisplayServerWindows::swap_buffers() {
@@ -3081,9 +3381,9 @@ void DisplayServerWindows::set_context(Context p_context) {
#define SIGNATURE_MASK 0xFFFFFF00
// Keeping the name suggested by Microsoft, but this macro really answers:
// Is this mouse event emulated from touch or pen input?
-#define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE)
+#define IsPenEvent(dw) (((dw) & SIGNATURE_MASK) == MI_WP_SIGNATURE)
// This one tells whether the event comes from touchscreen (and not from pen).
-#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80))
+#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw) & 0x80))
void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx) {
if (touch_state.has(idx) == p_pressed) {
@@ -3141,7 +3441,6 @@ void DisplayServerWindows::_dispatch_input_events(const Ref<InputEvent> &p_event
}
void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) {
- _THREAD_SAFE_METHOD_
if (in_dispatch_input_event) {
return;
}
@@ -3383,6 +3682,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
// Process window messages.
switch (uMsg) {
+ case WM_MENUCOMMAND: {
+ native_menu->_menu_activate(HMENU(lParam), (int)wParam);
+ } break;
case WM_CREATE: {
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
@@ -3425,63 +3727,18 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
return MA_NOACTIVATE; // Do not activate, but process mouse messages.
}
} break;
- case WM_SETFOCUS: {
- windows[window_id].window_has_focus = true;
- last_focused_window = window_id;
-
- // Restore mouse mode.
- _set_mouse_mode_impl(mouse_mode);
-
- if (!app_focused) {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
- }
- app_focused = true;
- }
- } break;
- case WM_KILLFOCUS: {
- windows[window_id].window_has_focus = false;
- last_focused_window = window_id;
-
- // Release capture unconditionally because it can be set due to dragging, in addition to captured mode.
- ReleaseCapture();
-
- // Release every touch to avoid sticky points.
- for (const KeyValue<int, Vector2> &E : touch_state) {
- _touch_event(window_id, false, E.value.x, E.value.y, E.key);
- }
- touch_state.clear();
-
- bool self_steal = false;
- HWND new_hwnd = (HWND)wParam;
- if (IsWindow(new_hwnd)) {
- self_steal = true;
+ case WM_ACTIVATEAPP: {
+ bool new_app_focused = (bool)wParam;
+ if (new_app_focused == app_focused) {
+ break;
}
-
- if (!self_steal) {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
- }
- app_focused = false;
+ app_focused = new_app_focused;
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(app_focused ? MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN : MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
}
} break;
- case WM_ACTIVATE: { // Watch for window activate message.
- if (!windows[window_id].window_focused) {
- _process_activate_event(window_id, wParam, lParam);
- } else {
- windows[window_id].saved_wparam = wParam;
- windows[window_id].saved_lparam = lParam;
-
- // Run a timer to prevent event catching warning if the focused window is closing.
- windows[window_id].focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
- }
- if (wParam != WA_INACTIVE) {
- track_mouse_leave_event(hWnd);
-
- if (!IsIconic(hWnd)) {
- SetFocus(hWnd);
- }
- }
+ case WM_ACTIVATE: {
+ _process_activate_event(window_id, wParam, lParam);
return 0; // Return to the message loop.
} break;
case WM_GETMINMAXINFO: {
@@ -3498,6 +3755,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
min_max_info->ptMaxTrackSize.x = windows[window_id].max_size.x + decor.x;
min_max_info->ptMaxTrackSize.y = windows[window_id].max_size.y + decor.y;
}
+ if (windows[window_id].borderless) {
+ Rect2i screen_rect = screen_get_usable_rect(window_get_current_screen(window_id));
+
+ // Set the size of (borderless) maximized mode to exclude taskbar (or any other panel) if present.
+ min_max_info->ptMaxPosition.x = screen_rect.position.x;
+ min_max_info->ptMaxPosition.y = screen_rect.position.y;
+ min_max_info->ptMaxSize.x = screen_rect.size.x;
+ min_max_info->ptMaxSize.y = screen_rect.size.y;
+ }
return 0;
}
} break;
@@ -3549,9 +3815,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
case SC_MONITORPOWER: // Monitor trying to enter powersave?
return 0; // Prevent from happening.
case SC_KEYMENU:
- if ((lParam >> 16) <= 0) {
+ Engine *engine = Engine::get_singleton();
+ if (((lParam >> 16) <= 0) && !engine->is_project_manager_hint() && !engine->is_editor_hint() && !GLOBAL_GET("application/run/enable_alt_space_menu")) {
+ return 0;
+ }
+ if (!alt_mem || !(GetAsyncKeyState(VK_SPACE) & (1 << 15))) {
return 0;
}
+ SendMessage(windows[window_id].hWnd, WM_SYSKEYUP, VK_SPACE, 0);
+ SendMessage(windows[window_id].hWnd, WM_SYSKEYUP, VK_MENU, 0);
}
} break;
case WM_INDICATOR_CALLBACK_MESSAGE: {
@@ -3580,9 +3852,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WM_CLOSE: // Did we receive a close message?
{
- if (windows[window_id].focus_timer_id != 0U) {
- KillTimer(windows[window_id].hWnd, windows[window_id].focus_timer_id);
- }
_send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);
return 0; // Jump back.
@@ -3694,7 +3963,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
mm->set_relative_screen_position(mm->get_relative());
- if ((windows[window_id].window_has_focus || windows[window_id].is_popup) && mm->get_relative() != Vector2()) {
+ if ((windows[window_id].window_focused || windows[window_id].is_popup) && mm->get_relative() != Vector2()) {
Input::get_singleton()->parse_input_event(mm);
}
}
@@ -3742,7 +4011,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
windows[window_id].last_pen_inverted = inverted;
// Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
- if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) {
+ if (!windows[window_id].window_focused && mouse_mode == MOUSE_MODE_CAPTURED) {
break;
}
@@ -3792,7 +4061,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_relative_screen_position(mm->get_relative());
old_x = mm->get_position().x;
old_y = mm->get_position().y;
- if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) {
+ if (windows[window_id].window_focused || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
}
}
@@ -3878,7 +4147,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
// Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
- if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) {
+ if (!windows[window_id].window_focused && mouse_mode == MOUSE_MODE_CAPTURED) {
break;
}
@@ -3941,7 +4210,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_relative_screen_position(mm->get_relative());
old_x = mm->get_position().x;
old_y = mm->get_position().y;
- if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) {
+ if (windows[window_id].window_focused || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
}
@@ -3993,7 +4262,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
// Don't calculate relative mouse movement if we don't have focus in CAPTURED mode.
- if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) {
+ if (!windows[window_id].window_focused && mouse_mode == MOUSE_MODE_CAPTURED) {
break;
}
@@ -4285,10 +4554,23 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
window.minimized = true;
} else if (IsZoomed(hWnd)) {
window.maximized = true;
+
+ // If maximized_window_size == screen_size add 1px border to prevent switching to exclusive_fs.
+ if (!window.maximized_fs && window.borderless && window_rect.position == screen_position && window_rect.size == screen_size) {
+ // Window (borderless) was just maximized and the covers the entire screen.
+ window.maximized_fs = true;
+ _update_window_style(window_id, false);
+ }
} else if (window_rect.position == screen_position && window_rect.size == screen_size) {
window.fullscreen = true;
}
+ if (window.maximized_fs && !window.maximized) {
+ // Window (maximized and covering fullscreen) was just non-maximized.
+ window.maximized_fs = false;
+ _update_window_style(window_id, false);
+ }
+
if (!window.minimized) {
window.width = window_client_rect.size.width;
window.height = window_client_rect.size.height;
@@ -4341,10 +4623,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
if (!Main::is_iterating()) {
Main::iteration();
}
- } else if (wParam == windows[window_id].focus_timer_id) {
- _process_activate_event(window_id, windows[window_id].saved_wparam, windows[window_id].saved_lparam);
- KillTimer(windows[window_id].hWnd, wParam);
- windows[window_id].focus_timer_id = 0U;
}
} break;
case WM_SYSKEYUP:
@@ -4493,7 +4771,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WM_SETCURSOR: {
if (LOWORD(lParam) == HTCLIENT) {
- if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) {
+ if (windows[window_id].window_focused && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) {
// Hide the cursor.
if (hCursor == nullptr) {
hCursor = SetCursor(nullptr);
@@ -4550,20 +4828,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM wParam, LPARAM lParam) {
if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) {
- _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN);
- windows[p_window_id].window_focused = true;
+ last_focused_window = p_window_id;
alt_mem = false;
control_mem = false;
shift_mem = false;
gr_mem = false;
-
- // Restore mouse mode.
_set_mouse_mode_impl(mouse_mode);
+ if (!IsIconic(windows[p_window_id].hWnd)) {
+ SetFocus(windows[p_window_id].hWnd);
+ }
+ windows[p_window_id].window_focused = true;
+ _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN);
} else { // WM_INACTIVE.
Input::get_singleton()->release_pressed_events();
- _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT);
- windows[p_window_id].window_focused = false;
+ track_mouse_leave_event(windows[p_window_id].hWnd);
+ // Release capture unconditionally because it can be set due to dragging, in addition to captured mode.
+ ReleaseCapture();
alt_mem = false;
+ windows[p_window_id].window_focused = false;
+ _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT);
}
if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window_id].wtctx) {
@@ -4731,7 +5014,7 @@ void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const
if ((p_old_driver == "wintab") && wintab_available && wd.wtctx) {
wintab_WTEnable(wd.wtctx, false);
wintab_WTClose(wd.wtctx);
- wd.wtctx = 0;
+ wd.wtctx = nullptr;
}
if ((p_new_driver == "wintab") && wintab_available) {
wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
@@ -4767,7 +5050,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
DWORD dwExStyle;
DWORD dwStyle;
- _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle);
+ _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle);
RECT WindowRect;
@@ -4792,8 +5075,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
Rect2i srect = screen_get_usable_rect(rq_screen);
Point2i wpos = p_rect.position;
if (srect != Rect2i()) {
- wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3);
- wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3);
+ wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3);
}
WindowRect.left = wpos.x;
@@ -4820,8 +5102,6 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
dwExStyle,
L"Engine", L"",
dwStyle,
- // (GetSystemMetrics(SM_CXSCREEN) - WindowRect.right) / 2,
- // (GetSystemMetrics(SM_CYSCREEN) - WindowRect.bottom) / 2,
WindowRect.left,
WindowRect.top,
WindowRect.right - WindowRect.left,
@@ -4939,7 +5219,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
print_verbose("WinTab context creation failed.");
}
} else {
- wd.wtctx = 0;
+ wd.wtctx = nullptr;
}
if (p_mode == WINDOW_MODE_MAXIMIZED) {
@@ -4956,9 +5236,36 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
wd.last_pressure_update = 0;
wd.last_tilt = Vector2();
+ IPropertyStore *prop_store;
+ HRESULT hr = SHGetPropertyStoreForWindow(wd.hWnd, IID_IPropertyStore, (void **)&prop_store);
+ if (hr == S_OK) {
+ PROPVARIANT val;
+ String appname;
+ if (Engine::get_singleton()->is_editor_hint()) {
+ appname = "Godot.GodotEditor." + String(VERSION_BRANCH);
+ } else {
+ String name = GLOBAL_GET("application/config/name");
+ String version = GLOBAL_GET("application/config/version");
+ if (version.is_empty()) {
+ version = "0";
+ }
+ String clean_app_name = name.to_pascal_case();
+ for (int i = 0; i < clean_app_name.length(); i++) {
+ if (!is_ascii_alphanumeric_char(clean_app_name[i]) && clean_app_name[i] != '_' && clean_app_name[i] != '.') {
+ clean_app_name[i] = '_';
+ }
+ }
+ clean_app_name = clean_app_name.substr(0, 120 - version.length()).trim_suffix(".");
+ appname = "Godot." + clean_app_name + "." + version;
+ }
+ InitPropVariantFromString((PCWSTR)appname.utf16().get_data(), &val);
+ prop_store->SetValue(PKEY_AppUserModel_ID, val);
+ prop_store->Release();
+ }
+
// IME.
wd.im_himc = ImmGetContext(wd.hWnd);
- ImmAssociateContext(wd.hWnd, (HIMC)0);
+ ImmAssociateContext(wd.hWnd, (HIMC) nullptr);
wd.im_position = Vector2();
@@ -4976,6 +5283,12 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
wd.height = p_rect.size.height;
}
+ // Set size of maximized borderless window (by default it covers the entire screen).
+ if (p_mode == WINDOW_MODE_MAXIMIZED && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) {
+ Rect2i srect = screen_get_usable_rect(rq_screen);
+ SetWindowPos(wd.hWnd, HWND_TOP, srect.position.x, srect.position.y, srect.size.width, srect.size.height, SWP_NOZORDER | SWP_NOACTIVATE);
+ }
+
window_id_counter++;
}
@@ -5013,17 +5326,17 @@ Vector2i _get_device_ids(const String &p_device_name) {
REFCLSID clsid = CLSID_WbemLocator; // Unmarshaler CLSID
REFIID uuid = IID_IWbemLocator; // Interface UUID
- IWbemLocator *wbemLocator = NULL; // to get the services
- IWbemServices *wbemServices = NULL; // to get the class
- IEnumWbemClassObject *iter = NULL;
+ IWbemLocator *wbemLocator = nullptr; // to get the services
+ IWbemServices *wbemServices = nullptr; // to get the class
+ IEnumWbemClassObject *iter = nullptr;
IWbemClassObject *pnpSDriverObject[1]; // contains driver name, version, etc.
- HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator);
+ HRESULT hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator);
if (hr != S_OK) {
return Vector2i();
}
BSTR resource_name = SysAllocString(L"root\\CIMV2");
- hr = wbemLocator->ConnectServer(resource_name, NULL, NULL, NULL, 0, NULL, NULL, &wbemServices);
+ hr = wbemLocator->ConnectServer(resource_name, nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbemServices);
SysFreeString(resource_name);
SAFE_RELEASE(wbemLocator) // from now on, use `wbemServices`
@@ -5037,7 +5350,7 @@ Vector2i _get_device_ids(const String &p_device_name) {
const String gpu_device_class_query = vformat("SELECT * FROM Win32_PnPSignedDriver WHERE DeviceName = \"%s\"", p_device_name);
BSTR query = SysAllocString((const WCHAR *)gpu_device_class_query.utf16().get_data());
BSTR query_lang = SysAllocString(L"WQL");
- hr = wbemServices->ExecQuery(query_lang, query, WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &iter);
+ hr = wbemServices->ExecQuery(query_lang, query, WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, nullptr, &iter);
SysFreeString(query_lang);
SysFreeString(query);
if (hr == S_OK) {
@@ -5048,7 +5361,7 @@ Vector2i _get_device_ids(const String &p_device_name) {
VARIANT did;
VariantInit(&did);
BSTR object_name = SysAllocString(L"DeviceID");
- hr = pnpSDriverObject[0]->Get(object_name, 0, &did, NULL, NULL);
+ hr = pnpSDriverObject[0]->Get(object_name, 0, &did, nullptr, nullptr);
SysFreeString(object_name);
if (hr == S_OK) {
String device_id = String(V_BSTR(&did));
@@ -5162,6 +5475,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
if (tts_enabled) {
tts = memnew(TTS_Windows);
}
+ native_menu = memnew(NativeMenuWindows);
// Enforce default keep screen on value.
screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
@@ -5187,6 +5501,32 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
GetImmersiveColorFromColorSetEx = (GetImmersiveColorFromColorSetExPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(95));
GetImmersiveColorTypeFromName = (GetImmersiveColorTypeFromNamePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(96));
GetImmersiveUserColorSetPreference = (GetImmersiveUserColorSetPreferencePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(98));
+ if (os_ver.dwBuildNumber >= 17763) {
+ AllowDarkModeForAppPtr AllowDarkModeForApp = nullptr;
+ SetPreferredAppModePtr SetPreferredAppMode = nullptr;
+ FlushMenuThemesPtr FlushMenuThemes = nullptr;
+ if (os_ver.dwBuildNumber < 18362) {
+ AllowDarkModeForApp = (AllowDarkModeForAppPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(135));
+ } else {
+ SetPreferredAppMode = (SetPreferredAppModePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(135));
+ FlushMenuThemes = (FlushMenuThemesPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(136));
+ }
+ RefreshImmersiveColorPolicyStatePtr RefreshImmersiveColorPolicyState = (RefreshImmersiveColorPolicyStatePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(104));
+ if (ShouldAppsUseDarkMode) {
+ bool dark_mode = ShouldAppsUseDarkMode();
+ if (SetPreferredAppMode) {
+ SetPreferredAppMode(dark_mode ? APPMODE_ALLOWDARK : APPMODE_DEFAULT);
+ } else if (AllowDarkModeForApp) {
+ AllowDarkModeForApp(dark_mode);
+ }
+ if (RefreshImmersiveColorPolicyState) {
+ RefreshImmersiveColorPolicyState();
+ }
+ if (FlushMenuThemes) {
+ FlushMenuThemes();
+ }
+ }
+ }
ux_theme_available = ShouldAppsUseDarkMode && GetImmersiveColorFromColorSetEx && GetImmersiveColorTypeFromName && GetImmersiveUserColorSetPreference;
if (os_ver.dwBuildNumber >= 18363) {
@@ -5245,6 +5585,23 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
}
+ HMODULE comctl32 = LoadLibraryW(L"comctl32.dll");
+ if (comctl32) {
+ typedef BOOL(WINAPI * InitCommonControlsExPtr)(_In_ const INITCOMMONCONTROLSEX *picce);
+ InitCommonControlsExPtr init_common_controls_ex = (InitCommonControlsExPtr)GetProcAddress(comctl32, "InitCommonControlsEx");
+
+ // Fails if the incorrect version was loaded. Probably not a big enough deal to print an error about.
+ if (init_common_controls_ex) {
+ INITCOMMONCONTROLSEX icc = {};
+ icc.dwICC = ICC_STANDARD_CLASSES;
+ icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
+ if (!init_common_controls_ex(&icc)) {
+ WARN_PRINT("Unable to initialize Windows common controls. Native dialogs may not work properly.");
+ }
+ }
+ FreeLibrary(comctl32);
+ }
+
memset(&wc, 0, sizeof(WNDCLASSEXW));
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_OWNDC | CS_DBLCLKS;
@@ -5295,7 +5652,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
if (rendering_driver == "opengl3") {
rendering_driver = "opengl3_angle";
}
-#else
+#elif defined(EGL_STATIC)
bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_angle");
if (fallback && (rendering_driver == "opengl3")) {
Dictionary gl_info = detect_wgl();
@@ -5354,6 +5711,26 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
#endif
+ String appname;
+ if (Engine::get_singleton()->is_editor_hint()) {
+ appname = "Godot.GodotEditor." + String(VERSION_BRANCH);
+ } else {
+ String name = GLOBAL_GET("application/config/name");
+ String version = GLOBAL_GET("application/config/version");
+ if (version.is_empty()) {
+ version = "0";
+ }
+ String clean_app_name = name.to_pascal_case();
+ for (int i = 0; i < clean_app_name.length(); i++) {
+ if (!is_ascii_alphanumeric_char(clean_app_name[i]) && clean_app_name[i] != '_' && clean_app_name[i] != '.') {
+ clean_app_name[i] = '_';
+ }
+ }
+ clean_app_name = clean_app_name.substr(0, 120 - version.length()).trim_suffix(".");
+ appname = "Godot." + clean_app_name + "." + version;
+ }
+ SetCurrentProcessExplicitAppUserModelID((PCWSTR)appname.utf16().get_data());
+
mouse_monitor = SetWindowsHookEx(WH_MOUSE, ::MouseProc, nullptr, GetCurrentThreadId());
Point2i window_position;
@@ -5479,7 +5856,7 @@ DisplayServerWindows::~DisplayServerWindows() {
cursors_cache.clear();
// Destroy all status indicators.
- for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
+ for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E; ++E) {
NOTIFYICONDATAW ndat;
ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
ndat.cbSize = sizeof(NOTIFYICONDATAW);
@@ -5501,6 +5878,11 @@ DisplayServerWindows::~DisplayServerWindows() {
// Close power request handle.
screen_set_keep_on(false);
+ if (native_menu) {
+ memdelete(native_menu);
+ native_menu = nullptr;
+ }
+
#ifdef GLES3_ENABLED
// destroy windows .. NYI?
// FIXME wglDeleteContext is never called
@@ -5518,7 +5900,7 @@ DisplayServerWindows::~DisplayServerWindows() {
#endif
if (wintab_available && windows[MAIN_WINDOW_ID].wtctx) {
wintab_WTClose(windows[MAIN_WINDOW_ID].wtctx);
- windows[MAIN_WINDOW_ID].wtctx = 0;
+ windows[MAIN_WINDOW_ID].wtctx = nullptr;
}
DestroyWindow(windows[MAIN_WINDOW_ID].hWnd);
}
@@ -5536,7 +5918,7 @@ DisplayServerWindows::~DisplayServerWindows() {
#endif
if (restore_mouse_trails > 1) {
- SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, 0, 0);
+ SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, nullptr, 0);
}
#ifdef GLES3_ENABLED
if (gl_manager_angle) {
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 81cddec49f..2fe1b0733d 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -61,6 +61,8 @@
#include "gl_manager_windows_native.h"
#endif // GLES3_ENABLED
+#include "native_menu_windows.h"
+
#include <io.h>
#include <stdio.h>
@@ -152,11 +154,23 @@ typedef UINT(WINAPI *WTInfoPtr)(UINT p_category, UINT p_index, LPVOID p_output);
typedef BOOL(WINAPI *WTPacketPtr)(HANDLE p_ctx, UINT p_param, LPVOID p_packets);
typedef BOOL(WINAPI *WTEnablePtr)(HANDLE p_ctx, BOOL p_enable);
+enum PreferredAppMode {
+ APPMODE_DEFAULT = 0,
+ APPMODE_ALLOWDARK = 1,
+ APPMODE_FORCEDARK = 2,
+ APPMODE_FORCELIGHT = 3,
+ APPMODE_MAX = 4
+};
+
typedef bool(WINAPI *ShouldAppsUseDarkModePtr)();
typedef DWORD(WINAPI *GetImmersiveColorFromColorSetExPtr)(UINT dwImmersiveColorSet, UINT dwImmersiveColorType, bool bIgnoreHighContrast, UINT dwHighContrastCacheMode);
typedef int(WINAPI *GetImmersiveColorTypeFromNamePtr)(const WCHAR *name);
typedef int(WINAPI *GetImmersiveUserColorSetPreferencePtr)(bool bForceCheckRegistry, bool bSkipCheckOnFail);
typedef HRESULT(WINAPI *RtlGetVersionPtr)(OSVERSIONINFOW *lpVersionInformation);
+typedef bool(WINAPI *AllowDarkModeForAppPtr)(bool darkMode);
+typedef PreferredAppMode(WINAPI *SetPreferredAppModePtr)(PreferredAppMode appMode);
+typedef void(WINAPI *RefreshImmersiveColorPolicyStatePtr)();
+typedef void(WINAPI *FlushMenuThemesPtr)();
// Windows Ink API
#ifndef POINTER_STRUCTURES
@@ -192,6 +206,7 @@ typedef UINT32 PEN_MASK;
#define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010
#endif
+#if WINVER < 0x0602
enum tagPOINTER_INPUT_TYPE {
PT_POINTER = 0x00000001,
PT_TOUCH = 0x00000002,
@@ -242,6 +257,7 @@ typedef struct tagPOINTER_PEN_INFO {
INT32 tiltX;
INT32 tiltY;
} POINTER_PEN_INFO;
+#endif
#endif //POINTER_STRUCTURES
@@ -356,6 +372,7 @@ class DisplayServerWindows : public DisplayServer {
HANDLE power_request;
TTS_Windows *tts = nullptr;
+ NativeMenuWindows *native_menu = nullptr;
struct WindowData {
HWND hWnd;
@@ -365,6 +382,7 @@ class DisplayServerWindows : public DisplayServer {
bool pre_fs_valid = false;
RECT pre_fs_rect;
bool maximized = false;
+ bool maximized_fs = false;
bool minimized = false;
bool fullscreen = false;
bool multiwindow_fs = false;
@@ -374,7 +392,6 @@ class DisplayServerWindows : public DisplayServer {
bool was_maximized = false;
bool always_on_top = false;
bool no_focus = false;
- bool window_has_focus = false;
bool exclusive = false;
bool context_created = false;
bool mpass = false;
@@ -385,7 +402,6 @@ class DisplayServerWindows : public DisplayServer {
// Timers.
uint32_t move_timer_id = 0U;
- uint32_t focus_timer_id = 0U;
HANDLE wtctx;
LOGCONTEXTW wtlc;
@@ -455,7 +471,7 @@ class DisplayServerWindows : public DisplayServer {
HashMap<IndicatorID, IndicatorData> indicators;
void _send_window_event(const WindowData &wd, WindowEvent p_event);
- void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
+ void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
MouseMode mouse_mode;
int restore_mouse_trails = 0;
@@ -642,6 +658,9 @@ public:
virtual void enable_for_stealing_focus(OS::ProcessID pid) override;
+ virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override;
+ virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
+
virtual int keyboard_get_layout_count() const override;
virtual int keyboard_get_current_layout() const override;
virtual void keyboard_set_current_layout(int p_index) override;
@@ -660,7 +679,6 @@ public:
virtual void force_process_and_drop_events() override;
virtual void release_rendering_thread() override;
- virtual void make_rendering_thread() override;
virtual void swap_buffers() override;
virtual void set_native_icon(const String &p_filename) override;
diff --git a/platform/windows/doc_classes/EditorExportPlatformWindows.xml b/platform/windows/doc_classes/EditorExportPlatformWindows.xml
index 1239a2b32f..06b272c10e 100644
--- a/platform/windows/doc_classes/EditorExportPlatformWindows.xml
+++ b/platform/windows/doc_classes/EditorExportPlatformWindows.xml
@@ -4,6 +4,7 @@
Exporter for Windows.
</brief_description>
<description>
+ The Windows exporter customizes how a Windows build is handled. In the editor's "Export" window, it is created when adding a new "Windows" preset.
</description>
<tutorials>
<link title="Exporting for Windows">$DOCS_URL/tutorials/export/exporting_for_windows.html</link>
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 0c6721e118..a3c86611a4 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -285,15 +285,15 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>
if (p_preset->get("codesign/enable")) {
_code_sign(p_preset, pck_path);
- String wrapper_path = p_path.get_basename() + ".console.exe";
+ String wrapper_path = path.get_basename() + ".console.exe";
if (FileAccess::exists(wrapper_path)) {
_code_sign(p_preset, wrapper_path);
}
}
if (embedded) {
- Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());
- err = tmp_dir->rename(pck_path, p_path);
+ Ref<DirAccess> tmp_dir = DirAccess::create_for_path(path.get_base_dir());
+ err = tmp_dir->rename(pck_path, path);
if (err != OK) {
add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to rename temporary file \"%s\"."), pck_path));
}
@@ -367,11 +367,17 @@ String EditorExportPlatformWindows::get_export_option_warning(const EditorExport
}
bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {
+ if (p_preset == nullptr) {
+ return true;
+ }
+
// 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;
}
+ bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
+
// Hide codesign.
bool codesign = p_preset->get("codesign/enable");
if (!codesign && p_option != "codesign/enable" && p_option.begins_with("codesign/")) {
@@ -390,6 +396,9 @@ bool EditorExportPlatformWindows::get_export_option_visibility(const EditorExpor
return false;
}
+ if (p_option == "dotnet/embed_build_outputs") {
+ return advanced_options_enabled;
+ }
return true;
}
diff --git a/platform/windows/gl_manager_windows_native.cpp b/platform/windows/gl_manager_windows_native.cpp
index 8af32395b7..80cf818199 100644
--- a/platform/windows/gl_manager_windows_native.cpp
+++ b/platform/windows/gl_manager_windows_native.cpp
@@ -107,22 +107,22 @@ static bool nvapi_err_check(const char *msg, int status) {
// to avoid stuttering, see https://stackoverflow.com/questions/36959508/nvidia-graphics-driver-causing-noticeable-frame-stuttering/37632948
// also see https://github.com/Ryujinx/Ryujinx/blob/master/src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs
void GLManagerNative_Windows::_nvapi_disable_threaded_optimization() {
- HMODULE nvapi = 0;
+ HMODULE nvapi = nullptr;
#ifdef _WIN64
nvapi = LoadLibraryA("nvapi64.dll");
#else
nvapi = LoadLibraryA("nvapi.dll");
#endif
- if (nvapi == NULL) {
+ if (nvapi == nullptr) {
return;
}
- void *(__cdecl * NvAPI_QueryInterface)(unsigned int interface_id) = 0;
+ void *(__cdecl * NvAPI_QueryInterface)(unsigned int interface_id) = nullptr;
NvAPI_QueryInterface = (void *(__cdecl *)(unsigned int))(void *)GetProcAddress(nvapi, "nvapi_QueryInterface");
- if (NvAPI_QueryInterface == NULL) {
+ if (NvAPI_QueryInterface == nullptr) {
print_verbose("Error getting NVAPI NvAPI_QueryInterface");
return;
}
@@ -176,7 +176,7 @@ void GLManagerNative_Windows::_nvapi_disable_threaded_optimization() {
Char16String app_executable_name_u16 = app_executable_name.utf16();
Char16String app_friendly_name_u16 = app_friendly_name.utf16();
- NvDRSProfileHandle profile_handle = 0;
+ NvDRSProfileHandle profile_handle = nullptr;
int profile_status = NvAPI_DRS_FindProfileByName(session_handle, (NvU16 *)(app_profile_name_u16.ptrw()), &profile_handle);
@@ -195,7 +195,7 @@ void GLManagerNative_Windows::_nvapi_disable_threaded_optimization() {
}
}
- NvDRSProfileHandle app_profile_handle = 0;
+ NvDRSProfileHandle app_profile_handle = nullptr;
NVDRS_APPLICATION_V4 app;
app.version = NVDRS_APPLICATION_VER_V4;
@@ -362,14 +362,14 @@ Error GLManagerNative_Windows::_create_context(GLWindow &win, GLDisplay &gl_disp
if (wglCreateContextAttribsARB == nullptr) //OpenGL 3.0 is not supported
{
gd_wglDeleteContext(gl_display.hRC);
- gl_display.hRC = 0;
+ gl_display.hRC = nullptr;
return ERR_CANT_CREATE;
}
- HGLRC new_hRC = wglCreateContextAttribsARB(win.hDC, 0, attribs);
+ HGLRC new_hRC = wglCreateContextAttribsARB(win.hDC, nullptr, attribs);
if (!new_hRC) {
gd_wglDeleteContext(gl_display.hRC);
- gl_display.hRC = 0;
+ gl_display.hRC = nullptr;
return ERR_CANT_CREATE;
}
@@ -384,7 +384,7 @@ Error GLManagerNative_Windows::_create_context(GLWindow &win, GLDisplay &gl_disp
{
ERR_PRINT("Could not attach OpenGL context to newly created window with replaced OpenGL context: " + format_error_message(GetLastError()));
gd_wglDeleteContext(gl_display.hRC);
- gl_display.hRC = 0;
+ gl_display.hRC = nullptr;
return ERR_CANT_CREATE;
}
@@ -427,10 +427,6 @@ Error GLManagerNative_Windows::window_create(DisplayServer::WindowID p_window_id
return OK;
}
-void GLManagerNative_Windows::_internal_set_current_window(GLWindow *p_win) {
- _current_window = p_win;
-}
-
void GLManagerNative_Windows::window_destroy(DisplayServer::WindowID p_window_id) {
GLWindow &win = get_window(p_window_id);
if (_current_window == &win) {
@@ -447,6 +443,8 @@ void GLManagerNative_Windows::release_current() {
if (!gd_wglMakeCurrent(_current_window->hDC, nullptr)) {
ERR_PRINT("Could not detach OpenGL context from window marked current: " + format_error_message(GetLastError()));
}
+
+ _current_window = nullptr;
}
void GLManagerNative_Windows::window_make_current(DisplayServer::WindowID p_window_id) {
@@ -467,17 +465,7 @@ void GLManagerNative_Windows::window_make_current(DisplayServer::WindowID p_wind
ERR_PRINT("Could not switch OpenGL context to other window: " + format_error_message(GetLastError()));
}
- _internal_set_current_window(&win);
-}
-
-void GLManagerNative_Windows::make_current() {
- if (!_current_window) {
- return;
- }
- const GLDisplay &disp = get_current_display();
- if (!gd_wglMakeCurrent(_current_window->hDC, disp.hRC)) {
- ERR_PRINT("Could not switch OpenGL context to window marked current: " + format_error_message(GetLastError()));
- }
+ _current_window = &win;
}
void GLManagerNative_Windows::swap_buffers() {
@@ -491,7 +479,6 @@ Error GLManagerNative_Windows::initialize() {
void GLManagerNative_Windows::set_use_vsync(DisplayServer::WindowID p_window_id, bool p_use) {
GLWindow &win = get_window(p_window_id);
- GLWindow *current = _current_window;
if (&win != _current_window) {
window_make_current(p_window_id);
@@ -506,11 +493,6 @@ void GLManagerNative_Windows::set_use_vsync(DisplayServer::WindowID p_window_id,
} else {
WARN_PRINT("Could not set V-Sync mode. V-Sync is not supported.");
}
-
- if (current != _current_window) {
- _current_window = current;
- make_current();
- }
}
bool GLManagerNative_Windows::is_using_vsync(DisplayServer::WindowID p_window_id) const {
diff --git a/platform/windows/gl_manager_windows_native.h b/platform/windows/gl_manager_windows_native.h
index 829c53b3d2..b4e2a3acdf 100644
--- a/platform/windows/gl_manager_windows_native.h
+++ b/platform/windows/gl_manager_windows_native.h
@@ -68,9 +68,6 @@ private:
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr;
- // funcs
- void _internal_set_current_window(GLWindow *p_win);
-
GLWindow &get_window(unsigned int id) { return _windows[id]; }
const GLWindow &get_window(unsigned int id) const { return _windows[id]; }
@@ -91,7 +88,6 @@ public:
void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {}
void release_current();
- void make_current();
void swap_buffers();
void window_make_current(DisplayServer::WindowID p_window_id);
diff --git a/platform/windows/godot_res.rc b/platform/windows/godot_res.rc
index 8187c0c936..86191ad9d9 100644
--- a/platform/windows/godot_res.rc
+++ b/platform/windows/godot_res.rc
@@ -1,6 +1,11 @@
#include "core/version.h"
+#ifndef RT_MANIFEST
+#define RT_MANIFEST 24
+#endif
+
GODOT_ICON ICON platform/windows/godot.ico
+1 RT_MANIFEST "godot.manifest"
1 VERSIONINFO
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp
index f86ecd87fb..5f41b4e568 100644
--- a/platform/windows/godot_windows.cpp
+++ b/platform/windows/godot_windows.cpp
@@ -171,13 +171,15 @@ int widechar_main(int argc, wchar_t **argv) {
delete[] argv_utf8;
if (err == ERR_HELP) { // Returned by --help and --version, so success.
- return 0;
+ return EXIT_SUCCESS;
}
- return 255;
+ return EXIT_FAILURE;
}
- if (Main::start()) {
+ if (Main::start() == EXIT_SUCCESS) {
os.run();
+ } else {
+ os.set_exit_code(EXIT_FAILURE);
}
Main::cleanup();
diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp
new file mode 100644
index 0000000000..40a08f87df
--- /dev/null
+++ b/platform/windows/native_menu_windows.cpp
@@ -0,0 +1,1165 @@
+/**************************************************************************/
+/* native_menu_windows.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "native_menu_windows.h"
+
+#include "display_server_windows.h"
+
+#include "scene/resources/image_texture.h"
+
+HBITMAP NativeMenuWindows::_make_bitmap(const Ref<Image> &p_img) const {
+ Vector2i texture_size = p_img->get_size();
+ UINT image_size = texture_size.width * texture_size.height;
+
+ COLORREF *buffer = nullptr;
+
+ BITMAPV5HEADER bi;
+ ZeroMemory(&bi, sizeof(bi));
+ bi.bV5Size = sizeof(bi);
+ bi.bV5Width = texture_size.width;
+ bi.bV5Height = -texture_size.height;
+ bi.bV5Planes = 1;
+ bi.bV5BitCount = 32;
+ bi.bV5Compression = BI_BITFIELDS;
+ bi.bV5RedMask = 0x00ff0000;
+ bi.bV5GreenMask = 0x0000ff00;
+ bi.bV5BlueMask = 0x000000ff;
+ bi.bV5AlphaMask = 0xff000000;
+
+ HDC dc = GetDC(nullptr);
+ HBITMAP bitmap = CreateDIBSection(dc, reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS, reinterpret_cast<void **>(&buffer), nullptr, 0);
+ for (UINT index = 0; index < image_size; index++) {
+ int row_index = floor(index / texture_size.width);
+ int column_index = (index % int(texture_size.width));
+ const Color &c = p_img->get_pixel(column_index, row_index);
+ *(buffer + index) = c.to_argb32();
+ }
+ ReleaseDC(nullptr, dc);
+
+ return bitmap;
+}
+
+void NativeMenuWindows::_menu_activate(HMENU p_menu, int p_index) const {
+ if (menu_lookup.has(p_menu)) {
+ MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
+ if (md) {
+ int count = GetMenuItemCount(md->menu);
+ if (p_index >= 0 && p_index < count) {
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE | MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_index, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->max_states > 0) {
+ item_data->state++;
+ if (item_data->state >= item_data->max_states) {
+ item_data->state = 0;
+ }
+ }
+
+ if (item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX) {
+ if ((item.fState & MFS_CHECKED) == MFS_CHECKED) {
+ item.fState &= ~MFS_CHECKED;
+ } else {
+ item.fState |= MFS_CHECKED;
+ }
+ SetMenuItemInfoW(md->menu, p_index, true, &item);
+ }
+
+ if (item_data->callback.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[1] = { &item_data->meta };
+
+ item_data->callback.callp(args, 1, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute menu callback: %s.", Variant::get_callable_error_text(item_data->callback, args, 1, ce)));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+bool NativeMenuWindows::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ // case FEATURE_GLOBAL_MENU:
+ // case FEATURE_OPEN_CLOSE_CALLBACK:
+ // case FEATURE_HOVER_CALLBACK:
+ // case FEATURE_KEY_CALLBACK:
+ case FEATURE_POPUP_MENU:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool NativeMenuWindows::has_system_menu(SystemMenus p_menu_id) const {
+ return false;
+}
+
+RID NativeMenuWindows::get_system_menu(SystemMenus p_menu_id) const {
+ return RID();
+}
+
+RID NativeMenuWindows::create_menu() {
+ MenuData *md = memnew(MenuData);
+ md->menu = CreatePopupMenu();
+
+ MENUINFO menu_info;
+ ZeroMemory(&menu_info, sizeof(menu_info));
+ menu_info.cbSize = sizeof(menu_info);
+ menu_info.fMask = MIM_STYLE;
+ menu_info.dwStyle = MNS_NOTIFYBYPOS;
+ SetMenuInfo(md->menu, &menu_info);
+
+ RID rid = menus.make_rid(md);
+ menu_lookup[md->menu] = rid;
+ return rid;
+}
+
+bool NativeMenuWindows::has_menu(const RID &p_rid) const {
+ return menus.owns(p_rid);
+}
+
+void NativeMenuWindows::free_menu(const RID &p_rid) {
+ MenuData *md = menus.get_or_null(p_rid);
+ if (md) {
+ clear(p_rid);
+ DestroyMenu(md->menu);
+ menus.free(p_rid);
+ menu_lookup.erase(md->menu);
+ memdelete(md);
+ }
+}
+
+Size2 NativeMenuWindows::get_size(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Size2());
+
+ Size2 size;
+ int count = GetMenuItemCount(md->menu);
+ for (int i = 0; i < count; i++) {
+ RECT rect;
+ if (GetMenuItemRect(NULL, md->menu, i, &rect)) {
+ size.x = MAX(size.x, rect.right - rect.left);
+ size.y += rect.bottom - rect.top;
+ }
+ }
+ return size;
+}
+
+void NativeMenuWindows::popup(const RID &p_rid, const Vector2i &p_position) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ HWND hwnd = (HWND)DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, DisplayServer::MAIN_WINDOW_ID);
+ UINT flags = TPM_HORIZONTAL | TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_VERPOSANIMATION;
+ if (md->is_rtl) {
+ flags |= TPM_LAYOUTRTL;
+ }
+ SetForegroundWindow(hwnd);
+ TrackPopupMenuEx(md->menu, flags, p_position.x, p_position.y, hwnd, nullptr);
+ PostMessage(hwnd, WM_NULL, 0, 0);
+}
+
+void NativeMenuWindows::set_interface_direction(const RID &p_rid, bool p_is_rtl) {
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ if (md->is_rtl == p_is_rtl) {
+ return;
+ }
+ md->is_rtl = p_is_rtl;
+}
+
+void NativeMenuWindows::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) {
+ // Not supported.
+}
+
+Callable NativeMenuWindows::get_popup_open_callback(const RID &p_rid) const {
+ // Not supported.
+ return Callable();
+}
+
+void NativeMenuWindows::set_popup_close_callback(const RID &p_rid, const Callable &p_callback) {
+ // Not supported.
+}
+
+Callable NativeMenuWindows::get_popup_close_callback(const RID &p_rid) const {
+ // Not supported.
+ return Callable();
+}
+
+void NativeMenuWindows::set_minimum_width(const RID &p_rid, float p_width) {
+ // Not supported.
+}
+
+float NativeMenuWindows::get_minimum_width(const RID &p_rid) const {
+ // Not supported.
+ return 0.f;
+}
+
+int NativeMenuWindows::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) {
+ MenuData *md = menus.get_or_null(p_rid);
+ MenuData *md_sub = menus.get_or_null(p_submenu_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ ERR_FAIL_NULL_V(md_sub, -1);
+ ERR_FAIL_COND_V_MSG(md->menu == md_sub->menu, -1, "Can't set submenu to self!");
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_SUBMENU;
+ item.fType = MFT_STRING;
+ item.hSubMenu = md_sub->menu;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = 0;
+ item_data->state = 0;
+ item_data->img = p_icon->get_image();
+ item_data->img = item_data->img->duplicate();
+ if (item_data->img->is_compressed()) {
+ item_data->img->decompress();
+ }
+ item_data->bmp = _make_bitmap(item_data->img);
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+ item.hbmpItem = item_data->bmp;
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_icon_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ item_data->max_states = 0;
+ item_data->state = 0;
+ item_data->img = p_icon->get_image();
+ item_data->img = item_data->img->duplicate();
+ if (item_data->img->is_compressed()) {
+ item_data->img->decompress();
+ }
+ item_data->bmp = _make_bitmap(item_data->img);
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+ item.hbmpItem = item_data->bmp;
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ item.fType = MFT_STRING | MFT_RADIOCHECK;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_icon_radio_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
+ item_data->max_states = 0;
+ item_data->state = 0;
+ item_data->img = p_icon->get_image();
+ item_data->img = item_data->img->duplicate();
+ if (item_data->img->is_compressed()) {
+ item_data->img->decompress();
+ }
+ item_data->bmp = _make_bitmap(item_data->img);
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP;
+ item.fType = MFT_STRING | MFT_RADIOCHECK;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+ item.hbmpItem = item_data->bmp;
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->callback = p_callback;
+ item_data->meta = p_tag;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = p_max_states;
+ item_data->state = p_default_state;
+
+ Char16String label = p_label.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ item.fType = MFT_STRING;
+ item.dwItemData = (ULONG_PTR)item_data;
+ item.dwTypeData = (LPWSTR)label.get_data();
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::add_separator(const RID &p_rid, int p_index) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ if (p_index == -1) {
+ p_index = GetMenuItemCount(md->menu);
+ } else {
+ p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu));
+ }
+
+ MenuItemData *item_data = memnew(MenuItemData);
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ item_data->max_states = 0;
+ item_data->state = 0;
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_DATA;
+ item.fType = MFT_SEPARATOR;
+ item.dwItemData = (ULONG_PTR)item_data;
+
+ if (!InsertMenuItemW(md->menu, p_index, true, &item)) {
+ memdelete(item_data);
+ return -1;
+ }
+ return p_index;
+}
+
+int NativeMenuWindows::find_item_index_with_text(const RID &p_rid, const String &p_text) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ MENUITEMINFOW item;
+ int count = GetMenuItemCount(md->menu);
+ for (int i = 0; i < count; i++) {
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STRING;
+ item.dwTypeData = nullptr;
+ if (GetMenuItemInfoW(md->menu, i, true, &item)) {
+ item.cch++;
+ Char16String str;
+ str.resize(item.cch);
+ item.dwTypeData = (LPWSTR)str.ptrw();
+ if (GetMenuItemInfoW(md->menu, i, true, &item)) {
+ if (String::utf16((const char16_t *)str.get_data()) == p_text) {
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+int NativeMenuWindows::find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+
+ MENUITEMINFOW item;
+ int count = GetMenuItemCount(md->menu);
+ for (int i = 0; i < count; i++) {
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, i, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->meta == p_tag) {
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, false);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return (item.fState & MFS_CHECKED) == MFS_CHECKED;
+ }
+ return false;
+}
+
+bool NativeMenuWindows::is_item_checkable(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, false);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX;
+ }
+ }
+ return false;
+}
+
+bool NativeMenuWindows::is_item_radio_checkable(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, false);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON;
+ }
+ }
+ return false;
+}
+
+Callable NativeMenuWindows::get_item_callback(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Callable());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Callable());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, Callable());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->callback;
+ }
+ }
+ return Callable();
+}
+
+Callable NativeMenuWindows::get_item_key_callback(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return Callable();
+}
+
+Variant NativeMenuWindows::get_item_tag(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Variant());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Variant());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, Variant());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->meta;
+ }
+ }
+ return Variant();
+}
+
+String NativeMenuWindows::get_item_text(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, String());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, String());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STRING;
+ item.dwTypeData = nullptr;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ item.cch++;
+ Char16String str;
+ str.resize(item.cch);
+ item.dwTypeData = (LPWSTR)str.ptrw();
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return String::utf16((const char16_t *)str.get_data());
+ }
+ }
+ return String();
+}
+
+RID NativeMenuWindows::get_item_submenu(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, RID());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, RID());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, RID());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_SUBMENU;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ if (menu_lookup.has(item.hSubMenu)) {
+ return menu_lookup[item.hSubMenu];
+ }
+ }
+ return RID();
+}
+
+Key NativeMenuWindows::get_item_accelerator(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return Key::NONE;
+}
+
+bool NativeMenuWindows::is_item_disabled(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, false);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, false);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return (item.fState & MFS_DISABLED) == MFS_DISABLED;
+ }
+ return false;
+}
+
+bool NativeMenuWindows::is_item_hidden(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return false;
+}
+
+String NativeMenuWindows::get_item_tooltip(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return String();
+}
+
+int NativeMenuWindows::get_item_state(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, -1);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, -1);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->state;
+ }
+ }
+ return -1;
+}
+
+int NativeMenuWindows::get_item_max_states(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, -1);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, -1);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->max_states;
+ }
+ }
+ return -1;
+}
+
+Ref<Texture2D> NativeMenuWindows::get_item_icon(const RID &p_rid, int p_idx) const {
+ ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, Ref<Texture2D>());
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, Ref<Texture2D>());
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return ImageTexture::create_from_image(item_data->img);
+ }
+ }
+ return Ref<Texture2D>();
+}
+
+int NativeMenuWindows::get_item_indentation_level(const RID &p_rid, int p_idx) const {
+ // Not supported.
+ return 0;
+}
+
+void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_checked) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ if (p_checked) {
+ item.fState |= MFS_CHECKED;
+ } else {
+ item.fState &= ~MFS_CHECKED;
+ }
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+}
+
+void NativeMenuWindows::set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item.fType &= ~MFT_RADIOCHECK;
+ item_data->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (p_checkable) {
+ item.fType |= MFT_RADIOCHECK;
+ item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
+ } else {
+ item.fType &= ~MFT_RADIOCHECK;
+ item_data->checkable_type = CHECKABLE_TYPE_NONE;
+ }
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->callback = p_callback;
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->meta = p_tag;
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_text(const RID &p_rid, int p_idx, const String &p_text) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ Char16String label = p_text.utf16();
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ item.dwTypeData = (LPWSTR)label.get_data();
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+}
+
+void NativeMenuWindows::set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MenuData *md_sub = menus.get_or_null(p_submenu_rid);
+ ERR_FAIL_COND_MSG(md->menu == md_sub->menu, "Can't set submenu to self!");
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_SUBMENU;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ if (p_submenu_rid.is_valid()) {
+ item.hSubMenu = md_sub->menu;
+ } else {
+ item.hSubMenu = 0;
+ }
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+}
+
+void NativeMenuWindows::set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STATE;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ if (p_disabled) {
+ item.fState |= MFS_DISABLED;
+ } else {
+ item.fState &= ~MFS_DISABLED;
+ }
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+}
+
+void NativeMenuWindows::set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) {
+ // Not supported.
+}
+
+void NativeMenuWindows::set_item_state(const RID &p_rid, int p_idx, int p_state) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->state = p_state;
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->max_states = p_max_states;
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_icon(const RID &p_rid, int p_idx, const Ref<Texture2D> &p_icon) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA | MIIM_BITMAP;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->bmp) {
+ DeleteObject(item_data->bmp);
+ }
+ if (p_icon.is_valid()) {
+ item_data->img = p_icon->get_image();
+ item_data->img = item_data->img->duplicate();
+ if (item_data->img->is_compressed()) {
+ item_data->img->decompress();
+ }
+ item_data->bmp = _make_bitmap(item_data->img);
+ } else {
+ item_data->img = Ref<Image>();
+ item_data->bmp = 0;
+ }
+ item.hbmpItem = item_data->bmp;
+ SetMenuItemInfoW(md->menu, p_idx, true, &item);
+ }
+ }
+}
+
+void NativeMenuWindows::set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) {
+ // Not supported.
+}
+
+int NativeMenuWindows::get_item_count(const RID &p_rid) const {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, 0);
+
+ return GetMenuItemCount(md->menu);
+}
+
+bool NativeMenuWindows::is_system_menu(const RID &p_rid) const {
+ return false;
+}
+
+void NativeMenuWindows::remove_item(const RID &p_rid, int p_idx) {
+ ERR_FAIL_COND(p_idx < 0);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND(p_idx >= count);
+
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->bmp) {
+ DeleteObject(item_data->bmp);
+ }
+ memdelete(item_data);
+ }
+ }
+ RemoveMenu(md->menu, p_idx, MF_BYPOSITION);
+}
+
+void NativeMenuWindows::clear(const RID &p_rid) {
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL(md);
+
+ MENUITEMINFOW item;
+ int count = GetMenuItemCount(md->menu);
+ for (int i = 0; i < count; i++) {
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_DATA;
+ if (GetMenuItemInfoW(md->menu, 0, true, &item)) {
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->bmp) {
+ DeleteObject(item_data->bmp);
+ }
+ memdelete(item_data);
+ }
+ }
+ RemoveMenu(md->menu, 0, MF_BYPOSITION);
+ }
+}
+
+NativeMenuWindows::NativeMenuWindows() {}
+
+NativeMenuWindows::~NativeMenuWindows() {}
diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h
new file mode 100644
index 0000000000..74fd231903
--- /dev/null
+++ b/platform/windows/native_menu_windows.h
@@ -0,0 +1,151 @@
+/**************************************************************************/
+/* native_menu_windows.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef NATIVE_MENU_WINDOWS_H
+#define NATIVE_MENU_WINDOWS_H
+
+#include "core/templates/hash_map.h"
+#include "core/templates/rid_owner.h"
+#include "servers/display/native_menu.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+class NativeMenuWindows : public NativeMenu {
+ GDCLASS(NativeMenuWindows, NativeMenu)
+
+ enum GlobalMenuCheckType {
+ CHECKABLE_TYPE_NONE,
+ CHECKABLE_TYPE_CHECK_BOX,
+ CHECKABLE_TYPE_RADIO_BUTTON,
+ };
+
+ struct MenuItemData {
+ Callable callback;
+ Variant meta;
+ GlobalMenuCheckType checkable_type;
+ int max_states = 0;
+ int state = 0;
+ Ref<Image> img;
+ HBITMAP bmp = 0;
+ };
+
+ struct MenuData {
+ HMENU menu = 0;
+ bool is_rtl = false;
+ };
+
+ mutable RID_PtrOwner<MenuData> menus;
+ HashMap<HMENU, RID> menu_lookup;
+
+ HBITMAP _make_bitmap(const Ref<Image> &p_img) const;
+
+public:
+ void _menu_activate(HMENU p_menu, int p_index) const;
+
+ virtual bool has_feature(Feature p_feature) const override;
+
+ virtual bool has_system_menu(SystemMenus p_menu_id) const override;
+ virtual RID get_system_menu(SystemMenus p_menu_id) const override;
+
+ virtual RID create_menu() override;
+ virtual bool has_menu(const RID &p_rid) const override;
+ virtual void free_menu(const RID &p_rid) override;
+
+ virtual Size2 get_size(const RID &p_rid) const override;
+ virtual void popup(const RID &p_rid, const Vector2i &p_position) override;
+
+ virtual void set_interface_direction(const RID &p_rid, bool p_is_rtl) override;
+ virtual void set_popup_open_callback(const RID &p_rid, const Callable &p_callback) override;
+ virtual Callable get_popup_open_callback(const RID &p_rid) const override;
+ virtual void set_popup_close_callback(const RID &p_rid, const Callable &p_callback) override;
+ virtual Callable get_popup_close_callback(const RID &p_rid) const override;
+ virtual void set_minimum_width(const RID &p_rid, float p_width) override;
+ virtual float get_minimum_width(const RID &p_rid) const override;
+
+ virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1) override;
+ virtual int add_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_icon_radio_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int add_separator(const RID &p_rid, int p_index = -1) override;
+
+ virtual int find_item_index_with_text(const RID &p_rid, const String &p_text) const override;
+ virtual int find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const override;
+
+ virtual bool is_item_checked(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_checkable(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_radio_checkable(const RID &p_rid, int p_idx) const override;
+ virtual Callable get_item_callback(const RID &p_rid, int p_idx) const override;
+ virtual Callable get_item_key_callback(const RID &p_rid, int p_idx) const override;
+ virtual Variant get_item_tag(const RID &p_rid, int p_idx) const override;
+ virtual String get_item_text(const RID &p_rid, int p_idx) const override;
+ virtual RID get_item_submenu(const RID &p_rid, int p_idx) const override;
+ virtual Key get_item_accelerator(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_disabled(const RID &p_rid, int p_idx) const override;
+ virtual bool is_item_hidden(const RID &p_rid, int p_idx) const override;
+ virtual String get_item_tooltip(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_state(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_max_states(const RID &p_rid, int p_idx) const override;
+ virtual Ref<Texture2D> get_item_icon(const RID &p_rid, int p_idx) const override;
+ virtual int get_item_indentation_level(const RID &p_rid, int p_idx) const override;
+
+ virtual void set_item_checked(const RID &p_rid, int p_idx, bool p_checked) override;
+ virtual void set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) override;
+ virtual void set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) override;
+ virtual void set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) override;
+ virtual void set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) override;
+ virtual void set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) override;
+ virtual void set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) override;
+ virtual void set_item_text(const RID &p_rid, int p_idx, const String &p_text) override;
+ virtual void set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) override;
+ virtual void set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) override;
+ virtual void set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) override;
+ virtual void set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) override;
+ virtual void set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) override;
+ virtual void set_item_state(const RID &p_rid, int p_idx, int p_state) override;
+ virtual void set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) override;
+ virtual void set_item_icon(const RID &p_rid, int p_idx, const Ref<Texture2D> &p_icon) override;
+ virtual void set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) override;
+
+ virtual int get_item_count(const RID &p_rid) const override;
+ virtual bool is_system_menu(const RID &p_rid) const override;
+
+ virtual void remove_item(const RID &p_rid, int p_idx) override;
+ virtual void clear(const RID &p_rid) override;
+
+ NativeMenuWindows();
+ ~NativeMenuWindows();
+};
+
+#endif // NATIVE_MENU_WINDOWS_H
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 93d1ffeac1..abed93d414 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -34,6 +34,7 @@
#include "joypad_windows.h"
#include "lang_table.h"
#include "windows_terminal_logger.h"
+#include "windows_utils.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
@@ -42,6 +43,7 @@
#include "drivers/unix/net_socket_posix.h"
#include "drivers/windows/dir_access_windows.h"
#include "drivers/windows/file_access_windows.h"
+#include "drivers/windows/file_access_windows_pipe.h"
#include "main/main.h"
#include "servers/audio_server.h"
#include "servers/rendering/rendering_server_default.h"
@@ -178,6 +180,7 @@ void OS_Windows::initialize() {
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA);
FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM);
+ FileAccess::make_default<FileAccessWindowsPipe>(FileAccess::ACCESS_PIPE);
DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_RESOURCES);
DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_USERDATA);
DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_FILESYSTEM);
@@ -267,6 +270,10 @@ void OS_Windows::finalize() {
}
void OS_Windows::finalize_core() {
+ while (!temp_libraries.is_empty()) {
+ _remove_temp_library(temp_libraries.last()->key);
+ }
+
FileAccessWindows::finalize();
timeEndPeriod(1);
@@ -329,7 +336,7 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S
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);
+ const char *name_cs = (const char *)ImageRvaToVa(loaded_image.FileHeader, loaded_image.MappedAddress, import_desc->Name, nullptr);
String name = String(name_cs);
if (name.begins_with("api-ms-win-")) {
r_checked.insert(name);
@@ -352,7 +359,7 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S
}
#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) {
+Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
String path = p_path.replace("/", "\\");
if (!FileAccess::exists(path)) {
@@ -362,6 +369,35 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
+ // Here we want a copy to be loaded.
+ // This is so the original file isn't locked and can be updated by a compiler.
+ if (p_data != nullptr && p_data->generate_temp_files) {
+ // Copy the file to the same directory as the original with a prefix in the name.
+ // This is so relative path to dependencies are satisfied.
+ String copy_path = path.get_base_dir().path_join("~" + path.get_file());
+
+ // If there's a left-over copy (possibly from a crash) then delete it first.
+ if (FileAccess::exists(copy_path)) {
+ DirAccess::remove_absolute(copy_path);
+ }
+
+ Error copy_err = DirAccess::copy_absolute(path, copy_path);
+ if (copy_err) {
+ ERR_PRINT("Error copying library: " + path);
+ return ERR_CANT_CREATE;
+ }
+
+ FileAccess::set_hidden_attribute(copy_path, true);
+
+ // Save the copied path so it can be deleted later.
+ path = copy_path;
+
+ Error pdb_err = WindowsUtils::copy_and_rename_pdb(path);
+ if (pdb_err != OK && pdb_err != ERR_SKIP) {
+ WARN_PRINT(vformat("Failed to rename the PDB file. The original PDB file for '%s' will be loaded.", path));
+ }
+ }
+
typedef DLL_DIRECTORY_COOKIE(WINAPI * PAddDllDirectory)(PCWSTR);
typedef BOOL(WINAPI * PRemoveDllDirectory)(DLL_DIRECTORY_COOKIE);
@@ -371,13 +407,17 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
bool has_dll_directory_api = ((add_dll_directory != nullptr) && (remove_dll_directory != nullptr));
DLL_DIRECTORY_COOKIE cookie = nullptr;
- if (p_also_set_library_path && has_dll_directory_api) {
+ if (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) {
cookie = add_dll_directory((LPCWSTR)(path.get_base_dir().utf16().get_data()));
}
- 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);
-#ifdef DEBUG_ENABLED
+ p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
if (!p_library_handle) {
+ if (p_data != nullptr && p_data->generate_temp_files) {
+ DirAccess::remove_absolute(path);
+ }
+
+#ifdef DEBUG_ENABLED
DWORD err_code = GetLastError();
HashSet<String> checekd_libs;
@@ -395,8 +435,10 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
} else {
ERR_FAIL_V_MSG(ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, format_error_message(err_code)));
}
+#endif
}
-#else
+
+#ifndef DEBUG_ENABLED
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, format_error_message(GetLastError())));
#endif
@@ -404,8 +446,12 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
remove_dll_directory(cookie);
}
- if (r_resolved_path != nullptr) {
- *r_resolved_path = path;
+ if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+ *p_data->r_resolved_path = path;
+ }
+
+ if (p_data != nullptr && p_data->generate_temp_files) {
+ temp_libraries[p_library_handle] = path;
}
return OK;
@@ -415,9 +461,22 @@ Error OS_Windows::close_dynamic_library(void *p_library_handle) {
if (!FreeLibrary((HMODULE)p_library_handle)) {
return FAILED;
}
+
+ // Delete temporary copy of library if it exists.
+ _remove_temp_library(p_library_handle);
+
return OK;
}
+void OS_Windows::_remove_temp_library(void *p_library_handle) {
+ if (temp_libraries.has(p_library_handle)) {
+ String path = temp_libraries[p_library_handle];
+ DirAccess::remove_absolute(path);
+ WindowsUtils::remove_temp_pdbs(path);
+ temp_libraries.erase(p_library_handle);
+ }
+}
+
Error OS_Windows::get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional) {
p_symbol_handle = (void *)GetProcAddress((HMODULE)p_library_handle, p_name.utf8().get_data());
if (!p_symbol_handle) {
@@ -463,9 +522,9 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
REFCLSID clsid = CLSID_WbemLocator; // Unmarshaler CLSID
REFIID uuid = IID_IWbemLocator; // Interface UUID
- IWbemLocator *wbemLocator = NULL; // to get the services
- IWbemServices *wbemServices = NULL; // to get the class
- IEnumWbemClassObject *iter = NULL;
+ IWbemLocator *wbemLocator = nullptr; // to get the services
+ IWbemServices *wbemServices = nullptr; // to get the class
+ IEnumWbemClassObject *iter = nullptr;
IWbemClassObject *pnpSDriverObject[1]; // contains driver name, version, etc.
String driver_name;
String driver_version;
@@ -475,12 +534,12 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
return Vector<String>();
}
- HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator);
+ HRESULT hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, uuid, (LPVOID *)&wbemLocator);
if (hr != S_OK) {
return Vector<String>();
}
BSTR resource_name = SysAllocString(L"root\\CIMV2");
- hr = wbemLocator->ConnectServer(resource_name, NULL, NULL, NULL, 0, NULL, NULL, &wbemServices);
+ hr = wbemLocator->ConnectServer(resource_name, nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbemServices);
SysFreeString(resource_name);
SAFE_RELEASE(wbemLocator) // from now on, use `wbemServices`
@@ -492,7 +551,7 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
const String gpu_device_class_query = vformat("SELECT * FROM Win32_PnPSignedDriver WHERE DeviceName = \"%s\"", device_name);
BSTR query = SysAllocString((const WCHAR *)gpu_device_class_query.utf16().get_data());
BSTR query_lang = SysAllocString(L"WQL");
- hr = wbemServices->ExecQuery(query_lang, query, WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &iter);
+ hr = wbemServices->ExecQuery(query_lang, query, WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, nullptr, &iter);
SysFreeString(query_lang);
SysFreeString(query);
if (hr == S_OK) {
@@ -504,13 +563,13 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
VariantInit(&dn);
BSTR object_name = SysAllocString(L"DriverName");
- hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, NULL, NULL);
+ hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, nullptr, nullptr);
SysFreeString(object_name);
if (hr == S_OK) {
String d_name = String(V_BSTR(&dn));
if (d_name.is_empty()) {
object_name = SysAllocString(L"DriverProviderName");
- hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, NULL, NULL);
+ hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, nullptr, nullptr);
SysFreeString(object_name);
if (hr == S_OK) {
driver_name = String(V_BSTR(&dn));
@@ -520,7 +579,7 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
}
} else {
object_name = SysAllocString(L"DriverProviderName");
- hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, NULL, NULL);
+ hr = pnpSDriverObject[0]->Get(object_name, 0, &dn, nullptr, nullptr);
SysFreeString(object_name);
if (hr == S_OK) {
driver_name = String(V_BSTR(&dn));
@@ -530,7 +589,7 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
VARIANT dv;
VariantInit(&dv);
object_name = SysAllocString(L"DriverVersion");
- hr = pnpSDriverObject[0]->Get(object_name, 0, &dv, NULL, NULL);
+ hr = pnpSDriverObject[0]->Get(object_name, 0, &dv, nullptr, nullptr);
SysFreeString(object_name);
if (hr == S_OK) {
driver_version = String(V_BSTR(&dv));
@@ -727,6 +786,107 @@ Dictionary OS_Windows::get_memory_info() const {
return meminfo;
}
+Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+#define CLEAN_PIPES \
+ if (pipe_in[0] != 0) { \
+ CloseHandle(pipe_in[0]); \
+ } \
+ if (pipe_in[1] != 0) { \
+ CloseHandle(pipe_in[1]); \
+ } \
+ if (pipe_out[0] != 0) { \
+ CloseHandle(pipe_out[0]); \
+ } \
+ if (pipe_out[1] != 0) { \
+ CloseHandle(pipe_out[1]); \
+ } \
+ if (pipe_err[0] != 0) { \
+ CloseHandle(pipe_err[0]); \
+ } \
+ if (pipe_err[1] != 0) { \
+ CloseHandle(pipe_err[1]); \
+ }
+
+ Dictionary ret;
+
+ String path = p_path.replace("/", "\\");
+ String command = _quote_command_line_argument(path);
+ for (const String &E : p_arguments) {
+ command += " " + _quote_command_line_argument(E);
+ }
+
+ // Create pipes.
+ HANDLE pipe_in[2] = { 0, 0 };
+ HANDLE pipe_out[2] = { 0, 0 };
+ HANDLE pipe_err[2] = { 0, 0 };
+
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa.bInheritHandle = true;
+ sa.lpSecurityDescriptor = nullptr;
+
+ ERR_FAIL_COND_V(!CreatePipe(&pipe_in[0], &pipe_in[1], &sa, 0), ret);
+ if (!SetHandleInformation(pipe_in[1], HANDLE_FLAG_INHERIT, 0)) {
+ CLEAN_PIPES
+ ERR_FAIL_V(ret);
+ }
+ if (!CreatePipe(&pipe_out[0], &pipe_out[1], &sa, 0)) {
+ CLEAN_PIPES
+ ERR_FAIL_V(ret);
+ }
+ if (!SetHandleInformation(pipe_out[0], HANDLE_FLAG_INHERIT, 0)) {
+ CLEAN_PIPES
+ ERR_FAIL_V(ret);
+ }
+ if (!CreatePipe(&pipe_err[0], &pipe_err[1], &sa, 0)) {
+ CLEAN_PIPES
+ ERR_FAIL_V(ret);
+ }
+ ERR_FAIL_COND_V(!SetHandleInformation(pipe_err[0], HANDLE_FLAG_INHERIT, 0), ret);
+
+ // Create process.
+ ProcessInfo pi;
+ ZeroMemory(&pi.si, sizeof(pi.si));
+ pi.si.cb = sizeof(pi.si);
+ ZeroMemory(&pi.pi, sizeof(pi.pi));
+ LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si;
+
+ pi.si.dwFlags |= STARTF_USESTDHANDLES;
+ pi.si.hStdInput = pipe_in[0];
+ pi.si.hStdOutput = pipe_out[1];
+ pi.si.hStdError = pipe_err[1];
+
+ DWORD creation_flags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW;
+
+ if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, nullptr, si_w, &pi.pi)) {
+ CLEAN_PIPES
+ ERR_FAIL_V_MSG(ret, "Could not create child process: " + command);
+ }
+ CloseHandle(pipe_in[0]);
+ CloseHandle(pipe_out[1]);
+ CloseHandle(pipe_err[1]);
+
+ ProcessID pid = pi.pi.dwProcessId;
+ process_map_mutex.lock();
+ process_map->insert(pid, pi);
+ process_map_mutex.unlock();
+
+ Ref<FileAccessWindowsPipe> main_pipe;
+ main_pipe.instantiate();
+ main_pipe->open_existing(pipe_out[0], pipe_in[1]);
+
+ Ref<FileAccessWindowsPipe> err_pipe;
+ err_pipe.instantiate();
+ err_pipe->open_existing(pipe_err[0], 0);
+
+ ret["stdio"] = main_pipe;
+ ret["stderr"] = err_pipe;
+ ret["pid"] = pid;
+
+#undef CLEAN_PIPES
+ return ret;
+}
+
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);
@@ -783,7 +943,7 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
DWORD read = 0;
for (;;) { // Read StdOut and StdErr from pipe.
bytes.resize(bytes_in_buffer + CHUNK_SIZE);
- const bool success = ReadFile(pipe[0], bytes.ptr() + bytes_in_buffer, CHUNK_SIZE, &read, NULL);
+ const bool success = ReadFile(pipe[0], bytes.ptr() + bytes_in_buffer, CHUNK_SIZE, &read, nullptr);
if (!success || read == 0) {
break;
}
@@ -856,13 +1016,16 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg
if (r_child_id) {
*r_child_id = pid;
}
+ process_map_mutex.lock();
process_map->insert(pid, pi);
+ process_map_mutex.unlock();
return OK;
}
Error OS_Windows::kill(const ProcessID &p_pid) {
int ret = 0;
+ MutexLock lock(process_map_mutex);
if (process_map->has(p_pid)) {
const PROCESS_INFORMATION pi = (*process_map)[p_pid].pi;
process_map->erase(p_pid);
@@ -873,7 +1036,7 @@ Error OS_Windows::kill(const ProcessID &p_pid) {
CloseHandle(pi.hThread);
} else {
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, false, (DWORD)p_pid);
- if (hProcess != NULL) {
+ if (hProcess != nullptr) {
ret = TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
@@ -888,24 +1051,58 @@ int OS_Windows::get_process_id() const {
}
bool OS_Windows::is_process_running(const ProcessID &p_pid) const {
+ MutexLock lock(process_map_mutex);
if (!process_map->has(p_pid)) {
return false;
}
- const PROCESS_INFORMATION &pi = (*process_map)[p_pid].pi;
+ const ProcessInfo &info = (*process_map)[p_pid];
+ if (!info.is_running) {
+ return false;
+ }
+ const PROCESS_INFORMATION &pi = info.pi;
DWORD dw_exit_code = 0;
if (!GetExitCodeProcess(pi.hProcess, &dw_exit_code)) {
return false;
}
if (dw_exit_code != STILL_ACTIVE) {
+ info.is_running = false;
+ info.exit_code = dw_exit_code;
return false;
}
return true;
}
+int OS_Windows::get_process_exit_code(const ProcessID &p_pid) const {
+ MutexLock lock(process_map_mutex);
+ if (!process_map->has(p_pid)) {
+ return -1;
+ }
+
+ const ProcessInfo &info = (*process_map)[p_pid];
+ if (!info.is_running) {
+ return info.exit_code;
+ }
+
+ const PROCESS_INFORMATION &pi = info.pi;
+
+ DWORD dw_exit_code = 0;
+ if (!GetExitCodeProcess(pi.hProcess, &dw_exit_code)) {
+ return -1;
+ }
+
+ if (dw_exit_code == STILL_ACTIVE) {
+ return -1;
+ }
+
+ info.is_running = false;
+ info.exit_code = dw_exit_code;
+ return dw_exit_code;
+}
+
Error OS_Windows::set_cwd(const String &p_cwd) {
if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0) {
return ERR_CANT_OPEN;
@@ -1455,7 +1652,7 @@ String OS_Windows::get_processor_name() const {
WCHAR buffer[256];
DWORD buffer_len = 256;
DWORD vtype = REG_SZ;
- if (RegQueryValueExW(hkey, L"ProcessorNameString", NULL, &vtype, (LPBYTE)buffer, &buffer_len) == ERROR_SUCCESS) {
+ if (RegQueryValueExW(hkey, L"ProcessorNameString", nullptr, &vtype, (LPBYTE)buffer, &buffer_len) == ERROR_SUCCESS) {
RegCloseKey(hkey);
return String::utf16((const char16_t *)buffer, buffer_len).strip_edges();
} else {
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 7e7d23a2a8..b6a21ed42d 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -69,7 +69,7 @@
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4
#endif
-template <class T>
+template <typename T>
class ComAutoreleaseRef {
public:
T *reference = nullptr;
@@ -127,6 +127,9 @@ class OS_Windows : public OS {
bool dwrite_init = false;
bool dwrite2_init = false;
+ HashMap<void *, String> temp_libraries;
+
+ void _remove_temp_library(void *p_library_handle);
String _get_default_fontname(const String &p_font_name) const;
DWRITE_FONT_WEIGHT _weight_to_dw(int p_weight) const;
DWRITE_FONT_STRETCH _stretch_to_dw(int p_stretch) const;
@@ -147,15 +150,18 @@ protected:
struct ProcessInfo {
STARTUPINFO si;
PROCESS_INFORMATION pi;
+ mutable bool is_running = true;
+ mutable int exit_code = -1;
};
HashMap<ProcessID, ProcessInfo> *process_map = nullptr;
+ Mutex process_map_mutex;
public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override;
- virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
+ virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;
@@ -181,10 +187,12 @@ public:
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 Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override;
virtual bool is_process_running(const ProcessID &p_pid) const override;
+ virtual int get_process_exit_code(const ProcessID &p_pid) const override;
virtual bool has_environment(const String &p_var) const override;
virtual String get_environment(const String &p_var) const override;
diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py
index 652dc06acf..729d55cea6 100644
--- a/platform/windows/platform_windows_builders.py
+++ b/platform/windows/platform_windows_builders.py
@@ -1,31 +1,24 @@
-"""Functions used to generate source files during build time
+"""Functions used to generate source files during build time"""
-All such functions are invoked in a subprocess on Windows to prevent build flakiness.
-
-"""
import os
from detect import get_mingw_bin_prefix
from detect import try_cmd
-from platform_methods import subprocess_main
def make_debug_mingw(target, source, env):
+ dst = str(target[0])
# Force separate debug symbols if executable size is larger than 1.9 GB.
- if env["separate_debug_symbols"] or os.stat(target[0]).st_size >= 2040109465:
+ if env["separate_debug_symbols"] or os.stat(dst).st_size >= 2040109465:
mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst))
else:
- os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst))
if try_cmd("strip --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(dst))
else:
- os.system("strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ os.system("strip --strip-debug --strip-unneeded {0}".format(dst))
if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
- os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
+ os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst))
else:
- os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
-
-
-if __name__ == "__main__":
- subprocess_main(globals())
+ os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst))
diff --git a/platform/windows/tts_windows.cpp b/platform/windows/tts_windows.cpp
index 11d63d85ee..39a8f3e120 100644
--- a/platform/windows/tts_windows.cpp
+++ b/platform/windows/tts_windows.cpp
@@ -35,7 +35,7 @@ TTS_Windows *TTS_Windows::singleton = nullptr;
void __stdcall TTS_Windows::speech_event_callback(WPARAM wParam, LPARAM lParam) {
TTS_Windows *tts = TTS_Windows::get_singleton();
SPEVENT event;
- while (tts->synth->GetEvents(1, &event, NULL) == S_OK) {
+ while (tts->synth->GetEvents(1, &event, nullptr) == S_OK) {
uint32_t stream_num = (uint32_t)event.ulStreamNum;
if (tts->ids.has(stream_num)) {
if (event.eEventId == SPEI_START_INPUT_STREAM) {
@@ -82,7 +82,7 @@ void TTS_Windows::_update_tts() {
if (SUCCEEDED(hr)) {
hr = cpEnum->GetCount(&ulCount);
while (SUCCEEDED(hr) && ulCount--) {
- wchar_t *w_id = 0L;
+ wchar_t *w_id = nullptr;
hr = cpEnum->Next(1, &cpVoiceToken, nullptr);
cpVoiceToken->GetId(&w_id);
if (String::utf16((const char16_t *)w_id) == message.voice) {
diff --git a/platform/windows/wgl_detect_version.cpp b/platform/windows/wgl_detect_version.cpp
index 49da4b58c7..12dd6f6ee6 100644
--- a/platform/windows/wgl_detect_version.cpp
+++ b/platform/windows/wgl_detect_version.cpp
@@ -140,7 +140,7 @@ Dictionary detect_wgl() {
PFNWGLCREATECONTEXTATTRIBSARBPROC gd_wglCreateContextAttribsARB = nullptr;
gd_wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)gd_wglGetProcAddress("wglCreateContextAttribsARB");
if (gd_wglCreateContextAttribsARB) {
- HGLRC new_hRC = gd_wglCreateContextAttribsARB(hDC, 0, attribs);
+ HGLRC new_hRC = gd_wglCreateContextAttribsARB(hDC, nullptr, attribs);
if (new_hRC) {
if (gd_wglMakeCurrent(hDC, new_hRC)) {
PFNWGLGETSTRINGPROC gd_wglGetString = (PFNWGLGETSTRINGPROC)GetProcAddress(module, "glGetString");
diff --git a/platform/windows/windows_utils.cpp b/platform/windows/windows_utils.cpp
new file mode 100644
index 0000000000..147b8bcbae
--- /dev/null
+++ b/platform/windows/windows_utils.cpp
@@ -0,0 +1,280 @@
+/**************************************************************************/
+/* windows_utils.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "windows_utils.h"
+
+#ifdef WINDOWS_ENABLED
+
+#include "core/error/error_macros.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#undef FAILED // Overrides Error::FAILED
+
+// dbghelp is linked only in DEBUG_ENABLED builds.
+#ifdef DEBUG_ENABLED
+#include <dbghelp.h>
+#endif
+#include <winnt.h>
+
+HashMap<String, Vector<String>> WindowsUtils::temp_pdbs;
+
+Error WindowsUtils::copy_and_rename_pdb(const String &p_dll_path) {
+#ifdef DEBUG_ENABLED
+ // 1000 ought to be enough for anybody, in case the debugger does not unblock previous PDBs.
+ // Usually no more than 2 will be used.
+ const int max_pdb_names = 1000;
+
+ struct PDBResourceInfo {
+ uint32_t address = 0;
+ String path;
+ } pdb_info;
+
+ // Open and read the PDB information if available.
+ {
+ ULONG dbg_info_size = 0;
+ DWORD dbg_info_position = 0;
+
+ {
+ // The custom LoadLibraryExW is used instead of open_dynamic_library
+ // to avoid loading the original PDB into the debugger.
+ HMODULE library_ptr = LoadLibraryExW((LPCWSTR)(p_dll_path.utf16().get_data()), NULL, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE);
+
+ ERR_FAIL_NULL_V_MSG(library_ptr, ERR_FILE_CANT_OPEN, vformat("Failed to load library '%s'.", p_dll_path));
+
+ IMAGE_DEBUG_DIRECTORY *dbg_dir = (IMAGE_DEBUG_DIRECTORY *)ImageDirectoryEntryToDataEx(library_ptr, FALSE, IMAGE_DIRECTORY_ENTRY_DEBUG, &dbg_info_size, NULL);
+
+ bool has_debug = dbg_dir && dbg_dir->Type == IMAGE_DEBUG_TYPE_CODEVIEW;
+ if (has_debug) {
+ dbg_info_position = dbg_dir->PointerToRawData;
+ dbg_info_size = dbg_dir->SizeOfData;
+ }
+
+ ERR_FAIL_COND_V_MSG(!FreeLibrary((HMODULE)library_ptr), FAILED, vformat("Failed to free library '%s'.", p_dll_path));
+
+ if (!has_debug) {
+ // Skip with no debugging symbols.
+ return ERR_SKIP;
+ }
+ }
+
+ struct CV_HEADER {
+ DWORD Signature;
+ DWORD Offset;
+ };
+
+ const DWORD nb10_magic = '01BN';
+ struct CV_INFO_PDB20 {
+ CV_HEADER CvHeader; // CvHeader.Signature = "NB10"
+ DWORD Signature;
+ DWORD Age;
+ BYTE PdbFileName[1];
+ };
+
+ const DWORD rsds_magic = 'SDSR';
+ struct CV_INFO_PDB70 {
+ DWORD Signature; // "RSDS"
+ BYTE Guid[16];
+ DWORD Age;
+ BYTE PdbFileName[1];
+ };
+
+ Vector<uint8_t> dll_data;
+
+ {
+ Error err = OK;
+ Ref<FileAccess> file = FileAccess::open(p_dll_path, FileAccess::READ, &err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to read library '%s'.", p_dll_path));
+
+ file->seek(dbg_info_position);
+ dll_data = file->get_buffer(dbg_info_size);
+ ERR_FAIL_COND_V_MSG(file->get_error() != OK, file->get_error(), vformat("Failed to read data from library '%s'.", p_dll_path));
+ }
+
+ const char *raw_pdb_path = nullptr;
+ int raw_pdb_offset = 0;
+ DWORD *pdb_info_signature = (DWORD *)dll_data.ptr();
+
+ if (*pdb_info_signature == rsds_magic) {
+ raw_pdb_path = (const char *)(((CV_INFO_PDB70 *)pdb_info_signature)->PdbFileName);
+ raw_pdb_offset = offsetof(CV_INFO_PDB70, PdbFileName);
+ } else if (*pdb_info_signature == nb10_magic) {
+ // Not even sure if this format still exists anywhere...
+ raw_pdb_path = (const char *)(((CV_INFO_PDB20 *)pdb_info_signature)->PdbFileName);
+ raw_pdb_offset = offsetof(CV_INFO_PDB20, PdbFileName);
+ } else {
+ ERR_FAIL_V_MSG(FAILED, vformat("Unknown PDB format in '%s'.", p_dll_path));
+ }
+
+ String utf_path;
+ Error err = utf_path.parse_utf8(raw_pdb_path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to read PDB path from '%s'.", p_dll_path));
+
+ pdb_info.path = utf_path;
+ pdb_info.address = dbg_info_position + raw_pdb_offset;
+ }
+
+ String dll_base_dir = p_dll_path.get_base_dir();
+ String copy_pdb_path = pdb_info.path;
+
+ // Attempting to find the PDB by absolute and relative paths.
+ if (copy_pdb_path.is_relative_path()) {
+ copy_pdb_path = dll_base_dir.path_join(copy_pdb_path);
+ if (!FileAccess::exists(copy_pdb_path)) {
+ copy_pdb_path = dll_base_dir.path_join(copy_pdb_path.get_file());
+ }
+ } else if (!FileAccess::exists(copy_pdb_path)) {
+ copy_pdb_path = dll_base_dir.path_join(copy_pdb_path.get_file());
+ }
+ ERR_FAIL_COND_V_MSG(!FileAccess::exists(copy_pdb_path), FAILED, vformat("File '%s' does not exist.", copy_pdb_path));
+
+ String new_pdb_base_name = p_dll_path.get_file().get_basename() + "_";
+
+ // Checking the available space for the updated string
+ // and trying to shorten it if there is not much space.
+ {
+ // e.g. 999.pdb
+ const uint8_t suffix_size = String::num_characters((int64_t)max_pdb_names - 1) + 4;
+ // e.g. ~lib_ + 1 for the \0
+ const uint8_t min_base_size = 5 + 1;
+ int original_path_size = pdb_info.path.utf8().length();
+ CharString utf8_name = new_pdb_base_name.utf8();
+ int new_expected_buffer_size = utf8_name.length() + suffix_size;
+
+ // Since we have limited space inside the DLL to patch the path to the PDB,
+ // it is necessary to limit the size based on the number of bytes occupied by the string.
+ if (new_expected_buffer_size > original_path_size) {
+ ERR_FAIL_COND_V_MSG(original_path_size < min_base_size + suffix_size, FAILED, vformat("The original PDB path size in bytes is too small: '%s'. Expected size: %d or more bytes, but available %d.", pdb_info.path, min_base_size + suffix_size, original_path_size));
+
+ utf8_name.resize(original_path_size - suffix_size + 1); // +1 for the \0
+ utf8_name[utf8_name.size() - 1] = '\0';
+ new_pdb_base_name.parse_utf8(utf8_name);
+ new_pdb_base_name[new_pdb_base_name.length() - 1] = '_'; // Restore the last '_'
+ WARN_PRINT(vformat("The original path size of '%s' in bytes was too small to fit the new name, so it was shortened to '%s%d.pdb'.", pdb_info.path, new_pdb_base_name, max_pdb_names - 1));
+ }
+ }
+
+ // Delete old PDB files.
+ for (const String &file : DirAccess::get_files_at(dll_base_dir)) {
+ if (file.begins_with(new_pdb_base_name) && file.ends_with(".pdb")) {
+ String path = dll_base_dir.path_join(file);
+
+ // Just try to delete without showing any errors.
+ Error err = DirAccess::remove_absolute(path);
+ if (err == OK && temp_pdbs[p_dll_path].has(path)) {
+ temp_pdbs[p_dll_path].erase(path);
+ }
+ }
+ }
+
+ // Try to copy PDB with new name and patch DLL.
+ Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ for (int i = 0; i < max_pdb_names; i++) {
+ String new_pdb_name = vformat("%s%d.pdb", new_pdb_base_name, i);
+ String new_pdb_path = dll_base_dir.path_join(new_pdb_name);
+ Error err = OK;
+
+ Ref<FileAccess> test_pdb_is_locked = FileAccess::open(new_pdb_path, FileAccess::READ_WRITE, &err);
+ if (err == ERR_FILE_CANT_OPEN) {
+ // If the file is blocked, continue searching.
+ continue;
+ } else if (err != OK && err != ERR_FILE_NOT_FOUND) {
+ ERR_FAIL_V_MSG(err, vformat("Failed to open '%s' to check if it is locked.", new_pdb_path));
+ }
+
+ err = d->copy(copy_pdb_path, new_pdb_path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to copy PDB from '%s' to '%s'.", copy_pdb_path, new_pdb_path));
+ temp_pdbs[p_dll_path].append(new_pdb_path);
+
+ Ref<FileAccess> file = FileAccess::open(p_dll_path, FileAccess::READ_WRITE, &err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s' to patch the PDB path.", p_dll_path));
+
+ int original_path_size = pdb_info.path.utf8().length();
+ // Double-check file bounds.
+ ERR_FAIL_INDEX_V_MSG(pdb_info.address + original_path_size, file->get_length(), FAILED, vformat("Failed to write a new PDB path. Probably '%s' has been changed.", p_dll_path));
+
+ Vector<uint8_t> u8 = new_pdb_name.to_utf8_buffer();
+ file->seek(pdb_info.address);
+ file->store_buffer(u8);
+
+ // Terminate string and fill the remaining part of the original string with the '\0'.
+ // Can be replaced by file->store_8('\0');
+ Vector<uint8_t> padding_buffer;
+ padding_buffer.resize((int64_t)original_path_size - u8.size());
+ padding_buffer.fill('\0');
+ file->store_buffer(padding_buffer);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to write a new PDB path to '%s'.", p_dll_path));
+
+ return OK;
+ }
+
+ ERR_FAIL_V_MSG(FAILED, vformat("Failed to find an unblocked PDB name for '%s' among %d files.", p_dll_path, max_pdb_names));
+#else
+ WARN_PRINT_ONCE("Renaming PDB files is only available in debug builds. If your libraries use PDB files, then the original ones will be used.");
+ return ERR_SKIP;
+#endif
+}
+
+void WindowsUtils::remove_temp_pdbs(const String &p_dll_path) {
+#ifdef DEBUG_ENABLED
+ if (temp_pdbs.has(p_dll_path)) {
+ Vector<String> removed;
+ int failed = 0;
+ const int failed_limit = 10;
+ for (const String &pdb : temp_pdbs[p_dll_path]) {
+ if (FileAccess::exists(pdb)) {
+ Error err = DirAccess::remove_absolute(pdb);
+ if (err == OK) {
+ removed.append(pdb);
+ } else {
+ failed++;
+ if (failed <= failed_limit) {
+ print_verbose("Failed to remove temp PDB: " + pdb);
+ }
+ }
+ } else {
+ removed.append(pdb);
+ }
+ }
+
+ if (failed > failed_limit) {
+ print_verbose(vformat("And %d more PDB files could not be removed....", failed - failed_limit));
+ }
+
+ for (const String &pdb : removed) {
+ temp_pdbs[p_dll_path].erase(pdb);
+ }
+ }
+#endif
+}
+
+#endif // WINDOWS_ENABLED
diff --git a/platform/windows/windows_utils.h b/platform/windows/windows_utils.h
new file mode 100644
index 0000000000..3f5d75294e
--- /dev/null
+++ b/platform/windows/windows_utils.h
@@ -0,0 +1,49 @@
+/**************************************************************************/
+/* windows_utils.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef WINDOWS_UTILS_H
+#define WINDOWS_UTILS_H
+
+#ifdef WINDOWS_ENABLED
+
+#include "core/string/ustring.h"
+#include "core/templates/hash_map.h"
+
+class WindowsUtils {
+ static HashMap<String, Vector<String>> temp_pdbs;
+
+public:
+ static Error copy_and_rename_pdb(const String &p_dll_path);
+ static void remove_temp_pdbs(const String &p_dll_path);
+};
+
+#endif // WINDOWS_ENABLED
+
+#endif // WINDOWS_UTILS_H