diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml
index 556a87c3d143..813392930030 100644
--- a/.github/workflows/windows_builds.yml
+++ b/.github/workflows/windows_builds.yml
@@ -8,7 +8,6 @@ env:
dev_mode=yes
module_text_server_fb_enabled=yes
debug_symbols=no
- d3d12=yes
"angle_libs=${{ github.workspace }}/"
"accesskit_sdk_path=${{ github.workspace }}/accesskit-c-0.17.0/"
SCONS_CACHE_MSVC_CONFIG: true
@@ -73,7 +72,16 @@ jobs:
uses: ./.github/actions/godot-deps
- name: Download Direct3D 12 SDK components
- run: python ./misc/scripts/install_d3d12_sdk_windows.py
+ shell: sh
+ id: d3d12-sdk
+ run: |
+ if python ./misc/scripts/install_d3d12_sdk_windows.py; then
+ echo "D3D12_ENABLED=yes" >> "$GITHUB_OUTPUT"
+ else
+ echo "::warning::Windows: Direct3D 12 SDK installation failed, building without Direct3D 12 support."
+ echo "D3D12_ENABLED=no" >> "$GITHUB_OUTPUT"
+ fi
+ continue-on-error: true
- name: Download pre-built ANGLE static libraries
uses: dsaltares/fetch-gh-release-asset@1.1.2
@@ -100,7 +108,7 @@ jobs:
- name: Compilation
uses: ./.github/actions/godot-build
with:
- scons-flags: ${{ env.SCONS_FLAGS }} ${{ matrix.scons-flags }}
+ scons-flags: ${{ env.SCONS_FLAGS }} ${{ matrix.scons-flags }} d3d12=${{ steps.d3d12-sdk.outputs.D3D12_ENABLED }}
platform: windows
target: ${{ matrix.target }}
diff --git a/SConstruct b/SConstruct
index a450b04a8e90..bf93e64fd0e8 100644
--- a/SConstruct
+++ b/SConstruct
@@ -679,17 +679,11 @@ elif methods.using_clang(env):
# Apple LLVM versions differ from upstream LLVM version \o/, compare
# in https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
if methods.is_apple_clang(env):
- if cc_version_major < 10:
+ if cc_version_major < 16:
print_error(
- "Detected Apple Clang version older than 10, which does not fully "
- "support C++17. Supported versions are Apple Clang 10 and later."
+ "Detected Apple Clang version older than 16, supported versions are Apple Clang 16 (Xcode 16) and later."
)
Exit(255)
- elif env["debug_paths_relative"] and cc_version_major < 12:
- print_warning(
- "Apple Clang < 12 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option."
- )
- env["debug_paths_relative"] = False
else:
if cc_version_major < 6:
print_error(
diff --git a/core/extension/gdextension_library_loader.cpp b/core/extension/gdextension_library_loader.cpp
index 0841443eba64..8db6ef390ef4 100644
--- a/core/extension/gdextension_library_loader.cpp
+++ b/core/extension/gdextension_library_loader.cpp
@@ -195,9 +195,13 @@ Error GDExtensionLibraryLoader::open_library(const String &p_path) {
&abs_dependencies_paths, // library_dependencies
};
- err = OS::get_singleton()->open_dynamic_library(is_static_library ? String() : abs_path, library, &data);
+ // Apple has a complex lookup system which goes beyond looking up the filename, so we try that first.
+ err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data);
if (err != OK) {
- return err;
+ err = OS::get_singleton()->open_dynamic_library(String(), library, &data);
+ if (err != OK) {
+ return err;
+ }
}
return OK;
@@ -361,8 +365,6 @@ Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
return ERR_FILE_NOT_FOUND;
}
- is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework");
-
if (!library_path.is_resource_file() && !library_path.is_absolute_path()) {
library_path = p_path.get_base_dir().path_join(library_path);
}
diff --git a/core/extension/gdextension_library_loader.h b/core/extension/gdextension_library_loader.h
index 75ad0ebedf7d..d416c83da796 100644
--- a/core/extension/gdextension_library_loader.h
+++ b/core/extension/gdextension_library_loader.h
@@ -49,8 +49,6 @@ class GDExtensionLibraryLoader : public GDExtensionLoader {
String library_path;
String entry_symbol;
- bool is_static_library = false;
-
#ifdef TOOLS_ENABLED
bool is_reloadable = false;
#endif
diff --git a/drivers/apple_embedded/display_server_apple_embedded.h b/drivers/apple_embedded/display_server_apple_embedded.h
index df7bcd9b345f..6ef197d2474d 100644
--- a/drivers/apple_embedded/display_server_apple_embedded.h
+++ b/drivers/apple_embedded/display_server_apple_embedded.h
@@ -230,4 +230,7 @@ class DisplayServerAppleEmbedded : public DisplayServer {
void resize_window(CGSize size);
virtual void swap_buffers() override {}
+
+ virtual void set_native_icon(const String &p_filename) override;
+ virtual void set_icon(const Ref &p_icon) override;
};
diff --git a/drivers/apple_embedded/display_server_apple_embedded.mm b/drivers/apple_embedded/display_server_apple_embedded.mm
index 886a17dcef04..5b5556934b83 100644
--- a/drivers/apple_embedded/display_server_apple_embedded.mm
+++ b/drivers/apple_embedded/display_server_apple_embedded.mm
@@ -818,3 +818,11 @@ _FORCE_INLINE_ int _convert_utf32_offset_to_utf16(const String &p_existing_text,
#endif
return DisplayServer::VSYNC_ENABLED;
}
+
+void DisplayServerAppleEmbedded::set_native_icon(const String &p_filename) {
+ // Not supported on Apple embedded platforms.
+}
+
+void DisplayServerAppleEmbedded::set_icon(const Ref &p_icon) {
+ // Not supported on Apple embedded platforms.
+}
diff --git a/drivers/apple_embedded/os_apple_embedded.mm b/drivers/apple_embedded/os_apple_embedded.mm
index 07d1f8a270c7..5acfce83ebbe 100644
--- a/drivers/apple_embedded/os_apple_embedded.mm
+++ b/drivers/apple_embedded/os_apple_embedded.mm
@@ -274,6 +274,11 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect)
path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".framework"));
}
+ if (!FileAccess::exists(path)) {
+ // Load .dylib from within the executable path.
+ path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".dylib"));
+ }
+
if (!FileAccess::exists(path)) {
// Load .dylib or framework from a standard iOS location.
path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file()));
@@ -284,8 +289,16 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect)
path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework"));
}
- ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
+ if (!FileAccess::exists(path)) {
+ // Load .dylib from a standard iOS location.
+ path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".dylib"));
+ }
+ if (!FileAccess::exists(path) && (p_path.ends_with(".a") || p_path.ends_with(".xcframework"))) {
+ path = String(); // Try loading static library.
+ } else {
+ ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
+ }
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
diff --git a/drivers/coreaudio/audio_driver_coreaudio.mm b/drivers/coreaudio/audio_driver_coreaudio.mm
index 6f9f690d5280..fe81283040b7 100644
--- a/drivers/coreaudio/audio_driver_coreaudio.mm
+++ b/drivers/coreaudio/audio_driver_coreaudio.mm
@@ -394,6 +394,11 @@
UInt32 flag = 1;
result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));
ERR_FAIL_COND_V(result != noErr, FAILED);
+#ifdef MACOS_ENABLED
+ flag = 0;
+ result = AudioUnitSetProperty(input_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof(flag));
+ ERR_FAIL_COND_V(result != noErr, FAILED);
+#endif
UInt32 size;
#ifdef MACOS_ENABLED
diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp
index b4f3cbd56d6d..e808c5ded239 100644
--- a/drivers/unix/file_access_unix_pipe.cpp
+++ b/drivers/unix/file_access_unix_pipe.cpp
@@ -41,6 +41,11 @@
#include
#include
#include
+#include
+
+#ifndef sighandler_t
+typedef typeof(void(int)) *sighandler_t;
+#endif
Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd, bool p_blocking) {
// Open pipe using handles created by pipe(fd) call in the OS.execute_with_pipe.
@@ -165,7 +170,11 @@ bool FileAccessUnixPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_V_MSG(fd[1] < 0, false, "Pipe must be opened before use.");
ERR_FAIL_COND_V(!p_src && p_length > 0, false);
- if (::write(fd[1], p_src, p_length) != (ssize_t)p_length) {
+ sighandler_t sig_pipe = signal(SIGPIPE, SIG_IGN);
+ ssize_t ret = ::write(fd[1], p_src, p_length);
+ signal(SIGPIPE, sig_pipe);
+
+ if (ret != (ssize_t)p_length) {
last_error = ERR_FILE_CANT_WRITE;
return false;
} else {
diff --git a/editor/export/editor_export_platform_apple_embedded.cpp b/editor/export/editor_export_platform_apple_embedded.cpp
index 29de9267c973..66b8f78f3b7f 100644
--- a/editor/export/editor_export_platform_apple_embedded.cpp
+++ b/editor/export/editor_export_platform_apple_embedded.cpp
@@ -235,6 +235,10 @@ bool EditorExportPlatformAppleEmbedded::get_export_option_visibility(const Edito
p_option == "application/signature") {
return advanced_options_enabled;
}
+ if (p_option == "capabilities/performance_a12") {
+ String rendering_method = get_project_setting(Ref(p_preset), "rendering/renderer/rendering_method.mobile");
+ return !(rendering_method == "forward_plus" || rendering_method == "mobile");
+ }
return true;
}
@@ -501,6 +505,7 @@ void EditorExportPlatformAppleEmbedded::_fix_config_file(const Ref capabilities_list = p_config.capabilities;
+ String rendering_method = get_project_setting(p_preset, "rendering/renderer/rendering_method.mobile");
if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) {
capabilities_list.push_back("wifi");
@@ -508,7 +513,7 @@ void EditorExportPlatformAppleEmbedded::_fix_config_file(const Refget("capabilities/performance_gaming_tier") && !capabilities_list.has("iphone-performance-gaming-tier")) {
capabilities_list.push_back("iphone-performance-gaming-tier");
}
- if ((bool)p_preset->get("capabilities/performance_a12") && !capabilities_list.has("iphone-ipad-minimum-performance-a12")) {
+ if (((bool)p_preset->get("capabilities/performance_a12") || rendering_method == "forward_plus" || rendering_method == "mobile") && !capabilities_list.has("iphone-ipad-minimum-performance-a12")) {
capabilities_list.push_back("iphone-ipad-minimum-performance-a12");
}
for (int idx = 0; idx < capabilities_list.size(); idx++) {
diff --git a/editor/export/gdextension_export_plugin.h b/editor/export/gdextension_export_plugin.h
index 9087725c1cd3..07e2692a96e3 100644
--- a/editor/export/gdextension_export_plugin.h
+++ b/editor/export/gdextension_export_plugin.h
@@ -149,7 +149,8 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
}
}
- Vector dependencies_shared_objects = GDExtensionLibraryLoader::find_extension_dependencies(p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); });
+ Vector dependencies_shared_objects = GDExtensionLibraryLoader::find_extension_dependencies(
+ p_path, config, [features_wo_arch, arch_tag](String p_feature) { return features_wo_arch.has(p_feature) || (p_feature == arch_tag); });
for (const SharedObject &shared_object : dependencies_shared_objects) {
_add_shared_object(shared_object);
}
diff --git a/editor/export/shader_baker_export_plugin.cpp b/editor/export/shader_baker_export_plugin.cpp
index b1f86544e71d..1257715aebec 100644
--- a/editor/export/shader_baker_export_plugin.cpp
+++ b/editor/export/shader_baker_export_plugin.cpp
@@ -54,20 +54,13 @@ bool ShaderBakerExportPlugin::_is_active(const Vector &p_features) const
return RendererSceneRenderRD::get_singleton() != nullptr && RendererRD::MaterialStorage::get_singleton() != nullptr && p_features.has("shader_baker");
}
-bool ShaderBakerExportPlugin::_initialize_container_format(const Ref &p_platform, const Vector &p_features, const Ref &p_preset) {
- Variant driver_variant = GLOBAL_GET("rendering/rendering_device/driver." + p_platform->get_os_name().to_lower());
- if (!driver_variant.is_string()) {
- driver_variant = GLOBAL_GET("rendering/rendering_device/driver");
- if (!driver_variant.is_string()) {
- return false;
- }
- }
-
- shader_container_driver = driver_variant;
+bool ShaderBakerExportPlugin::_initialize_container_format(const Ref &p_platform, const Ref &p_preset) {
+ shader_container_driver = p_preset->get_project_setting("rendering/rendering_device/driver");
+ ERR_FAIL_COND_V_MSG(shader_container_driver.is_empty(), false, "Invalid `rendering/rendering_device/driver` setting, disabling shader baking.");
for (Ref platform : platforms) {
if (platform->matches_driver(shader_container_driver)) {
- shader_container_format = platform->create_shader_container_format(p_platform, get_export_preset());
+ shader_container_format = platform->create_shader_container_format(p_platform, p_preset);
ERR_FAIL_NULL_V_MSG(shader_container_format, false, "Unable to create shader container format for the export platform.");
return true;
}
@@ -99,7 +92,7 @@ bool ShaderBakerExportPlugin::_begin_customize_resources(const Ref &p_features) const;
- virtual bool _initialize_container_format(const Ref &p_platform, const Vector &p_features, const Ref &p_preset);
+ virtual bool _initialize_container_format(const Ref &p_platform, const Ref &p_preset);
virtual void _cleanup_container_format();
virtual bool _initialize_cache_directory();
virtual bool _begin_customize_resources(const Ref &p_platform, const Vector &p_features) override;
diff --git a/methods.py b/methods.py
index 25fc5813a176..659ca00de7be 100644
--- a/methods.py
+++ b/methods.py
@@ -724,11 +724,14 @@ def get_compiler_version(env):
version = subprocess.check_output(args, encoding="utf-8").strip()
for line in version.splitlines():
split = line.split(":", 1)
- if split[0] == "catalog_productDisplayVersion":
- sem_ver = split[1].split(".")
- ret["major"] = int(sem_ver[0])
- ret["minor"] = int(sem_ver[1])
- ret["patch"] = int(sem_ver[2].split()[0])
+ if split[0] == "catalog_productSemanticVersion":
+ match = re.match(r" ([0-9]*).([0-9]*).([0-9]*)-?([a-z0-9.+]*)", split[1])
+ if match is not None:
+ ret["major"] = int(match.group(1))
+ ret["minor"] = int(match.group(2))
+ ret["patch"] = int(match.group(3))
+ # Semantic suffix (i.e. insiders+11116.177)
+ ret["metadata2"] = match.group(4)
# Could potentially add section for determining preview version, but
# that can wait until metadata is actually used for something.
if split[0] == "catalog_buildVersion":
diff --git a/misc/dist/linux/org.godotengine.Godot.appdata.xml b/misc/dist/linux/org.godotengine.Godot.appdata.xml
index 9c0cbfcc1233..95ec4fee3672 100644
--- a/misc/dist/linux/org.godotengine.Godot.appdata.xml
+++ b/misc/dist/linux/org.godotengine.Godot.appdata.xml
@@ -20,9 +20,9 @@
-
+
3D project loaded in the Godot Engine editor
- https://download.tuxfamily.org/godotengine/media/screenshots/editor_3d_fracteed-720p.jpg
+ https://docs.godotengine.org/en/stable/_images/introduction_editor.webp
https://godotengine.org
diff --git a/modules/basis_universal/SCsub b/modules/basis_universal/SCsub
index 6eac52984ffd..da24460d3175 100644
--- a/modules/basis_universal/SCsub
+++ b/modules/basis_universal/SCsub
@@ -60,6 +60,10 @@ if env["builtin_zstd"]:
env_thirdparty = env_basisu.Clone()
env_thirdparty.disable_warnings()
+if not env.msvc:
+ # Required by upstream for GCC. Enabling also for LLVM-based compilers to be consistent.
+ env_thirdparty.Append(CCFLAGS=["-fno-strict-aliasing"])
+
# Disable unneeded features to reduce binary size.
#
env_thirdparty.Append(
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index c303edf1e61e..4cd2513935fe 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -711,6 +711,14 @@ void DisplayServerAndroid::notify_surface_changed(int p_width, int p_height) {
}
}
+void DisplayServerAndroid::notify_application_paused() {
+#if defined(RD_ENABLED)
+ if (rendering_device) {
+ rendering_device->update_pipeline_cache();
+ }
+#endif // defined(RD_ENABLED)
+}
+
DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
rendering_driver = p_rendering_driver;
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 5b09128ee3c4..91c478cbba3d 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -245,6 +245,7 @@ class DisplayServerAndroid : public DisplayServer {
void reset_window();
void notify_surface_changed(int p_width, int p_height);
+ void notify_application_paused();
virtual Point2i mouse_get_position() const override;
virtual BitField mouse_get_button_state() const override;
diff --git a/platform/android/editor/game_menu_utils_jni.cpp b/platform/android/editor/game_menu_utils_jni.cpp
index 0049ee94e1a6..5e6955e3f26e 100644
--- a/platform/android/editor/game_menu_utils_jni.cpp
+++ b/platform/android/editor/game_menu_utils_jni.cpp
@@ -35,7 +35,7 @@
#include "editor/editor_node.h"
#include "editor/run/game_view_plugin.h"
-static GameViewPlugin *_get_game_view_plugin() {
+_FORCE_INLINE_ static GameViewPlugin *_get_game_view_plugin() {
ERR_FAIL_NULL_V(EditorNode::get_singleton(), nullptr);
ERR_FAIL_NULL_V(EditorNode::get_singleton()->get_editor_main_screen(), nullptr);
return Object::cast_to(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index b0155879e2df..97eee2926be4 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -97,7 +97,7 @@ android {
defaultConfig {
// The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects.
aaptOptions {
- ignoreAssetsPattern "!.svn:!.git:!.gitignore:!.ds_store:!*.scc:_*:!CVS:!thumbs.db:!picasa.ini:!*~"
+ ignoreAssetsPattern "!.svn:!.git:!.gitignore:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}
ndk {
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 30e4251de886..623b99acccff 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -7,7 +7,7 @@ ext.versions = [
minSdk : 24,
// Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
targetSdk : 35,
- buildTools : '35.0.0',
+ buildTools : '35.0.1',
kotlinVersion : '2.1.20',
fragmentVersion : '1.8.6',
nexusPublishVersion: '1.3.0',
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 529c9eae69d8..85087400cffc 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -107,6 +107,8 @@ class Godot private constructor(val context: Context) {
}
}
+ private const val EXIT_RENDERER_TIMEOUT_IN_MS = 1500L
+
// Supported build flavors
private const val EDITOR_FLAVOR = "editor"
private const val TEMPLATE_FLAVOR = "template"
@@ -390,7 +392,7 @@ class Godot private constructor(val context: Context) {
}
} else {
if (rootView.rootWindowInsets != null) {
- if (!useImmersive.get() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)) {
+ if (!useImmersive.get()) {
val windowInsets = WindowInsetsCompat.toWindowInsetsCompat(rootView.rootWindowInsets)
val insets = windowInsets.getInsets(getInsetType())
rootView.setPadding(insets.left, insets.top, insets.right, insets.bottom)
@@ -399,8 +401,7 @@ class Godot private constructor(val context: Context) {
ViewCompat.setOnApplyWindowInsetsListener(rootView) { v: View, insets: WindowInsetsCompat ->
v.post {
- if (useImmersive.get() && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
- // Fixes issue where padding remained visible in immersive mode on some devices.
+ if (useImmersive.get()) {
v.setPadding(0, 0, 0, 0)
} else {
val windowInsets = insets.getInsets(getInsetType())
@@ -736,7 +737,12 @@ class Godot private constructor(val context: Context) {
plugin.onMainDestroy()
}
- renderView?.onActivityDestroyed()
+ if (renderView?.blockingExitRenderer(EXIT_RENDERER_TIMEOUT_IN_MS) != true) {
+ Log.w(TAG, "Unable to exit the renderer within $EXIT_RENDERER_TIMEOUT_IN_MS ms... Force quitting the process.")
+ onGodotTerminating()
+ forceQuit(0)
+ }
+
this.primaryHost = null
}
@@ -749,7 +755,9 @@ class Godot private constructor(val context: Context) {
val newDarkMode = newConfig.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
if (darkMode != newDarkMode) {
darkMode = newDarkMode
- GodotLib.onNightModeChanged()
+ runOnRenderThread {
+ GodotLib.onNightModeChanged()
+ }
}
}
@@ -761,7 +769,9 @@ class Godot private constructor(val context: Context) {
plugin.onMainActivityResult(requestCode, resultCode, data)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- FilePicker.handleActivityResult(context, requestCode, resultCode, data)
+ runOnRenderThread {
+ FilePicker.handleActivityResult(context, requestCode, resultCode, data)
+ }
}
}
@@ -776,11 +786,13 @@ class Godot private constructor(val context: Context) {
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults)
}
- for (i in permissions.indices) {
- GodotLib.requestPermissionResult(
- permissions[i],
- grantResults[i] == PackageManager.PERMISSION_GRANTED
- )
+ runOnRenderThread {
+ for (i in permissions.indices) {
+ GodotLib.requestPermissionResult(
+ permissions[i],
+ grantResults[i] == PackageManager.PERMISSION_GRANTED
+ )
+ }
}
}
@@ -1089,7 +1101,7 @@ class Godot private constructor(val context: Context) {
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainBackPressed()
}
- renderView?.queueOnRenderThread { GodotLib.back() }
+ runOnRenderThread { GodotLib.back() }
}
/**
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
index fdd0d544485f..eb50140057db 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -130,8 +130,8 @@ public void onActivityStarted() {
}
@Override
- public void onActivityDestroyed() {
- requestRenderThreadExitAndWait();
+ public boolean blockingExitRenderer(long blockingTimeInMs) {
+ return requestRenderThreadExitAndWait(blockingTimeInMs);
}
@Override
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 9fe4fb20aa7a..7b7beadb0563 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -56,7 +56,7 @@ public interface GodotRenderView {
void onActivityStarted();
- void onActivityDestroyed();
+ boolean blockingExitRenderer(long blockingTimeInMs);
GodotInputHandler getInputHandler();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index 6287065f114b..e30c14d12550 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -115,8 +115,8 @@ public void onActivityResumed() {
}
@Override
- public void onActivityDestroyed() {
- requestRenderThreadExitAndWait();
+ public boolean blockingExitRenderer(long blockingTimeInMs) {
+ return requestRenderThreadExitAndWait(blockingTimeInMs);
}
@Override
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 6a4e9da699e3..8b25fdfeceb6 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
@@ -604,6 +604,18 @@ protected final void requestRenderThreadExitAndWait() {
mGLThread.requestExitAndWait();
}
}
+
+ /**
+ * Requests the render thread to exit and block up to the given timeInMs until it's done.
+ *
+ * @return true if the thread exited, false otherwise.
+ */
+ protected final boolean requestRenderThreadExitAndWait(long timeInMs) {
+ if (mGLThread != null) {
+ return mGLThread.requestExitAndWait(timeInMs);
+ }
+ return false;
+ }
// -- GODOT end --
/**
@@ -1796,6 +1808,23 @@ public void requestExitAndWait() {
}
}
+ public boolean requestExitAndWait(long timeInMs) {
+ // Don't call this from GLThread thread or it is a guaranteed deadlock!
+ synchronized(sGLThreadManager) {
+ mShouldExit = true;
+ sGLThreadManager.notifyAll();
+ if (!mExited) {
+ try {
+ sGLThreadManager.wait(timeInMs);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ return mExited;
+ }
+ }
+
public void requestReleaseEglContextLocked() {
mShouldReleaseEglContext = true;
sGLThreadManager.notifyAll();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index a0ec7312911d..770364e5645a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -221,7 +221,15 @@ public void setView(final GodotRenderView view) {
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
/* Let SurfaceView get focus if back key is input. */
if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // Clear focus from EditText immediately
+ clearFocus();
+
+ // Transfer focus to render view
mRenderView.getView().requestFocus();
+
+ // Forward this back key event to the render view's input handler
+ // since we're no longer the focused view
+ return mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent);
}
// When a hardware keyboard is connected, all key events come through so we can route them
@@ -248,6 +256,11 @@ public boolean onKeyUp(int keyCode, KeyEvent keyEvent) {
return mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent);
}
+ // If this is a BACK key and we don't have focus anymore, forward to render view
+ if (keyCode == KeyEvent.KEYCODE_BACK && !hasFocus()) {
+ return mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent);
+ }
+
if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent)) {
return true;
} else {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
index 574ecd58eb7d..0e8ed71e6954 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
@@ -69,6 +69,7 @@ internal enum class StorageScope {
private val internalAppDir: String? = context.filesDir.canonicalPath
private val internalCacheDir: String? = context.cacheDir.canonicalPath
private val externalAppDir: String? = context.getExternalFilesDir(null)?.canonicalPath
+ private val obbDir: String? = context.obbDir.canonicalPath
private val sharedDir : String? = Environment.getExternalStorageDirectory().canonicalPath
private val downloadsSharedDir: String? = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).canonicalPath
private val documentsSharedDir: String? = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).canonicalPath
@@ -127,6 +128,10 @@ internal enum class StorageScope {
return APP
}
+ if (obbDir != null && canonicalPathFile.startsWith(obbDir)) {
+ return APP
+ }
+
if (sharedDir != null && canonicalPathFile.startsWith(sharedDir)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
// Before R, apps had access to shared storage so long as they have the right
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
index 9e30de6a153f..c711fc519f5b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
@@ -119,6 +119,15 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf
vkThread.requestExitAndWait()
}
+ /**
+ * Requests the render thread to exit and block up to the given [timeInMs] until it's done.
+ *
+ * @return true if the thread exited, false otherwise.
+ */
+ fun requestRenderThreadExitAndWait(timeInMs: Long): Boolean {
+ return vkThread.requestExitAndWait(timeInMs)
+ }
+
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
vkThread.onSurfaceChanged(width, height)
}
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 c7cb97d911ed..e5e3b89b5957 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
@@ -32,6 +32,7 @@
package org.godotengine.godot.vulkan
import android.util.Log
+import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
@@ -104,13 +105,37 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
try {
Log.i(TAG, "Waiting on exit for $name")
lockCondition.await()
- } catch (ex: InterruptedException) {
+ } catch (_: InterruptedException) {
currentThread().interrupt()
}
}
}
}
+ /**
+ * Request the thread to exit and block up to the given [timeInMs] until it's done.
+ *
+ * @return true if the thread exited, false otherwise.
+ */
+ fun requestExitAndWait(timeInMs: Long): Boolean {
+ lock.withLock {
+ shouldExit = true
+ lockCondition.signalAll()
+
+ var remainingTimeInNanos = TimeUnit.MILLISECONDS.toNanos(timeInMs)
+ while (!exited && remainingTimeInNanos > 0) {
+ try {
+ Log.i(TAG, "Waiting on exit for $name for $remainingTimeInNanos")
+ remainingTimeInNanos = lockCondition.awaitNanos(remainingTimeInNanos)
+ } catch (_: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+
+ return exited
+ }
+ }
+
/**
* Invoked when the app resumes.
*/
diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index 1584c6fd5ce0..0c3956841d6d 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -21,4 +21,12 @@ target_include_directories(${PROJECT_NAME}
${ANDROID_ROOT_DIR}
${OPENXR_INCLUDE_DIR})
-add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED -DDEBUG_ENABLED)
+add_definitions(
+ -DUNIX_ENABLED
+ -DVULKAN_ENABLED
+ -DANDROID_ENABLED
+ -DGLES3_ENABLED
+ -DTOOLS_ENABLED
+ -DDEBUG_ENABLED
+ -DRD_ENABLED
+)
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index c7a549f1a851..31920fe53d60 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -605,6 +605,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE
if (os_android->get_main_loop()) {
os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
}
+
+ if (DisplayServerAndroid *dsa = Object::cast_to(DisplayServer::get_singleton())) {
+ dsa->notify_application_paused();
+ }
}
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz) {
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 2f495b1539c0..cebf2e49c377 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -73,6 +73,14 @@ String _remove_symlink(const String &dir) {
return dir_without_symlink;
}
+#ifdef TOOLS_ENABLED
+_FORCE_INLINE_ static GameViewPlugin *_get_game_view_plugin() {
+ ERR_FAIL_NULL_V(EditorNode::get_singleton(), nullptr);
+ ERR_FAIL_NULL_V(EditorNode::get_singleton()->get_editor_main_screen(), nullptr);
+ return Object::cast_to(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
+}
+#endif
+
class AndroidLogger : public Logger {
public:
virtual void logv(const char *p_format, va_list p_list, bool p_err) {
@@ -346,7 +354,7 @@ void OS_Android::main_loop_begin() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
- GameViewPlugin *game_view_plugin = Object::cast_to(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
+ GameViewPlugin *game_view_plugin = _get_game_view_plugin();
if (game_view_plugin != nullptr) {
game_view_plugin->connect("main_screen_changed", callable_mp_static(&OS_Android::_on_main_screen_changed));
}
@@ -376,7 +384,7 @@ bool OS_Android::main_loop_iterate(bool *r_should_swap_buffers) {
void OS_Android::main_loop_end() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
- GameViewPlugin *game_view_plugin = Object::cast_to(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
+ GameViewPlugin *game_view_plugin = _get_game_view_plugin();
if (game_view_plugin != nullptr) {
game_view_plugin->disconnect("main_screen_changed", callable_mp_static(&OS_Android::_on_main_screen_changed));
}
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index 65c85ff82283..8cd81a222d9e 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -301,9 +301,7 @@ def configure(env: "SConsEnvironment"):
env.ParseConfig("pkg-config libpcre2-32 --cflags --libs")
if not env["builtin_recastnavigation"]:
- # No pkgconfig file so far, hardcode default paths.
- env.Prepend(CPPPATH=["/usr/include/recastnavigation"])
- env.Append(LIBS=["Recast"])
+ env.ParseConfig("pkg-config recastnavigation --cflags --libs")
if not env["builtin_embree"] and env["arch"] in ["x86_64", "arm64"]:
# No pkgconfig file so far, hardcode expected lib name.
diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp
index 9a4372e61dab..8bda6d932c13 100644
--- a/platform/linuxbsd/godot_linuxbsd.cpp
+++ b/platform/linuxbsd/godot_linuxbsd.cpp
@@ -41,6 +41,18 @@
#include
#endif
+#if defined(__x86_64) || defined(__x86_64__)
+void __cpuid(int *r_cpuinfo, int p_info) {
+ // Note: Some compilers have a buggy `__cpuid` intrinsic, using inline assembly (based on LLVM-20 implementation) instead.
+ __asm__ __volatile__(
+ "xchgq %%rbx, %q1;"
+ "cpuid;"
+ "xchgq %%rbx, %q1;"
+ : "=a"(r_cpuinfo[0]), "=r"(r_cpuinfo[1]), "=c"(r_cpuinfo[2]), "=d"(r_cpuinfo[3])
+ : "0"(p_info));
+}
+#endif
+
// For export templates, add a section; the exporter will patch it to enclose
// the data appended to the executable (bundled PCK).
#if !defined(TOOLS_ENABLED) && defined(__GNUC__)
@@ -54,6 +66,27 @@ extern "C" const char *pck_section_dummy_call() {
#endif
int main(int argc, char *argv[]) {
+#if defined(__x86_64) || defined(__x86_64__)
+ int cpuinfo[4];
+ __cpuid(cpuinfo, 0x01);
+
+ if (!(cpuinfo[2] & (1 << 20))) {
+ printf("A CPU with SSE4.2 instruction set support is required.\n");
+
+ int ret = system("zenity --warning --title \"Godot Engine\" --text \"A CPU with SSE4.2 instruction set support is required.\" 2> /dev/null");
+ if (ret != 0) {
+ ret = system("kdialog --title \"Godot Engine\" --sorry \"A CPU with SSE4.2 instruction set support is required.\" 2> /dev/null");
+ }
+ if (ret != 0) {
+ ret = system("Xdialog --title \"Godot Engine\" --msgbox \"A CPU with SSE4.2 instruction set support is required.\" 0 0 2> /dev/null");
+ }
+ if (ret != 0) {
+ ret = system("xmessage -center -title \"Godot Engine\" \"A CPU with SSE4.2 instruction set support is required.\" 2> /dev/null");
+ }
+ abort();
+ }
+#endif
+
#if defined(SANITIZERS_ENABLED)
// Note: Set stack size to be at least 30 MB (vs 8 MB default) to avoid overflow, address sanitizer can increase stack usage up to 3 times.
struct rlimit stack_lim = { 0x1E00000, 0x1E00000 };
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
index bd1db41b0541..cce731b5b628 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -742,7 +742,7 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
if (name == registry->wp_viewporter_name) {
for (KeyValue &pair : registry->wayland_thread->windows) {
- WindowState ws = pair.value;
+ WindowState &ws = pair.value;
if (registry->wp_viewporter) {
wp_viewporter_destroy(registry->wp_viewporter);
registry->wp_viewporter = nullptr;
@@ -780,7 +780,7 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
if (name == registry->wp_fractional_scale_manager_name) {
for (KeyValue &pair : registry->wayland_thread->windows) {
- WindowState ws = pair.value;
+ WindowState &ws = pair.value;
if (registry->wp_fractional_scale_manager) {
wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager);
@@ -1550,6 +1550,16 @@ void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat
ss->xkb_context = nullptr;
}
+ if (ss->xkb_keymap) {
+ xkb_keymap_unref(ss->xkb_keymap);
+ ss->xkb_keymap = nullptr;
+ }
+
+ if (ss->xkb_state) {
+ xkb_state_unref(ss->xkb_state);
+ ss->xkb_state = nullptr;
+ }
+
if (ss->wl_keyboard) {
wl_keyboard_destroy(ss->wl_keyboard);
ss->wl_keyboard = nullptr;
@@ -2164,7 +2174,7 @@ void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- ss->repeat_key_delay_msec = 1000 / rate;
+ ss->repeat_key_delay_msec = rate ? 1000 / rate : 0;
ss->repeat_start_delay_msec = delay;
}
diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h
index 64e1babcf672..f7d29a0947ce 100644
--- a/platform/linuxbsd/wayland/wayland_thread.h
+++ b/platform/linuxbsd/wayland/wayland_thread.h
@@ -466,8 +466,11 @@ class WaylandThread {
xkb_layout_index_t current_layout_index = 0;
- int32_t repeat_key_delay_msec = 0;
- int32_t repeat_start_delay_msec = 0;
+ // Clients with `wl_seat`s older than version 4 do not support
+ // `wl_keyboard::repeat_info`, so we'll provide a reasonable default of 25
+ // keys per second, with a start delay of 600 milliseconds.
+ int32_t repeat_key_delay_msec = 1000 / 25;
+ int32_t repeat_start_delay_msec = 600;
xkb_keycode_t repeating_keycode = XKB_KEYCODE_INVALID;
uint64_t last_repeat_start_msec = 0;
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 0be999cc867b..d08704eea459 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -1798,59 +1798,57 @@ Ref DisplayServerX11::screen_get_image(int p_screen) const {
float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_
+ ERR_FAIL_COND_V_MSG(!xrandr_ext_ok || !xrr_get_monitors, SCREEN_REFRESH_RATE_FALLBACK, "XRandR extension is not available.");
+
p_screen = _get_screen_index(p_screen);
int screen_count = get_screen_count();
ERR_FAIL_INDEX_V(p_screen, screen_count, SCREEN_REFRESH_RATE_FALLBACK);
- //Use xrandr to get screen refresh rate.
- if (xrandr_ext_ok) {
- XRRScreenResources *screen_info = XRRGetScreenResourcesCurrent(x11_display, windows[MAIN_WINDOW_ID].x11_window);
- if (screen_info) {
- RRMode current_mode = 0;
- xrr_monitor_info *monitors = nullptr;
-
- if (xrr_get_monitors) {
- int count = 0;
- monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);
- ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK);
- } else {
- ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
- return SCREEN_REFRESH_RATE_FALLBACK;
- }
-
- bool found_active_mode = false;
- for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting.
- XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]);
- if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue.
- continue;
- }
+ int target_x;
+ int target_y;
+ {
+ int count = 0;
+ xrr_monitor_info *monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);
+ ERR_FAIL_NULL_V(monitors, SCREEN_REFRESH_RATE_FALLBACK);
+ if (count <= p_screen) {
+ xrr_free_monitors(monitors);
+ ERR_FAIL_V_MSG(SCREEN_REFRESH_RATE_FALLBACK, vformat("Invalid screen index: %d (count: %d).", p_screen, count));
+ }
+ target_x = monitors[p_screen].x;
+ target_y = monitors[p_screen].y;
+ xrr_free_monitors(monitors);
+ }
- if (monitor_info->mode != None) {
- current_mode = monitor_info->mode;
- found_active_mode = true;
- break;
- }
- }
+ XRRScreenResources *screen_res = XRRGetScreenResourcesCurrent(x11_display, windows[MAIN_WINDOW_ID].x11_window);
+ ERR_FAIL_NULL_V(screen_res, SCREEN_REFRESH_RATE_FALLBACK);
- if (found_active_mode) {
- for (int mode = 0; mode < screen_info->nmode; mode++) {
- XRRModeInfo m_info = screen_info->modes[mode];
- if (m_info.id == current_mode) {
- // Snap to nearest 0.01 to stay consistent with other platforms.
- return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01);
- }
- }
+ XRRModeInfo *mode_info = nullptr;
+ for (int crtc = 0; crtc < screen_res->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting.
+ XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_res, screen_res->crtcs[crtc]);
+ if (monitor_info->x != target_x || monitor_info->y != target_y || monitor_info->mode == None) {
+ XRRFreeCrtcInfo(monitor_info);
+ continue;
+ }
+ for (int mode = 0; mode < screen_res->nmode; mode++) {
+ if (screen_res->modes[mode].id == monitor_info->mode) {
+ mode_info = &screen_res->modes[mode];
}
-
- ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred.
- return SCREEN_REFRESH_RATE_FALLBACK;
- } else {
- ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
- return SCREEN_REFRESH_RATE_FALLBACK;
}
+ XRRFreeCrtcInfo(monitor_info);
+ break;
+ }
+
+ float result;
+ if (mode_info) {
+ // Snap to nearest 0.01 to stay consistent with other platforms.
+ result = Math::snapped((float)mode_info->dotClock / ((float)mode_info->hTotal * (float)mode_info->vTotal), 0.01);
+ } else {
+ ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
+ result = SCREEN_REFRESH_RATE_FALLBACK;
}
- ERR_PRINT("An error occurred while trying to get the screen refresh rate.");
- return SCREEN_REFRESH_RATE_FALLBACK;
+
+ XRRFreeScreenResources(screen_res);
+ return result;
}
#ifdef DBUS_ENABLED
@@ -3696,8 +3694,11 @@ Key DisplayServerX11::keyboard_get_label_from_physical(Key p_keycode) const {
Key key = KeyMappingX11::get_keycode(xkeysym);
#ifdef XKB_ENABLED
if (xkb_loaded_v08p) {
- String keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(xkeysym)));
- key = fix_key_label(keysym[0], KeyMappingX11::get_keycode(xkeysym));
+ char32_t chr = xkb_keysym_to_utf32(xkb_keysym_to_upper(xkeysym));
+ if (chr != 0) {
+ String keysym = String::chr(chr);
+ key = fix_key_label(keysym[0], KeyMappingX11::get_keycode(xkeysym));
+ }
}
#endif
@@ -4535,7 +4536,7 @@ Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XP
return True;
}
-bool DisplayServerX11::_wait_for_events() const {
+bool DisplayServerX11::_wait_for_events(int timeout_seconds, int timeout_microseconds) const {
int x11_fd = ConnectionNumber(x11_display);
fd_set in_fds;
@@ -4545,8 +4546,8 @@ bool DisplayServerX11::_wait_for_events() const {
FD_SET(x11_fd, &in_fds);
struct timeval tv;
- tv.tv_usec = 0;
- tv.tv_sec = 1;
+ tv.tv_sec = timeout_seconds;
+ tv.tv_usec = timeout_microseconds;
// Wait for next event or timeout.
int num_ready_fds = select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv);
@@ -4565,7 +4566,8 @@ bool DisplayServerX11::_wait_for_events() const {
void DisplayServerX11::_poll_events() {
while (!events_thread_done.is_set()) {
- _wait_for_events();
+ // Wait with a shorter timeout from the events thread to avoid delayed inputs.
+ _wait_for_events(0, 1000);
// Process events from the queue.
{
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index e155248a3da7..2b9f33737ae3 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -377,7 +377,7 @@ class DisplayServerX11 : public DisplayServer {
SafeFlag events_thread_done;
LocalVector polled_events;
static void _poll_events_thread(void *ud);
- bool _wait_for_events() const;
+ bool _wait_for_events(int timeout_seconds = 1, int timeout_microseconds = 0) const;
void _poll_events();
void _check_pending_events(LocalVector &r_events);
diff --git a/platform/macos/SCsub b/platform/macos/SCsub
index 19b0acb74f3e..180fd3b2527b 100644
--- a/platform/macos/SCsub
+++ b/platform/macos/SCsub
@@ -11,10 +11,7 @@ files = [
"godot_application_delegate.mm",
"crash_handler_macos.mm",
"display_server_macos_base.mm",
- "display_server_embedded.mm",
"display_server_macos.mm",
- "embedded_debugger.mm",
- "embedded_gl_manager.mm",
"godot_button_view.mm",
"godot_content_view.mm",
"godot_status_item.mm",
@@ -35,6 +32,9 @@ files = [
if env.editor_build:
files += [
+ "display_server_embedded.mm",
+ "embedded_debugger.mm",
+ "embedded_gl_manager.mm",
"editor/embedded_game_view_plugin.mm",
"editor/embedded_process_macos.mm",
]
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index a6f4f36c89e1..75a30dd89cd8 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -64,14 +64,18 @@
@class GodotContentView;
@class GodotWindowDelegate;
@class GodotButtonView;
+#ifdef TOOLS_ENABLED
@class GodotEmbeddedView;
@class CALayerHost;
+#endif
#undef BitMap
#undef CursorShape
#undef FontVariation
+#ifdef TOOLS_ENABLED
class EmbeddedProcessMacOS;
+#endif
class DisplayServerMacOS : public DisplayServerMacOSBase {
GDSOFTCLASS(DisplayServerMacOS, DisplayServerMacOSBase);
@@ -239,12 +243,14 @@ class DisplayServerMacOS : public DisplayServerMacOSBase {
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 &p_filters, const TypedArray &p_options, const Callable &p_callback, bool p_options_in_cb, WindowID p_window_id);
+#ifdef TOOLS_ENABLED
struct EmbeddedProcessData {
- EmbeddedProcessMacOS *process;
+ EmbeddedProcessMacOS *process = nullptr;
WindowData *wd = nullptr;
CALayer *layer_host = nil;
};
HashMap embedded_processes;
+#endif
void _window_update_display_id(WindowData *p_wd);
public:
@@ -440,9 +446,9 @@ class DisplayServerMacOS : public DisplayServerMacOSBase {
virtual void enable_for_stealing_focus(OS::ProcessID pid) override;
#ifdef TOOLS_ENABLED
Error embed_process_update(WindowID p_window, EmbeddedProcessMacOS *p_process);
-#endif
virtual Error request_close_embedded_process(OS::ProcessID p_pid) override;
virtual Error remove_embedded_process(OS::ProcessID p_pid) override;
+#endif
void _process_events(bool p_pump);
virtual void process_events() override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index deeb43557d70..138365c791e9 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -41,9 +41,12 @@
#import "godot_window.h"
#import "godot_window_delegate.h"
#import "key_mapping_macos.h"
-#import "macos_quartz_core_spi.h"
#import "os_macos.h"
+#ifdef TOOLS_ENABLED
+#import "macos_quartz_core_spi.h"
+#endif
+
#include "core/config/project_settings.h"
#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
@@ -2645,9 +2648,11 @@
[wd.window_object setBackgroundColor:[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.004f]];
}
// Force update of the window styles.
- NSRect frameRect = [wd.window_object frame];
- [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
- [wd.window_object setFrame:frameRect display:NO];
+ if ([wd.window_object isVisible]) {
+ NSRect frameRect = [wd.window_object frame];
+ [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
+ [wd.window_object setFrame:frameRect display:NO];
+ }
}
_update_window_style(wd, p_window);
if (was_visible || [wd.window_object isVisible]) {
@@ -2681,9 +2686,11 @@
wd.layered_window = p_enabled;
set_window_per_pixel_transparency_enabled(p_enabled, p_window);
// Force update of the window styles.
- NSRect frameRect = [wd.window_object frame];
- [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
- [wd.window_object setFrame:frameRect display:NO];
+ if ([wd.window_object isVisible]) {
+ NSRect frameRect = [wd.window_object frame];
+ [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO];
+ [wd.window_object setFrame:frameRect display:NO];
+ }
} break;
case WINDOW_FLAG_NO_FOCUS: {
wd.no_focus = p_enabled;
@@ -3219,8 +3226,6 @@
return OK;
}
-#endif
-
Error DisplayServerMacOS::request_close_embedded_process(OS::ProcessID p_pid) {
return OK;
}
@@ -3236,6 +3241,8 @@
return OK;
}
+#endif
+
void DisplayServerMacOS::process_events() {
_process_events(true);
}
diff --git a/platform/macos/editor/embedded_process_macos.mm b/platform/macos/editor/embedded_process_macos.mm
index d9bd1ce018f3..627d90b9bb01 100644
--- a/platform/macos/editor/embedded_process_macos.mm
+++ b/platform/macos/editor/embedded_process_macos.mm
@@ -104,6 +104,13 @@
if (current_process_id != 0 && is_embedding_completed()) {
ds->remove_embedded_process(current_process_id);
}
+ DisplayServer *ds = DisplayServer::get_singleton();
+ for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) {
+ ds->cursor_set_custom_image(Ref(), (DisplayServer::CursorShape)i, Vector2());
+ }
+ if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_VISIBLE) {
+ ds->mouse_set_mode(DisplayServer::MOUSE_MODE_VISIBLE);
+ }
current_process_id = 0;
embedding_state = EmbeddingState::IDLE;
context_id = 0;
@@ -191,6 +198,7 @@
EmbeddedProcessBase() {
layer_host = memnew(LayerHost(this));
add_child(layer_host);
+ set_focus_mode(FOCUS_NONE);
layer_host->set_focus_mode(FOCUS_ALL);
layer_host->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
layer_host->set_custom_minimum_size(Size2(100, 100));
@@ -218,18 +226,23 @@
if (script_debugger) {
script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_MOUSE_ENTER });
}
+ if (get_window()->has_focus()) {
+ grab_focus();
+ }
} break;
case NOTIFICATION_FOCUS_ENTER: {
// Restore mouse capture, if necessary.
- DisplayServer *ds = DisplayServer::get_singleton();
- if (process->get_mouse_mode() != ds->mouse_get_mode()) {
- // Restore embedded process mouse mode.
- ds->mouse_set_mode(process->get_mouse_mode());
- }
if (!window_focused && script_debugger) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ if (process->get_mouse_mode() != ds->mouse_get_mode()) {
+ // Restore embedded process mouse mode.
+ ds->mouse_set_mode(process->get_mouse_mode());
+ }
+ script_debugger->send_message("embed:notification", { NOTIFICATION_APPLICATION_FOCUS_IN });
script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_FOCUS_IN });
window_focused = true;
}
+ process->queue_redraw();
} break;
case NOTIFICATION_MOUSE_EXIT: {
DisplayServer *ds = DisplayServer::get_singleton();
@@ -242,14 +255,16 @@
} break;
case NOTIFICATION_FOCUS_EXIT: {
// Temporarily set mouse state back to visible, so the user can interact with the editor.
- DisplayServer *ds = DisplayServer::get_singleton();
- if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_VISIBLE) {
- ds->mouse_set_mode(DisplayServer::MOUSE_MODE_VISIBLE);
- }
if (window_focused && script_debugger) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_VISIBLE) {
+ ds->mouse_set_mode(DisplayServer::MOUSE_MODE_VISIBLE);
+ }
script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_FOCUS_OUT });
+ script_debugger->send_message("embed:notification", { NOTIFICATION_APPLICATION_FOCUS_OUT });
window_focused = false;
}
+ process->queue_redraw();
} break;
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (script_debugger && has_focus()) {
@@ -265,28 +280,37 @@
for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) {
ds->cursor_set_custom_image(Ref(), (DisplayServer::CursorShape)i, Vector2());
}
+ if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_VISIBLE) {
+ ds->mouse_set_mode(DisplayServer::MOUSE_MODE_VISIBLE);
+ }
}
} break;
+ case NOTIFICATION_APPLICATION_FOCUS_IN:
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
- if (!window_focused && script_debugger) {
+ if (has_focus() && !window_focused && script_debugger) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ if (process->get_mouse_mode() != ds->mouse_get_mode()) {
+ // Restore embedded process mouse mode.
+ ds->mouse_set_mode(process->get_mouse_mode());
+ if (process->get_mouse_mode() != DisplayServer::MOUSE_MODE_VISIBLE) {
+ get_window()->grab_focus();
+ }
+ }
+ script_debugger->send_message("embed:notification", { NOTIFICATION_APPLICATION_FOCUS_IN });
script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_FOCUS_IN });
window_focused = true;
}
} break;
+ case NOTIFICATION_APPLICATION_FOCUS_OUT:
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
- if (window_focused && script_debugger) {
+ if (has_focus() && window_focused && script_debugger) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_VISIBLE) {
+ ds->mouse_set_mode(DisplayServer::MOUSE_MODE_VISIBLE);
+ }
script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_FOCUS_OUT });
- window_focused = false;
- }
- } break;
- case NOTIFICATION_APPLICATION_FOCUS_IN: {
- if (script_debugger) {
- script_debugger->send_message("embed:notification", { NOTIFICATION_APPLICATION_FOCUS_IN });
- }
- } break;
- case NOTIFICATION_APPLICATION_FOCUS_OUT: {
- if (script_debugger) {
script_debugger->send_message("embed:notification", { NOTIFICATION_APPLICATION_FOCUS_OUT });
+ window_focused = false;
}
} break;
}
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index 165289c5905b..97263c58a151 100644
--- a/platform/macos/godot_content_view.mm
+++ b/platform/macos/godot_content_view.mm
@@ -543,7 +543,7 @@ - (void)mouseMoved:(NSEvent *)event {
ds->get_key_modifier_state([event modifierFlags], mm);
const NSRect contentRect = [wd.window_view frame];
- if (NSPointInRect([event locationInWindow], contentRect)) {
+ if (NSPointInRect([event locationInWindow], contentRect) && [NSWindow windowNumberAtPoint:[NSEvent mouseLocation] belowWindowWithWindowNumber:0 /*topmost*/] == [wd.window_object windowNumber]) {
ds->mouse_enter_window(window_id);
}
diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm
index 3807464fcf2e..d29596487b8a 100644
--- a/platform/macos/godot_main_macos.mm
+++ b/platform/macos/godot_main_macos.mm
@@ -110,7 +110,7 @@ int main(int argc, char **argv) {
OS_MacOS *os = nullptr;
if (is_embedded) {
-#ifdef DEBUG_ENABLED
+#ifdef TOOLS_ENABLED
os = memnew(OS_MacOS_Embedded(args[0], remaining_args, remaining_args > 0 ? &args[1] : nullptr));
#else
WARN_PRINT("Embedded mode is not supported in release builds.");
diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h
index 8cf47902b1d9..a15dcc6cf3b8 100644
--- a/platform/macos/os_macos.h
+++ b/platform/macos/os_macos.h
@@ -178,7 +178,7 @@ class OS_MacOS_Headless : public OS_MacOS {
OS_MacOS_Headless(const char *p_execpath, int p_argc, char **p_argv);
};
-#ifdef DEBUG_ENABLED
+#ifdef TOOLS_ENABLED
class OS_MacOS_Embedded : public OS_MacOS {
public:
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index a15b253b4009..67df52ac3550 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -31,7 +31,7 @@
#import "os_macos.h"
#import "dir_access_macos.h"
-#ifdef DEBUG_ENABLED
+#ifdef TOOLS_ENABLED
#import "display_server_embedded.h"
#endif
#import "display_server_macos.h"
@@ -1228,7 +1228,7 @@ static void handle_interrupt(int sig) {
// MARK: - OS_MacOS_Embedded
-#ifdef DEBUG_ENABLED
+#ifdef TOOLS_ENABLED
void OS_MacOS_Embedded::run() {
CFRunLoopGetCurrent();
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index 6e9acae91136..28a0238b45e7 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -1003,7 +1003,7 @@ Vector DisplayServerWeb::get_rendering_drivers_func() {
// Clipboard
void DisplayServerWeb::update_clipboard_callback(const char *p_text) {
- String text = p_text;
+ String text = String::utf8(p_text);
#ifdef PROXY_TO_PTHREAD_ENABLED
if (!Thread::is_main_thread()) {
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 4c5215fd462a..111a08ee935e 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -407,6 +407,7 @@ def spawn_capture(sh, escape, cmd, args, env):
"dwrite",
"wbemuuid",
"ntdll",
+ "hid",
]
if env.debug_features:
@@ -786,6 +787,7 @@ def configure_mingw(env: "SConsEnvironment"):
"dwrite",
"wbemuuid",
"ntdll",
+ "hid",
]
)
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 9233ce58ee97..7cee08223bfd 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -4763,9 +4763,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
// if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned.
// The problem with the timer is that the window cannot be resized or the buttons cannot be used correctly
// if the window is not activated first. This happens because the code in the activation process runs
- // after the mouse click is handled. To address this, the timer is now used only when the window is created.
+ // after the mouse click is handled. To address this, the timer is now used only during the window creation,
+ // and only as part of the activation process. We don't want 'Input::release_pressed_events()'
+ // to be called immediately in '_process_activate_event' when the window is not yet activated,
+ // as it would reset the currently pressed keys when hiding a window, which is incorrect behavior.
windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam);
- if (windows[window_id].first_activation_done) {
+ if (windows[window_id].first_activation_done && (windows[window_id].activate_state == WA_ACTIVE || windows[window_id].activate_state == WA_CLICKACTIVE)) {
_process_activate_event(window_id);
} else {
windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 6c76e538ded9..66f1518eb106 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -53,6 +53,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -60,6 +61,7 @@
#include
#include
#include
+#include
#if defined(RD_ENABLED)
#include "servers/rendering/rendering_device.h"
@@ -2735,9 +2737,108 @@ bool OS_Windows::_test_create_rendering_device_and_gl(const String &p_display_dr
}
#endif
+using GetProcAddressType = FARPROC(__stdcall *)(HMODULE, LPCSTR);
+GetProcAddressType Original_GetProcAddress = nullptr;
+
+using HidD_GetProductStringType = BOOLEAN(__stdcall *)(HANDLE, void *, ULONG);
+HidD_GetProductStringType Original_HidD_GetProductString = nullptr;
+
+#ifndef HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER
+#define HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER 0x08
+#endif
+
+bool _hid_is_controller(HANDLE p_hid_handle) {
+ PHIDP_PREPARSED_DATA hid_preparsed = nullptr;
+ BOOLEAN preparsed_res = HidD_GetPreparsedData(p_hid_handle, &hid_preparsed);
+ if (!preparsed_res) {
+ return false;
+ }
+
+ HIDP_CAPS hid_caps = {};
+ NTSTATUS caps_res = HidP_GetCaps(hid_preparsed, &hid_caps);
+ HidD_FreePreparsedData(hid_preparsed);
+ if (caps_res != HIDP_STATUS_SUCCESS) {
+ return false;
+ }
+
+ if (hid_caps.UsagePage != HID_USAGE_PAGE_GENERIC) {
+ return false;
+ }
+
+ if (hid_caps.Usage == HID_USAGE_GENERIC_JOYSTICK || hid_caps.Usage == HID_USAGE_GENERIC_GAMEPAD || hid_caps.Usage == HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER) {
+ return true;
+ }
+
+ return false;
+}
+
+BOOLEAN __stdcall Hook_HidD_GetProductString(HANDLE p_object, void *p_buffer, ULONG p_buffer_length) {
+ constexpr const wchar_t unknown_product_string[] = L"Unknown HID Device";
+ constexpr size_t unknown_product_length = sizeof(unknown_product_string);
+
+ if (_hid_is_controller(p_object)) {
+ return HidD_GetProductString(p_object, p_buffer, p_buffer_length);
+ }
+
+ // The HID is (probably) not a controller, so we don't care about returning its actual product string.
+ // This avoids stalls on `EnumDevices` because DirectInput attempts to enumerate all HIDs, including some DACs
+ // and other devices which take too long to respond to those requests, added to the lack of a shorter timeout.
+ if (p_buffer_length >= unknown_product_length) {
+ memcpy(p_buffer, unknown_product_string, unknown_product_length);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+FARPROC __stdcall Hook_GetProcAddress(HMODULE p_module, LPCSTR p_name) {
+ if (String(p_name) == "HidD_GetProductString") {
+ return (FARPROC)(LPVOID)Hook_HidD_GetProductString;
+ }
+ if (Original_GetProcAddress) {
+ return Original_GetProcAddress(p_module, p_name);
+ }
+ return nullptr;
+}
+
+LPVOID install_iat_hook(const String &p_target, const String &p_module, const String &p_symbol, LPVOID p_hook_func) {
+ LPVOID image_base = LoadLibraryA(p_target.ascii().get_data());
+ if (image_base) {
+ PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((DWORD_PTR)image_base + ((PIMAGE_DOS_HEADER)image_base)->e_lfanew);
+ PIMAGE_IMPORT_DESCRIPTOR import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)image_base + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
+ while (import_descriptor->Name != 0) {
+ LPCSTR library_name = (LPCSTR)((DWORD_PTR)image_base + import_descriptor->Name);
+ if (String(library_name).to_lower() == p_module) {
+ PIMAGE_THUNK_DATA original_first_thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)image_base + import_descriptor->OriginalFirstThunk);
+ PIMAGE_THUNK_DATA first_thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)image_base + import_descriptor->FirstThunk);
+
+ while ((LPVOID)original_first_thunk->u1.AddressOfData != nullptr) {
+ PIMAGE_IMPORT_BY_NAME function_import = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)image_base + original_first_thunk->u1.AddressOfData);
+ if (String(function_import->Name).to_lower() == p_symbol.to_lower()) {
+ DWORD old_protect = 0;
+ VirtualProtect((LPVOID)(&first_thunk->u1.Function), 8, PAGE_READWRITE, &old_protect);
+
+ LPVOID old_func = (LPVOID)first_thunk->u1.Function;
+ first_thunk->u1.Function = (DWORD_PTR)p_hook_func;
+
+ VirtualProtect((LPVOID)(&first_thunk->u1.Function), 8, old_protect, nullptr);
+ return old_func;
+ }
+ original_first_thunk++;
+ first_thunk++;
+ }
+ }
+ import_descriptor++;
+ }
+ }
+ return nullptr;
+}
+
OS_Windows::OS_Windows(HINSTANCE _hInstance) {
hInstance = _hInstance;
+ Original_GetProcAddress = (GetProcAddressType)install_iat_hook("dinput8.dll", "kernel32.dll", "GetProcAddress", (LPVOID)Hook_GetProcAddress);
+ Original_HidD_GetProductString = (HidD_GetProductStringType)install_iat_hook("dinput8.dll", "hid.dll", "HidD_GetProductString", (LPVOID)Hook_HidD_GetProductString);
+
_init_encodings();
// Reset CWD to ensure long path is used.
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index f513b907a8e4..dce530a3ef40 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -133,8 +133,8 @@ RID PopupMenu::bind_global_menu() {
nmenu->set_item_tooltip(global_menu, index, item.tooltip);
if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
Array events = item.shortcut->get_events();
- for (int j = 0; j < events.size(); j++) {
- Ref ie = events[j];
+ for (const Variant &v : events) {
+ Ref ie = v;
if (ie.is_valid() && _set_item_accelerator(index, ie)) {
break;
}
@@ -2853,7 +2853,26 @@ void PopupMenu::_unref_shortcut(Ref p_sc) {
void PopupMenu::_shortcut_changed() {
for (int i = 0; i < items.size(); i++) {
- items.write[i].dirty = true;
+ Item &item = items.write[i];
+ item.dirty = true;
+ if (global_menu.is_valid() && !item.separator) {
+ NativeMenu *nmenu = NativeMenu::get_singleton();
+ nmenu->set_item_accelerator(global_menu, i, Key::NONE);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (const Variant &v : events) {
+ Ref ie = v;
+ if (ie.is_valid() && _set_item_accelerator(i, ie)) {
+ break;
+ }
+ }
+ if (item.shortcut_is_global) {
+ nmenu->set_item_key_callback(global_menu, i, callable_mp(this, &PopupMenu::activate_item));
+ } else {
+ nmenu->set_item_key_callback(global_menu, i, Callable());
+ }
+ }
+ }
}
control->queue_redraw();
}
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 2419a0195b25..924f4de946c6 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -4187,7 +4187,7 @@ RID RenderingDevice::render_pipeline_create(RID p_shader, FramebufferFormatID p_
ERR_FAIL_COND_V(!pipeline.driver_id, RID());
if (pipeline_cache_enabled) {
- _update_pipeline_cache();
+ update_pipeline_cache();
}
pipeline.shader = p_shader;
@@ -4275,7 +4275,7 @@ RID RenderingDevice::compute_pipeline_create(RID p_shader, const Vector RenderingDevice::_load_pipeline_cache() {
}
}
-void RenderingDevice::_update_pipeline_cache(bool p_closing) {
+void RenderingDevice::update_pipeline_cache(bool p_closing) {
_THREAD_SAFE_METHOD_
{
@@ -7331,7 +7331,7 @@ void RenderingDevice::finalize() {
}
if (pipeline_cache_enabled) {
- _update_pipeline_cache(true);
+ update_pipeline_cache(true);
driver->pipeline_cache_free();
}
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 0bee8d7de825..0d705ed81f76 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -1167,7 +1167,6 @@ class RenderingDevice : public RenderingDeviceCommons {
WorkerThreadPool::TaskID pipeline_cache_save_task = WorkerThreadPool::INVALID_TASK_ID;
Vector _load_pipeline_cache();
- void _update_pipeline_cache(bool p_closing = false);
static void _save_pipeline_cache(void *p_data);
struct ComputePipeline {
@@ -1189,6 +1188,8 @@ class RenderingDevice : public RenderingDeviceCommons {
RID compute_pipeline_create(RID p_shader, const Vector &p_specialization_constants = Vector());
bool compute_pipeline_is_valid(RID p_pipeline);
+ void update_pipeline_cache(bool p_closing = false);
+
private:
/****************/
/**** SCREEN ****/